Drop etcd tests dependency

This commit is contained in:
Jordan Liggitt 2021-06-18 14:00:03 -04:00
parent 01760927b8
commit 730c21d386
57 changed files with 9 additions and 6874 deletions

View File

@ -1,206 +0,0 @@
= vendor/go.etcd.io/etcd/tests/v3 licensed under: =
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
= vendor/go.etcd.io/etcd/tests/v3/LICENSE 3b83ef96387f14655fc854ddc3c6bd57

7
go.mod
View File

@ -232,7 +232,6 @@ replace (
github.com/emicklei/go-restful => github.com/emicklei/go-restful v2.9.5+incompatible
github.com/envoyproxy/go-control-plane => github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d
github.com/envoyproxy/protoc-gen-validate => github.com/envoyproxy/protoc-gen-validate v0.1.0
github.com/etcd-io/gofail => github.com/etcd-io/gofail v0.0.0-20190801230047-ad7f989257ca
github.com/euank/go-kmsg-parser => github.com/euank/go-kmsg-parser v2.0.0+incompatible
github.com/evanphx/json-patch => github.com/evanphx/json-patch v4.11.0+incompatible
github.com/exponent-io/jsonpath => github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d
@ -334,7 +333,7 @@ replace (
github.com/mailru/easyjson => github.com/mailru/easyjson v0.7.6
github.com/mattn/go-colorable => github.com/mattn/go-colorable v0.0.9
github.com/mattn/go-isatty => github.com/mattn/go-isatty v0.0.3
github.com/mattn/go-runewidth => github.com/mattn/go-runewidth v0.0.9
github.com/mattn/go-runewidth => github.com/mattn/go-runewidth v0.0.7
github.com/matttproud/golang_protobuf_extensions => github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369
github.com/miekg/dns => github.com/miekg/dns v1.0.14
github.com/mindprince/gonvml => github.com/mindprince/gonvml v0.0.0-20190828220739-9ebdce4bb989
@ -362,7 +361,7 @@ replace (
github.com/mxk/go-flowrate => github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f
github.com/niemeyer/pretty => github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e
github.com/nxadm/tail => github.com/nxadm/tail v1.4.4
github.com/olekukonko/tablewriter => github.com/olekukonko/tablewriter v0.0.5
github.com/olekukonko/tablewriter => github.com/olekukonko/tablewriter v0.0.4
github.com/onsi/ginkgo => github.com/onsi/ginkgo v1.14.0
github.com/onsi/gomega => github.com/onsi/gomega v1.10.1
github.com/opencontainers/go-digest => github.com/opencontainers/go-digest v1.0.0
@ -426,11 +425,9 @@ replace (
go.etcd.io/etcd/client/pkg/v3 => go.etcd.io/etcd/client/pkg/v3 v3.5.0
go.etcd.io/etcd/client/v2 => go.etcd.io/etcd/client/v2 v2.305.0
go.etcd.io/etcd/client/v3 => go.etcd.io/etcd/client/v3 v3.5.0
go.etcd.io/etcd/etcdutl/v3 => go.etcd.io/etcd/etcdutl/v3 v3.5.0
go.etcd.io/etcd/pkg/v3 => go.etcd.io/etcd/pkg/v3 v3.5.0
go.etcd.io/etcd/raft/v3 => go.etcd.io/etcd/raft/v3 v3.5.0
go.etcd.io/etcd/server/v3 => go.etcd.io/etcd/server/v3 v3.5.0
go.etcd.io/etcd/tests/v3 => go.etcd.io/etcd/tests/v3 v3.5.0
go.opencensus.io => go.opencensus.io v0.22.3
go.opentelemetry.io/contrib => go.opentelemetry.io/contrib v0.20.0
go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc => go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0

8
go.sum
View File

@ -157,7 +157,6 @@ github.com/emicklei/go-restful v2.9.5+incompatible h1:spTtZBk5DYEvbxMVutUuTyh1Ao
github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/etcd-io/gofail v0.0.0-20190801230047-ad7f989257ca/go.mod h1:49H/RkXP8pKaZy4h0d+NW16rSLhyVBt4o6VLJbmOqDE=
github.com/euank/go-kmsg-parser v2.0.0+incompatible h1:cHD53+PLQuuQyLZeriD1V/esuG4MuU0Pjs5y6iknohY=
github.com/euank/go-kmsg-parser v2.0.0+incompatible/go.mod h1:MhmAMZ8V4CYH4ybgdRwPr2TU5ThnS43puaKEMpja1uw=
github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs=
@ -316,7 +315,7 @@ github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
@ -364,7 +363,7 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/olekukonko/tablewriter v0.0.4/go.mod h1:zq6QwlOf5SlnkVbMSr5EoBv3636FWnp+qbPhuoO21uA=
github.com/onsi/ginkgo v1.14.0 h1:2mOpI4JVVPBN+WQRa0WKH2eXR+Ey+uK4n7Zj0aYpIQA=
github.com/onsi/ginkgo v1.14.0/go.mod h1:iSB4RoI2tjJc9BBv4NKIKWKya62Rps+oPG/Lv9klQyY=
github.com/onsi/gomega v1.10.1 h1:o0+MgICZLuZ7xjH7Vx6zS/zcu93/BEp1VwkIW1mEXCE=
@ -473,15 +472,12 @@ go.etcd.io/etcd/client/v2 v2.305.0 h1:ftQ0nOOHMcbMS3KIaDQ0g5Qcd6bhaBrQT6b89DfwLT
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.etcd.io/etcd/client/v3 v3.5.0 h1:62Eh0XOro+rDwkrypAGDfgmNh5Joq+z+W9HZdlXMzek=
go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
go.etcd.io/etcd/etcdutl/v3 v3.5.0/go.mod h1:o98rKMCibbFAG8QS9KmvlYDGDShmmIbmRE8vSofzYNg=
go.etcd.io/etcd/pkg/v3 v3.5.0 h1:ntrg6vvKRW26JRmHTE0iNlDgYK6JX3hg/4cD62X0ixk=
go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE=
go.etcd.io/etcd/raft/v3 v3.5.0 h1:kw2TmO3yFTgE+F0mdKkG7xMxkit2duBDa2Hu6D/HMlw=
go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc=
go.etcd.io/etcd/server/v3 v3.5.0 h1:jk8D/lwGEDlQU9kZXUFMSANkE22Sg5+mW27ip8xcF9E=
go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4=
go.etcd.io/etcd/tests/v3 v3.5.0 h1:+uMuHYKKlLUzbW322XrQXbaGM9qiV7vUL+AEPT/KYY4=
go.etcd.io/etcd/tests/v3 v3.5.0/go.mod h1:f+mtZ1bE1YPvgKdOJV2BKy4JQW0nAFnQehgOE7+WyJE=
go.opencensus.io v0.22.3 h1:8sGtKOrtQqkN1bp2AtX+misvLIlOmsEsNd+9NIcPEm8=
go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=
go.opentelemetry.io/contrib v0.20.0 h1:ubFQUn0VCZ0gPwIoJfBJVpeBlyRMxu8Mm/huKWYd9p0=

View File

@ -115,7 +115,6 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/etcd-io/gofail v0.0.0-20190801230047-ad7f989257ca/go.mod h1:49H/RkXP8pKaZy4h0d+NW16rSLhyVBt4o6VLJbmOqDE=
github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs=
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@ -295,7 +294,6 @@ github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
@ -328,7 +326,6 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
@ -434,15 +431,12 @@ go.etcd.io/etcd/client/v2 v2.305.0 h1:ftQ0nOOHMcbMS3KIaDQ0g5Qcd6bhaBrQT6b89DfwLT
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.etcd.io/etcd/client/v3 v3.5.0 h1:62Eh0XOro+rDwkrypAGDfgmNh5Joq+z+W9HZdlXMzek=
go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
go.etcd.io/etcd/etcdutl/v3 v3.5.0/go.mod h1:o98rKMCibbFAG8QS9KmvlYDGDShmmIbmRE8vSofzYNg=
go.etcd.io/etcd/pkg/v3 v3.5.0 h1:ntrg6vvKRW26JRmHTE0iNlDgYK6JX3hg/4cD62X0ixk=
go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE=
go.etcd.io/etcd/raft/v3 v3.5.0 h1:kw2TmO3yFTgE+F0mdKkG7xMxkit2duBDa2Hu6D/HMlw=
go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc=
go.etcd.io/etcd/server/v3 v3.5.0 h1:jk8D/lwGEDlQU9kZXUFMSANkE22Sg5+mW27ip8xcF9E=
go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4=
go.etcd.io/etcd/tests/v3 v3.5.0 h1:+uMuHYKKlLUzbW322XrQXbaGM9qiV7vUL+AEPT/KYY4=
go.etcd.io/etcd/tests/v3 v3.5.0/go.mod h1:f+mtZ1bE1YPvgKdOJV2BKy4JQW0nAFnQehgOE7+WyJE=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=

View File

@ -19,7 +19,6 @@ require (
github.com/google/gofuzz v1.1.0
github.com/google/uuid v1.1.2
github.com/googleapis/gnostic v0.5.5
github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/hashicorp/golang-lru v0.5.1
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822
@ -30,7 +29,8 @@ require (
go.etcd.io/etcd/api/v3 v3.5.0
go.etcd.io/etcd/client/pkg/v3 v3.5.0
go.etcd.io/etcd/client/v3 v3.5.0
go.etcd.io/etcd/tests/v3 v3.5.0
go.etcd.io/etcd/server/v3 v3.5.0
go.uber.org/zap v1.17.0
golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c

View File

@ -115,7 +115,6 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/etcd-io/gofail v0.0.0-20190801230047-ad7f989257ca/go.mod h1:49H/RkXP8pKaZy4h0d+NW16rSLhyVBt4o6VLJbmOqDE=
github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs=
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@ -289,7 +288,6 @@ github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
@ -321,7 +319,6 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
@ -428,15 +425,12 @@ go.etcd.io/etcd/client/v2 v2.305.0 h1:ftQ0nOOHMcbMS3KIaDQ0g5Qcd6bhaBrQT6b89DfwLT
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.etcd.io/etcd/client/v3 v3.5.0 h1:62Eh0XOro+rDwkrypAGDfgmNh5Joq+z+W9HZdlXMzek=
go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
go.etcd.io/etcd/etcdutl/v3 v3.5.0/go.mod h1:o98rKMCibbFAG8QS9KmvlYDGDShmmIbmRE8vSofzYNg=
go.etcd.io/etcd/pkg/v3 v3.5.0 h1:ntrg6vvKRW26JRmHTE0iNlDgYK6JX3hg/4cD62X0ixk=
go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE=
go.etcd.io/etcd/raft/v3 v3.5.0 h1:kw2TmO3yFTgE+F0mdKkG7xMxkit2duBDa2Hu6D/HMlw=
go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc=
go.etcd.io/etcd/server/v3 v3.5.0 h1:jk8D/lwGEDlQU9kZXUFMSANkE22Sg5+mW27ip8xcF9E=
go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4=
go.etcd.io/etcd/tests/v3 v3.5.0 h1:+uMuHYKKlLUzbW322XrQXbaGM9qiV7vUL+AEPT/KYY4=
go.etcd.io/etcd/tests/v3 v3.5.0/go.mod h1:f+mtZ1bE1YPvgKdOJV2BKy4JQW0nAFnQehgOE7+WyJE=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=

View File

@ -110,7 +110,6 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/etcd-io/gofail v0.0.0-20190801230047-ad7f989257ca/go.mod h1:49H/RkXP8pKaZy4h0d+NW16rSLhyVBt4o6VLJbmOqDE=
github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs=
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@ -283,7 +282,6 @@ github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
@ -316,7 +314,6 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
@ -422,15 +419,12 @@ go.etcd.io/etcd/client/v2 v2.305.0 h1:ftQ0nOOHMcbMS3KIaDQ0g5Qcd6bhaBrQT6b89DfwLT
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.etcd.io/etcd/client/v3 v3.5.0 h1:62Eh0XOro+rDwkrypAGDfgmNh5Joq+z+W9HZdlXMzek=
go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
go.etcd.io/etcd/etcdutl/v3 v3.5.0/go.mod h1:o98rKMCibbFAG8QS9KmvlYDGDShmmIbmRE8vSofzYNg=
go.etcd.io/etcd/pkg/v3 v3.5.0 h1:ntrg6vvKRW26JRmHTE0iNlDgYK6JX3hg/4cD62X0ixk=
go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE=
go.etcd.io/etcd/raft/v3 v3.5.0 h1:kw2TmO3yFTgE+F0mdKkG7xMxkit2duBDa2Hu6D/HMlw=
go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc=
go.etcd.io/etcd/server/v3 v3.5.0 h1:jk8D/lwGEDlQU9kZXUFMSANkE22Sg5+mW27ip8xcF9E=
go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4=
go.etcd.io/etcd/tests/v3 v3.5.0 h1:+uMuHYKKlLUzbW322XrQXbaGM9qiV7vUL+AEPT/KYY4=
go.etcd.io/etcd/tests/v3 v3.5.0/go.mod h1:f+mtZ1bE1YPvgKdOJV2BKy4JQW0nAFnQehgOE7+WyJE=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=

View File

@ -145,7 +145,6 @@ github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.m
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/etcd-io/gofail v0.0.0-20190801230047-ad7f989257ca/go.mod h1:49H/RkXP8pKaZy4h0d+NW16rSLhyVBt4o6VLJbmOqDE=
github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs=
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@ -362,7 +361,6 @@ github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJ
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
@ -404,7 +402,6 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
@ -539,15 +536,12 @@ go.etcd.io/etcd/client/v2 v2.305.0 h1:ftQ0nOOHMcbMS3KIaDQ0g5Qcd6bhaBrQT6b89DfwLT
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.etcd.io/etcd/client/v3 v3.5.0 h1:62Eh0XOro+rDwkrypAGDfgmNh5Joq+z+W9HZdlXMzek=
go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
go.etcd.io/etcd/etcdutl/v3 v3.5.0/go.mod h1:o98rKMCibbFAG8QS9KmvlYDGDShmmIbmRE8vSofzYNg=
go.etcd.io/etcd/pkg/v3 v3.5.0 h1:ntrg6vvKRW26JRmHTE0iNlDgYK6JX3hg/4cD62X0ixk=
go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE=
go.etcd.io/etcd/raft/v3 v3.5.0 h1:kw2TmO3yFTgE+F0mdKkG7xMxkit2duBDa2Hu6D/HMlw=
go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc=
go.etcd.io/etcd/server/v3 v3.5.0 h1:jk8D/lwGEDlQU9kZXUFMSANkE22Sg5+mW27ip8xcF9E=
go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4=
go.etcd.io/etcd/tests/v3 v3.5.0 h1:+uMuHYKKlLUzbW322XrQXbaGM9qiV7vUL+AEPT/KYY4=
go.etcd.io/etcd/tests/v3 v3.5.0/go.mod h1:f+mtZ1bE1YPvgKdOJV2BKy4JQW0nAFnQehgOE7+WyJE=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=

View File

@ -109,7 +109,6 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/etcd-io/gofail v0.0.0-20190801230047-ad7f989257ca/go.mod h1:49H/RkXP8pKaZy4h0d+NW16rSLhyVBt4o6VLJbmOqDE=
github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs=
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@ -283,7 +282,6 @@ github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
@ -317,7 +315,6 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
@ -423,15 +420,12 @@ go.etcd.io/etcd/client/v2 v2.305.0 h1:ftQ0nOOHMcbMS3KIaDQ0g5Qcd6bhaBrQT6b89DfwLT
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.etcd.io/etcd/client/v3 v3.5.0 h1:62Eh0XOro+rDwkrypAGDfgmNh5Joq+z+W9HZdlXMzek=
go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
go.etcd.io/etcd/etcdutl/v3 v3.5.0/go.mod h1:o98rKMCibbFAG8QS9KmvlYDGDShmmIbmRE8vSofzYNg=
go.etcd.io/etcd/pkg/v3 v3.5.0 h1:ntrg6vvKRW26JRmHTE0iNlDgYK6JX3hg/4cD62X0ixk=
go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE=
go.etcd.io/etcd/raft/v3 v3.5.0 h1:kw2TmO3yFTgE+F0mdKkG7xMxkit2duBDa2Hu6D/HMlw=
go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc=
go.etcd.io/etcd/server/v3 v3.5.0 h1:jk8D/lwGEDlQU9kZXUFMSANkE22Sg5+mW27ip8xcF9E=
go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4=
go.etcd.io/etcd/tests/v3 v3.5.0 h1:+uMuHYKKlLUzbW322XrQXbaGM9qiV7vUL+AEPT/KYY4=
go.etcd.io/etcd/tests/v3 v3.5.0/go.mod h1:f+mtZ1bE1YPvgKdOJV2BKy4JQW0nAFnQehgOE7+WyJE=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=

View File

@ -95,7 +95,6 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/etcd-io/gofail v0.0.0-20190801230047-ad7f989257ca/go.mod h1:49H/RkXP8pKaZy4h0d+NW16rSLhyVBt4o6VLJbmOqDE=
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
github.com/form3tech-oss/jwt-go v3.2.2+incompatible/go.mod h1:pbq4aXjuKjdthFRnoDwaVPLA+WlJuPGy+QneDUgJi2k=
@ -247,7 +246,6 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
@ -276,7 +274,6 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWb
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
@ -364,11 +361,9 @@ go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQc
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
go.etcd.io/etcd/etcdutl/v3 v3.5.0/go.mod h1:o98rKMCibbFAG8QS9KmvlYDGDShmmIbmRE8vSofzYNg=
go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE=
go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc=
go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4=
go.etcd.io/etcd/tests/v3 v3.5.0/go.mod h1:f+mtZ1bE1YPvgKdOJV2BKy4JQW0nAFnQehgOE7+WyJE=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=

View File

@ -120,7 +120,6 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/etcd-io/gofail v0.0.0-20190801230047-ad7f989257ca/go.mod h1:49H/RkXP8pKaZy4h0d+NW16rSLhyVBt4o6VLJbmOqDE=
github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs=
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@ -292,7 +291,6 @@ github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
@ -324,7 +322,6 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
@ -425,11 +422,9 @@ go.etcd.io/etcd/api/v3 v3.5.0/go.mod h1:cbVKeC6lCfl7j/8jBhAK6aIYO9XOjdptoxU/nLQc
go.etcd.io/etcd/client/pkg/v3 v3.5.0/go.mod h1:IJHfcCEKxYu1Os13ZdwCwIUTUVGYTSAM3YSwc9/Ac1g=
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
go.etcd.io/etcd/etcdutl/v3 v3.5.0/go.mod h1:o98rKMCibbFAG8QS9KmvlYDGDShmmIbmRE8vSofzYNg=
go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE=
go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc=
go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4=
go.etcd.io/etcd/tests/v3 v3.5.0/go.mod h1:f+mtZ1bE1YPvgKdOJV2BKy4JQW0nAFnQehgOE7+WyJE=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=

View File

@ -108,7 +108,6 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m
github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
github.com/etcd-io/gofail v0.0.0-20190801230047-ad7f989257ca/go.mod h1:49H/RkXP8pKaZy4h0d+NW16rSLhyVBt4o6VLJbmOqDE=
github.com/evanphx/json-patch v4.11.0+incompatible h1:glyUF9yIYtMHzn8xaKw5rMhdWcwsYV8dZHIq5567/xs=
github.com/evanphx/json-patch v4.11.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk=
github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
@ -282,7 +281,6 @@ github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369 h1:I0XW9+e1XWDxdcEniV4rQAIOPUGDq67JSCiRCgGCZLI=
github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369/go.mod h1:BSXmuO+STAnVfrANrmjBb36TMTDstsz7MSK+HVaYKv4=
@ -314,7 +312,6 @@ github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLA
github.com/nxadm/tail v1.4.4 h1:DQuhQpB1tVlglWS2hLQ5OV6B5r8aGxSrPc5Qo6uTN78=
github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A=
github.com/oklog/ulid v1.3.1/go.mod h1:CirwcVhetQ6Lv90oh/F+FBtV6XMibvdAFo93nm5qn4U=
github.com/olekukonko/tablewriter v0.0.5/go.mod h1:hPp6KlRPjbx+hW8ykQs1w3UBbZlj6HuIJcUGPhkA7kY=
github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE=
github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk=
@ -420,15 +417,12 @@ go.etcd.io/etcd/client/v2 v2.305.0 h1:ftQ0nOOHMcbMS3KIaDQ0g5Qcd6bhaBrQT6b89DfwLT
go.etcd.io/etcd/client/v2 v2.305.0/go.mod h1:h9puh54ZTgAKtEbut2oe9P4L/oqKCVB6xsXlzd7alYQ=
go.etcd.io/etcd/client/v3 v3.5.0 h1:62Eh0XOro+rDwkrypAGDfgmNh5Joq+z+W9HZdlXMzek=
go.etcd.io/etcd/client/v3 v3.5.0/go.mod h1:AIKXXVX/DQXtfTEqBryiLTUXwON+GuvO6Z7lLS/oTh0=
go.etcd.io/etcd/etcdutl/v3 v3.5.0/go.mod h1:o98rKMCibbFAG8QS9KmvlYDGDShmmIbmRE8vSofzYNg=
go.etcd.io/etcd/pkg/v3 v3.5.0 h1:ntrg6vvKRW26JRmHTE0iNlDgYK6JX3hg/4cD62X0ixk=
go.etcd.io/etcd/pkg/v3 v3.5.0/go.mod h1:UzJGatBQ1lXChBkQF0AuAtkRQMYnHubxAEYIrC3MSsE=
go.etcd.io/etcd/raft/v3 v3.5.0 h1:kw2TmO3yFTgE+F0mdKkG7xMxkit2duBDa2Hu6D/HMlw=
go.etcd.io/etcd/raft/v3 v3.5.0/go.mod h1:UFOHSIvO/nKwd4lhkwabrTD3cqW5yVyYYf/KlD00Szc=
go.etcd.io/etcd/server/v3 v3.5.0 h1:jk8D/lwGEDlQU9kZXUFMSANkE22Sg5+mW27ip8xcF9E=
go.etcd.io/etcd/server/v3 v3.5.0/go.mod h1:3Ah5ruV+M+7RZr0+Y/5mNLwC+eQlni+mQmOVdCRJoS4=
go.etcd.io/etcd/tests/v3 v3.5.0 h1:+uMuHYKKlLUzbW322XrQXbaGM9qiV7vUL+AEPT/KYY4=
go.etcd.io/etcd/tests/v3 v3.5.0/go.mod h1:f+mtZ1bE1YPvgKdOJV2BKy4JQW0nAFnQehgOE7+WyJE=
go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU=
go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8=
go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw=

View File

@ -1,16 +0,0 @@
//
/*
grpc_logsettable contains a thread-safe wrapper around grpc-logging
infrastructure.
The go-grpc assumes that logger can be only configured once as the `SetLoggerV2`
method is:
```Not mutex-protected, should be called before any gRPC functions.```
This package allows to supply parent logger once ("before any grpc"), but
later change underlying implementation in thread-safe way when needed.
It's in particular useful for testing, where each testcase might need its own
logger.
*/
package grpc_logsettable

View File

@ -1,99 +0,0 @@
package grpc_logsettable
import (
"io/ioutil"
"sync"
"google.golang.org/grpc/grpclog"
)
// SettableLoggerV2 is thread-safe.
type SettableLoggerV2 interface {
grpclog.LoggerV2
// Sets given logger as the underlying implementation.
Set(loggerv2 grpclog.LoggerV2)
// Sets `discard` logger as the underlying implementation.
Reset()
}
// ReplaceGrpcLoggerV2 creates and configures SettableLoggerV2 as grpc logger.
func ReplaceGrpcLoggerV2() SettableLoggerV2 {
settable := &settableLoggerV2{}
settable.Reset()
grpclog.SetLoggerV2(settable)
return settable
}
// SettableLoggerV2 implements SettableLoggerV2
type settableLoggerV2 struct {
log grpclog.LoggerV2
mu sync.RWMutex
}
func (s *settableLoggerV2) Set(log grpclog.LoggerV2) {
s.mu.Lock()
defer s.mu.Unlock()
s.log = log
}
func (s *settableLoggerV2) Reset() {
s.Set(grpclog.NewLoggerV2(ioutil.Discard, ioutil.Discard, ioutil.Discard))
}
func (s *settableLoggerV2) get() grpclog.LoggerV2 {
s.mu.RLock()
defer s.mu.RUnlock()
return s.log
}
func (s *settableLoggerV2) Info(args ...interface{}) {
s.get().Info(args)
}
func (s *settableLoggerV2) Infoln(args ...interface{}) {
s.get().Infoln(args)
}
func (s *settableLoggerV2) Infof(format string, args ...interface{}) {
s.get().Infof(format, args)
}
func (s *settableLoggerV2) Warning(args ...interface{}) {
s.get().Warning(args)
}
func (s *settableLoggerV2) Warningln(args ...interface{}) {
s.get().Warningln(args)
}
func (s *settableLoggerV2) Warningf(format string, args ...interface{}) {
s.get().Warningf(format, args)
}
func (s *settableLoggerV2) Error(args ...interface{}) {
s.get().Error(args)
}
func (s *settableLoggerV2) Errorln(args ...interface{}) {
s.get().Errorln(args)
}
func (s *settableLoggerV2) Errorf(format string, args ...interface{}) {
s.get().Errorf(format, args)
}
func (s *settableLoggerV2) Fatal(args ...interface{}) {
s.get().Fatal(args)
}
func (s *settableLoggerV2) Fatalln(args ...interface{}) {
s.get().Fatalln(args)
}
func (s *settableLoggerV2) Fatalf(format string, args ...interface{}) {
s.get().Fatalf(format, args)
}
func (s *settableLoggerV2) V(l int) bool {
return s.get().V(l)
}

View File

@ -1,67 +0,0 @@
// Copyright 2017 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package testutil
import (
"fmt"
"reflect"
"testing"
)
func AssertEqual(t *testing.T, e, a interface{}, msg ...string) {
t.Helper()
if (e == nil || a == nil) && (isNil(e) && isNil(a)) {
return
}
if reflect.DeepEqual(e, a) {
return
}
s := ""
if len(msg) > 1 {
s = msg[0] + ": "
}
s = fmt.Sprintf("%sexpected %+v, got %+v", s, e, a)
FatalStack(t, s)
}
func AssertNil(t *testing.T, v interface{}) {
t.Helper()
AssertEqual(t, nil, v)
}
func AssertNotNil(t *testing.T, v interface{}) {
t.Helper()
if v == nil {
t.Fatalf("expected non-nil, got %+v", v)
}
}
func AssertTrue(t *testing.T, v bool, msg ...string) {
t.Helper()
AssertEqual(t, true, v, msg...)
}
func AssertFalse(t *testing.T, v bool, msg ...string) {
t.Helper()
AssertEqual(t, false, v, msg...)
}
func isNil(v interface{}) bool {
if v == nil {
return true
}
rv := reflect.ValueOf(v)
return rv.Kind() != reflect.Struct && rv.IsNil()
}

View File

@ -1,181 +0,0 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package testutil
import (
"fmt"
"net/http"
"os"
"regexp"
"runtime"
"sort"
"strings"
"testing"
"time"
)
// TODO: Replace with https://github.com/uber-go/goleak.
/*
CheckLeakedGoroutine verifies tests do not leave any leaky
goroutines. It returns true when there are goroutines still
running(leaking) after all tests.
import "go.etcd.io/etcd/client/pkg/v3/testutil"
func TestMain(m *testing.M) {
testutil.MustTestMainWithLeakDetection(m)
}
func TestSample(t *testing.T) {
RegisterLeakDetection(t)
...
}
*/
func CheckLeakedGoroutine() bool {
gs := interestingGoroutines()
if len(gs) == 0 {
return false
}
stackCount := make(map[string]int)
re := regexp.MustCompile(`\(0[0-9a-fx, ]*\)`)
for _, g := range gs {
// strip out pointer arguments in first function of stack dump
normalized := string(re.ReplaceAll([]byte(g), []byte("(...)")))
stackCount[normalized]++
}
fmt.Fprintf(os.Stderr, "Unexpected goroutines running after all test(s).\n")
for stack, count := range stackCount {
fmt.Fprintf(os.Stderr, "%d instances of:\n%s\n", count, stack)
}
return true
}
// CheckAfterTest returns an error if AfterTest would fail with an error.
// Waits for go-routines shutdown for 'd'.
func CheckAfterTest(d time.Duration) error {
http.DefaultTransport.(*http.Transport).CloseIdleConnections()
var bad string
// Presence of these goroutines causes immediate test failure.
badSubstring := map[string]string{
").writeLoop(": "a Transport",
"created by net/http/httptest.(*Server).Start": "an httptest.Server",
"timeoutHandler": "a TimeoutHandler",
"net.(*netFD).connect(": "a timing out dial",
").noteClientGone(": "a closenotifier sender",
").readLoop(": "a Transport",
".grpc": "a gRPC resource",
").sendCloseSubstream(": "a stream closing routine",
}
var stacks string
begin := time.Now()
for time.Since(begin) < d {
bad = ""
goroutines := interestingGoroutines()
if len(goroutines) == 0 {
return nil
}
stacks = strings.Join(goroutines, "\n\n")
for substr, what := range badSubstring {
if strings.Contains(stacks, substr) {
bad = what
}
}
// Undesired goroutines found, but goroutines might just still be
// shutting down, so give it some time.
runtime.Gosched()
time.Sleep(50 * time.Millisecond)
}
return fmt.Errorf("appears to have leaked %s:\n%s", bad, stacks)
}
// RegisterLeakDetection is a convenient way to register before-and-after code to a test.
// If you execute RegisterLeakDetection, you don't need to explicitly register AfterTest.
func RegisterLeakDetection(t TB) {
if err := CheckAfterTest(10 * time.Millisecond); err != nil {
t.Skip("Found leaked goroutined BEFORE test", err)
return
}
t.Cleanup(func() {
afterTest(t)
})
}
// afterTest is meant to run in a defer that executes after a test completes.
// It will detect common goroutine leaks, retrying in case there are goroutines
// not synchronously torn down, and fail the test if any goroutines are stuck.
func afterTest(t TB) {
// If test-failed the leaked goroutines list is hidding the real
// source of problem.
if !t.Failed() {
if err := CheckAfterTest(1 * time.Second); err != nil {
t.Errorf("Test %v", err)
}
}
}
func interestingGoroutines() (gs []string) {
buf := make([]byte, 2<<20)
buf = buf[:runtime.Stack(buf, true)]
for _, g := range strings.Split(string(buf), "\n\n") {
sl := strings.SplitN(g, "\n", 2)
if len(sl) != 2 {
continue
}
stack := strings.TrimSpace(sl[1])
if stack == "" ||
strings.Contains(stack, "sync.(*WaitGroup).Done") ||
strings.Contains(stack, "os.(*file).close") ||
strings.Contains(stack, "os.(*Process).Release") ||
strings.Contains(stack, "created by os/signal.init") ||
strings.Contains(stack, "runtime/panic.go") ||
strings.Contains(stack, "created by testing.RunTests") ||
strings.Contains(stack, "created by testing.runTests") ||
strings.Contains(stack, "created by testing.(*T).Run") ||
strings.Contains(stack, "testing.Main(") ||
strings.Contains(stack, "runtime.goexit") ||
strings.Contains(stack, "go.etcd.io/etcd/client/pkg/v3/testutil.interestingGoroutines") ||
strings.Contains(stack, "go.etcd.io/etcd/client/pkg/v3/logutil.(*MergeLogger).outputLoop") ||
strings.Contains(stack, "github.com/golang/glog.(*loggingT).flushDaemon") ||
strings.Contains(stack, "created by runtime.gc") ||
strings.Contains(stack, "created by text/template/parse.lex") ||
strings.Contains(stack, "runtime.MHeap_Scavenger") ||
strings.Contains(stack, "rcrypto/internal/boring.(*PublicKeyRSA).finalize") ||
strings.Contains(stack, "net.(*netFD).Close(") ||
strings.Contains(stack, "testing.(*T).Run") {
continue
}
gs = append(gs, stack)
}
sort.Strings(gs)
return gs
}
func MustCheckLeakedGoroutine() {
http.DefaultTransport.(*http.Transport).CloseIdleConnections()
CheckAfterTest(5 * time.Second)
// Let the other goroutines finalize.
runtime.Gosched()
if CheckLeakedGoroutine() {
os.Exit(1)
}
}
// MustTestMainWithLeakDetection expands standard m.Run with leaked
// goroutines detection.
func MustTestMainWithLeakDetection(m *testing.M) {
v := m.Run()
if v == 0 {
MustCheckLeakedGoroutine()
}
os.Exit(v)
}

View File

@ -1,57 +0,0 @@
// Copyright 2015 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package testutil
import (
"net/http"
"sync"
)
type PauseableHandler struct {
Next http.Handler
mu sync.Mutex
paused bool
}
func (ph *PauseableHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
ph.mu.Lock()
paused := ph.paused
ph.mu.Unlock()
if !paused {
ph.Next.ServeHTTP(w, r)
} else {
hj, ok := w.(http.Hijacker)
if !ok {
panic("webserver doesn't support hijacking")
}
conn, _, err := hj.Hijack()
if err != nil {
panic(err.Error())
}
conn.Close()
}
}
func (ph *PauseableHandler) Pause() {
ph.mu.Lock()
defer ph.mu.Unlock()
ph.paused = true
}
func (ph *PauseableHandler) Resume() {
ph.mu.Lock()
defer ph.mu.Unlock()
ph.paused = false
}

View File

@ -1,139 +0,0 @@
// Copyright 2015 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package testutil
import (
"errors"
"fmt"
"sync"
"time"
)
type Action struct {
Name string
Params []interface{}
}
type Recorder interface {
// Record publishes an Action (e.g., function call) which will
// be reflected by Wait() or Chan()
Record(a Action)
// Wait waits until at least n Actions are available or returns with error
Wait(n int) ([]Action, error)
// Action returns immediately available Actions
Action() []Action
// Chan returns the channel for actions published by Record
Chan() <-chan Action
}
// RecorderBuffered appends all Actions to a slice
type RecorderBuffered struct {
sync.Mutex
actions []Action
}
func (r *RecorderBuffered) Record(a Action) {
r.Lock()
r.actions = append(r.actions, a)
r.Unlock()
}
func (r *RecorderBuffered) Action() []Action {
r.Lock()
cpy := make([]Action, len(r.actions))
copy(cpy, r.actions)
r.Unlock()
return cpy
}
func (r *RecorderBuffered) Wait(n int) (acts []Action, err error) {
// legacy racey behavior
WaitSchedule()
acts = r.Action()
if len(acts) < n {
err = newLenErr(n, len(acts))
}
return acts, err
}
func (r *RecorderBuffered) Chan() <-chan Action {
ch := make(chan Action)
go func() {
acts := r.Action()
for i := range acts {
ch <- acts[i]
}
close(ch)
}()
return ch
}
// RecorderStream writes all Actions to an unbuffered channel
type recorderStream struct {
ch chan Action
waitTimeout time.Duration
}
func NewRecorderStream() Recorder {
return NewRecorderStreamWithWaitTimout(time.Duration(5 * time.Second))
}
func NewRecorderStreamWithWaitTimout(waitTimeout time.Duration) Recorder {
return &recorderStream{ch: make(chan Action), waitTimeout: waitTimeout}
}
func (r *recorderStream) Record(a Action) {
r.ch <- a
}
func (r *recorderStream) Action() (acts []Action) {
for {
select {
case act := <-r.ch:
acts = append(acts, act)
default:
return acts
}
}
}
func (r *recorderStream) Chan() <-chan Action {
return r.ch
}
func (r *recorderStream) Wait(n int) ([]Action, error) {
acts := make([]Action, n)
timeoutC := time.After(r.waitTimeout)
for i := 0; i < n; i++ {
select {
case acts[i] = <-r.ch:
case <-timeoutC:
acts = acts[:i]
return acts, newLenErr(n, i)
}
}
// extra wait to catch any Action spew
select {
case act := <-r.ch:
acts = append(acts, act)
case <-time.After(10 * time.Millisecond):
}
return acts, nil
}
func newLenErr(expected int, actual int) error {
s := fmt.Sprintf("len(actions) = %d, expected >= %d", actual, expected)
return errors.New(s)
}

View File

@ -1,130 +0,0 @@
// Copyright 2021 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package testutil
import (
"io/ioutil"
"log"
"os"
)
// TB is a subset of methods of testing.TB interface.
// We cannot implement testing.TB due to protection, so we expose this simplified interface.
type TB interface {
Cleanup(func())
Error(args ...interface{})
Errorf(format string, args ...interface{})
Fail()
FailNow()
Failed() bool
Fatal(args ...interface{})
Fatalf(format string, args ...interface{})
Logf(format string, args ...interface{})
Name() string
TempDir() string
Helper()
Skip(args ...interface{})
}
// NewTestingTBProthesis creates a fake variant of testing.TB implementation.
// It's supposed to be used in contexts were real testing.T is not provided,
// e.g. in 'examples'.
//
// The `closef` goroutine should get executed when tb will not be needed any longer.
//
// The provided implementation is NOT thread safe (Cleanup() method).
func NewTestingTBProthesis(name string) (tb TB, closef func()) {
testtb := &testingTBProthesis{name: name}
return testtb, testtb.close
}
type testingTBProthesis struct {
name string
failed bool
cleanups []func()
}
func (t *testingTBProthesis) Helper() {
// Ignored
}
func (t *testingTBProthesis) Skip(args ...interface{}) {
t.Log(append([]interface{}{"Skipping due to: "}, args...))
}
func (t *testingTBProthesis) Cleanup(f func()) {
t.cleanups = append(t.cleanups, f)
}
func (t *testingTBProthesis) Error(args ...interface{}) {
log.Println(args...)
t.Fail()
}
func (t *testingTBProthesis) Errorf(format string, args ...interface{}) {
log.Printf(format, args...)
t.Fail()
}
func (t *testingTBProthesis) Fail() {
t.failed = true
}
func (t *testingTBProthesis) FailNow() {
t.failed = true
panic("FailNow() called")
}
func (t *testingTBProthesis) Failed() bool {
return t.failed
}
func (t *testingTBProthesis) Fatal(args ...interface{}) {
log.Fatalln(args...)
}
func (t *testingTBProthesis) Fatalf(format string, args ...interface{}) {
log.Fatalf(format, args...)
}
func (t *testingTBProthesis) Logf(format string, args ...interface{}) {
log.Printf(format, args...)
}
func (t *testingTBProthesis) Log(args ...interface{}) {
log.Println(args...)
}
func (t *testingTBProthesis) Name() string {
return t.name
}
func (t *testingTBProthesis) TempDir() string {
dir, err := ioutil.TempDir("", t.name)
if err != nil {
t.Fatal(err)
}
t.cleanups = append([]func(){func() {
t.Logf("Cleaning UP: %v", dir)
os.RemoveAll(dir)
}}, t.cleanups...)
return dir
}
func (t *testingTBProthesis) close() {
for i := len(t.cleanups) - 1; i >= 0; i-- {
t.cleanups[i]()
}
}

View File

@ -1,106 +0,0 @@
// Copyright 2015 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package testutil provides test utility functions.
package testutil
import (
"net/url"
"os"
"runtime"
"testing"
"time"
)
// WaitSchedule briefly sleeps in order to invoke the go scheduler.
// TODO: improve this when we are able to know the schedule or status of target go-routine.
func WaitSchedule() {
time.Sleep(10 * time.Millisecond)
}
func MustNewURLs(t *testing.T, urls []string) []url.URL {
if urls == nil {
return nil
}
var us []url.URL
for _, url := range urls {
u := MustNewURL(t, url)
us = append(us, *u)
}
return us
}
func MustNewURL(t *testing.T, s string) *url.URL {
u, err := url.Parse(s)
if err != nil {
t.Fatalf("parse %v error: %v", s, err)
}
return u
}
// FatalStack helps to fatal the test and print out the stacks of all running goroutines.
func FatalStack(t *testing.T, s string) {
stackTrace := make([]byte, 1024*1024)
n := runtime.Stack(stackTrace, true)
t.Errorf("---> Test failed: %s", s)
t.Error(string(stackTrace[:n]))
t.Fatal(s)
}
// ConditionFunc returns true when a condition is met.
type ConditionFunc func() (bool, error)
// Poll calls a condition function repeatedly on a polling interval until it returns true, returns an error
// or the timeout is reached. If the condition function returns true or an error before the timeout, Poll
// immediately returns with the true value or the error. If the timeout is exceeded, Poll returns false.
func Poll(interval time.Duration, timeout time.Duration, condition ConditionFunc) (bool, error) {
timeoutCh := time.After(timeout)
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-timeoutCh:
return false, nil
case <-ticker.C:
success, err := condition()
if err != nil {
return false, err
}
if success {
return true, nil
}
}
}
}
func SkipTestIfShortMode(t TB, reason string) {
if t != nil {
t.Helper()
if testing.Short() {
t.Skip(reason)
}
}
}
// ExitInShortMode closes the current process (with 0) if the short test mode detected.
//
// To be used in Test-main, where test context (testing.TB) is not available.
//
// Requires custom env-variable (GOLANG_TEST_SHORT) apart of `go test --short flag`.
func ExitInShortMode(reason string) {
if os.Getenv("GOLANG_TEST_SHORT") == "true" {
os.Exit(0)
}
}

View File

@ -1,22 +0,0 @@
// Copyright 2018 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package testutil
import "time"
var (
ApplyTimeout = time.Second
RequestTimeout = 3 * time.Second
)

View File

@ -1,43 +0,0 @@
// Copyright 2017 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package namespace is a clientv3 wrapper that translates all keys to begin
// with a given prefix.
//
// First, create a client:
//
// cli, err := clientv3.New(clientv3.Config{Endpoints: []string{"localhost:2379"}})
// if err != nil {
// // handle error!
// }
//
// Next, override the client interfaces:
//
// unprefixedKV := cli.KV
// cli.KV = namespace.NewKV(cli.KV, "my-prefix/")
// cli.Watcher = namespace.NewWatcher(cli.Watcher, "my-prefix/")
// cli.Lease = namespace.NewLease(cli.Lease, "my-prefix/")
//
// Now calls using 'cli' will namespace / prefix all keys with "my-prefix/":
//
// cli.Put(context.TODO(), "abc", "123")
// resp, _ := unprefixedKV.Get(context.TODO(), "my-prefix/abc")
// fmt.Printf("%s\n", resp.Kvs[0].Value)
// // Output: 123
// unprefixedKV.Put(context.TODO(), "my-prefix/abc", "456")
// resp, _ = cli.Get(context.TODO(), "abc")
// fmt.Printf("%s\n", resp.Kvs[0].Value)
// // Output: 456
//
package namespace

View File

@ -1,206 +0,0 @@
// Copyright 2017 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package namespace
import (
"context"
pb "go.etcd.io/etcd/api/v3/etcdserverpb"
"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
"go.etcd.io/etcd/client/v3"
)
type kvPrefix struct {
clientv3.KV
pfx string
}
// NewKV wraps a KV instance so that all requests
// are prefixed with a given string.
func NewKV(kv clientv3.KV, prefix string) clientv3.KV {
return &kvPrefix{kv, prefix}
}
func (kv *kvPrefix) Put(ctx context.Context, key, val string, opts ...clientv3.OpOption) (*clientv3.PutResponse, error) {
if len(key) == 0 {
return nil, rpctypes.ErrEmptyKey
}
op := kv.prefixOp(clientv3.OpPut(key, val, opts...))
r, err := kv.KV.Do(ctx, op)
if err != nil {
return nil, err
}
put := r.Put()
kv.unprefixPutResponse(put)
return put, nil
}
func (kv *kvPrefix) Get(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.GetResponse, error) {
if len(key) == 0 && !(clientv3.IsOptsWithFromKey(opts) || clientv3.IsOptsWithPrefix(opts)) {
return nil, rpctypes.ErrEmptyKey
}
r, err := kv.KV.Do(ctx, kv.prefixOp(clientv3.OpGet(key, opts...)))
if err != nil {
return nil, err
}
get := r.Get()
kv.unprefixGetResponse(get)
return get, nil
}
func (kv *kvPrefix) Delete(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.DeleteResponse, error) {
if len(key) == 0 && !(clientv3.IsOptsWithFromKey(opts) || clientv3.IsOptsWithPrefix(opts)) {
return nil, rpctypes.ErrEmptyKey
}
r, err := kv.KV.Do(ctx, kv.prefixOp(clientv3.OpDelete(key, opts...)))
if err != nil {
return nil, err
}
del := r.Del()
kv.unprefixDeleteResponse(del)
return del, nil
}
func (kv *kvPrefix) Do(ctx context.Context, op clientv3.Op) (clientv3.OpResponse, error) {
if len(op.KeyBytes()) == 0 && !op.IsTxn() {
return clientv3.OpResponse{}, rpctypes.ErrEmptyKey
}
r, err := kv.KV.Do(ctx, kv.prefixOp(op))
if err != nil {
return r, err
}
switch {
case r.Get() != nil:
kv.unprefixGetResponse(r.Get())
case r.Put() != nil:
kv.unprefixPutResponse(r.Put())
case r.Del() != nil:
kv.unprefixDeleteResponse(r.Del())
case r.Txn() != nil:
kv.unprefixTxnResponse(r.Txn())
}
return r, nil
}
type txnPrefix struct {
clientv3.Txn
kv *kvPrefix
}
func (kv *kvPrefix) Txn(ctx context.Context) clientv3.Txn {
return &txnPrefix{kv.KV.Txn(ctx), kv}
}
func (txn *txnPrefix) If(cs ...clientv3.Cmp) clientv3.Txn {
txn.Txn = txn.Txn.If(txn.kv.prefixCmps(cs)...)
return txn
}
func (txn *txnPrefix) Then(ops ...clientv3.Op) clientv3.Txn {
txn.Txn = txn.Txn.Then(txn.kv.prefixOps(ops)...)
return txn
}
func (txn *txnPrefix) Else(ops ...clientv3.Op) clientv3.Txn {
txn.Txn = txn.Txn.Else(txn.kv.prefixOps(ops)...)
return txn
}
func (txn *txnPrefix) Commit() (*clientv3.TxnResponse, error) {
resp, err := txn.Txn.Commit()
if err != nil {
return nil, err
}
txn.kv.unprefixTxnResponse(resp)
return resp, nil
}
func (kv *kvPrefix) prefixOp(op clientv3.Op) clientv3.Op {
if !op.IsTxn() {
begin, end := kv.prefixInterval(op.KeyBytes(), op.RangeBytes())
op.WithKeyBytes(begin)
op.WithRangeBytes(end)
return op
}
cmps, thenOps, elseOps := op.Txn()
return clientv3.OpTxn(kv.prefixCmps(cmps), kv.prefixOps(thenOps), kv.prefixOps(elseOps))
}
func (kv *kvPrefix) unprefixGetResponse(resp *clientv3.GetResponse) {
for i := range resp.Kvs {
resp.Kvs[i].Key = resp.Kvs[i].Key[len(kv.pfx):]
}
}
func (kv *kvPrefix) unprefixPutResponse(resp *clientv3.PutResponse) {
if resp.PrevKv != nil {
resp.PrevKv.Key = resp.PrevKv.Key[len(kv.pfx):]
}
}
func (kv *kvPrefix) unprefixDeleteResponse(resp *clientv3.DeleteResponse) {
for i := range resp.PrevKvs {
resp.PrevKvs[i].Key = resp.PrevKvs[i].Key[len(kv.pfx):]
}
}
func (kv *kvPrefix) unprefixTxnResponse(resp *clientv3.TxnResponse) {
for _, r := range resp.Responses {
switch tv := r.Response.(type) {
case *pb.ResponseOp_ResponseRange:
if tv.ResponseRange != nil {
kv.unprefixGetResponse((*clientv3.GetResponse)(tv.ResponseRange))
}
case *pb.ResponseOp_ResponsePut:
if tv.ResponsePut != nil {
kv.unprefixPutResponse((*clientv3.PutResponse)(tv.ResponsePut))
}
case *pb.ResponseOp_ResponseDeleteRange:
if tv.ResponseDeleteRange != nil {
kv.unprefixDeleteResponse((*clientv3.DeleteResponse)(tv.ResponseDeleteRange))
}
case *pb.ResponseOp_ResponseTxn:
if tv.ResponseTxn != nil {
kv.unprefixTxnResponse((*clientv3.TxnResponse)(tv.ResponseTxn))
}
default:
}
}
}
func (kv *kvPrefix) prefixInterval(key, end []byte) (pfxKey []byte, pfxEnd []byte) {
return prefixInterval(kv.pfx, key, end)
}
func (kv *kvPrefix) prefixCmps(cs []clientv3.Cmp) []clientv3.Cmp {
newCmps := make([]clientv3.Cmp, len(cs))
for i := range cs {
newCmps[i] = cs[i]
pfxKey, endKey := kv.prefixInterval(cs[i].KeyBytes(), cs[i].RangeEnd)
newCmps[i].WithKeyBytes(pfxKey)
if len(cs[i].RangeEnd) != 0 {
newCmps[i].RangeEnd = endKey
}
}
return newCmps
}
func (kv *kvPrefix) prefixOps(ops []clientv3.Op) []clientv3.Op {
newOps := make([]clientv3.Op, len(ops))
for i := range ops {
newOps[i] = kv.prefixOp(ops[i])
}
return newOps
}

View File

@ -1,57 +0,0 @@
// Copyright 2017 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package namespace
import (
"bytes"
"context"
"go.etcd.io/etcd/client/v3"
)
type leasePrefix struct {
clientv3.Lease
pfx []byte
}
// NewLease wraps a Lease interface to filter for only keys with a prefix
// and remove that prefix when fetching attached keys through TimeToLive.
func NewLease(l clientv3.Lease, prefix string) clientv3.Lease {
return &leasePrefix{l, []byte(prefix)}
}
func (l *leasePrefix) TimeToLive(ctx context.Context, id clientv3.LeaseID, opts ...clientv3.LeaseOption) (*clientv3.LeaseTimeToLiveResponse, error) {
resp, err := l.Lease.TimeToLive(ctx, id, opts...)
if err != nil {
return nil, err
}
if len(resp.Keys) > 0 {
var outKeys [][]byte
for i := range resp.Keys {
if len(resp.Keys[i]) < len(l.pfx) {
// too short
continue
}
if !bytes.Equal(resp.Keys[i][:len(l.pfx)], l.pfx) {
// doesn't match prefix
continue
}
// strip prefix
outKeys = append(outKeys, resp.Keys[i][len(l.pfx):])
}
resp.Keys = outKeys
}
return resp, nil
}

View File

@ -1,42 +0,0 @@
// Copyright 2017 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package namespace
func prefixInterval(pfx string, key, end []byte) (pfxKey []byte, pfxEnd []byte) {
pfxKey = make([]byte, len(pfx)+len(key))
copy(pfxKey[copy(pfxKey, pfx):], key)
if len(end) == 1 && end[0] == 0 {
// the edge of the keyspace
pfxEnd = make([]byte, len(pfx))
copy(pfxEnd, pfx)
ok := false
for i := len(pfxEnd) - 1; i >= 0; i-- {
if pfxEnd[i]++; pfxEnd[i] != 0 {
ok = true
break
}
}
if !ok {
// 0xff..ff => 0x00
pfxEnd = []byte{0}
}
} else if len(end) >= 1 {
pfxEnd = make([]byte, len(pfx)+len(end))
copy(pfxEnd[copy(pfxEnd, pfx):], end)
}
return pfxKey, pfxEnd
}

View File

@ -1,83 +0,0 @@
// Copyright 2017 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package namespace
import (
"context"
"sync"
"go.etcd.io/etcd/client/v3"
)
type watcherPrefix struct {
clientv3.Watcher
pfx string
wg sync.WaitGroup
stopc chan struct{}
stopOnce sync.Once
}
// NewWatcher wraps a Watcher instance so that all Watch requests
// are prefixed with a given string and all Watch responses have
// the prefix removed.
func NewWatcher(w clientv3.Watcher, prefix string) clientv3.Watcher {
return &watcherPrefix{Watcher: w, pfx: prefix, stopc: make(chan struct{})}
}
func (w *watcherPrefix) Watch(ctx context.Context, key string, opts ...clientv3.OpOption) clientv3.WatchChan {
// since OpOption is opaque, determine range for prefixing through an OpGet
op := clientv3.OpGet(key, opts...)
end := op.RangeBytes()
pfxBegin, pfxEnd := prefixInterval(w.pfx, []byte(key), end)
if pfxEnd != nil {
opts = append(opts, clientv3.WithRange(string(pfxEnd)))
}
wch := w.Watcher.Watch(ctx, string(pfxBegin), opts...)
// translate watch events from prefixed to unprefixed
pfxWch := make(chan clientv3.WatchResponse)
w.wg.Add(1)
go func() {
defer func() {
close(pfxWch)
w.wg.Done()
}()
for wr := range wch {
for i := range wr.Events {
wr.Events[i].Kv.Key = wr.Events[i].Kv.Key[len(w.pfx):]
if wr.Events[i].PrevKv != nil {
wr.Events[i].PrevKv.Key = wr.Events[i].Kv.Key
}
}
select {
case pfxWch <- wr:
case <-ctx.Done():
return
case <-w.stopc:
return
}
}
}()
return pfxWch
}
func (w *watcherPrefix) Close() error {
err := w.Watcher.Close()
w.stopOnce.Do(func() { close(w.stopc) })
w.wg.Wait()
return err
}

View File

@ -1,82 +0,0 @@
package endpoints
import (
"context"
clientv3 "go.etcd.io/etcd/client/v3"
)
// Endpoint represents a single address the connection can be established with.
//
// Inspired by: https://pkg.go.dev/google.golang.org/grpc/resolver#Address.
// Please document etcd version since which version each field is supported.
type Endpoint struct {
// Addr is the server address on which a connection will be established.
// Since etcd 3.1
Addr string
// Metadata is the information associated with Addr, which may be used
// to make load balancing decision.
// Since etcd 3.1
Metadata interface{}
}
type Operation uint8
const (
// Add indicates an Endpoint is added.
Add Operation = iota
// Delete indicates an existing address is deleted.
Delete
)
// Update describes a single edit action of an Endpoint.
type Update struct {
// Op - action Add or Delete.
Op Operation
Key string
Endpoint Endpoint
}
// WatchChannel is used to deliver notifications about endpoints updates.
type WatchChannel <-chan []*Update
// Key2EndpointMap maps etcd key into struct describing the endpoint.
type Key2EndpointMap map[string]Endpoint
// UpdateWithOpts describes endpoint update (add or delete) together
// with etcd options (e.g. to attach an endpoint to a lease).
type UpdateWithOpts struct {
Update
Opts []clientv3.OpOption
}
// NewAddUpdateOpts constructs UpdateWithOpts for endpoint registration.
func NewAddUpdateOpts(key string, endpoint Endpoint, opts ...clientv3.OpOption) *UpdateWithOpts {
return &UpdateWithOpts{Update: Update{Op: Add, Key: key, Endpoint: endpoint}, Opts: opts}
}
// NewDeleteUpdateOpts constructs UpdateWithOpts for endpoint deletion.
func NewDeleteUpdateOpts(key string, opts ...clientv3.OpOption) *UpdateWithOpts {
return &UpdateWithOpts{Update: Update{Op: Delete, Key: key}, Opts: opts}
}
// Manager can be used to add/remove & inspect endpoints stored in etcd for
// a particular target.
type Manager interface {
// Update allows to atomically add/remove a few endpoints from etcd.
Update(ctx context.Context, updates []*UpdateWithOpts) error
// AddEndpoint registers a single endpoint in etcd.
// For more advanced use-cases use the Update method.
AddEndpoint(ctx context.Context, key string, endpoint Endpoint, opts ...clientv3.OpOption) error
// DeleteEndpoint deletes a single endpoint stored in etcd.
// For more advanced use-cases use the Update method.
DeleteEndpoint(ctx context.Context, key string, opts ...clientv3.OpOption) error
// List returns all the endpoints for the current target as a map.
List(ctx context.Context) (Key2EndpointMap, error)
// NewWatchChannel creates a channel that populates or endpoint updates.
// Cancel the 'ctx' to close the watcher.
NewWatchChannel(ctx context.Context) (WatchChannel, error)
}

View File

@ -1,175 +0,0 @@
package endpoints
// TODO: The API is not yet implemented.
import (
"context"
"encoding/json"
"errors"
"strings"
clientv3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/client/v3/naming/endpoints/internal"
"go.uber.org/zap"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
type endpointManager struct {
// Client is an initialized etcd client.
client *clientv3.Client
target string
}
// NewManager creates an endpoint manager which implements the interface of 'Manager'.
func NewManager(client *clientv3.Client, target string) (Manager, error) {
if client == nil {
return nil, errors.New("invalid etcd client")
}
if target == "" {
return nil, errors.New("invalid target")
}
em := &endpointManager{
client: client,
target: target,
}
return em, nil
}
func (m *endpointManager) Update(ctx context.Context, updates []*UpdateWithOpts) (err error) {
ops := make([]clientv3.Op, 0, len(updates))
for _, update := range updates {
if !strings.HasPrefix(update.Key, m.target+"/") {
return status.Errorf(codes.InvalidArgument, "endpoints: endpoint key should be prefixed with '%s/' got: '%s'", m.target, update.Key)
}
switch update.Op {
case Add:
internalUpdate := &internal.Update{
Op: internal.Add,
Addr: update.Endpoint.Addr,
Metadata: update.Endpoint.Metadata,
}
var v []byte
if v, err = json.Marshal(internalUpdate); err != nil {
return status.Error(codes.InvalidArgument, err.Error())
}
ops = append(ops, clientv3.OpPut(update.Key, string(v), update.Opts...))
case Delete:
ops = append(ops, clientv3.OpDelete(update.Key, update.Opts...))
default:
return status.Error(codes.InvalidArgument, "endpoints: bad update op")
}
}
_, err = m.client.KV.Txn(ctx).Then(ops...).Commit()
return err
}
func (m *endpointManager) AddEndpoint(ctx context.Context, key string, endpoint Endpoint, opts ...clientv3.OpOption) error {
return m.Update(ctx, []*UpdateWithOpts{NewAddUpdateOpts(key, endpoint, opts...)})
}
func (m *endpointManager) DeleteEndpoint(ctx context.Context, key string, opts ...clientv3.OpOption) error {
return m.Update(ctx, []*UpdateWithOpts{NewDeleteUpdateOpts(key, opts...)})
}
func (m *endpointManager) NewWatchChannel(ctx context.Context) (WatchChannel, error) {
resp, err := m.client.Get(ctx, m.target, clientv3.WithPrefix(), clientv3.WithSerializable())
if err != nil {
return nil, err
}
lg := m.client.GetLogger()
initUpdates := make([]*Update, 0, len(resp.Kvs))
for _, kv := range resp.Kvs {
var iup internal.Update
if err := json.Unmarshal(kv.Value, &iup); err != nil {
lg.Warn("unmarshal endpoint update failed", zap.String("key", string(kv.Key)), zap.Error(err))
continue
}
up := &Update{
Op: Add,
Key: string(kv.Key),
Endpoint: Endpoint{Addr: iup.Addr, Metadata: iup.Metadata},
}
initUpdates = append(initUpdates, up)
}
upch := make(chan []*Update, 1)
if len(initUpdates) > 0 {
upch <- initUpdates
}
go m.watch(ctx, resp.Header.Revision+1, upch)
return upch, nil
}
func (m *endpointManager) watch(ctx context.Context, rev int64, upch chan []*Update) {
defer close(upch)
lg := m.client.GetLogger()
opts := []clientv3.OpOption{clientv3.WithRev(rev), clientv3.WithPrefix()}
wch := m.client.Watch(ctx, m.target, opts...)
for {
select {
case <-ctx.Done():
return
case wresp, ok := <-wch:
if !ok {
lg.Warn("watch closed", zap.String("target", m.target))
return
}
if wresp.Err() != nil {
lg.Warn("watch failed", zap.String("target", m.target), zap.Error(wresp.Err()))
return
}
deltaUps := make([]*Update, 0, len(wresp.Events))
for _, e := range wresp.Events {
var iup internal.Update
var err error
var op Operation
switch e.Type {
case clientv3.EventTypePut:
err = json.Unmarshal(e.Kv.Value, &iup)
op = Add
if err != nil {
lg.Warn("unmarshal endpoint update failed", zap.String("key", string(e.Kv.Key)), zap.Error(err))
continue
}
case clientv3.EventTypeDelete:
iup = internal.Update{Op: internal.Delete}
op = Delete
default:
continue
}
up := &Update{Op: op, Key: string(e.Kv.Key), Endpoint: Endpoint{Addr: iup.Addr, Metadata: iup.Metadata}}
deltaUps = append(deltaUps, up)
}
if len(deltaUps) > 0 {
upch <- deltaUps
}
}
}
}
func (m *endpointManager) List(ctx context.Context) (Key2EndpointMap, error) {
resp, err := m.client.Get(ctx, m.target, clientv3.WithPrefix(), clientv3.WithSerializable())
if err != nil {
return nil, err
}
eps := make(Key2EndpointMap)
for _, kv := range resp.Kvs {
var iup internal.Update
if err := json.Unmarshal(kv.Value, &iup); err != nil {
continue
}
eps[string(kv.Key)] = Endpoint{Addr: iup.Addr, Metadata: iup.Metadata}
}
return eps, nil
}

View File

@ -1,38 +0,0 @@
package internal
// Operation describes action performed on endpoint (addition vs deletion).
// Must stay JSON-format compatible with:
// https://pkg.go.dev/google.golang.org/grpc@v1.29.1/naming#Operation
type Operation uint8
const (
// Add indicates a new address is added.
Add Operation = iota
// Delete indicates an existing address is deleted.
Delete
)
// Update defines a persistent (JSON marshalled) format representing
// endpoint within the etcd storage.
//
// As the format can be persisted by one version of etcd client library and
// read by other the format must be kept backward compatible and
// in particular must be superset of the grpc(<=1.29.1) naming.Update structure:
// https://pkg.go.dev/google.golang.org/grpc@v1.29.1/naming#Update
//
// Please document since which version of etcd-client given property is supported.
// Please keep the naming consistent with e.g. https://pkg.go.dev/google.golang.org/grpc/resolver#Address.
//
// Notice that it is not valid having both empty string Addr and nil Metadata in an Update.
type Update struct {
// Op indicates the operation of the update.
// Since etcd 3.1.
Op Operation
// Addr is the updated address. It is empty string if there is no address update.
// Since etcd 3.1.
Addr string
// Metadata is the updated metadata. It is nil if there is no metadata update.
// Metadata is not required for a custom naming implementation.
// Since etcd 3.1.
Metadata interface{}
}

View File

@ -1,115 +0,0 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"context"
pb "go.etcd.io/etcd/api/v3/etcdserverpb"
"go.etcd.io/etcd/client/v3"
)
type AuthProxy struct {
client *clientv3.Client
}
func NewAuthProxy(c *clientv3.Client) pb.AuthServer {
return &AuthProxy{client: c}
}
func (ap *AuthProxy) AuthEnable(ctx context.Context, r *pb.AuthEnableRequest) (*pb.AuthEnableResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).AuthEnable(ctx, r)
}
func (ap *AuthProxy) AuthDisable(ctx context.Context, r *pb.AuthDisableRequest) (*pb.AuthDisableResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).AuthDisable(ctx, r)
}
func (ap *AuthProxy) AuthStatus(ctx context.Context, r *pb.AuthStatusRequest) (*pb.AuthStatusResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).AuthStatus(ctx, r)
}
func (ap *AuthProxy) Authenticate(ctx context.Context, r *pb.AuthenticateRequest) (*pb.AuthenticateResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).Authenticate(ctx, r)
}
func (ap *AuthProxy) RoleAdd(ctx context.Context, r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).RoleAdd(ctx, r)
}
func (ap *AuthProxy) RoleDelete(ctx context.Context, r *pb.AuthRoleDeleteRequest) (*pb.AuthRoleDeleteResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).RoleDelete(ctx, r)
}
func (ap *AuthProxy) RoleGet(ctx context.Context, r *pb.AuthRoleGetRequest) (*pb.AuthRoleGetResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).RoleGet(ctx, r)
}
func (ap *AuthProxy) RoleList(ctx context.Context, r *pb.AuthRoleListRequest) (*pb.AuthRoleListResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).RoleList(ctx, r)
}
func (ap *AuthProxy) RoleRevokePermission(ctx context.Context, r *pb.AuthRoleRevokePermissionRequest) (*pb.AuthRoleRevokePermissionResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).RoleRevokePermission(ctx, r)
}
func (ap *AuthProxy) RoleGrantPermission(ctx context.Context, r *pb.AuthRoleGrantPermissionRequest) (*pb.AuthRoleGrantPermissionResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).RoleGrantPermission(ctx, r)
}
func (ap *AuthProxy) UserAdd(ctx context.Context, r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).UserAdd(ctx, r)
}
func (ap *AuthProxy) UserDelete(ctx context.Context, r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).UserDelete(ctx, r)
}
func (ap *AuthProxy) UserGet(ctx context.Context, r *pb.AuthUserGetRequest) (*pb.AuthUserGetResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).UserGet(ctx, r)
}
func (ap *AuthProxy) UserList(ctx context.Context, r *pb.AuthUserListRequest) (*pb.AuthUserListResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).UserList(ctx, r)
}
func (ap *AuthProxy) UserGrantRole(ctx context.Context, r *pb.AuthUserGrantRoleRequest) (*pb.AuthUserGrantRoleResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).UserGrantRole(ctx, r)
}
func (ap *AuthProxy) UserRevokeRole(ctx context.Context, r *pb.AuthUserRevokeRoleRequest) (*pb.AuthUserRevokeRoleResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).UserRevokeRole(ctx, r)
}
func (ap *AuthProxy) UserChangePassword(ctx context.Context, r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error) {
conn := ap.client.ActiveConnection()
return pb.NewAuthClient(conn).UserChangePassword(ctx, r)
}

