├── .codecov.yml
├── .conform.yaml
├── .dockerignore
├── .github
├── renovate.json
└── workflows
│ ├── ci.yaml
│ └── slack-notify.yaml
├── .gitignore
├── .golangci.yml
├── .kres.yaml
├── .markdownlint.json
├── Dockerfile
├── LICENSE.txt
├── Makefile
├── README.md
├── go.mod
├── go.sum
├── hack
├── git-chglog
│ ├── CHANGELOG.tpl.md
│ └── config.yaml
├── release.sh
└── release.toml
├── proxy
├── codec.go
├── codec_test.go
├── director.go
├── doc.go
├── examples_test.go
├── handler.go
├── handler_one2many.go
├── handler_one2many_test.go
├── handler_one2one.go
├── handler_one2one_test.go
├── proxy.go
└── serverstream.go
└── testservice
├── api
└── test.proto
├── test.pb.go
├── test.proto
└── test_grpc.pb.go
/.codecov.yml:
--------------------------------------------------------------------------------
1 | # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
2 | #
3 | # Generated on 2022-06-06T17:35:55Z by kres 65530e7.
4 |
5 | codecov:
6 | require_ci_to_pass: false
7 |
8 | coverage:
9 | status:
10 | project:
11 | default:
12 | target: 50%
13 | threshold: 0.5%
14 | base: auto
15 | if_ci_failed: success
16 | patch: off
17 |
18 | comment: false
19 |
--------------------------------------------------------------------------------
/.conform.yaml:
--------------------------------------------------------------------------------
1 | # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
2 | #
3 | # Generated on 2024-05-27T14:20:53Z by kres b5844f8.
4 |
5 | policies:
6 | - type: commit
7 | spec:
8 | dco: true
9 | gpg:
10 | required: true
11 | identity:
12 | gitHubOrganization: siderolabs
13 | spellcheck:
14 | locale: US
15 | maximumOfOneCommit: true
16 | header:
17 | length: 89
18 | imperative: true
19 | case: lower
20 | invalidLastCharacters: .
21 | body:
22 | required: true
23 | conventional:
24 | types:
25 | - chore
26 | - docs
27 | - perf
28 | - refactor
29 | - style
30 | - test
31 | - release
32 | scopes:
33 | - .*
34 |
--------------------------------------------------------------------------------
/.dockerignore:
--------------------------------------------------------------------------------
1 | # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
2 | #
3 | # Generated on 2024-05-08T10:44:06Z by kres d15226e-dirty.
4 |
5 | *
6 | !proxy
7 | !testservice
8 | !go.mod
9 | !go.sum
10 | !.golangci.yml
11 | !README.md
12 | !.markdownlint.json
13 |
--------------------------------------------------------------------------------
/.github/renovate.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json",
3 | "description": "THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.",
4 | "prHeader": "Update Request | Renovate Bot",
5 | "extends": [
6 | ":dependencyDashboard",
7 | ":gitSignOff",
8 | ":semanticCommitScopeDisabled",
9 | "schedule:earlyMondays"
10 | ],
11 | "packageRules": [
12 | {
13 | "groupName": "dependencies",
14 | "matchUpdateTypes": [
15 | "major",
16 | "minor",
17 | "patch",
18 | "pin",
19 | "digest"
20 | ]
21 | },
22 | {
23 | "enabled": false,
24 | "matchFileNames": [
25 | "Dockerfile"
26 | ]
27 | },
28 | {
29 | "enabled": false,
30 | "matchFileNames": [
31 | ".github/workflows/*.yaml"
32 | ]
33 | }
34 | ],
35 | "separateMajorMinor": false
36 | }
37 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
1 | # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
2 | #
3 | # Generated on 2025-01-22T20:13:50Z by kres 3075de9.
4 |
5 | name: default
6 | concurrency:
7 | group: ${{ github.head_ref || github.run_id }}
8 | cancel-in-progress: true
9 | "on":
10 | push:
11 | branches:
12 | - main
13 | - release-*
14 | tags:
15 | - v*
16 | pull_request:
17 | branches:
18 | - main
19 | - release-*
20 | jobs:
21 | default:
22 | permissions:
23 | actions: read
24 | contents: write
25 | issues: read
26 | packages: write
27 | pull-requests: read
28 | runs-on:
29 | - self-hosted
30 | - generic
31 | if: (!startsWith(github.head_ref, 'renovate/') && !startsWith(github.head_ref, 'dependabot/'))
32 | steps:
33 | - name: gather-system-info
34 | id: system-info
35 | uses: kenchan0130/actions-system-info@v1.3.0
36 | continue-on-error: true
37 | - name: print-system-info
38 | run: |
39 | MEMORY_GB=$((${{ steps.system-info.outputs.totalmem }}/1024/1024/1024))
40 |
41 | OUTPUTS=(
42 | "CPU Core: ${{ steps.system-info.outputs.cpu-core }}"
43 | "CPU Model: ${{ steps.system-info.outputs.cpu-model }}"
44 | "Hostname: ${{ steps.system-info.outputs.hostname }}"
45 | "NodeName: ${NODE_NAME}"
46 | "Kernel release: ${{ steps.system-info.outputs.kernel-release }}"
47 | "Kernel version: ${{ steps.system-info.outputs.kernel-version }}"
48 | "Name: ${{ steps.system-info.outputs.name }}"
49 | "Platform: ${{ steps.system-info.outputs.platform }}"
50 | "Release: ${{ steps.system-info.outputs.release }}"
51 | "Total memory: ${MEMORY_GB} GB"
52 | )
53 |
54 | for OUTPUT in "${OUTPUTS[@]}";do
55 | echo "${OUTPUT}"
56 | done
57 | continue-on-error: true
58 | - name: checkout
59 | uses: actions/checkout@v4
60 | - name: Unshallow
61 | run: |
62 | git fetch --prune --unshallow
63 | - name: Set up Docker Buildx
64 | id: setup-buildx
65 | uses: docker/setup-buildx-action@v3
66 | with:
67 | driver: remote
68 | endpoint: tcp://buildkit-amd64.ci.svc.cluster.local:1234
69 | timeout-minutes: 10
70 | - name: base
71 | run: |
72 | make base
73 | - name: unit-tests
74 | run: |
75 | make unit-tests
76 | - name: unit-tests-race
77 | run: |
78 | make unit-tests-race
79 | - name: coverage
80 | uses: codecov/codecov-action@v5
81 | with:
82 | files: _out/coverage-unit-tests.txt
83 | token: ${{ secrets.CODECOV_TOKEN }}
84 | timeout-minutes: 3
85 | - name: lint
86 | run: |
87 | make lint
88 | - name: release-notes
89 | if: startsWith(github.ref, 'refs/tags/')
90 | run: |
91 | make release-notes
92 | - name: Release
93 | if: startsWith(github.ref, 'refs/tags/')
94 | uses: crazy-max/ghaction-github-release@v2
95 | with:
96 | body_path: _out/RELEASE_NOTES.md
97 | draft: "true"
98 |
--------------------------------------------------------------------------------
/.github/workflows/slack-notify.yaml:
--------------------------------------------------------------------------------
1 | # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
2 | #
3 | # Generated on 2025-01-22T20:13:50Z by kres 3075de9.
4 |
5 | name: slack-notify
6 | "on":
7 | workflow_run:
8 | workflows:
9 | - default
10 | types:
11 | - completed
12 | jobs:
13 | slack-notify:
14 | runs-on:
15 | - self-hosted
16 | - generic
17 | if: github.event.workflow_run.conclusion != 'skipped'
18 | steps:
19 | - name: Get PR number
20 | id: get-pr-number
21 | if: github.event.workflow_run.event == 'pull_request'
22 | env:
23 | GH_TOKEN: ${{ github.token }}
24 | run: |
25 | echo pull_request_number=$(gh pr view -R ${{ github.repository }} ${{ github.event.workflow_run.head_repository.owner.login }}:${{ github.event.workflow_run.head_branch }} --json number --jq .number) >> $GITHUB_OUTPUT
26 | - name: Slack Notify
27 | uses: slackapi/slack-github-action@v2
28 | with:
29 | method: chat.postMessage
30 | payload: |
31 | {
32 | "channel": "proj-talos-maintainers",
33 | "attachments": [
34 | {
35 | "color": "${{ github.event.workflow_run.conclusion == 'success' && '#2EB886' || github.event.workflow_run.conclusion == 'failure' && '#A30002' || '#FFCC00' }}",
36 | "fallback": "test",
37 | "blocks": [
38 | {
39 | "type": "section",
40 | "fields": [
41 | {
42 | "type": "mrkdwn",
43 | "text": "${{ github.event.workflow_run.event == 'pull_request' && format('*Pull Request:* {0} (`{1}`)\n<{2}/pull/{3}|{4}>', github.repository, github.ref_name, github.event.repository.html_url, steps.get-pr-number.outputs.pull_request_number, github.event.workflow_run.display_title) || format('*Build:* {0} (`{1}`)\n<{2}/commit/{3}|{4}>', github.repository, github.ref_name, github.event.repository.html_url, github.sha, github.event.workflow_run.display_title) }}"
44 | },
45 | {
46 | "type": "mrkdwn",
47 | "text": "*Status:*\n`${{ github.event.workflow_run.conclusion }}`"
48 | }
49 | ]
50 | },
51 | {
52 | "type": "section",
53 | "fields": [
54 | {
55 | "type": "mrkdwn",
56 | "text": "*Author:*\n`${{ github.actor }}`"
57 | },
58 | {
59 | "type": "mrkdwn",
60 | "text": "*Event:*\n`${{ github.event.workflow_run.event }}`"
61 | }
62 | ]
63 | },
64 | {
65 | "type": "divider"
66 | },
67 | {
68 | "type": "actions",
69 | "elements": [
70 | {
71 | "type": "button",
72 | "text": {
73 | "type": "plain_text",
74 | "text": "Logs"
75 | },
76 | "url": "${{ github.event.workflow_run.html_url }}"
77 | },
78 | {
79 | "type": "button",
80 | "text": {
81 | "type": "plain_text",
82 | "text": "Commit"
83 | },
84 | "url": "${{ github.event.repository.html_url }}/commit/${{ github.sha }}"
85 | }
86 | ]
87 | }
88 | ]
89 | }
90 | ]
91 | }
92 | token: ${{ secrets.SLACK_BOT_TOKEN }}
93 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
2 | #
3 | # Generated on 2021-03-25T21:15:43Z by kres 424ae88-dirty.
4 |
5 | _out
6 |
--------------------------------------------------------------------------------
/.golangci.yml:
--------------------------------------------------------------------------------
1 | # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
2 | #
3 | # Generated on 2025-01-22T20:13:50Z by kres 3075de9.
4 |
5 | # options for analysis running
6 | run:
7 | timeout: 10m
8 | issues-exit-code: 1
9 | tests: true
10 | build-tags: [ ]
11 | modules-download-mode: readonly
12 |
13 | # output configuration options
14 | output:
15 | formats:
16 | - format: colored-line-number
17 | path: stdout
18 | print-issued-lines: true
19 | print-linter-name: true
20 | path-prefix: ""
21 |
22 | # all available settings of specific linters
23 | linters-settings:
24 | dogsled:
25 | max-blank-identifiers: 2
26 | dupl:
27 | threshold: 150
28 | errcheck:
29 | check-type-assertions: true
30 | check-blank: true
31 | exhaustive:
32 | default-signifies-exhaustive: false
33 | gci:
34 | sections:
35 | - standard # Standard section: captures all standard packages.
36 | - default # Default section: contains all imports that could not be matched to another section type.
37 | - localmodule # Imports from the same module.
38 | gocognit:
39 | min-complexity: 30
40 | nestif:
41 | min-complexity: 5
42 | goconst:
43 | min-len: 3
44 | min-occurrences: 3
45 | gocritic:
46 | disabled-checks: [ ]
47 | gocyclo:
48 | min-complexity: 20
49 | godot:
50 | scope: declarations
51 | gofmt:
52 | simplify: true
53 | gomodguard: { }
54 | govet:
55 | enable-all: true
56 | lll:
57 | line-length: 200
58 | tab-width: 4
59 | misspell:
60 | locale: US
61 | ignore-words: [ ]
62 | nakedret:
63 | max-func-lines: 30
64 | prealloc:
65 | simple: true
66 | range-loops: true # Report preallocation suggestions on range loops, true by default
67 | for-loops: false # Report preallocation suggestions on for loops, false by default
68 | nolintlint:
69 | allow-unused: false
70 | allow-no-explanation: [ ]
71 | require-explanation: false
72 | require-specific: true
73 | rowserrcheck: { }
74 | testpackage: { }
75 | unparam:
76 | check-exported: false
77 | unused:
78 | local-variables-are-used: false
79 | whitespace:
80 | multi-if: false # Enforces newlines (or comments) after every multi-line if statement
81 | multi-func: false # Enforces newlines (or comments) after every multi-line function signature
82 | wsl:
83 | strict-append: true
84 | allow-assign-and-call: true
85 | allow-multiline-assign: true
86 | allow-cuddle-declarations: false
87 | allow-trailing-comment: false
88 | force-case-trailing-whitespace: 0
89 | force-err-cuddling: false
90 | allow-separated-leading-comment: false
91 | gofumpt:
92 | extra-rules: false
93 | cyclop:
94 | # the maximal code complexity to report
95 | max-complexity: 20
96 | depguard:
97 | rules:
98 | prevent_unmaintained_packages:
99 | list-mode: lax # allow unless explicitly denied
100 | files:
101 | - $all
102 | deny:
103 | - pkg: io/ioutil
104 | desc: "replaced by io and os packages since Go 1.16: https://tip.golang.org/doc/go1.16#ioutil"
105 |
106 | linters:
107 | enable-all: true
108 | disable-all: false
109 | fast: false
110 | disable:
111 | - exhaustruct
112 | - err113
113 | - forbidigo
114 | - funlen
115 | - gochecknoglobals
116 | - gochecknoinits
117 | - godox
118 | - gomoddirectives
119 | - gosec
120 | - inamedparam
121 | - ireturn
122 | - mnd
123 | - nestif
124 | - nonamedreturns
125 | - paralleltest
126 | - tagalign
127 | - tagliatelle
128 | - thelper
129 | - varnamelen
130 | - wrapcheck
131 | - testifylint # complains about our assert recorder and has a number of false positives for assert.Greater(t, thing, 1)
132 | - protogetter # complains about us using Value field on typed spec, instead of GetValue which has a different signature
133 | - perfsprint # complains about us using fmt.Sprintf in non-performance critical code, updating just kres took too long
134 | - goimports # same as gci
135 | - musttag # seems to be broken - goes into imported libraries and reports issues there
136 | - exportloopref # WARN The linter 'exportloopref' is deprecated (since v1.60.2) due to: Since Go1.22 (loopvar) this linter is no longer relevant. Replaced by copyloopvar.
137 |
138 | issues:
139 | exclude: [ ]
140 | exclude-rules: [ ]
141 | exclude-use-default: false
142 | exclude-case-sensitive: false
143 | max-issues-per-linter: 10
144 | max-same-issues: 3
145 | new: false
146 | uniq-by-line: true
147 |
148 | severity:
149 | default-severity: error
150 | case-sensitive: false
151 |
--------------------------------------------------------------------------------
/.kres.yaml:
--------------------------------------------------------------------------------
1 | kind: common.Repository
2 | spec:
3 | conformLicenseCheck: false
4 | licenses:
5 | - enabled: false
6 | licenseChecks: []
7 | ---
8 | kind: golang.Generate
9 | spec:
10 | baseSpecPath: /testservice
11 | specs:
12 | - source: testservice/api/test.proto
13 |
--------------------------------------------------------------------------------
/.markdownlint.json:
--------------------------------------------------------------------------------
1 | # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
2 | #
3 | # Generated on 2021-03-25T21:15:43Z by kres 424ae88-dirty.
4 |
5 | {
6 | "MD013": false,
7 | "MD033": false,
8 | "default": true
9 | }
10 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # syntax = docker/dockerfile-upstream:1.12.1-labs
2 |
3 | # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
4 | #
5 | # Generated on 2025-01-22T20:13:50Z by kres 3075de9.
6 |
7 | ARG TOOLCHAIN
8 |
9 | # runs markdownlint
10 | FROM docker.io/oven/bun:1.1.43-alpine AS lint-markdown
11 | WORKDIR /src
12 | RUN bun i markdownlint-cli@0.43.0 sentences-per-line@0.3.0
13 | COPY .markdownlint.json .
14 | COPY ./README.md ./README.md
15 | RUN bunx markdownlint --ignore "CHANGELOG.md" --ignore "**/node_modules/**" --ignore '**/hack/chglog/**' --rules sentences-per-line .
16 |
17 | # collects proto specs
18 | FROM scratch AS proto-specs
19 | ADD testservice/api/test.proto /testservice/
20 |
21 | # base toolchain image
22 | FROM --platform=${BUILDPLATFORM} ${TOOLCHAIN} AS toolchain
23 | RUN apk --update --no-cache add bash curl build-base protoc protobuf-dev
24 |
25 | # build tools
26 | FROM --platform=${BUILDPLATFORM} toolchain AS tools
27 | ENV GO111MODULE=on
28 | ARG CGO_ENABLED
29 | ENV CGO_ENABLED=${CGO_ENABLED}
30 | ARG GOTOOLCHAIN
31 | ENV GOTOOLCHAIN=${GOTOOLCHAIN}
32 | ARG GOEXPERIMENT
33 | ENV GOEXPERIMENT=${GOEXPERIMENT}
34 | ENV GOPATH=/go
35 | ARG GOIMPORTS_VERSION
36 | RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go install golang.org/x/tools/cmd/goimports@v${GOIMPORTS_VERSION}
37 | RUN mv /go/bin/goimports /bin
38 | ARG PROTOBUF_GO_VERSION
39 | RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go install google.golang.org/protobuf/cmd/protoc-gen-go@v${PROTOBUF_GO_VERSION}
40 | RUN mv /go/bin/protoc-gen-go /bin
41 | ARG GRPC_GO_VERSION
42 | RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v${GRPC_GO_VERSION}
43 | RUN mv /go/bin/protoc-gen-go-grpc /bin
44 | ARG GRPC_GATEWAY_VERSION
45 | RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@v${GRPC_GATEWAY_VERSION}
46 | RUN mv /go/bin/protoc-gen-grpc-gateway /bin
47 | ARG DEEPCOPY_VERSION
48 | RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go install github.com/siderolabs/deep-copy@${DEEPCOPY_VERSION} \
49 | && mv /go/bin/deep-copy /bin/deep-copy
50 | ARG GOLANGCILINT_VERSION
51 | RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go install github.com/golangci/golangci-lint/cmd/golangci-lint@${GOLANGCILINT_VERSION} \
52 | && mv /go/bin/golangci-lint /bin/golangci-lint
53 | RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg go install golang.org/x/vuln/cmd/govulncheck@latest \
54 | && mv /go/bin/govulncheck /bin/govulncheck
55 | ARG GOFUMPT_VERSION
56 | RUN go install mvdan.cc/gofumpt@${GOFUMPT_VERSION} \
57 | && mv /go/bin/gofumpt /bin/gofumpt
58 |
59 | # tools and sources
60 | FROM tools AS base
61 | WORKDIR /src
62 | COPY go.mod go.mod
63 | COPY go.sum go.sum
64 | RUN cd .
65 | RUN --mount=type=cache,target=/go/pkg go mod download
66 | RUN --mount=type=cache,target=/go/pkg go mod verify
67 | COPY ./proxy ./proxy
68 | COPY ./testservice ./testservice
69 | RUN --mount=type=cache,target=/go/pkg go list -mod=readonly all >/dev/null
70 |
71 | # runs protobuf compiler
72 | FROM tools AS proto-compile
73 | COPY --from=proto-specs / /
74 | RUN protoc -I/testservice --go_out=paths=source_relative:/testservice --go-grpc_out=paths=source_relative:/testservice /testservice/test.proto
75 | RUN rm /testservice/test.proto
76 | RUN goimports -w -local github.com/siderolabs/grpc-proxy /testservice
77 | RUN gofumpt -w /testservice
78 |
79 | # runs gofumpt
80 | FROM base AS lint-gofumpt
81 | RUN FILES="$(gofumpt -l .)" && test -z "${FILES}" || (echo -e "Source code is not formatted with 'gofumpt -w .':\n${FILES}"; exit 1)
82 |
83 | # runs golangci-lint
84 | FROM base AS lint-golangci-lint
85 | WORKDIR /src
86 | COPY .golangci.yml .
87 | ENV GOGC=50
88 | RUN golangci-lint config verify --config .golangci.yml
89 | RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/root/.cache/golangci-lint --mount=type=cache,target=/go/pkg golangci-lint run --config .golangci.yml
90 |
91 | # runs govulncheck
92 | FROM base AS lint-govulncheck
93 | WORKDIR /src
94 | RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg govulncheck ./...
95 |
96 | # runs unit-tests with race detector
97 | FROM base AS unit-tests-race
98 | WORKDIR /src
99 | ARG TESTPKGS
100 | RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg --mount=type=cache,target=/tmp CGO_ENABLED=1 go test -v -race -count 1 ${TESTPKGS}
101 |
102 | # runs unit-tests
103 | FROM base AS unit-tests-run
104 | WORKDIR /src
105 | ARG TESTPKGS
106 | RUN --mount=type=cache,target=/root/.cache/go-build --mount=type=cache,target=/go/pkg --mount=type=cache,target=/tmp go test -v -covermode=atomic -coverprofile=coverage.txt -coverpkg=${TESTPKGS} -count 1 ${TESTPKGS}
107 |
108 | # cleaned up specs and compiled versions
109 | FROM scratch AS generate
110 | COPY --from=proto-compile /testservice/ /testservice/
111 |
112 | FROM scratch AS unit-tests
113 | COPY --from=unit-tests-run /src/coverage.txt /coverage-unit-tests.txt
114 |
115 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
--------------------------------------------------------------------------------
/Makefile:
--------------------------------------------------------------------------------
1 | # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
2 | #
3 | # Generated on 2025-01-22T20:13:50Z by kres 3075de9.
4 |
5 | # common variables
6 |
7 | SHA := $(shell git describe --match=none --always --abbrev=8 --dirty)
8 | TAG := $(shell git describe --tag --always --dirty --match v[0-9]\*)
9 | ABBREV_TAG := $(shell git describe --tags >/dev/null 2>/dev/null && git describe --tag --always --match v[0-9]\* --abbrev=0 || echo 'undefined')
10 | BRANCH := $(shell git rev-parse --abbrev-ref HEAD)
11 | ARTIFACTS := _out
12 | IMAGE_TAG ?= $(TAG)
13 | OPERATING_SYSTEM := $(shell uname -s | tr '[:upper:]' '[:lower:]')
14 | GOARCH := $(shell uname -m | sed 's/x86_64/amd64/' | sed 's/aarch64/arm64/')
15 | WITH_DEBUG ?= false
16 | WITH_RACE ?= false
17 | REGISTRY ?= ghcr.io
18 | USERNAME ?= siderolabs
19 | REGISTRY_AND_USERNAME ?= $(REGISTRY)/$(USERNAME)
20 | PROTOBUF_GO_VERSION ?= 1.36.2
21 | GRPC_GO_VERSION ?= 1.5.1
22 | GRPC_GATEWAY_VERSION ?= 2.25.1
23 | VTPROTOBUF_VERSION ?= 0.6.0
24 | GOIMPORTS_VERSION ?= 0.29.0
25 | DEEPCOPY_VERSION ?= v0.5.6
26 | GOLANGCILINT_VERSION ?= v1.63.4
27 | GOFUMPT_VERSION ?= v0.7.0
28 | GO_VERSION ?= 1.23.4
29 | GO_BUILDFLAGS ?=
30 | GO_LDFLAGS ?=
31 | CGO_ENABLED ?= 0
32 | GOTOOLCHAIN ?= local
33 | TESTPKGS ?= ./...
34 | KRES_IMAGE ?= ghcr.io/siderolabs/kres:latest
35 | CONFORMANCE_IMAGE ?= ghcr.io/siderolabs/conform:latest
36 |
37 | # docker build settings
38 |
39 | BUILD := docker buildx build
40 | PLATFORM ?= linux/amd64
41 | PROGRESS ?= auto
42 | PUSH ?= false
43 | CI_ARGS ?=
44 | BUILDKIT_MULTI_PLATFORM ?=
45 | COMMON_ARGS = --file=Dockerfile
46 | COMMON_ARGS += --provenance=false
47 | COMMON_ARGS += --progress=$(PROGRESS)
48 | COMMON_ARGS += --platform=$(PLATFORM)
49 | COMMON_ARGS += --build-arg=BUILDKIT_MULTI_PLATFORM=$(BUILDKIT_MULTI_PLATFORM)
50 | COMMON_ARGS += --push=$(PUSH)
51 | COMMON_ARGS += --build-arg=ARTIFACTS="$(ARTIFACTS)"
52 | COMMON_ARGS += --build-arg=SHA="$(SHA)"
53 | COMMON_ARGS += --build-arg=TAG="$(TAG)"
54 | COMMON_ARGS += --build-arg=ABBREV_TAG="$(ABBREV_TAG)"
55 | COMMON_ARGS += --build-arg=USERNAME="$(USERNAME)"
56 | COMMON_ARGS += --build-arg=REGISTRY="$(REGISTRY)"
57 | COMMON_ARGS += --build-arg=TOOLCHAIN="$(TOOLCHAIN)"
58 | COMMON_ARGS += --build-arg=CGO_ENABLED="$(CGO_ENABLED)"
59 | COMMON_ARGS += --build-arg=GO_BUILDFLAGS="$(GO_BUILDFLAGS)"
60 | COMMON_ARGS += --build-arg=GO_LDFLAGS="$(GO_LDFLAGS)"
61 | COMMON_ARGS += --build-arg=GOTOOLCHAIN="$(GOTOOLCHAIN)"
62 | COMMON_ARGS += --build-arg=GOEXPERIMENT="$(GOEXPERIMENT)"
63 | COMMON_ARGS += --build-arg=PROTOBUF_GO_VERSION="$(PROTOBUF_GO_VERSION)"
64 | COMMON_ARGS += --build-arg=GRPC_GO_VERSION="$(GRPC_GO_VERSION)"
65 | COMMON_ARGS += --build-arg=GRPC_GATEWAY_VERSION="$(GRPC_GATEWAY_VERSION)"
66 | COMMON_ARGS += --build-arg=VTPROTOBUF_VERSION="$(VTPROTOBUF_VERSION)"
67 | COMMON_ARGS += --build-arg=GOIMPORTS_VERSION="$(GOIMPORTS_VERSION)"
68 | COMMON_ARGS += --build-arg=DEEPCOPY_VERSION="$(DEEPCOPY_VERSION)"
69 | COMMON_ARGS += --build-arg=GOLANGCILINT_VERSION="$(GOLANGCILINT_VERSION)"
70 | COMMON_ARGS += --build-arg=GOFUMPT_VERSION="$(GOFUMPT_VERSION)"
71 | COMMON_ARGS += --build-arg=TESTPKGS="$(TESTPKGS)"
72 | TOOLCHAIN ?= docker.io/golang:1.23-alpine
73 |
74 | # help menu
75 |
76 | export define HELP_MENU_HEADER
77 | # Getting Started
78 |
79 | To build this project, you must have the following installed:
80 |
81 | - git
82 | - make
83 | - docker (19.03 or higher)
84 |
85 | ## Creating a Builder Instance
86 |
87 | The build process makes use of experimental Docker features (buildx).
88 | To enable experimental features, add 'experimental: "true"' to '/etc/docker/daemon.json' on
89 | Linux or enable experimental features in Docker GUI for Windows or Mac.
90 |
91 | To create a builder instance, run:
92 |
93 | docker buildx create --name local --use
94 |
95 | If running builds that needs to be cached aggresively create a builder instance with the following:
96 |
97 | docker buildx create --name local --use --config=config.toml
98 |
99 | config.toml contents:
100 |
101 | [worker.oci]
102 | gc = true
103 | gckeepstorage = 50000
104 |
105 | [[worker.oci.gcpolicy]]
106 | keepBytes = 10737418240
107 | keepDuration = 604800
108 | filters = [ "type==source.local", "type==exec.cachemount", "type==source.git.checkout"]
109 | [[worker.oci.gcpolicy]]
110 | all = true
111 | keepBytes = 53687091200
112 |
113 | If you already have a compatible builder instance, you may use that instead.
114 |
115 | ## Artifacts
116 |
117 | All artifacts will be output to ./$(ARTIFACTS). Images will be tagged with the
118 | registry "$(REGISTRY)", username "$(USERNAME)", and a dynamic tag (e.g. $(IMAGE):$(IMAGE_TAG)).
119 | The registry and username can be overridden by exporting REGISTRY, and USERNAME
120 | respectively.
121 |
122 | endef
123 |
124 | ifneq (, $(filter $(WITH_RACE), t true TRUE y yes 1))
125 | GO_BUILDFLAGS += -race
126 | CGO_ENABLED := 1
127 | GO_LDFLAGS += -linkmode=external -extldflags '-static'
128 | endif
129 |
130 | ifneq (, $(filter $(WITH_DEBUG), t true TRUE y yes 1))
131 | GO_BUILDFLAGS += -tags sidero.debug
132 | else
133 | GO_LDFLAGS += -s
134 | endif
135 |
136 | all: unit-tests lint
137 |
138 | $(ARTIFACTS): ## Creates artifacts directory.
139 | @mkdir -p $(ARTIFACTS)
140 |
141 | .PHONY: clean
142 | clean: ## Cleans up all artifacts.
143 | @rm -rf $(ARTIFACTS)
144 |
145 | target-%: ## Builds the specified target defined in the Dockerfile. The build result will only remain in the build cache.
146 | @$(BUILD) --target=$* $(COMMON_ARGS) $(TARGET_ARGS) $(CI_ARGS) .
147 |
148 | registry-%: ## Builds the specified target defined in the Dockerfile and the output is an image. The image is pushed to the registry if PUSH=true.
149 | @$(MAKE) target-$* TARGET_ARGS="--tag=$(REGISTRY)/$(USERNAME)/$(IMAGE_NAME):$(IMAGE_TAG)" BUILDKIT_MULTI_PLATFORM=1
150 |
151 | local-%: ## Builds the specified target defined in the Dockerfile using the local output type. The build result will be output to the specified local destination.
152 | @$(MAKE) target-$* TARGET_ARGS="--output=type=local,dest=$(DEST) $(TARGET_ARGS)"
153 | @PLATFORM=$(PLATFORM) DEST=$(DEST) bash -c '\
154 | for platform in $$(tr "," "\n" <<< "$$PLATFORM"); do \
155 | directory="$${platform//\//_}"; \
156 | if [[ -d "$$DEST/$$directory" ]]; then \
157 | echo $$platform; \
158 | mv -f "$$DEST/$$directory/"* $$DEST; \
159 | rmdir "$$DEST/$$directory/"; \
160 | fi; \
161 | done'
162 |
163 | generate: ## Generate .proto definitions.
164 | @$(MAKE) local-$@ DEST=./
165 |
166 | lint-golangci-lint: ## Runs golangci-lint linter.
167 | @$(MAKE) target-$@
168 |
169 | lint-gofumpt: ## Runs gofumpt linter.
170 | @$(MAKE) target-$@
171 |
172 | .PHONY: fmt
173 | fmt: ## Formats the source code
174 | @docker run --rm -it -v $(PWD):/src -w /src golang:$(GO_VERSION) \
175 | bash -c "export GOTOOLCHAIN=local; \
176 | export GO111MODULE=on; export GOPROXY=https://proxy.golang.org; \
177 | go install mvdan.cc/gofumpt@$(GOFUMPT_VERSION) && \
178 | gofumpt -w ."
179 |
180 | lint-govulncheck: ## Runs govulncheck linter.
181 | @$(MAKE) target-$@
182 |
183 | .PHONY: base
184 | base: ## Prepare base toolchain
185 | @$(MAKE) target-$@
186 |
187 | .PHONY: unit-tests
188 | unit-tests: ## Performs unit tests
189 | @$(MAKE) local-$@ DEST=$(ARTIFACTS)
190 |
191 | .PHONY: unit-tests-race
192 | unit-tests-race: ## Performs unit tests with race detection enabled.
193 | @$(MAKE) target-$@
194 |
195 | .PHONY: lint-markdown
196 | lint-markdown: ## Runs markdownlint.
197 | @$(MAKE) target-$@
198 |
199 | .PHONY: lint
200 | lint: lint-golangci-lint lint-gofumpt lint-govulncheck lint-markdown ## Run all linters for the project.
201 |
202 | .PHONY: rekres
203 | rekres:
204 | @docker pull $(KRES_IMAGE)
205 | @docker run --rm --net=host --user $(shell id -u):$(shell id -g) -v $(PWD):/src -w /src -e GITHUB_TOKEN $(KRES_IMAGE)
206 |
207 | .PHONY: help
208 | help: ## This help menu.
209 | @echo "$$HELP_MENU_HEADER"
210 | @grep -E '^[a-zA-Z%_-]+:.*?## .*$$' $(MAKEFILE_LIST) | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
211 |
212 | .PHONY: release-notes
213 | release-notes: $(ARTIFACTS)
214 | @ARTIFACTS=$(ARTIFACTS) ./hack/release.sh $@ $(ARTIFACTS)/RELEASE_NOTES.md $(TAG)
215 |
216 | .PHONY: conformance
217 | conformance:
218 | @docker pull $(CONFORMANCE_IMAGE)
219 | @docker run --rm -it -v $(PWD):/src -w /src $(CONFORMANCE_IMAGE) enforce
220 |
221 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # gRPC Proxy
2 |
3 | [](https://goreportcard.com/report/github.com/talos-systems/grpc-proxy)
4 | [](https://godoc.org/github.com/talos-systems/grpc-proxy)
5 | [](LICENSE)
6 |
7 | [gRPC Go](https://github.com/grpc/grpc-go) Proxy server
8 |
9 | ## Project Goal
10 |
11 | Build a transparent reverse proxy for gRPC targets that will make it easy to expose gRPC services
12 | over the Internet.
13 |
14 | This includes:
15 |
16 | * no needed knowledge of the semantics of requests exchanged in the call (independent rollouts)
17 | * easy declarative definition of backends and their mappings to frontends
18 | * simple round-robin load balancing of inbound requests from a single connection to multiple backends
19 |
20 | ## Proxying Modes
21 |
22 | There are two proxying modes supported:
23 |
24 | * one to one: in this mode data passed back and forth is transmitted as is without any modifications;
25 | * one to many: one client connection gets mapped into multiple upstream connections, results might be aggregated
26 | (for unary calls), errors translated into response messages; this mode requires a special layout of protobuf messages.
27 |
28 | ## Proxy Handler
29 |
30 | The package [`proxy`](proxy/) contains a generic gRPC reverse proxy handler that allows a gRPC server not to
31 | know about method names and their request/response data types.
32 | Please consult the package documentation.
33 | Here you can find an example usage.
34 |
35 | First, define `Backend` implementation to identify specific upstream.
36 | For one to one proxying, `SingleBackend` might be used:
37 |
38 | ```go
39 | conn, err := grpc.NewClient(
40 | "api-service.staging.svc.local",
41 | grpc.WithDefaultCallOptions(grpc.ForceCodec(proxy.Codec())),
42 | )
43 | if err != nil {
44 | log.Fatal(err)
45 | }
46 |
47 | backend := &proxy.SingleBackend{
48 | GetConn: func(ctx context.Context) (context.Context, *grpc.ClientConn, error) {
49 | md, _ := metadata.FromIncomingContext(ctx)
50 |
51 | // Copy the inbound metadata explicitly.
52 | outCtx := metadata.NewOutgoingContext(ctx, md.Copy())
53 |
54 | return outCtx, conn, nil
55 | },
56 | }
57 | ```
58 |
59 | Defining a `StreamDirector` that decides where (if at all) to send the request
60 |
61 | ```go
62 | director = func(ctx context.Context, fullMethodName string) (proxy.Mode, []proxy.Backend, error) {
63 | // Make sure we never forward internal services.
64 | if strings.HasPrefix(fullMethodName, "/com.example.internal.") {
65 | return proxy.One2One, nil, status.Errorf(codes.Unimplemented, "Unknown method")
66 | }
67 |
68 | md, ok := metadata.FromIncomingContext(ctx)
69 |
70 | if ok {
71 | // Decide on which backend to dial
72 | if val, exists := md[":authority"]; exists && val[0] == "staging.api.example.com" {
73 | return proxy.One2One, []proxy.Backend{stagingBackend}, nil
74 | } else if val, exists := md[":authority"]; exists && val[0] == "api.example.com" {
75 | return proxy.One2One, []proxy.Backend{prodBackend}, nil
76 | }
77 | }
78 |
79 | return proxy.One2One, nil, status.Errorf(codes.Unimplemented, "Unknown method")
80 | }
81 | ```
82 |
83 | Then you need to register it with a `grpc.Server`.
84 | The server may have other handlers that will be served locally:
85 |
86 | ```go
87 | server := grpc.NewServer(
88 | grpc.ForceServerCodec(proxy.Codec()),
89 | grpc.UnknownServiceHandler(
90 | proxy.TransparentHandler(director),
91 | proxy.WithMode(proxy.One2One),
92 | ))
93 | pb_test.RegisterTestServiceServer(server, &testImpl{})
94 | ```
95 |
96 | ## One to Many Proxying
97 |
98 | In one to many proxying mode, it's critical to identify source of each message proxied back from the upstreams.
99 | Also upstream error shouldn't fail whole request and instead return errors as messages back.
100 | In order to achieve this goal, protobuf response message should follow the same structure:
101 |
102 | 1. Every response should be `repeated` list of response messages so that responses from multiple upstreams might be
103 | concatenated to build a combined response from all the upstreams.
104 |
105 | 2. Response should contain common metadata fields which allow grpc-proxy to inject source information and error information
106 | into response.
107 |
108 | ## Talks
109 |
110 | * "Transparent gRPC proxy in Go" at [GopherCon Russia 2021](https://www.gophercon-russia.ru/) [slides](https://speakerdeck.com/smira/transparent-grpc-gateway-in-go)
111 |
112 | ## History
113 |
114 | This is a fork of awesome [mwitkow/grpc-proxy](https://github.com/mwitkow/grpc-proxy) package with added support
115 | for one to many proxying.
116 |
117 | ## License
118 |
119 | `grpc-proxy` is released under the Apache 2.0 license.
120 | See [LICENSE.txt](LICENSE.txt).
121 |
--------------------------------------------------------------------------------
/go.mod:
--------------------------------------------------------------------------------
1 | module github.com/siderolabs/grpc-proxy
2 |
3 | go 1.22.3
4 |
5 | require (
6 | github.com/golang/protobuf v1.5.4
7 | github.com/hashicorp/go-multierror v1.1.1
8 | github.com/jhump/grpctunnel v0.3.0
9 | github.com/stretchr/testify v1.10.0
10 | google.golang.org/grpc v1.69.4
11 | google.golang.org/protobuf v1.36.3
12 | )
13 |
14 | require (
15 | github.com/davecgh/go-spew v1.1.1 // indirect
16 | github.com/fullstorydev/grpchan v1.1.1 // indirect
17 | github.com/hashicorp/errwrap v1.1.0 // indirect
18 | github.com/pmezard/go-difflib v1.0.0 // indirect
19 | golang.org/x/net v0.34.0 // indirect
20 | golang.org/x/sys v0.29.0 // indirect
21 | golang.org/x/text v0.21.0 // indirect
22 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250122153221-138b5a5a4fd4 // indirect
23 | gopkg.in/yaml.v3 v3.0.1 // indirect
24 | )
25 |
26 | retract v0.5.0
27 |
--------------------------------------------------------------------------------
/go.sum:
--------------------------------------------------------------------------------
1 | cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
2 | github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
3 | github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
4 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
5 | github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
6 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
7 | github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
8 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
9 | github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
10 | github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
11 | github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
12 | github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
13 | github.com/fullstorydev/grpchan v1.1.1 h1:heQqIJlAv5Cnks9a70GRL2EJke6QQoUB25VGR6TZQas=
14 | github.com/fullstorydev/grpchan v1.1.1/go.mod h1:f4HpiV8V6htfY/K44GWV1ESQzHBTq7DinhzqQ95lpgc=
15 | github.com/go-logr/logr v1.4.2 h1:6pFjapn8bFcIbiKo3XT4j/BhANplGihG6tvd+8rYgrY=
16 | github.com/go-logr/logr v1.4.2/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
17 | github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
18 | github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
19 | github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
20 | github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A=
21 | github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
22 | github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
23 | github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
24 | github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
25 | github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
26 | github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w=
27 | github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0=
28 | github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
29 | github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
30 | github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
31 | github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
32 | github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
33 | github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
34 | github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
35 | github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
36 | github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
37 | github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
38 | github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
39 | github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
40 | github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
41 | github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
42 | github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
43 | github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
44 | github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
45 | github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I=
46 | github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4=
47 | github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo=
48 | github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM=
49 | github.com/jhump/gopoet v0.0.0-20190322174617-17282ff210b3/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI=
50 | github.com/jhump/gopoet v0.1.0/go.mod h1:me9yfT6IJSlOL3FCfrg+L6yzUEZ+5jW6WHt4Sk+UPUI=
51 | github.com/jhump/goprotoc v0.5.0/go.mod h1:VrbvcYrQOrTi3i0Vf+m+oqQWk9l72mjkJCYo7UvLHRQ=
52 | github.com/jhump/grpctunnel v0.3.0 h1:itddWDKl7J4CeW4nzY3S/a1s7mPZUb8UtUzEhc/R8mg=
53 | github.com/jhump/grpctunnel v0.3.0/go.mod h1:dn5zls1F+1ftPMkbh4kVTVgGuY5t/v3ZgdjtnSMC3f4=
54 | github.com/jhump/protoreflect v1.11.0 h1:bvACHUD1Ua/3VxY4aAMpItKMhhwbimlKFJKsLsVgDjU=
55 | github.com/jhump/protoreflect v1.11.0/go.mod h1:U7aMIjN0NWq9swDP7xDdoMfRHb35uiuTd3Z9nFXJf5E=
56 | github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
57 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
58 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
59 | github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
60 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
61 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
62 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
63 | github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
64 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
65 | github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
66 | github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
67 | github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
68 | go.opentelemetry.io/otel v1.31.0 h1:NsJcKPIW0D0H3NgzPDHmo0WW6SptzPdqg/L1zsIm2hY=
69 | go.opentelemetry.io/otel v1.31.0/go.mod h1:O0C14Yl9FgkjqcCZAsE053C13OaddMYr/hz6clDkEJE=
70 | go.opentelemetry.io/otel/metric v1.31.0 h1:FSErL0ATQAmYHUIzSezZibnyVlft1ybhy4ozRPcF2fE=
71 | go.opentelemetry.io/otel/metric v1.31.0/go.mod h1:C3dEloVbLuYoX41KpmAhOqNriGbA+qqH6PQ5E5mUfnY=
72 | go.opentelemetry.io/otel/sdk v1.31.0 h1:xLY3abVHYZ5HSfOg3l2E5LUj2Cwva5Y7yGxnSW9H5Gk=
73 | go.opentelemetry.io/otel/sdk v1.31.0/go.mod h1:TfRbMdhvxIIr/B2N2LQW2S5v9m3gOQ/08KsbbO5BPT0=
74 | go.opentelemetry.io/otel/sdk/metric v1.31.0 h1:i9hxxLJF/9kkvfHppyLL55aW7iIJz4JjxTeYusH7zMc=
75 | go.opentelemetry.io/otel/sdk/metric v1.31.0/go.mod h1:CRInTMVvNhUKgSAMbKyTMxqOBC0zgyxzW55lZzX43Y8=
76 | go.opentelemetry.io/otel/trace v1.31.0 h1:ffjsj1aRouKewfr85U2aGagJ46+MvodynlQ1HYdmJys=
77 | go.opentelemetry.io/otel/trace v1.31.0/go.mod h1:TXZkRk7SM2ZQLtR6eoAWQFIHPvzQ06FJAsO1tJg480A=
78 | golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
79 | golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
80 | golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
81 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
82 | golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU=
83 | golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc=
84 | golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
85 | golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
86 | golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
87 | golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
88 | golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
89 | golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA=
90 | golang.org/x/net v0.34.0 h1:Mb7Mrk043xzHgnRM88suvJFwzVrRfHEHJEl5/71CKw0=
91 | golang.org/x/net v0.34.0/go.mod h1:di0qlW3YNM5oh6GqDGQr92MyTozJPmybPK4Ev/Gm31k=
92 | golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
93 | golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
94 | golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
95 | golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
96 | golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
97 | golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ=
98 | golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
99 | golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
100 | golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
101 | golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
102 | golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
103 | golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
104 | golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
105 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
106 | golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
107 | golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo=
108 | golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
109 | golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
110 | golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
111 | golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY=
112 | golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
113 | golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q=
114 | golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
115 | google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
116 | google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
117 | google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc=
118 | google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc=
119 | google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo=
120 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250122153221-138b5a5a4fd4 h1:yrTuav+chrF0zF/joFGICKTzYv7mh/gr9AgEXrVU8ao=
121 | google.golang.org/genproto/googleapis/rpc v0.0.0-20250122153221-138b5a5a4fd4/go.mod h1:+2Yz8+CLJbIfL9z73EW45avw8Lmge3xVElCP9zEKi50=
122 | google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
123 | google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg=
124 | google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY=
125 | google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
126 | google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
127 | google.golang.org/grpc v1.69.4 h1:MF5TftSMkd8GLw/m0KM6V8CMOCY6NZ1NQDPGFgbTt4A=
128 | google.golang.org/grpc v1.69.4/go.mod h1:vyjdE6jLBI76dgpDojsFGNaHlxdjXN9ghpnd2o7JGZ4=
129 | google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
130 | google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
131 | google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
132 | google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE=
133 | google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo=
134 | google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
135 | google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
136 | google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU=
137 | google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c=
138 | google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
139 | google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
140 | google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
141 | google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
142 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
143 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
144 | gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
145 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
146 | gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
147 | gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
148 | gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
149 | honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
150 | honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
151 |
--------------------------------------------------------------------------------
/hack/git-chglog/CHANGELOG.tpl.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {{ range .Versions }}
6 |
7 | ## {{ if .Tag.Previous }}[{{ .Tag.Name }}]({{ $.Info.RepositoryURL }}/compare/{{ .Tag.Previous.Name }}...{{ .Tag.Name }}){{ else }}{{ .Tag.Name }}{{ end }} ({{ datetime "2006-01-02" .Tag.Date }})
8 |
9 | {{ range .CommitGroups -}}
10 | ### {{ .Title }}
11 |
12 | {{ range .Commits -}}
13 | * {{ if .Scope }}**{{ .Scope }}:** {{ end }}{{ .Subject }}
14 | {{ end }}
15 | {{ end -}}
16 |
17 | {{- if .NoteGroups -}}
18 | {{ range .NoteGroups -}}
19 | ### {{ .Title }}
20 |
21 | {{ range .Notes }}
22 | {{ .Body }}
23 | {{ end }}
24 | {{ end -}}
25 | {{ end -}}
26 | {{ end -}}
27 |
--------------------------------------------------------------------------------
/hack/git-chglog/config.yaml:
--------------------------------------------------------------------------------
1 | # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
2 | #
3 | # Generated on 2021-03-25T21:15:43Z by kres 424ae88-dirty.
4 |
5 | style: github
6 | template: CHANGELOG.tpl.md
7 | info:
8 | title: CHANGELOG
9 | repository_url: https://github.com/talos-systems/grpc-proxy
10 | options:
11 | commits:
12 | # filters:
13 | # Type:
14 | # - feat
15 | # - fix
16 | # - perf
17 | # - refactor
18 | commit_groups:
19 | # title_maps:
20 | # feat: Features
21 | # fix: Bug Fixes
22 | # perf: Performance Improvements
23 | # refactor: Code Refactoring
24 | header:
25 | pattern: "^(\\w*)(?:\\(([\\w\\$\\.\\-\\*\\s]*)\\))?\\:\\s(.*)$"
26 | pattern_maps:
27 | - Type
28 | - Scope
29 | - Subject
30 | notes:
31 | keywords:
32 | - BREAKING CHANGE
33 |
--------------------------------------------------------------------------------
/hack/release.sh:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env bash
2 |
3 | # THIS FILE WAS AUTOMATICALLY GENERATED, PLEASE DO NOT EDIT.
4 | #
5 | # Generated on 2024-10-10T19:23:45Z by kres 34e72ac.
6 |
7 | set -e
8 |
9 | RELEASE_TOOL_IMAGE="ghcr.io/siderolabs/release-tool:latest"
10 |
11 | function release-tool {
12 | docker pull "${RELEASE_TOOL_IMAGE}" >/dev/null
13 | docker run --rm -w /src -v "${PWD}":/src:ro "${RELEASE_TOOL_IMAGE}" -l -d -n -t "${1}" ./hack/release.toml
14 | }
15 |
16 | function changelog {
17 | if [ "$#" -eq 1 ]; then
18 | (release-tool ${1}; echo; cat CHANGELOG.md) > CHANGELOG.md- && mv CHANGELOG.md- CHANGELOG.md
19 | else
20 | echo 1>&2 "Usage: $0 changelog [tag]"
21 | exit 1
22 | fi
23 | }
24 |
25 | function release-notes {
26 | release-tool "${2}" > "${1}"
27 | }
28 |
29 | function cherry-pick {
30 | if [ $# -ne 2 ]; then
31 | echo 1>&2 "Usage: $0 cherry-pick "
32 | exit 1
33 | fi
34 |
35 | git checkout $2
36 | git fetch
37 | git rebase upstream/$2
38 | git cherry-pick -x $1
39 | }
40 |
41 | function commit {
42 | if [ $# -ne 1 ]; then
43 | echo 1>&2 "Usage: $0 commit "
44 | exit 1
45 | fi
46 |
47 | if is_on_main_branch; then
48 | update_license_files
49 | fi
50 |
51 | git commit -s -m "release($1): prepare release" -m "This is the official $1 release."
52 | }
53 |
54 | function is_on_main_branch {
55 | main_remotes=("upstream" "origin")
56 | branch_names=("main" "master")
57 | current_branch=$(git rev-parse --abbrev-ref HEAD)
58 |
59 | echo "Check current branch: $current_branch"
60 |
61 | for remote in "${main_remotes[@]}"; do
62 | echo "Fetch remote $remote..."
63 |
64 | if ! git fetch --quiet "$remote" &>/dev/null; then
65 | echo "Failed to fetch $remote, skip..."
66 |
67 | continue
68 | fi
69 |
70 | for branch_name in "${branch_names[@]}"; do
71 | if ! git rev-parse --verify "$branch_name" &>/dev/null; then
72 | echo "Branch $branch_name does not exist, skip..."
73 |
74 | continue
75 | fi
76 |
77 | echo "Branch $remote/$branch_name exists, comparing..."
78 |
79 | merge_base=$(git merge-base "$current_branch" "$remote/$branch_name")
80 | latest_main=$(git rev-parse "$remote/$branch_name")
81 |
82 | if [ "$merge_base" = "$latest_main" ]; then
83 | echo "Current branch is up-to-date with $remote/$branch_name"
84 |
85 | return 0
86 | else
87 | echo "Current branch is not on $remote/$branch_name"
88 |
89 | return 1
90 | fi
91 | done
92 | done
93 |
94 | echo "No main or master branch found on any remote"
95 |
96 | return 1
97 | }
98 |
99 | function update_license_files {
100 | script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
101 | parent_dir="$(dirname "$script_dir")"
102 | current_year=$(date +"%Y")
103 | change_date=$(date -v+4y +"%Y-%m-%d" 2>/dev/null || date -d "+4 years" +"%Y-%m-%d" 2>/dev/null || date --date="+4 years" +"%Y-%m-%d")
104 |
105 | # Find LICENSE and .kres.yaml files recursively in the parent directory (project root)
106 | find "$parent_dir" \( -name "LICENSE" -o -name ".kres.yaml" \) -type f | while read -r file; do
107 | temp_file="${file}.tmp"
108 |
109 | if [[ $file == *"LICENSE" ]]; then
110 | if grep -q "^Business Source License" "$file"; then
111 | sed -e "s/The Licensed Work is (c) [0-9]\{4\}/The Licensed Work is (c) $current_year/" \
112 | -e "s/Change Date: [0-9]\{4\}-[0-9]\{2\}-[0-9]\{2\}/Change Date: $change_date/" \
113 | "$file" >"$temp_file"
114 | else
115 | continue # Not a Business Source License file
116 | fi
117 | elif [[ $file == *".kres.yaml" ]]; then
118 | sed -E 's/^([[:space:]]*)ChangeDate:.*$/\1ChangeDate: "'"$change_date"'"/' "$file" >"$temp_file"
119 | fi
120 |
121 | # Check if the file has changed
122 | if ! cmp -s "$file" "$temp_file"; then
123 | mv "$temp_file" "$file"
124 | echo "Updated: $file"
125 | git add "$file"
126 | else
127 | echo "No changes: $file"
128 | rm "$temp_file"
129 | fi
130 | done
131 | }
132 |
133 | if declare -f "$1" > /dev/null
134 | then
135 | cmd="$1"
136 | shift
137 | $cmd "$@"
138 | else
139 | cat <%s", c.parentCodec.Name())
81 | }
82 |
--------------------------------------------------------------------------------
/proxy/codec_test.go:
--------------------------------------------------------------------------------
1 | package proxy_test
2 |
3 | import (
4 | "bytes"
5 | "strings"
6 | "testing"
7 |
8 | "github.com/stretchr/testify/require"
9 | "google.golang.org/grpc/mem"
10 |
11 | "github.com/siderolabs/grpc-proxy/proxy"
12 | talos_testproto "github.com/siderolabs/grpc-proxy/testservice"
13 | )
14 |
15 | func TestCodec_ReadYourWrites(t *testing.T) {
16 | d := []byte{0xDE, 0xAD, 0xBE, 0xEF}
17 |
18 | for key, val := range map[string][]byte{
19 | "short message": d,
20 | "long message": bytes.Repeat(d, 3072),
21 | } {
22 | t.Run(key, func(t *testing.T) {
23 | framePtr := proxy.NewFrame(nil)
24 | codec := proxy.Codec()
25 |
26 | buffer := mem.Copy(val, mem.DefaultBufferPool())
27 | defer func() { buffer.Free() }()
28 |
29 | require.NoError(t, codec.Unmarshal(mem.BufferSlice{buffer}, framePtr), "unmarshalling must go ok")
30 | out, err := codec.Marshal(framePtr)
31 | require.NoError(t, err, "no marshal error")
32 | require.Equal(t, val, out.Materialize(), "output and data must be the same")
33 |
34 | out.Free()
35 | buffer.Free()
36 | buffer = mem.Copy([]byte{0x55}, mem.DefaultBufferPool())
37 |
38 | // reuse
39 | require.NoError(t, codec.Unmarshal(mem.BufferSlice{buffer}, framePtr), "unmarshalling must go ok")
40 | out, err = codec.Marshal(framePtr)
41 | require.NoError(t, err, "no marshal error")
42 | require.Equal(t, []byte{0x55}, out.Materialize(), "output and data must be the same")
43 |
44 | out.Free()
45 | })
46 | }
47 | }
48 |
49 | func TestCodecUsualMessage(t *testing.T) {
50 | const msg = "short message"
51 |
52 | for key, val := range map[string]string{
53 | "short message": "edbca",
54 | "long message": strings.Repeat(msg, 3072),
55 | } {
56 | t.Run(key, func(t *testing.T) {
57 | msg := &talos_testproto.PingRequest{Value: val}
58 |
59 | codec := proxy.Codec()
60 |
61 | out, err := codec.Marshal(msg)
62 | require.NoError(t, err, "no marshal error")
63 |
64 | defer out.Free()
65 |
66 | var dst talos_testproto.PingRequest
67 |
68 | require.NoError(t, codec.Unmarshal(out, &dst), "unmarshalling must go ok")
69 | require.NotZero(t, dst.Value, "output must not be zero")
70 | require.Equal(t, msg.Value, dst.Value, "output and data must be the same")
71 | })
72 | }
73 | }
74 |
--------------------------------------------------------------------------------
/proxy/director.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Michal Witkowski. All Rights Reserved.
2 | // Copyright 2019 Andrey Smirnov. All Rights Reserved.
3 | // See LICENSE for licensing terms.
4 |
5 | package proxy
6 |
7 | import (
8 | "context"
9 |
10 | "google.golang.org/grpc"
11 | )
12 |
13 | // Backend wraps information about upstream connection.
14 | //
15 | // For simple one-to-one proxying, not much should be done in the Backend, simply
16 | // providing a connection is enough.
17 | //
18 | // When proxying one-to-many and aggregating results, Backend might be used to
19 | // append additional fields to upstream response to support more complicated
20 | // proxying.
21 | type Backend interface {
22 | // String provides backend name for logging and errors.
23 | String() string
24 |
25 | // GetConnection returns a grpc connection to the backend.
26 | //
27 | // The context returned from this function should be the context for the *outgoing* (to backend) call. In case you want
28 | // to forward any Metadata between the inbound request and outbound requests, you should do it manually. However, you
29 | // *must* propagate the cancel function (`context.WithCancel`) of the inbound context to the one returned.
30 | GetConnection(ctx context.Context, fullMethodName string) (context.Context, *grpc.ClientConn, error)
31 |
32 | // AppendInfo is called to enhance response from the backend with additional data.
33 | //
34 | // Parameter streaming indicates if response is delivered in streaming mode or not.
35 | //
36 | // Usecase might be appending backend endpoint (or name) to the protobuf serialized response, so that response is enhanced
37 | // with source information. This is particularly important for one to many calls, when it is required to identify
38 | // response from each of the backends participating in the proxying.
39 | //
40 | // If not additional proxying is required, simply returning the buffer without changes works fine.
41 | AppendInfo(streaming bool, resp []byte) ([]byte, error)
42 |
43 | // BuildError is called to convert error from upstream into response field.
44 | //
45 | // BuildError is never called for one to one proxying, in that case all the errors are returned back to the caller
46 | // as grpc errors. Parameter streaming indicates if response is delivered in streaming mode or not.
47 | //
48 | // When proxying one to many, if one the requests fails or upstream returns an error, it is undesirable to fail the whole
49 | // request and discard responses from other backends. BuildError converts (marshals) error from backend into protobuf encoded
50 | // response which is analyzed by the caller, so that caller reaching out to N upstreams receives N1 successful responses and
51 | // N2 error responses so that N1 + N2 == N.
52 | //
53 | // If BuildError returns nil, error is returned as grpc error (failing whole request).
54 | BuildError(streaming bool, err error) ([]byte, error)
55 | }
56 |
57 | // SingleBackend implements a simple wrapper around get connection function of one to one proxying.
58 | //
59 | // SingleBackend implements Backend interface and might be used as an easy wrapper for one to one proxying.
60 | type SingleBackend struct {
61 | // GetConn returns a grpc connection to the backend.
62 | //
63 | // The context returned from this function should be the context for the *outgoing* (to backend) call. In case you want
64 | // to forward any Metadata between the inbound request and outbound requests, you should do it manually. However, you
65 | // *must* propagate the cancel function (`context.WithCancel`) of the inbound context to the one returned.
66 | GetConn func(ctx context.Context) (context.Context, *grpc.ClientConn, error)
67 | }
68 |
69 | func (sb *SingleBackend) String() string {
70 | return "backend"
71 | }
72 |
73 | // GetConnection returns a grpc connection to the backend.
74 | func (sb *SingleBackend) GetConnection(ctx context.Context, _ string) (context.Context, *grpc.ClientConn, error) {
75 | return sb.GetConn(ctx)
76 | }
77 |
78 | // AppendInfo is called to enhance response from the backend with additional data.
79 | func (sb *SingleBackend) AppendInfo(_ bool, resp []byte) ([]byte, error) {
80 | return resp, nil
81 | }
82 |
83 | // BuildError is called to convert error from upstream into response field.
84 | func (sb *SingleBackend) BuildError(bool, error) ([]byte, error) {
85 | return nil, nil
86 | }
87 |
88 | // StreamDirector returns a list of Backend objects to forward the call to.
89 | //
90 | // There are two proxying modes:
91 | // 1. one to one: StreamDirector returns a single Backend object - proxying is done verbatim, Backend.AppendInfo might
92 | // be used to enhance response with source information (or it might be skipped).
93 | // 2. one to many: StreamDirector returns more than one Backend object - for unary calls responses from Backend objects
94 | // are aggregated by concatenating protobuf responses (requires top-level `repeated` protobuf definition) and errors
95 | // are wrapped as responses via BuildError. Responses are potentially enhanced via AppendInfo.
96 | //
97 | // The presence of the `Context` allows for rich filtering, e.g. based on Metadata (headers).
98 | // If no handling is meant to be done, a `codes.NotImplemented` gRPC error should be returned.
99 | //
100 | // It is worth noting that the StreamDirector will be fired *after* all server-side stream interceptors
101 | // are invoked. So decisions around authorization, monitoring etc. are better to be handled there.
102 | //
103 | // See the rather rich example.
104 | type StreamDirector func(ctx context.Context, fullMethodName string) (Mode, []Backend, error)
105 |
--------------------------------------------------------------------------------
/proxy/doc.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Michal Witkowski. All Rights Reserved.
2 | // See LICENSE for licensing terms.
3 |
4 | /*
5 | Package proxy provides a reverse proxy handler for gRPC.
6 |
7 | The implementation allows a `grpc.Server` to pass a received ServerStream to a ClientStream without understanding
8 | the semantics of the messages exchanged. It basically provides a transparent reverse-proxy.
9 |
10 | This package is intentionally generic, exposing a `StreamDirector` function that allows users of this package
11 | to implement whatever logic of backend-picking, dialing and service verification to perform.
12 |
13 | See examples on documented functions.
14 | */
15 | package proxy
16 |
--------------------------------------------------------------------------------
/proxy/examples_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Michal Witkowski. All Rights Reserved.
2 | // See LICENSE for licensing terms.
3 |
4 | package proxy_test
5 |
6 | import (
7 | "context"
8 | "log"
9 | "strings"
10 |
11 | "google.golang.org/grpc"
12 | "google.golang.org/grpc/codes"
13 | "google.golang.org/grpc/credentials/insecure"
14 | "google.golang.org/grpc/metadata"
15 | "google.golang.org/grpc/status"
16 |
17 | "github.com/siderolabs/grpc-proxy/proxy"
18 | )
19 |
20 | var director proxy.StreamDirector
21 |
22 | // ExampleRegisterService is a simple example of registering a service with the proxy.
23 | func ExampleRegisterService() {
24 | // A gRPC server with the proxying codec enabled.
25 | server := grpc.NewServer(grpc.ForceServerCodecV2(proxy.Codec()))
26 |
27 | // Register a TestService with 4 of its methods explicitly.
28 | proxy.RegisterService(server, director,
29 | "talos.testproto.TestService",
30 | proxy.WithMethodNames("PingEmpty", "Ping", "PingError", "PingList"),
31 | proxy.WithStreamedMethodNames("PingList"),
32 | )
33 |
34 | // Output:
35 | }
36 |
37 | // ExampleTransparentHandler is an example of redirecting all requests to the proxy.
38 | func ExampleTransparentHandler() {
39 | grpc.NewServer(
40 | grpc.ForceServerCodecV2(proxy.Codec()),
41 | grpc.UnknownServiceHandler(proxy.TransparentHandler(director)),
42 | )
43 |
44 | // Output:
45 | }
46 |
47 | // Provide sa simple example of a director that shields internal services and dials a staging or production backend.
48 | // This is a *very naive* implementation that creates a new connection on every request. Consider using pooling.
49 | func ExampleStreamDirector() {
50 | simpleBackendGen := func(hostname string) (proxy.Backend, error) {
51 | conn, err := grpc.NewClient(
52 | hostname,
53 | grpc.WithDefaultCallOptions(grpc.ForceCodecV2(proxy.Codec())),
54 | grpc.WithTransportCredentials(insecure.NewCredentials()),
55 | )
56 | if err != nil {
57 | return nil, err
58 | }
59 |
60 | return &proxy.SingleBackend{
61 | GetConn: func(ctx context.Context) (context.Context, *grpc.ClientConn, error) {
62 | md, _ := metadata.FromIncomingContext(ctx)
63 |
64 | // Copy the inbound metadata explicitly.
65 | outCtx := metadata.NewOutgoingContext(ctx, md.Copy())
66 |
67 | return outCtx, conn, nil
68 | },
69 | }, nil
70 | }
71 |
72 | stagingBackend, err := simpleBackendGen("api-service.staging.svc.local")
73 | if err != nil {
74 | log.Fatal("failed to create staging backend:", err)
75 | }
76 |
77 | prodBackend, err := simpleBackendGen("api-service.prod.svc.local")
78 | if err != nil {
79 | log.Fatal("failed to create production backend:", err)
80 | }
81 |
82 | director = func(ctx context.Context, fullMethodName string) (proxy.Mode, []proxy.Backend, error) {
83 | // Make sure we never forward internal services.
84 | if strings.HasPrefix(fullMethodName, "/com.example.internal.") {
85 | return proxy.One2One, nil, status.Errorf(codes.Unimplemented, "Unknown method")
86 | }
87 |
88 | md, ok := metadata.FromIncomingContext(ctx)
89 |
90 | if ok {
91 | // Decide on which backend to dial
92 | if val, exists := md[":authority"]; exists && val[0] == "staging.api.example.com" {
93 | return proxy.One2One, []proxy.Backend{stagingBackend}, nil
94 | } else if val, exists := md[":authority"]; exists && val[0] == "api.example.com" {
95 | return proxy.One2One, []proxy.Backend{prodBackend}, nil
96 | }
97 | }
98 |
99 | return proxy.One2One, nil, status.Errorf(codes.Unimplemented, "Unknown method")
100 | }
101 |
102 | // Output:
103 | }
104 |
--------------------------------------------------------------------------------
/proxy/handler.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Michal Witkowski. All Rights Reserved.
2 | // Copyright 2019 Andrey Smirnov. All Rights Reserved.
3 | // See LICENSE for licensing terms.
4 |
5 | package proxy
6 |
7 | import (
8 | "context"
9 |
10 | "google.golang.org/grpc"
11 | "google.golang.org/grpc/codes"
12 | "google.golang.org/grpc/status"
13 | )
14 |
15 | var clientStreamDescForProxying = &grpc.StreamDesc{
16 | ServerStreams: true,
17 | ClientStreams: true,
18 | }
19 |
20 | type handlerOptions struct {
21 | streamedMethods map[string]struct{}
22 | streamedDetector StreamedDetectorFunc
23 | serviceName string
24 | methodNames []string
25 | }
26 |
27 | type handler struct {
28 | director StreamDirector
29 | options handlerOptions
30 | }
31 |
32 | type backendConnection struct {
33 | backend Backend
34 |
35 | backendConn *grpc.ClientConn
36 | connError error
37 |
38 | clientStream grpc.ClientStream
39 | }
40 |
41 | // handler is where the real magic of proxying happens.
42 | // It is invoked like any gRPC server stream and uses the gRPC server framing to get and receive bytes from the wire,
43 | // forwarding it to a ClientStream established against the relevant ClientConn.
44 | func (s *handler) handler(_ any, serverStream grpc.ServerStream) error {
45 | // little bit of gRPC internals never hurt anyone
46 | fullMethodName, ok := grpc.MethodFromServerStream(serverStream)
47 | if !ok {
48 | return status.Errorf(codes.Internal, "lowLevelServerStream doesn't exist in the context")
49 | }
50 |
51 | mode, backends, err := s.director(serverStream.Context(), fullMethodName)
52 | if err != nil {
53 | return err
54 | }
55 |
56 | backendConnections := make([]backendConnection, len(backends))
57 |
58 | clientCtx, clientCancel := context.WithCancel(serverStream.Context())
59 | defer clientCancel()
60 |
61 | for i := range backends {
62 | backendConnections[i].backend = backends[i]
63 |
64 | // We require that the backend's returned context inherits from the serverStream.Context().
65 | var outgoingCtx context.Context
66 | outgoingCtx, backendConnections[i].backendConn, backendConnections[i].connError = backends[i].GetConnection(clientCtx, fullMethodName)
67 |
68 | if backendConnections[i].connError != nil {
69 | continue
70 | }
71 |
72 | backendConnections[i].clientStream, backendConnections[i].connError = grpc.NewClientStream(outgoingCtx, clientStreamDescForProxying,
73 | backendConnections[i].backendConn, fullMethodName)
74 |
75 | if backendConnections[i].connError != nil {
76 | continue
77 | }
78 | }
79 |
80 | switch mode {
81 | case One2One:
82 | if len(backendConnections) != 1 {
83 | return status.Errorf(codes.Internal, "one2one proxying should have exactly one connection (got %d)", len(backendConnections))
84 | }
85 |
86 | return s.handlerOne2One(serverStream, backendConnections)
87 | case One2Many:
88 | if len(backendConnections) == 0 {
89 | return status.Errorf(codes.Unavailable, "no backend connections for proxying")
90 | }
91 |
92 | return s.handlerOne2Many(fullMethodName, serverStream, backendConnections)
93 | default:
94 | return status.Errorf(codes.Internal, "unsupported proxy mode")
95 | }
96 | }
97 |
--------------------------------------------------------------------------------
/proxy/handler_one2many.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Andrey Smirnov. All Rights Reserved.
2 | // See LICENSE for licensing terms.
3 |
4 | package proxy
5 |
6 | import (
7 | "context"
8 | "errors"
9 | "fmt"
10 | "io"
11 |
12 | "github.com/hashicorp/go-multierror"
13 | "google.golang.org/grpc"
14 | "google.golang.org/grpc/codes"
15 | "google.golang.org/grpc/status"
16 | )
17 |
18 | func (s *handler) handlerOne2Many(fullMethodName string, serverStream grpc.ServerStream, backendConnections []backendConnection) error {
19 | // wrap the stream for safe concurrent access
20 | serverStream = &ServerStreamWrapper{ServerStream: serverStream}
21 |
22 | s2cErrChan := s.forwardServerToClientsMulti(serverStream, backendConnections)
23 |
24 | var c2sErrChan chan error
25 |
26 | if s.options.streamedDetector != nil && s.options.streamedDetector(fullMethodName) {
27 | c2sErrChan = s.forwardClientsToServerMultiStreaming(backendConnections, serverStream)
28 | } else {
29 | c2sErrChan = s.forwardClientsToServerMultiUnary(backendConnections, serverStream)
30 | }
31 |
32 | for range 2 {
33 | select {
34 | case s2cErr := <-s2cErrChan:
35 | if errors.Is(s2cErr, io.EOF) {
36 | // this is the happy case where the sender has encountered io.EOF, and won't be sending anymore./
37 | // the clientStream>serverStream may continue pumping though.
38 | for i := range backendConnections {
39 | if backendConnections[i].clientStream != nil {
40 | backendConnections[i].clientStream.CloseSend() //nolint: errcheck
41 | }
42 | }
43 | } else {
44 | // however, we may have gotten a receive error (stream disconnected, a read error etc) in which case we need
45 | // to cancel the clientStream to the backend, let all of its goroutines be freed up by the CancelFunc and
46 | // exit with an error to the stack
47 | return status.Errorf(codes.Internal, "failed proxying s2c: %v", s2cErr)
48 | }
49 | case c2sErr := <-c2sErrChan:
50 | // c2sErr will contain RPC error from client code. If not io.EOF return the RPC error as server stream error.
51 | if !errors.Is(c2sErr, io.EOF) {
52 | return c2sErr
53 | }
54 |
55 | return nil
56 | }
57 | }
58 |
59 | return status.Errorf(codes.Internal, "gRPC proxying should never reach this stage.")
60 | }
61 |
62 | // formatError tries to format error from upstream as message to the client.
63 | func (s *handler) formatError(streaming bool, src *backendConnection, backendErr error) ([]byte, error) {
64 | payload, err := src.backend.BuildError(streaming, backendErr)
65 | if err != nil {
66 | return nil, fmt.Errorf("error building error for %s: %w", src.backend, err)
67 | }
68 |
69 | if payload == nil {
70 | err = backendErr
71 | }
72 |
73 | return payload, err
74 | }
75 |
76 | // sendError tries to deliver error back to the client via dst.
77 | //
78 | // If sendError fails to deliver the error, error is returned.
79 | // If sendError successfully delivers the error, nil is returned.
80 | func (s *handler) sendError(src *backendConnection, dst grpc.ServerStream, backendErr error) error {
81 | payload, err := s.formatError(true, src, backendErr)
82 | if err != nil {
83 | return err
84 | }
85 |
86 | f := NewFrame(payload)
87 |
88 | if err = dst.SendMsg(f); err != nil {
89 | if errors.Is(err, context.Canceled) {
90 | return nil
91 | }
92 |
93 | if rpcStatus, ok := status.FromError(err); ok && rpcStatus.Message() == "transport is closing" {
94 | return nil
95 | }
96 |
97 | return fmt.Errorf("error sending error back: %w", err)
98 | }
99 |
100 | return nil
101 | }
102 |
103 | // forwardClientsToServerMultiUnary handles one:many proxying, unary call version (merging results)
104 | //
105 | //nolint:gocognit
106 | func (s *handler) forwardClientsToServerMultiUnary(sources []backendConnection, dst grpc.ServerStream) chan error {
107 | ret := make(chan error, 1)
108 |
109 | payloadCh := make(chan []byte, len(sources))
110 | errCh := make(chan error, len(sources))
111 |
112 | for i := range sources {
113 | go func(src *backendConnection) {
114 | errCh <- func() error {
115 | if src.connError != nil {
116 | payload, err := s.formatError(false, src, src.connError)
117 | if err != nil {
118 | return err
119 | }
120 |
121 | payloadCh <- payload
122 |
123 | return nil
124 | }
125 |
126 | // Send the header metadata first
127 | md, err := src.clientStream.Header()
128 | if err != nil {
129 | var payload []byte
130 |
131 | payload, err = s.formatError(false, src, err)
132 | if err != nil {
133 | return err
134 | }
135 |
136 | payloadCh <- payload
137 |
138 | return nil
139 | }
140 |
141 | if md != nil {
142 | if err = dst.SetHeader(md); err != nil {
143 | return fmt.Errorf("error setting headers from client %s: %w", src.backend, err)
144 | }
145 | }
146 |
147 | f := &frame{}
148 |
149 | for {
150 | if err := src.clientStream.RecvMsg(f); err != nil {
151 | if errors.Is(err, io.EOF) {
152 | // This happens when the clientStream has nothing else to offer (io.EOF), returned a gRPC error. In those two
153 | // cases we may have received Trailers as part of the call. In case of other errors (stream closed) the trailers
154 | // will be nil.
155 | dst.SetTrailer(src.clientStream.Trailer())
156 |
157 | return nil
158 | }
159 |
160 | var payload []byte
161 |
162 | payload, err = s.formatError(false, src, err)
163 | if err != nil {
164 | return err
165 | }
166 |
167 | payloadCh <- payload
168 |
169 | return nil
170 | }
171 |
172 | var err error
173 |
174 | f.payload, err = src.backend.AppendInfo(false, f.payload)
175 | if err != nil {
176 | return fmt.Errorf("error appending info for %s: %w", src.backend, err)
177 | }
178 |
179 | payloadCh <- f.payload
180 | }
181 | }()
182 | }(&sources[i])
183 | }
184 |
185 | go func() {
186 | var multiErr *multierror.Error
187 |
188 | for range sources {
189 | multiErr = multierror.Append(multiErr, <-errCh)
190 | }
191 |
192 | if multiErr.ErrorOrNil() != nil {
193 | ret <- multiErr.ErrorOrNil()
194 |
195 | return
196 | }
197 |
198 | close(payloadCh)
199 |
200 | var merged []byte
201 | for b := range payloadCh {
202 | merged = append(merged, b...)
203 | }
204 |
205 | ret <- dst.SendMsg(NewFrame(merged))
206 | }()
207 |
208 | return ret
209 | }
210 |
211 | // one:many proxying, streaming version (no merge).
212 | //
213 | //nolint:gocognit
214 | func (s *handler) forwardClientsToServerMultiStreaming(sources []backendConnection, dst grpc.ServerStream) chan error {
215 | ret := make(chan error, 1)
216 |
217 | errCh := make(chan error, len(sources))
218 |
219 | for i := range sources {
220 | go func(src *backendConnection) {
221 | errCh <- func() error {
222 | if src.connError != nil {
223 | return s.sendError(src, dst, src.connError)
224 | }
225 |
226 | f := &frame{}
227 |
228 | for j := 0; ; j++ {
229 | if err := src.clientStream.RecvMsg(f); err != nil {
230 | if errors.Is(err, io.EOF) {
231 | // This happens when the clientStream has nothing else to offer (io.EOF), returned a gRPC error. In those two
232 | // cases we may have received Trailers as part of the call. In case of other errors (stream closed) the trailers
233 | // will be nil.
234 | dst.SetTrailer(src.clientStream.Trailer())
235 |
236 | return nil
237 | }
238 |
239 | return s.sendError(src, dst, err)
240 | }
241 | if j == 0 {
242 | // This is a bit of a hack, but client to server headers are only readable after first client msg is
243 | // received but must be written to server stream before the first msg is flushed.
244 | // This is the only place to do it nicely.
245 | md, err := src.clientStream.Header()
246 | if err != nil {
247 | return s.sendError(src, dst, err)
248 | }
249 |
250 | dst.SetHeader(md) //nolint:errcheck // ignore errors, as we might try to set headers multiple times
251 | }
252 |
253 | var err error
254 | f.payload, err = src.backend.AppendInfo(true, f.payload)
255 | if err != nil {
256 | return fmt.Errorf("error appending info for %s: %w", src.backend, err)
257 | }
258 |
259 | if err = dst.SendMsg(f); err != nil {
260 | return fmt.Errorf("error sending back to server from %s: %w", src.backend, err)
261 | }
262 | }
263 | }()
264 | }(&sources[i])
265 | }
266 |
267 | go func() {
268 | var multiErr *multierror.Error
269 |
270 | for range sources {
271 | multiErr = multierror.Append(multiErr, <-errCh)
272 | }
273 |
274 | ret <- multiErr.ErrorOrNil()
275 | }()
276 |
277 | return ret
278 | }
279 |
280 | func (s *handler) forwardServerToClientsMulti(src grpc.ServerStream, destinations []backendConnection) chan error {
281 | ret := make(chan error, 1)
282 |
283 | go func() {
284 | f := NewFrame(nil)
285 |
286 | for {
287 | if err := src.RecvMsg(f); err != nil {
288 | ret <- err
289 |
290 | return
291 | }
292 |
293 | errCh := make(chan error)
294 |
295 | for i := range destinations {
296 | go func(dst *backendConnection) {
297 | errCh <- func() error {
298 | if dst.clientStream == nil || dst.connError != nil {
299 | return nil //nolint:nilerr // skip it
300 | }
301 |
302 | return dst.clientStream.SendMsg(f)
303 | }()
304 | }(&destinations[i])
305 | }
306 |
307 | liveDestinations := 0
308 |
309 | for range destinations {
310 | if err := <-errCh; err == nil {
311 | liveDestinations++
312 | }
313 | }
314 |
315 | if liveDestinations == 0 {
316 | ret <- io.EOF
317 |
318 | return
319 | }
320 | }
321 | }()
322 |
323 | return ret
324 | }
325 |
--------------------------------------------------------------------------------
/proxy/handler_one2many_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Michal Witkowski. All Rights Reserved.
2 | // Copyright 2019 Andrey Smirnov. All Rights Reserved.
3 | // See LICENSE for licensing terms.
4 |
5 | package proxy_test
6 |
7 | import (
8 | "context"
9 | "errors"
10 | "fmt"
11 | "io"
12 | "net"
13 | "os"
14 | "strconv"
15 | "strings"
16 | "testing"
17 | "time"
18 |
19 | "github.com/stretchr/testify/assert"
20 | "github.com/stretchr/testify/require"
21 | "github.com/stretchr/testify/suite"
22 | "google.golang.org/grpc"
23 | "google.golang.org/grpc/codes"
24 | "google.golang.org/grpc/credentials/insecure"
25 | "google.golang.org/grpc/grpclog"
26 | "google.golang.org/grpc/metadata"
27 | "google.golang.org/grpc/status"
28 | "google.golang.org/protobuf/encoding/protowire"
29 | "google.golang.org/protobuf/proto"
30 |
31 | "github.com/siderolabs/grpc-proxy/proxy"
32 | pb "github.com/siderolabs/grpc-proxy/testservice"
33 | )
34 |
35 | const (
36 | numUpstreams = 5
37 | )
38 |
39 | // asserting service is implemented on the server side and serves as a handler for stuff.
40 | type assertingMultiService struct {
41 | pb.UnimplementedMultiServiceServer
42 |
43 | t *testing.T
44 | server string
45 | }
46 |
47 | func (s *assertingMultiService) PingEmpty(ctx context.Context, _ *pb.Empty) (*pb.MultiPingReply, error) {
48 | // Check that this call has client's metadata.
49 | md, ok := metadata.FromIncomingContext(ctx)
50 | assert.True(s.t, ok, "PingEmpty call must have metadata in context")
51 | _, ok = md[clientMdKey]
52 | assert.True(s.t, ok, "PingEmpty call must have clients's custom headers in metadata")
53 |
54 | return &pb.MultiPingReply{
55 | Response: []*pb.MultiPingResponse{
56 | {
57 | Value: pingDefaultValue,
58 | Counter: 42,
59 | Server: s.server,
60 | },
61 | },
62 | }, nil
63 | }
64 |
65 | func (s *assertingMultiService) Ping(ctx context.Context, ping *pb.PingRequest) (*pb.MultiPingReply, error) {
66 | // Send user trailers and headers.
67 | grpc.SendHeader(ctx, metadata.Pairs(serverHeaderMdKey, "I like turtles.")) //nolint: errcheck
68 | grpc.SetTrailer(ctx, metadata.Pairs(serverTrailerMdKey, "I like ending turtles.")) //nolint: errcheck
69 |
70 | return &pb.MultiPingReply{
71 | Response: []*pb.MultiPingResponse{
72 | {
73 | Value: ping.Value,
74 | Counter: 42,
75 | Server: s.server,
76 | },
77 | },
78 | }, nil
79 | }
80 |
81 | func (s *assertingMultiService) PingError(context.Context, *pb.PingRequest) (*pb.EmptyReply, error) {
82 | return nil, status.Errorf(codes.FailedPrecondition, "Userspace error.")
83 | }
84 |
85 | func (s *assertingMultiService) PingList(ping *pb.PingRequest, stream pb.MultiService_PingListServer) error {
86 | // Send user trailers and headers.
87 | stream.SendHeader(metadata.Pairs(serverHeaderMdKey, "I like turtles.")) //nolint: errcheck
88 |
89 | for i := range countListResponses {
90 | stream.Send(&pb.MultiPingResponse{ //nolint: errcheck
91 | Value: ping.Value,
92 | Counter: int32(i),
93 | Server: s.server,
94 | })
95 | }
96 |
97 | stream.SetTrailer(metadata.Pairs(serverTrailerMdKey, "I like ending turtles."))
98 |
99 | return nil
100 | }
101 |
102 | func (s *assertingMultiService) PingStream(stream pb.MultiService_PingStreamServer) error {
103 | stream.SendHeader(metadata.Pairs(serverHeaderMdKey, "I like turtles.")) //nolint: errcheck
104 |
105 | counter := int32(0)
106 |
107 | for {
108 | ping, err := stream.Recv()
109 |
110 | if errors.Is(err, io.EOF) {
111 | break
112 | } else if err != nil {
113 | require.NoError(s.t, err, "can't fail reading stream")
114 |
115 | return err
116 | }
117 |
118 | pong := &pb.MultiPingResponse{
119 | Value: ping.Value,
120 | Counter: counter,
121 | Server: s.server,
122 | }
123 |
124 | if err := stream.Send(pong); err != nil {
125 | require.NoError(s.t, err, "can't fail sending back a pong")
126 | }
127 |
128 | counter++
129 | }
130 |
131 | stream.SetTrailer(metadata.Pairs(serverTrailerMdKey, "I like ending turtles."))
132 |
133 | return nil
134 | }
135 |
136 | func (s *assertingMultiService) PingStreamError(pb.MultiService_PingStreamErrorServer) error {
137 | return status.Errorf(codes.FailedPrecondition, "Userspace error.")
138 | }
139 |
140 | type assertingBackend struct {
141 | conn *grpc.ClientConn
142 | i int
143 | }
144 |
145 | func (b *assertingBackend) String() string {
146 | return fmt.Sprintf("backend%d", b.i)
147 | }
148 |
149 | func (b *assertingBackend) GetConnection(ctx context.Context, _ string) (context.Context, *grpc.ClientConn, error) {
150 | md, _ := metadata.FromIncomingContext(ctx)
151 | // Explicitly copy the metadata, otherwise the tests will fail.
152 | outCtx := metadata.NewOutgoingContext(ctx, md.Copy())
153 |
154 | if b.conn == nil {
155 | return ctx, nil, status.Error(codes.Unavailable, "backend connection failed")
156 | }
157 |
158 | return outCtx, b.conn, nil
159 | }
160 |
161 | func (b *assertingBackend) AppendInfo(streaming bool, resp []byte) ([]byte, error) {
162 | payload, err := proto.Marshal(&pb.ResponseMetadataPrepender{
163 | Metadata: &pb.ResponseMetadata{
164 | Hostname: fmt.Sprintf("server%d", b.i),
165 | },
166 | })
167 |
168 | if streaming {
169 | return append(resp, payload...), err
170 | }
171 |
172 | // decode protobuf embedded header
173 | typ, n1 := protowire.ConsumeVarint(resp)
174 | _, n2 := protowire.ConsumeVarint(resp[n1:]) // length
175 |
176 | if typ != (1<<3)|2 { // type: 2, field_number: 1
177 | return nil, fmt.Errorf("unexpected message format: %d", typ)
178 | }
179 |
180 | // cut off embedded message header
181 | resp = resp[n1+n2:]
182 | // build new embedded message header
183 | prefix := protowire.AppendVarint(protowire.AppendVarint(nil, (1<<3)|2), uint64(len(resp)+len(payload)))
184 | resp = append(prefix, resp...)
185 |
186 | return append(resp, payload...), err
187 | }
188 |
189 | func (b *assertingBackend) BuildError(streaming bool, err error) ([]byte, error) {
190 | resp := &pb.EmptyReply{
191 | Response: []*pb.EmptyResponse{
192 | {
193 | Metadata: &pb.ResponseMetadata{
194 | Hostname: fmt.Sprintf("server%d", b.i),
195 | UpstreamError: err.Error(),
196 | },
197 | },
198 | },
199 | }
200 |
201 | if streaming {
202 | return proto.Marshal(resp.Response[0])
203 | }
204 |
205 | return proto.Marshal(resp)
206 | }
207 |
208 | type ProxyOne2ManySuite struct { //nolint: govet
209 | suite.Suite
210 |
211 | serverListeners []net.Listener
212 | servers []*grpc.Server
213 | proxyListener net.Listener
214 | proxy *grpc.Server
215 | serverClientConn *grpc.ClientConn
216 |
217 | client *grpc.ClientConn
218 | testClient pb.MultiServiceClient
219 |
220 | ctx context.Context //nolint:containedctx
221 | ctxCancel context.CancelFunc
222 | }
223 |
224 | func (s *ProxyOne2ManySuite) TestPingEmptyCarriesClientMetadata() {
225 | ctx := metadata.NewOutgoingContext(s.ctx, metadata.Pairs(clientMdKey, "true"))
226 | out, err := s.testClient.PingEmpty(ctx, &pb.Empty{})
227 | require.NoError(s.T(), err, "PingEmpty should succeed without errors")
228 |
229 | expectedUpstreams := map[string]struct{}{}
230 | for i := range numUpstreams {
231 | expectedUpstreams[fmt.Sprintf("server%d", i)] = struct{}{}
232 | }
233 |
234 | s.Require().Len(out.Response, numUpstreams)
235 |
236 | for _, resp := range out.Response {
237 | s.Require().Equal(pingDefaultValue, resp.Value)
238 | s.Require().EqualValues(42, resp.Counter)
239 |
240 | // equal metadata set by proxy and server
241 | s.Require().Equal(resp.Metadata.Hostname, resp.Server)
242 |
243 | delete(expectedUpstreams, resp.Metadata.Hostname)
244 | }
245 |
246 | s.Require().Empty(expectedUpstreams)
247 | }
248 |
249 | func (s *ProxyOne2ManySuite) TestPingEmpty_StressTest() {
250 | for range 50 {
251 | s.TestPingEmptyCarriesClientMetadata()
252 | }
253 | }
254 |
255 | func (s *ProxyOne2ManySuite) TestPingEmptyTargets() {
256 | for _, targets := range [][]string{
257 | {"1", "2"},
258 | {"3", "2", "1"},
259 | {"0", "4"},
260 | {"3"},
261 | } {
262 | md := metadata.Pairs(clientMdKey, "true")
263 | md.Set("targets", targets...)
264 |
265 | ctx := metadata.NewOutgoingContext(s.ctx, md)
266 | out, err := s.testClient.PingEmpty(ctx, &pb.Empty{})
267 | require.NoError(s.T(), err, "PingEmpty should succeed without errors")
268 |
269 | expectedUpstreams := map[string]struct{}{}
270 | for _, target := range targets {
271 | expectedUpstreams[fmt.Sprintf("server%s", target)] = struct{}{}
272 | }
273 |
274 | s.Require().Len(out.Response, len(expectedUpstreams))
275 |
276 | for _, resp := range out.Response {
277 | s.Require().Equal(pingDefaultValue, resp.Value)
278 | s.Require().EqualValues(42, resp.Counter)
279 |
280 | // equal metadata set by proxy and server
281 | s.Require().Equal(resp.Metadata.Hostname, resp.Server)
282 |
283 | delete(expectedUpstreams, resp.Metadata.Hostname)
284 | }
285 |
286 | s.Require().Empty(expectedUpstreams)
287 | }
288 | }
289 |
290 | func (s *ProxyOne2ManySuite) TestPingEmptyConnError() {
291 | targets := []string{"0", "-1", "2"}
292 | md := metadata.Pairs(clientMdKey, "true")
293 | md.Set("targets", targets...)
294 |
295 | ctx := metadata.NewOutgoingContext(s.ctx, md)
296 | out, err := s.testClient.PingEmpty(ctx, &pb.Empty{})
297 | require.NoError(s.T(), err, "PingEmpty should succeed without errors")
298 |
299 | expectedUpstreams := map[string]struct{}{}
300 | for _, target := range targets {
301 | expectedUpstreams[fmt.Sprintf("server%s", target)] = struct{}{}
302 | }
303 |
304 | s.Require().Len(out.Response, len(expectedUpstreams))
305 |
306 | for _, resp := range out.Response {
307 | delete(expectedUpstreams, resp.Metadata.Hostname)
308 |
309 | if resp.Metadata.Hostname != "server-1" {
310 | s.Assert().Equal(pingDefaultValue, resp.Value)
311 | s.Assert().EqualValues(42, resp.Counter)
312 |
313 | // equal metadata set by proxy and server
314 | s.Assert().Equal(resp.Metadata.Hostname, resp.Server)
315 | } else {
316 | s.Assert().Equal("rpc error: code = Unavailable desc = backend connection failed", resp.Metadata.UpstreamError)
317 | }
318 | }
319 |
320 | s.Require().Empty(expectedUpstreams)
321 | }
322 |
323 | func (s *ProxyOne2ManySuite) TestPingCarriesServerHeadersAndTrailers() {
324 | headerMd := make(metadata.MD)
325 | trailerMd := make(metadata.MD)
326 | // This is an awkward calling convention... but meh.
327 | out, err := s.testClient.Ping(s.ctx, &pb.PingRequest{Value: "foo"}, grpc.Header(&headerMd), grpc.Trailer(&trailerMd))
328 | require.NoError(s.T(), err, "Ping should succeed without errors")
329 |
330 | s.Require().Len(out.Response, numUpstreams)
331 |
332 | for _, resp := range out.Response {
333 | s.Require().Equal("foo", resp.Value)
334 | s.Require().EqualValues(42, resp.Counter)
335 |
336 | // equal metadata set by proxy and server
337 | s.Require().Equal(resp.Metadata.Hostname, resp.Server)
338 | }
339 |
340 | assert.Contains(s.T(), headerMd, serverHeaderMdKey, "server response headers must contain server data")
341 | assert.Len(s.T(), trailerMd, 1, "server response trailers must contain server data")
342 | }
343 |
344 | func (s *ProxyOne2ManySuite) TestPingErrorPropagatesAppError() {
345 | out, err := s.testClient.PingError(s.ctx, &pb.PingRequest{Value: "foo"})
346 | s.Require().NoError(err, "error should be encapsulated in the response")
347 |
348 | s.Require().Len(out.Response, numUpstreams)
349 |
350 | for _, resp := range out.Response {
351 | s.Require().NotEmpty(resp.Metadata.UpstreamError)
352 | s.Require().NotEmpty(resp.Metadata.Hostname)
353 | s.Assert().Equal("rpc error: code = FailedPrecondition desc = Userspace error.", resp.Metadata.UpstreamError)
354 | }
355 | }
356 |
357 | func (s *ProxyOne2ManySuite) TestPingStreamErrorPropagatesAppError() {
358 | stream, err := s.testClient.PingStreamError(s.ctx)
359 | s.Require().NoError(err, "error should be encapsulated in the response")
360 |
361 | for range numUpstreams {
362 | var resp *pb.MultiPingResponse
363 |
364 | resp, err = stream.Recv()
365 | s.Require().NoError(err)
366 |
367 | s.Assert().Equal("rpc error: code = FailedPrecondition desc = Userspace error.", resp.Metadata.UpstreamError)
368 | }
369 |
370 | require.NoError(s.T(), stream.CloseSend(), "no error on close send")
371 | _, err = stream.Recv()
372 | require.Equal(s.T(), io.EOF, err, "stream should close with io.EOF, meaning OK")
373 | }
374 |
375 | func (s *ProxyOne2ManySuite) TestPingStreamConnError() {
376 | targets := []string{"0", "-1", "2"}
377 | md := metadata.Pairs(clientMdKey, "true")
378 | md.Set("targets", targets...)
379 |
380 | ctx := metadata.NewOutgoingContext(s.ctx, md)
381 | stream, err := s.testClient.PingStream(ctx)
382 | s.Require().NoError(err, "error should be encapsulated in the response")
383 |
384 | require.NoError(s.T(), stream.CloseSend(), "no error on close send")
385 |
386 | resp, err := stream.Recv()
387 | s.Require().NoError(err)
388 |
389 | s.Assert().Equal("rpc error: code = Unavailable desc = backend connection failed", resp.Metadata.UpstreamError)
390 |
391 | _, err = stream.Recv()
392 | require.Equal(s.T(), io.EOF, err, "stream should close with io.EOF, meaning OK")
393 | }
394 |
395 | func (s *ProxyOne2ManySuite) TestDirectorErrorIsPropagated() {
396 | // See SetupSuite where the StreamDirector has a special case.
397 | ctx := metadata.NewOutgoingContext(s.ctx, metadata.Pairs(rejectingMdKey, "true"))
398 | _, err := s.testClient.Ping(ctx, &pb.PingRequest{Value: "foo"})
399 | require.Error(s.T(), err, "Director should reject this RPC")
400 | assert.Equal(s.T(), codes.PermissionDenied, status.Code(err))
401 | assert.Equal(s.T(), "testing rejection", status.Convert(err).Message())
402 | }
403 |
404 | func (s *ProxyOne2ManySuite) TestPingStream_FullDuplexWorks() {
405 | stream, err := s.testClient.PingStream(s.ctx)
406 | require.NoError(s.T(), err, "PingStream request should be successful.")
407 |
408 | for i := range countListResponses {
409 | ping := &pb.PingRequest{Value: fmt.Sprintf("foo:%d", i)}
410 | require.NoError(s.T(), stream.Send(ping), "sending to PingStream must not fail")
411 |
412 | expectedUpstreams := map[string]struct{}{}
413 | for j := range numUpstreams {
414 | expectedUpstreams[fmt.Sprintf("server%d", j)] = struct{}{}
415 | }
416 |
417 | // each upstream should send back response
418 | for range numUpstreams {
419 | var resp *pb.MultiPingResponse
420 | resp, err = stream.Recv()
421 | s.Require().NoError(err)
422 |
423 | s.Assert().EqualValues(i, resp.Counter, "ping roundtrip must succeed with the correct id")
424 | s.Assert().EqualValues(resp.Metadata.Hostname, resp.Server)
425 |
426 | delete(expectedUpstreams, resp.Metadata.Hostname)
427 | }
428 |
429 | s.Require().Empty(expectedUpstreams)
430 |
431 | if i == 0 {
432 | // Check that the header arrives before all entries.
433 | var headerMd metadata.MD
434 | headerMd, err = stream.Header()
435 | require.NoError(s.T(), err, "PingStream headers should not error.")
436 | assert.Contains(s.T(), headerMd, serverHeaderMdKey, "PingStream response headers user contain metadata")
437 | }
438 | }
439 |
440 | require.NoError(s.T(), stream.CloseSend(), "no error on close send")
441 | _, err = stream.Recv()
442 | require.Equal(s.T(), io.EOF, err, "stream should close with io.EOF, meaning OK")
443 | // Check that the trailer headers are here.
444 | trailerMd := stream.Trailer()
445 | assert.Len(s.T(), trailerMd, 1, "PingList trailer headers user contain metadata")
446 | }
447 |
448 | //nolint:gocognit
449 | func (s *ProxyOne2ManySuite) TestPingStream_FullDuplexConcurrent() {
450 | stream, err := s.testClient.PingStream(s.ctx)
451 | require.NoError(s.T(), err, "PingStream request should be successful.")
452 |
453 | // send countListResponses requests and concurrently read numUpstreams * countListResponses replies
454 | errCh := make(chan error, 2)
455 |
456 | expectedUpstreams := map[string]int32{}
457 |
458 | for j := range numUpstreams {
459 | expectedUpstreams[fmt.Sprintf("server%d", j)] = 0
460 | }
461 |
462 | go func() {
463 | errCh <- func() error {
464 | for i := range countListResponses {
465 | ping := &pb.PingRequest{Value: fmt.Sprintf("foo:%d", i)}
466 | if err = stream.Send(ping); err != nil {
467 | return err
468 | }
469 | }
470 |
471 | return stream.CloseSend()
472 | }()
473 | }()
474 |
475 | go func() {
476 | errCh <- func() error {
477 | for range countListResponses * numUpstreams {
478 | var resp *pb.MultiPingResponse
479 |
480 | resp, err = stream.Recv()
481 | if err != nil {
482 | return err
483 | }
484 |
485 | if resp.Metadata == nil {
486 | return fmt.Errorf("response metadata expected: %v", resp)
487 | }
488 |
489 | if resp.Metadata.Hostname != resp.Server {
490 | return fmt.Errorf("mismatch on host metadata: %v != %v", resp.Metadata.Hostname, resp.Server)
491 | }
492 |
493 | expectedCounter, ok := expectedUpstreams[resp.Server]
494 | if !ok {
495 | return fmt.Errorf("unexpected host: %v", resp.Server)
496 | }
497 |
498 | if expectedCounter != resp.Counter {
499 | return fmt.Errorf("unexpected counter value: %d != %d", expectedCounter, resp.Counter)
500 | }
501 |
502 | expectedUpstreams[resp.Server]++
503 | }
504 |
505 | return nil
506 | }()
507 | }()
508 |
509 | s.Require().NoError(<-errCh)
510 | s.Require().NoError(<-errCh)
511 |
512 | _, err = stream.Recv()
513 | require.Equal(s.T(), io.EOF, err, "stream should close with io.EOF, meaning OK")
514 | // Check that the trailer headers are here.
515 | trailerMd := stream.Trailer()
516 | assert.Len(s.T(), trailerMd, 1, "PingList trailer headers user contain metadata")
517 | }
518 |
519 | func (s *ProxyOne2ManySuite) TestPingStream_StressTest() {
520 | for range 50 {
521 | s.TestPingStream_FullDuplexWorks()
522 | }
523 | }
524 |
525 | func (s *ProxyOne2ManySuite) SetupTest() {
526 | s.ctx, s.ctxCancel = context.WithTimeout(context.TODO(), 120*time.Second)
527 | }
528 |
529 | func (s *ProxyOne2ManySuite) TearDownTest() {
530 | s.ctxCancel()
531 | }
532 |
533 | //nolint:gocognit
534 | func (s *ProxyOne2ManySuite) SetupSuite() {
535 | var err error
536 |
537 | s.proxyListener, err = net.Listen("tcp", "127.0.0.1:0")
538 | require.NoError(s.T(), err, "must be able to allocate a port for proxyListener")
539 |
540 | s.serverListeners = make([]net.Listener, numUpstreams)
541 |
542 | for i := range s.serverListeners {
543 | s.serverListeners[i], err = net.Listen("tcp", "127.0.0.1:0")
544 | require.NoError(s.T(), err, "must be able to allocate a port for serverListener")
545 | }
546 |
547 | s.servers = make([]*grpc.Server, numUpstreams)
548 |
549 | for i := range s.servers {
550 | s.servers[i] = grpc.NewServer()
551 | pb.RegisterMultiServiceServer(s.servers[i],
552 | &assertingMultiService{
553 | t: s.T(),
554 | server: fmt.Sprintf("server%d", i),
555 | })
556 | }
557 |
558 | backends := make([]*assertingBackend, numUpstreams)
559 |
560 | for i := range backends {
561 | var conn *grpc.ClientConn
562 | conn, err = grpc.NewClient(
563 | s.serverListeners[i].Addr().String(),
564 | grpc.WithTransportCredentials(insecure.NewCredentials()),
565 | grpc.WithDefaultCallOptions(grpc.ForceCodecV2(proxy.Codec())),
566 | )
567 | require.NoError(s.T(), err)
568 |
569 | backends[i] = &assertingBackend{
570 | conn: conn,
571 | i: i,
572 | }
573 | }
574 |
575 | failingBackend := &assertingBackend{
576 | conn: nil,
577 | i: -1,
578 | }
579 |
580 | // Setup of the proxy's Director.
581 | director := func(ctx context.Context, _ string) (proxy.Mode, []proxy.Backend, error) {
582 | var targets []int
583 |
584 | md, ok := metadata.FromIncomingContext(ctx)
585 | if ok {
586 | if _, exists := md[rejectingMdKey]; exists {
587 | return proxy.One2Many, nil, status.Errorf(codes.PermissionDenied, "testing rejection")
588 | }
589 |
590 | if mdTargets, exists := md["targets"]; exists {
591 | for _, strTarget := range mdTargets {
592 | var t int
593 |
594 | t, err = strconv.Atoi(strTarget)
595 | if err != nil {
596 | return proxy.One2Many, nil, err
597 | }
598 |
599 | targets = append(targets, t)
600 | }
601 | }
602 | }
603 |
604 | var result []proxy.Backend
605 |
606 | if targets == nil {
607 | for i := range backends {
608 | targets = append(targets, i)
609 | }
610 | }
611 |
612 | for _, t := range targets {
613 | if t == -1 {
614 | result = append(result, failingBackend)
615 | } else {
616 | result = append(result, backends[t])
617 | }
618 | }
619 |
620 | return proxy.One2Many, result, nil
621 | }
622 |
623 | s.proxy = grpc.NewServer(
624 | grpc.ForceServerCodecV2(proxy.Codec()),
625 | grpc.UnknownServiceHandler(proxy.TransparentHandler(director)),
626 | )
627 | // Ping handler is handled as an explicit registration and not as a TransparentHandler.
628 | proxy.RegisterService(s.proxy, director,
629 | "talos.testproto.MultiService",
630 | proxy.WithMethodNames("Ping", "PingStream", "PingStreamError"),
631 | proxy.WithStreamedMethodNames("PingStream", "PingStreamError"),
632 | )
633 |
634 | // Start the serving loops.
635 | for i := range s.servers {
636 | s.T().Logf("starting grpc.Server at: %v", s.serverListeners[i].Addr().String())
637 |
638 | go func(i int) {
639 | s.servers[i].Serve(s.serverListeners[i]) //nolint: errcheck
640 | }(i)
641 | }
642 |
643 | s.T().Logf("starting grpc.Proxy at: %v", s.proxyListener.Addr().String())
644 |
645 | go func() {
646 | s.proxy.Serve(s.proxyListener) //nolint: errcheck
647 | }()
648 |
649 | clientConn, err := grpc.NewClient(strings.Replace(s.proxyListener.Addr().String(), "127.0.0.1", "localhost", 1), grpc.WithTransportCredentials(insecure.NewCredentials()))
650 | require.NoError(s.T(), err, "must not error on deferred client Dial")
651 | s.testClient = pb.NewMultiServiceClient(clientConn)
652 | }
653 |
654 | func (s *ProxyOne2ManySuite) TearDownSuite() {
655 | if s.client != nil {
656 | s.Assert().NoError(s.client.Close())
657 | }
658 |
659 | if s.serverClientConn != nil {
660 | s.Assert().NoError(s.serverClientConn.Close())
661 | }
662 |
663 | // Close all transports so the logs don't get spammy.
664 | time.Sleep(10 * time.Millisecond)
665 |
666 | if s.proxy != nil {
667 | s.proxy.Stop()
668 | s.proxyListener.Close() //nolint: errcheck
669 | }
670 |
671 | for _, server := range s.servers {
672 | if server != nil {
673 | server.Stop()
674 | }
675 | }
676 |
677 | for _, serverListener := range s.serverListeners {
678 | if serverListener != nil {
679 | serverListener.Close() //nolint: errcheck
680 | }
681 | }
682 | }
683 |
684 | func TestProxyOne2ManySuite(t *testing.T) {
685 | suite.Run(t, &ProxyOne2ManySuite{})
686 | }
687 |
688 | func init() {
689 | grpclog.SetLoggerV2(grpclog.NewLoggerV2(os.Stderr, os.Stderr, os.Stderr))
690 | }
691 |
--------------------------------------------------------------------------------
/proxy/handler_one2one.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Michal Witkowski. All Rights Reserved.
2 | // Copyright 2019 Andrey Smirnov. All Rights Reserved.
3 | // See LICENSE for licensing terms.
4 |
5 | package proxy
6 |
7 | import (
8 | "errors"
9 | "io"
10 |
11 | "google.golang.org/grpc"
12 | "google.golang.org/grpc/codes"
13 | "google.golang.org/grpc/status"
14 | )
15 |
16 | func (s *handler) handlerOne2One(serverStream grpc.ServerStream, backendConnections []backendConnection) error {
17 | // case of proxying one to one:
18 | if backendConnections[0].connError != nil {
19 | return backendConnections[0].connError
20 | }
21 |
22 | // Explicitly *do not close* s2cErrChan and c2sErrChan, otherwise the select below will not terminate.
23 | // Channels do not have to be closed, it is just a control flow mechanism, see
24 | // https://groups.google.com/forum/#!msg/golang-nuts/pZwdYRGxCIk/qpbHxRRPJdUJ
25 | s2cErrChan := s.forwardServerToClient(serverStream, &backendConnections[0])
26 | c2sErrChan := s.forwardClientToServer(&backendConnections[0], serverStream)
27 | // We don't know which side is going to stop sending first, so we need a select between the two.
28 | for range 2 {
29 | select {
30 | case s2cErr := <-s2cErrChan:
31 | if errors.Is(s2cErr, io.EOF) {
32 | // this is the happy case where the sender has encountered io.EOF, and won't be sending anymore./
33 | // the clientStream>serverStream may continue pumping though.
34 | //nolint: errcheck
35 | backendConnections[0].clientStream.CloseSend()
36 | } else {
37 | // however, we may have gotten a receive error (stream disconnected, a read error etc) in which case we need
38 | // to cancel the clientStream to the backend, let all of its goroutines be freed up by the CancelFunc and
39 | // exit with an error to the stack
40 | return status.Errorf(codes.Internal, "failed proxying s2c: %v", s2cErr)
41 | }
42 | case c2sErr := <-c2sErrChan:
43 | // This happens when the clientStream has nothing else to offer (io.EOF), returned a gRPC error. In those two
44 | // cases we may have received Trailers as part of the call. In case of other errors (stream closed) the trailers
45 | // will be nil.
46 | serverStream.SetTrailer(backendConnections[0].clientStream.Trailer())
47 | // c2sErr will contain RPC error from client code. If not io.EOF return the RPC error as server stream error.
48 | if !errors.Is(c2sErr, io.EOF) {
49 | return c2sErr
50 | }
51 |
52 | return nil
53 | }
54 | }
55 |
56 | return status.Errorf(codes.Internal, "gRPC proxying should never reach this stage.")
57 | }
58 |
59 | func (s *handler) forwardClientToServer(src *backendConnection, dst grpc.ServerStream) chan error {
60 | ret := make(chan error, 1)
61 |
62 | go func() {
63 | // Send the header metadata first
64 | md, err := src.clientStream.Header()
65 | if err != nil {
66 | ret <- err
67 |
68 | return
69 | }
70 |
71 | if md != nil {
72 | if err = dst.SendHeader(md); err != nil {
73 | ret <- err
74 |
75 | return
76 | }
77 | }
78 |
79 | f := NewFrame(nil)
80 |
81 | for {
82 | if err = src.clientStream.RecvMsg(f); err != nil {
83 | ret <- err // this can be io.EOF which is happy case
84 |
85 | break
86 | }
87 |
88 | if err = dst.SendMsg(f); err != nil {
89 | ret <- err
90 |
91 | break
92 | }
93 | }
94 | }()
95 |
96 | return ret
97 | }
98 |
99 | func (s *handler) forwardServerToClient(src grpc.ServerStream, dst *backendConnection) chan error {
100 | ret := make(chan error, 1)
101 |
102 | go func() {
103 | f := NewFrame(nil)
104 |
105 | for {
106 | if err := src.RecvMsg(f); err != nil {
107 | ret <- err // this can be io.EOF which is happy case
108 |
109 | break
110 | }
111 |
112 | if err := dst.clientStream.SendMsg(f); err != nil {
113 | ret <- err
114 |
115 | break
116 | }
117 | }
118 | }()
119 |
120 | return ret
121 | }
122 |
--------------------------------------------------------------------------------
/proxy/handler_one2one_test.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Michal Witkowski. All Rights Reserved.
2 | // See LICENSE for licensing terms.
3 |
4 | package proxy_test
5 |
6 | import (
7 | "context"
8 | "errors"
9 | "fmt"
10 | "io"
11 | "net"
12 | "strings"
13 | "testing"
14 | "time"
15 |
16 | "github.com/jhump/grpctunnel"
17 | "github.com/jhump/grpctunnel/tunnelpb"
18 | "github.com/stretchr/testify/assert"
19 | "github.com/stretchr/testify/require"
20 | "github.com/stretchr/testify/suite"
21 | "google.golang.org/grpc"
22 | "google.golang.org/grpc/codes"
23 | "google.golang.org/grpc/credentials/insecure"
24 | "google.golang.org/grpc/metadata"
25 | "google.golang.org/grpc/status"
26 | "google.golang.org/protobuf/proto"
27 |
28 | "github.com/siderolabs/grpc-proxy/proxy"
29 | pb "github.com/siderolabs/grpc-proxy/testservice"
30 | )
31 |
32 | const (
33 | pingDefaultValue = "I like kittens."
34 | clientMdKey = "test-client-header"
35 | serverHeaderMdKey = "test-client-header"
36 | serverTrailerMdKey = "test-client-trailer"
37 |
38 | rejectingMdKey = "test-reject-rpc-if-in-context"
39 |
40 | countListResponses = 20
41 | )
42 |
43 | // asserting service is implemented on the server side and serves as a handler for stuff.
44 | type assertingService struct {
45 | pb.UnimplementedTestServiceServer
46 |
47 | t *testing.T
48 | }
49 |
50 | func (s *assertingService) PingEmpty(ctx context.Context, _ *pb.Empty) (*pb.PingResponse, error) {
51 | // Check that this call has client's metadata.
52 | md, ok := metadata.FromIncomingContext(ctx)
53 | assert.True(s.t, ok, "PingEmpty call must have metadata in context")
54 | _, ok = md[clientMdKey]
55 | assert.True(s.t, ok, "PingEmpty call must have clients's custom headers in metadata")
56 |
57 | return &pb.PingResponse{Value: pingDefaultValue, Counter: 42}, nil
58 | }
59 |
60 | func (s *assertingService) Ping(ctx context.Context, ping *pb.PingRequest) (*pb.PingResponse, error) {
61 | // Send user trailers and headers.
62 | grpc.SendHeader(ctx, metadata.Pairs(serverHeaderMdKey, "I like turtles.")) //nolint: errcheck
63 | grpc.SetTrailer(ctx, metadata.Pairs(serverTrailerMdKey, "I like ending turtles.")) //nolint: errcheck
64 |
65 | return &pb.PingResponse{Value: ping.Value, Counter: 42}, nil
66 | }
67 |
68 | func (s *assertingService) PingError(context.Context, *pb.PingRequest) (*pb.Empty, error) {
69 | return nil, status.Errorf(codes.FailedPrecondition, "Userspace error.")
70 | }
71 |
72 | func (s *assertingService) PingList(ping *pb.PingRequest, stream pb.TestService_PingListServer) error {
73 | // Send user trailers and headers.
74 | stream.SendHeader(metadata.Pairs(serverHeaderMdKey, "I like turtles.")) //nolint: errcheck
75 |
76 | for i := range countListResponses {
77 | stream.Send(&pb.PingResponse{Value: ping.Value, Counter: int32(i)}) //nolint: errcheck
78 | }
79 |
80 | stream.SetTrailer(metadata.Pairs(serverTrailerMdKey, "I like ending turtles."))
81 |
82 | return nil
83 | }
84 |
85 | func (s *assertingService) PingStream(stream pb.TestService_PingStreamServer) error {
86 | stream.SendHeader(metadata.Pairs(serverHeaderMdKey, "I like turtles.")) //nolint: errcheck
87 |
88 | counter := int32(0)
89 |
90 | for {
91 | ping, err := stream.Recv()
92 | if errors.Is(err, io.EOF) {
93 | break
94 | } else if err != nil {
95 | require.NoError(s.t, err, "can't fail reading stream")
96 |
97 | return err
98 | }
99 |
100 | pong := &pb.PingResponse{Value: ping.Value, Counter: counter}
101 |
102 | if err := stream.Send(pong); err != nil {
103 | require.NoError(s.t, err, "can't fail sending back a pong")
104 | }
105 |
106 | counter++
107 | }
108 |
109 | stream.SetTrailer(metadata.Pairs(serverTrailerMdKey, "I like ending turtles."))
110 |
111 | return nil
112 | }
113 |
114 | // ProxyOne2OneSuite tests the "happy" path of handling: that everything works in absence of connection issues.
115 | type ProxyOne2OneSuite struct {
116 | suite.Suite
117 |
118 | serverListener net.Listener
119 | server *grpc.Server
120 | proxyListener net.Listener
121 | proxy *grpc.Server
122 | serverClientConn *grpc.ClientConn
123 |
124 | tunnelHandler *grpctunnel.TunnelServiceHandler
125 | tunnelOpenedCh chan struct{}
126 |
127 | client *grpc.ClientConn
128 | testClient pb.TestServiceClient
129 | tunnelClient tunnelpb.TunnelServiceClient
130 |
131 | ctx context.Context //nolint:containedctx
132 | ctxCancel context.CancelFunc
133 | }
134 |
135 | func (s *ProxyOne2OneSuite) SetupTest() {
136 | s.ctx, s.ctxCancel = context.WithTimeout(context.TODO(), 120*time.Second)
137 | }
138 |
139 | func (s *ProxyOne2OneSuite) TearDownTest() {
140 | s.ctxCancel()
141 | }
142 |
143 | func (s *ProxyOne2OneSuite) TestPingEmptyCarriesClientMetadata() {
144 | ctx := metadata.NewOutgoingContext(s.ctx, metadata.Pairs(clientMdKey, "true"))
145 | out, err := s.testClient.PingEmpty(ctx, &pb.Empty{})
146 | require.NoError(s.T(), err, "PingEmpty should succeed without errors")
147 | require.True(s.T(), proto.Equal(&pb.PingResponse{Value: pingDefaultValue, Counter: 42}, out))
148 | }
149 |
150 | func (s *ProxyOne2OneSuite) TestPingEmpty_StressTest() {
151 | for range 50 {
152 | s.TestPingEmptyCarriesClientMetadata()
153 | }
154 | }
155 |
156 | func (s *ProxyOne2OneSuite) TestPingCarriesServerHeadersAndTrailers() {
157 | s.testPingCarriesServerHeadersAndTrailers(s.testClient)
158 | }
159 |
160 | func (s *ProxyOne2OneSuite) testPingCarriesServerHeadersAndTrailers(client pb.TestServiceClient) {
161 | headerMd := make(metadata.MD)
162 | trailerMd := make(metadata.MD)
163 | // This is an awkward calling convention... but meh.
164 | out, err := client.Ping(s.ctx, &pb.PingRequest{Value: "foo"}, grpc.Header(&headerMd), grpc.Trailer(&trailerMd))
165 | require.NoError(s.T(), err, "Ping should succeed without errors")
166 | require.True(s.T(), proto.Equal(&pb.PingResponse{Value: "foo", Counter: 42}, out))
167 | assert.Contains(s.T(), headerMd, serverHeaderMdKey, "server response headers must contain server data")
168 | assert.Len(s.T(), trailerMd, 1, "server response trailers must contain server data")
169 | }
170 |
171 | func (s *ProxyOne2OneSuite) TestPingErrorPropagatesAppError() {
172 | s.testPingErrorPropagatesAppError(s.testClient)
173 | }
174 |
175 | func (s *ProxyOne2OneSuite) testPingErrorPropagatesAppError(client pb.TestServiceClient) {
176 | _, err := client.PingError(s.ctx, &pb.PingRequest{Value: "foo"})
177 | require.Error(s.T(), err, "PingError should never succeed")
178 | assert.Equal(s.T(), codes.FailedPrecondition, status.Code(err))
179 | assert.Equal(s.T(), "Userspace error.", status.Convert(err).Message())
180 | }
181 |
182 | func (s *ProxyOne2OneSuite) TestDirectorErrorIsPropagated() {
183 | // See SetupSuite where the StreamDirector has a special case.
184 | ctx := metadata.NewOutgoingContext(s.ctx, metadata.Pairs(rejectingMdKey, "true"))
185 | _, err := s.testClient.Ping(ctx, &pb.PingRequest{Value: "foo"})
186 | require.Error(s.T(), err, "Director should reject this RPC")
187 | assert.Equal(s.T(), codes.PermissionDenied, status.Code(err))
188 | assert.Equal(s.T(), "testing rejection", status.Convert(err).Message())
189 | }
190 |
191 | func (s *ProxyOne2OneSuite) TestPingStream_FullDuplexWorks() {
192 | s.testStream(s.testClient)
193 | }
194 |
195 | func (s *ProxyOne2OneSuite) TestReverseTunnel() {
196 | channelServer := grpctunnel.NewReverseTunnelServer(s.tunnelClient)
197 |
198 | pb.RegisterTestServiceServer(channelServer, &assertingService{t: s.T()})
199 |
200 | go func() {
201 | channelServer.Serve(s.ctx) //nolint:errcheck // we are not interested in the error here
202 | }()
203 |
204 | // wait for the tunnel to open
205 | select {
206 | case <-s.ctx.Done():
207 | s.FailNow("timeout waiting for tunnel to open")
208 | case <-s.tunnelOpenedCh:
209 | }
210 |
211 | fakeConn := s.tunnelHandler.KeyAsChannel("asdf")
212 | client := pb.NewTestServiceClient(fakeConn)
213 |
214 | s.testPingCarriesServerHeadersAndTrailers(client)
215 | s.testPingErrorPropagatesAppError(client)
216 | s.testStream(client)
217 | }
218 |
219 | func (s *ProxyOne2OneSuite) testStream(client pb.TestServiceClient) {
220 | stream, err := client.PingStream(s.ctx)
221 | require.NoError(s.T(), err, "PingStream request should be successful.")
222 |
223 | for i := range countListResponses {
224 | ping := &pb.PingRequest{Value: fmt.Sprintf("foo:%d", i)}
225 | require.NoError(s.T(), stream.Send(ping), "sending to PingStream must not fail")
226 |
227 | var resp *pb.PingResponse
228 |
229 | resp, err = stream.Recv()
230 | if errors.Is(err, io.EOF) {
231 | break
232 | }
233 |
234 | if i == 0 {
235 | // Check that the header arrives before all entries.
236 | var headerMd metadata.MD
237 | headerMd, err = stream.Header()
238 | require.NoError(s.T(), err, "PingStream headers should not error.")
239 | assert.Contains(s.T(), headerMd, serverHeaderMdKey, "PingStream response headers user contain metadata")
240 | }
241 |
242 | assert.EqualValues(s.T(), i, resp.Counter, "ping roundtrip must succeed with the correct id")
243 | }
244 |
245 | require.NoError(s.T(), stream.CloseSend(), "no error on close send")
246 | _, err = stream.Recv()
247 | require.Equal(s.T(), io.EOF, err, "stream should close with io.EOF, meaining OK")
248 | // Check that the trailer headers are here.
249 | trailerMd := stream.Trailer()
250 | assert.Len(s.T(), trailerMd, 1, "PingList trailer headers user contain metadata")
251 | }
252 |
253 | func (s *ProxyOne2OneSuite) TestPingStream_StressTest() {
254 | for range 50 {
255 | s.TestPingStream_FullDuplexWorks()
256 | }
257 | }
258 |
259 | func (s *ProxyOne2OneSuite) SetupSuite() {
260 | var err error
261 |
262 | s.proxyListener, err = net.Listen("tcp", "127.0.0.1:0")
263 | require.NoError(s.T(), err, "must be able to allocate a port for proxyListener")
264 | s.serverListener, err = net.Listen("tcp", "127.0.0.1:0")
265 | require.NoError(s.T(), err, "must be able to allocate a port for serverListener")
266 |
267 | s.server = grpc.NewServer()
268 | pb.RegisterTestServiceServer(s.server, &assertingService{t: s.T()})
269 |
270 | s.tunnelOpenedCh = make(chan struct{})
271 |
272 | s.tunnelHandler = grpctunnel.NewTunnelServiceHandler(
273 | grpctunnel.TunnelServiceHandlerOptions{
274 | OnReverseTunnelOpen: func(grpctunnel.TunnelChannel) {
275 | s.T().Logf("[tunnel] open reverse tunnel")
276 | },
277 | OnReverseTunnelClose: func(grpctunnel.TunnelChannel) {
278 | s.T().Logf("[tunnel] close reverse tunnel")
279 | },
280 | AffinityKey: func(grpctunnel.TunnelChannel) any {
281 | s.T().Logf("[tunnel] get affinity key")
282 |
283 | select {
284 | case <-s.ctx.Done():
285 | return "fail"
286 | case s.tunnelOpenedCh <- struct{}{}:
287 | return "asdf"
288 | }
289 | },
290 | },
291 | )
292 |
293 | tunnelpb.RegisterTunnelServiceServer(s.server, s.tunnelHandler.Service())
294 |
295 | // Setup of the proxy's Director.
296 | s.serverClientConn, err = grpc.NewClient(
297 | s.serverListener.Addr().String(),
298 | grpc.WithTransportCredentials(insecure.NewCredentials()),
299 | grpc.WithDefaultCallOptions(grpc.ForceCodecV2(proxy.Codec())),
300 | )
301 | require.NoError(s.T(), err, "must not error on deferred client Dial")
302 |
303 | director := func(ctx context.Context, _ string) (proxy.Mode, []proxy.Backend, error) {
304 | md, ok := metadata.FromIncomingContext(ctx)
305 | if ok {
306 | if _, exists := md[rejectingMdKey]; exists {
307 | return proxy.One2One, nil, status.Errorf(codes.PermissionDenied, "testing rejection")
308 | }
309 | }
310 |
311 | return proxy.One2One, []proxy.Backend{
312 | &proxy.SingleBackend{
313 | GetConn: func(ctx context.Context) (context.Context, *grpc.ClientConn, error) {
314 | md, _ := metadata.FromIncomingContext(ctx)
315 | // Explicitly copy the metadata, otherwise the tests will fail.
316 | outCtx := metadata.NewOutgoingContext(ctx, md.Copy())
317 |
318 | return outCtx, s.serverClientConn, nil
319 | },
320 | },
321 | }, nil
322 | }
323 |
324 | s.proxy = grpc.NewServer(
325 | grpc.ForceServerCodecV2(proxy.Codec()),
326 | grpc.UnknownServiceHandler(proxy.TransparentHandler(director)),
327 | )
328 |
329 | // Ping handler is handled as an explicit registration and not as a TransparentHandler.
330 | proxy.RegisterService(s.proxy, director,
331 | "talos.testproto.TestService",
332 | proxy.WithMethodNames("Ping"),
333 | )
334 |
335 | // Start the serving loops.
336 | s.T().Logf("starting grpc.Server at: %v", s.serverListener.Addr().String())
337 |
338 | go func() {
339 | s.server.Serve(s.serverListener) //nolint: errcheck
340 | }()
341 |
342 | s.T().Logf("starting grpc.Proxy at: %v", s.proxyListener.Addr().String())
343 |
344 | go func() {
345 | s.proxy.Serve(s.proxyListener) //nolint: errcheck
346 | }()
347 |
348 | clientConn, err := grpc.NewClient(
349 | strings.Replace(s.proxyListener.Addr().String(), "127.0.0.1", "localhost", 1),
350 | grpc.WithTransportCredentials(insecure.NewCredentials()),
351 | )
352 | require.NoError(s.T(), err, "must not error on deferred client Dial")
353 | s.testClient = pb.NewTestServiceClient(clientConn)
354 | s.tunnelClient = tunnelpb.NewTunnelServiceClient(clientConn)
355 | }
356 |
357 | func (s *ProxyOne2OneSuite) TearDownSuite() {
358 | if s.client != nil {
359 | s.client.Close() //nolint: errcheck
360 | }
361 |
362 | if s.serverClientConn != nil {
363 | s.serverClientConn.Close() //nolint: errcheck
364 | }
365 |
366 | // Close all transports so the logs don't get spammy.
367 | time.Sleep(10 * time.Millisecond)
368 |
369 | if s.proxy != nil {
370 | s.proxy.Stop()
371 | s.proxyListener.Close() //nolint: errcheck
372 | }
373 |
374 | if s.serverListener != nil {
375 | s.server.Stop()
376 | s.serverListener.Close() //nolint: errcheck
377 | }
378 | }
379 |
380 | func TestProxyOne2OneSuite(t *testing.T) {
381 | suite.Run(t, &ProxyOne2OneSuite{})
382 | }
383 |
--------------------------------------------------------------------------------
/proxy/proxy.go:
--------------------------------------------------------------------------------
1 | // Copyright 2017 Michal Witkowski. All Rights Reserved.
2 | // Copyright 2019 Andrey Smirnov. All Rights Reserved.
3 | // See LICENSE for licensing terms.
4 |
5 | package proxy
6 |
7 | import "google.golang.org/grpc"
8 |
9 | // Mode specifies proxying mode: one2one (transparent) or one2many (aggregation, error wrapping).
10 | type Mode int
11 |
12 | // Mode constants.
13 | const (
14 | One2One Mode = iota
15 | One2Many
16 | )
17 |
18 | // StreamedDetectorFunc reports is gRPC is doing streaming (only for one2many proxying).
19 | type StreamedDetectorFunc func(fullMethodName string) bool
20 |
21 | // Option configures gRPC proxy.
22 | type Option func(*handlerOptions)
23 |
24 | // WithMethodNames configures list of method names to proxy for non-transparent handler.
25 | func WithMethodNames(methodNames ...string) Option {
26 | return func(o *handlerOptions) {
27 | o.methodNames = append([]string(nil), methodNames...)
28 | }
29 | }
30 |
31 | // WithStreamedMethodNames configures list of streamed method names.
32 | //
33 | // This is only important for one2many proxying.
34 | // This option can't be used with TransparentHandler.
35 | func WithStreamedMethodNames(streamedMethodNames ...string) Option {
36 | return func(o *handlerOptions) {
37 | o.streamedMethods = map[string]struct{}{}
38 |
39 | for _, methodName := range streamedMethodNames {
40 | o.streamedMethods["/"+o.serviceName+"/"+methodName] = struct{}{}
41 | }
42 |
43 | o.streamedDetector = func(fullMethodName string) bool {
44 | _, exists := o.streamedMethods[fullMethodName]
45 |
46 | return exists
47 | }
48 | }
49 | }
50 |
51 | // WithStreamedDetector configures a function to detect streamed methods.
52 | //
53 | // This is only important for one2many proxying.
54 | func WithStreamedDetector(detector StreamedDetectorFunc) Option {
55 | return func(o *handlerOptions) {
56 | o.streamedDetector = detector
57 | }
58 | }
59 |
60 | // RegisterService sets up a proxy handler for a particular gRPC service and method.
61 | // The behavior is the same as if you were registering a handler method, e.g. from a codegenerated pb.go file.
62 | //
63 | // This can *only* be used if the `server` also uses grpc.CustomCodec() ServerOption.
64 | func RegisterService(server grpc.ServiceRegistrar, director StreamDirector, serviceName string, options ...Option) {
65 | streamer := &handler{
66 | director: director,
67 | options: handlerOptions{
68 | serviceName: serviceName,
69 | },
70 | }
71 |
72 | for _, o := range options {
73 | o(&streamer.options)
74 | }
75 |
76 | fakeDesc := &grpc.ServiceDesc{
77 | ServiceName: serviceName,
78 | HandlerType: (*any)(nil),
79 | }
80 |
81 | for _, m := range streamer.options.methodNames {
82 | streamDesc := grpc.StreamDesc{
83 | StreamName: m,
84 | Handler: streamer.handler,
85 | ServerStreams: true,
86 | ClientStreams: true,
87 | }
88 | fakeDesc.Streams = append(fakeDesc.Streams, streamDesc)
89 | }
90 |
91 | server.RegisterService(fakeDesc, streamer)
92 | }
93 |
94 | // TransparentHandler returns a handler that attempts to proxy all requests that are not registered in the server.
95 | // The indented use here is as a transparent proxy, where the server doesn't know about the services implemented by the
96 | // backends. It should be used as a `grpc.UnknownServiceHandler`.
97 | //
98 | // This can *only* be used if the `server` also uses grpc.CustomCodec() ServerOption.
99 | func TransparentHandler(director StreamDirector, options ...Option) grpc.StreamHandler {
100 | streamer := &handler{
101 | director: director,
102 | }
103 |
104 | for _, o := range options {
105 | o(&streamer.options)
106 | }
107 |
108 | return streamer.handler
109 | }
110 |
--------------------------------------------------------------------------------
/proxy/serverstream.go:
--------------------------------------------------------------------------------
1 | // Copyright 2019 Andrey Smirnov. All Rights Reserved.
2 | // See LICENSE for licensing terms.
3 |
4 | package proxy
5 |
6 | import (
7 | "sync"
8 |
9 | "google.golang.org/grpc"
10 | "google.golang.org/grpc/metadata"
11 | )
12 |
13 | // ServerStreamWrapper wraps grpc.ServerStream and adds locking to the send path.
14 | type ServerStreamWrapper struct {
15 | grpc.ServerStream
16 |
17 | sendMu sync.Mutex
18 | }
19 |
20 | // SetHeader sets the header metadata.
21 | //
22 | // It may be called multiple times.
23 | // When call multiple times, all the provided metadata will be merged.
24 | // All the metadata will be sent out when one of the following happens:
25 | // - ServerStream.SendHeader() is called;
26 | // - The first response is sent out;
27 | // - An RPC status is sent out (error or success).
28 | func (wrapper *ServerStreamWrapper) SetHeader(md metadata.MD) error {
29 | wrapper.sendMu.Lock()
30 | defer wrapper.sendMu.Unlock()
31 |
32 | err := wrapper.ServerStream.SetHeader(md)
33 | if err != nil && err.Error() == "transport: the stream is done or WriteHeader was already called" {
34 | // hack: swallow grpc.internal.transport.ErrIllegalHeaderWrite
35 | err = nil
36 | }
37 |
38 | return err
39 | }
40 |
41 | // SendHeader sends the header metadata.
42 | // The provided md and headers set by SetHeader() will be sent.
43 | // It fails if called multiple times.
44 | func (wrapper *ServerStreamWrapper) SendHeader(md metadata.MD) error {
45 | wrapper.sendMu.Lock()
46 | defer wrapper.sendMu.Unlock()
47 |
48 | err := wrapper.ServerStream.SendHeader(md)
49 | if err.Error() == "transport: the stream is done or WriteHeader was already called" {
50 | // hack: swallow grpc.internal.transport.ErrIllegalHeaderWrite
51 | err = nil
52 | }
53 |
54 | return err
55 | }
56 |
57 | // SetTrailer sets the trailer metadata which will be sent with the RPC status.
58 | // When called more than once, all the provided metadata will be merged.
59 | func (wrapper *ServerStreamWrapper) SetTrailer(md metadata.MD) {
60 | wrapper.sendMu.Lock()
61 | defer wrapper.sendMu.Unlock()
62 |
63 | wrapper.ServerStream.SetTrailer(md)
64 | }
65 |
66 | // SendMsg sends a message. On error, SendMsg aborts the stream and the
67 | // error is returned directly.
68 | //
69 | // SendMsg blocks until:
70 | // - There is sufficient flow control to schedule m with the transport, or
71 | // - The stream is done, or
72 | // - The stream breaks.
73 | //
74 | // SendMsg does not wait until the message is received by the client. An
75 | // untimely stream closure may result in lost messages.
76 | //
77 | // It is safe to have a goroutine calling SendMsg and another goroutine
78 | // calling RecvMsg on the same stream at the same time, but it is not safe
79 | // to call SendMsg on the same stream in different goroutines.
80 | func (wrapper *ServerStreamWrapper) SendMsg(m any) error {
81 | wrapper.sendMu.Lock()
82 | defer wrapper.sendMu.Unlock()
83 |
84 | return wrapper.ServerStream.SendMsg(m)
85 | }
86 |
--------------------------------------------------------------------------------
/testservice/api/test.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package talos.testproto;
4 |
5 | message Empty {}
6 |
7 | message PingRequest { string value = 1; }
8 |
9 | message PingResponse {
10 | string Value = 1;
11 | int32 counter = 2;
12 | }
13 |
14 | service TestService {
15 | rpc PingEmpty(Empty) returns (PingResponse) {}
16 |
17 | rpc Ping(PingRequest) returns (PingResponse) {}
18 |
19 | rpc PingError(PingRequest) returns (Empty) {}
20 |
21 | rpc PingList(PingRequest) returns (stream PingResponse) {}
22 |
23 | rpc PingStream(stream PingRequest) returns (stream PingResponse) {}
24 | }
25 |
26 | message ResponseMetadata {
27 | string hostname = 99;
28 | string upstream_error = 100;
29 | }
30 |
31 | message ResponseMetadataPrepender { ResponseMetadata metadata = 99; }
32 |
33 | message MultiPingResponse {
34 | ResponseMetadata metadata = 99;
35 | string Value = 1;
36 | int32 counter = 2;
37 | string server = 3;
38 | }
39 |
40 | message MultiPingReply { repeated MultiPingResponse response = 1; }
41 |
42 | message EmptyReply { repeated EmptyResponse response = 1; }
43 |
44 | message EmptyResponse { ResponseMetadata metadata = 99; }
45 |
46 | service MultiService {
47 | rpc PingEmpty(Empty) returns (MultiPingReply) {}
48 |
49 | rpc Ping(PingRequest) returns (MultiPingReply) {}
50 |
51 | rpc PingError(PingRequest) returns (EmptyReply) {}
52 |
53 | rpc PingList(PingRequest) returns (stream MultiPingResponse) {}
54 |
55 | rpc PingStream(stream PingRequest) returns (stream MultiPingResponse) {}
56 |
57 | rpc PingStreamError(stream PingRequest) returns (stream MultiPingResponse) {}
58 | }
59 |
--------------------------------------------------------------------------------
/testservice/test.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go. DO NOT EDIT.
2 | // versions:
3 | // protoc-gen-go v1.25.0
4 | // protoc v3.13.0
5 | // source: test.proto
6 |
7 | package talos_testproto
8 |
9 | import (
10 | reflect "reflect"
11 | sync "sync"
12 |
13 | proto "github.com/golang/protobuf/proto"
14 | protoreflect "google.golang.org/protobuf/reflect/protoreflect"
15 | protoimpl "google.golang.org/protobuf/runtime/protoimpl"
16 | )
17 |
18 | const (
19 | // Verify that this generated code is sufficiently up-to-date.
20 | _ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
21 | // Verify that runtime/protoimpl is sufficiently up-to-date.
22 | _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
23 | )
24 |
25 | // This is a compile-time assertion that a sufficiently up-to-date version
26 | // of the legacy proto package is being used.
27 | const _ = proto.ProtoPackageIsVersion4
28 |
29 | type Empty struct {
30 | state protoimpl.MessageState
31 | sizeCache protoimpl.SizeCache
32 | unknownFields protoimpl.UnknownFields
33 | }
34 |
35 | func (x *Empty) Reset() {
36 | *x = Empty{}
37 | if protoimpl.UnsafeEnabled {
38 | mi := &file_test_proto_msgTypes[0]
39 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
40 | ms.StoreMessageInfo(mi)
41 | }
42 | }
43 |
44 | func (x *Empty) String() string {
45 | return protoimpl.X.MessageStringOf(x)
46 | }
47 |
48 | func (*Empty) ProtoMessage() {}
49 |
50 | func (x *Empty) ProtoReflect() protoreflect.Message {
51 | mi := &file_test_proto_msgTypes[0]
52 | if protoimpl.UnsafeEnabled && x != nil {
53 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
54 | if ms.LoadMessageInfo() == nil {
55 | ms.StoreMessageInfo(mi)
56 | }
57 | return ms
58 | }
59 | return mi.MessageOf(x)
60 | }
61 |
62 | // Deprecated: Use Empty.ProtoReflect.Descriptor instead.
63 | func (*Empty) Descriptor() ([]byte, []int) {
64 | return file_test_proto_rawDescGZIP(), []int{0}
65 | }
66 |
67 | type PingRequest struct {
68 | state protoimpl.MessageState
69 | sizeCache protoimpl.SizeCache
70 | unknownFields protoimpl.UnknownFields
71 |
72 | Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"`
73 | }
74 |
75 | func (x *PingRequest) Reset() {
76 | *x = PingRequest{}
77 | if protoimpl.UnsafeEnabled {
78 | mi := &file_test_proto_msgTypes[1]
79 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
80 | ms.StoreMessageInfo(mi)
81 | }
82 | }
83 |
84 | func (x *PingRequest) String() string {
85 | return protoimpl.X.MessageStringOf(x)
86 | }
87 |
88 | func (*PingRequest) ProtoMessage() {}
89 |
90 | func (x *PingRequest) ProtoReflect() protoreflect.Message {
91 | mi := &file_test_proto_msgTypes[1]
92 | if protoimpl.UnsafeEnabled && x != nil {
93 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
94 | if ms.LoadMessageInfo() == nil {
95 | ms.StoreMessageInfo(mi)
96 | }
97 | return ms
98 | }
99 | return mi.MessageOf(x)
100 | }
101 |
102 | // Deprecated: Use PingRequest.ProtoReflect.Descriptor instead.
103 | func (*PingRequest) Descriptor() ([]byte, []int) {
104 | return file_test_proto_rawDescGZIP(), []int{1}
105 | }
106 |
107 | func (x *PingRequest) GetValue() string {
108 | if x != nil {
109 | return x.Value
110 | }
111 | return ""
112 | }
113 |
114 | type PingResponse struct {
115 | state protoimpl.MessageState
116 | sizeCache protoimpl.SizeCache
117 | unknownFields protoimpl.UnknownFields
118 |
119 | Value string `protobuf:"bytes,1,opt,name=Value,proto3" json:"Value,omitempty"`
120 | Counter int32 `protobuf:"varint,2,opt,name=counter,proto3" json:"counter,omitempty"`
121 | }
122 |
123 | func (x *PingResponse) Reset() {
124 | *x = PingResponse{}
125 | if protoimpl.UnsafeEnabled {
126 | mi := &file_test_proto_msgTypes[2]
127 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
128 | ms.StoreMessageInfo(mi)
129 | }
130 | }
131 |
132 | func (x *PingResponse) String() string {
133 | return protoimpl.X.MessageStringOf(x)
134 | }
135 |
136 | func (*PingResponse) ProtoMessage() {}
137 |
138 | func (x *PingResponse) ProtoReflect() protoreflect.Message {
139 | mi := &file_test_proto_msgTypes[2]
140 | if protoimpl.UnsafeEnabled && x != nil {
141 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
142 | if ms.LoadMessageInfo() == nil {
143 | ms.StoreMessageInfo(mi)
144 | }
145 | return ms
146 | }
147 | return mi.MessageOf(x)
148 | }
149 |
150 | // Deprecated: Use PingResponse.ProtoReflect.Descriptor instead.
151 | func (*PingResponse) Descriptor() ([]byte, []int) {
152 | return file_test_proto_rawDescGZIP(), []int{2}
153 | }
154 |
155 | func (x *PingResponse) GetValue() string {
156 | if x != nil {
157 | return x.Value
158 | }
159 | return ""
160 | }
161 |
162 | func (x *PingResponse) GetCounter() int32 {
163 | if x != nil {
164 | return x.Counter
165 | }
166 | return 0
167 | }
168 |
169 | type ResponseMetadata struct {
170 | state protoimpl.MessageState
171 | sizeCache protoimpl.SizeCache
172 | unknownFields protoimpl.UnknownFields
173 |
174 | Hostname string `protobuf:"bytes,99,opt,name=hostname,proto3" json:"hostname,omitempty"`
175 | UpstreamError string `protobuf:"bytes,100,opt,name=upstream_error,json=upstreamError,proto3" json:"upstream_error,omitempty"`
176 | }
177 |
178 | func (x *ResponseMetadata) Reset() {
179 | *x = ResponseMetadata{}
180 | if protoimpl.UnsafeEnabled {
181 | mi := &file_test_proto_msgTypes[3]
182 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
183 | ms.StoreMessageInfo(mi)
184 | }
185 | }
186 |
187 | func (x *ResponseMetadata) String() string {
188 | return protoimpl.X.MessageStringOf(x)
189 | }
190 |
191 | func (*ResponseMetadata) ProtoMessage() {}
192 |
193 | func (x *ResponseMetadata) ProtoReflect() protoreflect.Message {
194 | mi := &file_test_proto_msgTypes[3]
195 | if protoimpl.UnsafeEnabled && x != nil {
196 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
197 | if ms.LoadMessageInfo() == nil {
198 | ms.StoreMessageInfo(mi)
199 | }
200 | return ms
201 | }
202 | return mi.MessageOf(x)
203 | }
204 |
205 | // Deprecated: Use ResponseMetadata.ProtoReflect.Descriptor instead.
206 | func (*ResponseMetadata) Descriptor() ([]byte, []int) {
207 | return file_test_proto_rawDescGZIP(), []int{3}
208 | }
209 |
210 | func (x *ResponseMetadata) GetHostname() string {
211 | if x != nil {
212 | return x.Hostname
213 | }
214 | return ""
215 | }
216 |
217 | func (x *ResponseMetadata) GetUpstreamError() string {
218 | if x != nil {
219 | return x.UpstreamError
220 | }
221 | return ""
222 | }
223 |
224 | type ResponseMetadataPrepender struct {
225 | state protoimpl.MessageState
226 | sizeCache protoimpl.SizeCache
227 | unknownFields protoimpl.UnknownFields
228 |
229 | Metadata *ResponseMetadata `protobuf:"bytes,99,opt,name=metadata,proto3" json:"metadata,omitempty"`
230 | }
231 |
232 | func (x *ResponseMetadataPrepender) Reset() {
233 | *x = ResponseMetadataPrepender{}
234 | if protoimpl.UnsafeEnabled {
235 | mi := &file_test_proto_msgTypes[4]
236 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
237 | ms.StoreMessageInfo(mi)
238 | }
239 | }
240 |
241 | func (x *ResponseMetadataPrepender) String() string {
242 | return protoimpl.X.MessageStringOf(x)
243 | }
244 |
245 | func (*ResponseMetadataPrepender) ProtoMessage() {}
246 |
247 | func (x *ResponseMetadataPrepender) ProtoReflect() protoreflect.Message {
248 | mi := &file_test_proto_msgTypes[4]
249 | if protoimpl.UnsafeEnabled && x != nil {
250 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
251 | if ms.LoadMessageInfo() == nil {
252 | ms.StoreMessageInfo(mi)
253 | }
254 | return ms
255 | }
256 | return mi.MessageOf(x)
257 | }
258 |
259 | // Deprecated: Use ResponseMetadataPrepender.ProtoReflect.Descriptor instead.
260 | func (*ResponseMetadataPrepender) Descriptor() ([]byte, []int) {
261 | return file_test_proto_rawDescGZIP(), []int{4}
262 | }
263 |
264 | func (x *ResponseMetadataPrepender) GetMetadata() *ResponseMetadata {
265 | if x != nil {
266 | return x.Metadata
267 | }
268 | return nil
269 | }
270 |
271 | type MultiPingResponse struct {
272 | state protoimpl.MessageState
273 | sizeCache protoimpl.SizeCache
274 | unknownFields protoimpl.UnknownFields
275 |
276 | Metadata *ResponseMetadata `protobuf:"bytes,99,opt,name=metadata,proto3" json:"metadata,omitempty"`
277 | Value string `protobuf:"bytes,1,opt,name=Value,proto3" json:"Value,omitempty"`
278 | Counter int32 `protobuf:"varint,2,opt,name=counter,proto3" json:"counter,omitempty"`
279 | Server string `protobuf:"bytes,3,opt,name=server,proto3" json:"server,omitempty"`
280 | }
281 |
282 | func (x *MultiPingResponse) Reset() {
283 | *x = MultiPingResponse{}
284 | if protoimpl.UnsafeEnabled {
285 | mi := &file_test_proto_msgTypes[5]
286 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
287 | ms.StoreMessageInfo(mi)
288 | }
289 | }
290 |
291 | func (x *MultiPingResponse) String() string {
292 | return protoimpl.X.MessageStringOf(x)
293 | }
294 |
295 | func (*MultiPingResponse) ProtoMessage() {}
296 |
297 | func (x *MultiPingResponse) ProtoReflect() protoreflect.Message {
298 | mi := &file_test_proto_msgTypes[5]
299 | if protoimpl.UnsafeEnabled && x != nil {
300 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
301 | if ms.LoadMessageInfo() == nil {
302 | ms.StoreMessageInfo(mi)
303 | }
304 | return ms
305 | }
306 | return mi.MessageOf(x)
307 | }
308 |
309 | // Deprecated: Use MultiPingResponse.ProtoReflect.Descriptor instead.
310 | func (*MultiPingResponse) Descriptor() ([]byte, []int) {
311 | return file_test_proto_rawDescGZIP(), []int{5}
312 | }
313 |
314 | func (x *MultiPingResponse) GetMetadata() *ResponseMetadata {
315 | if x != nil {
316 | return x.Metadata
317 | }
318 | return nil
319 | }
320 |
321 | func (x *MultiPingResponse) GetValue() string {
322 | if x != nil {
323 | return x.Value
324 | }
325 | return ""
326 | }
327 |
328 | func (x *MultiPingResponse) GetCounter() int32 {
329 | if x != nil {
330 | return x.Counter
331 | }
332 | return 0
333 | }
334 |
335 | func (x *MultiPingResponse) GetServer() string {
336 | if x != nil {
337 | return x.Server
338 | }
339 | return ""
340 | }
341 |
342 | type MultiPingReply struct {
343 | state protoimpl.MessageState
344 | sizeCache protoimpl.SizeCache
345 | unknownFields protoimpl.UnknownFields
346 |
347 | Response []*MultiPingResponse `protobuf:"bytes,1,rep,name=response,proto3" json:"response,omitempty"`
348 | }
349 |
350 | func (x *MultiPingReply) Reset() {
351 | *x = MultiPingReply{}
352 | if protoimpl.UnsafeEnabled {
353 | mi := &file_test_proto_msgTypes[6]
354 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
355 | ms.StoreMessageInfo(mi)
356 | }
357 | }
358 |
359 | func (x *MultiPingReply) String() string {
360 | return protoimpl.X.MessageStringOf(x)
361 | }
362 |
363 | func (*MultiPingReply) ProtoMessage() {}
364 |
365 | func (x *MultiPingReply) ProtoReflect() protoreflect.Message {
366 | mi := &file_test_proto_msgTypes[6]
367 | if protoimpl.UnsafeEnabled && x != nil {
368 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
369 | if ms.LoadMessageInfo() == nil {
370 | ms.StoreMessageInfo(mi)
371 | }
372 | return ms
373 | }
374 | return mi.MessageOf(x)
375 | }
376 |
377 | // Deprecated: Use MultiPingReply.ProtoReflect.Descriptor instead.
378 | func (*MultiPingReply) Descriptor() ([]byte, []int) {
379 | return file_test_proto_rawDescGZIP(), []int{6}
380 | }
381 |
382 | func (x *MultiPingReply) GetResponse() []*MultiPingResponse {
383 | if x != nil {
384 | return x.Response
385 | }
386 | return nil
387 | }
388 |
389 | type EmptyReply struct {
390 | state protoimpl.MessageState
391 | sizeCache protoimpl.SizeCache
392 | unknownFields protoimpl.UnknownFields
393 |
394 | Response []*EmptyResponse `protobuf:"bytes,1,rep,name=response,proto3" json:"response,omitempty"`
395 | }
396 |
397 | func (x *EmptyReply) Reset() {
398 | *x = EmptyReply{}
399 | if protoimpl.UnsafeEnabled {
400 | mi := &file_test_proto_msgTypes[7]
401 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
402 | ms.StoreMessageInfo(mi)
403 | }
404 | }
405 |
406 | func (x *EmptyReply) String() string {
407 | return protoimpl.X.MessageStringOf(x)
408 | }
409 |
410 | func (*EmptyReply) ProtoMessage() {}
411 |
412 | func (x *EmptyReply) ProtoReflect() protoreflect.Message {
413 | mi := &file_test_proto_msgTypes[7]
414 | if protoimpl.UnsafeEnabled && x != nil {
415 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
416 | if ms.LoadMessageInfo() == nil {
417 | ms.StoreMessageInfo(mi)
418 | }
419 | return ms
420 | }
421 | return mi.MessageOf(x)
422 | }
423 |
424 | // Deprecated: Use EmptyReply.ProtoReflect.Descriptor instead.
425 | func (*EmptyReply) Descriptor() ([]byte, []int) {
426 | return file_test_proto_rawDescGZIP(), []int{7}
427 | }
428 |
429 | func (x *EmptyReply) GetResponse() []*EmptyResponse {
430 | if x != nil {
431 | return x.Response
432 | }
433 | return nil
434 | }
435 |
436 | type EmptyResponse struct {
437 | state protoimpl.MessageState
438 | sizeCache protoimpl.SizeCache
439 | unknownFields protoimpl.UnknownFields
440 |
441 | Metadata *ResponseMetadata `protobuf:"bytes,99,opt,name=metadata,proto3" json:"metadata,omitempty"`
442 | }
443 |
444 | func (x *EmptyResponse) Reset() {
445 | *x = EmptyResponse{}
446 | if protoimpl.UnsafeEnabled {
447 | mi := &file_test_proto_msgTypes[8]
448 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
449 | ms.StoreMessageInfo(mi)
450 | }
451 | }
452 |
453 | func (x *EmptyResponse) String() string {
454 | return protoimpl.X.MessageStringOf(x)
455 | }
456 |
457 | func (*EmptyResponse) ProtoMessage() {}
458 |
459 | func (x *EmptyResponse) ProtoReflect() protoreflect.Message {
460 | mi := &file_test_proto_msgTypes[8]
461 | if protoimpl.UnsafeEnabled && x != nil {
462 | ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
463 | if ms.LoadMessageInfo() == nil {
464 | ms.StoreMessageInfo(mi)
465 | }
466 | return ms
467 | }
468 | return mi.MessageOf(x)
469 | }
470 |
471 | // Deprecated: Use EmptyResponse.ProtoReflect.Descriptor instead.
472 | func (*EmptyResponse) Descriptor() ([]byte, []int) {
473 | return file_test_proto_rawDescGZIP(), []int{8}
474 | }
475 |
476 | func (x *EmptyResponse) GetMetadata() *ResponseMetadata {
477 | if x != nil {
478 | return x.Metadata
479 | }
480 | return nil
481 | }
482 |
483 | var File_test_proto protoreflect.FileDescriptor
484 |
485 | var file_test_proto_rawDesc = []byte{
486 | 0x0a, 0x0a, 0x74, 0x65, 0x73, 0x74, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x12, 0x0f, 0x74, 0x61,
487 | 0x6c, 0x6f, 0x73, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x07, 0x0a,
488 | 0x05, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x23, 0x0a, 0x0b, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65,
489 | 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01,
490 | 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x22, 0x3e, 0x0a, 0x0c, 0x50,
491 | 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x56,
492 | 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x56, 0x61, 0x6c, 0x75,
493 | 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01,
494 | 0x28, 0x05, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x65, 0x72, 0x22, 0x55, 0x0a, 0x10, 0x52,
495 | 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12,
496 | 0x1a, 0x0a, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x63, 0x20, 0x01, 0x28,
497 | 0x09, 0x52, 0x08, 0x68, 0x6f, 0x73, 0x74, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x25, 0x0a, 0x0e, 0x75,
498 | 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x5f, 0x65, 0x72, 0x72, 0x6f, 0x72, 0x18, 0x64, 0x20,
499 | 0x01, 0x28, 0x09, 0x52, 0x0d, 0x75, 0x70, 0x73, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x72, 0x72,
500 | 0x6f, 0x72, 0x22, 0x5a, 0x0a, 0x19, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65,
501 | 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x50, 0x72, 0x65, 0x70, 0x65, 0x6e, 0x64, 0x65, 0x72, 0x12,
502 | 0x3d, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x63, 0x20, 0x01, 0x28,
503 | 0x0b, 0x32, 0x21, 0x2e, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72,
504 | 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61,
505 | 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x22, 0x9a,
506 | 0x01, 0x0a, 0x11, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70,
507 | 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61,
508 | 0x18, 0x63, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74,
509 | 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
510 | 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64,
511 | 0x61, 0x74, 0x61, 0x12, 0x14, 0x0a, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01, 0x20, 0x01,
512 | 0x28, 0x09, 0x52, 0x05, 0x56, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x18, 0x0a, 0x07, 0x63, 0x6f, 0x75,
513 | 0x6e, 0x74, 0x65, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x07, 0x63, 0x6f, 0x75, 0x6e,
514 | 0x74, 0x65, 0x72, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x18, 0x03, 0x20,
515 | 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x76, 0x65, 0x72, 0x22, 0x50, 0x0a, 0x0e, 0x4d,
516 | 0x75, 0x6c, 0x74, 0x69, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x3e, 0x0a,
517 | 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32,
518 | 0x22, 0x2e, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74,
519 | 0x6f, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f,
520 | 0x6e, 0x73, 0x65, 0x52, 0x08, 0x72, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x48, 0x0a,
521 | 0x0a, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x3a, 0x0a, 0x08, 0x72,
522 | 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1e, 0x2e,
523 | 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
524 | 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x72,
525 | 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x4e, 0x0a, 0x0d, 0x45, 0x6d, 0x70, 0x74, 0x79,
526 | 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x3d, 0x0a, 0x08, 0x6d, 0x65, 0x74, 0x61,
527 | 0x64, 0x61, 0x74, 0x61, 0x18, 0x63, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x21, 0x2e, 0x74, 0x61, 0x6c,
528 | 0x6f, 0x73, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x52, 0x65, 0x73,
529 | 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x52, 0x08, 0x6d,
530 | 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x32, 0xfd, 0x02, 0x0a, 0x0b, 0x54, 0x65, 0x73, 0x74,
531 | 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x44, 0x0a, 0x09, 0x50, 0x69, 0x6e, 0x67, 0x45,
532 | 0x6d, 0x70, 0x74, 0x79, 0x12, 0x16, 0x2e, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74, 0x65, 0x73,
533 | 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1d, 0x2e, 0x74,
534 | 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50,
535 | 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x45, 0x0a,
536 | 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x1c, 0x2e, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74, 0x65,
537 | 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75,
538 | 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74, 0x65, 0x73, 0x74,
539 | 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
540 | 0x73, 0x65, 0x22, 0x00, 0x12, 0x43, 0x0a, 0x09, 0x50, 0x69, 0x6e, 0x67, 0x45, 0x72, 0x72, 0x6f,
541 | 0x72, 0x12, 0x1c, 0x2e, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72,
542 | 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
543 | 0x16, 0x2e, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74,
544 | 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x4b, 0x0a, 0x08, 0x50, 0x69, 0x6e,
545 | 0x67, 0x4c, 0x69, 0x73, 0x74, 0x12, 0x1c, 0x2e, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74, 0x65,
546 | 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75,
547 | 0x65, 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74, 0x65, 0x73, 0x74,
548 | 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e,
549 | 0x73, 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x4f, 0x0a, 0x0a, 0x50, 0x69, 0x6e, 0x67, 0x53, 0x74,
550 | 0x72, 0x65, 0x61, 0x6d, 0x12, 0x1c, 0x2e, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74, 0x65, 0x73,
551 | 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65,
552 | 0x73, 0x74, 0x1a, 0x1d, 0x2e, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70,
553 | 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
554 | 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x32, 0xec, 0x03, 0x0a, 0x0c, 0x4d, 0x75, 0x6c, 0x74,
555 | 0x69, 0x53, 0x65, 0x72, 0x76, 0x69, 0x63, 0x65, 0x12, 0x46, 0x0a, 0x09, 0x50, 0x69, 0x6e, 0x67,
556 | 0x45, 0x6d, 0x70, 0x74, 0x79, 0x12, 0x16, 0x2e, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74, 0x65,
557 | 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x1a, 0x1f, 0x2e,
558 | 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
559 | 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00,
560 | 0x12, 0x47, 0x0a, 0x04, 0x50, 0x69, 0x6e, 0x67, 0x12, 0x1c, 0x2e, 0x74, 0x61, 0x6c, 0x6f, 0x73,
561 | 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52,
562 | 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74,
563 | 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x50, 0x69,
564 | 0x6e, 0x67, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x09, 0x50, 0x69, 0x6e,
565 | 0x67, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1c, 0x2e, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74,
566 | 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71,
567 | 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1b, 0x2e, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74, 0x65, 0x73,
568 | 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x52, 0x65, 0x70, 0x6c,
569 | 0x79, 0x22, 0x00, 0x12, 0x50, 0x0a, 0x08, 0x50, 0x69, 0x6e, 0x67, 0x4c, 0x69, 0x73, 0x74, 0x12,
570 | 0x1c, 0x2e, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74,
571 | 0x6f, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e,
572 | 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e,
573 | 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
574 | 0x65, 0x22, 0x00, 0x30, 0x01, 0x12, 0x54, 0x0a, 0x0a, 0x50, 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72,
575 | 0x65, 0x61, 0x6d, 0x12, 0x1c, 0x2e, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74, 0x65, 0x73, 0x74,
576 | 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
577 | 0x74, 0x1a, 0x22, 0x2e, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72,
578 | 0x6f, 0x74, 0x6f, 0x2e, 0x4d, 0x75, 0x6c, 0x74, 0x69, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73,
579 | 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x12, 0x59, 0x0a, 0x0f, 0x50,
580 | 0x69, 0x6e, 0x67, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x45, 0x72, 0x72, 0x6f, 0x72, 0x12, 0x1c,
581 | 0x2e, 0x74, 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f,
582 | 0x2e, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x22, 0x2e, 0x74,
583 | 0x61, 0x6c, 0x6f, 0x73, 0x2e, 0x74, 0x65, 0x73, 0x74, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x2e, 0x4d,
584 | 0x75, 0x6c, 0x74, 0x69, 0x50, 0x69, 0x6e, 0x67, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65,
585 | 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
586 | }
587 |
588 | var (
589 | file_test_proto_rawDescOnce sync.Once
590 | file_test_proto_rawDescData = file_test_proto_rawDesc
591 | )
592 |
593 | func file_test_proto_rawDescGZIP() []byte {
594 | file_test_proto_rawDescOnce.Do(func() {
595 | file_test_proto_rawDescData = protoimpl.X.CompressGZIP(file_test_proto_rawDescData)
596 | })
597 | return file_test_proto_rawDescData
598 | }
599 |
600 | var (
601 | file_test_proto_msgTypes = make([]protoimpl.MessageInfo, 9)
602 | file_test_proto_goTypes = []interface{}{
603 | (*Empty)(nil), // 0: talos.testproto.Empty
604 | (*PingRequest)(nil), // 1: talos.testproto.PingRequest
605 | (*PingResponse)(nil), // 2: talos.testproto.PingResponse
606 | (*ResponseMetadata)(nil), // 3: talos.testproto.ResponseMetadata
607 | (*ResponseMetadataPrepender)(nil), // 4: talos.testproto.ResponseMetadataPrepender
608 | (*MultiPingResponse)(nil), // 5: talos.testproto.MultiPingResponse
609 | (*MultiPingReply)(nil), // 6: talos.testproto.MultiPingReply
610 | (*EmptyReply)(nil), // 7: talos.testproto.EmptyReply
611 | (*EmptyResponse)(nil), // 8: talos.testproto.EmptyResponse
612 | }
613 | )
614 | var file_test_proto_depIdxs = []int32{
615 | 3, // 0: talos.testproto.ResponseMetadataPrepender.metadata:type_name -> talos.testproto.ResponseMetadata
616 | 3, // 1: talos.testproto.MultiPingResponse.metadata:type_name -> talos.testproto.ResponseMetadata
617 | 5, // 2: talos.testproto.MultiPingReply.response:type_name -> talos.testproto.MultiPingResponse
618 | 8, // 3: talos.testproto.EmptyReply.response:type_name -> talos.testproto.EmptyResponse
619 | 3, // 4: talos.testproto.EmptyResponse.metadata:type_name -> talos.testproto.ResponseMetadata
620 | 0, // 5: talos.testproto.TestService.PingEmpty:input_type -> talos.testproto.Empty
621 | 1, // 6: talos.testproto.TestService.Ping:input_type -> talos.testproto.PingRequest
622 | 1, // 7: talos.testproto.TestService.PingError:input_type -> talos.testproto.PingRequest
623 | 1, // 8: talos.testproto.TestService.PingList:input_type -> talos.testproto.PingRequest
624 | 1, // 9: talos.testproto.TestService.PingStream:input_type -> talos.testproto.PingRequest
625 | 0, // 10: talos.testproto.MultiService.PingEmpty:input_type -> talos.testproto.Empty
626 | 1, // 11: talos.testproto.MultiService.Ping:input_type -> talos.testproto.PingRequest
627 | 1, // 12: talos.testproto.MultiService.PingError:input_type -> talos.testproto.PingRequest
628 | 1, // 13: talos.testproto.MultiService.PingList:input_type -> talos.testproto.PingRequest
629 | 1, // 14: talos.testproto.MultiService.PingStream:input_type -> talos.testproto.PingRequest
630 | 1, // 15: talos.testproto.MultiService.PingStreamError:input_type -> talos.testproto.PingRequest
631 | 2, // 16: talos.testproto.TestService.PingEmpty:output_type -> talos.testproto.PingResponse
632 | 2, // 17: talos.testproto.TestService.Ping:output_type -> talos.testproto.PingResponse
633 | 0, // 18: talos.testproto.TestService.PingError:output_type -> talos.testproto.Empty
634 | 2, // 19: talos.testproto.TestService.PingList:output_type -> talos.testproto.PingResponse
635 | 2, // 20: talos.testproto.TestService.PingStream:output_type -> talos.testproto.PingResponse
636 | 6, // 21: talos.testproto.MultiService.PingEmpty:output_type -> talos.testproto.MultiPingReply
637 | 6, // 22: talos.testproto.MultiService.Ping:output_type -> talos.testproto.MultiPingReply
638 | 7, // 23: talos.testproto.MultiService.PingError:output_type -> talos.testproto.EmptyReply
639 | 5, // 24: talos.testproto.MultiService.PingList:output_type -> talos.testproto.MultiPingResponse
640 | 5, // 25: talos.testproto.MultiService.PingStream:output_type -> talos.testproto.MultiPingResponse
641 | 5, // 26: talos.testproto.MultiService.PingStreamError:output_type -> talos.testproto.MultiPingResponse
642 | 16, // [16:27] is the sub-list for method output_type
643 | 5, // [5:16] is the sub-list for method input_type
644 | 5, // [5:5] is the sub-list for extension type_name
645 | 5, // [5:5] is the sub-list for extension extendee
646 | 0, // [0:5] is the sub-list for field type_name
647 | }
648 |
649 | func init() { file_test_proto_init() }
650 | func file_test_proto_init() {
651 | if File_test_proto != nil {
652 | return
653 | }
654 | if !protoimpl.UnsafeEnabled {
655 | file_test_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
656 | switch v := v.(*Empty); i {
657 | case 0:
658 | return &v.state
659 | case 1:
660 | return &v.sizeCache
661 | case 2:
662 | return &v.unknownFields
663 | default:
664 | return nil
665 | }
666 | }
667 | file_test_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
668 | switch v := v.(*PingRequest); i {
669 | case 0:
670 | return &v.state
671 | case 1:
672 | return &v.sizeCache
673 | case 2:
674 | return &v.unknownFields
675 | default:
676 | return nil
677 | }
678 | }
679 | file_test_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
680 | switch v := v.(*PingResponse); i {
681 | case 0:
682 | return &v.state
683 | case 1:
684 | return &v.sizeCache
685 | case 2:
686 | return &v.unknownFields
687 | default:
688 | return nil
689 | }
690 | }
691 | file_test_proto_msgTypes[3].Exporter = func(v interface{}, i int) interface{} {
692 | switch v := v.(*ResponseMetadata); i {
693 | case 0:
694 | return &v.state
695 | case 1:
696 | return &v.sizeCache
697 | case 2:
698 | return &v.unknownFields
699 | default:
700 | return nil
701 | }
702 | }
703 | file_test_proto_msgTypes[4].Exporter = func(v interface{}, i int) interface{} {
704 | switch v := v.(*ResponseMetadataPrepender); i {
705 | case 0:
706 | return &v.state
707 | case 1:
708 | return &v.sizeCache
709 | case 2:
710 | return &v.unknownFields
711 | default:
712 | return nil
713 | }
714 | }
715 | file_test_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
716 | switch v := v.(*MultiPingResponse); i {
717 | case 0:
718 | return &v.state
719 | case 1:
720 | return &v.sizeCache
721 | case 2:
722 | return &v.unknownFields
723 | default:
724 | return nil
725 | }
726 | }
727 | file_test_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
728 | switch v := v.(*MultiPingReply); i {
729 | case 0:
730 | return &v.state
731 | case 1:
732 | return &v.sizeCache
733 | case 2:
734 | return &v.unknownFields
735 | default:
736 | return nil
737 | }
738 | }
739 | file_test_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
740 | switch v := v.(*EmptyReply); i {
741 | case 0:
742 | return &v.state
743 | case 1:
744 | return &v.sizeCache
745 | case 2:
746 | return &v.unknownFields
747 | default:
748 | return nil
749 | }
750 | }
751 | file_test_proto_msgTypes[8].Exporter = func(v interface{}, i int) interface{} {
752 | switch v := v.(*EmptyResponse); i {
753 | case 0:
754 | return &v.state
755 | case 1:
756 | return &v.sizeCache
757 | case 2:
758 | return &v.unknownFields
759 | default:
760 | return nil
761 | }
762 | }
763 | }
764 | type x struct{}
765 | out := protoimpl.TypeBuilder{
766 | File: protoimpl.DescBuilder{
767 | GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
768 | RawDescriptor: file_test_proto_rawDesc,
769 | NumEnums: 0,
770 | NumMessages: 9,
771 | NumExtensions: 0,
772 | NumServices: 2,
773 | },
774 | GoTypes: file_test_proto_goTypes,
775 | DependencyIndexes: file_test_proto_depIdxs,
776 | MessageInfos: file_test_proto_msgTypes,
777 | }.Build()
778 | File_test_proto = out.File
779 | file_test_proto_rawDesc = nil
780 | file_test_proto_goTypes = nil
781 | file_test_proto_depIdxs = nil
782 | }
783 |
--------------------------------------------------------------------------------
/testservice/test.proto:
--------------------------------------------------------------------------------
1 | syntax = "proto3";
2 |
3 | package talos.testproto;
4 |
5 | message Empty {}
6 |
7 | message PingRequest { string value = 1; }
8 |
9 | message PingResponse {
10 | string Value = 1;
11 | int32 counter = 2;
12 | }
13 |
14 | service TestService {
15 | rpc PingEmpty(Empty) returns (PingResponse) {}
16 |
17 | rpc Ping(PingRequest) returns (PingResponse) {}
18 |
19 | rpc PingError(PingRequest) returns (Empty) {}
20 |
21 | rpc PingList(PingRequest) returns (stream PingResponse) {}
22 |
23 | rpc PingStream(stream PingRequest) returns (stream PingResponse) {}
24 | }
25 |
26 | message ResponseMetadata {
27 | string hostname = 99;
28 | string upstream_error = 100;
29 | }
30 |
31 | message ResponseMetadataPrepender { ResponseMetadata metadata = 99; }
32 |
33 | message MultiPingResponse {
34 | ResponseMetadata metadata = 99;
35 | string Value = 1;
36 | int32 counter = 2;
37 | string server = 3;
38 | }
39 |
40 | message MultiPingReply { repeated MultiPingResponse response = 1; }
41 |
42 | message EmptyReply { repeated EmptyResponse response = 1; }
43 |
44 | message EmptyResponse { ResponseMetadata metadata = 99; }
45 |
46 | service MultiService {
47 | rpc PingEmpty(Empty) returns (MultiPingReply) {}
48 |
49 | rpc Ping(PingRequest) returns (MultiPingReply) {}
50 |
51 | rpc PingError(PingRequest) returns (EmptyReply) {}
52 |
53 | rpc PingList(PingRequest) returns (stream MultiPingResponse) {}
54 |
55 | rpc PingStream(stream PingRequest) returns (stream MultiPingResponse) {}
56 |
57 | rpc PingStreamError(stream PingRequest) returns (stream MultiPingResponse) {}
58 | }
59 |
--------------------------------------------------------------------------------
/testservice/test_grpc.pb.go:
--------------------------------------------------------------------------------
1 | // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
2 |
3 | package talos_testproto
4 |
5 | import (
6 | context "context"
7 |
8 | grpc "google.golang.org/grpc"
9 | codes "google.golang.org/grpc/codes"
10 | status "google.golang.org/grpc/status"
11 | )
12 |
13 | // This is a compile-time assertion to ensure that this generated file
14 | // is compatible with the grpc package it is being compiled against.
15 | // Requires gRPC-Go v1.32.0 or later.
16 | const _ = grpc.SupportPackageIsVersion7
17 |
18 | // TestServiceClient is the client API for TestService service.
19 | //
20 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
21 | type TestServiceClient interface {
22 | PingEmpty(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*PingResponse, error)
23 | Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error)
24 | PingError(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*Empty, error)
25 | PingList(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (TestService_PingListClient, error)
26 | PingStream(ctx context.Context, opts ...grpc.CallOption) (TestService_PingStreamClient, error)
27 | }
28 |
29 | type testServiceClient struct {
30 | cc grpc.ClientConnInterface
31 | }
32 |
33 | func NewTestServiceClient(cc grpc.ClientConnInterface) TestServiceClient {
34 | return &testServiceClient{cc}
35 | }
36 |
37 | func (c *testServiceClient) PingEmpty(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*PingResponse, error) {
38 | out := new(PingResponse)
39 | err := c.cc.Invoke(ctx, "/talos.testproto.TestService/PingEmpty", in, out, opts...)
40 | if err != nil {
41 | return nil, err
42 | }
43 | return out, nil
44 | }
45 |
46 | func (c *testServiceClient) Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*PingResponse, error) {
47 | out := new(PingResponse)
48 | err := c.cc.Invoke(ctx, "/talos.testproto.TestService/Ping", in, out, opts...)
49 | if err != nil {
50 | return nil, err
51 | }
52 | return out, nil
53 | }
54 |
55 | func (c *testServiceClient) PingError(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*Empty, error) {
56 | out := new(Empty)
57 | err := c.cc.Invoke(ctx, "/talos.testproto.TestService/PingError", in, out, opts...)
58 | if err != nil {
59 | return nil, err
60 | }
61 | return out, nil
62 | }
63 |
64 | func (c *testServiceClient) PingList(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (TestService_PingListClient, error) {
65 | stream, err := c.cc.NewStream(ctx, &TestService_ServiceDesc.Streams[0], "/talos.testproto.TestService/PingList", opts...)
66 | if err != nil {
67 | return nil, err
68 | }
69 | x := &testServicePingListClient{stream}
70 | if err := x.ClientStream.SendMsg(in); err != nil {
71 | return nil, err
72 | }
73 | if err := x.ClientStream.CloseSend(); err != nil {
74 | return nil, err
75 | }
76 | return x, nil
77 | }
78 |
79 | type TestService_PingListClient interface {
80 | Recv() (*PingResponse, error)
81 | grpc.ClientStream
82 | }
83 |
84 | type testServicePingListClient struct {
85 | grpc.ClientStream
86 | }
87 |
88 | func (x *testServicePingListClient) Recv() (*PingResponse, error) {
89 | m := new(PingResponse)
90 | if err := x.ClientStream.RecvMsg(m); err != nil {
91 | return nil, err
92 | }
93 | return m, nil
94 | }
95 |
96 | func (c *testServiceClient) PingStream(ctx context.Context, opts ...grpc.CallOption) (TestService_PingStreamClient, error) {
97 | stream, err := c.cc.NewStream(ctx, &TestService_ServiceDesc.Streams[1], "/talos.testproto.TestService/PingStream", opts...)
98 | if err != nil {
99 | return nil, err
100 | }
101 | x := &testServicePingStreamClient{stream}
102 | return x, nil
103 | }
104 |
105 | type TestService_PingStreamClient interface {
106 | Send(*PingRequest) error
107 | Recv() (*PingResponse, error)
108 | grpc.ClientStream
109 | }
110 |
111 | type testServicePingStreamClient struct {
112 | grpc.ClientStream
113 | }
114 |
115 | func (x *testServicePingStreamClient) Send(m *PingRequest) error {
116 | return x.ClientStream.SendMsg(m)
117 | }
118 |
119 | func (x *testServicePingStreamClient) Recv() (*PingResponse, error) {
120 | m := new(PingResponse)
121 | if err := x.ClientStream.RecvMsg(m); err != nil {
122 | return nil, err
123 | }
124 | return m, nil
125 | }
126 |
127 | // TestServiceServer is the server API for TestService service.
128 | // All implementations must embed UnimplementedTestServiceServer
129 | // for forward compatibility
130 | type TestServiceServer interface {
131 | PingEmpty(context.Context, *Empty) (*PingResponse, error)
132 | Ping(context.Context, *PingRequest) (*PingResponse, error)
133 | PingError(context.Context, *PingRequest) (*Empty, error)
134 | PingList(*PingRequest, TestService_PingListServer) error
135 | PingStream(TestService_PingStreamServer) error
136 | mustEmbedUnimplementedTestServiceServer()
137 | }
138 |
139 | // UnimplementedTestServiceServer must be embedded to have forward compatible implementations.
140 | type UnimplementedTestServiceServer struct{}
141 |
142 | func (UnimplementedTestServiceServer) PingEmpty(context.Context, *Empty) (*PingResponse, error) {
143 | return nil, status.Errorf(codes.Unimplemented, "method PingEmpty not implemented")
144 | }
145 |
146 | func (UnimplementedTestServiceServer) Ping(context.Context, *PingRequest) (*PingResponse, error) {
147 | return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented")
148 | }
149 |
150 | func (UnimplementedTestServiceServer) PingError(context.Context, *PingRequest) (*Empty, error) {
151 | return nil, status.Errorf(codes.Unimplemented, "method PingError not implemented")
152 | }
153 |
154 | func (UnimplementedTestServiceServer) PingList(*PingRequest, TestService_PingListServer) error {
155 | return status.Errorf(codes.Unimplemented, "method PingList not implemented")
156 | }
157 |
158 | func (UnimplementedTestServiceServer) PingStream(TestService_PingStreamServer) error {
159 | return status.Errorf(codes.Unimplemented, "method PingStream not implemented")
160 | }
161 | func (UnimplementedTestServiceServer) mustEmbedUnimplementedTestServiceServer() {}
162 |
163 | // UnsafeTestServiceServer may be embedded to opt out of forward compatibility for this service.
164 | // Use of this interface is not recommended, as added methods to TestServiceServer will
165 | // result in compilation errors.
166 | type UnsafeTestServiceServer interface {
167 | mustEmbedUnimplementedTestServiceServer()
168 | }
169 |
170 | func RegisterTestServiceServer(s grpc.ServiceRegistrar, srv TestServiceServer) {
171 | s.RegisterService(&TestService_ServiceDesc, srv)
172 | }
173 |
174 | func _TestService_PingEmpty_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
175 | in := new(Empty)
176 | if err := dec(in); err != nil {
177 | return nil, err
178 | }
179 | if interceptor == nil {
180 | return srv.(TestServiceServer).PingEmpty(ctx, in)
181 | }
182 | info := &grpc.UnaryServerInfo{
183 | Server: srv,
184 | FullMethod: "/talos.testproto.TestService/PingEmpty",
185 | }
186 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
187 | return srv.(TestServiceServer).PingEmpty(ctx, req.(*Empty))
188 | }
189 | return interceptor(ctx, in, info, handler)
190 | }
191 |
192 | func _TestService_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
193 | in := new(PingRequest)
194 | if err := dec(in); err != nil {
195 | return nil, err
196 | }
197 | if interceptor == nil {
198 | return srv.(TestServiceServer).Ping(ctx, in)
199 | }
200 | info := &grpc.UnaryServerInfo{
201 | Server: srv,
202 | FullMethod: "/talos.testproto.TestService/Ping",
203 | }
204 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
205 | return srv.(TestServiceServer).Ping(ctx, req.(*PingRequest))
206 | }
207 | return interceptor(ctx, in, info, handler)
208 | }
209 |
210 | func _TestService_PingError_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
211 | in := new(PingRequest)
212 | if err := dec(in); err != nil {
213 | return nil, err
214 | }
215 | if interceptor == nil {
216 | return srv.(TestServiceServer).PingError(ctx, in)
217 | }
218 | info := &grpc.UnaryServerInfo{
219 | Server: srv,
220 | FullMethod: "/talos.testproto.TestService/PingError",
221 | }
222 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
223 | return srv.(TestServiceServer).PingError(ctx, req.(*PingRequest))
224 | }
225 | return interceptor(ctx, in, info, handler)
226 | }
227 |
228 | func _TestService_PingList_Handler(srv interface{}, stream grpc.ServerStream) error {
229 | m := new(PingRequest)
230 | if err := stream.RecvMsg(m); err != nil {
231 | return err
232 | }
233 | return srv.(TestServiceServer).PingList(m, &testServicePingListServer{stream})
234 | }
235 |
236 | type TestService_PingListServer interface {
237 | Send(*PingResponse) error
238 | grpc.ServerStream
239 | }
240 |
241 | type testServicePingListServer struct {
242 | grpc.ServerStream
243 | }
244 |
245 | func (x *testServicePingListServer) Send(m *PingResponse) error {
246 | return x.ServerStream.SendMsg(m)
247 | }
248 |
249 | func _TestService_PingStream_Handler(srv interface{}, stream grpc.ServerStream) error {
250 | return srv.(TestServiceServer).PingStream(&testServicePingStreamServer{stream})
251 | }
252 |
253 | type TestService_PingStreamServer interface {
254 | Send(*PingResponse) error
255 | Recv() (*PingRequest, error)
256 | grpc.ServerStream
257 | }
258 |
259 | type testServicePingStreamServer struct {
260 | grpc.ServerStream
261 | }
262 |
263 | func (x *testServicePingStreamServer) Send(m *PingResponse) error {
264 | return x.ServerStream.SendMsg(m)
265 | }
266 |
267 | func (x *testServicePingStreamServer) Recv() (*PingRequest, error) {
268 | m := new(PingRequest)
269 | if err := x.ServerStream.RecvMsg(m); err != nil {
270 | return nil, err
271 | }
272 | return m, nil
273 | }
274 |
275 | // TestService_ServiceDesc is the grpc.ServiceDesc for TestService service.
276 | // It's only intended for direct use with grpc.RegisterService,
277 | // and not to be introspected or modified (even as a copy)
278 | var TestService_ServiceDesc = grpc.ServiceDesc{
279 | ServiceName: "talos.testproto.TestService",
280 | HandlerType: (*TestServiceServer)(nil),
281 | Methods: []grpc.MethodDesc{
282 | {
283 | MethodName: "PingEmpty",
284 | Handler: _TestService_PingEmpty_Handler,
285 | },
286 | {
287 | MethodName: "Ping",
288 | Handler: _TestService_Ping_Handler,
289 | },
290 | {
291 | MethodName: "PingError",
292 | Handler: _TestService_PingError_Handler,
293 | },
294 | },
295 | Streams: []grpc.StreamDesc{
296 | {
297 | StreamName: "PingList",
298 | Handler: _TestService_PingList_Handler,
299 | ServerStreams: true,
300 | },
301 | {
302 | StreamName: "PingStream",
303 | Handler: _TestService_PingStream_Handler,
304 | ServerStreams: true,
305 | ClientStreams: true,
306 | },
307 | },
308 | Metadata: "test.proto",
309 | }
310 |
311 | // MultiServiceClient is the client API for MultiService service.
312 | //
313 | // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
314 | type MultiServiceClient interface {
315 | PingEmpty(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*MultiPingReply, error)
316 | Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*MultiPingReply, error)
317 | PingError(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*EmptyReply, error)
318 | PingList(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (MultiService_PingListClient, error)
319 | PingStream(ctx context.Context, opts ...grpc.CallOption) (MultiService_PingStreamClient, error)
320 | PingStreamError(ctx context.Context, opts ...grpc.CallOption) (MultiService_PingStreamErrorClient, error)
321 | }
322 |
323 | type multiServiceClient struct {
324 | cc grpc.ClientConnInterface
325 | }
326 |
327 | func NewMultiServiceClient(cc grpc.ClientConnInterface) MultiServiceClient {
328 | return &multiServiceClient{cc}
329 | }
330 |
331 | func (c *multiServiceClient) PingEmpty(ctx context.Context, in *Empty, opts ...grpc.CallOption) (*MultiPingReply, error) {
332 | out := new(MultiPingReply)
333 | err := c.cc.Invoke(ctx, "/talos.testproto.MultiService/PingEmpty", in, out, opts...)
334 | if err != nil {
335 | return nil, err
336 | }
337 | return out, nil
338 | }
339 |
340 | func (c *multiServiceClient) Ping(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*MultiPingReply, error) {
341 | out := new(MultiPingReply)
342 | err := c.cc.Invoke(ctx, "/talos.testproto.MultiService/Ping", in, out, opts...)
343 | if err != nil {
344 | return nil, err
345 | }
346 | return out, nil
347 | }
348 |
349 | func (c *multiServiceClient) PingError(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (*EmptyReply, error) {
350 | out := new(EmptyReply)
351 | err := c.cc.Invoke(ctx, "/talos.testproto.MultiService/PingError", in, out, opts...)
352 | if err != nil {
353 | return nil, err
354 | }
355 | return out, nil
356 | }
357 |
358 | func (c *multiServiceClient) PingList(ctx context.Context, in *PingRequest, opts ...grpc.CallOption) (MultiService_PingListClient, error) {
359 | stream, err := c.cc.NewStream(ctx, &MultiService_ServiceDesc.Streams[0], "/talos.testproto.MultiService/PingList", opts...)
360 | if err != nil {
361 | return nil, err
362 | }
363 | x := &multiServicePingListClient{stream}
364 | if err := x.ClientStream.SendMsg(in); err != nil {
365 | return nil, err
366 | }
367 | if err := x.ClientStream.CloseSend(); err != nil {
368 | return nil, err
369 | }
370 | return x, nil
371 | }
372 |
373 | type MultiService_PingListClient interface {
374 | Recv() (*MultiPingResponse, error)
375 | grpc.ClientStream
376 | }
377 |
378 | type multiServicePingListClient struct {
379 | grpc.ClientStream
380 | }
381 |
382 | func (x *multiServicePingListClient) Recv() (*MultiPingResponse, error) {
383 | m := new(MultiPingResponse)
384 | if err := x.ClientStream.RecvMsg(m); err != nil {
385 | return nil, err
386 | }
387 | return m, nil
388 | }
389 |
390 | func (c *multiServiceClient) PingStream(ctx context.Context, opts ...grpc.CallOption) (MultiService_PingStreamClient, error) {
391 | stream, err := c.cc.NewStream(ctx, &MultiService_ServiceDesc.Streams[1], "/talos.testproto.MultiService/PingStream", opts...)
392 | if err != nil {
393 | return nil, err
394 | }
395 | x := &multiServicePingStreamClient{stream}
396 | return x, nil
397 | }
398 |
399 | type MultiService_PingStreamClient interface {
400 | Send(*PingRequest) error
401 | Recv() (*MultiPingResponse, error)
402 | grpc.ClientStream
403 | }
404 |
405 | type multiServicePingStreamClient struct {
406 | grpc.ClientStream
407 | }
408 |
409 | func (x *multiServicePingStreamClient) Send(m *PingRequest) error {
410 | return x.ClientStream.SendMsg(m)
411 | }
412 |
413 | func (x *multiServicePingStreamClient) Recv() (*MultiPingResponse, error) {
414 | m := new(MultiPingResponse)
415 | if err := x.ClientStream.RecvMsg(m); err != nil {
416 | return nil, err
417 | }
418 | return m, nil
419 | }
420 |
421 | func (c *multiServiceClient) PingStreamError(ctx context.Context, opts ...grpc.CallOption) (MultiService_PingStreamErrorClient, error) {
422 | stream, err := c.cc.NewStream(ctx, &MultiService_ServiceDesc.Streams[2], "/talos.testproto.MultiService/PingStreamError", opts...)
423 | if err != nil {
424 | return nil, err
425 | }
426 | x := &multiServicePingStreamErrorClient{stream}
427 | return x, nil
428 | }
429 |
430 | type MultiService_PingStreamErrorClient interface {
431 | Send(*PingRequest) error
432 | Recv() (*MultiPingResponse, error)
433 | grpc.ClientStream
434 | }
435 |
436 | type multiServicePingStreamErrorClient struct {
437 | grpc.ClientStream
438 | }
439 |
440 | func (x *multiServicePingStreamErrorClient) Send(m *PingRequest) error {
441 | return x.ClientStream.SendMsg(m)
442 | }
443 |
444 | func (x *multiServicePingStreamErrorClient) Recv() (*MultiPingResponse, error) {
445 | m := new(MultiPingResponse)
446 | if err := x.ClientStream.RecvMsg(m); err != nil {
447 | return nil, err
448 | }
449 | return m, nil
450 | }
451 |
452 | // MultiServiceServer is the server API for MultiService service.
453 | // All implementations must embed UnimplementedMultiServiceServer
454 | // for forward compatibility
455 | type MultiServiceServer interface {
456 | PingEmpty(context.Context, *Empty) (*MultiPingReply, error)
457 | Ping(context.Context, *PingRequest) (*MultiPingReply, error)
458 | PingError(context.Context, *PingRequest) (*EmptyReply, error)
459 | PingList(*PingRequest, MultiService_PingListServer) error
460 | PingStream(MultiService_PingStreamServer) error
461 | PingStreamError(MultiService_PingStreamErrorServer) error
462 | mustEmbedUnimplementedMultiServiceServer()
463 | }
464 |
465 | // UnimplementedMultiServiceServer must be embedded to have forward compatible implementations.
466 | type UnimplementedMultiServiceServer struct{}
467 |
468 | func (UnimplementedMultiServiceServer) PingEmpty(context.Context, *Empty) (*MultiPingReply, error) {
469 | return nil, status.Errorf(codes.Unimplemented, "method PingEmpty not implemented")
470 | }
471 |
472 | func (UnimplementedMultiServiceServer) Ping(context.Context, *PingRequest) (*MultiPingReply, error) {
473 | return nil, status.Errorf(codes.Unimplemented, "method Ping not implemented")
474 | }
475 |
476 | func (UnimplementedMultiServiceServer) PingError(context.Context, *PingRequest) (*EmptyReply, error) {
477 | return nil, status.Errorf(codes.Unimplemented, "method PingError not implemented")
478 | }
479 |
480 | func (UnimplementedMultiServiceServer) PingList(*PingRequest, MultiService_PingListServer) error {
481 | return status.Errorf(codes.Unimplemented, "method PingList not implemented")
482 | }
483 |
484 | func (UnimplementedMultiServiceServer) PingStream(MultiService_PingStreamServer) error {
485 | return status.Errorf(codes.Unimplemented, "method PingStream not implemented")
486 | }
487 |
488 | func (UnimplementedMultiServiceServer) PingStreamError(MultiService_PingStreamErrorServer) error {
489 | return status.Errorf(codes.Unimplemented, "method PingStreamError not implemented")
490 | }
491 | func (UnimplementedMultiServiceServer) mustEmbedUnimplementedMultiServiceServer() {}
492 |
493 | // UnsafeMultiServiceServer may be embedded to opt out of forward compatibility for this service.
494 | // Use of this interface is not recommended, as added methods to MultiServiceServer will
495 | // result in compilation errors.
496 | type UnsafeMultiServiceServer interface {
497 | mustEmbedUnimplementedMultiServiceServer()
498 | }
499 |
500 | func RegisterMultiServiceServer(s grpc.ServiceRegistrar, srv MultiServiceServer) {
501 | s.RegisterService(&MultiService_ServiceDesc, srv)
502 | }
503 |
504 | func _MultiService_PingEmpty_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
505 | in := new(Empty)
506 | if err := dec(in); err != nil {
507 | return nil, err
508 | }
509 | if interceptor == nil {
510 | return srv.(MultiServiceServer).PingEmpty(ctx, in)
511 | }
512 | info := &grpc.UnaryServerInfo{
513 | Server: srv,
514 | FullMethod: "/talos.testproto.MultiService/PingEmpty",
515 | }
516 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
517 | return srv.(MultiServiceServer).PingEmpty(ctx, req.(*Empty))
518 | }
519 | return interceptor(ctx, in, info, handler)
520 | }
521 |
522 | func _MultiService_Ping_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
523 | in := new(PingRequest)
524 | if err := dec(in); err != nil {
525 | return nil, err
526 | }
527 | if interceptor == nil {
528 | return srv.(MultiServiceServer).Ping(ctx, in)
529 | }
530 | info := &grpc.UnaryServerInfo{
531 | Server: srv,
532 | FullMethod: "/talos.testproto.MultiService/Ping",
533 | }
534 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
535 | return srv.(MultiServiceServer).Ping(ctx, req.(*PingRequest))
536 | }
537 | return interceptor(ctx, in, info, handler)
538 | }
539 |
540 | func _MultiService_PingError_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
541 | in := new(PingRequest)
542 | if err := dec(in); err != nil {
543 | return nil, err
544 | }
545 | if interceptor == nil {
546 | return srv.(MultiServiceServer).PingError(ctx, in)
547 | }
548 | info := &grpc.UnaryServerInfo{
549 | Server: srv,
550 | FullMethod: "/talos.testproto.MultiService/PingError",
551 | }
552 | handler := func(ctx context.Context, req interface{}) (interface{}, error) {
553 | return srv.(MultiServiceServer).PingError(ctx, req.(*PingRequest))
554 | }
555 | return interceptor(ctx, in, info, handler)
556 | }
557 |
558 | func _MultiService_PingList_Handler(srv interface{}, stream grpc.ServerStream) error {
559 | m := new(PingRequest)
560 | if err := stream.RecvMsg(m); err != nil {
561 | return err
562 | }
563 | return srv.(MultiServiceServer).PingList(m, &multiServicePingListServer{stream})
564 | }
565 |
566 | type MultiService_PingListServer interface {
567 | Send(*MultiPingResponse) error
568 | grpc.ServerStream
569 | }
570 |
571 | type multiServicePingListServer struct {
572 | grpc.ServerStream
573 | }
574 |
575 | func (x *multiServicePingListServer) Send(m *MultiPingResponse) error {
576 | return x.ServerStream.SendMsg(m)
577 | }
578 |
579 | func _MultiService_PingStream_Handler(srv interface{}, stream grpc.ServerStream) error {
580 | return srv.(MultiServiceServer).PingStream(&multiServicePingStreamServer{stream})
581 | }
582 |
583 | type MultiService_PingStreamServer interface {
584 | Send(*MultiPingResponse) error
585 | Recv() (*PingRequest, error)
586 | grpc.ServerStream
587 | }
588 |
589 | type multiServicePingStreamServer struct {
590 | grpc.ServerStream
591 | }
592 |
593 | func (x *multiServicePingStreamServer) Send(m *MultiPingResponse) error {
594 | return x.ServerStream.SendMsg(m)
595 | }
596 |
597 | func (x *multiServicePingStreamServer) Recv() (*PingRequest, error) {
598 | m := new(PingRequest)
599 | if err := x.ServerStream.RecvMsg(m); err != nil {
600 | return nil, err
601 | }
602 | return m, nil
603 | }
604 |
605 | func _MultiService_PingStreamError_Handler(srv interface{}, stream grpc.ServerStream) error {
606 | return srv.(MultiServiceServer).PingStreamError(&multiServicePingStreamErrorServer{stream})
607 | }
608 |
609 | type MultiService_PingStreamErrorServer interface {
610 | Send(*MultiPingResponse) error
611 | Recv() (*PingRequest, error)
612 | grpc.ServerStream
613 | }
614 |
615 | type multiServicePingStreamErrorServer struct {
616 | grpc.ServerStream
617 | }
618 |
619 | func (x *multiServicePingStreamErrorServer) Send(m *MultiPingResponse) error {
620 | return x.ServerStream.SendMsg(m)
621 | }
622 |
623 | func (x *multiServicePingStreamErrorServer) Recv() (*PingRequest, error) {
624 | m := new(PingRequest)
625 | if err := x.ServerStream.RecvMsg(m); err != nil {
626 | return nil, err
627 | }
628 | return m, nil
629 | }
630 |
631 | // MultiService_ServiceDesc is the grpc.ServiceDesc for MultiService service.
632 | // It's only intended for direct use with grpc.RegisterService,
633 | // and not to be introspected or modified (even as a copy)
634 | var MultiService_ServiceDesc = grpc.ServiceDesc{
635 | ServiceName: "talos.testproto.MultiService",
636 | HandlerType: (*MultiServiceServer)(nil),
637 | Methods: []grpc.MethodDesc{
638 | {
639 | MethodName: "PingEmpty",
640 | Handler: _MultiService_PingEmpty_Handler,
641 | },
642 | {
643 | MethodName: "Ping",
644 | Handler: _MultiService_Ping_Handler,
645 | },
646 | {
647 | MethodName: "PingError",
648 | Handler: _MultiService_PingError_Handler,
649 | },
650 | },
651 | Streams: []grpc.StreamDesc{
652 | {
653 | StreamName: "PingList",
654 | Handler: _MultiService_PingList_Handler,
655 | ServerStreams: true,
656 | },
657 | {
658 | StreamName: "PingStream",
659 | Handler: _MultiService_PingStream_Handler,
660 | ServerStreams: true,
661 | ClientStreams: true,
662 | },
663 | {
664 | StreamName: "PingStreamError",
665 | Handler: _MultiService_PingStreamError_Handler,
666 | ServerStreams: true,
667 | ClientStreams: true,
668 | },
669 | },
670 | Metadata: "test.proto",
671 | }
672 |
--------------------------------------------------------------------------------