View File

@ -1,172 +0,0 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package cache exports functionality for efficiently caching and mapping
// `RangeRequest`s to corresponding `RangeResponse`s.
package cache
import (
"errors"
"sync"
"github.com/golang/groupcache/lru"
pb "go.etcd.io/etcd/api/v3/etcdserverpb"
"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
"go.etcd.io/etcd/pkg/v3/adt"
)
var (
DefaultMaxEntries = 2048
ErrCompacted = rpctypes.ErrGRPCCompacted
)
type Cache interface {
Add(req *pb.RangeRequest, resp *pb.RangeResponse)
Get(req *pb.RangeRequest) (*pb.RangeResponse, error)
Compact(revision int64)
Invalidate(key []byte, endkey []byte)
Size() int
Close()
}
// keyFunc returns the key of a request, which is used to look up its caching response in the cache.
func keyFunc(req *pb.RangeRequest) string {
// TODO: use marshalTo to reduce allocation
b, err := req.Marshal()
if err != nil {
panic(err)
}
return string(b)
}
func NewCache(maxCacheEntries int) Cache {
return &cache{
lru: lru.New(maxCacheEntries),
cachedRanges: adt.NewIntervalTree(),
compactedRev: -1,
}
}
func (c *cache) Close() {}
// cache implements Cache
type cache struct {
mu sync.RWMutex
lru *lru.Cache
// a reverse index for cache invalidation
cachedRanges adt.IntervalTree
compactedRev int64
}
// Add adds the response of a request to the cache if its revision is larger than the compacted revision of the cache.
func (c *cache) Add(req *pb.RangeRequest, resp *pb.RangeResponse) {
key := keyFunc(req)
c.mu.Lock()
defer c.mu.Unlock()
if req.Revision > c.compactedRev {
c.lru.Add(key, resp)
}
// we do not need to invalidate a request with a revision specified.
// so we do not need to add it into the reverse index.
if req.Revision != 0 {
return
}
var (
iv *adt.IntervalValue
ivl adt.Interval
)
if len(req.RangeEnd) != 0 {
ivl = adt.NewStringAffineInterval(string(req.Key), string(req.RangeEnd))
} else {
ivl = adt.NewStringAffinePoint(string(req.Key))
}
iv = c.cachedRanges.Find(ivl)
if iv == nil {
val := map[string]struct{}{key: {}}
c.cachedRanges.Insert(ivl, val)
} else {
val := iv.Val.(map[string]struct{})
val[key] = struct{}{}
iv.Val = val
}
}
// Get looks up the caching response for a given request.
// Get is also responsible for lazy eviction when accessing compacted entries.
func (c *cache) Get(req *pb.RangeRequest) (*pb.RangeResponse, error) {
key := keyFunc(req)
c.mu.Lock()
defer c.mu.Unlock()
if req.Revision > 0 && req.Revision < c.compactedRev {
c.lru.Remove(key)
return nil, ErrCompacted
}
if resp, ok := c.lru.Get(key); ok {
return resp.(*pb.RangeResponse), nil
}
return nil, errors.New("not exist")
}
// Invalidate invalidates the cache entries that intersecting with the given range from key to endkey.
func (c *cache) Invalidate(key, endkey []byte) {
c.mu.Lock()
defer c.mu.Unlock()
var (
ivs []*adt.IntervalValue
ivl adt.Interval
)
if len(endkey) == 0 {
ivl = adt.NewStringAffinePoint(string(key))
} else {
ivl = adt.NewStringAffineInterval(string(key), string(endkey))
}
ivs = c.cachedRanges.Stab(ivl)
for _, iv := range ivs {
keys := iv.Val.(map[string]struct{})
for key := range keys {
c.lru.Remove(key)
}
}
// delete after removing all keys since it is destructive to 'ivs'
c.cachedRanges.Delete(ivl)
}
// Compact invalidate all caching response before the given rev.
// Replace with the invalidation is lazy. The actual removal happens when the entries is accessed.
func (c *cache) Compact(revision int64) {
c.mu.Lock()
defer c.mu.Unlock()
if revision > c.compactedRev {
c.compactedRev = revision
}
}
func (c *cache) Size() int {
c.mu.RLock()
defer c.mu.RUnlock()
return c.lru.Len()
}

View File

@ -1,213 +0,0 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"context"
"errors"
"fmt"
"os"
"sync"
pb "go.etcd.io/etcd/api/v3/etcdserverpb"
"go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/client/v3/naming/endpoints"
"golang.org/x/time/rate"
"go.uber.org/zap"
)
// allow maximum 1 retry per second
const resolveRetryRate = 1
type clusterProxy struct {
lg *zap.Logger
clus clientv3.Cluster
ctx context.Context
// advertise client URL
advaddr string
prefix string
em endpoints.Manager
umu sync.RWMutex
umap map[string]endpoints.Endpoint
}
// NewClusterProxy takes optional prefix to fetch grpc-proxy member endpoints.
// The returned channel is closed when there is grpc-proxy endpoint registered
// and the client's context is canceled so the 'register' loop returns.
// TODO: Expand the API to report creation errors
func NewClusterProxy(lg *zap.Logger, c *clientv3.Client, advaddr string, prefix string) (pb.ClusterServer, <-chan struct{}) {
if lg == nil {
lg = zap.NewNop()
}
var em endpoints.Manager
if advaddr != "" && prefix != "" {
var err error
if em, err = endpoints.NewManager(c, prefix); err != nil {
lg.Error("failed to provision endpointsManager", zap.String("prefix", prefix), zap.Error(err))
return nil, nil
}
}
cp := &clusterProxy{
lg: lg,
clus: c.Cluster,
ctx: c.Ctx(),
advaddr: advaddr,
prefix: prefix,
umap: make(map[string]endpoints.Endpoint),
em: em,
}
donec := make(chan struct{})
if em != nil {
go func() {
defer close(donec)
cp.establishEndpointWatch(prefix)
}()
return cp, donec
}
close(donec)
return cp, donec
}
func (cp *clusterProxy) establishEndpointWatch(prefix string) {
rm := rate.NewLimiter(rate.Limit(resolveRetryRate), resolveRetryRate)
for rm.Wait(cp.ctx) == nil {
wc, err := cp.em.NewWatchChannel(cp.ctx)
if err != nil {
cp.lg.Warn("failed to establish endpoint watch", zap.String("prefix", prefix), zap.Error(err))
continue
}
cp.monitor(wc)
}
}
func (cp *clusterProxy) monitor(wa endpoints.WatchChannel) {
for {
select {
case <-cp.ctx.Done():
cp.lg.Info("watching endpoints interrupted", zap.Error(cp.ctx.Err()))
return
case updates := <-wa:
cp.umu.Lock()
for _, up := range updates {
switch up.Op {
case endpoints.Add:
cp.umap[up.Endpoint.Addr] = up.Endpoint
case endpoints.Delete:
delete(cp.umap, up.Endpoint.Addr)
}
}
cp.umu.Unlock()
}
}
}
func (cp *clusterProxy) MemberAdd(ctx context.Context, r *pb.MemberAddRequest) (*pb.MemberAddResponse, error) {
if r.IsLearner {
return cp.memberAddAsLearner(ctx, r.PeerURLs)
}
return cp.memberAdd(ctx, r.PeerURLs)
}
func (cp *clusterProxy) memberAdd(ctx context.Context, peerURLs []string) (*pb.MemberAddResponse, error) {
mresp, err := cp.clus.MemberAdd(ctx, peerURLs)
if err != nil {
return nil, err
}
resp := (pb.MemberAddResponse)(*mresp)
return &resp, err
}
func (cp *clusterProxy) memberAddAsLearner(ctx context.Context, peerURLs []string) (*pb.MemberAddResponse, error) {
mresp, err := cp.clus.MemberAddAsLearner(ctx, peerURLs)
if err != nil {
return nil, err
}
resp := (pb.MemberAddResponse)(*mresp)
return &resp, err
}
func (cp *clusterProxy) MemberRemove(ctx context.Context, r *pb.MemberRemoveRequest) (*pb.MemberRemoveResponse, error) {
mresp, err := cp.clus.MemberRemove(ctx, r.ID)
if err != nil {
return nil, err
}
resp := (pb.MemberRemoveResponse)(*mresp)
return &resp, err
}
func (cp *clusterProxy) MemberUpdate(ctx context.Context, r *pb.MemberUpdateRequest) (*pb.MemberUpdateResponse, error) {
mresp, err := cp.clus.MemberUpdate(ctx, r.ID, r.PeerURLs)
if err != nil {
return nil, err
}
resp := (pb.MemberUpdateResponse)(*mresp)
return &resp, err
}
func (cp *clusterProxy) membersFromUpdates() ([]*pb.Member, error) {
cp.umu.RLock()
defer cp.umu.RUnlock()
mbs := make([]*pb.Member, 0, len(cp.umap))
for addr, upt := range cp.umap {
m, err := decodeMeta(fmt.Sprint(upt.Metadata))
if err != nil {
return nil, err
}
mbs = append(mbs, &pb.Member{Name: m.Name, ClientURLs: []string{addr}})
}
return mbs, nil
}
// MemberList wraps member list API with following rules:
// - If 'advaddr' is not empty and 'prefix' is not empty, return registered member lists via resolver
// - If 'advaddr' is not empty and 'prefix' is not empty and registered grpc-proxy members haven't been fetched, return the 'advaddr'
// - If 'advaddr' is not empty and 'prefix' is empty, return 'advaddr' without forcing it to 'register'
// - If 'advaddr' is empty, forward to member list API
func (cp *clusterProxy) MemberList(ctx context.Context, r *pb.MemberListRequest) (*pb.MemberListResponse, error) {
if cp.advaddr != "" {
if cp.prefix != "" {
mbs, err := cp.membersFromUpdates()
if err != nil {
return nil, err
}
if len(mbs) > 0 {
return &pb.MemberListResponse{Members: mbs}, nil
}
}
// prefix is empty or no grpc-proxy members haven't been registered
hostname, _ := os.Hostname()
return &pb.MemberListResponse{Members: []*pb.Member{{Name: hostname, ClientURLs: []string{cp.advaddr}}}}, nil
}
mresp, err := cp.clus.MemberList(ctx)
if err != nil {
return nil, err
}
resp := (pb.MemberListResponse)(*mresp)
return &resp, err
}
func (cp *clusterProxy) MemberPromote(ctx context.Context, r *pb.MemberPromoteRequest) (*pb.MemberPromoteResponse, error) {
// TODO: implement
return nil, errors.New("not implemented")
}

View File

@ -1,16 +0,0 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package grpcproxy is an OSI level 7 proxy for etcd v3 API requests.
package grpcproxy

View File

@ -1,65 +0,0 @@
// Copyright 2017 The etcd Lockors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"context"
"go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/server/v3/etcdserver/api/v3election/v3electionpb"
)
type electionProxy struct {
client *clientv3.Client
}
func NewElectionProxy(client *clientv3.Client) v3electionpb.ElectionServer {
return &electionProxy{client: client}
}
func (ep *electionProxy) Campaign(ctx context.Context, req *v3electionpb.CampaignRequest) (*v3electionpb.CampaignResponse, error) {
return v3electionpb.NewElectionClient(ep.client.ActiveConnection()).Campaign(ctx, req)
}
func (ep *electionProxy) Proclaim(ctx context.Context, req *v3electionpb.ProclaimRequest) (*v3electionpb.ProclaimResponse, error) {
return v3electionpb.NewElectionClient(ep.client.ActiveConnection()).Proclaim(ctx, req)
}
func (ep *electionProxy) Leader(ctx context.Context, req *v3electionpb.LeaderRequest) (*v3electionpb.LeaderResponse, error) {
return v3electionpb.NewElectionClient(ep.client.ActiveConnection()).Leader(ctx, req)
}
func (ep *electionProxy) Observe(req *v3electionpb.LeaderRequest, s v3electionpb.Election_ObserveServer) error {
conn := ep.client.ActiveConnection()
ctx, cancel := context.WithCancel(s.Context())
defer cancel()
sc, err := v3electionpb.NewElectionClient(conn).Observe(ctx, req)
if err != nil {
return err
}
for {
rr, err := sc.Recv()
if err != nil {
return err
}
if err = s.Send(rr); err != nil {
return err
}
}
}
func (ep *electionProxy) Resign(ctx context.Context, req *v3electionpb.ResignRequest) (*v3electionpb.ResignResponse, error) {
return v3electionpb.NewElectionClient(ep.client.ActiveConnection()).Resign(ctx, req)
}

View File

@ -1,76 +0,0 @@
// Copyright 2017 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"context"
"fmt"
"net/http"
"time"
"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
"go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/server/v3/etcdserver/api/etcdhttp"
"go.uber.org/zap"
)
// HandleHealth registers health handler on '/health'.
func HandleHealth(lg *zap.Logger, mux *http.ServeMux, c *clientv3.Client) {
if lg == nil {
lg = zap.NewNop()
}
mux.Handle(etcdhttp.PathHealth, etcdhttp.NewHealthHandler(lg, func(excludedAlarms etcdhttp.AlarmSet) etcdhttp.Health { return checkHealth(c) }))
}
// HandleProxyHealth registers health handler on '/proxy/health'.
func HandleProxyHealth(lg *zap.Logger, mux *http.ServeMux, c *clientv3.Client) {
if lg == nil {
lg = zap.NewNop()
}
mux.Handle(etcdhttp.PathProxyHealth, etcdhttp.NewHealthHandler(lg, func(excludedAlarms etcdhttp.AlarmSet) etcdhttp.Health { return checkProxyHealth(c) }))
}
func checkHealth(c *clientv3.Client) etcdhttp.Health {
h := etcdhttp.Health{Health: "false"}
ctx, cancel := context.WithTimeout(c.Ctx(), time.Second)
_, err := c.Get(ctx, "a")
cancel()
if err == nil || err == rpctypes.ErrPermissionDenied {
h.Health = "true"
} else {
h.Reason = fmt.Sprintf("GET ERROR:%s", err)
}
return h
}
func checkProxyHealth(c *clientv3.Client) etcdhttp.Health {
if c == nil {
return etcdhttp.Health{Health: "false", Reason: "no connection to proxy"}
}
h := checkHealth(c)
if h.Health != "true" {
return h
}
ctx, cancel := context.WithTimeout(c.Ctx(), time.Second*3)
ch := c.Watch(ctx, "a", clientv3.WithCreatedNotify())
select {
case <-ch:
case <-ctx.Done():
h.Health = "false"
h.Reason = "WATCH TIMEOUT"
}
cancel()
return h
}

View File

@ -1,232 +0,0 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"context"
pb "go.etcd.io/etcd/api/v3/etcdserverpb"
"go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/server/v3/proxy/grpcproxy/cache"
)
type kvProxy struct {
kv clientv3.KV
cache cache.Cache
}
func NewKvProxy(c *clientv3.Client) (pb.KVServer, <-chan struct{}) {
kv := &kvProxy{
kv: c.KV,
cache: cache.NewCache(cache.DefaultMaxEntries),
}
donec := make(chan struct{})
close(donec)
return kv, donec
}
func (p *kvProxy) Range(ctx context.Context, r *pb.RangeRequest) (*pb.RangeResponse, error) {
if r.Serializable {
resp, err := p.cache.Get(r)
switch err {
case nil:
cacheHits.Inc()
return resp, nil
case cache.ErrCompacted:
cacheHits.Inc()
return nil, err
}
cachedMisses.Inc()
}
resp, err := p.kv.Do(ctx, RangeRequestToOp(r))
if err != nil {
return nil, err
}
// cache linearizable as serializable
req := *r
req.Serializable = true
gresp := (*pb.RangeResponse)(resp.Get())
p.cache.Add(&req, gresp)
cacheKeys.Set(float64(p.cache.Size()))
return gresp, nil
}
func (p *kvProxy) Put(ctx context.Context, r *pb.PutRequest) (*pb.PutResponse, error) {
p.cache.Invalidate(r.Key, nil)
cacheKeys.Set(float64(p.cache.Size()))
resp, err := p.kv.Do(ctx, PutRequestToOp(r))
return (*pb.PutResponse)(resp.Put()), err
}
func (p *kvProxy) DeleteRange(ctx context.Context, r *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error) {
p.cache.Invalidate(r.Key, r.RangeEnd)
cacheKeys.Set(float64(p.cache.Size()))
resp, err := p.kv.Do(ctx, DelRequestToOp(r))
return (*pb.DeleteRangeResponse)(resp.Del()), err
}
func (p *kvProxy) txnToCache(reqs []*pb.RequestOp, resps []*pb.ResponseOp) {
for i := range resps {
switch tv := resps[i].Response.(type) {
case *pb.ResponseOp_ResponsePut:
p.cache.Invalidate(reqs[i].GetRequestPut().Key, nil)
case *pb.ResponseOp_ResponseDeleteRange:
rdr := reqs[i].GetRequestDeleteRange()
p.cache.Invalidate(rdr.Key, rdr.RangeEnd)
case *pb.ResponseOp_ResponseRange:
req := *(reqs[i].GetRequestRange())
req.Serializable = true
p.cache.Add(&req, tv.ResponseRange)
}
}
}
func (p *kvProxy) Txn(ctx context.Context, r *pb.TxnRequest) (*pb.TxnResponse, error) {
op := TxnRequestToOp(r)
opResp, err := p.kv.Do(ctx, op)
if err != nil {
return nil, err
}
resp := opResp.Txn()
// txn may claim an outdated key is updated; be safe and invalidate
for _, cmp := range r.Compare {
p.cache.Invalidate(cmp.Key, cmp.RangeEnd)
}
// update any fetched keys
if resp.Succeeded {
p.txnToCache(r.Success, resp.Responses)
} else {
p.txnToCache(r.Failure, resp.Responses)
}
cacheKeys.Set(float64(p.cache.Size()))
return (*pb.TxnResponse)(resp), nil
}
func (p *kvProxy) Compact(ctx context.Context, r *pb.CompactionRequest) (*pb.CompactionResponse, error) {
var opts []clientv3.CompactOption
if r.Physical {
opts = append(opts, clientv3.WithCompactPhysical())
}
resp, err := p.kv.Compact(ctx, r.Revision, opts...)
if err == nil {
p.cache.Compact(r.Revision)
}
cacheKeys.Set(float64(p.cache.Size()))
return (*pb.CompactionResponse)(resp), err
}
func requestOpToOp(union *pb.RequestOp) clientv3.Op {
switch tv := union.Request.(type) {
case *pb.RequestOp_RequestRange:
if tv.RequestRange != nil {
return RangeRequestToOp(tv.RequestRange)
}
case *pb.RequestOp_RequestPut:
if tv.RequestPut != nil {
return PutRequestToOp(tv.RequestPut)
}
case *pb.RequestOp_RequestDeleteRange:
if tv.RequestDeleteRange != nil {
return DelRequestToOp(tv.RequestDeleteRange)
}
case *pb.RequestOp_RequestTxn:
if tv.RequestTxn != nil {
return TxnRequestToOp(tv.RequestTxn)
}
}
panic("unknown request")
}
func RangeRequestToOp(r *pb.RangeRequest) clientv3.Op {
opts := []clientv3.OpOption{}
if len(r.RangeEnd) != 0 {
opts = append(opts, clientv3.WithRange(string(r.RangeEnd)))
}
opts = append(opts, clientv3.WithRev(r.Revision))
opts = append(opts, clientv3.WithLimit(r.Limit))
opts = append(opts, clientv3.WithSort(
clientv3.SortTarget(r.SortTarget),
clientv3.SortOrder(r.SortOrder)),
)
opts = append(opts, clientv3.WithMaxCreateRev(r.MaxCreateRevision))
opts = append(opts, clientv3.WithMinCreateRev(r.MinCreateRevision))
opts = append(opts, clientv3.WithMaxModRev(r.MaxModRevision))
opts = append(opts, clientv3.WithMinModRev(r.MinModRevision))
if r.CountOnly {
opts = append(opts, clientv3.WithCountOnly())
}
if r.KeysOnly {
opts = append(opts, clientv3.WithKeysOnly())
}
if r.Serializable {
opts = append(opts, clientv3.WithSerializable())
}
return clientv3.OpGet(string(r.Key), opts...)
}
func PutRequestToOp(r *pb.PutRequest) clientv3.Op {
opts := []clientv3.OpOption{}
opts = append(opts, clientv3.WithLease(clientv3.LeaseID(r.Lease)))
if r.IgnoreValue {
opts = append(opts, clientv3.WithIgnoreValue())
}
if r.IgnoreLease {
opts = append(opts, clientv3.WithIgnoreLease())
}
if r.PrevKv {
opts = append(opts, clientv3.WithPrevKV())
}
return clientv3.OpPut(string(r.Key), string(r.Value), opts...)
}
func DelRequestToOp(r *pb.DeleteRangeRequest) clientv3.Op {
opts := []clientv3.OpOption{}
if len(r.RangeEnd) != 0 {
opts = append(opts, clientv3.WithRange(string(r.RangeEnd)))
}
if r.PrevKv {
opts = append(opts, clientv3.WithPrevKV())
}
return clientv3.OpDelete(string(r.Key), opts...)
}
func TxnRequestToOp(r *pb.TxnRequest) clientv3.Op {
cmps := make([]clientv3.Cmp, len(r.Compare))
thenops := make([]clientv3.Op, len(r.Success))
elseops := make([]clientv3.Op, len(r.Failure))
for i := range r.Compare {
cmps[i] = (clientv3.Cmp)(*r.Compare[i])
}
for i := range r.Success {
thenops[i] = requestOpToOp(r.Success[i])
}
for i := range r.Failure {
elseops[i] = requestOpToOp(r.Failure[i])
}
return clientv3.OpTxn(cmps, thenops, elseops)
}

View File

@ -1,113 +0,0 @@
// Copyright 2017 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"context"
"math"
"sync"
"go.etcd.io/etcd/client/v3"
"golang.org/x/time/rate"
)
const (
lostLeaderKey = "__lostleader" // watched to detect leader loss
retryPerSecond = 10
)
type leader struct {
ctx context.Context
w clientv3.Watcher
// mu protects leaderc updates.
mu sync.RWMutex
leaderc chan struct{}
disconnc chan struct{}
donec chan struct{}
}
func newLeader(ctx context.Context, w clientv3.Watcher) *leader {
l := &leader{
ctx: clientv3.WithRequireLeader(ctx),
w: w,
leaderc: make(chan struct{}),
disconnc: make(chan struct{}),
donec: make(chan struct{}),
}
// begin assuming leader is lost
close(l.leaderc)
go l.recvLoop()
return l
}
func (l *leader) recvLoop() {
defer close(l.donec)
limiter := rate.NewLimiter(rate.Limit(retryPerSecond), retryPerSecond)
rev := int64(math.MaxInt64 - 2)
for limiter.Wait(l.ctx) == nil {
wch := l.w.Watch(l.ctx, lostLeaderKey, clientv3.WithRev(rev), clientv3.WithCreatedNotify())
cresp, ok := <-wch
if !ok {
l.loseLeader()
continue
}
if cresp.Err() != nil {
l.loseLeader()
if clientv3.IsConnCanceled(cresp.Err()) {
close(l.disconnc)
return
}
continue
}
l.gotLeader()
<-wch
l.loseLeader()
}
}
func (l *leader) loseLeader() {
l.mu.RLock()
defer l.mu.RUnlock()
select {
case <-l.leaderc:
default:
close(l.leaderc)
}
}
// gotLeader will force update the leadership status to having a leader.
func (l *leader) gotLeader() {
l.mu.Lock()
defer l.mu.Unlock()
select {
case <-l.leaderc:
l.leaderc = make(chan struct{})
default:
}
}
func (l *leader) disconnectNotify() <-chan struct{} { return l.disconnc }
func (l *leader) stopNotify() <-chan struct{} { return l.donec }
// lostNotify returns a channel that is closed if there has been
// a leader loss not yet followed by a leader reacquire.
func (l *leader) lostNotify() <-chan struct{} {
l.mu.RLock()
defer l.mu.RUnlock()
return l.leaderc
}

View File

@ -1,384 +0,0 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"context"
"io"
"sync"
"sync/atomic"
"time"
pb "go.etcd.io/etcd/api/v3/etcdserverpb"
"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
"go.etcd.io/etcd/client/v3"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
type leaseProxy struct {
// leaseClient handles req from LeaseGrant() that requires a lease ID.
leaseClient pb.LeaseClient
lessor clientv3.Lease
ctx context.Context
leader *leader
// mu protects adding outstanding leaseProxyStream through wg.
mu sync.RWMutex
// wg waits until all outstanding leaseProxyStream quit.
wg sync.WaitGroup
}
func NewLeaseProxy(ctx context.Context, c *clientv3.Client) (pb.LeaseServer, <-chan struct{}) {
cctx, cancel := context.WithCancel(ctx)
lp := &leaseProxy{
leaseClient: pb.NewLeaseClient(c.ActiveConnection()),
lessor: c.Lease,
ctx: cctx,
leader: newLeader(cctx, c.Watcher),
}
ch := make(chan struct{})
go func() {
defer close(ch)
<-lp.leader.stopNotify()
lp.mu.Lock()
select {
case <-lp.ctx.Done():
case <-lp.leader.disconnectNotify():
cancel()
}
<-lp.ctx.Done()
lp.mu.Unlock()
lp.wg.Wait()
}()
return lp, ch
}
func (lp *leaseProxy) LeaseGrant(ctx context.Context, cr *pb.LeaseGrantRequest) (*pb.LeaseGrantResponse, error) {
rp, err := lp.leaseClient.LeaseGrant(ctx, cr, grpc.WaitForReady(true))
if err != nil {
return nil, err
}
lp.leader.gotLeader()
return rp, nil
}
func (lp *leaseProxy) LeaseRevoke(ctx context.Context, rr *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error) {
r, err := lp.lessor.Revoke(ctx, clientv3.LeaseID(rr.ID))
if err != nil {
return nil, err
}
lp.leader.gotLeader()
return (*pb.LeaseRevokeResponse)(r), nil
}
func (lp *leaseProxy) LeaseTimeToLive(ctx context.Context, rr *pb.LeaseTimeToLiveRequest) (*pb.LeaseTimeToLiveResponse, error) {
var (
r *clientv3.LeaseTimeToLiveResponse
err error
)
if rr.Keys {
r, err = lp.lessor.TimeToLive(ctx, clientv3.LeaseID(rr.ID), clientv3.WithAttachedKeys())
} else {
r, err = lp.lessor.TimeToLive(ctx, clientv3.LeaseID(rr.ID))
}
if err != nil {
return nil, err
}
rp := &pb.LeaseTimeToLiveResponse{
Header: r.ResponseHeader,
ID: int64(r.ID),
TTL: r.TTL,
GrantedTTL: r.GrantedTTL,
Keys: r.Keys,
}
return rp, err
}
func (lp *leaseProxy) LeaseLeases(ctx context.Context, rr *pb.LeaseLeasesRequest) (*pb.LeaseLeasesResponse, error) {
r, err := lp.lessor.Leases(ctx)
if err != nil {
return nil, err
}
leases := make([]*pb.LeaseStatus, len(r.Leases))
for i := range r.Leases {
leases[i] = &pb.LeaseStatus{ID: int64(r.Leases[i].ID)}
}
rp := &pb.LeaseLeasesResponse{
Header: r.ResponseHeader,
Leases: leases,
}
return rp, err
}
func (lp *leaseProxy) LeaseKeepAlive(stream pb.Lease_LeaseKeepAliveServer) error {
lp.mu.Lock()
select {
case <-lp.ctx.Done():
lp.mu.Unlock()
return lp.ctx.Err()
default:
lp.wg.Add(1)
}
lp.mu.Unlock()
ctx, cancel := context.WithCancel(stream.Context())
lps := leaseProxyStream{
stream: stream,
lessor: lp.lessor,
keepAliveLeases: make(map[int64]*atomicCounter),
respc: make(chan *pb.LeaseKeepAliveResponse),
ctx: ctx,
cancel: cancel,
}
errc := make(chan error, 2)
var lostLeaderC <-chan struct{}
if md, ok := metadata.FromOutgoingContext(stream.Context()); ok {
v := md[rpctypes.MetadataRequireLeaderKey]
if len(v) > 0 && v[0] == rpctypes.MetadataHasLeader {
lostLeaderC = lp.leader.lostNotify()
// if leader is known to be lost at creation time, avoid
// letting events through at all
select {
case <-lostLeaderC:
lp.wg.Done()
return rpctypes.ErrNoLeader
default:
}
}
}
stopc := make(chan struct{}, 3)
go func() {
defer func() { stopc <- struct{}{} }()
if err := lps.recvLoop(); err != nil {
errc <- err
}
}()
go func() {
defer func() { stopc <- struct{}{} }()
if err := lps.sendLoop(); err != nil {
errc <- err
}
}()
// tears down LeaseKeepAlive stream if leader goes down or entire leaseProxy is terminated.
go func() {
defer func() { stopc <- struct{}{} }()
select {
case <-lostLeaderC:
case <-ctx.Done():
case <-lp.ctx.Done():
}
}()
var err error
select {
case <-stopc:
stopc <- struct{}{}
case err = <-errc:
}
cancel()
// recv/send may only shutdown after function exits;
// this goroutine notifies lease proxy that the stream is through
go func() {
<-stopc
<-stopc
<-stopc
lps.close()
close(errc)
lp.wg.Done()
}()
select {
case <-lostLeaderC:
return rpctypes.ErrNoLeader
case <-lp.leader.disconnectNotify():
return status.Error(codes.Canceled, "the client connection is closing")
default:
if err != nil {
return err
}
return ctx.Err()
}
}
type leaseProxyStream struct {
stream pb.Lease_LeaseKeepAliveServer
lessor clientv3.Lease
// wg tracks keepAliveLoop goroutines
wg sync.WaitGroup
// mu protects keepAliveLeases
mu sync.RWMutex
// keepAliveLeases tracks how many outstanding keepalive requests which need responses are on a lease.
keepAliveLeases map[int64]*atomicCounter
// respc receives lease keepalive responses from etcd backend
respc chan *pb.LeaseKeepAliveResponse
ctx context.Context
cancel context.CancelFunc
}
func (lps *leaseProxyStream) recvLoop() error {
for {
rr, err := lps.stream.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
lps.mu.Lock()
neededResps, ok := lps.keepAliveLeases[rr.ID]
if !ok {
neededResps = &atomicCounter{}
lps.keepAliveLeases[rr.ID] = neededResps
lps.wg.Add(1)
go func() {
defer lps.wg.Done()
if err := lps.keepAliveLoop(rr.ID, neededResps); err != nil {
lps.cancel()
}
}()
}
neededResps.add(1)
lps.mu.Unlock()
}
}
func (lps *leaseProxyStream) keepAliveLoop(leaseID int64, neededResps *atomicCounter) error {
cctx, ccancel := context.WithCancel(lps.ctx)
defer ccancel()
respc, err := lps.lessor.KeepAlive(cctx, clientv3.LeaseID(leaseID))
if err != nil {
return err
}
// ticker expires when loop hasn't received keepalive within TTL
var ticker <-chan time.Time
for {
select {
case <-ticker:
lps.mu.Lock()
// if there are outstanding keepAlive reqs at the moment of ticker firing,
// don't close keepAliveLoop(), let it continuing to process the KeepAlive reqs.
if neededResps.get() > 0 {
lps.mu.Unlock()
ticker = nil
continue
}
delete(lps.keepAliveLeases, leaseID)
lps.mu.Unlock()
return nil
case rp, ok := <-respc:
if !ok {
lps.mu.Lock()
delete(lps.keepAliveLeases, leaseID)
lps.mu.Unlock()
if neededResps.get() == 0 {
return nil
}
ttlResp, err := lps.lessor.TimeToLive(cctx, clientv3.LeaseID(leaseID))
if err != nil {
return err
}
r := &pb.LeaseKeepAliveResponse{
Header: ttlResp.ResponseHeader,
ID: int64(ttlResp.ID),
TTL: ttlResp.TTL,
}
for neededResps.get() > 0 {
select {
case lps.respc <- r:
neededResps.add(-1)
case <-lps.ctx.Done():
return nil
}
}
return nil
}
if neededResps.get() == 0 {
continue
}
ticker = time.After(time.Duration(rp.TTL) * time.Second)
r := &pb.LeaseKeepAliveResponse{
Header: rp.ResponseHeader,
ID: int64(rp.ID),
TTL: rp.TTL,
}
lps.replyToClient(r, neededResps)
}
}
}
func (lps *leaseProxyStream) replyToClient(r *pb.LeaseKeepAliveResponse, neededResps *atomicCounter) {
timer := time.After(500 * time.Millisecond)
for neededResps.get() > 0 {
select {
case lps.respc <- r:
neededResps.add(-1)
case <-timer:
return
case <-lps.ctx.Done():
return
}
}
}
func (lps *leaseProxyStream) sendLoop() error {
for {
select {
case lrp, ok := <-lps.respc:
if !ok {
return nil
}
if err := lps.stream.Send(lrp); err != nil {
return err
}
case <-lps.ctx.Done():
return lps.ctx.Err()
}
}
}
func (lps *leaseProxyStream) close() {
lps.cancel()
lps.wg.Wait()
// only close respc channel if all the keepAliveLoop() goroutines have finished
// this ensures those goroutines don't send resp to a closed resp channel
close(lps.respc)
}
type atomicCounter struct {
counter int64
}
func (ac *atomicCounter) add(delta int64) {
atomic.AddInt64(&ac.counter, delta)
}
func (ac *atomicCounter) get() int64 {
return atomic.LoadInt64(&ac.counter)
}

View File

@ -1,38 +0,0 @@
// Copyright 2017 The etcd Lockors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"context"
"go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/server/v3/etcdserver/api/v3lock/v3lockpb"
)
type lockProxy struct {
client *clientv3.Client
}
func NewLockProxy(client *clientv3.Client) v3lockpb.LockServer {
return &lockProxy{client: client}
}
func (lp *lockProxy) Lock(ctx context.Context, req *v3lockpb.LockRequest) (*v3lockpb.LockResponse, error) {
return v3lockpb.NewLockClient(lp.client.ActiveConnection()).Lock(ctx, req)
}
func (lp *lockProxy) Unlock(ctx context.Context, req *v3lockpb.UnlockRequest) (*v3lockpb.UnlockResponse, error) {
return v3lockpb.NewLockClient(lp.client.ActiveConnection()).Unlock(ctx, req)
}

View File

@ -1,95 +0,0 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"context"
"io"
pb "go.etcd.io/etcd/api/v3/etcdserverpb"
"go.etcd.io/etcd/client/v3"
)
type maintenanceProxy struct {
client *clientv3.Client
}
func NewMaintenanceProxy(c *clientv3.Client) pb.MaintenanceServer {
return &maintenanceProxy{
client: c,
}
}
func (mp *maintenanceProxy) Defragment(ctx context.Context, dr *pb.DefragmentRequest) (*pb.DefragmentResponse, error) {
conn := mp.client.ActiveConnection()
return pb.NewMaintenanceClient(conn).Defragment(ctx, dr)
}
func (mp *maintenanceProxy) Snapshot(sr *pb.SnapshotRequest, stream pb.Maintenance_SnapshotServer) error {
conn := mp.client.ActiveConnection()
ctx, cancel := context.WithCancel(stream.Context())
defer cancel()
ctx = withClientAuthToken(ctx, stream.Context())
sc, err := pb.NewMaintenanceClient(conn).Snapshot(ctx, sr)
if err != nil {
return err
}
for {
rr, err := sc.Recv()
if err != nil {
if err == io.EOF {
return nil
}
return err
}
err = stream.Send(rr)
if err != nil {
return err
}
}
}
func (mp *maintenanceProxy) Hash(ctx context.Context, r *pb.HashRequest) (*pb.HashResponse, error) {
conn := mp.client.ActiveConnection()
return pb.NewMaintenanceClient(conn).Hash(ctx, r)
}
func (mp *maintenanceProxy) HashKV(ctx context.Context, r *pb.HashKVRequest) (*pb.HashKVResponse, error) {
conn := mp.client.ActiveConnection()
return pb.NewMaintenanceClient(conn).HashKV(ctx, r)
}
func (mp *maintenanceProxy) Alarm(ctx context.Context, r *pb.AlarmRequest) (*pb.AlarmResponse, error) {
conn := mp.client.ActiveConnection()
return pb.NewMaintenanceClient(conn).Alarm(ctx, r)
}
func (mp *maintenanceProxy) Status(ctx context.Context, r *pb.StatusRequest) (*pb.StatusResponse, error) {
conn := mp.client.ActiveConnection()
return pb.NewMaintenanceClient(conn).Status(ctx, r)
}
func (mp *maintenanceProxy) MoveLeader(ctx context.Context, r *pb.MoveLeaderRequest) (*pb.MoveLeaderResponse, error) {
conn := mp.client.ActiveConnection()
return pb.NewMaintenanceClient(conn).MoveLeader(ctx, r)
}
func (mp *maintenanceProxy) Downgrade(ctx context.Context, r *pb.DowngradeRequest) (*pb.DowngradeResponse, error) {
conn := mp.client.ActiveConnection()
return pb.NewMaintenanceClient(conn).Downgrade(ctx, r)
}

View File

@ -1,121 +0,0 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"fmt"
"io/ioutil"
"math/rand"
"net/http"
"strings"
"time"
"github.com/prometheus/client_golang/prometheus"
"github.com/prometheus/client_golang/prometheus/promhttp"
"go.etcd.io/etcd/server/v3/etcdserver/api/etcdhttp"
)
var (
watchersCoalescing = prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: "etcd",
Subsystem: "grpc_proxy",
Name: "watchers_coalescing_total",
Help: "Total number of current watchers coalescing",
})
eventsCoalescing = prometheus.NewCounter(prometheus.CounterOpts{
Namespace: "etcd",
Subsystem: "grpc_proxy",
Name: "events_coalescing_total",
Help: "Total number of events coalescing",
})
cacheKeys = prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: "etcd",
Subsystem: "grpc_proxy",
Name: "cache_keys_total",
Help: "Total number of keys/ranges cached",
})
cacheHits = prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: "etcd",
Subsystem: "grpc_proxy",
Name: "cache_hits_total",
Help: "Total number of cache hits",
})
cachedMisses = prometheus.NewGauge(prometheus.GaugeOpts{
Namespace: "etcd",
Subsystem: "grpc_proxy",
Name: "cache_misses_total",
Help: "Total number of cache misses",
})
)
func init() {
prometheus.MustRegister(watchersCoalescing)
prometheus.MustRegister(eventsCoalescing)
prometheus.MustRegister(cacheKeys)
prometheus.MustRegister(cacheHits)
prometheus.MustRegister(cachedMisses)
}
// HandleMetrics performs a GET request against etcd endpoint and returns '/metrics'.
func HandleMetrics(mux *http.ServeMux, c *http.Client, eps []string) {
// random shuffle endpoints
r := rand.New(rand.NewSource(int64(time.Now().Nanosecond())))
if len(eps) > 1 {
eps = shuffleEndpoints(r, eps)
}
pathMetrics := etcdhttp.PathMetrics
mux.HandleFunc(pathMetrics, func(w http.ResponseWriter, r *http.Request) {
target := fmt.Sprintf("%s%s", eps[0], pathMetrics)
if !strings.HasPrefix(target, "http") {
scheme := "http"
if r.TLS != nil {
scheme = "https"
}
target = fmt.Sprintf("%s://%s", scheme, target)
}
resp, err := c.Get(target)
if err != nil {
http.Error(w, "Internal server error", http.StatusInternalServerError)
return
}
defer resp.Body.Close()
w.Header().Set("Content-Type", "text/plain; version=0.0.4")
body, _ := ioutil.ReadAll(resp.Body)
fmt.Fprintf(w, "%s", body)
})
}
// HandleProxyMetrics registers metrics handler on '/proxy/metrics'.
func HandleProxyMetrics(mux *http.ServeMux) {
mux.Handle(etcdhttp.PathProxyMetrics, promhttp.Handler())
}
func shuffleEndpoints(r *rand.Rand, eps []string) []string {
// copied from Go 1.9<= rand.Rand.Perm
n := len(eps)
p := make([]int, n)
for i := 0; i < n; i++ {
j := r.Intn(i + 1)
p[i] = p[j]
p[j] = i
}
neps := make([]string, n)
for i, k := range p {
neps[i] = eps[k]
}
return neps
}

View File

@ -1,102 +0,0 @@
// Copyright 2017 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"encoding/json"
"os"
"go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/client/v3/concurrency"
"go.etcd.io/etcd/client/v3/naming/endpoints"
"go.uber.org/zap"
"golang.org/x/time/rate"
)
// allow maximum 1 retry per second
const registerRetryRate = 1
// Register registers itself as a grpc-proxy server by writing prefixed-key
// with session of specified TTL (in seconds). The returned channel is closed
// when the client's context is canceled.
func Register(lg *zap.Logger, c *clientv3.Client, prefix string, addr string, ttl int) <-chan struct{} {
rm := rate.NewLimiter(rate.Limit(registerRetryRate), registerRetryRate)
donec := make(chan struct{})
go func() {
defer close(donec)
for rm.Wait(c.Ctx()) == nil {
ss, err := registerSession(lg, c, prefix, addr, ttl)
if err != nil {
lg.Warn("failed to create a session", zap.Error(err))
continue
}
select {
case <-c.Ctx().Done():
ss.Close()
return
case <-ss.Done():
lg.Warn("session expired; possible network partition or server restart")
lg.Warn("creating a new session to rejoin")
continue
}
}
}()
return donec
}
func registerSession(lg *zap.Logger, c *clientv3.Client, prefix string, addr string, ttl int) (*concurrency.Session, error) {
ss, err := concurrency.NewSession(c, concurrency.WithTTL(ttl))
if err != nil {
return nil, err
}
em, err := endpoints.NewManager(c, prefix)
if err != nil {
return nil, err
}
endpoint := endpoints.Endpoint{Addr: addr, Metadata: getMeta()}
if err = em.AddEndpoint(c.Ctx(), prefix+"/"+addr, endpoint, clientv3.WithLease(ss.Lease())); err != nil {
return nil, err
}
lg.Info(
"registered session with lease",
zap.String("addr", addr),
zap.Int("lease-ttl", ttl),
)
return ss, nil
}
// meta represents metadata of proxy register.
type meta struct {
Name string `json:"name"`
}
func getMeta() string {
hostname, _ := os.Hostname()
bts, _ := json.Marshal(meta{Name: hostname})
return string(bts)
}
func decodeMeta(s string) (meta, error) {
m := meta{}
err := json.Unmarshal([]byte(s), &m)
return m, err
}

View File

@ -1,75 +0,0 @@
// Copyright 2017 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"context"
"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
func getAuthTokenFromClient(ctx context.Context) string {
md, ok := metadata.FromIncomingContext(ctx)
if ok {
ts, ok := md[rpctypes.TokenFieldNameGRPC]
if ok {
return ts[0]
}
}
return ""
}
func withClientAuthToken(ctx, ctxWithToken context.Context) context.Context {
token := getAuthTokenFromClient(ctxWithToken)
if token != "" {
ctx = context.WithValue(ctx, rpctypes.TokenFieldNameGRPC, token)
}
return ctx
}
type proxyTokenCredential struct {
token string
}
func (cred *proxyTokenCredential) RequireTransportSecurity() bool {
return false
}
func (cred *proxyTokenCredential) GetRequestMetadata(ctx context.Context, s ...string) (map[string]string, error) {
return map[string]string{
rpctypes.TokenFieldNameGRPC: cred.token,
}, nil
}
func AuthUnaryClientInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
token := getAuthTokenFromClient(ctx)
if token != "" {
tokenCred := &proxyTokenCredential{token}
opts = append(opts, grpc.PerRPCCredentials(tokenCred))
}
return invoker(ctx, method, req, reply, cc, opts...)
}
func AuthStreamClientInterceptor(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
tokenif := ctx.Value(rpctypes.TokenFieldNameGRPC)
if tokenif != nil {
tokenCred := &proxyTokenCredential{tokenif.(string)}
opts = append(opts, grpc.PerRPCCredentials(tokenCred))
}
return streamer(ctx, desc, cc, method, opts...)
}

View File

@ -1,313 +0,0 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"context"
"sync"
pb "go.etcd.io/etcd/api/v3/etcdserverpb"
"go.etcd.io/etcd/api/v3/v3rpc/rpctypes"
"go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/server/v3/etcdserver/api/v3rpc"
"go.uber.org/zap"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
type watchProxy struct {
cw clientv3.Watcher
ctx context.Context
leader *leader
ranges *watchRanges
// mu protects adding outstanding watch servers through wg.
mu sync.Mutex
// wg waits until all outstanding watch servers quit.
wg sync.WaitGroup
// kv is used for permission checking
kv clientv3.KV
lg *zap.Logger
}
func NewWatchProxy(ctx context.Context, lg *zap.Logger, c *clientv3.Client) (pb.WatchServer, <-chan struct{}) {
cctx, cancel := context.WithCancel(ctx)
wp := &watchProxy{
cw: c.Watcher,
ctx: cctx,
leader: newLeader(cctx, c.Watcher),
kv: c.KV, // for permission checking
lg: lg,
}
wp.ranges = newWatchRanges(wp)
ch := make(chan struct{})
go func() {
defer close(ch)
<-wp.leader.stopNotify()
wp.mu.Lock()
select {
case <-wp.ctx.Done():
case <-wp.leader.disconnectNotify():
cancel()
}
<-wp.ctx.Done()
wp.mu.Unlock()
wp.wg.Wait()
wp.ranges.stop()
}()
return wp, ch
}
func (wp *watchProxy) Watch(stream pb.Watch_WatchServer) (err error) {
wp.mu.Lock()
select {
case <-wp.ctx.Done():
wp.mu.Unlock()
select {
case <-wp.leader.disconnectNotify():
return status.Error(codes.Canceled, "the client connection is closing")
default:
return wp.ctx.Err()
}
default:
wp.wg.Add(1)
}
wp.mu.Unlock()
ctx, cancel := context.WithCancel(stream.Context())
wps := &watchProxyStream{
ranges: wp.ranges,
watchers: make(map[int64]*watcher),
stream: stream,
watchCh: make(chan *pb.WatchResponse, 1024),
ctx: ctx,
cancel: cancel,
kv: wp.kv,
lg: wp.lg,
}
var lostLeaderC <-chan struct{}
if md, ok := metadata.FromOutgoingContext(stream.Context()); ok {
v := md[rpctypes.MetadataRequireLeaderKey]
if len(v) > 0 && v[0] == rpctypes.MetadataHasLeader {
lostLeaderC = wp.leader.lostNotify()
// if leader is known to be lost at creation time, avoid
// letting events through at all
select {
case <-lostLeaderC:
wp.wg.Done()
return rpctypes.ErrNoLeader
default:
}
}
}
// post to stopc => terminate server stream; can't use a waitgroup
// since all goroutines will only terminate after Watch() exits.
stopc := make(chan struct{}, 3)
go func() {
defer func() { stopc <- struct{}{} }()
wps.recvLoop()
}()
go func() {
defer func() { stopc <- struct{}{} }()
wps.sendLoop()
}()
// tear down watch if leader goes down or entire watch proxy is terminated
go func() {
defer func() { stopc <- struct{}{} }()
select {
case <-lostLeaderC:
case <-ctx.Done():
case <-wp.ctx.Done():
}
}()
<-stopc
cancel()
// recv/send may only shutdown after function exits;
// goroutine notifies proxy that stream is through
go func() {
<-stopc
<-stopc
wps.close()
wp.wg.Done()
}()
select {
case <-lostLeaderC:
return rpctypes.ErrNoLeader
case <-wp.leader.disconnectNotify():
return status.Error(codes.Canceled, "the client connection is closing")
default:
return wps.ctx.Err()
}
}
// watchProxyStream forwards etcd watch events to a proxied client stream.
type watchProxyStream struct {
ranges *watchRanges
// mu protects watchers and nextWatcherID
mu sync.Mutex
// watchers receive events from watch broadcast.
watchers map[int64]*watcher
// nextWatcherID is the id to assign the next watcher on this stream.
nextWatcherID int64
stream pb.Watch_WatchServer
// watchCh receives watch responses from the watchers.
watchCh chan *pb.WatchResponse
ctx context.Context
cancel context.CancelFunc
// kv is used for permission checking
kv clientv3.KV
lg *zap.Logger
}
func (wps *watchProxyStream) close() {
var wg sync.WaitGroup
wps.cancel()
wps.mu.Lock()
wg.Add(len(wps.watchers))
for _, wpsw := range wps.watchers {
go func(w *watcher) {
wps.ranges.delete(w)
wg.Done()
}(wpsw)
}
wps.watchers = nil
wps.mu.Unlock()
wg.Wait()
close(wps.watchCh)
}
func (wps *watchProxyStream) checkPermissionForWatch(key, rangeEnd []byte) error {
if len(key) == 0 {
// If the length of the key is 0, we need to obtain full range.
// look at clientv3.WithPrefix()
key = []byte{0}
rangeEnd = []byte{0}
}
req := &pb.RangeRequest{
Serializable: true,
Key: key,
RangeEnd: rangeEnd,
CountOnly: true,
Limit: 1,
}
_, err := wps.kv.Do(wps.ctx, RangeRequestToOp(req))
return err
}
func (wps *watchProxyStream) recvLoop() error {
for {
req, err := wps.stream.Recv()
if err != nil {
return err
}
switch uv := req.RequestUnion.(type) {
case *pb.WatchRequest_CreateRequest:
cr := uv.CreateRequest
if err := wps.checkPermissionForWatch(cr.Key, cr.RangeEnd); err != nil {
wps.watchCh <- &pb.WatchResponse{
Header: &pb.ResponseHeader{},
WatchId: -1,
Created: true,
Canceled: true,
CancelReason: err.Error(),
}
continue
}
wps.mu.Lock()
w := &watcher{
wr: watchRange{string(cr.Key), string(cr.RangeEnd)},
id: wps.nextWatcherID,
wps: wps,
nextrev: cr.StartRevision,
progress: cr.ProgressNotify,
prevKV: cr.PrevKv,
filters: v3rpc.FiltersFromRequest(cr),
}
if !w.wr.valid() {
w.post(&pb.WatchResponse{WatchId: -1, Created: true, Canceled: true})
wps.mu.Unlock()
continue
}
wps.nextWatcherID++
w.nextrev = cr.StartRevision
wps.watchers[w.id] = w
wps.ranges.add(w)
wps.mu.Unlock()
wps.lg.Debug("create watcher", zap.String("key", w.wr.key), zap.String("end", w.wr.end), zap.Int64("watcherId", wps.nextWatcherID))
case *pb.WatchRequest_CancelRequest:
wps.delete(uv.CancelRequest.WatchId)
wps.lg.Debug("cancel watcher", zap.Int64("watcherId", uv.CancelRequest.WatchId))
default:
// Panic or Fatalf would allow to network clients to crash the serve remotely.
wps.lg.Error("not supported request type by gRPC proxy", zap.Stringer("request", req))
}
}
}
func (wps *watchProxyStream) sendLoop() {
for {
select {
case wresp, ok := <-wps.watchCh:
if !ok {
return
}
if err := wps.stream.Send(wresp); err != nil {
return
}
case <-wps.ctx.Done():
return
}
}
}
func (wps *watchProxyStream) delete(id int64) {
wps.mu.Lock()
defer wps.mu.Unlock()
w, ok := wps.watchers[id]
if !ok {
return
}
wps.ranges.delete(w)
delete(wps.watchers, id)
resp := &pb.WatchResponse{
Header: &w.lastHeader,
WatchId: id,
Canceled: true,
}
wps.watchCh <- resp
}

View File

@ -1,166 +0,0 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"context"
"sync"
"time"
pb "go.etcd.io/etcd/api/v3/etcdserverpb"
clientv3 "go.etcd.io/etcd/client/v3"
"go.uber.org/zap"
)
// watchBroadcast broadcasts a server watcher to many client watchers.
type watchBroadcast struct {
// cancel stops the underlying etcd server watcher and closes ch.
cancel context.CancelFunc
donec chan struct{}
// mu protects rev and receivers.
mu sync.RWMutex
// nextrev is the minimum expected next revision of the watcher on ch.
nextrev int64
// receivers contains all the client-side watchers to serve.
receivers map[*watcher]struct{}
// responses counts the number of responses
responses int
lg *zap.Logger
}
func newWatchBroadcast(lg *zap.Logger, wp *watchProxy, w *watcher, update func(*watchBroadcast)) *watchBroadcast {
cctx, cancel := context.WithCancel(wp.ctx)
wb := &watchBroadcast{
cancel: cancel,
nextrev: w.nextrev,
receivers: make(map[*watcher]struct{}),
donec: make(chan struct{}),
lg: lg,
}
wb.add(w)
go func() {
defer close(wb.donec)
opts := []clientv3.OpOption{
clientv3.WithRange(w.wr.end),
clientv3.WithProgressNotify(),
clientv3.WithRev(wb.nextrev),
clientv3.WithPrevKV(),
clientv3.WithCreatedNotify(),
}
cctx = withClientAuthToken(cctx, w.wps.stream.Context())
wch := wp.cw.Watch(cctx, w.wr.key, opts...)
wp.lg.Debug("watch", zap.String("key", w.wr.key))
for wr := range wch {
wb.bcast(wr)
update(wb)
}
}()
return wb
}
func (wb *watchBroadcast) bcast(wr clientv3.WatchResponse) {
wb.mu.Lock()
defer wb.mu.Unlock()
// watchers start on the given revision, if any; ignore header rev on create
if wb.responses > 0 || wb.nextrev == 0 {
wb.nextrev = wr.Header.Revision + 1
}
wb.responses++
for r := range wb.receivers {
r.send(wr)
}
if len(wb.receivers) > 0 {
eventsCoalescing.Add(float64(len(wb.receivers) - 1))
}
}
// add puts a watcher into receiving a broadcast if its revision at least
// meets the broadcast revision. Returns true if added.
func (wb *watchBroadcast) add(w *watcher) bool {
wb.mu.Lock()
defer wb.mu.Unlock()
if wb.nextrev > w.nextrev || (wb.nextrev == 0 && w.nextrev != 0) {
// wb is too far ahead, w will miss events
// or wb is being established with a current watcher
return false
}
if wb.responses == 0 {
// Newly created; create event will be sent by etcd.
wb.receivers[w] = struct{}{}
return true
}
// already sent by etcd; emulate create event
ok := w.post(&pb.WatchResponse{
Header: &pb.ResponseHeader{
// todo: fill in ClusterId
// todo: fill in MemberId:
Revision: w.nextrev,
// todo: fill in RaftTerm:
},
WatchId: w.id,
Created: true,
})
if !ok {
return false
}
wb.receivers[w] = struct{}{}
watchersCoalescing.Inc()
return true
}
func (wb *watchBroadcast) delete(w *watcher) {
wb.mu.Lock()
defer wb.mu.Unlock()
if _, ok := wb.receivers[w]; !ok {
panic("deleting missing watcher from broadcast")
}
delete(wb.receivers, w)
if len(wb.receivers) > 0 {
// do not dec the only left watcher for coalescing.
watchersCoalescing.Dec()
}
}
func (wb *watchBroadcast) size() int {
wb.mu.RLock()
defer wb.mu.RUnlock()
return len(wb.receivers)
}
func (wb *watchBroadcast) empty() bool { return wb.size() == 0 }
func (wb *watchBroadcast) stop() {
if !wb.empty() {
// do not dec the only left watcher for coalescing.
watchersCoalescing.Sub(float64(wb.size() - 1))
}
wb.cancel()
select {
case <-wb.donec:
// watchProxyStream will hold watchRanges global mutex lock all the time if client failed to cancel etcd watchers.
// and it will cause the watch proxy to not work.
// please see pr https://github.com/etcd-io/etcd/pull/12030 to get more detail info.
case <-time.After(time.Second):
wb.lg.Error("failed to cancel etcd watcher")
}
}

View File

@ -1,135 +0,0 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"sync"
)
type watchBroadcasts struct {
wp *watchProxy
// mu protects bcasts and watchers from the coalesce loop.
mu sync.Mutex
bcasts map[*watchBroadcast]struct{}
watchers map[*watcher]*watchBroadcast
updatec chan *watchBroadcast
donec chan struct{}
}
// maxCoalesceRecievers prevents a popular watchBroadcast from being coalseced.
const maxCoalesceReceivers = 5
func newWatchBroadcasts(wp *watchProxy) *watchBroadcasts {
wbs := &watchBroadcasts{
wp: wp,
bcasts: make(map[*watchBroadcast]struct{}),
watchers: make(map[*watcher]*watchBroadcast),
updatec: make(chan *watchBroadcast, 1),
donec: make(chan struct{}),
}
go func() {
defer close(wbs.donec)
for wb := range wbs.updatec {
wbs.coalesce(wb)
}
}()
return wbs
}
func (wbs *watchBroadcasts) coalesce(wb *watchBroadcast) {
if wb.size() >= maxCoalesceReceivers {
return
}
wbs.mu.Lock()
for wbswb := range wbs.bcasts {
if wbswb == wb {
continue
}
wb.mu.Lock()
wbswb.mu.Lock()
// 1. check if wbswb is behind wb so it won't skip any events in wb
// 2. ensure wbswb started; nextrev == 0 may mean wbswb is waiting
// for a current watcher and expects a create event from the server.
if wb.nextrev >= wbswb.nextrev && wbswb.responses > 0 {
for w := range wb.receivers {
wbswb.receivers[w] = struct{}{}
wbs.watchers[w] = wbswb
}
wb.receivers = nil
}
wbswb.mu.Unlock()
wb.mu.Unlock()
if wb.empty() {
delete(wbs.bcasts, wb)
wb.stop()
break
}
}
wbs.mu.Unlock()
}
func (wbs *watchBroadcasts) add(w *watcher) {
wbs.mu.Lock()
defer wbs.mu.Unlock()
// find fitting bcast
for wb := range wbs.bcasts {
if wb.add(w) {
wbs.watchers[w] = wb
return
}
}
// no fit; create a bcast
wb := newWatchBroadcast(wbs.wp.lg, wbs.wp, w, wbs.update)
wbs.watchers[w] = wb
wbs.bcasts[wb] = struct{}{}
}
// delete removes a watcher and returns the number of remaining watchers.
func (wbs *watchBroadcasts) delete(w *watcher) int {
wbs.mu.Lock()
defer wbs.mu.Unlock()
wb, ok := wbs.watchers[w]
if !ok {
panic("deleting missing watcher from broadcasts")
}
delete(wbs.watchers, w)
wb.delete(w)
if wb.empty() {
delete(wbs.bcasts, wb)
wb.stop()
}
return len(wbs.bcasts)
}
func (wbs *watchBroadcasts) stop() {
wbs.mu.Lock()
for wb := range wbs.bcasts {
wb.stop()
}
wbs.bcasts = nil
close(wbs.updatec)
wbs.mu.Unlock()
<-wbs.donec
}
func (wbs *watchBroadcasts) update(wb *watchBroadcast) {
select {
case wbs.updatec <- wb:
default:
}
}

View File

@ -1,69 +0,0 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"sync"
)
// watchRanges tracks all open watches for the proxy.
type watchRanges struct {
wp *watchProxy
mu sync.Mutex
bcasts map[watchRange]*watchBroadcasts
}
func newWatchRanges(wp *watchProxy) *watchRanges {
return &watchRanges{
wp: wp,
bcasts: make(map[watchRange]*watchBroadcasts),
}
}
func (wrs *watchRanges) add(w *watcher) {
wrs.mu.Lock()
defer wrs.mu.Unlock()
if wbs := wrs.bcasts[w.wr]; wbs != nil {
wbs.add(w)
return
}
wbs := newWatchBroadcasts(wrs.wp)
wrs.bcasts[w.wr] = wbs
wbs.add(w)
}
func (wrs *watchRanges) delete(w *watcher) {
wrs.mu.Lock()
defer wrs.mu.Unlock()
wbs, ok := wrs.bcasts[w.wr]
if !ok {
panic("deleting missing range")
}
if wbs.delete(w) == 0 {
wbs.stop()
delete(wrs.bcasts, w.wr)
}
}
func (wrs *watchRanges) stop() {
wrs.mu.Lock()
defer wrs.mu.Unlock()
for _, wb := range wrs.bcasts {
wb.stop()
}
wrs.bcasts = nil
}

View File

@ -1,130 +0,0 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package grpcproxy
import (
"time"
pb "go.etcd.io/etcd/api/v3/etcdserverpb"
"go.etcd.io/etcd/api/v3/mvccpb"
"go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/server/v3/mvcc"
)
type watchRange struct {
key, end string
}
func (wr *watchRange) valid() bool {
return len(wr.end) == 0 || wr.end > wr.key || (wr.end[0] == 0 && len(wr.end) == 1)
}
type watcher struct {
// user configuration
wr watchRange
filters []mvcc.FilterFunc
progress bool
prevKV bool
// id is the id returned to the client on its watch stream.
id int64
// nextrev is the minimum expected next event revision.
nextrev int64
// lastHeader has the last header sent over the stream.
lastHeader pb.ResponseHeader
// wps is the parent.
wps *watchProxyStream
}
// send filters out repeated events by discarding revisions older
// than the last one sent over the watch channel.
func (w *watcher) send(wr clientv3.WatchResponse) {
if wr.IsProgressNotify() && !w.progress {
return
}
if w.nextrev > wr.Header.Revision && len(wr.Events) > 0 {
return
}
if w.nextrev == 0 {
// current watch; expect updates following this revision
w.nextrev = wr.Header.Revision + 1
}
events := make([]*mvccpb.Event, 0, len(wr.Events))
var lastRev int64
for i := range wr.Events {
ev := (*mvccpb.Event)(wr.Events[i])
if ev.Kv.ModRevision < w.nextrev {
continue
} else {
// We cannot update w.rev here.
// txn can have multiple events with the same rev.
// If w.nextrev updates here, it would skip events in the same txn.
lastRev = ev.Kv.ModRevision
}
filtered := false
for _, filter := range w.filters {
if filter(*ev) {
filtered = true
break
}
}
if filtered {
continue
}
if !w.prevKV {
evCopy := *ev
evCopy.PrevKv = nil
ev = &evCopy
}
events = append(events, ev)
}
if lastRev >= w.nextrev {
w.nextrev = lastRev + 1
}
// all events are filtered out?
if !wr.IsProgressNotify() && !wr.Created && len(events) == 0 && wr.CompactRevision == 0 {
return
}
w.lastHeader = wr.Header
w.post(&pb.WatchResponse{
Header: &wr.Header,
Created: wr.Created,
CompactRevision: wr.CompactRevision,
Canceled: wr.Canceled,
WatchId: w.id,
Events: events,
})
}
// post puts a watch response on the watcher's proxy stream channel
func (w *watcher) post(wr *pb.WatchResponse) bool {
select {
case w.wps.watchCh <- wr:
case <-time.After(50 * time.Millisecond):
w.wps.cancel()
w.wps.lg.Error("failed to put a watch response on the watcher's proxy stream channel,err is timeout")
return false
}
return true
}

View File

@ -1,202 +0,0 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -1,228 +0,0 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package integration
import (
"fmt"
"io"
"io/ioutil"
"net"
"sync"
"go.etcd.io/etcd/client/pkg/v3/transport"
)
// bridge creates a unix socket bridge to another unix socket, making it possible
// to disconnect grpc network connections without closing the logical grpc connection.
type bridge struct {
inaddr string
outaddr string
l net.Listener
conns map[*bridgeConn]struct{}
stopc chan struct{}
pausec chan struct{}
blackholec chan struct{}
wg sync.WaitGroup
mu sync.Mutex
}
func newBridge(addr string) (*bridge, error) {
b := &bridge{
// bridge "port" is ("%05d%05d0", port, pid) since go1.8 expects the port to be a number
inaddr: addr + "0",
outaddr: addr,
conns: make(map[*bridgeConn]struct{}),
stopc: make(chan struct{}),
pausec: make(chan struct{}),
blackholec: make(chan struct{}),
}
close(b.pausec)
l, err := transport.NewUnixListener(b.inaddr)
if err != nil {
return nil, fmt.Errorf("listen failed on socket %s (%v)", addr, err)
}
b.l = l
b.wg.Add(1)
go b.serveListen()
return b, nil
}
func (b *bridge) URL() string { return "unix://" + b.inaddr }
func (b *bridge) Close() {
b.l.Close()
b.mu.Lock()
select {
case <-b.stopc:
default:
close(b.stopc)
}
b.mu.Unlock()
b.wg.Wait()
}
func (b *bridge) Reset() {
b.mu.Lock()
defer b.mu.Unlock()
for bc := range b.conns {
bc.Close()
}
b.conns = make(map[*bridgeConn]struct{})
}
func (b *bridge) Pause() {
b.mu.Lock()
b.pausec = make(chan struct{})
b.mu.Unlock()
}
func (b *bridge) Unpause() {
b.mu.Lock()
select {
case <-b.pausec:
default:
close(b.pausec)
}
b.mu.Unlock()
}
func (b *bridge) serveListen() {
defer func() {
b.l.Close()
b.mu.Lock()
for bc := range b.conns {
bc.Close()
}
b.mu.Unlock()
b.wg.Done()
}()
for {
inc, ierr := b.l.Accept()
if ierr != nil {
return
}
b.mu.Lock()
pausec := b.pausec
b.mu.Unlock()
select {
case <-b.stopc:
inc.Close()
return
case <-pausec:
}
outc, oerr := net.Dial("unix", b.outaddr)
if oerr != nil {
inc.Close()
return
}
bc := &bridgeConn{inc, outc, make(chan struct{})}
b.wg.Add(1)
b.mu.Lock()
b.conns[bc] = struct{}{}
go b.serveConn(bc)
b.mu.Unlock()
}
}
func (b *bridge) serveConn(bc *bridgeConn) {
defer func() {
close(bc.donec)
bc.Close()
b.mu.Lock()
delete(b.conns, bc)
b.mu.Unlock()
b.wg.Done()
}()
var wg sync.WaitGroup
wg.Add(2)
go func() {
b.ioCopy(bc.out, bc.in)
bc.close()
wg.Done()
}()
go func() {
b.ioCopy(bc.in, bc.out)
bc.close()
wg.Done()
}()
wg.Wait()
}
type bridgeConn struct {
in net.Conn
out net.Conn
donec chan struct{}
}
func (bc *bridgeConn) Close() {
bc.close()
<-bc.donec
}
func (bc *bridgeConn) close() {
bc.in.Close()
bc.out.Close()
}
func (b *bridge) Blackhole() {
b.mu.Lock()
close(b.blackholec)
b.mu.Unlock()
}
func (b *bridge) Unblackhole() {
b.mu.Lock()
for bc := range b.conns {
bc.Close()
}
b.conns = make(map[*bridgeConn]struct{})
b.blackholec = make(chan struct{})
b.mu.Unlock()
}
// ref. https://github.com/golang/go/blob/master/src/io/io.go copyBuffer
func (b *bridge) ioCopy(dst io.Writer, src io.Reader) (err error) {
buf := make([]byte, 32*1024)
for {
select {
case <-b.blackholec:
io.Copy(ioutil.Discard, src)
return nil
default:
}
nr, er := src.Read(buf)
if nr > 0 {
nw, ew := dst.Write(buf[0:nr])
if ew != nil {
return ew
}
if nr != nw {
return io.ErrShortWrite
}
}
if er != nil {
err = er
break
}
}
return err
}

File diff suppressed because it is too large Load Diff

View File

@ -1,46 +0,0 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !cluster_proxy
// +build !cluster_proxy
package integration
import (
pb "go.etcd.io/etcd/api/v3/etcdserverpb"
"go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/server/v3/etcdserver/api/v3election/v3electionpb"
"go.etcd.io/etcd/server/v3/etcdserver/api/v3lock/v3lockpb"
"go.uber.org/zap"
)
const ThroughProxy = false
func toGRPC(c *clientv3.Client) grpcAPI {
return grpcAPI{
pb.NewClusterClient(c.ActiveConnection()),
pb.NewKVClient(c.ActiveConnection()),
pb.NewLeaseClient(c.ActiveConnection()),
pb.NewWatchClient(c.ActiveConnection()),
pb.NewMaintenanceClient(c.ActiveConnection()),
pb.NewAuthClient(c.ActiveConnection()),
v3lockpb.NewLockClient(c.ActiveConnection()),
v3electionpb.NewElectionClient(c.ActiveConnection()),
}
}
func newClientV3(cfg clientv3.Config, lg *zap.Logger) (*clientv3.Client, error) {
cfg.Logger = lg
return clientv3.New(cfg)
}

View File

@ -1,132 +0,0 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build cluster_proxy
// +build cluster_proxy
package integration
import (
"context"
"sync"
"go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/client/v3/namespace"
"go.etcd.io/etcd/server/v3/proxy/grpcproxy"
"go.etcd.io/etcd/server/v3/proxy/grpcproxy/adapter"
"go.uber.org/zap"
)
const ThroughProxy = true
var (
pmu sync.Mutex
proxies map[*clientv3.Client]grpcClientProxy = make(map[*clientv3.Client]grpcClientProxy)
)
const proxyNamespace = "proxy-namespace"
type grpcClientProxy struct {
ctx context.Context
ctxCancel func()
grpc grpcAPI
wdonec <-chan struct{}
kvdonec <-chan struct{}
lpdonec <-chan struct{}
}
func toGRPC(c *clientv3.Client) grpcAPI {
pmu.Lock()
defer pmu.Unlock()
// dedicated context bound to 'grpc-proxy' lifetype
// (so in practice lifetime of the client connection to the proxy).
// TODO: Refactor to a separate clientv3.Client instance instead of the context alone.
ctx, ctxCancel := context.WithCancel(context.WithValue(context.TODO(), "_name", "grpcProxyContext"))
lg := c.GetLogger()
if v, ok := proxies[c]; ok {
return v.grpc
}
// test namespacing proxy
c.KV = namespace.NewKV(c.KV, proxyNamespace)
c.Watcher = namespace.NewWatcher(c.Watcher, proxyNamespace)
c.Lease = namespace.NewLease(c.Lease, proxyNamespace)
// test coalescing/caching proxy
kvp, kvpch := grpcproxy.NewKvProxy(c)
wp, wpch := grpcproxy.NewWatchProxy(ctx, lg, c)
lp, lpch := grpcproxy.NewLeaseProxy(ctx, c)
mp := grpcproxy.NewMaintenanceProxy(c)
clp, _ := grpcproxy.NewClusterProxy(lg, c, "", "") // without registering proxy URLs
authp := grpcproxy.NewAuthProxy(c)
lockp := grpcproxy.NewLockProxy(c)
electp := grpcproxy.NewElectionProxy(c)
grpc := grpcAPI{
adapter.ClusterServerToClusterClient(clp),
adapter.KvServerToKvClient(kvp),
adapter.LeaseServerToLeaseClient(lp),
adapter.WatchServerToWatchClient(wp),
adapter.MaintenanceServerToMaintenanceClient(mp),
adapter.AuthServerToAuthClient(authp),
adapter.LockServerToLockClient(lockp),
adapter.ElectionServerToElectionClient(electp),
}
proxies[c] = grpcClientProxy{ctx: ctx, ctxCancel: ctxCancel, grpc: grpc, wdonec: wpch, kvdonec: kvpch, lpdonec: lpch}
return grpc
}
type proxyCloser struct {
clientv3.Watcher
proxyCtxCancel func()
wdonec <-chan struct{}
kvdonec <-chan struct{}
lclose func()
lpdonec <-chan struct{}
}
func (pc *proxyCloser) Close() error {
pc.proxyCtxCancel()
<-pc.kvdonec
err := pc.Watcher.Close()
<-pc.wdonec
pc.lclose()
<-pc.lpdonec
return err
}
func newClientV3(cfg clientv3.Config, lg *zap.Logger) (*clientv3.Client, error) {
cfg.Logger = lg
c, err := clientv3.New(cfg)
if err != nil {
return nil, err
}
rpc := toGRPC(c)
c.KV = clientv3.NewKVFromKVClient(rpc.KV, c)
pmu.Lock()
lc := c.Lease
c.Lease = clientv3.NewLeaseFromLeaseClient(rpc.Lease, c, cfg.DialTimeout)
c.Watcher = &proxyCloser{
Watcher: clientv3.NewWatchFromWatchClient(rpc.Watch, c),
wdonec: proxies[c].wdonec,
kvdonec: proxies[c].kvdonec,
lclose: func() { lc.Close() },
lpdonec: proxies[c].lpdonec,
proxyCtxCancel: proxies[c].ctxCancel,
}
pmu.Unlock()
return c, nil
}

View File

@ -1,25 +0,0 @@
// Copyright 2015 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
/*
Package integration implements tests built upon embedded etcd, and focus on
etcd correctness.
Features/goals of the integration tests:
1. test the whole code base except command-line parsing.
2. check internal data, including raft, store and etc.
3. based on goroutines, which is faster than process.
4. mainly tests user behavior and user-facing API.
*/
package integration

View File

@ -1,120 +0,0 @@
// Copyright 2020 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package integration
import (
"log"
"net/http"
"sync"
"time"
"go.etcd.io/etcd/client/pkg/v3/testutil"
"go.etcd.io/etcd/client/pkg/v3/transport"
)
// Infrastructure to provision a single shared cluster for tests - only
// when its needed.
//
// See ./tests/integration/clientv3/examples/main_test.go for canonical usage.
// Please notice that the shared (LazyCluster's) state is preserved between
// testcases, so left-over state might has cross-testcase effects.
// Prefer dedicated clusters for substancial test-cases.
type LazyCluster interface {
// EndpointsV2 - exposes connection points for client v2.
// Calls to this method might initialize the cluster.
EndpointsV2() []string
// EndpointsV3 - exposes connection points for client v3.
// Calls to this method might initialize the cluster.
EndpointsV3() []string
// Cluster - calls to this method might initialize the cluster.
Cluster() *ClusterV3
// Transport - call to this method might initialize the cluster.
Transport() *http.Transport
Terminate()
TB() testutil.TB
}
type lazyCluster struct {
cfg ClusterConfig
cluster *ClusterV3
transport *http.Transport
once sync.Once
tb testutil.TB
closer func()
}
// NewLazyCluster returns a new test cluster handler that gets created on the
// first call to GetEndpoints() or GetTransport()
func NewLazyCluster() LazyCluster {
return NewLazyClusterWithConfig(ClusterConfig{Size: 1})
}
// NewLazyClusterWithConfig returns a new test cluster handler that gets created
// on the first call to GetEndpoints() or GetTransport()
func NewLazyClusterWithConfig(cfg ClusterConfig) LazyCluster {
tb, closer := testutil.NewTestingTBProthesis("lazy_cluster")
return &lazyCluster{cfg: cfg, tb: tb, closer: closer}
}
func (lc *lazyCluster) mustLazyInit() {
lc.once.Do(func() {
var err error
lc.transport, err = transport.NewTransport(transport.TLSInfo{}, time.Second)
if err != nil {
log.Fatal(err)
}
lc.cluster = NewClusterV3(lc.tb, &lc.cfg)
})
}
func (lc *lazyCluster) Terminate() {
lc.tb.Logf("Terminating...")
if lc != nil && lc.cluster != nil {
lc.cluster.Terminate(nil)
lc.cluster = nil
}
if lc.closer != nil {
lc.tb.Logf("Closer...")
lc.closer()
}
}
func (lc *lazyCluster) EndpointsV2() []string {
return []string{lc.Cluster().Members[0].URL()}
}
func (lc *lazyCluster) EndpointsV3() []string {
return lc.Cluster().Client(0).Endpoints()
}
func (lc *lazyCluster) Cluster() *ClusterV3 {
lc.mustLazyInit()
return lc.cluster
}
func (lc *lazyCluster) Transport() *http.Transport {
lc.mustLazyInit()
return lc.transport
}
func (lc *lazyCluster) TB() testutil.TB {
return lc.tb
}

View File

@ -1,136 +0,0 @@
// Copyright 2021 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package integration
import (
"os"
"path/filepath"
"testing"
grpc_logsettable "github.com/grpc-ecosystem/go-grpc-middleware/logging/settable"
"go.etcd.io/etcd/client/pkg/v3/testutil"
clientv3 "go.etcd.io/etcd/client/v3"
"go.etcd.io/etcd/server/v3/embed"
"go.etcd.io/etcd/server/v3/verify"
"go.uber.org/zap/zapcore"
"go.uber.org/zap/zapgrpc"
"go.uber.org/zap/zaptest"
)
var grpc_logger grpc_logsettable.SettableLoggerV2
var insideTestContext bool
func init() {
grpc_logger = grpc_logsettable.ReplaceGrpcLoggerV2()
}
type testOptions struct {
goLeakDetection bool
skipInShort bool
}
func newTestOptions(opts ...TestOption) *testOptions {
o := &testOptions{goLeakDetection: true, skipInShort: true}
for _, opt := range opts {
opt(o)
}
return o
}
type TestOption func(opt *testOptions)
// WithoutGoLeakDetection disables checking whether a testcase leaked a goroutine.
func WithoutGoLeakDetection() TestOption {
return func(opt *testOptions) { opt.goLeakDetection = false }
}
func WithoutSkipInShort() TestOption {
return func(opt *testOptions) { opt.skipInShort = false }
}
// BeforeTestExternal initializes test context and is targeted for external APIs.
// In general the `integration` package is not targeted to be used outside of
// etcd project, but till the dedicated package is developed, this is
// the best entry point so far (without backward compatibility promise).
func BeforeTestExternal(t testutil.TB) {
BeforeTest(t, WithoutSkipInShort(), WithoutGoLeakDetection())
}
func BeforeTest(t testutil.TB, opts ...TestOption) {
t.Helper()
options := newTestOptions(opts...)
if options.skipInShort {
testutil.SkipTestIfShortMode(t, "Cannot create clusters in --short tests")
}
if options.goLeakDetection {
testutil.RegisterLeakDetection(t)
}
previousWD, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
previousInsideTestContext := insideTestContext
// Registering cleanup early, such it will get executed even if the helper fails.
t.Cleanup(func() {
grpc_logger.Reset()
insideTestContext = previousInsideTestContext
os.Chdir(previousWD)
})
if insideTestContext {
t.Fatal("already in test context. BeforeTest was likely already called")
}
grpc_logger.Set(zapgrpc.NewLogger(zaptest.NewLogger(t).Named("grpc")))
insideTestContext = true
// Integration tests should verify written state as much as possible.
os.Setenv(verify.ENV_VERIFY, verify.ENV_VERIFY_ALL_VALUE)
os.Chdir(t.TempDir())
}
func assertInTestContext(t testutil.TB) {
if !insideTestContext {
t.Errorf("the function can be called only in the test context. Was integration.BeforeTest() called ?")
}
}
func MustAbsPath(path string) string {
abs, err := filepath.Abs(path)
if err != nil {
panic(err)
}
return abs
}
func NewEmbedConfig(t testing.TB, name string) *embed.Config {
cfg := embed.NewConfig()
cfg.Name = name
lg := zaptest.NewLogger(t, zaptest.Level(zapcore.InfoLevel)).Named(cfg.Name)
cfg.ZapLoggerBuilder = embed.NewZapLoggerBuilder(lg)
cfg.Dir = t.TempDir()
return cfg
}
func NewClient(t testing.TB, cfg clientv3.Config) (*clientv3.Client, error) {
if cfg.Logger != nil {
cfg.Logger = zaptest.NewLogger(t)
}
return clientv3.New(cfg)
}

17
vendor/modules.txt vendored
View File

@ -467,7 +467,6 @@ github.com/gregjones/httpcache
github.com/gregjones/httpcache/diskcache
# github.com/grpc-ecosystem/go-grpc-middleware v1.3.0 => github.com/grpc-ecosystem/go-grpc-middleware v1.3.0
github.com/grpc-ecosystem/go-grpc-middleware
github.com/grpc-ecosystem/go-grpc-middleware/logging/settable
# github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0 => github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
github.com/grpc-ecosystem/go-grpc-prometheus
# github.com/grpc-ecosystem/grpc-gateway v1.16.0 => github.com/grpc-ecosystem/grpc-gateway v1.16.0
@ -798,7 +797,6 @@ go.etcd.io/etcd/client/pkg/v3/logutil
go.etcd.io/etcd/client/pkg/v3/pathutil
go.etcd.io/etcd/client/pkg/v3/srv
go.etcd.io/etcd/client/pkg/v3/systemd
go.etcd.io/etcd/client/pkg/v3/testutil
go.etcd.io/etcd/client/pkg/v3/tlsutil
go.etcd.io/etcd/client/pkg/v3/transport
go.etcd.io/etcd/client/pkg/v3/types
@ -811,9 +809,6 @@ go.etcd.io/etcd/client/v3/concurrency
go.etcd.io/etcd/client/v3/credentials
go.etcd.io/etcd/client/v3/internal/endpoint
go.etcd.io/etcd/client/v3/internal/resolver
go.etcd.io/etcd/client/v3/namespace
go.etcd.io/etcd/client/v3/naming/endpoints
go.etcd.io/etcd/client/v3/naming/endpoints/internal
# go.etcd.io/etcd/pkg/v3 v3.5.0 => go.etcd.io/etcd/pkg/v3 v3.5.0
go.etcd.io/etcd/pkg/v3/adt
go.etcd.io/etcd/pkg/v3/contention
@ -873,14 +868,10 @@ go.etcd.io/etcd/server/v3/lease/leasepb
go.etcd.io/etcd/server/v3/mvcc
go.etcd.io/etcd/server/v3/mvcc/backend
go.etcd.io/etcd/server/v3/mvcc/buckets
go.etcd.io/etcd/server/v3/proxy/grpcproxy
go.etcd.io/etcd/server/v3/proxy/grpcproxy/adapter
go.etcd.io/etcd/server/v3/proxy/grpcproxy/cache
go.etcd.io/etcd/server/v3/verify
go.etcd.io/etcd/server/v3/wal
go.etcd.io/etcd/server/v3/wal/walpb
# go.etcd.io/etcd/tests/v3 v3.5.0 => go.etcd.io/etcd/tests/v3 v3.5.0
go.etcd.io/etcd/tests/v3/integration
# go.opencensus.io v0.22.3 => go.opencensus.io v0.22.3
go.opencensus.io
go.opencensus.io/internal
@ -1539,6 +1530,7 @@ k8s.io/apiserver/pkg/storage/errors
k8s.io/apiserver/pkg/storage/etcd3
k8s.io/apiserver/pkg/storage/etcd3/metrics
k8s.io/apiserver/pkg/storage/etcd3/testing
k8s.io/apiserver/pkg/storage/etcd3/testserver
k8s.io/apiserver/pkg/storage/names
k8s.io/apiserver/pkg/storage/storagebackend
k8s.io/apiserver/pkg/storage/storagebackend/factory
@ -2461,7 +2453,6 @@ sigs.k8s.io/yaml
# github.com/emicklei/go-restful => github.com/emicklei/go-restful v2.9.5+incompatible
# github.com/envoyproxy/go-control-plane => github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d
# github.com/envoyproxy/protoc-gen-validate => github.com/envoyproxy/protoc-gen-validate v0.1.0
# github.com/etcd-io/gofail => github.com/etcd-io/gofail v0.0.0-20190801230047-ad7f989257ca
# github.com/euank/go-kmsg-parser => github.com/euank/go-kmsg-parser v2.0.0+incompatible
# github.com/evanphx/json-patch => github.com/evanphx/json-patch v4.11.0+incompatible
# github.com/exponent-io/jsonpath => github.com/exponent-io/jsonpath v0.0.0-20151013193312-d6023ce2651d
@ -2563,7 +2554,7 @@ sigs.k8s.io/yaml
# github.com/mailru/easyjson => github.com/mailru/easyjson v0.7.6
# github.com/mattn/go-colorable => github.com/mattn/go-colorable v0.0.9
# github.com/mattn/go-isatty => github.com/mattn/go-isatty v0.0.3
# github.com/mattn/go-runewidth => github.com/mattn/go-runewidth v0.0.9
# github.com/mattn/go-runewidth => github.com/mattn/go-runewidth v0.0.7
# github.com/matttproud/golang_protobuf_extensions => github.com/matttproud/golang_protobuf_extensions v1.0.2-0.20181231171920-c182affec369
# github.com/miekg/dns => github.com/miekg/dns v1.0.14
# github.com/mindprince/gonvml => github.com/mindprince/gonvml v0.0.0-20190828220739-9ebdce4bb989
@ -2591,7 +2582,7 @@ sigs.k8s.io/yaml
# github.com/mxk/go-flowrate => github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f
# github.com/niemeyer/pretty => github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e
# github.com/nxadm/tail => github.com/nxadm/tail v1.4.4
# github.com/olekukonko/tablewriter => github.com/olekukonko/tablewriter v0.0.5
# github.com/olekukonko/tablewriter => github.com/olekukonko/tablewriter v0.0.4
# github.com/onsi/ginkgo => github.com/onsi/ginkgo v1.14.0
# github.com/onsi/gomega => github.com/onsi/gomega v1.10.1
# github.com/opencontainers/go-digest => github.com/opencontainers/go-digest v1.0.0
@ -2655,11 +2646,9 @@ sigs.k8s.io/yaml
# go.etcd.io/etcd/client/pkg/v3 => go.etcd.io/etcd/client/pkg/v3 v3.5.0
# go.etcd.io/etcd/client/v2 => go.etcd.io/etcd/client/v2 v2.305.0
# go.etcd.io/etcd/client/v3 => go.etcd.io/etcd/client/v3 v3.5.0
# go.etcd.io/etcd/etcdutl/v3 => go.etcd.io/etcd/etcdutl/v3 v3.5.0
# go.etcd.io/etcd/pkg/v3 => go.etcd.io/etcd/pkg/v3 v3.5.0
# go.etcd.io/etcd/raft/v3 => go.etcd.io/etcd/raft/v3 v3.5.0
# go.etcd.io/etcd/server/v3 => go.etcd.io/etcd/server/v3 v3.5.0
# go.etcd.io/etcd/tests/v3 => go.etcd.io/etcd/tests/v3 v3.5.0
# go.opencensus.io => go.opencensus.io v0.22.3
# go.opentelemetry.io/contrib => go.opentelemetry.io/contrib v0.20.0
# go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc => go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.20.0