Compare commits

..

413 Commits

Author SHA1 Message Date
Benjamin Wang
6d1bfe4f99 bump version to 3.4.24
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2023-02-16 09:39:00 +08:00
Benjamin Wang
9c81b86e90 test: enhance the test case TestV3WatchProgressOnMemberRestart
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2023-02-10 21:03:53 +08:00
Benjamin Wang
ed529ab0e5 clientv3: correct the nextRev on receving progress notification response
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2023-02-10 16:47:56 +08:00
James Blair
d32dceb8a6 Fix regression in timestamp resolution
Historic capnslog timestamps are in microsecond resolution. We need to match that when we migrate to the zap logger.

Signed-off-by: James Blair <mail@jamesblair.net>
2023-02-10 04:35:24 +08:00
Marek Siarkowicz
fb7a8973bd Merge pull request #15265 from ahrtr/3.4_walSync_failpoint_20230209
[3.4] etctserver: add failpoints walBeforeSync and walAfterSync
2023-02-09 09:10:19 +01:00
Benjamin Wang
109873dcb6 etctserver: add failpoints walBeforeSync and walAfterSync
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2023-02-09 07:06:46 +08:00
Benjamin Wang
b4e3ed72e3 bump bbolt to v1.3.7 for release-3.4
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2023-02-02 03:47:21 +08:00
Wilson Wang
2f8158650f server: set multiple concurrentReadTx instances share one txReadBuffer.
(cherry picked from commit 9c82e8c72b)
Signed-off-by: Wei Fu <fuweid89@gmail.com>
2023-01-30 11:43:19 +08:00
kidsan
c5347cb0c6 netutil: consistently format ipv6 addresses
This formats ipv6 addresses to ensure they can be compared safely

Signed-off-by: kidsan <8798449+Kidsan@users.noreply.github.com>
2023-01-27 06:49:26 +08:00
Iavael
d2fc8dbeeb docker: remove nsswitch.conf
Signed-off-by: Iavael <905853+iavael@users.noreply.github.com>
2023-01-25 02:45:52 +08:00
Benjamin Wang
e4b154231c Merge pull request #15137 from fuweid/backport-11990-to-3.4
[3.4] mvcc: push down RangeOptions.limit argv into index tree to reduce memory overhead
2023-01-20 06:23:32 +08:00
Wei Fu
931cf9a814 mvcc: update ut for Revisions/CountRevisions
It is kind of backport from etcd-io#14124.

Signed-off-by: Wei Fu <fuweid89@gmail.com>
2023-01-18 10:18:57 +08:00
Marek Siarkowicz
1246c52d04 etcdserver: Fix invalid count returned on Range with Limit
(cherry picked from commit 182aef6e6b)
Signed-off-by: Wei Fu <fuweid89@gmail.com>
2023-01-18 10:02:10 +08:00
tangcong
d48f7ad7c1 mvcc: push down RangeOptions.limit argv into index tree
(cherry picked from commit 26c930f27d)
Signed-off-by: Wei Fu <fuweid89@gmail.com>
2023-01-18 10:01:20 +08:00
Benjamin Wang
a1d1af5774 Merge pull request #15099 from fuweid/backport-11771-11743-pr-to-3.4
[3.4] mvcc: reduce count-only range overhead
2023-01-18 08:48:29 +08:00
Piotr Tabor
4be8c0e5a5 Merge pull request #15097 from ahrtr/3.4_promote_non_exist_id_20230113
[3.4] etcdserver: return membership.ErrIDNotFound when the memberID not found
2023-01-17 09:15:02 +01:00
Benjamin Wang
00b31512a1 etcdserver: return membership.ErrIDNotFound when the memberID not found
Backport https://github.com/etcd-io/etcd/pull/15095 to 3.4.

When promoting a learner, we need to wait until the leader's applied ID
catches up to the commitId. Afterwards, check whether the learner ID
exist or not, and return `membership.ErrIDNotFound` directly in the API
if the member ID not found, to avoid the request being unnecessarily
delivered to raft.

Signed-off-by: Benjamin Wang <wachao@vmware.com>
2023-01-17 06:27:31 +08:00
Wei Fu
10c080dc5e mvcc: Add ut for Revisions/CountRevisions
It is kind of backport from #14124.

Signed-off-by: Wei Fu <fuweid89@gmail.com>
2023-01-16 15:15:34 +08:00
tangcong
2070f55aab e2e: add getCountOnlyTest testcase
(cherry picked from commit 3594ab94cf)
Signed-off-by: Wei Fu <fuweid89@gmail.com>
2023-01-13 16:33:56 +08:00
tangcong
00a005c300 mvcc: reduce count-only range overhead
(cherry picked from commit 730f3f1d78)
Signed-off-by: Wei Fu <fuweid89@gmail.com>
2023-01-13 16:32:35 +08:00
mlmhl
841f3bd2be etcdctl: support query count only of specified prefix
(cherry picked from commit aa7b056a77)
Signed-off-by: Wei Fu <fuweid89@gmail.com>
2023-01-13 16:31:23 +08:00
Benjamin Wang
a577940b4e Merge pull request #15088 from fuweid/3.4-fix-flaky-testcase
[3.4] grpc-gateway: update version to v1.11.0
2023-01-13 10:39:28 +08:00
Wei Fu
c320f75a15 grpc-gateway: update version to v1.11.0
The issue is caused by hand-crafted protobuf message. The runtime.errorBody
defines two protobuf fields with same number. We need to upgrade the
version to fix it. Otherwise, the client side won't receive any errors
from server side because of panic.

```
mismatching field: runtime.errorBody.error, want runtime.errorBody.message
```

It can fix the cases

PASSES="build grpcproxy" CPU=4 RACE=true ./test -run TestV3CurlLeaseRevokeNoTLS

The original error is like:

```
v3_curl_lease_test.go:109: testV3CurlLeaseRevoke: prefix (/v3) endpoint (/kv/lease/revoke): error (read /dev/ptmx: input/output error (expected "etcdserver: requested lease not found", got ["curl: (52) Empty reply from server\r\n"])), wanted etcdserver: requested lease not found
    v3_curl_lease_test.go:109: testV3CurlLeaseRevoke: prefix (/v3beta) endpoint (/kv/lease/revoke): error (read /dev/ptmx: input/output error (expected "etcdserver: requested lease not found", got ["curl: (52) Empty reply from server\r\n"])), wanted etcdserver: requested lease not found
```

The `Empty reply from server` is caused by panic and server recover it
but it doesn't have chance to reply to client.

Signed-off-by: Wei Fu <fuweid89@gmail.com>
2023-01-12 17:06:00 +08:00
Benjamin Wang
46511ab96e Merge pull request #15042 from ahrtr/update_nsswitch_3.4
[3.4] Update nsswitch.conf for 3.4
2022-12-24 07:13:34 +08:00
Benjamin Wang
58c2f5f228 update nsswitch.conf for 3.4
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-12-23 20:31:45 +08:00
Benjamin Wang
283e447df5 Merge pull request #15038 from ahrtr/remove_busybox_3.4_20221223
3.4: remove the dependency on busybox
2022-12-23 19:27:41 +08:00
Benjamin Wang
8aace73c77 3.4: remove the dependency on busybox
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-12-23 18:43:44 +08:00
Benjamin Wang
c8b7831967 bump version to 3.4.23
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-12-21 14:11:16 +08:00
Benjamin Wang
8119eb3951 Merge pull request #15019 from ahrtr/deps_3.4_20221219
[3.4] Security: address HIGH Vulnerabilities
2022-12-19 19:33:56 +08:00
Benjamin Wang
5413ce46dc bump go version to 1.17.3
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-12-19 18:34:04 +08:00
Benjamin Wang
86479c5ba9 deps: bump golang.org/x/net to v0.4.0
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-12-19 17:43:43 +08:00
Benjamin Wang
68a55439e1 deps: bump golang.org/x/net to 0.0.0-20220906165146-f3363e06e74c to address CVE CVE-2021-44716 and CVE-2022-27664
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-12-19 16:34:06 +08:00
Benjamin Wang
40566d943a deps: bump github.com/prometheus/client_golang to 1.11.1 to address CVE CVE-2022-21698
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-12-19 16:32:23 +08:00
Benjamin Wang
fcb048dd67 deps: bump github.com/gogo/protobuf to 1.3.2 to address CVE CVE-2021-3121
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-12-19 16:30:53 +08:00
Benjamin Wang
f318a39998 Merge pull request #15017 from ahrtr/use_distroless_3.4_20221219
[3.4] Security: use distroless base image to address critical Vulnerabilities
2022-12-19 16:23:30 +08:00
Benjamin Wang
c1bec6bd97 security: use distroless base image to address critical Vulnerabilities
Command:
trivy image --severity CRITICAL gcr.io/etcd-development/etcd:v3.4.22  -f json -o 3.4.22_image_critical.json

Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-12-19 08:04:47 +08:00
Benjamin Wang
9d37e7626a Merge pull request #15011 from MukulKolpe/specify_branch_release-3.4
fix: specify the branch name of release-3.4 in the workflow
2022-12-17 18:09:47 +08:00
Mukul Kolpe
fb07cf843a fix: specify the branch name of release-3.4 in the workflow
Signed-off-by: Mukul Kolpe <mukulkolpe45@gmail.com>
2022-12-17 14:40:24 +05:30
Benjamin Wang
e03c62d5e7 Merge pull request #15007 from ArkaSaha30/trivy-release-3-4
Add trivy nightly scan for `release-3.4`
2022-12-16 13:59:40 +08:00
ArkaSaha30
7450bcfc49 Add trivy nightly scan for release-3.4
Signed-off-by: ArkaSaha30 <arkasaha30@gmail.com>
2022-12-16 11:06:58 +05:30
Benjamin Wang
593711848e Merge pull request #14900 from ahrtr/fix_readyonly_txn_panic_3.4_20221206
[3.4] etcdserver: fix nil pointer panic for readonly txn
2022-12-06 19:25:12 +08:00
Benjamin Wang
acca4fa93e etcdserver: fix nil pointer panic for readonly txn
Backporting https://github.com/etcd-io/etcd/pull/14895

Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-12-06 18:09:47 +08:00
Benjamin Wang
c619e2705e Merge pull request #14853 from ahrtr/remove_memberid_alarm_3.4_20221125
[3.4] etcdserver: intentionally set the memberID as 0 in corruption alarm
2022-11-25 17:01:02 +08:00
Benjamin Wang
2f4f7328d0 etcdserver: intentionally set the memberID as 0 in corruption alarm
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-11-25 15:58:23 +08:00
Benjamin Wang
f4bf538781 Merge pull request #14792 from ahrtr/auth_3.4_20221117
[3.4] clientv3: do not refresh token when users use CommonName based authentication
2022-11-17 18:08:11 +08:00
Benjamin Wang
90585e03a0 test: add test case to cover the CommonName based authentication
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-11-17 09:12:13 +08:00
Benjamin Wang
8b4405b276 test: add certificate with root CommonName
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-11-17 08:34:59 +08:00
Benjamin Wang
8ca42a7ae4 clientv3: do not refresh token when using TLS CommonName based authentication
When users use the TLS CommonName based authentication, the
authTokenBundle is always nil. But it's possible for the clients
to get `rpctypes.ErrAuthOldRevision` response when the clients
concurrently modify auth data (e.g, addUser, deleteUser etc.).
In this case, there is no need to refresh the token; instead the
clients just need to retry the operations (e.g. Put, Delete etc).

Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-11-17 08:32:35 +08:00
Benjamin Wang
1f054980bc Bump version to 3.4.22
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-11-02 08:08:33 +08:00
Benjamin Wang
c9cf4db813 Merge pull request #14675 from cenkalti/release-3.4
server: add more context to panic message
2022-11-02 07:56:50 +08:00
Cenk Alti
7a4a3ad8db server: add more context to panic message
Signed-off-by: Cenk Alti <cenkalti@gmail.com>
2022-11-01 18:59:17 -04:00
Benjamin Wang
7c1499d3bb Merge pull request #14649 from mitake/test-authrecover-3.4
[3.4] server: add a unit test case for authStore.Reocver() with empty rangePermCache
2022-10-29 13:11:36 +08:00
Hitoshi Mitake
b7a23311e6 etcdserver: call refreshRangePermCache on Recover() in AuthStore
Signed-off-by: Oleg Guba <oleg@dropbox.com>
Signed-off-by: Hitoshi Mitake <h.mitake@gmail.com>
2022-10-29 13:55:06 +09:00
Hitoshi Mitake
0b3ff06868 server: add a unit test case for authStore.Reocver() with empty rangePermCache
Signed-off-by: Hitoshi Mitake <h.mitake@gmail.com>
2022-10-29 13:27:53 +09:00
Benjamin Wang
ce1630f68f Merge pull request #14601 from dusk125/release-3.4
Backport #14500 to 3.4
2022-10-27 14:21:22 +08:00
Allen Ray
9254f8f05b Release-3.4: server/etcdmain: add configurable cipher list to gRPC proxy listener
Signed-off-by: Allen Ray <alray@redhat.com>
2022-10-19 16:02:13 -04:00
Benjamin Wang
b058374fbd Merge pull request #14594 from ZoeShaw101/fix-watch-test-issue-3.4
Backport #14591 to 3.4.
2022-10-17 05:25:50 +08:00
王霄霄
dcebdf7958 Backport #14591 to 3.4.
Signed-off-by: 王霄霄 1141195807@qq.com
Signed-off-by: 王霄霄 <1141195807@qq.com>
2022-10-16 21:18:53 +08:00
Benjamin Wang
5b764d8771 Merge pull request #14581 from tomari/tomari/watch-backoff-for-3.4
[3.4] client/v3: Add backoff before retry when watch stream returns unavailable
2022-10-13 07:23:02 +08:00
Hisanobu Tomari
7b7fbbf8b8 client/v3: Add backoff before retry when watch stream returns unavailable
The client retries connection without backoff when the server is gone
after the watch stream is established. This results in high CPU usage
in the client process. This change introduces backoff when the stream is
failed and unavailable.

Signed-off-by: Hisanobu Tomari <posco.grubb@gmail.com>
2022-10-13 05:26:31 +09:00
Sahdev Zala
429fcb98ab Merge pull request #14579 from ahrtr/wal_log_3.4
[3.4] etcdserver: added more debug log for the purgeFile goroutine
2022-10-12 11:34:33 -04:00
Benjamin Wang
1d7639f796 etcdserver: added more debug log for the purgeFile goroutine
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-10-12 19:39:20 +08:00
Benjamin Wang
5b3ac7da6b Merge pull request #14577 from pchan/acp3.4
Cherry pick of #13224
2022-10-12 17:58:26 +08:00
Sergey Kacheev
5381dafaae netutil: make a raw URL comparison part of the urlsEqual function
Signed-off-by: Prasad Chandrasekaran <prasadc@vmware.com>
2022-10-12 15:07:46 +05:30
Sergey Kacheev
90e7e254ae Apply suggestions from code review
Co-authored-by: Lili Cosic <cosiclili@gmail.com>
Signed-off-by: Prasad Chandrasekaran <prasadc@vmware.com>
2022-10-12 15:07:46 +05:30
Sergey Kacheev
abb019a51e netutil: add url comparison without resolver to URLStringsEqual
If one of the nodes in the cluster has lost a dns record,
restarting the second node will break it.
This PR makes an attempt to add a comparison without using a resolver,
which allows to protect cluster from dns errors and does not break
the current logic of comparing urls in the URLStringsEqual function.
You can read more in the issue #7798

Fixes #7798

Signed-off-by: Prasad Chandrasekaran <prasadc@vmware.com>
2022-10-12 15:07:46 +05:30
Hitoshi Mitake
57a27de189 Merge pull request #14562 from kafuu-chino/3.4-backport-14296
*: avoid closing a watch with ID 0 incorrectly
2022-10-10 22:48:53 +09:00
Kafuu Chino
ed10ca13f4 *: avoid closing a watch with ID 0 incorrectly
Signed-off-by: Kafuu Chino <KafuuChinoQ@gmail.com>

add test

1

1

1
2022-10-10 19:54:58 +08:00
Benjamin Wang
de11726a8a Merge pull request #14548 from mitake/3.4-backport-14322
Backport PR 14322 to release-3.4
2022-10-05 05:50:43 +08:00
Hitoshi Mitake
91365174b3 tests: a test case for watch with auth token expiration
Signed-off-by: Hitoshi Mitake <h.mitake@gmail.com>
2022-10-04 22:55:36 +09:00
Hitoshi Mitake
0c6e466024 *: handle auth invalid token and old revision errors in watch
Signed-off-by: Hitoshi Mitake <h.mitake@gmail.com>
2022-10-04 22:49:06 +09:00
Marek Siarkowicz
d0a732f96d Merge pull request #14530 from ahrtr/memberid_alarm
etcdserver: fix memberID equals to zero in corruption alarm
2022-09-28 09:30:10 +02:00
Benjamin Wang
29911e9a5b etcdserver: fix memberID equals to zero in corruption alarm
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-09-28 11:01:26 +08:00
Benjamin Wang
85b640cee7 Bump version to 3.4.21
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-09-15 08:46:22 +08:00
Marek Siarkowicz
1a05326fae Merge pull request #14442 from ahrtr/fix_TestV3AuthRestartMember
[release-3.4] Fix the flaky test TestV3AuthRestartMember
2022-09-09 09:57:24 +02:00
Benjamin Wang
b8bea91f22 fix the flaky test TestV3AuthRestartMember
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-09-09 09:37:25 +08:00
Benjamin Wang
6730ed8477 Merge pull request #14410 from vivekpatani/release-3.4
[release-3.4] server,test: refresh cache on each NewAuthStore
2022-09-09 09:34:32 +08:00
Benjamin Wang
a55a9f5e07 Merge pull request #14441 from tjungblu/bz_1918413_3.4_upstream
[release-3.4] etcdctl: fix move-leader for multiple endpoints
2022-09-09 09:26:40 +08:00
Thomas Jungblut
86bc0a25c4 etcdctl: fix move-leader for multiple endpoints
Due to a duplicate call of clientConfigFromCmd, the move-leader command
would fail with "conflicting environment variable is shadowed by corresponding command-line flag".
Also in scenarios where no command-line flag was supplied.

Signed-off-by: Thomas Jungblut <tjungblu@redhat.com>
2022-09-08 15:51:19 +02:00
Benjamin Wang
dd743eea81 Merge pull request #14439 from vsvastey/usr/vsvastey/open-with-max-index-test-fix-3.4
[release-3.4] testing: fix TestOpenWithMaxIndex cleanup
2022-09-08 17:00:20 +08:00
Vladimir Sokolov
1ed5dfc20e testing: fix TestOpenWithMaxIndex cleanup
A WAL object was closed by defer, however the WAL was rewritten afterwards,
so defer closed already closed WAL but not the new one. It caused a data
race between writing file and cleaning up a temporary test directory,
which led to a non-deterministic bug.

Fixes #14332

Signed-off-by: Vladimir Sokolov <vsvastey@gmail.com>
2022-09-08 10:49:47 +03:00
Benjamin Wang
b2b7b9d535 Merge pull request #14423 from serathius/one_member_data_loss_raft_3_4
[release-3.4] fix the potential data loss for clusters with only one member
2022-09-06 03:29:45 +08:00
Benjamin Wang
119e4dda19 fix the potential data loss for clusters with only one member
For a cluster with only one member, the raft always send identical
unstable entries and committed entries to etcdserver, and etcd
responds to the client once it finishes (actually partially) the
applying workflow.

When the client receives the response, it doesn't mean etcd has already
successfully saved the data, including BoltDB and WAL, because:
   1. etcd commits the boltDB transaction periodically instead of on each request;
   2. etcd saves WAL entries in parallel with applying the committed entries.
Accordingly, it may run into a situation of data loss when the etcd crashes
immediately after responding to the client and before the boltDB and WAL
successfully save the data to disk.
Note that this issue can only happen for clusters with only one member.

For clusters with multiple members, it isn't an issue, because etcd will
not commit & apply the data before it being replicated to majority members.
When the client receives the response, it means the data must have been applied.
It further means the data must have been committed.
Note: for clusters with multiple members, the raft will never send identical
unstable entries and committed entries to etcdserver.

Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-09-05 14:15:47 +02:00
Benjamin Wang
9d5ae56764 Merge pull request #14420 from vsvastey/usr/vsvastey/nil-logger
etcdserver: nil-logger issue fix for version 3.4
2022-09-05 14:53:08 +08:00
Vladimir Sokolov
38342e88da etcdserver: nil-logger issue fix for version 3.4
In v3.5 it is assumed that the logger should not be nil, however it is
still a case in v3.4. The PR targeted to v3.5 was backported to 3.4 and
that's why it's possible to get panic on nil logger in 3.4. This commit
fixed this issue.

Fixes #14402

Signed-off-by: Vladimir Sokolov <vsvastey@gmail.com>
2022-09-03 04:34:03 +03:00
vivekpatani
c0ef7d52e0 server,test: refresh cache on each NewAuthStore
- permissions were incorrectly loaded on restarts.
- #14355
- Backport of https://github.com/etcd-io/etcd/pull/14358

Signed-off-by: vivekpatani <9080894+vivekpatani@users.noreply.github.com>
2022-08-31 13:08:11 -07:00
Benjamin Wang
1e2682301c Bump version to 3.4.20
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-08-06 05:27:01 +08:00
Sahdev Zala
ee366151c6 Merge pull request #14290 from ahrtr/3.4_no_prevkv_for_create
[3.4] Do not get previous K/V for create event
2022-08-01 08:39:19 -04:00
Benjamin Wang
095bbfc4ed lock down the version of shadow to v0.1.11
The latest vesion v0.1.12 was just released On Jul 27, 2022,
and it is causing issue (see below) on the govet check,

```
govet_shadow' started at Sun Jul 31 23:23:27 PDT 2022
go get: upgraded golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 => v0.0.0-20220722155237-a158d28d115b
go get: upgraded golang.org/x/sys v0.0.0-20211019181941-9d821ace8654 => v0.0.0-20220722155257-8c9f86f7a55f
go get: upgraded golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135 => v0.1.12
/root/go/pkg/mod/github.com/grpc-ecosystem/go-grpc-prometheus@v1.2.0/client_metrics.go:7:2: missing go.sum entry for module providing package golang.org/x/net/context (imported by go.etcd.io/etcd/etcdserver/etcdserverpb); to add:
	go get go.etcd.io/etcd/etcdserver/etcdserverpb
/root/go/pkg/mod/google.golang.org/grpc@v1.26.0/internal/transport/controlbuf.go:28:2: missing go.sum entry for module providing package golang.org/x/net/http2 (imported by go.etcd.io/etcd/embed); to add:
	go get go.etcd.io/etcd/embed
/root/go/pkg/mod/google.golang.org/grpc@v1.26.0/internal/transport/controlbuf.go:29:2: missing go.sum entry for module providing package golang.org/x/net/http2/hpack (imported by github.com/soheilhy/cmux); to add:
	go get github.com/soheilhy/cmux@v0.1.4
/root/go/pkg/mod/google.golang.org/grpc@v1.26.0/server.go:36:2: missing go.sum entry for module providing package golang.org/x/net/trace (imported by go.etcd.io/etcd/embed); to add:
	go get go.etcd.io/etcd/embed
```

It isn't good to always to use the latest version. Instead, we should
lock down the version, and v0.1.11 was confirmed to be working.

Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-08-01 15:11:49 +08:00
Benjamin Wang
cc1b0e6a44 do not get previous K/V for create event
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-08-01 13:11:46 +08:00
Benjamin Wang
314dcbf6f5 Merge pull request #14274 from lavacat/release-3.4-fix-TestRoundRobinBalancedResolvableFailoverFromServerFail
[3.4] clientv3/balancer: fixed flaky TestRoundRobinBalancedResolvableFailoverFromServerFail
2022-07-27 04:59:38 +08:00
Bogdan Kanivets
6f483a649e clientv3/balancer: fixed flaky TestRoundRobinBalancedResolvableFailoverFromServerFail
- ignore "transport is closing" error during connections warmup after stopping one peer.

Signed-off-by: Bogdan Kanivets <bkanivets@apple.com>
2022-07-26 08:06:59 -07:00
Benjamin Wang
ce539a960c Merge pull request #14279 from SimFG/mvcc-race
[3.4] clientv3/mvcc: fixed DATA RACE
2022-07-26 23:01:34 +08:00
SimFG
04e5e5516e [3.4] clientv3/mvcc: fixed DATA RACE between mvcc.(*store).setupMetricsReporter and mvcc.(*store).restore
Signed-off-by: SimFG <1142838399@qq.com>
2022-07-26 21:38:23 +08:00
Benjamin Wang
2c778eebf7 Merge pull request #14269 from ahrtr/3.4_resend_readindex
[3.4] etcdserver: resend ReadIndex request on empty apply request
2022-07-25 16:53:06 +08:00
Benjamin Wang
f53db9b246 etcdserver: resend ReadIndex request on empty apply request
Backport https://github.com/etcd-io/etcd/pull/12795 to 3.4

Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-07-25 09:21:31 +08:00
Benjamin Wang
e2b36f8879 Merge pull request #14253 from serathius/checkpoints-fix-3.4
[3.4] Checkpoints fix 3.4
2022-07-22 16:56:17 +08:00
Benjamin Wang
de2e8ccc78 Merge pull request #14258 from ahrtr/3.4_postphone_read_index
[3.4] raft: postpone MsgReadIndex until first commit in the term
2022-07-22 16:46:32 +08:00
Marek Siarkowicz
783e99cbfe Fix lease checkpointing tests by forcing a snapshot
Signed-off-by: Marek Siarkowicz <siarkowicz@google.com>
2022-07-22 10:28:44 +02:00
Marek Siarkowicz
8f4735dfd4 server: Require either cluster version v3.6 or --experimental-enable-lease-checkpoint-persist to persist lease remainingTTL
To avoid inconsistant behavior during cluster upgrade we are feature
gating persistance behind cluster version. This should ensure that
all cluster members are upgraded to v3.6 before changing behavior.

To allow backporting this fix to v3.5 we are also introducing flag
--experimental-enable-lease-checkpoint-persist that will allow for
smooth upgrade in v3.5 clusters with this feature enabled.

Signed-off-by: Marek Siarkowicz <siarkowicz@google.com>
2022-07-22 10:28:29 +02:00
Benjamin Wang
9c9148c4cd raft: postpone MsgReadIndex until first commit in the term
Backport https://github.com/etcd-io/etcd/pull/12762 to 3.4

Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-07-22 13:56:27 +08:00
Benjamin Wang
f18d074866 Merge pull request #14254 from ramses/backport-13435
[3.4] Backport: non mutating requests pass through quotaKVServer when NOSPACE
2022-07-22 09:27:00 +08:00
Benjamin Wang
aca5cd1717 Merge pull request #14246 from vivekpatani/release-3.4
[3.4] etcdserver,pkg: remove temp files in snap dir when etcdserver starting
2022-07-22 09:14:23 +08:00
vivekpatani
e4deb09c9e etcdserver,pkg: remove temp files in snap dir when etcdserver starting
- Backporting: https://github.com/etcd-io/etcd/pull/12846
- Reference: https://github.com/etcd-io/etcd/issues/14232

Signed-off-by: vivekpatani <9080894+vivekpatani@users.noreply.github.com>
2022-07-21 15:50:27 -07:00
Chao Chen
96f69dee47 Backport: non mutating requests pass through quotaKVServer when NOSPACE
This is a backport of https://github.com/etcd-io/etcd/pull/13435 and is
part of the work for 3.4.20
https://github.com/etcd-io/etcd/issues/14232.

The original change had a second commit that modifies a changelog file.
The 3.4 branch does not include any changelog file, so that part was not
cherry-picked.

Local Testing:

- `make build`
- `make test`

Both succeed.

Signed-off-by: Ramsés Morales <ramses@gmail.com>
2022-07-21 15:06:09 -07:00
Michał Jasionowski
8d83691d53 etcdserver,integration: Store remaining TTL on checkpoint
To extend lease checkpointing mechanism to cases when the whole etcd
cluster is restarted.

Signed-off-by: Marek Siarkowicz <siarkowicz@google.com>
2022-07-21 17:35:21 +02:00
Michał Jasionowski
a30aba8fc2 lease,integration: add checkpoint scheduling after leader change
Current checkpointing mechanism is buggy. New checkpoints for any lease
are scheduled only until the first leader change. Added fix for that
and a test that will check it.

Signed-off-by: Marek Siarkowicz <siarkowicz@google.com>
2022-07-21 17:35:15 +02:00
Benjamin Wang
7ee7029c08 Merge pull request #14251 from ahrtr/3.4_maxstream
[3.4] Support configuring MaxConcurrentStreams for http2
2022-07-21 17:43:15 +08:00
Benjamin Wang
6071b1c523 Support configuring MaxConcurrentStreams for http2
Backport https://github.com/etcd-io/etcd/pull/14219 to 3.4

Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-07-21 14:25:29 +08:00
Benjamin Wang
40ccb8b454 Merge pull request #14240 from chaochn47/cherry-pick-12335
[3.4] etcdserver: add more detailed traces on linearized reading
2022-07-21 03:32:02 +08:00
Chao Chen
864006b72d print out applied index as uint64
Signed-off-by: Chao Chen <chaochn@amazon.com>
2022-07-20 12:07:51 -07:00
Pierre Zemb
3f9fba9112 etcdserver: add more detailed traces on linearized reading
To improve debuggability of `agreement among raft nodes before
linearized reading`, we added some tracing inside
`linearizableReadLoop`.

This will allow us to know the timing of `s.r.ReadIndex` vs
`s.applyWait.Wait(rs.Index)`.

Signed-off-by: Chao Chen <chaochn@amazon.com>
2022-07-20 12:07:51 -07:00
Benjamin Wang
fc76e90cf2 Merge pull request #14230 from mitake/perm-cache-lock-3.4
server/auth: protect rangePermCache with a RW lock
2022-07-20 18:51:54 +08:00
Benjamin Wang
3ea12d352e Merge pull request #14241 from vivekpatani/release-3.4
clientv3: fix isOptsWithFromKey/isOptsWithPrefix
2022-07-20 18:51:24 +08:00
Benjamin Wang
6313502fb4 Merge pull request #14239 from chaochn47/backport-13676
backport 3.5: #13676 load all leases from backend
2022-07-20 18:50:46 +08:00
Benjamin Wang
b0e1aaef69 Merge pull request #14236 from chrisayoub/release-3.4
[release-3.4] clientv3: filter learners members during autosync
2022-07-20 12:49:31 +08:00
Chris Ayoub
36a76e8531 clientv3: filter learners members during autosync
This change is to ensure that all members returned during the client's
AutoSync are started and are not learners, which are not valid
etcd members to make requests to.

Signed-off-by: Chris Ayoub <cayoub@hubspot.com>
2022-07-20 00:04:03 -04:00
vivekpatani
4fef7fcb90 clientv3: fix isOptsWithFromKey/isOptsWithPrefix
- Addressing: https://github.com/etcd-io/etcd/issues/13332
- Backporting: https://github.com/etcd-io/etcd/pull/13334

Signed-off-by: vivekpatani <9080894+vivekpatani@users.noreply.github.com>
2022-07-19 17:20:56 -07:00
Chao Chen
fd51434b54 backport 3.5: #13676 load all leases from backend
Signed-off-by: Chao Chen <chaochn@amazon.com>
2022-07-19 16:08:01 -07:00
Benjamin Wang
d58a0c0434 Merge pull request #14177 from ahrtr/3.4_lease_renew_linearizable
[3.4] Support linearizable renew lease for 3.4
2022-07-19 16:39:00 +08:00
Hitoshi Mitake
ecd91da40d server/auth: protect rangePermCache with a RW lock
Signed-off-by: Hitoshi Mitake <h.mitake@gmail.com>
2022-07-19 15:51:48 +09:00
Benjamin Wang
07d2b1d626 support linearizable renew lease for 3.4
Cherry pick https://github.com/etcd-io/etcd/pull/13932 to 3.4.

When etcdserver receives a LeaseRenew request, it may be still in
progress of processing the LeaseGrantRequest on exact the same
leaseID. Accordingly it may return a TTL=0 to client due to the
leaseID not found error. So the leader should wait for the appliedID
to be available before processing client requests.

Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-07-19 13:34:55 +08:00
Benjamin Wang
4636a5fab4 Bump version to 3.4.19
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-07-12 16:18:45 +08:00
Benjamin Wang
06561ae4bf Merge pull request #14210 from ahrtr/fix_release_script
[3.4] Fix pipeline failure for release test
2022-07-12 16:06:33 +08:00
Benjamin Wang
be0ce4f15b fix pipeline failure for release test
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-07-12 08:31:59 +08:00
Benjamin Wang
d3dfc9b796 Merge pull request #14204 from lavacat/release-3.4-balancer-tests
clientv3/balance: fixed flaky balancer tests
2022-07-12 06:14:35 +08:00
Bogdan Kanivets
185f203528 clientv3/balance: fixed flaky balancer tests
- added verification step to indirectly verify that all peers are in balancer subconn list

Signed-off-by: Bogdan Kanivets <bkanivets@apple.com>
2022-07-11 14:43:58 -07:00
Benjamin Wang
7de53273dd Merge pull request #14205 from ahrtr/3.4_release_script
[3.4] Update release scripts for release-3.4
2022-07-11 20:06:06 +08:00
Benjamin Wang
6cc9416ae5 backport release test to 3.4
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-07-11 19:47:08 +08:00
Benjamin Wang
e6b3d97712 Update release scripts for release-3.4
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-07-11 16:06:32 +08:00
Marek Siarkowicz
852ac37bc0 Merge pull request #14200 from ahrtr/3.4_pipeline_race
set RACE as true for linux-amd64-unit and linux-amd64-grpcproxy
2022-07-08 10:23:21 +02:00
Benjamin Wang
8c1c5fefdb set RACE as true for linux-amd64-unit and linux-amd64-grpcproxy
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-07-08 08:37:31 +08:00
Marek Siarkowicz
0c6063fa82 Merge pull request #14192 from ahrtr/3.4_bump_yaml
[3.4] Bump gopkg.in/yaml.v2 v2.2.2 -> v2.4.0 due to: CVE-2019-11254
2022-07-05 14:32:09 +02:00
Benjamin Wang
860dc149b2 Bump gopkg.in/yaml.v2 v2.2.8 -> v2.4.0 due to: CVE-2019-11254
Cherry pick https://github.com/etcd-io/etcd/pull/13616 to 3.4.

Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-07-05 06:26:06 +08:00
Marek Siarkowicz
f0256eeec9 Merge pull request #14179 from lavacat/release-3.4-crypto
[backport 3.4] Update golang.org/x/crypto to latest
2022-07-04 11:57:58 +02:00
Bogdan Kanivets
576a798bf9 [backport 3.4] Update golang.org/x/crypto to latest
Update crypto to address CVE-2022-27191.

The CVE fix is added in 0.0.0-20220315160706-3147a52a75dd but this
change updates to latest.

Backport of https://github.com/etcd-io/etcd/pull/13996

Signed-off-by: Bogdan Kanivets <bkanivets@apple.com>
2022-06-30 23:08:13 -07:00
Benjamin Wang
bae61786fc Merge pull request #14183 from ahrtr/3.4_pipeline_issues_20220630
[3.4] Fix pipeline failures in 3.4
2022-07-01 05:36:29 +08:00
Benjamin Wang
8160e9ebe2 disable test cases on certificate-based authentication which isn't supported by gRPC proxy.
Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-06-30 14:11:54 +08:00
Benjamin Wang
5b3f269159 replace all 3.4 certificates and keys with the files from 3.5
Fix the following error in integration pipeline,
```
=== RUN   TestTLSReloadCopy
    v3_grpc_test.go:1754: tls: failed to find any PEM data in key input
    v3_grpc_test.go:1754: tls: private key does not match public key
    v3_grpc_test.go:1754: tls: private key does not match public key
    v3_grpc_test.go:1754: tls: private key does not match public key
```

Refer to https://github.com/etcd-io/etcd/runs/7123775361?check_suite_focus=true

Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-06-30 13:21:48 +08:00
Benjamin Wang
bb9113097a fix test failure in TestCtlV3WatchClientTLS
Also refer to the following commit in 3.5,
093282f5ea

Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-06-30 10:19:03 +08:00
Benjamin Wang
f169e5dcba Merge pull request #14151 from ahrtr/3.4_skip_TestWatchRequestProgress_proxy
[3.4] Skip WatchRequestProgress test in grpc-proxy mode.
2022-06-29 05:40:05 +08:00
Benjamin Wang
6958ee8ff2 Skip WatchRequestProgress test in grpc-proxy mode.
We shouldn't fail the grpc-server (completely) by a not implemented RPC.
Failing whole server by remote request is anti-pattern and security
risk.

Refer to https://github.com/etcd-io/etcd/runs/7034342964?check_suite_focus=true#step:5:2284

```
=== RUN   TestWatchRequestProgress/1-watcher
panic: not implemented
goroutine 83024 [running]:
go.etcd.io/etcd/proxy/grpcproxy.(*watchProxyStream).recvLoop(0xc009232f00, 0x4a73e1, 0xc00e2406e0)
	/home/runner/work/etcd/etcd/proxy/grpcproxy/watch.go:265 +0xbf2
go.etcd.io/etcd/proxy/grpcproxy.(*watchProxy).Watch.func1(0xc0038a3bc0, 0xc009232f00)
	/home/runner/work/etcd/etcd/proxy/grpcproxy/watch.go:125 +0x70
created by go.etcd.io/etcd/proxy/grpcproxy.(*watchProxy).Watch
	/home/runner/work/etcd/etcd/proxy/grpcproxy/watch.go:123 +0x73b
FAIL	go.etcd.io/etcd/clientv3/integration	222.813s
FAIL
```

Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-06-29 05:12:43 +08:00
Marek Siarkowicz
f1c59dcfac Merge pull request #14170 from ahrtr/3.4_proxy_fix_20220628
Fix deadlock in 'go test -tags cluster_proxy -v ./integration/... ./client'
2022-06-28 17:56:44 +02:00
Benjamin Wang
1c9fa07cd7 Fix deadlock in 'go test -tags cluster_proxy -v ./integration/... ./clientv3/...'
Cherry pick https://github.com/etcd-io/etcd/pull/12319 to 3.4.

Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-06-28 13:44:47 +08:00
Benjamin Wang
4e88cce06c Merge pull request #14168 from lavacat/release-3.4-TestGetToken
[backport 3.4] clientv3/integration: Reduce flakines of TestGetTokenWithoutAuth
2022-06-28 04:35:17 +08:00
Bogdan Kanivets
2d99b341ad [backport 3.4] clientv3/integration: Reduce flakines of TestGetTokenWithoutAuth
backport from branch-3.5:
https://github.com/etcd-io/etcd/pull/12200/

Signed-off-by: Bogdan Kanivets <bkanivets@apple.com>
2022-06-27 11:31:16 -07:00
Marek Siarkowicz
17fc680454 Merge pull request #14150 from ahrtr/lease_revoke_race_3.4
[3.4] Backport two lease related bug fixes to 3.4
2022-06-24 11:27:09 +02:00
Benjamin Wang
f036529b5d Backport two lease related bug fixes to 3.4
The first bug fix is to resolve the race condition between goroutine
and channel on the same leases to be revoked. It's a classic mistake
in using Golang channel + goroutine. Please refer to
https://go.dev/doc/effective_go#channels

The second bug fix is to resolve the issue that etcd lessor may
continue to schedule checkpoint after stepping down the leader role.

Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-06-24 09:09:40 +08:00
Benjamin Wang
953376e666 Merge pull request #14136 from ahrtr/3.4_pipeline_issues
[3.4] Fix all the pipeline failues for release 3.4
2022-06-23 04:54:42 +08:00
Benjamin Wang
1abf085cfb fix all the pipeline failues for release 3.4
Items resolved:
1. fix the vet error: possible misuse of reflect.SliceHeader;
2. fix the vet error: call to (*T).Fatal from a non-test goroutine;
3. bump package golang.org/x/crypto, net and sys;
4. bump boltdb from 1.3.3 to 1.3.6;
5. remove the vendor directory;
6. remove go 1.12.17 and 1.15.15, add go 1.16.15 into pipeline;
7. bump go version to 1.16 in go.mod;
8. fix the issue: compile: version go1.16.15 does not match go tool version go1.17.11,
   refer to https://github.com/actions/setup-go/issues/107;
9. fix data race on compactMainRev and watcherGauge;
10. fix test failure for TestLeasingTxnOwnerGet in cluster_proxy mode.

Signed-off-by: Benjamin Wang <wachao@vmware.com>
2022-06-22 05:28:45 +08:00
Benjamin Wang
c2c9e7de01 Merge pull request #14075 from lavacat/release-3.4-go1.15.15-tests
tests: fixing dependencies that brake tests in go.1.15.15
2022-05-31 05:52:21 +08:00
Bogdan Kanivets
ceed023f7c tests: fixing dependencies that brake tests in go.1.15.15
- retry_interceptor_test causes:
clientv3/naming/grpc.go:25:2: module google.golang.org/grpc@latest found (v1.46.0),
but does not contain package google.golang.org/grpc/naming
https://github.com/etcd-io/etcd/issues/12124
2022-05-30 12:08:47 -07:00
Benjamin Wang
5505d7a95b Merge pull request #13206 from cfz/cherry-pick-#13172-r34
[backport 3.4]: server/auth: enable tokenProvider if recoved store enables auth
2022-05-07 06:59:33 +08:00
Piotr Tabor
76147c9c79 Merge pull request #13999 from mitake/backport-13308-to-3.4
Backport PR 13308 to release 3.4
2022-05-06 13:03:05 +02:00
cfz
23e79dbf19 [backport 3.4]: server/auth: enable tokenProvider if recoved store enables auth
this is a manual backport of #13172
2022-05-06 12:26:55 +08:00
Hitoshi Mitake
757a8e8f5b *: implement a retry logic for auth old revision in the client 2022-04-29 23:46:24 +09:00
Ashish Ranjan
9bbdeb4a64 client/v3: refresh the token when ErrUserEmpty is received while retrying
To fix a bug in the retry logic caused when the auth token is cleared after receiving `ErrInvalidAuthToken` from the server and the subsequent call to `getToken` also fails due to some reason (eg. context deadline exceeded).
This leaves the client without a token and the retry will continue to fail with `ErrUserEmpty` unless the token is refreshed.
2022-04-29 23:43:36 +09:00
Marek Siarkowicz
c50b7260cc Merge pull request #13713 from lavacat/defrag-bopts-fix-3.4
mvcc/backend: restore original bolt db options after defrag
2022-02-18 10:54:21 +01:00
Bogdan Kanivets
d30a4fbf0c mvcc/backend: restore original bolt db options after defrag
Problem: Defrag was implemented before custom bolt options were added.
Currently defrag doesn't restore backend options.
For example BackendFreelistType will be unset after defrag.

Solution: save bolt db options and use them in defrag.
2022-02-17 15:33:05 -08:00
richkun
a905430d27 embed: only log stream error with debug level (#13656)
Co-authored-by: tangcong <tangcong506@gmail.com>
2022-01-30 12:24:22 -08:00
Sam Batschelet
161bf7e7be Merge pull request #13475 from chaochn47/backport-release-3.4
backport 3.4 from #13467 exclude the same alarm type activated by multiple peers
2021-11-13 22:10:38 -05:00
Chao Chen
04d47a93f9 backport from #13467 exclude the same alarm type activated by multiple peers 2021-11-12 14:17:14 -08:00
Sam Batschelet
72d3e382e7 version: 3.4.18
Signed-off-by: Sam Batschelet <sbatsche@redhat.com>
2021-10-15 09:47:08 -04:00
Piotr Tabor
eb9cee9ee3 Merge pull request #13397 from geetasg/release-3.4
storage/backend: Add a gauge to indicate if defrag is active (backport)
2021-10-07 19:08:31 +02:00
Geeta Gharpure
85abf6e46d storage/backend: Add a gauge to indicate if defrag is active (backport from 3.6) 2021-10-06 11:04:47 -07:00
Piotr Tabor
1eac258f58 Merge pull request #13385 from hexfusion/cp-13376-release-3.4
[release-3.4] Dockerfile: bump debian bullseye-20210927
2021-10-04 08:40:32 +02:00
Sam Batschelet
91da298560 Dockerfile: bump debian bullseye-20210927
fixes: CVE-2021-3711, CVE-2021-35942, CVE-2019-9893

Signed-off-by: Sam Batschelet <sbatsche@redhat.com>
2021-10-04 00:32:23 -04:00
Sam Batschelet
19e2e70e4f version: 3.4.17
Signed-off-by: Sam Batschelet <sbatsche@redhat.com>
2021-10-03 22:30:27 -04:00
Sam Batschelet
8ea187e2cf Merge pull request #13378 from ysksuzuki/replace-jwt-go
Replace github.com/dgrijalva/jwt-go with github.com/golang-jwt/jwt
2021-10-03 21:48:32 -04:00
Yusuke Suzuki
e63d058247 test: update go to 1.15.15
Update go to 1.15.15 which is the latest of 1.15 because linux-amd64-fmt fails with go 1.15.13.

Signed-off-by: Yusuke Suzuki <yusuke-suzuki@cybozu.co.jp>
2021-10-02 10:04:22 +09:00
Yusuke Suzuki
1558ede7f8 go.mod,go.sum: Replace github.com/dgrijalva/jwt-go with github.com/golang-jwt/jwt
github.com/dgrijalva/jwt-go has CVE https://github.com/advisories/GHSA-w73w-5m7g-f7qc
and is already archived. etcd v3.4 should use a community maintained fork
github.com/golang-jwt/jwt which provides the fixed version of the CVE.

Signed-off-by: Yusuke Suzuki <yusuke-suzuki@cybozu.co.jp>
2021-10-02 10:01:52 +09:00
Sam Batschelet
41061e56ad Merge pull request #13139 from hexfusion/bp-12727
[release-3.4]: ClientV3: Ordering: Fix TestEndpointSwitchResolvesViolation test
2021-06-24 10:38:10 -04:00
Sam Batschelet
501d8f01ea [release-3.4]: ClientV3: Ordering: Fix TestEndpointSwitchResolvesViolation test
Signed-off-by: Sam Batschelet <sbatsche@redhat.com>
2021-06-23 21:26:55 -04:00
Sam Batschelet
38669a0709 Merge pull request #13137 from hexfusion/track-modules
vendor: track vendor/modules.txt
2021-06-23 14:52:10 -04:00
Sam Batschelet
7489911d51 Merge pull request #13135 from serathius/actions-3.4
Migrate PR testing from travis to GitHub actions
2021-06-23 14:03:37 -04:00
Sam Batschelet
15b7954d03 vendor: track vendor/modules.txt
Signed-off-by: Sam Batschelet <sbatsche@redhat.com>
2021-06-23 13:56:39 -04:00
Marek Siarkowicz
6cc1345a0b Migrate PR testing from travis to GitHub actions 2021-06-23 18:25:29 +02:00
Sam Batschelet
589a6993b8 Merge pull request #13101 from mrueg/backport-12864
[backport 3.4]  fix check datascale command for https endpoints
2021-06-14 08:19:22 -04:00
Saeid Bostandoust
4bacd21e20 fix check datascale command for https endpoints 2021-06-11 11:58:20 +02:00
Sam Batschelet
0ecc337028 Merge pull request #13100 from tangcong/automated-cherry-pick-of-#13077-origin-release-3.4
[backport 3.4] embed: unlimit the recv msg size of grpc-gateway
2021-06-10 21:29:34 -04:00
spacewander
628fa1818e embed: unlimit the recv msg size of grpc-gateway
Ensure the client which access etcd via grpc-gateway won't
be limited by the MaxCallRecvMsgSize. Here we choose the same
default value of etcdcli as grpc-gateway's MaxCallRecvMsgSize.

Fix https://github.com/etcd-io/etcd/issues/12576
2021-06-11 08:07:28 +08:00
Gyuho Lee
d19fbe541b version: 3.4.16
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2021-05-12 01:52:43 +00:00
Piotr Tabor
6bbc85827b Merge pull request #12917 from chaochn47/2021-05-03-backport-#12880
Backport-3.4 exclude alarms from health check conditionally
2021-05-06 10:21:09 +02:00
Chao Chen
dbde4f2d5e Backport-3.4 exclude alarms from health check conditionally 2021-05-04 10:37:12 -07:00
Gyuho Lee
15715dcf1a Merge pull request #12902 from MakDon/release-3.4
[Backport-3.4] etcdserver/mvcc: update trace.Step condition
2021-04-28 11:05:35 -07:00
makdon
963d3b9369 etcdserver/mvcc: update trace.Step condition
backport PR #12894 to release-3.4
2021-04-28 11:35:49 +08:00
Piotr Tabor
ba829044f5 Merge pull request #12888 from chaochn47/2021-04-22-cherry-pick-12871
Backport-3.4 etcdserver/util.go: reduce memory when logging range requests
2021-04-23 00:45:20 +02:00
Chao Chen
c4eb81af99 Backport-3.4 etcdserver/util.go: reduce memory when logging range requests 2021-04-22 15:07:44 -07:00
Piotr Tabor
ceafa1b33e Merge pull request #12882 from lilic/bump-go-12
.travis,Makefile,functional: Bump go 1.12 version to v1.12.17
2021-04-20 23:33:23 +02:00
Lili Cosic
5890bc8bd6 .travis,Makefile,functional: Bump go 1.12 version to v1.12.17
This version was already used to build the release v3.4.15.
2021-04-20 14:00:44 +02:00
Piotr Tabor
c274aa5ea4 Merge pull request #12849 from lilic/test-go-1-15
[release-3.4]: .travis.yml: Test with go v1.15.11
2021-04-19 18:17:05 +02:00
Piotr Tabor
276ee962ec integration: Fix 'go test --tags cluster_proxy --timeout=30m -v ./integration/...'
grpc proxy opens additional 2 watching channels. The metric is shared
between etcd-server & grpc_proxy, so all assertions on number of open
watch channels need to take in consideration the additional "2"
channels.
2021-04-19 16:41:28 +02:00
Lili Cosic
8d1b8335e3 pkg/tlsutil: Adjust cipher suites for go 1.12
Cherry-pick of 60e44286fa from master branch does not work due to
missing `tls.CipherSuites()` function. We work around by using go build
tags for both the building and tests.
2021-04-19 11:49:13 +02:00
Piotr Tabor
c3f447a698 Fix pkg/tlsutil (test) to not fail on 386.
In fact this commit rewrites the functionality to use upstream list of
ciphers instead of checking whether the lists are in sync using ast
analysis.
2021-04-19 11:49:13 +02:00
Lili Cosic
85e037d9c6 bill-of-materials.json: Update golang.org/x/sys 2021-04-19 11:49:13 +02:00
Lili Cosic
a1691be1bd .travis,test: Turn race off in Travis for go version 1.15
Currently with race it fails, we can enable this at a later point.
2021-04-19 11:49:13 +02:00
Vimal K
df35086b6a integration : fix TestTLSClientCipherSuitesMismatch in go1.13
In go1.13, the TLS13 is enablled by default, and as per go1.13 release notes :
TLS 1.3 cipher suites are not configurable. All supported cipher suites are safe,
and if PreferServerCipherSuites is set in Config the preference order is based
on the available hardware.

Fixing the test case for go1.13 by limiting the TLS version to TLS12
2021-04-19 11:18:14 +02:00
Lili Cosic
eeefd614c8 vendor: Run go mod vendor 2021-04-19 11:18:14 +02:00
Lili Cosic
4276c33026 go.mod,go.sum: Bump github.com/creack/pty that includes patch
This patch is needed due to go 1.15 erroring on:

"Setctty set but Ctty not valid in child".
2021-04-19 11:18:13 +02:00
Lili Cosic
cfc08e5f06 go.mod,go.sum: Comply with go v1.15 2021-04-19 11:18:13 +02:00
Lili Cosic
0b7e4184e8 etcdserver,wal: Convert int to string using rune() 2021-04-19 11:18:13 +02:00
Lili Cosic
35bd924596 integration,raft,tests: Comply with go v1.15 gofmt 2021-04-19 11:18:13 +02:00
Lili Cosic
62596faeed .travis.yml: Test with go v1.15.11
Currently in CI the tests are only run with go v1.12, this adds also go
v1.15.11.

Excludes certain variants for v1.15.
2021-04-19 11:18:13 +02:00
Piotr Tabor
b7e5f5bc12 Merge pull request #12839 from lilic/fix-go-version
[release-3.4]: Pin go version in go.mod to 1.12
2021-04-07 17:52:05 +02:00
Lili Cosic
91bed2e01f pkpkg/testutil/leak.go: Allowlist created by testing.runTests.func1 2021-04-07 17:20:52 +02:00
Lili Cosic
b19eb0f339 vendor: Run go mod vendor 2021-04-07 15:25:32 +02:00
Lili Cosic
8557cb29ba go.sum, go.mod: Run go mod tidy with go 1.12 2021-04-07 15:25:08 +02:00
Lili Cosic
ef415e3fe1 go.mod: Pin go to 1.12 version
As go 1.12.2 is what is tested in CI as well as recommended to be built
with 1.12.2 we should also pin to this in the go directive version.
2021-04-07 15:21:42 +02:00
Sam Batschelet
82eae9227c Merge pull request #12803 from cwedgwood/metrics-3.4
etcdserver: fix incorrect metrics generated when clients cancel watches
2021-04-01 08:17:37 -04:00
Chris Wedgwood
656dc63eab etcdserver: fix incorrect metrics generated when clients cancel watches
Manual cherry-pick of 9571325fe8 for
release-3.4.
2021-03-31 22:59:29 -07:00
Piotr Tabor
30799c97be Merge pull request #12815 from dbavatar/release-3.4-peervalidation
etcdserver: Fix PeerURL validation
2021-03-30 12:54:32 +02:00
Piotr Tabor
16fe9a89ff Merge pull request #12816 from cwedgwood/3.4-relax-gate-timeout
integration: relax leader timeout from 3s to 4s
2021-03-30 12:53:27 +02:00
Chris Wedgwood
c499d9b047 integration: relax leader timeout from 3s to 4s
The integration jobs fail with timeouts slightly over 3s, increase
this marginally so false failures are less prevalent.
2021-03-29 10:17:44 -07:00
Piotr Tabor
2702f9e5f2 Merge pull request #12751 from cwedgwood/nofsyncdowrite
When using --unsafe-no-fsync still write out the data
2021-03-07 11:52:33 +01:00
Chris Wedgwood
94634fc258 etcdserver: when using --unsafe-no-fsync write data
There are situations where we don't wish to fsync but we do want to
write the data.

Typically this occurs in clusters where fsync latency (often the
result of firmware) transiently spikes.  For Kubernetes clusters this
causes (many) elections which have knock-on effects such that the API
server will transiently fail causing other components fail in turn.

By writing the data (buffered and asynchronously flushed, so in most
situations the write is fast) and avoiding the fsync we no longer
trigger this situation and opportunistically write out the data.

Anecdotally:
  Because the fsync is missing there is the argument that certain
  types of failure events will cause data corruption or loss, in
  testing this wasn't seen.  If this was to occur the expectation is
  the member can be readded to a cluster or worst-case restored from a
  robust persisted snapshot.

  The etcd members are deployed across isolated racks with different
  power feeds.  An instantaneous failure of all of them simultaneously
  is unlikely.

  Testing was usually of the form:
   * create (Kubernetes) etcd write-churn by creating replicasets of
     some 1000s of pods
   * break/fail the leader

  Failure testing included:
   * hard node power-off events
   * disk removal
   * orderly reboots/shutdown

  In all cases when the node recovered it was able to rejoin the
  cluster and synchronize.
2021-03-05 10:09:52 -08:00
Sam Batschelet
afd6d8a40d Merge pull request #12740 from hexfusion/cp-12448--release-3.4
Manual cherry pick of #12448 on release 3.4
2021-03-03 13:37:20 -05:00
Sam Batschelet
9aeabe447d server: Added config parameter experimental-warning-apply-duration
Signed-off-by: Sam Batschelet <sbatsche@redhat.com>
2021-03-03 12:14:30 -05:00
Gyuho Lee
aa7126864d version: 3.4.15
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2021-02-26 22:08:24 +00:00
Gyuho Lee
3be9460ddc Merge pull request #12679 from chaochn47/backport_3.4_#12677
[Backport-3.4] etcdserver/api/etcdhttp: log successful etcd server side health check in debug level
2021-02-09 15:01:19 -08:00
Chao Chen
f27ef4d343 [Backport-3.4] etcdserver/api/etcdhttp: log successful etcd server side health check in debug level
ref. #12677
ref. 0b9cfa8677
2021-02-08 21:44:44 -08:00
Piotr Tabor
a1c5f59b59 Merge pull request #12402 from vitalif/release-3.4
etcdserver: Fix 64 KB websocket notification message limit
2021-02-03 09:19:21 +01:00
a40f14d92c etcdserver: Fix 64 KB websocket notification message limit
This fixes etcd being unable to send any message longer than 64 KB as
a notification over the websocket. This was because the older version
of grpc-websocket-proxy was used and WithMaxRespBodyBufferSize option
wasn't set.
2021-01-30 00:37:02 +03:00
Sam Batschelet
d51c6c689b Merge pull request #12645 from hexfusion/bump-dep
vendor: bump gorilla/websocket
2021-01-23 13:49:45 -05:00
Sam Batschelet
becc228c5a vendor: bump gorilla/websocket
Signed-off-by: Sam Batschelet <sbatsche@redhat.com>
2021-01-23 11:20:53 -05:00
Piotr Tabor
0880605772 Merge pull request #12551 from kolyshkin/3.4-fix-lock
[3.4 backport] pkg/fileutil: fix F_OFD_ constants
2021-01-15 23:16:49 +01:00
Kir Kolyshkin
bea35fd2c6 pkg/fileutil: fix F_OFD_ constants
Use golang.org/x/sys/unix for F_OFD_* constants.

This fixes the issue that F_OFD_GETLK was defined incorrectly,
resulting in bugs such as https://github.com/moby/moby/issues/31182

Signed-off-by: Kir Kolyshkin <kolyshkin@gmail.com>
2020-12-14 10:42:13 -08:00
Gyuho Lee
8a03d2e961 version: 3.4.14
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-11-25 11:31:52 -08:00
Gyuho Lee
a4b43b388d pkg/netutil: remove unused "iptables" wrapper
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-11-25 11:31:17 -08:00
Gyuho Lee
e3b29b66a4 tools/etcd-dump-metrics: validate exec cmd args
To prevent arbitrary command invocations.

Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-11-25 11:30:31 -08:00
Gyuho Lee
eb0fb0e799 Merge pull request #12356 from cfc4n/automated-cherry-pick-of-#12264-upstream-release-3.4
Automated cherry pick of #12264
2020-10-12 14:26:50 -07:00
CFC4N
40b71074e8 clientv3: get AuthToken automatically when clientConn is ready.
fixes: #11954
2020-09-30 17:14:22 +08:00
Jingyi Hu
7e2d426ec0 Merge pull request #12299 from galal-hussein/fix_panic_34
[Backport 3.4] etcdserver: add ConfChangeAddLearnerNode to the list of config changes
2020-09-15 09:04:18 -07:00
galal-hussein
3019246742 etcdserver: add ConfChangeAddLearnerNode to the list of config changes
To fix a panic that happens when trying to get ids of etcd members in
force new cluster mode, the issue happen if the cluster previously had
etcd learner nodes added to the cluster

Fixes #12285
2020-09-14 17:50:57 +02:00
Joe Betz
dd1b699fc4 Merge pull request #12280 from jingyih/automated-cherry-pick-of-#12271-upstream-release-3.4
Automated cherry pick of #12271 on release 3.4
2020-09-10 11:07:54 -07:00
jingyih
f44aaf8248 integration: add flag WatchProgressNotifyInterval in integration test 2020-09-09 12:39:42 -07:00
Gyuho Lee
ae9734ed27 version: 3.4.13
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-08-24 12:11:28 -07:00
Gyuho Lee
781bde75e2 Merge pull request #12250 from spzala/automated-cherry-pick-of-#12242-upstream-release-3.4
Automated cherry pick of #12242
2020-08-24 12:05:03 -07:00
Sahdev P. Zala
d5ebbbceb8 pkg: file stat warning
Provide warning and doc instead of enforcing file permission.
2020-08-24 11:21:29 -04:00
Sam Batschelet
7cd5872656 Merge pull request #12244 from hexfusion/automated-cherry-pick-of-#12243-upstream-release-3.4
Automated cherry pick of #12243 on release 3.4
2020-08-21 11:24:21 -04:00
Sam Batschelet
46a0a44f95 Automated cherry pick of #12243 on release 3.4
Signed-off-by: Sam Batschelet <sbatsche@redhat.com>
2020-08-21 10:14:07 -04:00
Gyuho Lee
17cef6e3e9 version: 3.4.12
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-08-19 09:56:24 -07:00
Gyuho Lee
c07cba001b Merge pull request #12239 from liggitt/slow-v2-panic-3.4
[3.4] etcdserver: Avoid panics logging slow v2 requests in integration tests
2020-08-19 09:55:08 -07:00
Jordan Liggitt
b8878eac45 etcdserver: Avoid panics logging slow v2 requests in integration tests 2020-08-19 11:30:39 -04:00
Gyuho Lee
e71e0c5c88 Merge pull request #12226 from jingyih/fix_backport_PR12216
*: add plog logging to the backport of PR12216
2020-08-18 08:48:09 -07:00
Gyuho Lee
bc44e367c3 version: 3.4.11
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-08-18 08:46:13 -07:00
Gyuho Lee
299e0f17aa Revert "etcdserver/api/v3rpc: "MemberList" never return non-empty ClientURLs"
This reverts commit 0372cfc7ab.
2020-08-18 08:45:38 -07:00
jingyih
75d5e78d1f *: fix backport of PR12216
Fix bugs introduced in commit c60dabf
2020-08-16 15:01:18 +08:00
jingyih
c60dabf2f3 *: add experimental flag for watch notify interval
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-08-15 10:24:25 -07:00
Gyuho Lee
8a4afdbcc2 Merge pull request #12189 from jingyih/automated-cherry-pick-of-#11452-#12187-upstream-release-3.4
Automated cherry pick of #11452 #12187 on release 3.4
2020-08-13 21:38:05 -07:00
jingyih
6fcab5af9f clientv3: remove excessive watch cancel logging 2020-08-13 13:51:21 +08:00
Gyuho Lee
008074187c etcdserver: add OS level FD metrics
Similar counts are exposed via Prometheus.
This adds the one that are perceived by etcd server.

e.g.

os_fd_limit 120000
os_fd_used 14
process_cpu_seconds_total 0.31
process_max_fds 120000
process_open_fds 17

Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-08-12 18:38:35 -07:00
Gyuho Lee
cf558ee8b7 pkg/runtime: optimize FDUsage by removing sort
No need sort when we just want the counts.

Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-08-12 18:38:17 -07:00
Ted Yu
e800c62eca clientv3: log warning in case of error sending request 2020-07-30 22:54:35 +08:00
Gyuho Lee
0372cfc7ab etcdserver/api/v3rpc: "MemberList" never return non-empty ClientURLs
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>

cr https://code.amazon.com/reviews/CR-29712724
2020-07-16 16:29:51 -07:00
Gyuho Lee
18dfb9cca3 version: 3.4.10
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-07-16 15:16:20 -07:00
Jingyi Hu
7b8270416d Merge pull request #12106 from bart0sh/PR001-cherry-pick-change-protobuf-field-type-from-int-to-int64
etcdserver: change protobuf field type from int to int64 (#12000)
2020-07-16 00:45:16 +08:00
Sahdev Zala
a2c37485dd Merge pull request #12127 from spzala/automated-cherry-pick-of-#12012-upstream-release-3.4
Automated cherry pick of #12012
2020-07-13 10:53:52 -04:00
Hitoshi Mitake
67bfc310f0 Documentation: note on data encryption 2020-07-13 09:50:30 -04:00
Yuchen Zhou
ed28c768a3 etcdserver: change protobuf field type from int to int64 (#12000) 2020-07-08 10:21:10 +03:00
Gyuho Lee
d3a702a09d Merge pull request #12112 from spzala/automated-cherry-pick-of-#12018-upstream-release-3.4
Automated cherry pick of #12018
2020-07-07 10:32:18 -07:00
Sahdev P. Zala
319331192e pkg: consider umask when use MkdirAll
os.MkdirAll creates directory before umask so make sure that a desired
permission is set after creating a directory with MkdirAll. Use the
existing TouchDirAll function which checks for permission if dir is already
exist and when create a new dir.
2020-07-07 11:46:31 -04:00
Gyuho Lee
2acdf88406 Merge pull request #12076 from cfc4n/automated-cherry-pick-of-#11987-upstream-release-3.4
Automated cherry pick of #11987
2020-07-06 13:01:24 -07:00
Gyuho Lee
a8454e453f Merge pull request #12089 from tangcong/automated-cherry-pick-of-#11997-origin-release-3.4
Automated cherry pick of #11997
2020-07-06 13:00:56 -07:00
Gyuho Lee
32583af167 Merge pull request #12101 from tangcong/automated-cherry-pick-of-#12100-origin-release-3.4
Automated cherry pick of #12100
2020-07-06 11:47:24 -07:00
Sahdev Zala
85cc4deae6 Merge pull request #12103 from spzala/automated-cherry-pick-of-#12092-upstream-release-3.4
Automated cherry pick of #12092
2020-07-05 11:45:30 -04:00
Hitoshi Mitake
7dec4c412c etcdmain: let grpc proxy warn about insecure-skip-tls-verify 2020-07-01 18:25:29 -04:00
tangcong
a4667f596a etcdmain: fix shadow error 2020-07-01 13:36:48 +08:00
tangcong
0207d1df66 pkg/fileutil: print desired file permission in error log 2020-06-29 09:59:19 +08:00
Gyuho Lee
99e893d285 Merge pull request #12074 from cfc4n/automated-cherry-pick-of-#12005-upstream-release-3.4
Automated cherry pick of #12005
2020-06-26 11:30:07 -07:00
Gyuho Lee
d5dec731db Merge pull request #12077 from cfc4n/automated-cherry-pick-of-#11980-upstream-release-3.4
Automated cherry pick of #11980
2020-06-26 11:29:16 -07:00
Gyuho Lee
81a2edc365 Merge pull request #12081 from spzala/automated-cherry-pick-of-#11945-upstream-release-3.4
Automated cherry pick of #11945
2020-06-26 11:28:34 -07:00
Changxin Miao
e5424fc474 pkg: Fix dir permission check on Windows 2020-06-25 20:20:55 -04:00
cfc4n
4488595e05 auth: Customize simpleTokenTTL settings.
see https://github.com/etcd-io/etcd/issues/11978 for more detail.
2020-06-25 19:58:26 +08:00
cfc4n
7b99863e02 mvcc: chanLen 1024 is to biger,and it used more memory. 128 seems to be enough. Sometimes the consumption speed is more than the production speed.
See https://github.com/etcd-io/etcd/issues/11906 for more detail.
2020-06-25 19:53:13 +08:00
cfc4n
490c6139ac auth: return incorrect result 'ErrUserNotFound' when client request without username or username was empty.
Fiexs https://github.com/etcd-io/etcd/issues/12004 .
2020-06-25 19:48:36 +08:00
Gyuho Lee
31e49a4df3 Merge pull request #12048 from spzala/automated-cherry-pick-of-#11793-upstream-release-3.4
Automated cherry pick of #11793
2020-06-24 20:42:26 -07:00
Gyuho Lee
83fc96df0c Merge pull request #12055 from tangcong/automated-cherry-pick-of-#11850-origin-release-3.4
Automated cherry pick of #11850
2020-06-24 20:41:44 -07:00
Gyuho Lee
45192cf62b Merge pull request #12064 from cfc4n/automated-cherry-pick-of-#11986-upstream-release-3.4
Automated cherry pick of #11986
2020-06-24 20:40:37 -07:00
Gyuho Lee
1a1281005c Merge pull request #12070 from spzala/automated-cherry-pick-of-#12060-upstream-release-3.4
Automated cherry pick of #12060
2020-06-24 20:39:33 -07:00
Gyuho Lee
a4f42948e8 Merge pull request #12072 from tangcong/automated-cherry-pick-of-#12066-origin-release-3.4
Automated cherry pick of #12066
2020-06-24 20:39:15 -07:00
Gyuho Lee
2212a84adb Merge pull request #12034 from spzala/automated-cherry-pick-of-#11798-upstream-release-3.4
Automated cherry pick of #11798
2020-06-24 20:38:46 -07:00
tangcong
e42d7b5248 etcdmain: fix shadow error 2020-06-25 06:40:33 +08:00
Xiang Li
b86bb615ff doc: add TLS related warnings 2020-06-24 16:39:35 -04:00
cfc4n
ee963470f4 etcdserver:FDUsage set ticker to 10 minute from 5 seconds. This ticker will check File Descriptor Requirements ,and count all fds in used. And recorded some logs when in used >= limit/5*4. Just recorded message. If fds was more than 10K,It's low performance due to FDUsage() works. So need to increase it.
see https://github.com/etcd-io/etcd/issues/11969 for more detail.
2020-06-24 13:28:40 +08:00
Jack Kleeman
36452a1c1d clientv3: cancel watches proactively on client context cancellation
Currently, watch cancel requests are only sent to the server after a
message comes through on a watch where the client has cancelled. This
means that cancelled watches that don't receive any new messages are
never cancelled; they persist for the lifetime of the client stream.
This has negative connotations for locking applications where a watch
may observe a key which might never change again after cancellation,
leading to many accumulating watches on the server.

By cancelling proactively, in most cases we simply move the cancel
request to happen earlier, and additionally we solve the case where the
cancel request would never be sent.

Fixes #9416
Heavy inspiration drawn from the solutions proposed there.
2020-06-23 19:50:21 +08:00
Gyuho Lee
4571e528f4 wal: check out of range slice in "ReadAll", "decoder"
wal: add slice bound checks in decoder

CHANGELOG-3.5: add wal slice bound check
CHANGELOG-3.5: add "decodeRecord"

Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-06-22 11:57:43 -04:00
Gyuho Lee
37ac22205b Merge pull request #12035 from spzala/automated-cherry-pick-of-#11787-upstream-release-3.4
Automated cherry pick of #11787
2020-06-21 19:22:54 -07:00
Gyuho Lee
493f15c156 Merge pull request #12037 from spzala/automated-cherry-pick-of-#11807-upstream-release-3.4
Automated cherry pick of #11807
2020-06-21 19:20:42 -07:00
Gyuho Lee
c8b3c6f54c Merge pull request #12041 from spzala/automated-cherry-pick-of-#11795-upstream-release-3.4
Automated cherry pick of #11795
2020-06-21 19:20:26 -07:00
Gyuho Lee
368ff75a10 Merge pull request #12039 from spzala/automated-cherry-pick-of-#11845-upstream-release-3.4
Automated cherry pick of #11845
2020-06-21 19:20:04 -07:00
Gyuho Lee
7adbfa1144 Merge pull request #12038 from spzala/automated-cherry-pick-of-#11608-upstream-release-3.4
Automated cherry pick of #11608
2020-06-21 19:19:50 -07:00
Gyuho Lee
e151faf3cc Merge pull request #12040 from spzala/automated-cherry-pick-of-#11796-upstream-release-3.4
Automated cherry pick of #11796
2020-06-21 19:19:31 -07:00
Gyuho Lee
8292fd5051 Merge pull request #12042 from spzala/automated-cherry-pick-of-#11818-upstream-release-3.4
Automated cherry pick of #11818
2020-06-21 19:19:17 -07:00
Gyuho Lee
c37245ed4b Merge pull request #12043 from spzala/automated-cherry-pick-of-#11830-upstream-release-3.4
Automated cherry pick of #11830
2020-06-21 19:18:51 -07:00
Gyuho Lee
6dab8aff66 Merge pull request #12044 from spzala/automated-cherry-pick-of-#11841-upstream-release-3.4
Automated cherry pick of #11841
2020-06-21 19:18:35 -07:00
Hitoshi Mitake
c69efda350 etcdctl, etcdmain: warn about --insecure-skip-tls-verify options 2020-06-21 19:23:06 -04:00
Hitoshi Mitake
3d8e9a323d Documentation: note on the policy of insecure by default 2020-06-21 19:21:05 -04:00
Hitoshi Mitake
963b242846 etcdserver: don't let InternalAuthenticateRequest have password 2020-06-21 19:18:18 -04:00
Hitoshi Mitake
6f011ce524 auth: a new error code for the case of password auth against no password user 2020-06-21 19:12:55 -04:00
Hitoshi Mitake
36f8dee003 Documentation: note on password strength 2020-06-21 19:08:39 -04:00
Xiang Li
47001f28bd etcdmain: best effort detection of self pointing in tcp proxy 2020-06-21 18:12:24 -04:00
Sahdev P. Zala
9a24f73f7b Discovery: do not allow passing negative cluster size
When an etcd instance attempts to perform service discovery, if a
cluster size with negative value  is provided, the etcd instance
will panic without recovery because of
2020-06-21 18:00:35 -04:00
Sahdev P. Zala
7d1cf64049 wal: fix panic when decoder not set
Handle the related panic and clarify doc.
2020-06-21 16:21:34 -04:00
Sahdev P. Zala
05c441f92f embed: fix compaction runtime err
Handle negative value input which currently gives a runtime error.
2020-06-20 20:58:18 -04:00
Sahdev P. Zala
434f7e83f0 pkg: check file stats
modify file util.
2020-06-20 16:29:47 -04:00
Gyuho Lee
91b1a9182a Merge pull request #11977 from jpbetz/automated-cherry-pick-of-#11946-release-3.4
Automated cherry pick of #11946
2020-06-05 12:46:33 -07:00
David Crawshaw
78f67988aa etcdserver, et al: add --unsafe-no-fsync flag
This makes it possible to run an etcd node for testing and development
without placing lots of load on the file system.

Fixes #11930.

Signed-off-by: David Crawshaw <crawshaw@tailscale.com>
2020-06-04 20:19:28 -07:00
Gyuho Lee
54ba958911 version: 3.4.9
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-05-20 16:28:29 -07:00
Gyuho Lee
609e844f86 Merge pull request #11811 from wswcfan/automated-cherry-pick-of-#11735-origin-release-3.4
Automated cherry pick of #11735 on release-3.4
2020-05-20 15:57:48 -07:00
tangcong
166b4473fa wal: add TestValidSnapshotEntriesAfterPurgeWal testcase
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-05-20 11:38:06 -07:00
tangcong
ed231df7c0 wal: fix crc mismatch crash bug 2020-05-20 11:37:04 -07:00
Gyuho Lee
cfe37de6c0 rafthttp: log snapshot download duration
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-05-20 11:37:01 -07:00
Gyuho Lee
0de2b1f860 version: 3.4.8
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-05-18 11:40:23 -07:00
Gyuho Lee
a668adba78 rafthttp: improve snapshot send logging
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-05-18 11:39:24 -07:00
Gyuho Lee
9bad82fee5 *: make sure snapshot save downloads SHA256 checksum
ref. https://github.com/etcd-io/etcd/pull/11896

Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-05-17 17:38:42 -07:00
Gyuho Lee
f1ea03a7c8 etcdserver/api/snap: exclude orphaned defragmentation files in snapNames
ref. https://github.com/etcd-io/etcd/pull/11900

Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-05-17 14:21:02 -07:00
Ted Yu
4079deadb4 etcdserver: continue releasing snap db in case of error
Signed-off-by: Ted Yu <yuzhihong@gmail.com>
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-05-17 14:15:26 -07:00
Viacheslav Biriukov
87fc3c9e57 etcdserver,wal: fix inconsistencies in WAL and snapshot
etcdserver/*, wal/*: changes to snapshots and wal logic
etcdserver/*: changes to snapshots and wal logic to fix #10219
etcdserver/*, wal/*: add Sync method
etcdserver/*, wal/*: find valid snapshots by cross checking snap files and wal snap entries
etcdserver/*, wal/*:Add comments, clean up error messages and tests
etcdserver/*, wal/*: Remove orphaned .snap.db files during Release

Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-05-15 08:40:09 -07:00
Gary Belvin
e048e166ab cherry pick of #11564 (#11880)
* clientv3: fix grpc-go(v1.27.0) incompatible changes to balancer/resolver.

* vendor: upgrade gRPC Go to v1.24.0

Picking up some performance improvements and bug fixes.

https://github.com/grpc/grpc-go/releases/tag/v1.24.0

Signed-off-by: Gyuho Lee <leegyuho@amazon.com>

* vendor: update gRPC Go to v1.26.0 (#11522)

* GO111MODULE=on go mod vendor

* GO111MODULE=on go mod vendor go 1.14

Bump travis 2

Co-authored-by: EDDYCJY <313687982@qq.com>
Co-authored-by: Gyuho Lee <leegyuho@amazon.com>
Co-authored-by: Yuchen Zhou <yczhou@google.com>
2020-05-13 10:12:58 -07:00
Gyuho Lee
2333c727a2 Merge pull request #11855 from tangcong/automated-cherry-pick-of-#11817-origin-release-3.4
Automated cherry pick of #11817 on release-3.4
2020-05-07 20:00:26 -07:00
tangcong
aa75e90ac8 mvcc: fix deadlock bug 2020-05-08 09:56:23 +08:00
shawwang
f18976f4b8 auth: optimize lock scope for CheckPassword
to improve authentication performance in concurrent scenarios when enable auth and using authentication based password
2020-04-25 18:36:18 +08:00
Jingyi Hu
f1eca4e1fa Merge pull request #11752 from tangcong/automated-cherry-pick-of-#11652-#11670-#11710-origin-release-3.4
Automated cherry pick of #11652 #11670 #11710
2020-04-10 23:21:45 +08:00
tangcong
b733b22712 auth: ensure RoleGrantPermission is compatible with older versions 2020-04-09 09:33:40 +08:00
tangcong
eb80716532 etcdserver: print warn log when failed to apply request 2020-04-09 09:33:40 +08:00
tangcong
e2abd97659 auth: cleanup saveConsistentIndex in NewAuthStore 2020-04-09 09:33:40 +08:00
tangcong
716821b9b5 auth: print warning log when error is ErrAuthOldRevision 2020-04-09 09:33:40 +08:00
shawwang
63116ffdb4 auth: add new metric 'etcd_debugging_auth_revision' 2020-04-09 09:33:40 +08:00
shawwang
b3d54def77 tools/etcd-dump-db: add auth decoder, optimize print format 2020-04-09 09:33:40 +08:00
tangcong
347c8dac3b *: fix auth revision corruption bug 2020-04-09 09:33:36 +08:00
Sahdev Zala
e2ae6013a4 Merge pull request #11757 from jingyih/automated-cherry-pick-of-#11754-upstream-release-3.4
Automated cherry pick of #11754 on release-3.4
2020-04-06 11:09:26 -04:00
Changxin Miao
9c8554573f etcdserver: watch stream got closed once one request is not permitted (#11708) 2020-04-06 07:06:57 -07:00
Gyuho Lee
e694b7bb08 version: 3.4.7
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-04-01 10:46:54 -07:00
Gyuho Lee
e99399d0dc wal: add "etcd_wal_writes_bytes_total"
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-04-01 09:30:09 -07:00
Gyuho Lee
b68f8ff31d pkg/ioutil: add "FlushN"
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-04-01 09:29:59 -07:00
Gyuho Lee
857dffa386 Merge pull request #11734 from jingyih/automated-cherry-pick-of-#11330-upstream-release-3.4
Cherry pick of #11330 on release-3.4
2020-03-31 14:58:59 -07:00
jingyih
5f17aa2c8b test: auto detect branch when finding merge base 2020-03-31 10:59:44 -07:00
shenjiangc
89b10cf967 mvcc/kvstore:when the number key-value is greater than one million, compact take too long and blocks other requests 2020-03-30 08:21:38 -07:00
Gyuho Lee
bdc9bc1d81 version: 3.4.6
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-03-29 12:47:27 -07:00
tangcong
b0bdaaa449 lease: fix memory leak in LeaseGrant when node is follower 2020-03-29 12:47:14 -07:00
Gyuho Lee
e784ba73c2 version: 3.4.5
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-03-18 17:24:42 -07:00
Gyuho Lee
35dc623a98 words: whitelist "racey"
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-03-18 17:24:15 -07:00
Gyuho Lee
130342152a Revert "version: 3.4.5"
This reverts commit 0dc5d577fc.
2020-03-18 17:17:19 -07:00
Gyuho Lee
fc93fbf9de words: whitelist "hasleader"
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-03-18 17:17:04 -07:00
Gyuho Lee
0dc5d577fc version: 3.4.5
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-03-18 17:17:04 -07:00
Gyuho Lee
e63db56cc9 etcdserver/api/v3rpc: handle api version metadata, add metrics
ref.
https://github.com/etcd-io/etcd/pull/11687

Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-03-18 17:17:04 -07:00
Gyuho Lee
1471e12108 clientv3: embed api version in metadata
ref.
https://github.com/etcd-io/etcd/pull/11687

Signed-off-by: Gyuho Lee <leegyuho@amazon.com>

clientv3: fix racy writes to context key

=== RUN   TestWatchOverlapContextCancel

==================

WARNING: DATA RACE

Write at 0x00c42110dd40 by goroutine 99:

  runtime.mapassign()

      /usr/local/go/src/runtime/hashmap.go:485 +0x0

  github.com/coreos/etcd/clientv3.metadataSet()

      /go/src/github.com/coreos/etcd/gopath/src/github.com/coreos/etcd/clientv3/ctx.go:61 +0x8c

  github.com/coreos/etcd/clientv3.withVersion()

      /go/src/github.com/coreos/etcd/gopath/src/github.com/coreos/etcd/clientv3/ctx.go:47 +0x137

  github.com/coreos/etcd/clientv3.newStreamClientInterceptor.func1()

      /go/src/github.com/coreos/etcd/gopath/src/github.com/coreos/etcd/clientv3/client.go:309 +0x81

  google.golang.org/grpc.NewClientStream()

      /go/src/github.com/coreos/etcd/gopath/src/google.golang.org/grpc/stream.go:101 +0x10e

  github.com/coreos/etcd/etcdserver/etcdserverpb.(*watchClient).Watch()

      /go/src/github.com/coreos/etcd/gopath/src/github.com/coreos/etcd/etcdserver/etcdserverpb/rpc.pb.go:3193 +0xe9

  github.com/coreos/etcd/clientv3.(*watchGrpcStream).openWatchClient()

      /go/src/github.com/coreos/etcd/gopath/src/github.com/coreos/etcd/clientv3/watch.go:788 +0x143

  github.com/coreos/etcd/clientv3.(*watchGrpcStream).newWatchClient()

      /go/src/github.com/coreos/etcd/gopath/src/github.com/coreos/etcd/clientv3/watch.go:700 +0x5c3

  github.com/coreos/etcd/clientv3.(*watchGrpcStream).run()

      /go/src/github.com/coreos/etcd/gopath/src/github.com/coreos/etcd/clientv3/watch.go:431 +0x12b

Previous read at 0x00c42110dd40 by goroutine 130:

  reflect.maplen()

      /usr/local/go/src/runtime/hashmap.go:1165 +0x0

  reflect.Value.MapKeys()

      /usr/local/go/src/reflect/value.go:1090 +0x43b

  fmt.(*pp).printValue()

      /usr/local/go/src/fmt/print.go:741 +0x1885

  fmt.(*pp).printArg()

      /usr/local/go/src/fmt/print.go:682 +0x1b1

  fmt.(*pp).doPrintf()

      /usr/local/go/src/fmt/print.go:998 +0x1cad

  fmt.Sprintf()

      /usr/local/go/src/fmt/print.go:196 +0x77

  github.com/coreos/etcd/clientv3.streamKeyFromCtx()

      /go/src/github.com/coreos/etcd/gopath/src/github.com/coreos/etcd/clientv3/watch.go:825 +0xc8

  github.com/coreos/etcd/clientv3.(*watcher).Watch()

      /go/src/github.com/coreos/etcd/gopath/src/github.com/coreos/etcd/clientv3/watch.go:265 +0x426

  github.com/coreos/etcd/clientv3/integration.testWatchOverlapContextCancel.func1()

      /go/src/github.com/coreos/etcd/gopath/src/github.com/coreos/etcd/clientv3/integration/watch_test.go:959 +0x23e

Goroutine 99 (running) created at:

  github.com/coreos/etcd/clientv3.(*watcher).newWatcherGrpcStream()

      /go/src/github.com/coreos/etcd/gopath/src/github.com/coreos/etcd/clientv3/watch.go:236 +0x59d

  github.com/coreos/etcd/clientv3.(*watcher).Watch()

      /go/src/github.com/coreos/etcd/gopath/src/github.com/coreos/etcd/clientv3/watch.go:278 +0xbb6

  github.com/coreos/etcd/clientv3/integration.testWatchOverlapContextCancel.func1()

      /go/src/github.com/coreos/etcd/gopath/src/github.com/coreos/etcd/clientv3/integration/watch_test.go:959 +0x23e

Goroutine 130 (running) created at:

  github.com/coreos/etcd/clientv3/integration.testWatchOverlapContextCancel()

      /go/src/github.com/coreos/etcd/gopath/src/github.com/coreos/etcd/clientv3/integration/watch_test.go:979 +0x76d

  github.com/coreos/etcd/clientv3/integration.TestWatchOverlapContextCancel()

      /go/src/github.com/coreos/etcd/gopath/src/github.com/coreos/etcd/clientv3/integration/watch_test.go:922 +0x44

  testing.tRunner()

      /usr/local/go/src/testing/testing.go:657 +0x107

==================

Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-03-18 17:17:00 -07:00
Gyuho Lee
0b9cfa8677 etcdserver/api/etcdhttp: log server-side /health checks
ref.
https://github.com/etcd-io/etcd/pull/11704

Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-03-18 16:29:24 -07:00
Sam Batschelet
b66c53ff5f proxy/grpcproxy: add return on error for metrics handler
Signed-off-by: Sam Batschelet <sbatsche@redhat.com>
2020-03-16 12:06:01 -04:00
Sahdev Zala
8f6c3f4d09 Merge pull request #11664 from jingyih/automated-cherry-pick-of-#11638-upstream-release-3.4
Automated cherry pick of #11638 on release-3.4
2020-03-11 19:26:11 -04:00
jingyih
379d01a0d2 etcdctl: fix member add command
Use members information from member add response, which is
guaranteed to be up to date.
2020-02-29 07:18:11 -08:00
Gyuho Lee
c65a9e2dd1 version: 3.4.4
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2020-02-24 13:14:02 -08:00
Jingyi Hu
7862f6ed2c Merge pull request #11644 from jingyih/automated-cherry-pick-of-#11640-upstream-release-3.4
Automated cherry pick of #11640 on release-3.4
2020-02-23 14:42:34 +08:00
Rafael Fernández López
257319fb18 etcdserver: fix quorum calculation when promoting a learner member
When promoting a learner member we should not count already a voting
member, but take only into account the number of existing voting
members and their current status (started, unstarted) when taking the
decision whether a learner member can be promoted.

Before this change, it was impossible to grow from a quorum N to a N+1
through promoting a learning member.

Fixes: #11633
2020-02-21 23:14:55 -08:00
Jingyi Hu
cdb2dc11b8 Merge pull request #11636 from YoyinZyc/automated-cherry-pick-of-#11621-upstream-release-3.4
Automated cherry pick of #11621 to release-3.4
2020-02-20 13:04:33 +08:00
jingyih
770674e4a6 etcdserver: corruption check via http
During corruption check, get peer's hashKV via http call.
2020-02-18 14:12:19 -08:00
Jingyi Hu
c10168f718 Merge pull request #11631 from jingyih/automated-cherry-pick-of-#11630-upstream-release-3.4
Automated cherry pick of #11630 to release-3.4
2020-02-16 08:35:23 +08:00
jingyih
94673a6ba4 mvcc/backend: check for nil boltOpenOptions
Check if boltOpenOptions is nil before use it.
2020-02-15 00:18:26 -08:00
Joe Betz
a1bf5574fc Merge pull request #11622 from jpbetz/automated-cherry-pick-of-#11613-origin-release-3.4
Automated cherry pick of #11613 to release-3.4
2020-02-13 14:45:38 -08:00
Joe Betz
6d646c442a mvcc/backend: Delete orphaned db.tmp files before defrag 2020-02-13 12:26:54 -08:00
Jingyi Hu
1226686cf3 Merge pull request #11588 from jingyih/automated-cherry-pick-of-#11586-upstream-release-3.4
Automated cherry pick of #11586 on release 3.4
2020-02-04 19:45:59 -08:00
jingyih
50e12328ac auth: correct logging level 2020-02-04 05:38:58 -08:00
Jingyi Hu
0dc78a144b Merge pull request #11439 from YoyinZyc/automated-cherry-pick-of-#11418-upstream-release-3.4
Automated cherry pick of #11418 to release 3.4
2019-12-11 14:41:06 -08:00
yoyinzyc
7cf32c262c e2e: test curl auth on onoption user 2019-12-10 12:53:10 -08:00
yoyinzyc
4a9247a47e auth: fix NoPassWord check when add user 2019-12-10 12:53:10 -08:00
Jingyi Hu
ac63c2fbd0 Merge pull request #11415 from YoyinZyc/automated-cherry-pick-of-#11413-upstream-release-3.4
Automated cherry pick of #11413 to release-3.4
2019-12-02 14:51:47 -08:00
yoyinzyc
ae5bd3c268 auth: fix user.Options nil pointer 2019-12-02 14:44:15 -08:00
Jingyi Hu
94e46ba0d7 Merge pull request #11403 from jingyih/automated-cherry-pick-of-#11400-upstream-release-3.4
Automated cherry pick of #11400 on release 3.4
2019-11-27 13:28:34 -08:00
宇慕
8c10973820 mvcc/kvstore:fixcompactbug 2019-11-27 13:07:47 -08:00
Wenjia
1af0b51537 Merge pull request #11393 from jingyih/automated-cherry-pick-of-#11374-upstream-release-3.4
Automated cherry pick of #11374 on release 3.4
2019-11-26 15:02:27 -08:00
yoyinzyc
f4669c3b62 mvcc: update to "etcd_debugging_mvcc_total_put_size_in_bytes" 2019-11-26 14:03:07 -08:00
yoyinzyc
55c3476abc mvcc: add "etcd_mvcc_put_size_in_bytes" to monitor the throughput of put request. 2019-11-26 14:03:07 -08:00
Jingyi Hu
b66203c0a1 Merge pull request #11299 from jingyih/automated-cherry-pick-of-#10468-upstream-release-3.4
Automated cherry pick of #10468 on release-3.4
2019-11-05 18:34:22 -08:00
Gyuho Lee
4388404f56 clientv3: fix retry/streamer error message
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2019-10-31 10:09:18 -07:00
Gyuho Lee
a447d51f23 Merge pull request #11312 from jingyih/automated-cherry-pick-of-#11308-upstream-release-3.4
Automated cherry pick of #11308 on release-3.4
2019-10-31 10:08:34 -07:00
Jingyi Hu
4f3c81d81d etcdserver: wait purge file loop during shutdown
To prevent the purge file loop from accidentally acquiring the file lock
and remove the files during server shutdowm.
2019-10-30 16:04:41 -07:00
Jingyi Hu
478da3bf24 integration: disable TestV3AuthOldRevConcurrent
Disable TestV3AuthOldRevConcurrent for now. See
https://github.com/etcd-io/etcd/pull/10468#issuecomment-463253361
2019-10-28 15:03:44 -07:00
Jingyi Hu
d6b30e43cd etcdserver: remove auth validation loop
Remove auth validation loop in v3_server.raftRequest(). Re-validation
when error ErrAuthOldRevision occurs should be handled on client side.
2019-10-28 15:03:44 -07:00
Gyuho Lee
1e98c9642e scripts/release: list GPG key only when tagging is needed
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2019-10-23 11:13:21 -07:00
Gyuho Lee
3cf2f69b57 version: 3.4.3
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2019-10-23 10:11:46 -07:00
Gyuho Lee
d617055284 *: use Go 1.12.12
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2019-10-23 10:11:02 -07:00
Gyuho Lee
84db9b0878 Merge pull request #11252 from YoyinZyc/automated-cherry-pick-of-#11247-origin-release-3.4
Automated cherry pick of #11247
2019-10-18 10:30:30 -07:00
Gyuho Lee
6cf418ff6d Merge pull request #11275 from YoyinZyc/stream-support-3.4
rafthttp: add 3.4 stream type
2019-10-18 10:25:53 -07:00
yoyinzyc
97e68cf4e7 rafthttp: add 3.4 stream type 2019-10-17 14:33:53 -07:00
Gyuho Lee
90556d550d Merge pull request #11269 from jingyih/automated-cherry-pick-of-#11265-upstream-release-3.4
Automated cherry pick of #11265 on release 3.4
2019-10-16 16:50:06 -07:00
Jingyi Hu
a00abf5f2a etcdserver: strip patch version in metrics
Strip patch version in cluster version metrics during node restart.
2019-10-16 16:29:53 -07:00
Gyuho Lee
b3329ebcd2 Merge pull request #11255 from jingyih/automated-cherry-pick-of-#11233-#11254-upstream-release-3.4
Automated cherry pick of #11233 #11254 on release 3.4
2019-10-15 11:06:09 -07:00
Jingyi Hu
b67862c0a6 etcdserver: strip patch version in cluster version
Strip patch version in cluster version metrics.
2019-10-14 17:37:49 -07:00
Jingyi Hu
6a699b6b7f etcdserver: unset old cluster version in metrics 2019-10-14 17:35:10 -07:00
Joe Betz
bb5ba14aac Add version, tag and branch checks to release script 2019-10-14 12:55:17 -07:00
Gyuho Lee
c3dc994567 Merge pull request #11243 from YoyinZyc/automated-cherry-pick-of-#11237-origin-release-3.4
Automated cherry pick of #11237
2019-10-11 12:38:10 -07:00
Yuchen Zhou
f3fbed5b72 Merge branch 'release-3.4' into automated-cherry-pick-of-#11237-origin-release-3.4 2019-10-11 11:17:03 -07:00
yoyinzyc
e2547907c5 scripts: avoid release builds on darwin machine. 2019-10-11 11:12:30 -07:00
Gyuho Lee
14c239030f Merge pull request #11235 from YoyinZyc/automated-cherry-pick-of-#11234-origin-release-3.4
Automated cherry pick of #11234
2019-10-10 16:28:59 -07:00
yoyinzyc
7b67e8a5c5 scripts: fix read failure prompt in release; use https for git clone. 2019-10-10 16:20:17 -07:00
Joe Betz
bbe86b066c version: 3.4.2 2019-10-09 15:26:52 -07:00
Gyuho Lee
2c36cab87d Merge pull request #11223 from YoyinZyc/automated-cherry-pick-of-#11179-origin-release-3.4
Automated cherry pick of #11179
2019-10-09 13:28:04 -07:00
yoyinzyc
480d5510f9 etcdserver: trace compaction request; add return parameter 'trace' to applierV3.Compaction() mvcc: trace compaction request; add input parameter 'trace' to KV.Compact() 2019-10-09 12:40:12 -07:00
yoyinzyc
9245518363 etcdserver: trace raft requests. 2019-10-09 12:40:12 -07:00
yoyinzyc
daa432cfa7 etcdserver: add put request steps. mvcc: add put request steps; add trace to KV.Write() as input parameter. 2019-10-09 12:40:12 -07:00
yoyinzyc
8717327697 pkg: use zap logger to format the structure log output. 2019-10-09 12:40:12 -07:00
yoyinzyc
4f1bbff888 pkg: add field to record additional detail of trace; add stepThreshold to reduce log volume. 2019-10-09 12:40:12 -07:00
yoyinzyc
28bb8037d9 pkg: create package traceutil for tracing. mvcc: add tracing steps:range from the in-memory index tree; range from boltdb. etcdserver: add tracing steps: agreement among raft nodes before linerized reading; authentication; filter and sort kv pairs; assemble the response. 2019-10-09 12:40:12 -07:00
Joe Betz
03b5e7229b Merge pull request #11213 from jpbetz/automated-cherry-pick-of-#11211-origin-release-3.4
Automated cherry pick of #11211
2019-10-08 18:47:06 -07:00
Joe Betz
0781c0327d clientv3: Replace endpoint.ParseHostPort with net.SplitHostPort to fix IPv6 client endpoints 2019-10-08 18:27:03 -07:00
Joe Betz
99774d8ed4 Merge pull request #11214 from jpbetz/automated-cherry-pick-of-#11184-origin-release-3.4
Automated cherry pick of #11184
2019-10-08 17:35:02 -07:00
Joe Betz
c454344f14 clientv3: Set authority used in cert checks to host of endpoint 2019-10-08 15:35:27 -07:00
Gyuho Lee
dae0a72a42 Merge pull request #11200 from jingyih/automated-cherry-pick-of-#11194-origin-release-3.4
Automated cherry pick of #11194 on release-3.4
2019-10-03 16:03:23 -07:00
Gyuho Lee
c91a6bf14f tests/e2e: fix metrics tests
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2019-10-03 16:02:39 -07:00
Jingyi Hu
b7ff97f54e etcdctl: fix member add command 2019-10-03 13:52:22 -07:00
Gyuho Lee
d08bb07d6d scripts/build-binary: fix darwin tar commands
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2019-09-28 11:39:04 -07:00
Gyuho Lee
3a736a81e8 scripts/release: fix SHA256SUMS command
Signed-off-by: Gyuho Lee <leegyuho@amazon.com>
2019-09-17 14:12:18 -07:00
Debabrata Banerjee
3b8f812955 etcdserver: Fix PeerURL validation
In case of URLs that are synonyms, the current lexicographic sorting
and compare of the URLs fails with frustrating errors. Make sure to do
a full comparison between every set of PeerURLs before failing.

Fixes #11013
2019-09-16 11:49:58 -04:00
1339 changed files with 8391 additions and 449392 deletions

24
.github/workflows/release.yaml vendored Normal file
View File

@@ -0,0 +1,24 @@
name: Release
on: [push, pull_request]
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: "1.17.13"
- run: |
git config --global user.email "github-action@etcd.io"
git config --global user.name "Github Action"
gpg --batch --gen-key <<EOF
%no-protection
Key-Type: 1
Key-Length: 2048
Subkey-Type: 1
Subkey-Length: 2048
Name-Real: Github Action
Name-Email: github-action@etcd.io
Expire-Date: 0
EOF
DRY_RUN=true ./scripts/release.sh --no-upload --no-docker-push 3.4.99

77
.github/workflows/tests.yaml vendored Normal file
View File

@@ -0,0 +1,77 @@
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
target:
- linux-amd64-fmt
- linux-amd64-integration-1-cpu
- linux-amd64-integration-2-cpu
- linux-amd64-integration-4-cpu
- linux-amd64-functional
- linux-amd64-unit-4-cpu-race
- all-build
- linux-amd64-grpcproxy
- linux-386-unit
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v2
with:
go-version: "1.17.13"
- run: date
- env:
TARGET: ${{ matrix.target }}
run: |
go version
echo ${GOROOT}
# The version of ${GOROOT} is 1.17.13, while the `/usr/bin/go` links to go1.17.11.
# We need to make sure they are linking to the same go version; otherwise, we will
# run into issue: "compile: version go1.17.13 does not match go tool version go1.17.11".
# Refer to https://github.com/actions/setup-go/issues/107 as well.
sudo unlink /usr/bin/go; sudo ln -s ${GOROOT}/bin/go /usr/bin/go; ls -lrt /usr/bin/go
echo "${TARGET}"
case "${TARGET}" in
linux-amd64-fmt)
GOARCH=amd64 PASSES='fmt bom dep' ./test
;;
linux-amd64-integration-1-cpu)
GOARCH=amd64 CPU=1 PASSES='integration' RACE='false' ./test
;;
linux-amd64-integration-2-cpu)
GOARCH=amd64 CPU=2 PASSES='integration' RACE='false' ./test
;;
linux-amd64-integration-4-cpu)
GOARCH=amd64 CPU=4 PASSES='integration' RACE='false' ./test
;;
linux-amd64-functional)
./build && GOARCH=amd64 PASSES='functional' ./test
;;
linux-amd64-unit-4-cpu-race)
GOARCH=amd64 PASSES='unit' RACE='true' CPU='4' ./test -p=2
;;
all-build)
GOARCH=amd64 PASSES='build' ./test
GOARCH=386 PASSES='build' ./test
GO_BUILD_FLAGS='-v' GOOS=darwin GOARCH=amd64 ./build
GO_BUILD_FLAGS='-v' GOOS=windows GOARCH=amd64 ./build
GO_BUILD_FLAGS='-v' GOARCH=arm ./build
GO_BUILD_FLAGS='-v' GOARCH=arm64 ./build
GO_BUILD_FLAGS='-v' GOARCH=ppc64le ./build
GO_BUILD_FLAGS='-v' GOARCH=s390x ./build
;;
linux-amd64-grpcproxy)
PASSES='build grpcproxy' CPU='4' RACE='true' ./test 2>&1 | tee test.log
! egrep "(--- FAIL:|DATA RACE|panic: test timed out|appears to have leaked)" -B50 -A10 test.log
;;
linux-386-unit)
GOARCH=386 PASSES='unit' ./test
;;
*)
echo "Failed to find target"
exit 1
;;
esac

View File

@@ -0,0 +1,37 @@
name: Trivy Nightly Scan
on:
schedule:
- cron: '0 2 * * *' # run at 2 AM UTC
permissions: read-all
jobs:
nightly-scan:
name: Trivy Scan nightly
strategy:
fail-fast: false
matrix:
# maintain the versions of etcd that need to be actively
# security scanned
versions: [v3.4.22]
permissions:
security-events: write # for github/codeql-action/upload-sarif to upload SARIF results
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@93ea575cb5d8a053eaa0ac8fa3b40d7e05a33cc8 # v3.1.0
with:
ref: release-3.4
- name: Run Trivy vulnerability scanner
uses: aquasecurity/trivy-action@9ab158e8597f3b310480b9a69402b419bc03dbd5 # master
with:
image-ref: 'gcr.io/etcd-development/etcd:${{ matrix.versions }}'
severity: 'CRITICAL,HIGH'
format: 'template'
template: '@/contrib/sarif.tpl'
output: 'trivy-results-3-4.sarif'
- name: Upload Trivy scan results to GitHub Security tab
uses: github/codeql-action/upload-sarif@a669cc5936cc5e1b6a362ec1ff9e410dc570d190 # v2.1.36
with:
sarif_file: 'trivy-results-3-4.sarif'

1
.gitignore vendored
View File

@@ -31,6 +31,7 @@ vendor/**/*
!vendor/**/License*
!vendor/**/LICENCE*
!vendor/**/LICENSE*
!vendor/modules.txt
vendor/**/*_test.go
*.bak

View File

@@ -1,94 +0,0 @@
language: go
go_import_path: go.etcd.io/etcd
sudo: required
services: docker
go:
- 1.12.9
notifications:
on_success: never
on_failure: never
env:
matrix:
- TARGET=linux-amd64-fmt
- TARGET=linux-amd64-integration-1-cpu
- TARGET=linux-amd64-integration-2-cpu
- TARGET=linux-amd64-integration-4-cpu
- TARGET=linux-amd64-functional
- TARGET=linux-amd64-unit
- TARGET=all-build
- TARGET=linux-amd64-grpcproxy
- TARGET=linux-386-unit
matrix:
fast_finish: true
allow_failures:
- go: 1.12.9
env: TARGET=linux-amd64-grpcproxy
- go: 1.12.9
env: TARGET=linux-386-unit
before_install:
- if [[ $TRAVIS_GO_VERSION == 1.* ]]; then docker pull gcr.io/etcd-development/etcd-test:go${TRAVIS_GO_VERSION}; fi
install:
- go get -t -v -d ./...
script:
- echo "TRAVIS_GO_VERSION=${TRAVIS_GO_VERSION}"
- >
case "${TARGET}" in
linux-amd64-fmt)
docker run --rm \
--volume=`pwd`:/go/src/go.etcd.io/etcd gcr.io/etcd-development/etcd-test:go${TRAVIS_GO_VERSION} \
/bin/bash -c "GOARCH=amd64 PASSES='fmt bom dep' ./test"
;;
linux-amd64-integration-1-cpu)
docker run --rm \
--volume=`pwd`:/go/src/go.etcd.io/etcd gcr.io/etcd-development/etcd-test:go${TRAVIS_GO_VERSION} \
/bin/bash -c "GOARCH=amd64 CPU=1 PASSES='integration' ./test"
;;
linux-amd64-integration-2-cpu)
docker run --rm \
--volume=`pwd`:/go/src/go.etcd.io/etcd gcr.io/etcd-development/etcd-test:go${TRAVIS_GO_VERSION} \
/bin/bash -c "GOARCH=amd64 CPU=2 PASSES='integration' ./test"
;;
linux-amd64-integration-4-cpu)
docker run --rm \
--volume=`pwd`:/go/src/go.etcd.io/etcd gcr.io/etcd-development/etcd-test:go${TRAVIS_GO_VERSION} \
/bin/bash -c "GOARCH=amd64 CPU=4 PASSES='integration' ./test"
;;
linux-amd64-functional)
docker run --rm \
--volume=`pwd`:/go/src/go.etcd.io/etcd gcr.io/etcd-development/etcd-test:go${TRAVIS_GO_VERSION} \
/bin/bash -c "./build && GOARCH=amd64 PASSES='functional' ./test"
;;
linux-amd64-unit)
docker run --rm \
--volume=`pwd`:/go/src/go.etcd.io/etcd gcr.io/etcd-development/etcd-test:go${TRAVIS_GO_VERSION} \
/bin/bash -c "GOARCH=amd64 PASSES='unit' ./test"
;;
all-build)
docker run --rm \
--volume=`pwd`:/go/src/go.etcd.io/etcd gcr.io/etcd-development/etcd-test:go${TRAVIS_GO_VERSION} \
/bin/bash -c "GOARCH=amd64 PASSES='build' ./test \
&& GOARCH=386 PASSES='build' ./test \
&& GO_BUILD_FLAGS='-v' GOOS=darwin GOARCH=amd64 ./build \
&& GO_BUILD_FLAGS='-v' GOOS=windows GOARCH=amd64 ./build \
&& GO_BUILD_FLAGS='-v' GOARCH=arm ./build \
&& GO_BUILD_FLAGS='-v' GOARCH=arm64 ./build \
&& GO_BUILD_FLAGS='-v' GOARCH=ppc64le ./build"
;;
linux-amd64-grpcproxy)
sudo HOST_TMP_DIR=/tmp TEST_OPTS="PASSES='build grpcproxy'" make docker-test
;;
linux-386-unit)
docker run --rm \
--volume=`pwd`:/go/src/go.etcd.io/etcd gcr.io/etcd-development/etcd-test:go${TRAVIS_GO_VERSION} \
/bin/bash -c "GOARCH=386 PASSES='unit' ./test"
;;
esac

4
.words
View File

@@ -36,6 +36,8 @@ iff
inflight
keepalive
keepalives
hasleader
racey
keyspace
linearization
liveness
@@ -74,6 +76,7 @@ consistentIndex
todo
saveWALAndSnap
SHA
subconns
nop
SubConns
@@ -95,6 +98,7 @@ jitter
WithBackoff
BackoffLinearWithJitter
jitter
WithDialer
WithMax
ServerStreams
BidiStreams

View File

@@ -1,15 +1,10 @@
FROM k8s.gcr.io/debian-base:v1.0.0
FROM --platform=linux/amd64 gcr.io/distroless/static-debian11
ADD etcd /usr/local/bin/
ADD etcdctl /usr/local/bin/
RUN mkdir -p /var/etcd/
RUN mkdir -p /var/lib/etcd/
# Alpine Linux doesn't use pam, which means that there is no /etc/nsswitch.conf,
# but Golang relies on /etc/nsswitch.conf to check the order of DNS resolving
# (see https://github.com/golang/go/commit/9dee7771f561cf6aee081c0af6658cc81fac3918)
# To fix this we just create /etc/nsswitch.conf and add the following line:
RUN echo 'hosts: files mdns4_minimal [NOTFOUND=return] dns mdns4' >> /etc/nsswitch.conf
WORKDIR /var/etcd/
WORKDIR /var/lib/etcd/
EXPOSE 2379 2380

View File

@@ -1,9 +1,10 @@
FROM k8s.gcr.io/debian-base-arm64:v1.0.0
FROM --platform=linux/arm64 gcr.io/distroless/static-debian11
ADD etcd /usr/local/bin/
ADD etcdctl /usr/local/bin/
ADD var/etcd /var/etcd
ADD var/lib/etcd /var/lib/etcd
WORKDIR /var/etcd/
WORKDIR /var/lib/etcd/
EXPOSE 2379 2380

View File

@@ -1,9 +1,10 @@
FROM k8s.gcr.io/debian-base-ppc64le:v1.0.0
FROM --platform=linux/ppc64le gcr.io/distroless/static-debian11
ADD etcd /usr/local/bin/
ADD etcdctl /usr/local/bin/
ADD var/etcd /var/etcd
ADD var/lib/etcd /var/lib/etcd
WORKDIR /var/etcd/
WORKDIR /var/lib/etcd/
EXPOSE 2379 2380

View File

@@ -128,7 +128,7 @@ for TARGET_ARCH in "amd64" "arm64" "ppc64le"; do
TAG=quay.io/coreos/etcd GOARCH=${TARGET_ARCH} \
BINARYDIR=release/etcd-${VERSION}-linux-${TARGET_ARCH} \
BUILDDIR=release \
./scripts/build-docker ${VERSION}
./scripts/build-docker.sh ${VERSION}
done
```

View File

@@ -174,3 +174,5 @@ As of version v3.2 if an etcd server is launched with the option `--client-cert-
As of version v3.3 if an etcd server is launched with the option `--peer-cert-allowed-cn` or `--peer-cert-allowed-hostname` filtering of inter-peer connections is enabled. Nodes can only join the etcd cluster if their TLS certificate identity match the allowed one.
See [etcd security page](https://github.com/etcd-io/etcd/blob/master/Documentation/op-guide/security.md) for more details.
## Notes on password strength
`etcdctl` command line interface and etcd API don't check a strength (length, coexistence of numbers and alphabets, etc) of the password during creating a new user or updating password of an existing user. An administrator needs to care about a requirement of password strength by themselves.

View File

@@ -4,7 +4,7 @@ title: etcd gateway
## What is etcd gateway
etcd gateway is a simple TCP proxy that forwards network data to the etcd cluster. The gateway is stateless and transparent; it neither inspects client requests nor interferes with cluster responses.
etcd gateway is a simple TCP proxy that forwards network data to the etcd cluster. The gateway is stateless and transparent; it neither inspects client requests nor interferes with cluster responses. It does not terminate TLS connections, do TLS handshakes on behalf of its clients, or verify if the connection is secured.
The gateway supports multiple etcd server endpoints and works on a simple round-robin policy. It only routes to available endpoints and hides failures from its clients. Other retry policies, such as weighted round-robin, may be supported in the future.
@@ -74,7 +74,7 @@ $ etcd gateway start --discovery-srv=example.com
* Comma-separated list of etcd server targets for forwarding client connections.
* Default: `127.0.0.1:2379`
* Invalid example: `https://127.0.0.1:2379` (gateway does not terminate TLS)
* Invalid example: `https://127.0.0.1:2379` (gateway does not terminate TLS). Note that the gateway does not verify the HTTP schema or inspect the requests, it only forwards requests to the given endpoints.
#### --discovery-srv
@@ -103,5 +103,5 @@ $ etcd gateway start --discovery-srv=example.com
#### --trusted-ca-file
* Path to the client TLS CA file for the etcd cluster. Used to authenticate endpoints.
* Path to the client TLS CA file for the etcd cluster to verify the endpoints returned from SRV discovery. Note that it is ONLY used for authenticating the discovered endpoints rather than creating connections for data transferring. The gateway never terminates TLS connections or create TLS connections on behalf of its clients.
* Default: (not set)

View File

@@ -2,7 +2,7 @@
title: Transport security model
---
etcd supports automatic TLS as well as authentication through client certificates for both clients to server as well as peer (server to server / cluster) communication.
etcd supports automatic TLS as well as authentication through client certificates for both clients to server as well as peer (server to server / cluster) communication. **Note that etcd doesn't enable [RBAC based authentication][auth] or the authentication feature in the transport layer by default to reduce friction for users getting started with the database. Further, changing this default would be a breaking change for the project which was established since 2013. An etcd cluster which doesn't enable security features can expose its data to any clients.**
To get up and running, first have a CA certificate and a signed key pair for one member. It is recommended to create and sign a new key pair for every member in a cluster.
@@ -426,8 +426,17 @@ Make sure to sign the certificates with a Subject Name the member's public IP ad
The certificate needs to be signed for the member's FQDN in its Subject Name, use Subject Alternative Names (short IP SANs) to add the IP address. The `etcd-ca` tool provides `--domain=` option for its `new-cert` command, and openssl can make [it][alt-name] too.
### Does etcd encrypt data stored on disk drives?
No. etcd doesn't encrypt key/value data stored on disk drives. If a user need to encrypt data stored on etcd, there are some options:
* Let client applications encrypt and decrypt the data
* Use a feature of underlying storage systems for encrypting stored data like [dm-crypt]
### Im seeing a log warning that "directory X exist without recommended permission -rwx------"
When etcd create certain new directories it sets file permission to 700 to prevent unprivileged access as possible. However, if user has already created a directory with own preference, etcd uses the existing directory and logs a warning message if the permission is different than 700.
[cfssl]: https://github.com/cloudflare/cfssl
[tls-setup]: ../../hack/tls-setup
[tls-guide]: https://github.com/coreos/docs/blob/master/os/generate-self-signed-certificates.md
[alt-name]: http://wiki.cacert.org/FAQ/subjectAltName
[auth]: authentication.md
[dm-crypt]: https://en.wikipedia.org/wiki/Dm-crypt

View File

@@ -51,7 +51,7 @@ docker-remove:
GO_VERSION ?= 1.12.9
GO_VERSION ?= 1.12.17
ETCD_VERSION ?= $(shell git rev-parse --short HEAD || echo "GitNotFound")
TEST_SUFFIX = $(shell date +%s | base64 | head -c 15)
@@ -65,11 +65,11 @@ endif
# Example:
# GO_VERSION=1.12.9 make build-docker-test
# GO_VERSION=1.12.17 make build-docker-test
# make build-docker-test
#
# gcloud docker -- login -u _json_key -p "$(cat /etc/gcp-key-etcd-development.json)" https://gcr.io
# GO_VERSION=1.12.9 make push-docker-test
# GO_VERSION=1.12.17 make push-docker-test
# make push-docker-test
#
# gsutil -m acl ch -u allUsers:R -r gs://artifacts.etcd-development.appspot.com

View File

@@ -21,7 +21,7 @@ import (
"errors"
"time"
jwt "github.com/dgrijalva/jwt-go"
"github.com/golang-jwt/jwt"
"go.uber.org/zap"
)
@@ -105,7 +105,7 @@ func (t *tokenJWT) assign(ctx context.Context, username string, revision uint64)
token, err := tk.SignedString(t.key)
if err != nil {
if t.lg != nil {
t.lg.Warn(
t.lg.Debug(
"failed to sign a JWT token",
zap.String("user-name", username),
zap.Uint64("revision", revision),
@@ -118,7 +118,7 @@ func (t *tokenJWT) assign(ctx context.Context, username string, revision uint64)
}
if t.lg != nil {
t.lg.Info(
t.lg.Debug(
"created/assigned a new JWT token",
zap.String("user-name", username),
zap.Uint64("revision", revision),
@@ -136,7 +136,7 @@ func newTokenProviderJWT(lg *zap.Logger, optMap map[string]string) (*tokenJWT, e
err = opts.ParseWithDefaults(optMap)
if err != nil {
if lg != nil {
lg.Warn("problem loading JWT options", zap.Error(err))
lg.Error("problem loading JWT options", zap.Error(err))
} else {
plog.Errorf("problem loading JWT options: %s", err)
}

42
auth/metrics.go Normal file
View File

@@ -0,0 +1,42 @@
// 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 auth
import (
"github.com/prometheus/client_golang/prometheus"
"sync"
)
var (
currentAuthRevision = prometheus.NewGaugeFunc(prometheus.GaugeOpts{
Namespace: "etcd_debugging",
Subsystem: "auth",
Name: "revision",
Help: "The current revision of auth store.",
},
func() float64 {
reportCurrentAuthRevMu.RLock()
defer reportCurrentAuthRevMu.RUnlock()
return reportCurrentAuthRev()
},
)
// overridden by auth store initialization
reportCurrentAuthRevMu sync.RWMutex
reportCurrentAuthRev = func() float64 { return 0 }
)
func init() {
prometheus.MustRegister(currentAuthRevision)
}

View File

@@ -21,7 +21,7 @@ import (
"io/ioutil"
"time"
jwt "github.com/dgrijalva/jwt-go"
"github.com/golang-jwt/jwt"
)
const (

View File

@@ -113,38 +113,48 @@ func checkKeyPoint(lg *zap.Logger, cachedPerms *unifiedRangePermissions, key []b
return false
}
func (as *authStore) isRangeOpPermitted(tx backend.BatchTx, userName string, key, rangeEnd []byte, permtyp authpb.Permission_Type) bool {
// assumption: tx is Lock()ed
_, ok := as.rangePermCache[userName]
func (as *authStore) isRangeOpPermitted(userName string, key, rangeEnd []byte, permtyp authpb.Permission_Type) bool {
as.rangePermCacheMu.RLock()
defer as.rangePermCacheMu.RUnlock()
rangePerm, ok := as.rangePermCache[userName]
if !ok {
perms := getMergedPerms(as.lg, tx, userName)
if perms == nil {
if as.lg != nil {
as.lg.Warn(
"failed to create a merged permission",
zap.String("user-name", userName),
)
} else {
plog.Errorf("failed to create a unified permission of user %s", userName)
}
return false
}
as.rangePermCache[userName] = perms
as.lg.Error(
"user doesn't exist",
zap.String("user-name", userName),
)
return false
}
if len(rangeEnd) == 0 {
return checkKeyPoint(as.lg, as.rangePermCache[userName], key, permtyp)
return checkKeyPoint(as.lg, rangePerm, key, permtyp)
}
return checkKeyInterval(as.lg, as.rangePermCache[userName], key, rangeEnd, permtyp)
return checkKeyInterval(as.lg, rangePerm, key, rangeEnd, permtyp)
}
func (as *authStore) clearCachedPerm() {
func (as *authStore) refreshRangePermCache(tx backend.BatchTx) {
// Note that every authentication configuration update calls this method and it invalidates the entire
// rangePermCache and reconstruct it based on information of users and roles stored in the backend.
// This can be a costly operation.
as.rangePermCacheMu.Lock()
defer as.rangePermCacheMu.Unlock()
as.rangePermCache = make(map[string]*unifiedRangePermissions)
}
func (as *authStore) invalidateCachedPerm(userName string) {
delete(as.rangePermCache, userName)
users := getAllUsers(as.lg, tx)
for _, user := range users {
userName := string(user.Name)
perms := getMergedPerms(as.lg, tx, userName)
if perms == nil {
as.lg.Error(
"failed to create a merged permission",
zap.String("user-name", userName),
)
continue
}
as.rangePermCache[userName] = perms
}
}
type unifiedRangePermissions struct {

View File

@@ -37,7 +37,7 @@ const (
// var for testing purposes
var (
simpleTokenTTL = 5 * time.Minute
simpleTokenTTLDefault = 300 * time.Second
simpleTokenTTLResolution = 1 * time.Second
)
@@ -47,6 +47,7 @@ type simpleTokenTTLKeeper struct {
stopc chan struct{}
deleteTokenFunc func(string)
mu *sync.Mutex
simpleTokenTTL time.Duration
}
func (tm *simpleTokenTTLKeeper) stop() {
@@ -58,12 +59,12 @@ func (tm *simpleTokenTTLKeeper) stop() {
}
func (tm *simpleTokenTTLKeeper) addSimpleToken(token string) {
tm.tokens[token] = time.Now().Add(simpleTokenTTL)
tm.tokens[token] = time.Now().Add(tm.simpleTokenTTL)
}
func (tm *simpleTokenTTLKeeper) resetSimpleToken(token string) {
if _, ok := tm.tokens[token]; ok {
tm.tokens[token] = time.Now().Add(simpleTokenTTL)
tm.tokens[token] = time.Now().Add(tm.simpleTokenTTL)
}
}
@@ -101,6 +102,7 @@ type tokenSimple struct {
simpleTokenKeeper *simpleTokenTTLKeeper
simpleTokensMu sync.Mutex
simpleTokens map[string]string // token -> username
simpleTokenTTL time.Duration
}
func (t *tokenSimple) genTokenPrefix() (string, error) {
@@ -157,6 +159,15 @@ func (t *tokenSimple) invalidateUser(username string) {
}
func (t *tokenSimple) enable() {
t.simpleTokensMu.Lock()
defer t.simpleTokensMu.Unlock()
if t.simpleTokenKeeper != nil { // already enabled
return
}
if t.simpleTokenTTL <= 0 {
t.simpleTokenTTL = simpleTokenTTLDefault
}
delf := func(tk string) {
if username, ok := t.simpleTokens[tk]; ok {
if t.lg != nil {
@@ -177,6 +188,7 @@ func (t *tokenSimple) enable() {
stopc: make(chan struct{}),
deleteTokenFunc: delf,
mu: &t.simpleTokensMu,
simpleTokenTTL: t.simpleTokenTTL,
}
go t.simpleTokenKeeper.run()
}
@@ -234,10 +246,14 @@ func (t *tokenSimple) isValidSimpleToken(ctx context.Context, token string) bool
return false
}
func newTokenProviderSimple(lg *zap.Logger, indexWaiter func(uint64) <-chan struct{}) *tokenSimple {
func newTokenProviderSimple(lg *zap.Logger, indexWaiter func(uint64) <-chan struct{}, TokenTTL time.Duration) *tokenSimple {
if lg == nil {
lg = zap.NewNop()
}
return &tokenSimple{
lg: lg,
simpleTokens: make(map[string]string),
indexWaiter: indexWaiter,
lg: lg,
simpleTokens: make(map[string]string),
indexWaiter: indexWaiter,
simpleTokenTTL: TokenTTL,
}
}

View File

@@ -24,9 +24,9 @@ import (
// TestSimpleTokenDisabled ensures that TokenProviderSimple behaves correctly when
// disabled.
func TestSimpleTokenDisabled(t *testing.T) {
initialState := newTokenProviderSimple(zap.NewExample(), dummyIndexWaiter)
initialState := newTokenProviderSimple(zap.NewExample(), dummyIndexWaiter, simpleTokenTTLDefault)
explicitlyDisabled := newTokenProviderSimple(zap.NewExample(), dummyIndexWaiter)
explicitlyDisabled := newTokenProviderSimple(zap.NewExample(), dummyIndexWaiter, simpleTokenTTLDefault)
explicitlyDisabled.enable()
explicitlyDisabled.disable()
@@ -48,7 +48,7 @@ func TestSimpleTokenDisabled(t *testing.T) {
// TestSimpleTokenAssign ensures that TokenProviderSimple can correctly assign a
// token, look it up with info, and invalidate it by user.
func TestSimpleTokenAssign(t *testing.T) {
tp := newTokenProviderSimple(zap.NewExample(), dummyIndexWaiter)
tp := newTokenProviderSimple(zap.NewExample(), dummyIndexWaiter, simpleTokenTTLDefault)
tp.enable()
ctx := context.WithValue(context.WithValue(context.TODO(), AuthenticateParamIndex{}, uint64(1)), AuthenticateParamSimpleTokenPrefix{}, "dummy")
token, err := tp.assign(ctx, "user1", 0)

View File

@@ -23,6 +23,7 @@ import (
"strings"
"sync"
"sync/atomic"
"time"
"go.etcd.io/etcd/auth/authpb"
"go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes"
@@ -59,6 +60,7 @@ var (
ErrRoleNotFound = errors.New("auth: role not found")
ErrRoleEmpty = errors.New("auth: role name is empty")
ErrAuthFailed = errors.New("auth: authentication failed, invalid user ID or password")
ErrNoPasswordUser = errors.New("auth: authentication failed, password was given for no password user")
ErrPermissionDenied = errors.New("auth: permission denied")
ErrRoleNotGranted = errors.New("auth: role is not granted to the user")
ErrPermissionNotGranted = errors.New("auth: permission is not granted to the role")
@@ -94,6 +96,9 @@ type AuthenticateParamIndex struct{}
// AuthenticateParamSimpleTokenPrefix is used for a key of context in the parameters of Authenticate()
type AuthenticateParamSimpleTokenPrefix struct{}
// saveConsistentIndexFunc is used to sync consistentIndex to backend, now reusing store.saveIndex
type saveConsistentIndexFunc func(tx backend.BatchTx)
// AuthStore defines auth storage interface.
type AuthStore interface {
// AuthEnable turns on the authentication feature
@@ -186,6 +191,9 @@ type AuthStore interface {
// HasRole checks that user has role
HasRole(user, role string) bool
// SetConsistentIndexSyncer sets consistentIndex syncer
SetConsistentIndexSyncer(syncer saveConsistentIndexFunc)
}
type TokenProvider interface {
@@ -207,12 +215,23 @@ type authStore struct {
enabled bool
enabledMu sync.RWMutex
rangePermCache map[string]*unifiedRangePermissions // username -> unifiedRangePermissions
// rangePermCache needs to be protected by rangePermCacheMu
// rangePermCacheMu needs to be write locked only in initialization phase or configuration changes
// Hot paths like Range(), needs to acquire read lock for improving performance
//
// Note that BatchTx and ReadTx cannot be a mutex for rangePermCache because they are independent resources
// see also: https://github.com/etcd-io/etcd/pull/13920#discussion_r849114855
rangePermCache map[string]*unifiedRangePermissions // username -> unifiedRangePermissions
rangePermCacheMu sync.RWMutex
tokenProvider TokenProvider
bcryptCost int // the algorithm cost / strength for hashing auth passwords
tokenProvider TokenProvider
syncConsistentIndex saveConsistentIndexFunc
bcryptCost int // the algorithm cost / strength for hashing auth passwords
}
func (as *authStore) SetConsistentIndexSyncer(syncer saveConsistentIndexFunc) {
as.syncConsistentIndex = syncer
}
func (as *authStore) AuthEnable() error {
as.enabledMu.Lock()
defer as.enabledMu.Unlock()
@@ -246,7 +265,7 @@ func (as *authStore) AuthEnable() error {
as.enabled = true
as.tokenProvider.enable()
as.rangePermCache = make(map[string]*unifiedRangePermissions)
as.refreshRangePermCache(tx)
as.setRevision(getRevision(tx))
@@ -269,6 +288,7 @@ func (as *authStore) AuthDisable() {
tx.Lock()
tx.UnsafePut(authBucketName, enableFlagKey, authDisabled)
as.commitRevision(tx)
as.saveConsistentIndex(tx)
tx.Unlock()
b.ForceCommit()
@@ -306,7 +326,7 @@ func (as *authStore) Authenticate(ctx context.Context, username, password string
return nil, ErrAuthFailed
}
if user.Options.NoPassword {
if user.Options != nil && user.Options.NoPassword {
return nil, ErrAuthFailed
}
@@ -335,17 +355,27 @@ func (as *authStore) CheckPassword(username, password string) (uint64, error) {
return 0, ErrAuthNotEnabled
}
tx := as.be.BatchTx()
tx.Lock()
defer tx.Unlock()
var user *authpb.User
// CompareHashAndPassword is very expensive, so we use closures
// to avoid putting it in the critical section of the tx lock.
revision, err := func() (uint64, error) {
tx := as.be.BatchTx()
tx.Lock()
defer tx.Unlock()
user := getUser(as.lg, tx, username)
if user == nil {
return 0, ErrAuthFailed
}
user = getUser(as.lg, tx, username)
if user == nil {
return 0, ErrAuthFailed
}
if user.Options.NoPassword {
return 0, ErrAuthFailed
if user.Options != nil && user.Options.NoPassword {
return 0, ErrNoPasswordUser
}
return getRevision(tx), nil
}()
if err != nil {
return 0, err
}
if bcrypt.CompareHashAndPassword(user.Password, []byte(password)) != nil {
@@ -356,7 +386,7 @@ func (as *authStore) CheckPassword(username, password string) (uint64, error) {
}
return 0, ErrAuthFailed
}
return getRevision(tx), nil
return revision, nil
}
func (as *authStore) Recover(be backend.Backend) {
@@ -372,11 +402,15 @@ func (as *authStore) Recover(be backend.Backend) {
}
as.setRevision(getRevision(tx))
as.refreshRangePermCache(tx)
tx.Unlock()
as.enabledMu.Lock()
as.enabled = enabled
if enabled {
as.tokenProvider.enable()
}
as.enabledMu.Unlock()
}
@@ -388,7 +422,8 @@ func (as *authStore) UserAdd(r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse,
var hashed []byte
var err error
if r.Options != nil && !r.Options.NoPassword {
noPassword := r.Options != nil && r.Options.NoPassword
if !noPassword {
hashed, err = bcrypt.GenerateFromPassword([]byte(r.Password), as.bcryptCost)
if err != nil {
if as.lg != nil {
@@ -429,6 +464,8 @@ func (as *authStore) UserAdd(r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse,
putUser(as.lg, tx, newUser)
as.commitRevision(tx)
as.saveConsistentIndex(tx)
as.refreshRangePermCache(tx)
if as.lg != nil {
as.lg.Info("added a user", zap.String("user-name", r.Name))
@@ -460,8 +497,9 @@ func (as *authStore) UserDelete(r *pb.AuthUserDeleteRequest) (*pb.AuthUserDelete
delUser(tx, r.Name)
as.commitRevision(tx)
as.saveConsistentIndex(tx)
as.refreshRangePermCache(tx)
as.invalidateCachedPerm(r.Name)
as.tokenProvider.invalidateUser(r.Name)
if as.lg != nil {
@@ -512,8 +550,9 @@ func (as *authStore) UserChangePassword(r *pb.AuthUserChangePasswordRequest) (*p
putUser(as.lg, tx, updatedUser)
as.commitRevision(tx)
as.saveConsistentIndex(tx)
as.refreshRangePermCache(tx)
as.invalidateCachedPerm(r.Name)
as.tokenProvider.invalidateUser(r.Name)
if as.lg != nil {
@@ -565,9 +604,9 @@ func (as *authStore) UserGrantRole(r *pb.AuthUserGrantRoleRequest) (*pb.AuthUser
putUser(as.lg, tx, user)
as.invalidateCachedPerm(r.User)
as.commitRevision(tx)
as.saveConsistentIndex(tx)
as.refreshRangePermCache(tx)
if as.lg != nil {
as.lg.Info(
@@ -651,9 +690,9 @@ func (as *authStore) UserRevokeRole(r *pb.AuthUserRevokeRoleRequest) (*pb.AuthUs
putUser(as.lg, tx, updatedUser)
as.invalidateCachedPerm(r.Name)
as.commitRevision(tx)
as.saveConsistentIndex(tx)
as.refreshRangePermCache(tx)
if as.lg != nil {
as.lg.Info(
@@ -723,11 +762,9 @@ func (as *authStore) RoleRevokePermission(r *pb.AuthRoleRevokePermissionRequest)
putRole(as.lg, tx, updatedRole)
// TODO(mitake): currently single role update invalidates every cache
// It should be optimized.
as.clearCachedPerm()
as.commitRevision(tx)
as.saveConsistentIndex(tx)
as.refreshRangePermCache(tx)
if as.lg != nil {
as.lg.Info(
@@ -783,10 +820,11 @@ func (as *authStore) RoleDelete(r *pb.AuthRoleDeleteRequest) (*pb.AuthRoleDelete
putUser(as.lg, tx, updatedUser)
as.invalidateCachedPerm(string(user.Name))
}
as.commitRevision(tx)
as.saveConsistentIndex(tx)
as.refreshRangePermCache(tx)
if as.lg != nil {
as.lg.Info("deleted a role", zap.String("role-name", r.Role))
@@ -817,6 +855,7 @@ func (as *authStore) RoleAdd(r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse,
putRole(as.lg, tx, newRole)
as.commitRevision(tx)
as.saveConsistentIndex(tx)
if as.lg != nil {
as.lg.Info("created a role", zap.String("role-name", r.Name))
@@ -875,11 +914,9 @@ func (as *authStore) RoleGrantPermission(r *pb.AuthRoleGrantPermissionRequest) (
putRole(as.lg, tx, role)
// TODO(mitake): currently single role update invalidates every cache
// It should be optimized.
as.clearCachedPerm()
as.commitRevision(tx)
as.saveConsistentIndex(tx)
as.refreshRangePermCache(tx)
if as.lg != nil {
as.lg.Info(
@@ -903,8 +940,21 @@ func (as *authStore) isOpPermitted(userName string, revision uint64, key, rangeE
if revision == 0 {
return ErrUserEmpty
}
if revision < as.Revision() {
rev := as.Revision()
if revision < rev {
if as.lg != nil {
as.lg.Warn("request auth revision is less than current node auth revision",
zap.Uint64("current node auth revision", rev),
zap.Uint64("request auth revision", revision),
zap.ByteString("request key", key),
zap.Error(ErrAuthOldRevision))
} else {
plog.Warningf("request auth revision is less than current node auth revision,"+
"current node auth revision is %d,"+
"request auth revision is %d,"+
"request key is %s, "+
"err is %v", rev, revision, key, ErrAuthOldRevision)
}
return ErrAuthOldRevision
}
@@ -927,7 +977,7 @@ func (as *authStore) isOpPermitted(userName string, revision uint64, key, rangeE
return nil
}
if as.isRangeOpPermitted(tx, userName, key, rangeEnd, permTyp) {
if as.isRangeOpPermitted(userName, key, rangeEnd, permTyp) {
return nil
}
@@ -950,7 +1000,7 @@ func (as *authStore) IsAdminPermitted(authInfo *AuthInfo) error {
if !as.IsAuthEnabled() {
return nil
}
if authInfo == nil {
if authInfo == nil || authInfo.Username == "" {
return ErrUserEmpty
}
@@ -993,7 +1043,15 @@ func getUser(lg *zap.Logger, tx backend.BatchTx, username string) *authpb.User {
}
func getAllUsers(lg *zap.Logger, tx backend.BatchTx) []*authpb.User {
_, vs := tx.UnsafeRange(authUsersBucketName, []byte{0}, []byte{0xff}, -1)
var vs [][]byte
err := tx.UnsafeForEach(authUsersBucketName, func(k []byte, v []byte) error {
vs = append(vs, v)
return nil
})
if err != nil {
lg.Panic("failed to get users",
zap.Error(err))
}
if len(vs) == 0 {
return nil
}
@@ -1144,6 +1202,10 @@ func NewAuthStore(lg *zap.Logger, be backend.Backend, tp TokenProvider, bcryptCo
as.commitRevision(tx)
}
as.setupMetricsReporter()
as.refreshRangePermCache(tx)
tx.Unlock()
be.ForceCommit()
@@ -1305,7 +1367,8 @@ func decomposeOpts(lg *zap.Logger, optstr string) (string, map[string]string, er
func NewTokenProvider(
lg *zap.Logger,
tokenOpts string,
indexWaiter func(uint64) <-chan struct{}) (TokenProvider, error) {
indexWaiter func(uint64) <-chan struct{},
TokenTTL time.Duration) (TokenProvider, error) {
tokenType, typeSpecificOpts, err := decomposeOpts(lg, tokenOpts)
if err != nil {
return nil, ErrInvalidAuthOpts
@@ -1318,7 +1381,7 @@ func NewTokenProvider(
} else {
plog.Warningf("simple token is not cryptographically signed")
}
return newTokenProviderSimple(lg, indexWaiter), nil
return newTokenProviderSimple(lg, indexWaiter, TokenTTL), nil
case tokenTypeJWT:
return newTokenProviderJWT(lg, typeSpecificOpts)
@@ -1418,3 +1481,23 @@ func (as *authStore) HasRole(user, role string) bool {
func (as *authStore) BcryptCost() int {
return as.bcryptCost
}
func (as *authStore) saveConsistentIndex(tx backend.BatchTx) {
if as.syncConsistentIndex != nil {
as.syncConsistentIndex(tx)
} else {
if as.lg != nil {
as.lg.Error("failed to save consistentIndex,syncConsistentIndex is nil")
} else {
plog.Error("failed to save consistentIndex,syncConsistentIndex is nil")
}
}
}
func (as *authStore) setupMetricsReporter() {
reportCurrentAuthRevMu.Lock()
reportCurrentAuthRev = func() float64 {
return float64(as.Revision())
}
reportCurrentAuthRevMu.Unlock()
}

View File

@@ -28,6 +28,7 @@ import (
"go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes"
pb "go.etcd.io/etcd/etcdserver/etcdserverpb"
"go.etcd.io/etcd/mvcc/backend"
"go.etcd.io/etcd/pkg/adt"
"go.uber.org/zap"
"golang.org/x/crypto/bcrypt"
@@ -48,7 +49,7 @@ func TestNewAuthStoreRevision(t *testing.T) {
b, tPath := backend.NewDefaultTmpBackend()
defer os.Remove(tPath)
tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter)
tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter, simpleTokenTTLDefault)
if err != nil {
t.Fatal(err)
}
@@ -78,7 +79,7 @@ func TestNewAuthStoreBcryptCost(t *testing.T) {
b, tPath := backend.NewDefaultTmpBackend()
defer os.Remove(tPath)
tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter)
tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter, simpleTokenTTLDefault)
if err != nil {
t.Fatal(err)
}
@@ -98,7 +99,7 @@ func TestNewAuthStoreBcryptCost(t *testing.T) {
func setupAuthStore(t *testing.T) (store *authStore, teardownfunc func(t *testing.T)) {
b, tPath := backend.NewDefaultTmpBackend()
tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter)
tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter, simpleTokenTTLDefault)
if err != nil {
t.Fatal(err)
}
@@ -151,7 +152,8 @@ func TestUserAdd(t *testing.T) {
as, tearDown := setupAuthStore(t)
defer tearDown(t)
ua := &pb.AuthUserAddRequest{Name: "foo", Options: &authpb.UserAddOptions{NoPassword: false}}
const userName = "foo"
ua := &pb.AuthUserAddRequest{Name: userName, Options: &authpb.UserAddOptions{NoPassword: false}}
_, err := as.UserAdd(ua) // add an existing user
if err == nil {
t.Fatalf("expected %v, got %v", ErrUserAlreadyExist, err)
@@ -165,6 +167,11 @@ func TestUserAdd(t *testing.T) {
if err != ErrUserEmpty {
t.Fatal(err)
}
if _, ok := as.rangePermCache[userName]; !ok {
t.Fatalf("user %s should be added but it doesn't exist in rangePermCache", userName)
}
}
func TestRecover(t *testing.T) {
@@ -179,6 +186,30 @@ func TestRecover(t *testing.T) {
}
}
func TestRecoverWithEmptyRangePermCache(t *testing.T) {
as, tearDown := setupAuthStore(t)
defer as.Close()
defer tearDown(t)
as.enabled = false
as.rangePermCache = map[string]*unifiedRangePermissions{}
as.Recover(as.be)
if !as.IsAuthEnabled() {
t.Fatalf("expected auth enabled got disabled")
}
if len(as.rangePermCache) != 2 {
t.Fatalf("rangePermCache should have permission information for 2 users (\"root\" and \"foo\"), but has %d information", len(as.rangePermCache))
}
if _, ok := as.rangePermCache["root"]; !ok {
t.Fatal("user \"root\" should be created by setupAuthStore() but doesn't exist in rangePermCache")
}
if _, ok := as.rangePermCache["foo"]; !ok {
t.Fatal("user \"foo\" should be created by setupAuthStore() but doesn't exist in rangePermCache")
}
}
func TestCheckPassword(t *testing.T) {
as, tearDown := setupAuthStore(t)
defer tearDown(t)
@@ -213,7 +244,8 @@ func TestUserDelete(t *testing.T) {
defer tearDown(t)
// delete an existing user
ud := &pb.AuthUserDeleteRequest{Name: "foo"}
const userName = "foo"
ud := &pb.AuthUserDeleteRequest{Name: userName}
_, err := as.UserDelete(ud)
if err != nil {
t.Fatal(err)
@@ -227,6 +259,47 @@ func TestUserDelete(t *testing.T) {
if err != ErrUserNotFound {
t.Fatalf("expected %v, got %v", ErrUserNotFound, err)
}
if _, ok := as.rangePermCache[userName]; ok {
t.Fatalf("user %s should be deleted but it exists in rangePermCache", userName)
}
}
func TestUserDeleteAndPermCache(t *testing.T) {
as, tearDown := setupAuthStore(t)
defer tearDown(t)
// delete an existing user
const deletedUserName = "foo"
ud := &pb.AuthUserDeleteRequest{Name: deletedUserName}
_, err := as.UserDelete(ud)
if err != nil {
t.Fatal(err)
}
// delete a non-existing user
_, err = as.UserDelete(ud)
if err != ErrUserNotFound {
t.Fatalf("expected %v, got %v", ErrUserNotFound, err)
}
if _, ok := as.rangePermCache[deletedUserName]; ok {
t.Fatalf("user %s should be deleted but it exists in rangePermCache", deletedUserName)
}
// add a new user
const newUser = "bar"
ua := &pb.AuthUserAddRequest{Name: newUser, Options: &authpb.UserAddOptions{NoPassword: false}}
_, err = as.UserAdd(ua)
if err != nil {
t.Fatal(err)
}
if _, ok := as.rangePermCache[newUser]; !ok {
t.Fatalf("user %s should exist but it doesn't exist in rangePermCache", deletedUserName)
}
}
func TestUserChangePassword(t *testing.T) {
@@ -503,17 +576,44 @@ func TestUserRevokePermission(t *testing.T) {
t.Fatal(err)
}
_, err = as.UserGrantRole(&pb.AuthUserGrantRoleRequest{User: "foo", Role: "role-test"})
const userName = "foo"
_, err = as.UserGrantRole(&pb.AuthUserGrantRoleRequest{User: userName, Role: "role-test"})
if err != nil {
t.Fatal(err)
}
_, err = as.UserGrantRole(&pb.AuthUserGrantRoleRequest{User: "foo", Role: "role-test-1"})
_, err = as.UserGrantRole(&pb.AuthUserGrantRoleRequest{User: userName, Role: "role-test-1"})
if err != nil {
t.Fatal(err)
}
u, err := as.UserGet(&pb.AuthUserGetRequest{Name: "foo"})
perm := &authpb.Permission{
PermType: authpb.WRITE,
Key: []byte("WriteKeyBegin"),
RangeEnd: []byte("WriteKeyEnd"),
}
_, err = as.RoleGrantPermission(&pb.AuthRoleGrantPermissionRequest{
Name: "role-test-1",
Perm: perm,
})
if err != nil {
t.Fatal(err)
}
if _, ok := as.rangePermCache[userName]; !ok {
t.Fatalf("User %s should have its entry in rangePermCache", userName)
}
unifiedPerm := as.rangePermCache[userName]
pt1 := adt.NewBytesAffinePoint([]byte("WriteKeyBegin"))
if !unifiedPerm.writePerms.Contains(pt1) {
t.Fatal("rangePermCache should contain WriteKeyBegin")
}
pt2 := adt.NewBytesAffinePoint([]byte("OutOfRange"))
if unifiedPerm.writePerms.Contains(pt2) {
t.Fatal("rangePermCache should not contain OutOfRange")
}
u, err := as.UserGet(&pb.AuthUserGetRequest{Name: userName})
if err != nil {
t.Fatal(err)
}
@@ -523,12 +623,12 @@ func TestUserRevokePermission(t *testing.T) {
t.Fatalf("expected %v, got %v", expected, u.Roles)
}
_, err = as.UserRevokeRole(&pb.AuthUserRevokeRoleRequest{Name: "foo", Role: "role-test-1"})
_, err = as.UserRevokeRole(&pb.AuthUserRevokeRoleRequest{Name: userName, Role: "role-test-1"})
if err != nil {
t.Fatal(err)
}
u, err = as.UserGet(&pb.AuthUserGetRequest{Name: "foo"})
u, err = as.UserGet(&pb.AuthUserGetRequest{Name: userName})
if err != nil {
t.Fatal(err)
}
@@ -626,7 +726,7 @@ func TestAuthInfoFromCtxRace(t *testing.T) {
b, tPath := backend.NewDefaultTmpBackend()
defer os.Remove(tPath)
tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter)
tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter, simpleTokenTTLDefault)
if err != nil {
t.Fatal(err)
}
@@ -658,6 +758,12 @@ func TestIsAdminPermitted(t *testing.T) {
t.Errorf("expected %v, got %v", ErrUserNotFound, err)
}
// empty user
err = as.IsAdminPermitted(&AuthInfo{Username: "", Revision: 1})
if err != ErrUserEmpty {
t.Errorf("expected %v, got %v", ErrUserEmpty, err)
}
// non-admin user
err = as.IsAdminPermitted(&AuthInfo{Username: "foo", Revision: 1})
if err != ErrPermissionDenied {
@@ -692,7 +798,7 @@ func TestRecoverFromSnapshot(t *testing.T) {
as.Close()
tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter)
tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter, simpleTokenTTLDefault)
if err != nil {
t.Fatal(err)
}
@@ -725,13 +831,13 @@ func contains(array []string, str string) bool {
func TestHammerSimpleAuthenticate(t *testing.T) {
// set TTL values low to try to trigger races
oldTTL, oldTTLRes := simpleTokenTTL, simpleTokenTTLResolution
oldTTL, oldTTLRes := simpleTokenTTLDefault, simpleTokenTTLResolution
defer func() {
simpleTokenTTL = oldTTL
simpleTokenTTLDefault = oldTTL
simpleTokenTTLResolution = oldTTLRes
}()
simpleTokenTTL = 10 * time.Millisecond
simpleTokenTTLResolution = simpleTokenTTL
simpleTokenTTLDefault = 10 * time.Millisecond
simpleTokenTTLResolution = simpleTokenTTLDefault
users := make(map[string]struct{})
as, tearDown := setupAuthStore(t)
@@ -774,7 +880,7 @@ func TestRolesOrder(t *testing.T) {
b, tPath := backend.NewDefaultTmpBackend()
defer os.Remove(tPath)
tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter)
tp, err := NewTokenProvider(zap.NewExample(), tokenTypeSimple, dummyIndexWaiter, simpleTokenTTLDefault)
if err != nil {
t.Fatal(err)
}
@@ -829,7 +935,7 @@ func testAuthInfoFromCtxWithRoot(t *testing.T, opts string) {
b, tPath := backend.NewDefaultTmpBackend()
defer os.Remove(tPath)
tp, err := NewTokenProvider(zap.NewExample(), opts, dummyIndexWaiter)
tp, err := NewTokenProvider(zap.NewExample(), opts, dummyIndexWaiter, simpleTokenTTLDefault)
if err != nil {
t.Fatal(err)
}

View File

@@ -44,15 +44,6 @@
}
]
},
{
"project": "github.com/dgrijalva/jwt-go",
"licenses": [
{
"type": "MIT License",
"confidence": 0.9891304347826086
}
]
},
{
"project": "github.com/dustin/go-humanize",
"licenses": [
@@ -71,6 +62,15 @@
}
]
},
{
"project": "github.com/golang-jwt/jwt",
"licenses": [
{
"type": "MIT License",
"confidence": 0.9891304347826086
}
]
},
{
"project": "github.com/golang/groupcache/lru",
"licenses": [
@@ -378,7 +378,7 @@
]
},
{
"project": "golang.org/x/sys/unix",
"project": "golang.org/x/sys",
"licenses": [
{
"type": "BSD 3-clause \"New\" or \"Revised\" License",

2
build
View File

@@ -16,7 +16,7 @@ GO_LDFLAGS="$GO_LDFLAGS -X ${REPO_PATH}/version.GitSHA=${GIT_SHA}"
toggle_failpoints() {
mode="$1"
if command -v gofail >/dev/null 2>&1; then
gofail "$mode" etcdserver/ mvcc/backend/
gofail "$mode" etcdserver/ mvcc/backend/ wal/
elif [[ "$mode" != "disable" ]]; then
echo "FAILPOINTS set but gofail not found"
exit 1

View File

@@ -19,7 +19,6 @@ import (
"fmt"
"strings"
"testing"
"time"
"go.etcd.io/etcd/clientv3/balancer/picker"
"go.etcd.io/etcd/clientv3/balancer/resolver/endpoint"
@@ -92,24 +91,25 @@ func TestRoundRobinBalancedResolvableNoFailover(t *testing.T) {
return picked, err
}
prev, switches := "", 0
_, picked, err := warmupConnections(reqFunc, tc.serverCount, "")
if err != nil {
t.Fatalf("Unexpected failure %v", err)
}
// verify that we round robin
prev, switches := picked, 0
for i := 0; i < tc.reqN; i++ {
picked, err := reqFunc(context.Background())
picked, err = reqFunc(context.Background())
if err != nil {
t.Fatalf("#%d: unexpected failure %v", i, err)
}
if prev == "" {
prev = picked
continue
}
if prev != picked {
switches++
}
prev = picked
}
if tc.serverCount > 1 && switches < tc.reqN-3 { // -3 for initial resolutions
// TODO: FIX ME
t.Skipf("expected balanced loads for %d requests, got switches %d", tc.reqN, switches)
if tc.serverCount > 1 && switches != tc.reqN {
t.Fatalf("expected balanced loads for %d requests, got switches %d", tc.reqN, switches)
}
})
}
@@ -160,26 +160,21 @@ func TestRoundRobinBalancedResolvableFailoverFromServerFail(t *testing.T) {
}
// stop first server, loads should be redistributed
// stopped server should never be picked
ms.StopAt(0)
available := make(map[string]struct{})
for i := 1; i < serverCount; i++ {
available[eps[i]] = struct{}{}
// stopped server will be transitioned into TRANSIENT_FAILURE state
// but it doesn't happen instantaneously and it can still be picked for a short period of time
// we ignore "transport is closing" in such case
available, picked, err := warmupConnections(reqFunc, serverCount-1, "transport is closing")
if err != nil {
t.Fatalf("Unexpected failure %v", err)
}
reqN := 10
prev, switches := "", 0
prev, switches := picked, 0
for i := 0; i < reqN; i++ {
picked, err := reqFunc(context.Background())
if err != nil && strings.Contains(err.Error(), "transport is closing") {
continue
}
if prev == "" { // first failover
if eps[0] == picked {
t.Fatalf("expected failover from %q, picked %q", eps[0], picked)
}
prev = picked
continue
picked, err = reqFunc(context.Background())
if err != nil {
t.Fatalf("#%d: unexpected failure %v", i, err)
}
if _, ok := available[picked]; !ok {
t.Fatalf("picked unavailable address %q (available %v)", picked, available)
@@ -189,18 +184,18 @@ func TestRoundRobinBalancedResolvableFailoverFromServerFail(t *testing.T) {
}
prev = picked
}
if switches < reqN-3 { // -3 for initial resolutions + failover
// TODO: FIX ME!
t.Skipf("expected balanced loads for %d requests, got switches %d", reqN, switches)
if switches != reqN {
t.Fatalf("expected balanced loads for %d requests, got switches %d", reqN, switches)
}
// now failed server comes back
ms.StartAt(0)
available, picked, err = warmupConnections(reqFunc, serverCount, "")
if err != nil {
t.Fatalf("Unexpected failure %v", err)
}
// enough time for reconnecting to recovered server
time.Sleep(time.Second)
prev, switches = "", 0
prev, switches = picked, 0
recoveredAddr, recovered := eps[0], 0
available[recoveredAddr] = struct{}{}
@@ -209,10 +204,6 @@ func TestRoundRobinBalancedResolvableFailoverFromServerFail(t *testing.T) {
if err != nil {
t.Fatalf("#%d: unexpected failure %v", i, err)
}
if prev == "" {
prev = picked
continue
}
if _, ok := available[picked]; !ok {
t.Fatalf("#%d: picked unavailable address %q (available %v)", i, picked, available)
}
@@ -224,10 +215,10 @@ func TestRoundRobinBalancedResolvableFailoverFromServerFail(t *testing.T) {
}
prev = picked
}
if switches < reqN-3 { // -3 for initial resolutions
if switches != 2*reqN {
t.Fatalf("expected balanced loads for %d requests, got switches %d", reqN, switches)
}
if recovered < reqN/serverCount {
if recovered != 2*reqN/serverCount {
t.Fatalf("recovered server %q got only %d requests", recoveredAddr, recovered)
}
}
@@ -242,11 +233,10 @@ func TestRoundRobinBalancedResolvableFailoverFromRequestFail(t *testing.T) {
}
defer ms.Stop()
var eps []string
available := make(map[string]struct{})
for _, svr := range ms.Servers {
eps = append(eps, svr.ResolverAddress().Addr)
available[svr.Address] = struct{}{}
}
rsv, err := endpoint.NewResolverGroup("requestfail")
if err != nil {
t.Fatal(err)
@@ -277,6 +267,11 @@ func TestRoundRobinBalancedResolvableFailoverFromRequestFail(t *testing.T) {
return picked, err
}
available, picked, err := warmupConnections(reqFunc, serverCount, "")
if err != nil {
t.Fatalf("Unexpected failure %v", err)
}
reqN := 20
prev, switches := "", 0
for i := 0; i < reqN; i++ {
@@ -285,17 +280,13 @@ func TestRoundRobinBalancedResolvableFailoverFromRequestFail(t *testing.T) {
if i%2 == 0 {
cancel()
}
picked, err := reqFunc(ctx)
picked, err = reqFunc(ctx)
if i%2 == 0 {
if s, ok := status.FromError(err); ok && s.Code() != codes.Canceled || picked != "" {
if s, ok := status.FromError(err); ok && s.Code() != codes.Canceled {
t.Fatalf("#%d: expected %v, got %v", i, context.Canceled, err)
}
continue
}
if prev == "" && picked != "" {
prev = picked
continue
}
if _, ok := available[picked]; !ok {
t.Fatalf("#%d: picked unavailable address %q (available %v)", i, picked, available)
}
@@ -304,7 +295,29 @@ func TestRoundRobinBalancedResolvableFailoverFromRequestFail(t *testing.T) {
}
prev = picked
}
if switches < reqN/2-3 { // -3 for initial resolutions + failover
if switches != reqN/2 {
t.Fatalf("expected balanced loads for %d requests, got switches %d", reqN, switches)
}
}
type reqFuncT = func(ctx context.Context) (picked string, err error)
func warmupConnections(reqFunc reqFuncT, serverCount int, ignoreErr string) (map[string]struct{}, string, error) {
var picked string
var err error
available := make(map[string]struct{})
// cycle through all peers to indirectly verify that balancer subconn list is fully loaded
// otherwise we can't reliably count switches between 'picked' peers in the test assert phase
for len(available) < serverCount {
picked, err = reqFunc(context.Background())
if err != nil {
if ignoreErr != "" && strings.Contains(err.Error(), ignoreErr) {
// skip ignored errors
continue
}
return available, picked, err
}
available[picked] = struct{}{}
}
return available, picked, err
}

View File

@@ -34,6 +34,6 @@ func (ep *errPicker) String() string {
return ep.p.String()
}
func (ep *errPicker) Pick(context.Context, balancer.PickOptions) (balancer.SubConn, func(balancer.DoneInfo), error) {
func (ep *errPicker) Pick(context.Context, balancer.PickInfo) (balancer.SubConn, func(balancer.DoneInfo), error) {
return nil, nil, ep.err
}

View File

@@ -52,7 +52,7 @@ type rrBalanced struct {
func (rb *rrBalanced) String() string { return rb.p.String() }
// Pick is called for every client request.
func (rb *rrBalanced) Pick(ctx context.Context, opts balancer.PickOptions) (balancer.SubConn, func(balancer.DoneInfo), error) {
func (rb *rrBalanced) Pick(ctx context.Context, opts balancer.PickInfo) (balancer.SubConn, func(balancer.DoneInfo), error) {
rb.mu.RLock()
n := len(rb.scs)
rb.mu.RUnlock()

View File

@@ -16,7 +16,9 @@
package endpoint
import (
"context"
"fmt"
"net"
"net/url"
"strings"
"sync"
@@ -109,7 +111,7 @@ func (e *ResolverGroup) Close() {
}
// Build creates or reuses an etcd resolver for the etcd cluster name identified by the authority part of the target.
func (b *builder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOption) (resolver.Resolver, error) {
func (b *builder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (resolver.Resolver, error) {
if len(target.Authority) < 1 {
return nil, fmt.Errorf("'etcd' target scheme requires non-empty authority identifying etcd cluster being routed to")
}
@@ -177,7 +179,7 @@ func epsToAddrs(eps ...string) (addrs []resolver.Address) {
return addrs
}
func (*Resolver) ResolveNow(o resolver.ResolveNowOption) {}
func (*Resolver) ResolveNow(o resolver.ResolveNowOptions) {}
func (r *Resolver) Close() {
es, err := bldr.getResolverGroup(r.endpointID)
@@ -228,13 +230,18 @@ func ParseTarget(target string) (string, string, error) {
return parts[0], parts[1], nil
}
// ParseHostPort splits a "<host>:<port>" string into the host and port parts.
// The port part is optional.
func ParseHostPort(hostPort string) (host string, port string) {
parts := strings.SplitN(hostPort, ":", 2)
host = parts[0]
if len(parts) > 1 {
port = parts[1]
// Dialer dials a endpoint using net.Dialer.
// Context cancelation and timeout are supported.
func Dialer(ctx context.Context, dialEp string) (net.Conn, error) {
proto, host, _ := ParseEndpoint(dialEp)
select {
case <-ctx.Done():
return nil, ctx.Err()
default:
}
return host, port
dialer := &net.Dialer{}
if deadline, ok := ctx.Deadline(); ok {
dialer.Deadline = deadline
}
return dialer.DialContext(ctx, proto, host)
}

View File

@@ -37,7 +37,6 @@ import (
"google.golang.org/grpc/codes"
grpccredentials "google.golang.org/grpc/credentials"
"google.golang.org/grpc/keepalive"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/status"
)
@@ -175,7 +174,9 @@ func (c *Client) Sync(ctx context.Context) error {
}
var eps []string
for _, m := range mresp.Members {
eps = append(eps, m.ClientURLs...)
if len(m.Name) != 0 && !m.IsLearner {
eps = append(eps, m.ClientURLs...)
}
}
c.SetEndpoints(eps...)
return nil
@@ -230,24 +231,17 @@ func (c *Client) dialSetupOpts(creds grpccredentials.TransportCredentials, dopts
}
opts = append(opts, dopts...)
// Provide a net dialer that supports cancelation and timeout.
f := func(dialEp string, t time.Duration) (net.Conn, error) {
proto, host, _ := endpoint.ParseEndpoint(dialEp)
select {
case <-c.ctx.Done():
return nil, c.ctx.Err()
default:
}
dialer := &net.Dialer{Timeout: t}
return dialer.DialContext(c.ctx, proto, host)
}
opts = append(opts, grpc.WithDialer(f))
dialer := endpoint.Dialer
if creds != nil {
opts = append(opts, grpc.WithTransportCredentials(creds))
// gRPC load balancer workaround. See credentials.transportCredential for details.
if credsDialer, ok := creds.(TransportCredentialsWithDialer); ok {
dialer = credsDialer.Dialer
}
} else {
opts = append(opts, grpc.WithInsecure())
}
opts = append(opts, grpc.WithContextDialer(dialer))
// Interceptor retry and backoff.
// TODO: Replace all of clientv3/retry.go with interceptor based retry, or with
@@ -266,7 +260,10 @@ func (c *Client) dialSetupOpts(creds grpccredentials.TransportCredentials, dopts
// Dial connects to a single endpoint using the client's config.
func (c *Client) Dial(ep string) (*grpc.ClientConn, error) {
creds := c.directDialCreds(ep)
creds, err := c.directDialCreds(ep)
if err != nil {
return nil, err
}
// Use the grpc passthrough resolver to directly dial a single endpoint.
// This resolver passes through the 'unix' and 'unixs' endpoints schemes used
// by etcd without modification, allowing us to directly dial endpoints and
@@ -369,8 +366,8 @@ func (c *Client) dial(target string, creds grpccredentials.TransportCredentials,
return conn, nil
}
func (c *Client) directDialCreds(ep string) grpccredentials.TransportCredentials {
_, hostPort, scheme := endpoint.ParseEndpoint(ep)
func (c *Client) directDialCreds(ep string) (grpccredentials.TransportCredentials, error) {
_, host, scheme := endpoint.ParseEndpoint(ep)
creds := c.creds
if len(scheme) != 0 {
creds = c.processCreds(scheme)
@@ -379,12 +376,17 @@ func (c *Client) directDialCreds(ep string) grpccredentials.TransportCredentials
// Set the server name must to the endpoint hostname without port since grpc
// otherwise attempts to check if x509 cert is valid for the full endpoint
// including the scheme and port, which fails.
host, _ := endpoint.ParseHostPort(hostPort)
clone.OverrideServerName(host)
overrideServerName, _, err := net.SplitHostPort(host)
if err != nil {
// Either the host didn't have a port or the host could not be parsed. Either way, continue with the
// original host string.
overrideServerName = host
}
clone.OverrideServerName(overrideServerName)
creds = clone
}
}
return creds
return creds, nil
}
func (c *Client) dialWithBalancerCreds(ep string) grpccredentials.TransportCredentials {
@@ -396,13 +398,6 @@ func (c *Client) dialWithBalancerCreds(ep string) grpccredentials.TransportCrede
return creds
}
// WithRequireLeader requires client requests to only succeed
// when the cluster has a leader.
func WithRequireLeader(ctx context.Context) context.Context {
md := metadata.Pairs(rpctypes.MetadataRequireLeaderKey, rpctypes.MetadataHasLeader)
return metadata.NewOutgoingContext(ctx, md)
}
func newClient(cfg *Config) (*Client, error) {
if cfg == nil {
cfg = &Config{}
@@ -663,3 +658,9 @@ func IsConnCanceled(err error) bool {
// <= gRPC v1.7.x returns 'errors.New("grpc: the client connection is closing")'
return strings.Contains(err.Error(), "grpc: the client connection is closing")
}
// TransportCredentialsWithDialer is for a gRPC load balancer workaround. See credentials.transportCredential for details.
type TransportCredentialsWithDialer interface {
grpccredentials.TransportCredentials
Dialer(ctx context.Context, dialEp string) (net.Conn, error)
}

View File

@@ -22,6 +22,7 @@ import (
"time"
"go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes"
"go.etcd.io/etcd/etcdserver/etcdserverpb"
"go.etcd.io/etcd/pkg/testutil"
"google.golang.org/grpc"
@@ -166,3 +167,51 @@ func TestCloseCtxClient(t *testing.T) {
t.Errorf("failed to Close the client. %v", err)
}
}
func TestSyncFiltersMembers(t *testing.T) {
defer testutil.AfterTest(t)
c, _ := New(Config{Endpoints: []string{"http://254.0.0.1:12345"}})
c.Cluster = &mockCluster{
[]*etcdserverpb.Member{
{ID: 0, Name: "", ClientURLs: []string{"http://254.0.0.1:12345"}, IsLearner: false},
{ID: 1, Name: "isStarted", ClientURLs: []string{"http://254.0.0.2:12345"}, IsLearner: true},
{ID: 2, Name: "isStartedAndNotLearner", ClientURLs: []string{"http://254.0.0.3:12345"}, IsLearner: false},
},
}
c.Sync(context.Background())
endpoints := c.Endpoints()
if len(endpoints) != 1 || endpoints[0] != "http://254.0.0.3:12345" {
t.Error("Client.Sync uses learner and/or non-started member client URLs")
}
c.Close()
}
type mockCluster struct {
members []*etcdserverpb.Member
}
func (mc *mockCluster) MemberList(ctx context.Context) (*MemberListResponse, error) {
return &MemberListResponse{Members: mc.members}, nil
}
func (mc *mockCluster) MemberAdd(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error) {
return nil, nil
}
func (mc *mockCluster) MemberAddAsLearner(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error) {
return nil, nil
}
func (mc *mockCluster) MemberRemove(ctx context.Context, id uint64) (*MemberRemoveResponse, error) {
return nil, nil
}
func (mc *mockCluster) MemberUpdate(ctx context.Context, id uint64, peerAddrs []string) (*MemberUpdateResponse, error) {
return nil, nil
}
func (mc *mockCluster) MemberPromote(ctx context.Context, id uint64) (*MemberPromoteResponse, error) {
return nil, nil
}

View File

@@ -65,22 +65,18 @@ func TestResumeElection(t *testing.T) {
respChan := make(chan *clientv3.GetResponse)
go func() {
defer close(respChan)
o := e.Observe(ctx)
respChan <- nil
for {
select {
case resp, ok := <-o:
if !ok {
t.Fatal("Observe() channel closed prematurely")
}
// Ignore any observations that candidate1 was elected
if string(resp.Kvs[0].Value) == "candidate1" {
continue
}
respChan <- &resp
return
for resp := range o {
// Ignore any observations that candidate1 was elected
if string(resp.Kvs[0].Value) == "candidate1" {
continue
}
respChan <- &resp
return
}
t.Error("Observe() channel closed prematurely")
}()
// wait until observe goroutine is running

View File

@@ -22,6 +22,7 @@ import (
"net"
"sync"
"go.etcd.io/etcd/clientv3/balancer/resolver/endpoint"
"go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes"
grpccredentials "google.golang.org/grpc/credentials"
)
@@ -65,38 +66,37 @@ func (b *bundle) NewWithMode(mode string) (grpccredentials.Bundle, error) {
}
// transportCredential implements "grpccredentials.TransportCredentials" interface.
// transportCredential wraps TransportCredentials to track which
// addresses are dialed for which endpoints, and then sets the authority when checking the endpoint's cert to the
// hostname or IP of the dialed endpoint.
// This is a workaround of a gRPC load balancer issue. gRPC uses the dialed target's service name as the authority when
// checking all endpoint certs, which does not work for etcd servers using their hostname or IP as the Subject Alternative Name
// in their TLS certs.
// To enable, include both WithTransportCredentials(creds) and WithContextDialer(creds.Dialer)
// when dialing.
type transportCredential struct {
gtc grpccredentials.TransportCredentials
mu sync.Mutex
// addrToEndpoint maps from the connection addresses that are dialed to the hostname or IP of the
// endpoint provided to the dialer when dialing
addrToEndpoint map[string]string
}
func newTransportCredential(cfg *tls.Config) *transportCredential {
return &transportCredential{
gtc: grpccredentials.NewTLS(cfg),
gtc: grpccredentials.NewTLS(cfg),
addrToEndpoint: map[string]string{},
}
}
func (tc *transportCredential) ClientHandshake(ctx context.Context, authority string, rawConn net.Conn) (net.Conn, grpccredentials.AuthInfo, error) {
// Only overwrite when authority is an IP address!
// Let's say, a server runs SRV records on "etcd.local" that resolves
// to "m1.etcd.local", and its SAN field also includes "m1.etcd.local".
// But what if SAN does not include its resolved IP address (e.g. 127.0.0.1)?
// Then, the server should only authenticate using its DNS hostname "m1.etcd.local",
// instead of overwriting it with its IP address.
// And we do not overwrite "localhost" either. Only overwrite IP addresses!
if isIP(authority) {
target := rawConn.RemoteAddr().String()
if authority != target {
// When user dials with "grpc.WithDialer", "grpc.DialContext" "cc.parsedTarget"
// update only happens once. This is problematic, because when TLS is enabled,
// retries happen through "grpc.WithDialer" with static "cc.parsedTarget" from
// the initial dial call.
// If the server authenticates by IP addresses, we want to set a new endpoint as
// a new authority. Otherwise
// "transport: authentication handshake failed: x509: certificate is valid for 127.0.0.1, 192.168.121.180, not 192.168.223.156"
// when the new dial target is "192.168.121.180" whose certificate host name is also "192.168.121.180"
// but client tries to authenticate with previously set "cc.parsedTarget" field "192.168.223.156"
authority = target
}
// Set the authority when checking the endpoint's cert to the hostname or IP of the dialed endpoint
tc.mu.Lock()
dialEp, ok := tc.addrToEndpoint[rawConn.RemoteAddr().String()]
tc.mu.Unlock()
if ok {
_, host, _ := endpoint.ParseEndpoint(dialEp)
authority = host
}
return tc.gtc.ClientHandshake(ctx, authority, rawConn)
}
@@ -115,8 +115,15 @@ func (tc *transportCredential) Info() grpccredentials.ProtocolInfo {
}
func (tc *transportCredential) Clone() grpccredentials.TransportCredentials {
copy := map[string]string{}
tc.mu.Lock()
for k, v := range tc.addrToEndpoint {
copy[k] = v
}
tc.mu.Unlock()
return &transportCredential{
gtc: tc.gtc.Clone(),
gtc: tc.gtc.Clone(),
addrToEndpoint: copy,
}
}
@@ -124,6 +131,17 @@ func (tc *transportCredential) OverrideServerName(serverNameOverride string) err
return tc.gtc.OverrideServerName(serverNameOverride)
}
func (tc *transportCredential) Dialer(ctx context.Context, dialEp string) (net.Conn, error) {
// Keep track of which addresses are dialed for which endpoints
conn, err := endpoint.Dialer(ctx, dialEp)
if conn != nil {
tc.mu.Lock()
tc.addrToEndpoint[conn.RemoteAddr().String()] = dialEp
tc.mu.Unlock()
}
return conn, err
}
// perRPCCredential implements "grpccredentials.PerRPCCredentials" interface.
type perRPCCredential struct {
authToken string

64
clientv3/ctx.go Normal file
View File

@@ -0,0 +1,64 @@
// 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 clientv3
import (
"context"
"strings"
"go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes"
"go.etcd.io/etcd/version"
"google.golang.org/grpc/metadata"
)
// WithRequireLeader requires client requests to only succeed
// when the cluster has a leader.
func WithRequireLeader(ctx context.Context) context.Context {
md, ok := metadata.FromOutgoingContext(ctx)
if !ok { // no outgoing metadata ctx key, create one
md = metadata.Pairs(rpctypes.MetadataRequireLeaderKey, rpctypes.MetadataHasLeader)
return metadata.NewOutgoingContext(ctx, md)
}
copied := md.Copy() // avoid racey updates
// overwrite/add 'hasleader' key/value
metadataSet(copied, rpctypes.MetadataRequireLeaderKey, rpctypes.MetadataHasLeader)
return metadata.NewOutgoingContext(ctx, copied)
}
// embeds client version
func withVersion(ctx context.Context) context.Context {
md, ok := metadata.FromOutgoingContext(ctx)
if !ok { // no outgoing metadata ctx key, create one
md = metadata.Pairs(rpctypes.MetadataClientAPIVersionKey, version.APIVersion)
return metadata.NewOutgoingContext(ctx, md)
}
copied := md.Copy() // avoid racey updates
// overwrite/add version key/value
metadataSet(copied, rpctypes.MetadataClientAPIVersionKey, version.APIVersion)
return metadata.NewOutgoingContext(ctx, copied)
}
func metadataGet(md metadata.MD, k string) []string {
k = strings.ToLower(k)
return md[k]
}
func metadataSet(md metadata.MD, k string, vals ...string) {
if len(vals) == 0 {
return
}
k = strings.ToLower(k)
md[k] = vals
}

67
clientv3/ctx_test.go Normal file
View File

@@ -0,0 +1,67 @@
// 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 clientv3
import (
"context"
"reflect"
"testing"
"go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes"
"go.etcd.io/etcd/version"
"google.golang.org/grpc/metadata"
)
func TestMetadataWithRequireLeader(t *testing.T) {
ctx := context.TODO()
md, ok := metadata.FromOutgoingContext(ctx)
if ok {
t.Fatal("expected no outgoing metadata ctx key")
}
// add a conflicting key with some other value
md = metadata.Pairs(rpctypes.MetadataRequireLeaderKey, "invalid")
// add a key, and expect not be overwritten
metadataSet(md, "hello", "1", "2")
ctx = metadata.NewOutgoingContext(ctx, md)
// expect overwrites but still keep other keys
ctx = WithRequireLeader(ctx)
md, ok = metadata.FromOutgoingContext(ctx)
if !ok {
t.Fatal("expected outgoing metadata ctx key")
}
if ss := metadataGet(md, rpctypes.MetadataRequireLeaderKey); !reflect.DeepEqual(ss, []string{rpctypes.MetadataHasLeader}) {
t.Fatalf("unexpected metadata for %q %v", rpctypes.MetadataRequireLeaderKey, ss)
}
if ss := metadataGet(md, "hello"); !reflect.DeepEqual(ss, []string{"1", "2"}) {
t.Fatalf("unexpected metadata for 'hello' %v", ss)
}
}
func TestMetadataWithClientAPIVersion(t *testing.T) {
ctx := withVersion(WithRequireLeader(context.TODO()))
md, ok := metadata.FromOutgoingContext(ctx)
if !ok {
t.Fatal("expected outgoing metadata ctx key")
}
if ss := metadataGet(md, rpctypes.MetadataRequireLeaderKey); !reflect.DeepEqual(ss, []string{rpctypes.MetadataHasLeader}) {
t.Fatalf("unexpected metadata for %q %v", rpctypes.MetadataRequireLeaderKey, ss)
}
if ss := metadataGet(md, rpctypes.MetadataClientAPIVersionKey); !reflect.DeepEqual(ss, []string{version.APIVersion}) {
t.Fatalf("unexpected metadata for %q %v", rpctypes.MetadataClientAPIVersionKey, ss)
}
}

View File

@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !cluster_proxy
// +build !cluster_proxy
package integration

View File

@@ -619,16 +619,28 @@ func TestLeasingTxnOwnerGet(t *testing.T) {
clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1})
defer clus.Terminate(t)
client := clus.Client(0)
lkv, closeLKV, err := leasing.NewKV(clus.Client(0), "pfx/")
testutil.AssertNil(t, err)
defer closeLKV()
defer func() {
// In '--tags cluster_proxy' mode the client need to be closed before
// closeLKV(). This interrupts all outstanding watches. Closing by closeLKV()
// is not sufficient as (unfortunately) context close does not interrupts Watches.
// See ./clientv3/watch.go:
// >> Currently, client contexts are overwritten with "valCtx" that never closes. <<
clus.TakeClient(0) // avoid double Close() of the client.
client.Close()
closeLKV()
}()
keyCount := rand.Intn(10) + 1
var ops []clientv3.Op
presps := make([]*clientv3.PutResponse, keyCount)
for i := range presps {
k := fmt.Sprintf("k-%d", i)
presp, err := clus.Client(0).Put(context.TODO(), k, k+k)
presp, err := client.Put(context.TODO(), k, k+k)
if err != nil {
t.Fatal(err)
}

View File

@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !cluster_proxy
// +build !cluster_proxy
package integration

View File

@@ -65,8 +65,8 @@ func TestUserErrorAuth(t *testing.T) {
authSetupRoot(t, authapi.Auth)
// unauthenticated client
if _, err := authapi.UserAdd(context.TODO(), "foo", "bar"); err != rpctypes.ErrUserNotFound {
t.Fatalf("expected %v, got %v", rpctypes.ErrUserNotFound, err)
if _, err := authapi.UserAdd(context.TODO(), "foo", "bar"); err != rpctypes.ErrUserEmpty {
t.Fatalf("expected %v, got %v", rpctypes.ErrUserEmpty, err)
}
// wrong id or password
@@ -114,7 +114,7 @@ func authSetupRoot(t *testing.T, auth clientv3.Auth) {
func TestGetTokenWithoutAuth(t *testing.T) {
defer testutil.AfterTest(t)
clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 10})
clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 2})
defer clus.Terminate(t)
authapi := clus.RandClient()
@@ -130,7 +130,7 @@ func TestGetTokenWithoutAuth(t *testing.T) {
// "Username" and "Password" must be used
cfg := clientv3.Config{
Endpoints: authapi.Endpoints(),
DialTimeout: 1 * time.Second, // make sure all connection time of connect all endpoint must be more DialTimeout
DialTimeout: 5 * time.Second,
Username: "root",
Password: "123",
}
@@ -142,7 +142,7 @@ func TestGetTokenWithoutAuth(t *testing.T) {
switch err {
case nil:
t.Log("passes as expected, but may be connection time less than DialTimeout")
t.Log("passes as expected")
case context.DeadlineExceeded:
t.Errorf("not expected result:%v with endpoint:%s", err, authapi.Endpoints())
case rpctypes.ErrAuthNotEnabled:
@@ -150,5 +150,4 @@ func TestGetTokenWithoutAuth(t *testing.T) {
default:
t.Errorf("other errors:%v", err)
}
}

View File

@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !cluster_proxy
// +build !cluster_proxy
package integration

View File

@@ -338,6 +338,9 @@ func putAndWatch(t *testing.T, wctx *watchctx, key, val string) {
if !ok {
t.Fatalf("unexpected watch close")
}
if err := v.Err(); err != nil {
t.Fatalf("unexpected watch response error: %v", err)
}
if string(v.Events[0].Kv.Value) != val {
t.Fatalf("bad value got %v, wanted %v", v.Events[0].Kv.Value, val)
}
@@ -582,7 +585,34 @@ func testWatchWithProgressNotify(t *testing.T, watchOnPut bool) {
}
}
func TestConfigurableWatchProgressNotifyInterval(t *testing.T) {
progressInterval := 200 * time.Millisecond
clus := integration.NewClusterV3(t,
&integration.ClusterConfig{
Size: 3,
WatchProgressNotifyInterval: progressInterval,
})
defer clus.Terminate(t)
opts := []clientv3.OpOption{clientv3.WithProgressNotify()}
rch := clus.RandClient().Watch(context.Background(), "foo", opts...)
timeout := 1 * time.Second // we expect to receive watch progress notify in 2 * progressInterval,
// but for CPU-starved situation it may take longer. So we use 1 second here for timeout.
select {
case resp := <-rch: // waiting for a watch progress notify response
if !resp.IsProgressNotify() {
t.Fatalf("expected resp.IsProgressNotify() == true")
}
case <-time.After(timeout):
t.Fatalf("timed out waiting for watch progress notify response in %v", timeout)
}
}
func TestWatchRequestProgress(t *testing.T) {
if integration.ThroughProxy {
t.Skip("grpc-proxy does not support WatchProgress yet")
}
testCases := []struct {
name string
watchers []string

View File

@@ -20,6 +20,7 @@ import (
"io"
pb "go.etcd.io/etcd/etcdserver/etcdserverpb"
"go.uber.org/zap"
"google.golang.org/grpc"
)
@@ -68,6 +69,7 @@ type Maintenance interface {
}
type maintenance struct {
lg *zap.Logger
dial func(endpoint string) (pb.MaintenanceClient, func(), error)
remote pb.MaintenanceClient
callOpts []grpc.CallOption
@@ -75,6 +77,7 @@ type maintenance struct {
func NewMaintenance(c *Client) Maintenance {
api := &maintenance{
lg: c.lg,
dial: func(endpoint string) (pb.MaintenanceClient, func(), error) {
conn, err := c.Dial(endpoint)
if err != nil {
@@ -93,6 +96,7 @@ func NewMaintenance(c *Client) Maintenance {
func NewMaintenanceFromMaintenanceClient(remote pb.MaintenanceClient, c *Client) Maintenance {
api := &maintenance{
lg: c.lg,
dial: func(string) (pb.MaintenanceClient, func(), error) {
return remote, func() {}, nil
},
@@ -193,23 +197,32 @@ func (m *maintenance) Snapshot(ctx context.Context) (io.ReadCloser, error) {
return nil, toErr(ctx, err)
}
m.lg.Info("opened snapshot stream; downloading")
pr, pw := io.Pipe()
go func() {
for {
resp, err := ss.Recv()
if err != nil {
switch err {
case io.EOF:
m.lg.Info("completed snapshot read; closing")
default:
m.lg.Warn("failed to receive from snapshot stream; closing", zap.Error(err))
}
pw.CloseWithError(err)
return
}
if resp == nil && err == nil {
break
}
// can "resp == nil && err == nil"
// before we receive snapshot SHA digest?
// No, server sends EOF with an empty response
// after it sends SHA digest at the end
if _, werr := pw.Write(resp.Blob); werr != nil {
pw.CloseWithError(werr)
return
}
}
pw.Close()
}()
return &snapshotReadCloser{ctx: ctx, ReadCloser: pr}, nil
}

View File

@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
package naming
package naming_test
import (
"context"
@@ -21,6 +21,7 @@ import (
"testing"
etcd "go.etcd.io/etcd/clientv3"
namingv3 "go.etcd.io/etcd/clientv3/naming"
"go.etcd.io/etcd/integration"
"go.etcd.io/etcd/pkg/testutil"
@@ -33,7 +34,7 @@ func TestGRPCResolver(t *testing.T) {
clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1})
defer clus.Terminate(t)
r := GRPCResolver{
r := namingv3.GRPCResolver{
Client: clus.RandClient(),
}
@@ -107,7 +108,7 @@ func TestGRPCResolverMulti(t *testing.T) {
t.Fatal(err)
}
r := GRPCResolver{c}
r := namingv3.GRPCResolver{c}
w, err := r.Resolve("foo")
if err != nil {

View File

@@ -77,6 +77,9 @@ type Op struct {
cmps []Cmp
thenOps []Op
elseOps []Op
isOptsWithFromKey bool
isOptsWithPrefix bool
}
// accessors / mutators
@@ -216,6 +219,10 @@ func (op Op) isWrite() bool {
return op.t != tRange
}
func NewOp() *Op {
return &Op{key: []byte("")}
}
// OpGet returns "get" operation based on given key and operation options.
func OpGet(key string, opts ...OpOption) Op {
// WithPrefix and WithFromKey are not supported together
@@ -387,6 +394,7 @@ func WithPrefix() OpOption {
return
}
op.end = getPrefix(op.key)
op.isOptsWithPrefix = true
}
}
@@ -406,6 +414,7 @@ func WithFromKey() OpOption {
op.key = []byte{0}
}
op.end = []byte("\x00")
op.isOptsWithFromKey = true
}
}
@@ -554,7 +563,21 @@ func toLeaseTimeToLiveRequest(id LeaseID, opts ...LeaseOption) *pb.LeaseTimeToLi
}
// isWithPrefix returns true if WithPrefix is being called in the op
func isWithPrefix(opts []OpOption) bool { return isOpFuncCalled("WithPrefix", opts) }
func isWithPrefix(opts []OpOption) bool {
ret := NewOp()
for _, opt := range opts {
opt(ret)
}
return ret.isOptsWithPrefix
}
// isWithFromKey returns true if WithFromKey is being called in the op
func isWithFromKey(opts []OpOption) bool { return isOpFuncCalled("WithFromKey", opts) }
func isWithFromKey(opts []OpOption) bool {
ret := NewOp()
for _, opt := range opts {
opt(ret)
}
return ret.isOptsWithFromKey
}

View File

@@ -16,8 +16,7 @@ package ordering
import (
"errors"
"sync"
"time"
"sync/atomic"
"go.etcd.io/etcd/clientv3"
)
@@ -26,26 +25,18 @@ type OrderViolationFunc func(op clientv3.Op, resp clientv3.OpResponse, prevRev i
var ErrNoGreaterRev = errors.New("etcdclient: no cluster members have a revision higher than the previously received revision")
func NewOrderViolationSwitchEndpointClosure(c clientv3.Client) OrderViolationFunc {
var mu sync.Mutex
violationCount := 0
return func(op clientv3.Op, resp clientv3.OpResponse, prevRev int64) error {
if violationCount > len(c.Endpoints()) {
func NewOrderViolationSwitchEndpointClosure(c *clientv3.Client) OrderViolationFunc {
violationCount := int32(0)
return func(_ clientv3.Op, _ clientv3.OpResponse, _ int64) error {
// Each request is assigned by round-robin load-balancer's picker to a different
// endpoints. If we cycled them 5 times (even with some level of concurrency),
// with high probability no endpoint points on a member with fresh data.
// TODO: Ideally we should track members (resp.opp.Header) that returned
// stale result and explicitly temporarily disable them in 'picker'.
if atomic.LoadInt32(&violationCount) > int32(5*len(c.Endpoints())) {
return ErrNoGreaterRev
}
mu.Lock()
defer mu.Unlock()
eps := c.Endpoints()
// force client to connect to given endpoint by limiting to a single endpoint
c.SetEndpoints(eps[violationCount%len(eps)])
// give enough time for operation
time.Sleep(1 * time.Second)
// set available endpoints back to all endpoints in to ensure
// the client has access to all the endpoints.
c.SetEndpoints(eps...)
// give enough time for operation
time.Sleep(1 * time.Second)
violationCount++
atomic.AddInt32(&violationCount, 1)
return nil
}
}

View File

@@ -64,19 +64,19 @@ func TestEndpointSwitchResolvesViolation(t *testing.T) {
// NewOrderViolationSwitchEndpointClosure will be able to
// access the full list of endpoints.
cli.SetEndpoints(eps...)
OrderingKv := NewKV(cli.KV, NewOrderViolationSwitchEndpointClosure(*cli))
orderingKv := NewKV(cli.KV, NewOrderViolationSwitchEndpointClosure(cli))
// set prevRev to the second member's revision of "foo" such that
// the revision is higher than the third member's revision of "foo"
_, err = OrderingKv.Get(ctx, "foo")
_, err = orderingKv.Get(ctx, "foo")
if err != nil {
t.Fatal(err)
}
t.Logf("Reconfigure client to speak only to the 'partitioned' member")
cli.SetEndpoints(clus.Members[2].GRPCAddr())
time.Sleep(1 * time.Second) // give enough time for operation
_, err = OrderingKv.Get(ctx, "foo", clientv3.WithSerializable())
if err != nil {
t.Fatalf("failed to resolve order violation %v", err)
_, err = orderingKv.Get(ctx, "foo", clientv3.WithSerializable())
if err != ErrNoGreaterRev {
t.Fatal("While speaking to partitioned leader, we should get ErrNoGreaterRev error")
}
}
@@ -123,7 +123,7 @@ func TestUnresolvableOrderViolation(t *testing.T) {
// access the full list of endpoints.
cli.SetEndpoints(eps...)
time.Sleep(1 * time.Second) // give enough time for operation
OrderingKv := NewKV(cli.KV, NewOrderViolationSwitchEndpointClosure(*cli))
OrderingKv := NewKV(cli.KV, NewOrderViolationSwitchEndpointClosure(cli))
// set prevRev to the first member's revision of "foo" such that
// the revision is higher than the fourth and fifth members' revision of "foo"
_, err = OrderingKv.Get(ctx, "foo")

View File

@@ -38,6 +38,7 @@ import (
func (c *Client) unaryClientInterceptor(logger *zap.Logger, optFuncs ...retryOption) grpc.UnaryClientInterceptor {
intOpts := reuseOrNewWithCallOptions(defaultOptions, optFuncs)
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
ctx = withVersion(ctx)
grpcOpts, retryOpts := filterCallOptions(opts)
callOpts := reuseOrNewWithCallOptions(intOpts, retryOpts)
// short circuit for simplicity, and avoiding allocations.
@@ -72,8 +73,8 @@ func (c *Client) unaryClientInterceptor(logger *zap.Logger, optFuncs ...retryOpt
// its the callCtx deadline or cancellation, in which case try again.
continue
}
if callOpts.retryAuth && rpctypes.Error(lastErr) == rpctypes.ErrInvalidAuthToken {
gterr := c.getToken(ctx)
if c.shouldRefreshToken(lastErr, callOpts) {
gterr := c.refreshToken(ctx)
if gterr != nil {
logger.Warn(
"retrying of unary invoker failed to fetch new auth token",
@@ -103,6 +104,17 @@ func (c *Client) unaryClientInterceptor(logger *zap.Logger, optFuncs ...retryOpt
func (c *Client) streamClientInterceptor(logger *zap.Logger, optFuncs ...retryOption) grpc.StreamClientInterceptor {
intOpts := reuseOrNewWithCallOptions(defaultOptions, optFuncs)
return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) {
ctx = withVersion(ctx)
// getToken automatically
// TODO(cfc4n): keep this code block, remove codes about getToken in client.go after pr #12165 merged.
if c.authTokenBundle != nil {
// equal to c.Username != "" && c.Password != ""
err := c.getToken(ctx)
if err != nil && rpctypes.Error(err) != rpctypes.ErrAuthNotEnabled {
logger.Error("clientv3/retry_interceptor: getToken failed", zap.Error(err))
return nil, err
}
}
grpcOpts, retryOpts := filterCallOptions(opts)
callOpts := reuseOrNewWithCallOptions(intOpts, retryOpts)
// short circuit for simplicity, and avoiding allocations.
@@ -113,10 +125,9 @@ func (c *Client) streamClientInterceptor(logger *zap.Logger, optFuncs ...retryOp
return nil, status.Errorf(codes.Unimplemented, "clientv3/retry_interceptor: cannot retry on ClientStreams, set Disable()")
}
newStreamer, err := streamer(ctx, desc, cc, method, grpcOpts...)
logger.Warn("retry stream intercept", zap.Error(err))
if err != nil {
// TODO(mwitkow): Maybe dial and transport errors should be retriable?
return nil, err
logger.Error("streamer failed to create ClientStream", zap.Error(err))
return nil, err // TODO(mwitkow): Maybe dial and transport errors should be retriable?
}
retryingStreamer := &serverStreamingRetryingStream{
client: c,
@@ -131,6 +142,37 @@ func (c *Client) streamClientInterceptor(logger *zap.Logger, optFuncs ...retryOp
}
}
// shouldRefreshToken checks whether there's a need to refresh the token based on the error and callOptions,
// and returns a boolean value.
func (c *Client) shouldRefreshToken(err error, callOpts *options) bool {
if rpctypes.Error(err) == rpctypes.ErrUserEmpty {
// refresh the token when username, password is present but the server returns ErrUserEmpty
// which is possible when the client token is cleared somehow
return c.authTokenBundle != nil // equal to c.Username != "" && c.Password != ""
}
return callOpts.retryAuth &&
(rpctypes.Error(err) == rpctypes.ErrInvalidAuthToken || rpctypes.Error(err) == rpctypes.ErrAuthOldRevision)
}
func (c *Client) refreshToken(ctx context.Context) error {
if c.authTokenBundle == nil {
// c.authTokenBundle will be initialized only when
// c.Username != "" && c.Password != "".
//
// When users use the TLS CommonName based authentication, the
// authTokenBundle is always nil. But it's possible for the clients
// to get `rpctypes.ErrAuthOldRevision` response when the clients
// concurrently modify auth data (e.g, addUser, deleteUser etc.).
// In this case, there is no need to refresh the token; instead the
// clients just need to retry the operations (e.g. Put, Delete etc).
return nil
}
// clear auth token before refreshing it.
c.authTokenBundle.UpdateAuthToken("")
return c.getToken(ctx)
}
// type serverStreamingRetryingStream is the implementation of grpc.ClientStream that acts as a
// proxy to the underlying call. If any of the RecvMsg() calls fail, it will try to reestablish
// a new ClientStream according to the retry policy.
@@ -185,6 +227,7 @@ func (s *serverStreamingRetryingStream) RecvMsg(m interface{}) error {
if !attemptRetry {
return lastErr // success or hard failure
}
// We start off from attempt 1, because zeroth was already made on normal SendMsg().
for attempt := uint(1); attempt < s.callOpts.max; attempt++ {
if err := waitRetryBackoff(s.ctx, attempt, s.callOpts); err != nil {
@@ -192,12 +235,13 @@ func (s *serverStreamingRetryingStream) RecvMsg(m interface{}) error {
}
newStream, err := s.reestablishStreamAndResendBuffer(s.ctx)
if err != nil {
// TODO(mwitkow): Maybe dial and transport errors should be retriable?
return err
s.client.lg.Error("failed reestablishStreamAndResendBuffer", zap.Error(err))
return err // TODO(mwitkow): Maybe dial and transport errors should be retriable?
}
s.setStream(newStream)
s.client.lg.Warn("retrying RecvMsg", zap.Error(lastErr))
attemptRetry, lastErr = s.receiveMsgAndIndicateRetry(m)
//fmt.Printf("Received message and indicate: %v %v\n", attemptRetry, lastErr)
if !attemptRetry {
return lastErr
}
@@ -226,8 +270,8 @@ func (s *serverStreamingRetryingStream) receiveMsgAndIndicateRetry(m interface{}
// its the callCtx deadline or cancellation, in which case try again.
return true, err
}
if s.callOpts.retryAuth && rpctypes.Error(err) == rpctypes.ErrInvalidAuthToken {
gterr := s.client.getToken(s.ctx)
if s.client.shouldRefreshToken(err, s.callOpts) {
gterr := s.client.refreshToken(s.ctx)
if gterr != nil {
s.client.lg.Warn("retry failed to fetch new auth token", zap.Error(gterr))
return false, err // return the original error for simplicity

View File

@@ -0,0 +1,141 @@
// Copyright 2022 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.
// Based on github.com/grpc-ecosystem/go-grpc-middleware/retry, but modified to support the more
// fine grained error checking required by write-at-most-once retry semantics of etcd.
package clientv3
import (
"go.etcd.io/etcd/clientv3/credentials"
"go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes"
grpccredentials "google.golang.org/grpc/credentials"
"testing"
)
type dummyAuthTokenBundle struct{}
func (d dummyAuthTokenBundle) TransportCredentials() grpccredentials.TransportCredentials {
return nil
}
func (d dummyAuthTokenBundle) PerRPCCredentials() grpccredentials.PerRPCCredentials {
return nil
}
func (d dummyAuthTokenBundle) NewWithMode(mode string) (grpccredentials.Bundle, error) {
return nil, nil
}
func (d dummyAuthTokenBundle) UpdateAuthToken(token string) {
}
func TestClientShouldRefreshToken(t *testing.T) {
type fields struct {
authTokenBundle credentials.Bundle
}
type args struct {
err error
callOpts *options
}
optsWithTrue := &options{
retryAuth: true,
}
optsWithFalse := &options{
retryAuth: false,
}
tests := []struct {
name string
fields fields
args args
want bool
}{
{
name: "ErrUserEmpty and non nil authTokenBundle",
fields: fields{
authTokenBundle: &dummyAuthTokenBundle{},
},
args: args{rpctypes.ErrGRPCUserEmpty, optsWithTrue},
want: true,
},
{
name: "ErrUserEmpty and nil authTokenBundle",
fields: fields{
authTokenBundle: nil,
},
args: args{rpctypes.ErrGRPCUserEmpty, optsWithTrue},
want: false,
},
{
name: "ErrGRPCInvalidAuthToken and retryAuth",
fields: fields{
authTokenBundle: nil,
},
args: args{rpctypes.ErrGRPCInvalidAuthToken, optsWithTrue},
want: true,
},
{
name: "ErrGRPCInvalidAuthToken and !retryAuth",
fields: fields{
authTokenBundle: nil,
},
args: args{rpctypes.ErrGRPCInvalidAuthToken, optsWithFalse},
want: false,
},
{
name: "ErrGRPCAuthOldRevision and retryAuth",
fields: fields{
authTokenBundle: nil,
},
args: args{rpctypes.ErrGRPCAuthOldRevision, optsWithTrue},
want: true,
},
{
name: "ErrGRPCAuthOldRevision and !retryAuth",
fields: fields{
authTokenBundle: nil,
},
args: args{rpctypes.ErrGRPCAuthOldRevision, optsWithFalse},
want: false,
},
{
name: "Other error and retryAuth",
fields: fields{
authTokenBundle: nil,
},
args: args{rpctypes.ErrGRPCAuthFailed, optsWithTrue},
want: false,
},
{
name: "Other error and !retryAuth",
fields: fields{
authTokenBundle: nil,
},
args: args{rpctypes.ErrGRPCAuthFailed, optsWithFalse},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
c := &Client{
authTokenBundle: tt.fields.authTokenBundle,
}
if got := c.shouldRefreshToken(tt.args.err, tt.args.callOpts); got != tt.want {
t.Errorf("shouldRefreshToken() = %v, want %v", got, tt.want)
}
})
}
}

View File

@@ -28,6 +28,7 @@ import (
"strings"
"time"
"github.com/dustin/go-humanize"
bolt "go.etcd.io/bbolt"
"go.etcd.io/etcd/clientv3"
"go.etcd.io/etcd/etcdserver"
@@ -39,6 +40,7 @@ import (
"go.etcd.io/etcd/mvcc"
"go.etcd.io/etcd/mvcc/backend"
"go.etcd.io/etcd/pkg/fileutil"
"go.etcd.io/etcd/pkg/traceutil"
"go.etcd.io/etcd/pkg/types"
"go.etcd.io/etcd/raft"
"go.etcd.io/etcd/raft/raftpb"
@@ -87,6 +89,14 @@ type v3Manager struct {
skipHashCheck bool
}
// hasChecksum returns "true" if the file size "n"
// has appended sha256 hash digest.
func hasChecksum(n int64) bool {
// 512 is chosen because it's a minimum disk sector size
// smaller than (and multiplies to) OS page size in most systems
return (n % 512) == sha256.Size
}
// Save fetches snapshot from remote etcd server and saves data to target path.
func (s *v3Manager) Save(ctx context.Context, cfg clientv3.Config, dbPath string) error {
if len(cfg.Endpoints) != 1 {
@@ -106,10 +116,7 @@ func (s *v3Manager) Save(ctx context.Context, cfg clientv3.Config, dbPath string
if err != nil {
return fmt.Errorf("could not open %s (%v)", partpath, err)
}
s.lg.Info(
"created temporary db file",
zap.String("path", partpath),
)
s.lg.Info("created temporary db file", zap.String("path", partpath))
now := time.Now()
var rd io.ReadCloser
@@ -117,13 +124,15 @@ func (s *v3Manager) Save(ctx context.Context, cfg clientv3.Config, dbPath string
if err != nil {
return err
}
s.lg.Info(
"fetching snapshot",
zap.String("endpoint", cfg.Endpoints[0]),
)
if _, err = io.Copy(f, rd); err != nil {
s.lg.Info("fetching snapshot", zap.String("endpoint", cfg.Endpoints[0]))
var size int64
size, err = io.Copy(f, rd)
if err != nil {
return err
}
if !hasChecksum(size) {
return fmt.Errorf("sha256 checksum not found [bytes: %d]", size)
}
if err = fileutil.Fsync(f); err != nil {
return err
}
@@ -133,6 +142,7 @@ func (s *v3Manager) Save(ctx context.Context, cfg clientv3.Config, dbPath string
s.lg.Info(
"fetched snapshot",
zap.String("endpoint", cfg.Endpoints[0]),
zap.String("size", humanize.Bytes(uint64(size))),
zap.Duration("took", time.Since(now)),
)
@@ -345,7 +355,7 @@ func (s *v3Manager) saveDB() error {
if serr != nil {
return serr
}
hasHash := (off % 512) == sha256.Size
hasHash := hasChecksum(off)
if hasHash {
if err := db.Truncate(off - sha256.Size); err != nil {
return err
@@ -381,10 +391,10 @@ func (s *v3Manager) saveDB() error {
be := backend.NewDefaultBackend(dbpath)
// a lessor never timeouts leases
lessor := lease.NewLessor(s.lg, be, lease.LessorConfig{MinLeaseTTL: math.MaxInt64})
lessor := lease.NewLessor(s.lg, be, nil, lease.LessorConfig{MinLeaseTTL: math.MaxInt64})
mvs := mvcc.NewStore(s.lg, be, lessor, (*initIndex)(&commit), mvcc.StoreConfig{CompactionBatchLimit: math.MaxInt32})
txn := mvs.Write()
txn := mvs.Write(traceutil.TODO())
btx := be.BatchTx()
del := func(k, v []byte) error {
txn.DeleteRange(k, nil)

View File

@@ -16,9 +16,6 @@ package clientv3
import (
"math/rand"
"reflect"
"runtime"
"strings"
"time"
)
@@ -32,18 +29,3 @@ func jitterUp(duration time.Duration, jitter float64) time.Duration {
multiplier := jitter * (rand.Float64()*2 - 1)
return time.Duration(float64(duration) * (1 + multiplier))
}
// Check if the provided function is being called in the op options.
func isOpFuncCalled(op string, opts []OpOption) bool {
for _, opt := range opts {
v := reflect.ValueOf(opt)
if v.Kind() == reflect.Func {
if opFunc := runtime.FuncForPC(v.Pointer()); opFunc != nil {
if strings.Contains(opFunc.Name(), op) {
return true
}
}
}
}
return false
}

View File

@@ -18,6 +18,7 @@ import (
"context"
"errors"
"fmt"
"strings"
"sync"
"time"
@@ -25,6 +26,7 @@ import (
pb "go.etcd.io/etcd/etcdserver/etcdserverpb"
mvccpb "go.etcd.io/etcd/mvcc/mvccpb"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
@@ -36,6 +38,13 @@ const (
EventTypePut = mvccpb.PUT
closeSendErrTimeout = 250 * time.Millisecond
// AutoWatchID is the watcher ID passed in WatchStream.Watch when no
// user-provided ID is available. If pass, an ID will automatically be assigned.
AutoWatchID = 0
// InvalidWatchID represents an invalid watch ID and prevents duplication with an existing watch.
InvalidWatchID = -1
)
type Event mvccpb.Event
@@ -140,6 +149,7 @@ type watcher struct {
// streams holds all the active grpc streams keyed by ctx value.
streams map[string]*watchGrpcStream
lg *zap.Logger
}
// watchGrpcStream tracks all watch resources attached to a single grpc stream.
@@ -176,6 +186,8 @@ type watchGrpcStream struct {
resumec chan struct{}
// closeErr is the error that closed the watch stream
closeErr error
lg *zap.Logger
}
// watchStreamRequest is a union of the supported watch request operation types
@@ -242,6 +254,7 @@ func NewWatchFromWatchClient(wc pb.WatchClient, c *Client) Watcher {
}
if c != nil {
w.callOpts = c.callOpts
w.lg = c.lg
}
return w
}
@@ -273,6 +286,7 @@ func (w *watcher) newWatcherGrpcStream(inctx context.Context) *watchGrpcStream {
errc: make(chan error, 1),
closingc: make(chan *watcherStream),
resumec: make(chan struct{}),
lg: w.lg,
}
go wgs.run()
return wgs
@@ -437,7 +451,7 @@ func (w *watcher) closeStream(wgs *watchGrpcStream) {
func (w *watchGrpcStream) addSubstream(resp *pb.WatchResponse, ws *watcherStream) {
// check watch ID for backward compatibility (<= v3.3)
if resp.WatchId == -1 || (resp.Canceled && resp.CancelReason != "") {
if resp.WatchId == InvalidWatchID || (resp.Canceled && resp.CancelReason != "") {
w.closeErr = v3rpc.Error(errors.New(resp.CancelReason))
// failed; no channel
close(ws.recvc)
@@ -468,7 +482,7 @@ func (w *watchGrpcStream) closeSubstream(ws *watcherStream) {
} else if ws.outc != nil {
close(ws.outc)
}
if ws.id != -1 {
if ws.id != InvalidWatchID {
delete(w.substreams, ws.id)
return
}
@@ -520,6 +534,7 @@ func (w *watchGrpcStream) run() {
cancelSet := make(map[int64]struct{})
var cur *pb.WatchResponse
backoff := time.Millisecond
for {
select {
// Watch() requested
@@ -530,7 +545,7 @@ func (w *watchGrpcStream) run() {
// TODO: pass custom watch ID?
ws := &watcherStream{
initReq: *wreq,
id: -1,
id: InvalidWatchID,
outc: outc,
// unbuffered so resumes won't cause repeat events
recvc: make(chan *WatchResponse),
@@ -544,10 +559,18 @@ func (w *watchGrpcStream) run() {
w.resuming = append(w.resuming, ws)
if len(w.resuming) == 1 {
// head of resume queue, can register a new watcher
wc.Send(ws.initReq.toPB())
if err := wc.Send(ws.initReq.toPB()); err != nil {
if w.lg != nil {
w.lg.Debug("error when sending request", zap.Error(err))
}
}
}
case *progressRequest:
wc.Send(wreq.toPB())
if err := wc.Send(wreq.toPB()); err != nil {
if w.lg != nil {
w.lg.Debug("error when sending request", zap.Error(err))
}
}
}
// new events from the watch client
@@ -563,6 +586,26 @@ func (w *watchGrpcStream) run() {
switch {
case pbresp.Created:
cancelReasonError := v3rpc.Error(errors.New(pbresp.CancelReason))
if shouldRetryWatch(cancelReasonError) {
var newErr error
if wc, newErr = w.newWatchClient(); newErr != nil {
w.lg.Error("failed to create a new watch client", zap.Error(newErr))
return
}
if len(w.resuming) != 0 {
if ws := w.resuming[0]; ws != nil {
if err := wc.Send(ws.initReq.toPB()); err != nil {
w.lg.Debug("error when sending request", zap.Error(err))
}
}
}
cur = nil
continue
}
// response to head of queue creation
if ws := w.resuming[0]; ws != nil {
w.addSubstream(pbresp, ws)
@@ -571,7 +614,11 @@ func (w *watchGrpcStream) run() {
}
if ws := w.nextResume(); ws != nil {
wc.Send(ws.initReq.toPB())
if err := wc.Send(ws.initReq.toPB()); err != nil {
if w.lg != nil {
w.lg.Debug("error when sending request", zap.Error(err))
}
}
}
// reset for next iteration
@@ -616,7 +663,14 @@ func (w *watchGrpcStream) run() {
},
}
req := &pb.WatchRequest{RequestUnion: cr}
wc.Send(req)
if w.lg != nil {
w.lg.Debug("sending watch cancel request for failed dispatch", zap.Int64("watch-id", pbresp.WatchId))
}
if err := wc.Send(req); err != nil {
if w.lg != nil {
w.lg.Debug("failed to send watch cancel request", zap.Int64("watch-id", pbresp.WatchId), zap.Error(err))
}
}
}
// watch client failed on Recv; spawn another if possible
@@ -625,11 +679,16 @@ func (w *watchGrpcStream) run() {
closeErr = err
return
}
backoff = w.backoffIfUnavailable(backoff, err)
if wc, closeErr = w.newWatchClient(); closeErr != nil {
return
}
if ws := w.nextResume(); ws != nil {
wc.Send(ws.initReq.toPB())
if err := wc.Send(ws.initReq.toPB()); err != nil {
if w.lg != nil {
w.lg.Debug("error when sending request", zap.Error(err))
}
}
}
cancelSet = make(map[int64]struct{})
@@ -637,6 +696,25 @@ func (w *watchGrpcStream) run() {
return
case ws := <-w.closingc:
if ws.id != InvalidWatchID {
// client is closing an established watch; close it on the server proactively instead of waiting
// to close when the next message arrives
cancelSet[ws.id] = struct{}{}
cr := &pb.WatchRequest_CancelRequest{
CancelRequest: &pb.WatchCancelRequest{
WatchId: ws.id,
},
}
req := &pb.WatchRequest{RequestUnion: cr}
if w.lg != nil {
w.lg.Debug("sending watch cancel request for closed watcher", zap.Int64("watch-id", ws.id))
}
if err := wc.Send(req); err != nil {
if w.lg != nil {
w.lg.Debug("failed to send watch cancel request", zap.Int64("watch-id", ws.id), zap.Error(err))
}
}
}
w.closeSubstream(ws)
delete(closing, ws)
// no more watchers on this stream, shutdown
@@ -647,6 +725,11 @@ func (w *watchGrpcStream) run() {
}
}
func shouldRetryWatch(cancelReasonError error) bool {
return (strings.Compare(cancelReasonError.Error(), v3rpc.ErrGRPCInvalidAuthToken.Error()) == 0) ||
(strings.Compare(cancelReasonError.Error(), v3rpc.ErrGRPCAuthOldRevision.Error()) == 0)
}
// nextResume chooses the next resuming to register with the grpc stream. Abandoned
// streams are marked as nil in the queue since the head must wait for its inflight registration.
func (w *watchGrpcStream) nextResume() *watcherStream {
@@ -675,9 +758,9 @@ func (w *watchGrpcStream) dispatchEvent(pbresp *pb.WatchResponse) bool {
cancelReason: pbresp.CancelReason,
}
// watch IDs are zero indexed, so request notify watch responses are assigned a watch ID of -1 to
// watch IDs are zero indexed, so request notify watch responses are assigned a watch ID of InvalidWatchID to
// indicate they should be broadcast.
if wr.IsProgressNotify() && pbresp.WatchId == -1 {
if wr.IsProgressNotify() && pbresp.WatchId == InvalidWatchID {
return w.broadcastResponse(wr)
}
@@ -798,7 +881,7 @@ func (w *watchGrpcStream) serveSubstream(ws *watcherStream, resumec chan struct{
}
} else {
// current progress of watch; <= store revision
nextRev = wr.Header.Revision
nextRev = wr.Header.Revision + 1
}
if len(wr.Events) > 0 {
@@ -832,7 +915,7 @@ func (w *watchGrpcStream) newWatchClient() (pb.Watch_WatchClient, error) {
w.resumec = make(chan struct{})
w.joinSubstreams()
for _, ws := range w.substreams {
ws.id = -1
ws.id = InvalidWatchID
w.resuming = append(w.resuming, ws)
}
// strip out nils, if any
@@ -922,6 +1005,21 @@ func (w *watchGrpcStream) joinSubstreams() {
var maxBackoff = 100 * time.Millisecond
func (w *watchGrpcStream) backoffIfUnavailable(backoff time.Duration, err error) time.Duration {
if isUnavailableErr(w.ctx, err) {
// retry, but backoff
if backoff < maxBackoff {
// 25% backoff factor
backoff = backoff + backoff/4
if backoff > maxBackoff {
backoff = maxBackoff
}
}
time.Sleep(backoff)
}
return backoff
}
// openWatchClient retries opening a watch client until success or halt.
// manually retry in case "ws==nil && err==nil"
// TODO: remove FailFast=false
@@ -942,17 +1040,7 @@ func (w *watchGrpcStream) openWatchClient() (ws pb.Watch_WatchClient, err error)
if isHaltErr(w.ctx, err) {
return nil, v3rpc.Error(err)
}
if isUnavailableErr(w.ctx, err) {
// retry, but backoff
if backoff < maxBackoff {
// 25% backoff factor
backoff = backoff + backoff/4
if backoff > maxBackoff {
backoff = maxBackoff
}
}
time.Sleep(backoff)
}
backoff = w.backoffIfUnavailable(backoff, err)
}
return ws, nil
}

View File

@@ -18,6 +18,7 @@ import (
"crypto/tls"
"fmt"
"io/ioutil"
"math"
"net"
"net/http"
"net/url"
@@ -53,7 +54,9 @@ const (
DefaultMaxSnapshots = 5
DefaultMaxWALs = 5
DefaultMaxTxnOps = uint(128)
DefaultWarningApplyDuration = 100 * time.Millisecond
DefaultMaxRequestBytes = 1.5 * 1024 * 1024
DefaultMaxConcurrentStreams = math.MaxUint32
DefaultGRPCKeepAliveMinTime = 5 * time.Second
DefaultGRPCKeepAliveInterval = 2 * time.Hour
DefaultGRPCKeepAliveTimeout = 20 * time.Second
@@ -176,6 +179,10 @@ type Config struct {
MaxTxnOps uint `json:"max-txn-ops"`
MaxRequestBytes uint `json:"max-request-bytes"`
// MaxConcurrentStreams specifies the maximum number of concurrent
// streams that each client can open at a time.
MaxConcurrentStreams uint32 `json:"max-concurrent-streams"`
LPUrls, LCUrls []url.URL
APUrls, ACUrls []url.URL
ClientTLSInfo transport.TLSInfo
@@ -273,14 +280,26 @@ type Config struct {
AuthToken string `json:"auth-token"`
BcryptCost uint `json:"bcrypt-cost"`
// AuthTokenTTL specifies the TTL in seconds of the simple token
AuthTokenTTL uint `json:"auth-token-ttl"`
ExperimentalInitialCorruptCheck bool `json:"experimental-initial-corrupt-check"`
ExperimentalCorruptCheckTime time.Duration `json:"experimental-corrupt-check-time"`
ExperimentalEnableV2V3 string `json:"experimental-enable-v2v3"`
// ExperimentalBackendFreelistType specifies the type of freelist that boltdb backend uses (array and map are supported types).
ExperimentalBackendFreelistType string `json:"experimental-backend-bbolt-freelist-type"`
// ExperimentalEnableLeaseCheckpoint enables primary lessor to persist lease remainingTTL to prevent indefinite auto-renewal of long lived leases.
// ExperimentalEnableLeaseCheckpoint enables leader to send regular checkpoints to other members to prevent reset of remaining TTL on leader change.
ExperimentalEnableLeaseCheckpoint bool `json:"experimental-enable-lease-checkpoint"`
ExperimentalCompactionBatchLimit int `json:"experimental-compaction-batch-limit"`
// ExperimentalEnableLeaseCheckpointPersist enables persisting remainingTTL to prevent indefinite auto-renewal of long lived leases. Always enabled in v3.6. Should be used to ensure smooth upgrade from v3.5 clusters with this feature enabled.
// Requires experimental-enable-lease-checkpoint to be enabled.
// Deprecated in v3.6.
// TODO: Delete in v3.7
ExperimentalEnableLeaseCheckpointPersist bool `json:"experimental-enable-lease-checkpoint-persist"`
ExperimentalCompactionBatchLimit int `json:"experimental-compaction-batch-limit"`
ExperimentalWatchProgressNotifyInterval time.Duration `json:"experimental-watch-progress-notify-interval"`
// ExperimentalWarningApplyDuration is the time duration after which a warning is generated if applying request
// takes more time than this value.
ExperimentalWarningApplyDuration time.Duration `json:"experimental-warning-apply-duration"`
// ForceNewCluster starts a new cluster even if previously started; unsafe.
ForceNewCluster bool `json:"force-new-cluster"`
@@ -335,6 +354,10 @@ type Config struct {
// Only valid if "logger" option is "capnslog".
// WARN: DO NOT USE THIS!
LogPkgLevels string `json:"log-package-levels"`
// UnsafeNoFsync disables all uses of fsync.
// Setting this is unsafe and will cause data loss.
UnsafeNoFsync bool `json:"unsafe-no-fsync"`
}
// configYAML holds the config suitable for yaml parsing
@@ -380,8 +403,10 @@ func NewConfig() *Config {
SnapshotCount: etcdserver.DefaultSnapshotCount,
SnapshotCatchUpEntries: etcdserver.DefaultSnapshotCatchUpEntries,
MaxTxnOps: DefaultMaxTxnOps,
MaxRequestBytes: DefaultMaxRequestBytes,
MaxTxnOps: DefaultMaxTxnOps,
MaxRequestBytes: DefaultMaxRequestBytes,
MaxConcurrentStreams: DefaultMaxConcurrentStreams,
ExperimentalWarningApplyDuration: DefaultWarningApplyDuration,
GRPCKeepAliveMinTime: DefaultGRPCKeepAliveMinTime,
GRPCKeepAliveInterval: DefaultGRPCKeepAliveInterval,
@@ -406,8 +431,9 @@ func NewConfig() *Config {
CORS: map[string]struct{}{"*": {}},
HostWhitelist: map[string]struct{}{"*": {}},
AuthToken: "simple",
BcryptCost: uint(bcrypt.DefaultCost),
AuthToken: "simple",
BcryptCost: uint(bcrypt.DefaultCost),
AuthTokenTTL: 300,
PreVote: false, // TODO: enable by default in v3.5
@@ -540,13 +566,9 @@ func updateCipherSuites(tls *transport.TLSInfo, ss []string) error {
return fmt.Errorf("TLSInfo.CipherSuites is already specified (given %v)", ss)
}
if len(ss) > 0 {
cs := make([]uint16, len(ss))
for i, s := range ss {
var ok bool
cs[i], ok = tlsutil.GetCipherSuite(s)
if !ok {
return fmt.Errorf("unexpected TLS cipher suite %q", s)
}
cs, err := tlsutil.GetCipherSuites(ss)
if err != nil {
return err
}
tls.CipherSuites = cs
}
@@ -616,6 +638,14 @@ func (cfg *Config) Validate() error {
return fmt.Errorf("unknown auto-compaction-mode %q", cfg.AutoCompactionMode)
}
if !cfg.ExperimentalEnableLeaseCheckpointPersist && cfg.ExperimentalEnableLeaseCheckpoint {
cfg.logger.Warn("Detected that checkpointing is enabled without persistence. Consider enabling experimental-enable-lease-checkpoint-persist")
}
if cfg.ExperimentalEnableLeaseCheckpointPersist && !cfg.ExperimentalEnableLeaseCheckpoint {
return fmt.Errorf("setting experimental-enable-lease-checkpoint-persist requires experimental-enable-lease-checkpoint")
}
return nil
}

View File

@@ -196,10 +196,14 @@ func (cfg *Config) setupLogging() error {
grpcLogOnce.Do(func() {
// debug true, enable info, warning, error
// debug false, only discard info
var gl grpclog.LoggerV2
gl, err = logutil.NewGRPCLoggerV2(copied)
if err == nil {
grpclog.SetLoggerV2(gl)
if cfg.LogLevel == "debug" {
var gl grpclog.LoggerV2
gl, err = logutil.NewGRPCLoggerV2(copied)
if err == nil {
grpclog.SetLoggerV2(gl)
}
} else {
grpclog.SetLoggerV2(grpclog.NewLoggerV2(ioutil.Discard, os.Stderr, os.Stderr))
}
})
return nil
@@ -245,7 +249,11 @@ func (cfg *Config) setupLogging() error {
c.loggerWriteSyncer = syncer
grpcLogOnce.Do(func() {
grpclog.SetLoggerV2(logutil.NewGRPCLoggerV2FromZapCore(cr, syncer))
if cfg.LogLevel == "debug" {
grpclog.SetLoggerV2(logutil.NewGRPCLoggerV2FromZapCore(cr, syncer))
} else {
grpclog.SetLoggerV2(grpclog.NewLoggerV2(ioutil.Discard, os.Stderr, os.Stderr))
}
})
return nil
}

View File

@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !windows
// +build !windows
package embed

View File

@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build windows
// +build windows
package embed

View File

@@ -178,9 +178,11 @@ func TestAutoCompactionModeParse(t *testing.T) {
{"revision", "1", false, 1},
{"revision", "1h", false, time.Hour},
{"revision", "a", true, 0},
{"revision", "-1", true, 0},
// periodic
{"periodic", "1", false, time.Hour},
{"periodic", "a", true, 0},
{"revision", "-1", true, 0},
// err mode
{"errmode", "1", false, 0},
{"errmode", "1h", false, time.Hour},

View File

@@ -162,50 +162,56 @@ func StartEtcd(inCfg *Config) (e *Etcd, err error) {
backendFreelistType := parseBackendFreelistType(cfg.ExperimentalBackendFreelistType)
srvcfg := etcdserver.ServerConfig{
Name: cfg.Name,
ClientURLs: cfg.ACUrls,
PeerURLs: cfg.APUrls,
DataDir: cfg.Dir,
DedicatedWALDir: cfg.WalDir,
SnapshotCount: cfg.SnapshotCount,
SnapshotCatchUpEntries: cfg.SnapshotCatchUpEntries,
MaxSnapFiles: cfg.MaxSnapFiles,
MaxWALFiles: cfg.MaxWalFiles,
InitialPeerURLsMap: urlsmap,
InitialClusterToken: token,
DiscoveryURL: cfg.Durl,
DiscoveryProxy: cfg.Dproxy,
NewCluster: cfg.IsNewCluster(),
PeerTLSInfo: cfg.PeerTLSInfo,
TickMs: cfg.TickMs,
ElectionTicks: cfg.ElectionTicks(),
InitialElectionTickAdvance: cfg.InitialElectionTickAdvance,
AutoCompactionRetention: autoCompactionRetention,
AutoCompactionMode: cfg.AutoCompactionMode,
QuotaBackendBytes: cfg.QuotaBackendBytes,
BackendBatchLimit: cfg.BackendBatchLimit,
BackendFreelistType: backendFreelistType,
BackendBatchInterval: cfg.BackendBatchInterval,
MaxTxnOps: cfg.MaxTxnOps,
MaxRequestBytes: cfg.MaxRequestBytes,
StrictReconfigCheck: cfg.StrictReconfigCheck,
ClientCertAuthEnabled: cfg.ClientTLSInfo.ClientCertAuth,
AuthToken: cfg.AuthToken,
BcryptCost: cfg.BcryptCost,
CORS: cfg.CORS,
HostWhitelist: cfg.HostWhitelist,
InitialCorruptCheck: cfg.ExperimentalInitialCorruptCheck,
CorruptCheckTime: cfg.ExperimentalCorruptCheckTime,
PreVote: cfg.PreVote,
Logger: cfg.logger,
LoggerConfig: cfg.loggerConfig,
LoggerCore: cfg.loggerCore,
LoggerWriteSyncer: cfg.loggerWriteSyncer,
Debug: cfg.Debug,
ForceNewCluster: cfg.ForceNewCluster,
EnableGRPCGateway: cfg.EnableGRPCGateway,
EnableLeaseCheckpoint: cfg.ExperimentalEnableLeaseCheckpoint,
CompactionBatchLimit: cfg.ExperimentalCompactionBatchLimit,
Name: cfg.Name,
ClientURLs: cfg.ACUrls,
PeerURLs: cfg.APUrls,
DataDir: cfg.Dir,
DedicatedWALDir: cfg.WalDir,
SnapshotCount: cfg.SnapshotCount,
SnapshotCatchUpEntries: cfg.SnapshotCatchUpEntries,
MaxSnapFiles: cfg.MaxSnapFiles,
MaxWALFiles: cfg.MaxWalFiles,
InitialPeerURLsMap: urlsmap,
InitialClusterToken: token,
DiscoveryURL: cfg.Durl,
DiscoveryProxy: cfg.Dproxy,
NewCluster: cfg.IsNewCluster(),
PeerTLSInfo: cfg.PeerTLSInfo,
TickMs: cfg.TickMs,
ElectionTicks: cfg.ElectionTicks(),
InitialElectionTickAdvance: cfg.InitialElectionTickAdvance,
AutoCompactionRetention: autoCompactionRetention,
AutoCompactionMode: cfg.AutoCompactionMode,
QuotaBackendBytes: cfg.QuotaBackendBytes,
BackendBatchLimit: cfg.BackendBatchLimit,
BackendFreelistType: backendFreelistType,
BackendBatchInterval: cfg.BackendBatchInterval,
MaxTxnOps: cfg.MaxTxnOps,
MaxRequestBytes: cfg.MaxRequestBytes,
MaxConcurrentStreams: cfg.MaxConcurrentStreams,
StrictReconfigCheck: cfg.StrictReconfigCheck,
ClientCertAuthEnabled: cfg.ClientTLSInfo.ClientCertAuth,
AuthToken: cfg.AuthToken,
BcryptCost: cfg.BcryptCost,
TokenTTL: cfg.AuthTokenTTL,
CORS: cfg.CORS,
HostWhitelist: cfg.HostWhitelist,
InitialCorruptCheck: cfg.ExperimentalInitialCorruptCheck,
CorruptCheckTime: cfg.ExperimentalCorruptCheckTime,
PreVote: cfg.PreVote,
Logger: cfg.logger,
LoggerConfig: cfg.loggerConfig,
LoggerCore: cfg.loggerCore,
LoggerWriteSyncer: cfg.loggerWriteSyncer,
Debug: cfg.Debug,
ForceNewCluster: cfg.ForceNewCluster,
EnableGRPCGateway: cfg.EnableGRPCGateway,
UnsafeNoFsync: cfg.UnsafeNoFsync,
EnableLeaseCheckpoint: cfg.ExperimentalEnableLeaseCheckpoint,
LeaseCheckpointPersist: cfg.ExperimentalEnableLeaseCheckpointPersist,
CompactionBatchLimit: cfg.ExperimentalCompactionBatchLimit,
WatchProgressNotifyInterval: cfg.ExperimentalWatchProgressNotifyInterval,
WarningApplyDuration: cfg.ExperimentalWarningApplyDuration,
}
print(e.cfg.logger, *cfg, srvcfg, memberInitialized)
if e.Server, err = etcdserver.NewServer(srvcfg); err != nil {
@@ -316,6 +322,8 @@ func print(lg *zap.Logger, ec Config, sc etcdserver.ServerConfig, memberInitiali
zap.String("election-timeout", fmt.Sprintf("%v", time.Duration(sc.ElectionTicks*int(sc.TickMs))*time.Millisecond)),
zap.Bool("initial-election-tick-advance", sc.InitialElectionTickAdvance),
zap.Uint64("snapshot-count", sc.SnapshotCount),
zap.Uint("max-wals", sc.MaxWALFiles),
zap.Uint("max-snapshots", sc.MaxSnapFiles),
zap.Uint64("snapshot-catchup-entries", sc.SnapshotCatchUpEntries),
zap.Strings("initial-advertise-peer-urls", ec.getAPURLs()),
zap.Strings("listen-peer-urls", ec.getLPURLs()),
@@ -327,7 +335,10 @@ func print(lg *zap.Logger, ec Config, sc etcdserver.ServerConfig, memberInitiali
zap.String("initial-cluster", sc.InitialPeerURLsMap.String()),
zap.String("initial-cluster-state", ec.ClusterState),
zap.String("initial-cluster-token", sc.InitialClusterToken),
zap.Int64("quota-size-bytes", quota),
zap.Int64("quota-backend-bytes", quota),
zap.Uint("max-request-bytes", sc.MaxRequestBytes),
zap.Uint32("max-concurrent-streams", sc.MaxConcurrentStreams),
zap.Bool("pre-vote", sc.PreVote),
zap.Bool("initial-corrupt-check", sc.InitialCorruptCheck),
zap.String("corrupt-check-time-interval", sc.CorruptCheckTime.String()),
@@ -811,7 +822,7 @@ func (e *Etcd) GetLogger() *zap.Logger {
func parseCompactionRetention(mode, retention string) (ret time.Duration, err error) {
h, err := strconv.Atoi(retention)
if err == nil {
if err == nil && h >= 0 {
switch mode {
case CompactorModeRevision:
ret = time.Duration(int64(h))

View File

@@ -17,8 +17,10 @@ package embed
import (
"context"
"fmt"
"golang.org/x/net/http2"
"io/ioutil"
defaultLog "log"
"math"
"net"
"net/http"
"strings"
@@ -131,6 +133,10 @@ func (sctx *serveCtx) serve(
Handler: createAccessController(sctx.lg, s, httpmux),
ErrorLog: logger, // do not log user error
}
if err = configureHttpServer(srvhttp, s.Cfg); err != nil {
sctx.lg.Error("Configure http server failed", zap.Error(err))
return err
}
httpl := m.Match(cmux.HTTP1())
go func() { errHandler(srvhttp.Serve(httpl)) }()
@@ -184,6 +190,10 @@ func (sctx *serveCtx) serve(
TLSConfig: tlscfg,
ErrorLog: logger, // do not log user error
}
if err = configureHttpServer(srv, s.Cfg); err != nil {
sctx.lg.Error("Configure https server failed", zap.Error(err))
return err
}
go func() { errHandler(srv.Serve(tlsl)) }()
sctx.serversC <- &servers{secure: true, grpc: gs, http: srv}
@@ -201,6 +211,13 @@ func (sctx *serveCtx) serve(
return m.Serve()
}
func configureHttpServer(srv *http.Server, cfg etcdserver.ServerConfig) error {
// todo (ahrtr): should we support configuring other parameters in the future as well?
return http2.ConfigureServer(srv, &http2.Server{
MaxConcurrentStreams: cfg.MaxConcurrentStreams,
})
}
// grpcHandlerFunc returns an http.Handler that delegates to grpcServer on incoming gRPC
// connections or otherHandler otherwise. Given in gRPC docs.
func grpcHandlerFunc(grpcServer *grpc.Server, otherHandler http.Handler) http.Handler {
@@ -229,6 +246,10 @@ func (sctx *serveCtx) registerGateway(opts []grpc.DialOption) (*gw.ServeMux, err
addr = fmt.Sprintf("%s://%s", network, addr)
}
opts = append(opts, grpc.WithDefaultCallOptions([]grpc.CallOption{
grpc.MaxCallRecvMsgSize(math.MaxInt32),
}...))
conn, err := grpc.DialContext(ctx, addr, opts...)
if err != nil {
return nil, err
@@ -286,6 +307,7 @@ func (sctx *serveCtx) createMux(gwmux *gw.ServeMux, handler http.Handler) *http.
return outgoing
},
),
wsproxy.WithMaxRespBodyBufferSize(0x7fffffff),
),
)
}

View File

@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build cov
// +build cov
package ctlv2

View File

@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !cov
// +build !cov
package ctlv2

View File

@@ -311,6 +311,8 @@ func newCheckDatascaleCommand(cmd *cobra.Command, args []string) {
ExitWithError(ExitError, errEndpoints)
}
sec := secureCfgFromCmd(cmd)
ctx, cancel := context.WithCancel(context.Background())
resp, err := clients[0].Get(ctx, checkDatascalePrefix, v3.WithPrefix(), v3.WithLimit(1))
cancel()
@@ -329,7 +331,7 @@ func newCheckDatascaleCommand(cmd *cobra.Command, args []string) {
wg.Add(len(clients))
// get the process_resident_memory_bytes and process_virtual_memory_bytes before the put operations
bytesBefore := endpointMemoryMetrics(eps[0])
bytesBefore := endpointMemoryMetrics(eps[0], sec)
if bytesBefore == 0 {
fmt.Println("FAIL: Could not read process_resident_memory_bytes before the put operations.")
os.Exit(ExitError)
@@ -367,7 +369,7 @@ func newCheckDatascaleCommand(cmd *cobra.Command, args []string) {
s := <-sc
// get the process_resident_memory_bytes after the put operations
bytesAfter := endpointMemoryMetrics(eps[0])
bytesAfter := endpointMemoryMetrics(eps[0], sec)
if bytesAfter == 0 {
fmt.Println("FAIL: Could not read process_resident_memory_bytes after the put operations.")
os.Exit(ExitError)

View File

@@ -31,6 +31,7 @@ var (
getFromKey bool
getRev int64
getKeysOnly bool
getCountOnly bool
printValueOnly bool
)
@@ -50,6 +51,7 @@ func NewGetCommand() *cobra.Command {
cmd.Flags().BoolVar(&getFromKey, "from-key", false, "Get keys that are greater than or equal to the given key using byte compare")
cmd.Flags().Int64Var(&getRev, "rev", 0, "Specify the kv revision")
cmd.Flags().BoolVar(&getKeysOnly, "keys-only", false, "Get only the keys")
cmd.Flags().BoolVar(&getCountOnly, "count-only", false, "Get only the count")
cmd.Flags().BoolVar(&printValueOnly, "print-value-only", false, `Only write values when using the "simple" output format`)
return cmd
}
@@ -64,6 +66,12 @@ func getCommandFunc(cmd *cobra.Command, args []string) {
ExitWithError(ExitError, err)
}
if getCountOnly {
if _, fields := display.(*fieldsPrinter); !fields {
ExitWithError(ExitBadArgs, fmt.Errorf("--count-only is only for `--write-out=fields`"))
}
}
if printValueOnly {
dp, simple := (display).(*simplePrinter)
if !simple {
@@ -83,6 +91,10 @@ func getGetOp(args []string) (string, []clientv3.OpOption) {
ExitWithError(ExitBadArgs, fmt.Errorf("`--prefix` and `--from-key` cannot be set at the same time, choose one"))
}
if getKeysOnly && getCountOnly {
ExitWithError(ExitBadArgs, fmt.Errorf("`--keys-only` and `--count-only` cannot be set at the same time, choose one"))
}
opts := []clientv3.OpOption{}
switch getConsistency {
case "s":
@@ -159,5 +171,9 @@ func getGetOp(args []string) (string, []clientv3.OpOption) {
opts = append(opts, clientv3.WithKeysOnly())
}
if getCountOnly {
opts = append(opts, clientv3.WithCountOnly())
}
return key, opts
}

View File

@@ -156,28 +156,8 @@ func memberAddCommandFunc(cmd *cobra.Command, args []string) {
display.MemberAdd(*resp)
if _, ok := (display).(*simplePrinter); ok {
ctx, cancel = commandCtx(cmd)
listResp, err := cli.MemberList(ctx)
// get latest member list; if there's failover new member might have outdated list
for {
if err != nil {
ExitWithError(ExitError, err)
}
if listResp.Header.MemberId == resp.Header.MemberId {
break
}
// quorum get to sync cluster list
gresp, gerr := cli.Get(ctx, "_")
if gerr != nil {
ExitWithError(ExitError, err)
}
resp.Header.MemberId = gresp.Header.MemberId
listResp, err = cli.MemberList(ctx)
}
cancel()
conf := []string{}
for _, memb := range listResp.Members {
for _, memb := range resp.Members {
for _, u := range memb.PeerURLs {
n := memb.Name
if memb.ID == newID {

View File

@@ -42,7 +42,8 @@ func transferLeadershipCommandFunc(cmd *cobra.Command, args []string) {
ExitWithError(ExitBadArgs, err)
}
c := mustClientFromCmd(cmd)
cfg := clientConfigFromCmd(cmd)
c := cfg.mustClient()
eps := c.Endpoints()
c.Close()
@@ -52,7 +53,6 @@ func transferLeadershipCommandFunc(cmd *cobra.Command, args []string) {
var leaderCli *clientv3.Client
var leaderID uint64
for _, ep := range eps {
cfg := clientConfigFromCmd(cmd)
cfg.endpoints = []string{ep}
cli := cfg.mustClient()
resp, serr := cli.Status(ctx, ep)

View File

@@ -16,6 +16,7 @@ package command
import (
"context"
"crypto/tls"
"encoding/hex"
"fmt"
"io/ioutil"
@@ -90,14 +91,26 @@ func isCommandTimeoutFlagSet(cmd *cobra.Command) bool {
return commandTimeoutFlag.Changed
}
// get the process_resident_memory_bytes from <server:2379>/metrics
func endpointMemoryMetrics(host string) float64 {
// get the process_resident_memory_bytes from <server>/metrics
func endpointMemoryMetrics(host string, scfg *secureCfg) float64 {
residentMemoryKey := "process_resident_memory_bytes"
var residentMemoryValue string
if !strings.HasPrefix(host, `http://`) {
if !strings.HasPrefix(host, "http://") && !strings.HasPrefix(host, "https://") {
host = "http://" + host
}
url := host + "/metrics"
if strings.HasPrefix(host, "https://") {
// load client certificate
cert, err := tls.LoadX509KeyPair(scfg.cert, scfg.key)
if err != nil {
fmt.Println(fmt.Sprintf("client certificate error: %v", err))
return 0.0
}
http.DefaultTransport.(*http.Transport).TLSClientConfig = &tls.Config{
Certificates: []tls.Certificate{cert},
InsecureSkipVerify: scfg.insecureSkipVerify,
}
}
resp, err := http.Get(url)
if err != nil {
fmt.Println(fmt.Sprintf("fetch error: %v", err))

View File

@@ -60,7 +60,7 @@ func init() {
// TODO: secure by default when etcd enables secure gRPC by default.
rootCmd.PersistentFlags().BoolVar(&globalFlags.Insecure, "insecure-transport", true, "disable transport security for client connections")
rootCmd.PersistentFlags().BoolVar(&globalFlags.InsecureDiscovery, "insecure-discovery", true, "accept insecure SRV records describing cluster endpoints")
rootCmd.PersistentFlags().BoolVar(&globalFlags.InsecureSkipVerify, "insecure-skip-tls-verify", false, "skip server certificate verification")
rootCmd.PersistentFlags().BoolVar(&globalFlags.InsecureSkipVerify, "insecure-skip-tls-verify", false, "skip server certificate verification (CAUTION: this option should be enabled only for testing purposes)")
rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.CertFile, "cert", "", "identify secure client using this TLS certificate file")
rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.KeyFile, "key", "", "identify secure client using this TLS key file")
rootCmd.PersistentFlags().StringVar(&globalFlags.TLS.TrustedCAFile, "cacert", "", "verify certificates of TLS-enabled secure servers using this CA bundle")

View File

@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build cov
// +build cov
package ctlv3

View File

@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !cov
// +build !cov
package ctlv3

View File

@@ -163,6 +163,8 @@ func newConfig() *config {
fs.DurationVar(&cfg.ec.GRPCKeepAliveInterval, "grpc-keepalive-interval", cfg.ec.GRPCKeepAliveInterval, "Frequency duration of server-to-client ping to check if a connection is alive (0 to disable).")
fs.DurationVar(&cfg.ec.GRPCKeepAliveTimeout, "grpc-keepalive-timeout", cfg.ec.GRPCKeepAliveTimeout, "Additional duration of wait before closing a non-responsive connection (0 to disable).")
fs.Var(flags.NewUint32Value(cfg.ec.MaxConcurrentStreams), "max-concurrent-streams", "Maximum concurrent streams that each client can open at a time.")
// clustering
fs.Var(
flags.NewUniqueURLsWithExceptions(embed.DefaultInitialAdvertisePeerURLs, ""),
@@ -245,6 +247,7 @@ func newConfig() *config {
// auth
fs.StringVar(&cfg.ec.AuthToken, "auth-token", cfg.ec.AuthToken, "Specify auth token specific options.")
fs.UintVar(&cfg.ec.BcryptCost, "bcrypt-cost", cfg.ec.BcryptCost, "Specify bcrypt algorithm cost factor for auth password hashing.")
fs.UintVar(&cfg.ec.AuthTokenTTL, "auth-token-ttl", cfg.ec.AuthTokenTTL, "The lifetime in seconds of the auth token.")
// gateway
fs.BoolVar(&cfg.ec.EnableGRPCGateway, "enable-grpc-gateway", true, "Enable GRPC gateway.")
@@ -254,10 +257,15 @@ func newConfig() *config {
fs.DurationVar(&cfg.ec.ExperimentalCorruptCheckTime, "experimental-corrupt-check-time", cfg.ec.ExperimentalCorruptCheckTime, "Duration of time between cluster corruption check passes.")
fs.StringVar(&cfg.ec.ExperimentalEnableV2V3, "experimental-enable-v2v3", cfg.ec.ExperimentalEnableV2V3, "v3 prefix for serving emulated v2 state.")
fs.StringVar(&cfg.ec.ExperimentalBackendFreelistType, "experimental-backend-bbolt-freelist-type", cfg.ec.ExperimentalBackendFreelistType, "ExperimentalBackendFreelistType specifies the type of freelist that boltdb backend uses(array and map are supported types)")
fs.BoolVar(&cfg.ec.ExperimentalEnableLeaseCheckpoint, "experimental-enable-lease-checkpoint", false, "Enable to persist lease remaining TTL to prevent indefinite auto-renewal of long lived leases.")
fs.BoolVar(&cfg.ec.ExperimentalEnableLeaseCheckpoint, "experimental-enable-lease-checkpoint", false, "Enable leader to send regular checkpoints to other members to prevent reset of remaining TTL on leader change.")
// TODO: delete in v3.7
fs.BoolVar(&cfg.ec.ExperimentalEnableLeaseCheckpointPersist, "experimental-enable-lease-checkpoint-persist", false, "Enable persisting remainingTTL to prevent indefinite auto-renewal of long lived leases. Always enabled in v3.6. Should be used to ensure smooth upgrade from v3.5 clusters with this feature enabled. Requires experimental-enable-lease-checkpoint to be enabled.")
fs.IntVar(&cfg.ec.ExperimentalCompactionBatchLimit, "experimental-compaction-batch-limit", cfg.ec.ExperimentalCompactionBatchLimit, "Sets the maximum revisions deleted in each compaction batch.")
fs.DurationVar(&cfg.ec.ExperimentalWatchProgressNotifyInterval, "experimental-watch-progress-notify-interval", cfg.ec.ExperimentalWatchProgressNotifyInterval, "Duration of periodic watch progress notifications.")
fs.DurationVar(&cfg.ec.ExperimentalWarningApplyDuration, "experimental-warning-apply-duration", cfg.ec.ExperimentalWarningApplyDuration, "Time duration after which a warning is generated if request takes more time.")
// unsafe
fs.BoolVar(&cfg.ec.UnsafeNoFsync, "unsafe-no-fsync", false, "Disables fsync, unsafe, will cause data loss.")
fs.BoolVar(&cfg.ec.ForceNewCluster, "force-new-cluster", false, "Force to create a new one member cluster.")
// ignored
@@ -332,6 +340,8 @@ func (cfg *config) configFromCmdLine() error {
cfg.ec.CipherSuites = flags.StringsFromFlag(cfg.cf.flagSet, "cipher-suites")
cfg.ec.MaxConcurrentStreams = flags.Uint32FromFlag(cfg.cf.flagSet, "max-concurrent-streams")
// TODO: remove this in v3.5
cfg.ec.DeprecatedLogOutput = flags.UniqueStringsFromFlag(cfg.cf.flagSet, "log-output")
cfg.ec.LogOutputs = flags.UniqueStringsFromFlag(cfg.cf.flagSet, "log-outputs")

View File

@@ -358,7 +358,7 @@ func startProxy(cfg *config) error {
}
cfg.ec.Dir = filepath.Join(cfg.ec.Dir, "proxy")
err = os.MkdirAll(cfg.ec.Dir, fileutil.PrivateDirMode)
err = fileutil.TouchDirAll(cfg.ec.Dir)
if err != nil {
return err
}

View File

@@ -71,7 +71,7 @@ func newGatewayStartCommand() *cobra.Command {
cmd.Flags().StringVar(&gatewayDNSCluster, "discovery-srv", "", "DNS domain used to bootstrap initial cluster")
cmd.Flags().StringVar(&gatewayDNSClusterServiceName, "discovery-srv-name", "", "service name to query when using DNS discovery")
cmd.Flags().BoolVar(&gatewayInsecureDiscovery, "insecure-discovery", false, "accept insecure SRV records")
cmd.Flags().StringVar(&gatewayCA, "trusted-ca-file", "", "path to the client server TLS CA file.")
cmd.Flags().StringVar(&gatewayCA, "trusted-ca-file", "", "path to the client server TLS CA file for verifying the discovered endpoints when discovery-srv is provided.")
cmd.Flags().StringSliceVar(&gatewayEndpoints, "endpoints", []string{"127.0.0.1:2379"}, "comma separated etcd cluster endpoints")
@@ -119,6 +119,41 @@ func startGateway(cmd *cobra.Command, args []string) {
}
}
lhost, lport, err := net.SplitHostPort(gatewayListenAddr)
if err != nil {
fmt.Println("failed to validate listen address:", gatewayListenAddr)
os.Exit(1)
}
laddrs, err := net.LookupHost(lhost)
if err != nil {
fmt.Println("failed to resolve listen host:", lhost)
os.Exit(1)
}
laddrsMap := make(map[string]bool)
for _, addr := range laddrs {
laddrsMap[addr] = true
}
for _, srv := range srvs.SRVs {
var eaddrs []string
eaddrs, err = net.LookupHost(srv.Target)
if err != nil {
fmt.Println("failed to resolve endpoint host:", srv.Target)
os.Exit(1)
}
if fmt.Sprintf("%d", srv.Port) != lport {
continue
}
for _, ea := range eaddrs {
if laddrsMap[ea] {
fmt.Printf("SRV or endpoint (%s:%d->%s:%d) should not resolve to the gateway listen addr (%s)\n", srv.Target, srv.Port, ea, srv.Port, gatewayListenAddr)
os.Exit(1)
}
}
}
if len(srvs.Endpoints) == 0 {
fmt.Println("no endpoints found")
os.Exit(1)

View File

@@ -38,6 +38,7 @@ import (
pb "go.etcd.io/etcd/etcdserver/etcdserverpb"
"go.etcd.io/etcd/pkg/debugutil"
"go.etcd.io/etcd/pkg/logutil"
"go.etcd.io/etcd/pkg/tlsutil"
"go.etcd.io/etcd/pkg/transport"
"go.etcd.io/etcd/proxy/grpcproxy"
@@ -45,6 +46,7 @@ import (
"github.com/soheilhy/cmux"
"github.com/spf13/cobra"
"go.uber.org/zap"
"golang.org/x/net/http2"
"google.golang.org/grpc"
"google.golang.org/grpc/grpclog"
)
@@ -69,11 +71,12 @@ var (
// tls for clients connecting to proxy
grpcProxyListenCA string
grpcProxyListenCert string
grpcProxyListenKey string
grpcProxyListenAutoTLS bool
grpcProxyListenCRL string
grpcProxyListenCA string
grpcProxyListenCert string
grpcProxyListenKey string
grpcProxyListenCipherSuites []string
grpcProxyListenAutoTLS bool
grpcProxyListenCRL string
grpcProxyAdvertiseClientURL string
grpcProxyResolverPrefix string
@@ -86,6 +89,8 @@ var (
grpcProxyEnableOrdering bool
grpcProxyDebug bool
maxConcurrentStreams uint32
)
const defaultGRPCMaxCallSendMsgSize = 1.5 * 1024 * 1024
@@ -131,12 +136,13 @@ func newGRPCProxyStartCommand() *cobra.Command {
cmd.Flags().StringVar(&grpcProxyCert, "cert", "", "identify secure connections with etcd servers using this TLS certificate file")
cmd.Flags().StringVar(&grpcProxyKey, "key", "", "identify secure connections with etcd servers using this TLS key file")
cmd.Flags().StringVar(&grpcProxyCA, "cacert", "", "verify certificates of TLS-enabled secure etcd servers using this CA bundle")
cmd.Flags().BoolVar(&grpcProxyInsecureSkipTLSVerify, "insecure-skip-tls-verify", false, "skip authentication of etcd server TLS certificates")
cmd.Flags().BoolVar(&grpcProxyInsecureSkipTLSVerify, "insecure-skip-tls-verify", false, "skip authentication of etcd server TLS certificates (CAUTION: this option should be enabled only for testing purposes)")
// client TLS for connecting to proxy
cmd.Flags().StringVar(&grpcProxyListenCert, "cert-file", "", "identify secure connections to the proxy using this TLS certificate file")
cmd.Flags().StringVar(&grpcProxyListenKey, "key-file", "", "identify secure connections to the proxy using this TLS key file")
cmd.Flags().StringVar(&grpcProxyListenCA, "trusted-ca-file", "", "verify certificates of TLS-enabled secure proxy using this CA bundle")
cmd.Flags().StringSliceVar(&grpcProxyListenCipherSuites, "listen-cipher-suites", grpcProxyListenCipherSuites, "Comma-separated list of supported TLS cipher suites between client/proxy (empty will be auto-populated by Go).")
cmd.Flags().BoolVar(&grpcProxyListenAutoTLS, "auto-tls", false, "proxy TLS using generated certificates")
cmd.Flags().StringVar(&grpcProxyListenCRL, "client-crl-file", "", "proxy client certificate revocation list file.")
@@ -146,6 +152,8 @@ func newGRPCProxyStartCommand() *cobra.Command {
cmd.Flags().BoolVar(&grpcProxyDebug, "debug", false, "Enable debug-level logging for grpc-proxy.")
cmd.Flags().Uint32Var(&maxConcurrentStreams, "max-concurrent-streams", math.MaxUint32, "Maximum concurrent streams that each client can open at a time.")
return &cmd
}
@@ -171,20 +179,27 @@ func startGRPCProxy(cmd *cobra.Command, args []string) {
}
grpclog.SetLoggerV2(gl)
tlsinfo := newTLS(grpcProxyListenCA, grpcProxyListenCert, grpcProxyListenKey)
if tlsinfo == nil && grpcProxyListenAutoTLS {
tlsInfo := newTLS(grpcProxyListenCA, grpcProxyListenCert, grpcProxyListenKey)
if len(grpcProxyListenCipherSuites) > 0 {
cs, err := tlsutil.GetCipherSuites(grpcProxyListenCipherSuites)
if err != nil {
log.Fatal(err)
}
tlsInfo.CipherSuites = cs
}
if tlsInfo == nil && grpcProxyListenAutoTLS {
host := []string{"https://" + grpcProxyListenAddr}
dir := filepath.Join(grpcProxyDataDir, "fixtures", "proxy")
autoTLS, err := transport.SelfCert(lg, dir, host)
if err != nil {
log.Fatal(err)
}
tlsinfo = &autoTLS
tlsInfo = &autoTLS
}
if tlsinfo != nil {
lg.Info("gRPC proxy server TLS", zap.String("tls-info", fmt.Sprintf("%+v", tlsinfo)))
if tlsInfo != nil {
lg.Info("gRPC proxy server TLS", zap.String("tls-info", fmt.Sprintf("%+v", tlsInfo)))
}
m := mustListenCMux(lg, tlsinfo)
m := mustListenCMux(lg, tlsInfo)
grpcl := m.Match(cmux.HTTP2())
defer func() {
grpcl.Close()
@@ -194,13 +209,20 @@ func startGRPCProxy(cmd *cobra.Command, args []string) {
client := mustNewClient(lg)
httpClient := mustNewHTTPClient(lg)
srvhttp, httpl := mustHTTPListener(lg, m, tlsinfo, client)
srvhttp, httpl := mustHTTPListener(lg, m, tlsInfo, client)
if err := http2.ConfigureServer(srvhttp, &http2.Server{
MaxConcurrentStreams: maxConcurrentStreams,
}); err != nil {
lg.Fatal("Failed to configure the http server", zap.Error(err))
}
errc := make(chan error)
go func() { errc <- newGRPCProxyServer(lg, client).Serve(grpcl) }()
go func() { errc <- srvhttp.Serve(httpl) }()
go func() { errc <- m.Serve() }()
if len(grpcProxyMetricsListenAddr) > 0 {
mhttpl := mustMetricsListener(lg, tlsinfo)
mhttpl := mustMetricsListener(lg, tlsInfo)
go func() {
mux := http.NewServeMux()
grpcproxy.HandleMetrics(mux, httpClient, client.Endpoints())
@@ -286,6 +308,9 @@ func newClientCfg(lg *zap.Logger, eps []string) (*clientv3.Config, error) {
return nil, err
}
clientTLS.InsecureSkipVerify = grpcProxyInsecureSkipTLSVerify
if clientTLS.InsecureSkipVerify {
lg.Warn("--insecure-skip-tls-verify was given, this grpc proxy process skips authentication of etcd server TLS certificates. This option should be enabled only for testing purposes.")
}
cfg.TLS = clientTLS
lg.Info("gRPC proxy client TLS", zap.String("tls-info", fmt.Sprintf("%+v", tls)))
}
@@ -323,7 +348,7 @@ func mustListenCMux(lg *zap.Logger, tlsinfo *transport.TLSInfo) cmux.CMux {
func newGRPCProxyServer(lg *zap.Logger, client *clientv3.Client) *grpc.Server {
if grpcProxyEnableOrdering {
vf := ordering.NewOrderViolationSwitchEndpointClosure(*client)
vf := ordering.NewOrderViolationSwitchEndpointClosure(client)
client.KV = ordering.NewKV(client.KV, vf)
lg.Info("waiting for linearized read from cluster to recover ordering")
for {
@@ -347,12 +372,12 @@ func newGRPCProxyServer(lg *zap.Logger, client *clientv3.Client) *grpc.Server {
}
kvp, _ := grpcproxy.NewKvProxy(client)
watchp, _ := grpcproxy.NewWatchProxy(client)
watchp, _ := grpcproxy.NewWatchProxy(client.Ctx(), client)
if grpcProxyResolverPrefix != "" {
grpcproxy.Register(client, grpcProxyResolverPrefix, grpcProxyAdvertiseClientURL, grpcProxyResolverTTL)
}
clusterp, _ := grpcproxy.NewClusterProxy(client, grpcProxyAdvertiseClientURL, grpcProxyResolverPrefix)
leasep, _ := grpcproxy.NewLeaseProxy(client)
leasep, _ := grpcproxy.NewLeaseProxy(client.Ctx(), client)
mainp := grpcproxy.NewMaintenanceProxy(client)
authp := grpcproxy.NewAuthProxy(client)
electionp := grpcproxy.NewElectionProxy(client)

View File

@@ -77,6 +77,8 @@ Member:
Maximum number of operations permitted in a transaction.
--max-request-bytes '1572864'
Maximum client request size in bytes the server will accept.
--max-concurrent-streams 'math.MaxUint32'
Maximum concurrent streams that each client can open at a time.
--grpc-keepalive-min-time '5s'
Minimum duration interval that a client should wait before pinging server.
--grpc-keepalive-interval '2h'
@@ -162,6 +164,8 @@ Auth:
Specify a v3 authentication token type and its options ('simple' or 'jwt').
--bcrypt-cost ` + fmt.Sprintf("%d", bcrypt.DefaultCost) + `
Specify the cost / strength of the bcrypt algorithm for hashing auth passwords. Valid values are between ` + fmt.Sprintf("%d", bcrypt.MinCost) + ` and ` + fmt.Sprintf("%d", bcrypt.MaxCost) + `.
--auth-token-ttl 300
Time (in seconds) of the auth-token-ttl.
Profiling and Monitoring:
--enable-pprof 'false'
@@ -208,6 +212,10 @@ Experimental feature:
ExperimentalCompactionBatchLimit sets the maximum revisions deleted in each compaction batch.
--experimental-peer-skip-client-san-verification 'false'
Skip verification of SAN field in client certificate for peer connections.
--experimental-watch-progress-notify-interval '10m'
Duration of periodical watch progress notification.
--experimental-warning-apply-duration '100ms'
Warning is generated if requests take more than this duration.
Unsafe feature:
--force-new-cluster 'false'

View File

@@ -36,7 +36,7 @@ const (
// HandleMetricsHealth registers metrics and health handlers.
func HandleMetricsHealth(mux *http.ServeMux, srv etcdserver.ServerV2) {
mux.Handle(PathMetrics, promhttp.Handler())
mux.Handle(PathHealth, NewHealthHandler(func() Health { return checkHealth(srv) }))
mux.Handle(PathHealth, NewHealthHandler(func(excludedAlarms AlarmSet) Health { return checkHealth(srv, excludedAlarms) }))
}
// HandlePrometheus registers prometheus handler on '/metrics'.
@@ -45,14 +45,16 @@ func HandlePrometheus(mux *http.ServeMux) {
}
// NewHealthHandler handles '/health' requests.
func NewHealthHandler(hfunc func() Health) http.HandlerFunc {
func NewHealthHandler(hfunc func(excludedAlarms AlarmSet) Health) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
w.Header().Set("Allow", http.MethodGet)
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
plog.Warningf("/health error (status code %d)", http.StatusMethodNotAllowed)
return
}
h := hfunc()
excludedAlarms := getExcludedAlarms(r)
h := hfunc(excludedAlarms)
d, _ := json.Marshal(h)
if h.Health != "true" {
http.Error(w, string(d), http.StatusServiceUnavailable)
@@ -89,19 +91,45 @@ type Health struct {
Health string `json:"health"`
}
type AlarmSet map[string]struct{}
func getExcludedAlarms(r *http.Request) (alarms AlarmSet) {
alarms = make(map[string]struct{}, 2)
alms, found := r.URL.Query()["exclude"]
if found {
for _, alm := range alms {
if len(alms) == 0 {
continue
}
alarms[alm] = struct{}{}
}
}
return alarms
}
// TODO: server NOSPACE, etcdserver.ErrNoLeader in health API
func checkHealth(srv etcdserver.ServerV2) Health {
func checkHealth(srv etcdserver.ServerV2, excludedAlarms AlarmSet) Health {
h := Health{Health: "true"}
as := srv.Alarms()
if len(as) > 0 {
h.Health = "false"
for _, v := range as {
alarmName := v.Alarm.String()
if _, found := excludedAlarms[alarmName]; found {
plog.Debugf("/health excluded alarm %s", v.String())
continue
}
h.Health = "false"
plog.Warningf("/health error due to %s", v.String())
return h
}
}
if h.Health == "true" {
if uint64(srv.Leader()) == raft.None {
h.Health = "false"
plog.Warningf("/health error; no leader (status code %d)", http.StatusServiceUnavailable)
}
}
@@ -111,11 +139,13 @@ func checkHealth(srv etcdserver.ServerV2) Health {
cancel()
if err != nil {
h.Health = "false"
plog.Warningf("/health error; QGET failed %v (status code %d)", err, http.StatusServiceUnavailable)
}
}
if h.Health == "true" {
healthSuccess.Inc()
plog.Debugf("/health OK (status code %d)", http.StatusOK)
} else {
healthFailed.Inc()
}

View File

@@ -0,0 +1,157 @@
// 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 etcdhttp
import (
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"
"go.etcd.io/etcd/etcdserver"
stats "go.etcd.io/etcd/etcdserver/api/v2stats"
pb "go.etcd.io/etcd/etcdserver/etcdserverpb"
"go.etcd.io/etcd/pkg/testutil"
"go.etcd.io/etcd/pkg/types"
"go.etcd.io/etcd/raft"
)
type fakeStats struct{}
func (s *fakeStats) SelfStats() []byte { return nil }
func (s *fakeStats) LeaderStats() []byte { return nil }
func (s *fakeStats) StoreStats() []byte { return nil }
type fakeServerV2 struct {
fakeServer
stats.Stats
health string
}
func (s *fakeServerV2) Leader() types.ID {
if s.health == "true" {
return 1
}
return types.ID(raft.None)
}
func (s *fakeServerV2) Do(ctx context.Context, r pb.Request) (etcdserver.Response, error) {
if s.health == "true" {
return etcdserver.Response{}, nil
}
return etcdserver.Response{}, fmt.Errorf("fail health check")
}
func (s *fakeServerV2) ClientCertAuthEnabled() bool { return false }
func TestHealthHandler(t *testing.T) {
// define the input and expected output
// input: alarms, and healthCheckURL
tests := []struct {
alarms []*pb.AlarmMember
healthCheckURL string
statusCode int
health string
}{
{
[]*pb.AlarmMember{},
"/health",
http.StatusOK,
"true",
},
{
[]*pb.AlarmMember{{MemberID: uint64(0), Alarm: pb.AlarmType_NOSPACE}},
"/health",
http.StatusServiceUnavailable,
"false",
},
{
[]*pb.AlarmMember{{MemberID: uint64(0), Alarm: pb.AlarmType_NOSPACE}},
"/health?exclude=NOSPACE",
http.StatusOK,
"true",
},
{
[]*pb.AlarmMember{},
"/health?exclude=NOSPACE",
http.StatusOK,
"true",
},
{
[]*pb.AlarmMember{{MemberID: uint64(1), Alarm: pb.AlarmType_NOSPACE}, {MemberID: uint64(2), Alarm: pb.AlarmType_NOSPACE}, {MemberID: uint64(3), Alarm: pb.AlarmType_NOSPACE}},
"/health?exclude=NOSPACE",
http.StatusOK,
"true",
},
{
[]*pb.AlarmMember{{MemberID: uint64(0), Alarm: pb.AlarmType_NOSPACE}, {MemberID: uint64(1), Alarm: pb.AlarmType_CORRUPT}},
"/health?exclude=NOSPACE",
http.StatusServiceUnavailable,
"false",
},
{
[]*pb.AlarmMember{{MemberID: uint64(0), Alarm: pb.AlarmType_NOSPACE}, {MemberID: uint64(1), Alarm: pb.AlarmType_CORRUPT}},
"/health?exclude=NOSPACE&exclude=CORRUPT",
http.StatusOK,
"true",
},
}
for i, tt := range tests {
func() {
mux := http.NewServeMux()
HandleMetricsHealth(mux, &fakeServerV2{
fakeServer: fakeServer{alarms: tt.alarms},
Stats: &fakeStats{},
health: tt.health,
})
ts := httptest.NewServer(mux)
defer ts.Close()
res, err := ts.Client().Do(&http.Request{Method: http.MethodGet, URL: testutil.MustNewURL(t, ts.URL+tt.healthCheckURL)})
if err != nil {
t.Errorf("fail serve http request %s %v in test case #%d", tt.healthCheckURL, err, i+1)
}
if res == nil {
t.Errorf("got nil http response with http request %s in test case #%d", tt.healthCheckURL, i+1)
return
}
if res.StatusCode != tt.statusCode {
t.Errorf("want statusCode %d but got %d in test case #%d", tt.statusCode, res.StatusCode, i+1)
}
health, err := parseHealthOutput(res.Body)
if err != nil {
t.Errorf("fail parse health check output %v", err)
}
if health.Health != tt.health {
t.Errorf("want health %s but got %s", tt.health, health.Health)
}
}()
}
}
func parseHealthOutput(body io.Reader) (Health, error) {
obj := Health{}
d, derr := ioutil.ReadAll(body)
if derr != nil {
return obj, derr
}
if err := json.Unmarshal(d, &obj); err != nil {
return obj, err
}
return obj, nil
}

View File

@@ -37,11 +37,17 @@ const (
)
// NewPeerHandler generates an http.Handler to handle etcd peer requests.
func NewPeerHandler(lg *zap.Logger, s etcdserver.ServerPeer) http.Handler {
return newPeerHandler(lg, s, s.RaftHandler(), s.LeaseHandler())
func NewPeerHandler(lg *zap.Logger, s etcdserver.ServerPeerV2) http.Handler {
return newPeerHandler(lg, s, s.RaftHandler(), s.LeaseHandler(), s.HashKVHandler())
}
func newPeerHandler(lg *zap.Logger, s etcdserver.Server, raftHandler http.Handler, leaseHandler http.Handler) http.Handler {
func newPeerHandler(
lg *zap.Logger,
s etcdserver.Server,
raftHandler http.Handler,
leaseHandler http.Handler,
hashKVHandler http.Handler,
) http.Handler {
peerMembersHandler := newPeerMembersHandler(lg, s.Cluster())
peerMemberPromoteHandler := newPeerMemberPromoteHandler(lg, s)
@@ -55,6 +61,9 @@ func newPeerHandler(lg *zap.Logger, s etcdserver.Server, raftHandler http.Handle
mux.Handle(leasehttp.LeasePrefix, leaseHandler)
mux.Handle(leasehttp.LeaseInternalPrefix, leaseHandler)
}
if hashKVHandler != nil {
mux.Handle(etcdserver.PeerHashKVPath, hashKVHandler)
}
mux.HandleFunc(versionPath, versionHandler(s.Cluster(), serveVersion))
return mux
}

View File

@@ -58,6 +58,7 @@ func (c *fakeCluster) Version() *semver.Version { return nil }
type fakeServer struct {
cluster api.Cluster
alarms []*pb.AlarmMember
}
func (s *fakeServer) AddMember(ctx context.Context, memb membership.Member) ([]*membership.Member, error) {
@@ -74,7 +75,7 @@ func (s *fakeServer) PromoteMember(ctx context.Context, id uint64) ([]*membershi
}
func (s *fakeServer) ClusterVersion() *semver.Version { return nil }
func (s *fakeServer) Cluster() api.Cluster { return s.cluster }
func (s *fakeServer) Alarms() []*pb.AlarmMember { return nil }
func (s *fakeServer) Alarms() []*pb.AlarmMember { return s.alarms }
var fakeRaftHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("test data"))
@@ -83,7 +84,7 @@ var fakeRaftHandler = http.HandlerFunc(func(w http.ResponseWriter, r *http.Reque
// TestNewPeerHandlerOnRaftPrefix tests that NewPeerHandler returns a handler that
// handles raft-prefix requests well.
func TestNewPeerHandlerOnRaftPrefix(t *testing.T) {
ph := newPeerHandler(zap.NewExample(), &fakeServer{cluster: &fakeCluster{}}, fakeRaftHandler, nil)
ph := newPeerHandler(zap.NewExample(), &fakeServer{cluster: &fakeCluster{}}, fakeRaftHandler, nil, nil)
srv := httptest.NewServer(ph)
defer srv.Close()
@@ -231,7 +232,7 @@ func TestServeMemberPromoteFails(t *testing.T) {
// TestNewPeerHandlerOnMembersPromotePrefix verifies the request with members promote prefix is routed correctly
func TestNewPeerHandlerOnMembersPromotePrefix(t *testing.T) {
ph := newPeerHandler(zap.NewExample(), &fakeServer{cluster: &fakeCluster{}}, fakeRaftHandler, nil)
ph := newPeerHandler(zap.NewExample(), &fakeServer{cluster: &fakeCluster{}}, fakeRaftHandler, nil, nil)
srv := httptest.NewServer(ph)
defer srv.Close()

View File

@@ -565,6 +565,7 @@ func (c *RaftCluster) SetVersion(ver *semver.Version, onSet func(*zap.Logger, *s
plog.Noticef("set the initial cluster version to %v", version.Cluster(ver.String()))
}
}
oldVer := c.version
c.version = ver
mustDetectDowngrade(c.lg, c.version)
if c.v2store != nil {
@@ -573,7 +574,10 @@ func (c *RaftCluster) SetVersion(ver *semver.Version, onSet func(*zap.Logger, *s
if c.be != nil {
mustSaveClusterVersionToBackend(c.be, ver)
}
ClusterVersionMetrics.With(prometheus.Labels{"cluster_version": ver.String()}).Set(1)
if oldVer != nil {
ClusterVersionMetrics.With(prometheus.Labels{"cluster_version": version.Cluster(oldVer.String())}).Set(0)
}
ClusterVersionMetrics.With(prometheus.Labels{"cluster_version": version.Cluster(ver.String())}).Set(1)
onSet(c.lg, ver)
}
@@ -653,8 +657,8 @@ func (c *RaftCluster) IsReadyToRemoveVotingMember(id uint64) bool {
}
func (c *RaftCluster) IsReadyToPromoteMember(id uint64) bool {
nmembers := 1
nstarted := 0
nmembers := 1 // We count the learner to be promoted for the future quorum
nstarted := 1 // and we also count it as started.
for _, member := range c.VotingMembers() {
if member.IsStarted() {
@@ -759,16 +763,21 @@ func ValidateClusterAndAssignIDs(lg *zap.Logger, local *RaftCluster, existing *R
if len(ems) != len(lms) {
return fmt.Errorf("member count is unequal")
}
sort.Sort(MembersByPeerURLs(ems))
sort.Sort(MembersByPeerURLs(lms))
ctx, cancel := context.WithTimeout(context.TODO(), 30*time.Second)
defer cancel()
for i := range ems {
if ok, err := netutil.URLStringsEqual(ctx, lg, ems[i].PeerURLs, lms[i].PeerURLs); !ok {
return fmt.Errorf("unmatched member while checking PeerURLs (%v)", err)
var err error
ok := false
for j := range lms {
if ok, err = netutil.URLStringsEqual(ctx, lg, ems[i].PeerURLs, lms[j].PeerURLs); ok {
lms[j].ID = ems[i].ID
break
}
}
if !ok {
return fmt.Errorf("PeerURLs: no match found for existing member (%v, %v), last resolver error (%v)", ems[i].ID, ems[i].PeerURLs, err)
}
lms[i].ID = ems[i].ID
}
local.members = make(map[types.ID]*Member)
for _, m := range lms {

View File

@@ -859,3 +859,90 @@ func TestIsReadyToRemoveVotingMember(t *testing.T) {
}
}
}
func TestIsReadyToPromoteMember(t *testing.T) {
tests := []struct {
members []*Member
promoteID uint64
want bool
}{
{
// 1/1 members ready, should succeed (quorum = 1, new quorum = 2)
[]*Member{
newTestMember(1, nil, "1", nil),
newTestMemberAsLearner(2, nil, "2", nil),
},
2,
true,
},
{
// 0/1 members ready, should fail (quorum = 1)
[]*Member{
newTestMember(1, nil, "", nil),
newTestMemberAsLearner(2, nil, "2", nil),
},
2,
false,
},
{
// 2/2 members ready, should succeed (quorum = 2)
[]*Member{
newTestMember(1, nil, "1", nil),
newTestMember(2, nil, "2", nil),
newTestMemberAsLearner(3, nil, "3", nil),
},
3,
true,
},
{
// 1/2 members ready, should succeed (quorum = 2)
[]*Member{
newTestMember(1, nil, "1", nil),
newTestMember(2, nil, "", nil),
newTestMemberAsLearner(3, nil, "3", nil),
},
3,
true,
},
{
// 1/3 members ready, should fail (quorum = 2)
[]*Member{
newTestMember(1, nil, "1", nil),
newTestMember(2, nil, "", nil),
newTestMember(3, nil, "", nil),
newTestMemberAsLearner(4, nil, "4", nil),
},
4,
false,
},
{
// 2/3 members ready, should succeed (quorum = 2, new quorum = 3)
[]*Member{
newTestMember(1, nil, "1", nil),
newTestMember(2, nil, "2", nil),
newTestMember(3, nil, "", nil),
newTestMemberAsLearner(4, nil, "4", nil),
},
4,
true,
},
{
// 2/4 members ready, should succeed (quorum = 3)
[]*Member{
newTestMember(1, nil, "1", nil),
newTestMember(2, nil, "2", nil),
newTestMember(3, nil, "", nil),
newTestMember(4, nil, "", nil),
newTestMemberAsLearner(5, nil, "5", nil),
},
5,
true,
},
}
for i, tt := range tests {
c := newTestCluster(tt.members)
if got := c.IsReadyToPromoteMember(tt.promoteID); got != tt.want {
t.Errorf("%d: isReadyToPromoteMember returned %t, want %t", i, got, tt.want)
}
}
}

View File

@@ -239,8 +239,9 @@ func (h *snapshotHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
msgSize := m.Size()
receivedBytes.WithLabelValues(from).Add(float64(msgSize))
msgSizeVal := m.Size()
msgSize := humanize.Bytes(uint64(msgSizeVal))
receivedBytes.WithLabelValues(from).Add(float64(msgSizeVal))
if m.Type != raftpb.MsgSnap {
if h.lg != nil {
@@ -269,11 +270,11 @@ func (h *snapshotHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
zap.String("local-member-id", h.localID.String()),
zap.String("remote-snapshot-sender-id", from),
zap.Uint64("incoming-snapshot-index", m.Snapshot.Metadata.Index),
zap.Int("incoming-snapshot-message-size-bytes", msgSize),
zap.String("incoming-snapshot-message-size", humanize.Bytes(uint64(msgSize))),
zap.Int("incoming-snapshot-message-size-bytes", msgSizeVal),
zap.String("incoming-snapshot-message-size", msgSize),
)
} else {
plog.Infof("receiving database snapshot [index:%d, from %s] ...", m.Snapshot.Metadata.Index, types.ID(m.From))
plog.Infof("receiving database snapshot [index: %d, from: %s, raft message size: %s]", m.Snapshot.Metadata.Index, types.ID(m.From), msgSize)
}
// save incoming database snapshot.
@@ -296,8 +297,10 @@ func (h *snapshotHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
return
}
dbSize := humanize.Bytes(uint64(n))
receivedBytes.WithLabelValues(from).Add(float64(n))
downloadTook := time.Since(start)
if h.lg != nil {
h.lg.Info(
"received and saved database snapshot",
@@ -305,10 +308,11 @@ func (h *snapshotHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
zap.String("remote-snapshot-sender-id", from),
zap.Uint64("incoming-snapshot-index", m.Snapshot.Metadata.Index),
zap.Int64("incoming-snapshot-size-bytes", n),
zap.String("incoming-snapshot-size", humanize.Bytes(uint64(n))),
zap.String("incoming-snapshot-size", dbSize),
zap.String("download-took", downloadTook.String()),
)
} else {
plog.Infof("received and saved database snapshot [index: %d, from: %s] successfully", m.Snapshot.Metadata.Index, types.ID(m.From))
plog.Infof("successfully received and saved database snapshot [index: %d, from: %s, raft message size: %s, db size: %s, took: %s]", m.Snapshot.Metadata.Index, types.ID(m.From), msgSize, dbSize, downloadTook.String())
}
if err := h.r.Process(context.TODO(), m); err != nil {

View File

@@ -78,16 +78,18 @@ func (s *snapshotSender) send(merged snap.Message) {
u := s.picker.pick()
req := createPostRequest(u, RaftSnapshotPrefix, body, "application/octet-stream", s.tr.URLs, s.from, s.cid)
snapshotTotalSizeVal := uint64(merged.TotalSize)
snapshotTotalSize := humanize.Bytes(snapshotTotalSizeVal)
if s.tr.Logger != nil {
s.tr.Logger.Info(
"sending database snapshot",
zap.Uint64("snapshot-index", m.Snapshot.Metadata.Index),
zap.String("remote-peer-id", to),
zap.Int64("bytes", merged.TotalSize),
zap.String("size", humanize.Bytes(uint64(merged.TotalSize))),
zap.String("size", snapshotTotalSize),
)
} else {
plog.Infof("start to send database snapshot [index: %d, to %s]...", m.Snapshot.Metadata.Index, types.ID(m.To))
plog.Infof("start to send database snapshot [index: %d, to %s, size %s]...", m.Snapshot.Metadata.Index, types.ID(m.To), snapshotTotalSize)
}
snapshotSendInflights.WithLabelValues(to).Inc()
@@ -104,7 +106,7 @@ func (s *snapshotSender) send(merged snap.Message) {
zap.Uint64("snapshot-index", m.Snapshot.Metadata.Index),
zap.String("remote-peer-id", to),
zap.Int64("bytes", merged.TotalSize),
zap.String("size", humanize.Bytes(uint64(merged.TotalSize))),
zap.String("size", snapshotTotalSize),
zap.Error(err),
)
} else {
@@ -137,7 +139,7 @@ func (s *snapshotSender) send(merged snap.Message) {
zap.Uint64("snapshot-index", m.Snapshot.Metadata.Index),
zap.String("remote-peer-id", to),
zap.Int64("bytes", merged.TotalSize),
zap.String("size", humanize.Bytes(uint64(merged.TotalSize))),
zap.String("size", snapshotTotalSize),
)
} else {
plog.Infof("database snapshot [index: %d, to: %s] sent out successfully", m.Snapshot.Metadata.Index, types.ID(m.To))

View File

@@ -57,6 +57,7 @@ var (
"3.1.0": {streamTypeMsgAppV2, streamTypeMessage},
"3.2.0": {streamTypeMsgAppV2, streamTypeMessage},
"3.3.0": {streamTypeMsgAppV2, streamTypeMessage},
"3.4.0": {streamTypeMsgAppV2, streamTypeMessage},
}
)

View File

@@ -22,16 +22,17 @@ import (
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"time"
"github.com/coreos/pkg/capnslog"
"go.etcd.io/etcd/etcdserver/api/snap/snappb"
pioutil "go.etcd.io/etcd/pkg/ioutil"
"go.etcd.io/etcd/pkg/pbutil"
"go.etcd.io/etcd/raft"
"go.etcd.io/etcd/raft/raftpb"
"github.com/coreos/pkg/capnslog"
"go.etcd.io/etcd/wal/walpb"
"go.uber.org/zap"
)
@@ -108,21 +109,37 @@ func (s *Snapshotter) save(snapshot *raftpb.Snapshot) error {
return nil
}
// Load returns the newest snapshot.
func (s *Snapshotter) Load() (*raftpb.Snapshot, error) {
return s.loadMatching(func(*raftpb.Snapshot) bool { return true })
}
// LoadNewestAvailable loads the newest snapshot available that is in walSnaps.
func (s *Snapshotter) LoadNewestAvailable(walSnaps []walpb.Snapshot) (*raftpb.Snapshot, error) {
return s.loadMatching(func(snapshot *raftpb.Snapshot) bool {
m := snapshot.Metadata
for i := len(walSnaps) - 1; i >= 0; i-- {
if m.Term == walSnaps[i].Term && m.Index == walSnaps[i].Index {
return true
}
}
return false
})
}
// loadMatching returns the newest snapshot where matchFn returns true.
func (s *Snapshotter) loadMatching(matchFn func(*raftpb.Snapshot) bool) (*raftpb.Snapshot, error) {
names, err := s.snapNames()
if err != nil {
return nil, err
}
var snap *raftpb.Snapshot
for _, name := range names {
if snap, err = loadSnap(s.lg, s.dir, name); err == nil {
break
if snap, err = loadSnap(s.lg, s.dir, name); err == nil && matchFn(snap) {
return snap, nil
}
}
if err != nil {
return nil, ErrNoSnapshot
}
return snap, nil
return nil, ErrNoSnapshot
}
func loadSnap(lg *zap.Logger, dir, name string) (*raftpb.Snapshot, error) {
@@ -226,6 +243,10 @@ func (s *Snapshotter) snapNames() ([]string, error) {
if err != nil {
return nil, err
}
names, err = s.cleanupSnapdir(names)
if err != nil {
return nil, err
}
snaps := checkSuffix(s.lg, names)
if len(snaps) == 0 {
return nil, ErrNoSnapshot
@@ -253,3 +274,64 @@ func checkSuffix(lg *zap.Logger, names []string) []string {
}
return snaps
}
// cleanupSnapdir removes any files that should not be in the snapshot directory:
// - db.tmp prefixed files that can be orphaned by defragmentation
func (s *Snapshotter) cleanupSnapdir(filenames []string) (names []string, err error) {
for _, filename := range filenames {
if strings.HasPrefix(filename, "db.tmp") {
if s.lg != nil {
s.lg.Info("found orphaned defragmentation file; deleting", zap.String("path", filename))
} else {
plog.Infof("found orphaned defragmentation file; deleting: %s", filename)
}
if rmErr := os.Remove(filepath.Join(s.dir, filename)); rmErr != nil && !os.IsNotExist(rmErr) {
return nil, fmt.Errorf("failed to remove orphaned .snap.db file %s: %v", filename, rmErr)
}
continue
}
names = append(names, filename)
}
return names, nil
}
func (s *Snapshotter) ReleaseSnapDBs(snap raftpb.Snapshot) error {
dir, err := os.Open(s.dir)
if err != nil {
return err
}
defer dir.Close()
filenames, err := dir.Readdirnames(-1)
if err != nil {
return err
}
for _, filename := range filenames {
if strings.HasSuffix(filename, ".snap.db") {
hexIndex := strings.TrimSuffix(filepath.Base(filename), ".snap.db")
index, err := strconv.ParseUint(hexIndex, 16, 64)
if err != nil {
if s.lg != nil {
s.lg.Warn("failed to parse index from filename", zap.String("path", filename), zap.String("error", err.Error()))
} else {
plog.Warningf("failed to parse index from filename: %s (%v)", filename, err)
}
continue
}
if index < snap.Metadata.Index {
if s.lg != nil {
s.lg.Warn("found orphaned .snap.db file; deleting", zap.String("path", filename))
} else {
plog.Warningf("found orphaned .snap.db file; deleting: %s", filename)
}
if rmErr := os.Remove(filepath.Join(s.dir, filename)); rmErr != nil && !os.IsNotExist(rmErr) {
if s.lg != nil {
s.lg.Warn("failed to remove orphaned .snap.db file", zap.String("path", filename), zap.Error(rmErr))
} else {
plog.Warningf("failed to remove orphaned .snap.db file: %s (%v)", filename, rmErr)
}
}
}
}
}
return nil
}

View File

@@ -23,7 +23,10 @@ import (
"reflect"
"testing"
"go.etcd.io/etcd/pkg/fileutil"
"go.etcd.io/etcd/raft/raftpb"
"go.etcd.io/etcd/wal/walpb"
"go.uber.org/zap"
)
@@ -166,12 +169,47 @@ func TestLoadNewestSnap(t *testing.T) {
t.Fatal(err)
}
g, err := ss.Load()
if err != nil {
t.Errorf("err = %v, want nil", err)
cases := []struct {
name string
availableWalSnaps []walpb.Snapshot
expected *raftpb.Snapshot
}{
{
name: "load-newest",
expected: &newSnap,
},
{
name: "loadnewestavailable-newest",
availableWalSnaps: []walpb.Snapshot{{Index: 0, Term: 0}, {Index: 1, Term: 1}, {Index: 5, Term: 1}},
expected: &newSnap,
},
{
name: "loadnewestavailable-newest-unsorted",
availableWalSnaps: []walpb.Snapshot{{Index: 5, Term: 1}, {Index: 1, Term: 1}, {Index: 0, Term: 0}},
expected: &newSnap,
},
{
name: "loadnewestavailable-previous",
availableWalSnaps: []walpb.Snapshot{{Index: 0, Term: 0}, {Index: 1, Term: 1}},
expected: testSnap,
},
}
if !reflect.DeepEqual(g, &newSnap) {
t.Errorf("snap = %#v, want %#v", g, &newSnap)
for _, tc := range cases {
t.Run(tc.name, func(t *testing.T) {
var err error
var g *raftpb.Snapshot
if tc.availableWalSnaps != nil {
g, err = ss.LoadNewestAvailable(tc.availableWalSnaps)
} else {
g, err = ss.Load()
}
if err != nil {
t.Errorf("err = %v, want nil", err)
}
if !reflect.DeepEqual(g, tc.expected) {
t.Errorf("snap = %#v, want %#v", g, tc.expected)
}
})
}
}
@@ -229,3 +267,42 @@ func TestAllSnapshotBroken(t *testing.T) {
t.Errorf("err = %v, want %v", err, ErrNoSnapshot)
}
}
func TestReleaseSnapDBs(t *testing.T) {
dir := filepath.Join(os.TempDir(), "snapshot")
err := os.Mkdir(dir, 0700)
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(dir)
snapIndices := []uint64{100, 200, 300, 400}
for _, index := range snapIndices {
filename := filepath.Join(dir, fmt.Sprintf("%016x.snap.db", index))
if err := ioutil.WriteFile(filename, []byte("snap file\n"), 0644); err != nil {
t.Fatal(err)
}
}
ss := New(zap.NewExample(), dir)
if err := ss.ReleaseSnapDBs(raftpb.Snapshot{Metadata: raftpb.SnapshotMetadata{Index: 300}}); err != nil {
t.Fatal(err)
}
deleted := []uint64{100, 200}
for _, index := range deleted {
filename := filepath.Join(dir, fmt.Sprintf("%016x.snap.db", index))
if fileutil.Exist(filename) {
t.Errorf("expected %s (index: %d) to be deleted, but it still exists", filename, index)
}
}
retained := []uint64{300, 400}
for _, index := range retained {
filename := filepath.Join(dir, fmt.Sprintf("%016x.snap.db", index))
if !fileutil.Exist(filename) {
t.Errorf("expected %s (index: %d) to be retained, but it no longer exists", filename, index)
}
}
}

View File

@@ -218,7 +218,7 @@ func (d *discovery) createSelf(contents string) error {
return err
}
func (d *discovery) checkCluster() ([]*client.Node, int, uint64, error) {
func (d *discovery) checkCluster() ([]*client.Node, uint64, uint64, error) {
configKey := path.Join("/", d.cluster, "_config")
ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
// find cluster size
@@ -247,7 +247,7 @@ func (d *discovery) checkCluster() ([]*client.Node, int, uint64, error) {
}
return nil, 0, 0, err
}
size, err := strconv.Atoi(resp.Node.Value)
size, err := strconv.ParseUint(resp.Node.Value, 10, 0)
if err != nil {
return nil, 0, 0, ErrBadSizeKey
}
@@ -288,7 +288,7 @@ func (d *discovery) checkCluster() ([]*client.Node, int, uint64, error) {
if path.Base(nodes[i].Key) == path.Base(d.selfKey()) {
break
}
if i >= size-1 {
if uint64(i) >= size-1 {
return nodes[:size], size, resp.Index, ErrFullCluster
}
}
@@ -316,7 +316,7 @@ func (d *discovery) logAndBackoffForRetry(step string) {
d.clock.Sleep(retryTimeInSecond)
}
func (d *discovery) checkClusterRetry() ([]*client.Node, int, uint64, error) {
func (d *discovery) checkClusterRetry() ([]*client.Node, uint64, uint64, error) {
if d.retries < nRetries {
d.logAndBackoffForRetry("cluster status check")
return d.checkCluster()
@@ -336,8 +336,8 @@ func (d *discovery) waitNodesRetry() ([]*client.Node, error) {
return nil, ErrTooManyRetries
}
func (d *discovery) waitNodes(nodes []*client.Node, size int, index uint64) ([]*client.Node, error) {
if len(nodes) > size {
func (d *discovery) waitNodes(nodes []*client.Node, size uint64, index uint64) ([]*client.Node, error) {
if uint64(len(nodes)) > size {
nodes = nodes[:size]
}
// watch from the next index
@@ -369,16 +369,16 @@ func (d *discovery) waitNodes(nodes []*client.Node, size int, index uint64) ([]*
}
// wait for others
for len(all) < size {
for uint64(len(all)) < size {
if d.lg != nil {
d.lg.Info(
"found peers from discovery server; waiting for more",
zap.String("discovery-url", d.url.String()),
zap.Int("found-peers", len(all)),
zap.Int("needed-peers", size-len(all)),
zap.Int("needed-peers", int(size-uint64(len(all)))),
)
} else {
plog.Noticef("found %d peer(s), waiting for %d more", len(all), size-len(all))
plog.Noticef("found %d peer(s), waiting for %d more", len(all), size-uint64(len(all)))
}
resp, err := w.Next(context.Background())
if err != nil {
@@ -415,7 +415,7 @@ func (d *discovery) selfKey() string {
return path.Join("/", d.cluster, d.id.String())
}
func nodesToCluster(ns []*client.Node, size int) (string, error) {
func nodesToCluster(ns []*client.Node, size uint64) (string, error) {
s := make([]string, len(ns))
for i, n := range ns {
s[i] = n.Value
@@ -425,7 +425,7 @@ func nodesToCluster(ns []*client.Node, size int) (string, error) {
if err != nil {
return us, ErrInvalidURL
}
if m.Len() != size {
if uint64(m.Len()) != size {
return us, ErrDuplicateName
}
return us, nil

View File

@@ -217,7 +217,7 @@ func TestCheckCluster(t *testing.T) {
if reflect.DeepEqual(ns, tt.nodes) {
t.Errorf("#%d: nodes = %v, want %v", i, ns, tt.nodes)
}
if size != tt.wsize {
if size != uint64(tt.wsize) {
t.Errorf("#%d: size = %v, want %d", i, size, tt.wsize)
}
if index != tt.index {
@@ -301,7 +301,7 @@ func TestWaitNodes(t *testing.T) {
fc.Advance(time.Second * (0x1 << i))
}
}()
g, err := d.waitNodes(tt.nodes, 3, 0) // we do not care about index in this test
g, err := d.waitNodes(tt.nodes, uint64(3), 0) // we do not care about index in this test
if err != nil {
t.Errorf("#%d: err = %v, want %v", i, err, nil)
}
@@ -348,7 +348,7 @@ func TestCreateSelf(t *testing.T) {
func TestNodesToCluster(t *testing.T) {
tests := []struct {
nodes []*client.Node
size int
size uint64
wcluster string
werr error
}{

View File

@@ -104,5 +104,5 @@ func TestNodeExternClone(t *testing.T) {
func sameSlice(a, b []*NodeExtern) bool {
ah := (*reflect.SliceHeader)(unsafe.Pointer(&a))
bh := (*reflect.SliceHeader)(unsafe.Pointer(&b))
return *ah == *bh
return ah.Data == bh.Data && ah.Len == bh.Len && ah.Cap == bh.Cap
}

View File

@@ -844,7 +844,7 @@ func TestStoreWatchSlowConsumer(t *testing.T) {
s.Watch("/foo", true, true, 0) // stream must be true
// Fill watch channel with 100 events
for i := 1; i <= 100; i++ {
s.Set("/foo", false, string(i), v2store.TTLOptionSet{ExpireTime: v2store.Permanent}) // ok
s.Set("/foo", false, string(rune(i)), v2store.TTLOptionSet{ExpireTime: v2store.Permanent}) // ok
}
// testutil.AssertEqual(t, s.WatcherHub.count, int64(1))
s.Set("/foo", false, "101", v2store.TTLOptionSet{ExpireTime: v2store.Permanent}) // ok

View File

@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build !v2v3
// +build !v2v3
package v2store_test

View File

@@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
//go:build v2v3
// +build v2v3
package v2store_test

View File

@@ -31,7 +31,6 @@ import (
const (
grpcOverheadBytes = 512 * 1024
maxStreams = math.MaxUint32
maxSendBytes = math.MaxInt32
)
@@ -53,7 +52,7 @@ func Server(s *etcdserver.EtcdServer, tls *tls.Config, gopts ...grpc.ServerOptio
)))
opts = append(opts, grpc.MaxRecvMsgSize(int(s.Cfg.MaxRequestBytes+grpcOverheadBytes)))
opts = append(opts, grpc.MaxSendMsgSize(maxSendBytes))
opts = append(opts, grpc.MaxConcurrentStreams(maxStreams))
opts = append(opts, grpc.MaxConcurrentStreams(s.Cfg.MaxConcurrentStreams))
grpcServer := grpc.NewServer(append(opts, gopts...)...)
pb.RegisterKVServer(grpcServer, NewQuotaKVServer(s))

View File

@@ -16,17 +16,17 @@ package v3rpc
import (
"context"
"strings"
"sync"
"time"
"github.com/coreos/pkg/capnslog"
"go.etcd.io/etcd/etcdserver"
"go.etcd.io/etcd/etcdserver/api"
"go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes"
pb "go.etcd.io/etcd/etcdserver/etcdserverpb"
"go.etcd.io/etcd/pkg/types"
"go.etcd.io/etcd/raft"
"github.com/coreos/pkg/capnslog"
pb "go.etcd.io/etcd/etcdserver/etcdserverpb"
"go.uber.org/zap"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
@@ -54,6 +54,12 @@ func newUnaryInterceptor(s *etcdserver.EtcdServer) grpc.UnaryServerInterceptor {
md, ok := metadata.FromIncomingContext(ctx)
if ok {
ver, vs := "unknown", metadataGet(md, rpctypes.MetadataClientAPIVersionKey)
if len(vs) > 0 {
ver = vs[0]
}
clientRequests.WithLabelValues("unary", ver).Inc()
if ks := md[rpctypes.MetadataRequireLeaderKey]; len(ks) > 0 && ks[0] == rpctypes.MetadataHasLeader {
if s.Leader() == types.ID(raft.None) {
return nil, rpctypes.ErrGRPCNoLeader
@@ -200,13 +206,19 @@ func newStreamInterceptor(s *etcdserver.EtcdServer) grpc.StreamServerInterceptor
md, ok := metadata.FromIncomingContext(ss.Context())
if ok {
ver, vs := "unknown", metadataGet(md, rpctypes.MetadataClientAPIVersionKey)
if len(vs) > 0 {
ver = vs[0]
}
clientRequests.WithLabelValues("stream", ver).Inc()
if ks := md[rpctypes.MetadataRequireLeaderKey]; len(ks) > 0 && ks[0] == rpctypes.MetadataHasLeader {
if s.Leader() == types.ID(raft.None) {
return rpctypes.ErrGRPCNoLeader
}
cctx, cancel := context.WithCancel(ss.Context())
ss = serverStreamWithCtx{ctx: cctx, cancel: &cancel, ServerStream: ss}
ctx := newCancellableContext(ss.Context())
ss = serverStreamWithCtx{ctx: ctx, ServerStream: ss}
smap.mu.Lock()
smap.streams[ss] = struct{}{}
@@ -216,9 +228,9 @@ func newStreamInterceptor(s *etcdserver.EtcdServer) grpc.StreamServerInterceptor
smap.mu.Lock()
delete(smap.streams, ss)
smap.mu.Unlock()
cancel()
// TODO: investigate whether the reason for cancellation here is useful to know
ctx.Cancel(nil)
}()
}
}
@@ -226,10 +238,52 @@ func newStreamInterceptor(s *etcdserver.EtcdServer) grpc.StreamServerInterceptor
}
}
// cancellableContext wraps a context with new cancellable context that allows a
// specific cancellation error to be preserved and later retrieved using the
// Context.Err() function. This is so downstream context users can disambiguate
// the reason for the cancellation which could be from the client (for example)
// or from this interceptor code.
type cancellableContext struct {
context.Context
lock sync.RWMutex
cancel context.CancelFunc
cancelReason error
}
func newCancellableContext(parent context.Context) *cancellableContext {
ctx, cancel := context.WithCancel(parent)
return &cancellableContext{
Context: ctx,
cancel: cancel,
}
}
// Cancel stores the cancellation reason and then delegates to context.WithCancel
// against the parent context.
func (c *cancellableContext) Cancel(reason error) {
c.lock.Lock()
c.cancelReason = reason
c.lock.Unlock()
c.cancel()
}
// Err will return the preserved cancel reason error if present, and will
// otherwise return the underlying error from the parent context.
func (c *cancellableContext) Err() error {
c.lock.RLock()
defer c.lock.RUnlock()
if c.cancelReason != nil {
return c.cancelReason
}
return c.Context.Err()
}
type serverStreamWithCtx struct {
grpc.ServerStream
ctx context.Context
cancel *context.CancelFunc
// ctx is used so that we can preserve a reason for cancellation.
ctx *cancellableContext
}
func (ssc serverStreamWithCtx) Context() context.Context { return ssc.ctx }
@@ -261,7 +315,7 @@ func monitorLeader(s *etcdserver.EtcdServer) *streamsMap {
smap.mu.Lock()
for ss := range smap.streams {
if ssWithCtx, ok := ss.(serverStreamWithCtx); ok {
(*ssWithCtx.cancel)()
ssWithCtx.ctx.Cancel(rpctypes.ErrGRPCNoLeader)
<-ss.Context().Done()
}
}
@@ -274,3 +328,8 @@ func monitorLeader(s *etcdserver.EtcdServer) *streamsMap {
return smap
}
func metadataGet(md metadata.MD, k string) []string {
k = strings.ToLower(k)
return md[k]
}

View File

@@ -18,7 +18,9 @@ import (
"context"
"crypto/sha256"
"io"
"time"
"github.com/dustin/go-humanize"
"go.etcd.io/etcd/auth"
"go.etcd.io/etcd/etcdserver"
"go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes"
@@ -98,6 +100,9 @@ func (ms *maintenanceServer) Defragment(ctx context.Context, sr *pb.DefragmentRe
return &pb.DefragmentResponse{}, nil
}
// big enough size to hold >1 OS pages in the buffer
const snapshotSendBufferSize = 32 * 1024
func (ms *maintenanceServer) Snapshot(sr *pb.SnapshotRequest, srv pb.Maintenance_SnapshotServer) error {
snap := ms.bg.Backend().Snapshot()
pr, pw := io.Pipe()
@@ -116,19 +121,46 @@ func (ms *maintenanceServer) Snapshot(sr *pb.SnapshotRequest, srv pb.Maintenance
pw.Close()
}()
// send file data
// record SHA digest of snapshot data
// used for integrity checks during snapshot restore operation
h := sha256.New()
br := int64(0)
buf := make([]byte, 32*1024)
sz := snap.Size()
for br < sz {
// buffer just holds read bytes from stream
// response size is multiple of OS page size, fetched in boltdb
// e.g. 4*1024
buf := make([]byte, snapshotSendBufferSize)
sent := int64(0)
total := snap.Size()
size := humanize.Bytes(uint64(total))
start := time.Now()
if ms.lg != nil {
ms.lg.Info("sending database snapshot to client",
zap.Int64("total-bytes", total),
zap.String("size", size),
)
} else {
plog.Infof("sending database snapshot to client %s [%d bytes]", size, total)
}
for total-sent > 0 {
n, err := io.ReadFull(pr, buf)
if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
return togRPCError(err)
}
br += int64(n)
sent += int64(n)
// if total is x * snapshotSendBufferSize. it is possible that
// resp.RemainingBytes == 0
// resp.Blob == zero byte but not nil
// does this make server response sent to client nil in proto
// and client stops receiving from snapshot stream before
// server sends snapshot SHA?
// No, the client will still receive non-nil response
// until server closes the stream with EOF
resp := &pb.SnapshotResponse{
RemainingBytes: uint64(sz - br),
RemainingBytes: uint64(total - sent),
Blob: buf[:n],
}
if err = srv.Send(resp); err != nil {
@@ -137,13 +169,34 @@ func (ms *maintenanceServer) Snapshot(sr *pb.SnapshotRequest, srv pb.Maintenance
h.Write(buf[:n])
}
// send sha
// send SHA digest for integrity checks
// during snapshot restore operation
sha := h.Sum(nil)
if ms.lg != nil {
ms.lg.Info("sending database sha256 checksum to client",
zap.Int64("total-bytes", total),
zap.Int("checksum-size", len(sha)),
)
} else {
plog.Infof("sending database sha256 checksum to client [%d bytes]", len(sha))
}
hresp := &pb.SnapshotResponse{RemainingBytes: 0, Blob: sha}
if err := srv.Send(hresp); err != nil {
return togRPCError(err)
}
if ms.lg != nil {
ms.lg.Info("successfully sent database snapshot to client",
zap.Int64("total-bytes", total),
zap.String("size", size),
zap.String("took", humanize.Time(start)),
)
} else {
plog.Infof("successfully sent database snapshot to client %s [%d bytes]", size, total)
}
return nil
}

View File

@@ -39,10 +39,20 @@ var (
},
[]string{"Type", "API"},
)
clientRequests = prometheus.NewCounterVec(prometheus.CounterOpts{
Namespace: "etcd",
Subsystem: "server",
Name: "client_requests_total",
Help: "The total number of client requests per client version.",
},
[]string{"type", "client_api_version"},
)
)
func init() {
prometheus.MustRegister(sentBytes)
prometheus.MustRegister(receivedBytes)
prometheus.MustRegister(streamFailures)
prometheus.MustRegister(clientRequests)
}

View File

@@ -35,6 +35,8 @@ var (
ErrGRPCLeaseExist = status.New(codes.FailedPrecondition, "etcdserver: lease already exists").Err()
ErrGRPCLeaseTTLTooLarge = status.New(codes.OutOfRange, "etcdserver: too large lease TTL").Err()
ErrGRPCWatchCanceled = status.New(codes.Canceled, "etcdserver: watch canceled").Err()
ErrGRPCMemberExist = status.New(codes.FailedPrecondition, "etcdserver: member ID already exist").Err()
ErrGRPCPeerURLExist = status.New(codes.FailedPrecondition, "etcdserver: Peer URLs already exists").Err()
ErrGRPCMemberNotEnoughStarted = status.New(codes.FailedPrecondition, "etcdserver: re-configuration failed due to not enough started members").Err()
@@ -62,6 +64,7 @@ var (
ErrGRPCAuthNotEnabled = status.New(codes.FailedPrecondition, "etcdserver: authentication is not enabled").Err()
ErrGRPCInvalidAuthToken = status.New(codes.Unauthenticated, "etcdserver: invalid auth token").Err()
ErrGRPCInvalidAuthMgmt = status.New(codes.InvalidArgument, "etcdserver: invalid auth management").Err()
ErrGRPCAuthOldRevision = status.New(codes.InvalidArgument, "etcdserver: revision of auth store is old").Err()
ErrGRPCNoLeader = status.New(codes.Unavailable, "etcdserver: no leader").Err()
ErrGRPCNotLeader = status.New(codes.FailedPrecondition, "etcdserver: not leader").Err()
@@ -71,6 +74,7 @@ var (
ErrGRPCTimeout = status.New(codes.Unavailable, "etcdserver: request timed out").Err()
ErrGRPCTimeoutDueToLeaderFail = status.New(codes.Unavailable, "etcdserver: request timed out, possibly due to previous leader failure").Err()
ErrGRPCTimeoutDueToConnectionLost = status.New(codes.Unavailable, "etcdserver: request timed out, possibly due to connection lost").Err()
ErrGRPCTimeoutWaitAppliedIndex = status.New(codes.Unavailable, "etcdserver: request timed out, waiting for the applied index took too long").Err()
ErrGRPCUnhealthy = status.New(codes.Unavailable, "etcdserver: unhealthy cluster").Err()
ErrGRPCCorrupt = status.New(codes.DataLoss, "etcdserver: corrupt cluster").Err()
ErrGPRCNotSupportedForLearner = status.New(codes.Unavailable, "etcdserver: rpc not supported for learner").Err()
@@ -119,6 +123,7 @@ var (
ErrorDesc(ErrGRPCAuthNotEnabled): ErrGRPCAuthNotEnabled,
ErrorDesc(ErrGRPCInvalidAuthToken): ErrGRPCInvalidAuthToken,
ErrorDesc(ErrGRPCInvalidAuthMgmt): ErrGRPCInvalidAuthMgmt,
ErrorDesc(ErrGRPCAuthOldRevision): ErrGRPCAuthOldRevision,
ErrorDesc(ErrGRPCNoLeader): ErrGRPCNoLeader,
ErrorDesc(ErrGRPCNotLeader): ErrGRPCNotLeader,
@@ -128,6 +133,7 @@ var (
ErrorDesc(ErrGRPCTimeout): ErrGRPCTimeout,
ErrorDesc(ErrGRPCTimeoutDueToLeaderFail): ErrGRPCTimeoutDueToLeaderFail,
ErrorDesc(ErrGRPCTimeoutDueToConnectionLost): ErrGRPCTimeoutDueToConnectionLost,
ErrorDesc(ErrGRPCTimeoutWaitAppliedIndex): ErrGRPCTimeoutWaitAppliedIndex,
ErrorDesc(ErrGRPCUnhealthy): ErrGRPCUnhealthy,
ErrorDesc(ErrGRPCCorrupt): ErrGRPCCorrupt,
ErrorDesc(ErrGPRCNotSupportedForLearner): ErrGPRCNotSupportedForLearner,
@@ -177,6 +183,7 @@ var (
ErrPermissionNotGranted = Error(ErrGRPCPermissionNotGranted)
ErrAuthNotEnabled = Error(ErrGRPCAuthNotEnabled)
ErrInvalidAuthToken = Error(ErrGRPCInvalidAuthToken)
ErrAuthOldRevision = Error(ErrGRPCAuthOldRevision)
ErrInvalidAuthMgmt = Error(ErrGRPCInvalidAuthMgmt)
ErrNoLeader = Error(ErrGRPCNoLeader)
@@ -187,6 +194,7 @@ var (
ErrTimeout = Error(ErrGRPCTimeout)
ErrTimeoutDueToLeaderFail = Error(ErrGRPCTimeoutDueToLeaderFail)
ErrTimeoutDueToConnectionLost = Error(ErrGRPCTimeoutDueToConnectionLost)
ErrTimeoutWaitAppliedIndex = Error(ErrGRPCTimeoutWaitAppliedIndex)
ErrUnhealthy = Error(ErrGRPCUnhealthy)
ErrCorrupt = Error(ErrGRPCCorrupt)
ErrBadLeaderTransferee = Error(ErrGRPCBadLeaderTransferee)

View File

@@ -17,4 +17,6 @@ package rpctypes
var (
MetadataRequireLeaderKey = "hasleader"
MetadataHasLeader = "true"
MetadataClientAPIVersionKey = "client-api-version"
)

View File

@@ -53,6 +53,7 @@ var toGRPCErrorMap = map[error]error{
etcdserver.ErrTimeout: rpctypes.ErrGRPCTimeout,
etcdserver.ErrTimeoutDueToLeaderFail: rpctypes.ErrGRPCTimeoutDueToLeaderFail,
etcdserver.ErrTimeoutDueToConnectionLost: rpctypes.ErrGRPCTimeoutDueToConnectionLost,
etcdserver.ErrTimeoutWaitAppliedIndex: rpctypes.ErrGRPCTimeoutWaitAppliedIndex,
etcdserver.ErrUnhealthy: rpctypes.ErrGRPCUnhealthy,
etcdserver.ErrKeyNotFound: rpctypes.ErrGRPCKeyNotFound,
etcdserver.ErrCorrupt: rpctypes.ErrGRPCCorrupt,
@@ -77,6 +78,7 @@ var toGRPCErrorMap = map[error]error{
auth.ErrAuthNotEnabled: rpctypes.ErrGRPCAuthNotEnabled,
auth.ErrInvalidAuthToken: rpctypes.ErrGRPCInvalidAuthToken,
auth.ErrInvalidAuthMgmt: rpctypes.ErrGRPCInvalidAuthMgmt,
auth.ErrAuthOldRevision: rpctypes.ErrGRPCAuthOldRevision,
}
func togRPCError(err error) error {

View File

@@ -16,12 +16,14 @@ package v3rpc
import (
"context"
"fmt"
"io"
"math/rand"
"sync"
"time"
"go.etcd.io/etcd/auth"
"go.etcd.io/etcd/clientv3"
"go.etcd.io/etcd/etcdserver"
"go.etcd.io/etcd/etcdserver/api/v3rpc/rpctypes"
pb "go.etcd.io/etcd/etcdserver/etcdserverpb"
@@ -31,6 +33,8 @@ import (
"go.uber.org/zap"
)
const minWatchProgressInterval = 100 * time.Millisecond
type watchServer struct {
lg *zap.Logger
@@ -46,7 +50,7 @@ type watchServer struct {
// NewWatchServer returns a new watch server.
func NewWatchServer(s *etcdserver.EtcdServer) pb.WatchServer {
return &watchServer{
srv := &watchServer{
lg: s.Cfg.Logger,
clusterID: int64(s.Cluster().ID()),
@@ -58,6 +62,21 @@ func NewWatchServer(s *etcdserver.EtcdServer) pb.WatchServer {
watchable: s.Watchable(),
ag: s,
}
if s.Cfg.WatchProgressNotifyInterval > 0 {
if s.Cfg.WatchProgressNotifyInterval < minWatchProgressInterval {
if srv.lg != nil {
srv.lg.Warn(
"adjusting watch progress notify interval to minimum period",
zap.Duration("min-watch-progress-notify-interval", minWatchProgressInterval),
)
} else {
plog.Warningf("adjusting watch progress notify interval to minimum period %v", minWatchProgressInterval)
}
s.Cfg.WatchProgressNotifyInterval = minWatchProgressInterval
}
SetProgressReportInterval(s.Cfg.WatchProgressNotifyInterval)
}
return srv
}
var (
@@ -189,15 +208,25 @@ func (ws *watchServer) Watch(stream pb.Watch_WatchServer) (err error) {
}
}()
// TODO: There's a race here. When a stream is closed (e.g. due to a cancellation),
// the underlying error (e.g. a gRPC stream error) may be returned and handled
// through errc if the recv goroutine finishes before the send goroutine.
// When the recv goroutine wins, the stream error is retained. When recv loses
// the race, the underlying error is lost (unless the root error is propagated
// through Context.Err() which is not always the case (as callers have to decide
// to implement a custom context to do so). The stdlib context package builtins
// may be insufficient to carry semantically useful errors around and should be
// revisited.
select {
case err = <-errc:
if err == context.Canceled {
err = rpctypes.ErrGRPCWatchCanceled
}
close(sws.ctrlStream)
case <-stream.Context().Done():
err = stream.Context().Err()
// the only server-side cancellation is noleader for now.
if err == context.Canceled {
err = rpctypes.ErrGRPCNoLeader
err = rpctypes.ErrGRPCWatchCanceled
}
}
@@ -205,16 +234,16 @@ func (ws *watchServer) Watch(stream pb.Watch_WatchServer) (err error) {
return err
}
func (sws *serverWatchStream) isWatchPermitted(wcr *pb.WatchCreateRequest) bool {
func (sws *serverWatchStream) isWatchPermitted(wcr *pb.WatchCreateRequest) error {
authInfo, err := sws.ag.AuthInfoFromCtx(sws.gRPCStream.Context())
if err != nil {
return false
return err
}
if authInfo == nil {
// if auth is enabled, IsRangePermitted() can cause an error
authInfo = &auth.AuthInfo{}
}
return sws.ag.AuthStore().IsRangePermitted(authInfo, wcr.Key, wcr.RangeEnd) == nil
return sws.ag.AuthStore().IsRangePermitted(authInfo, wcr.Key, wcr.RangeEnd)
}
func (sws *serverWatchStream) recvLoop() error {
@@ -248,20 +277,37 @@ func (sws *serverWatchStream) recvLoop() error {
creq.RangeEnd = []byte{}
}
if !sws.isWatchPermitted(creq) {
err := sws.isWatchPermitted(creq)
if err != nil {
var cancelReason string
switch err {
case auth.ErrInvalidAuthToken:
cancelReason = rpctypes.ErrGRPCInvalidAuthToken.Error()
case auth.ErrAuthOldRevision:
cancelReason = rpctypes.ErrGRPCAuthOldRevision.Error()
case auth.ErrUserEmpty:
cancelReason = rpctypes.ErrGRPCUserEmpty.Error()
default:
if err != auth.ErrPermissionDenied {
sws.lg.Error("unexpected error code", zap.Error(err))
}
cancelReason = rpctypes.ErrGRPCPermissionDenied.Error()
}
wr := &pb.WatchResponse{
Header: sws.newResponseHeader(sws.watchStream.Rev()),
WatchId: creq.WatchId,
WatchId: clientv3.InvalidWatchID,
Canceled: true,
Created: true,
CancelReason: rpctypes.ErrGRPCPermissionDenied.Error(),
CancelReason: cancelReason,
}
select {
case sws.ctrlStream <- wr:
continue
case <-sws.closec:
return nil
}
return nil
}
filters := FiltersFromRequest(creq)
@@ -284,7 +330,10 @@ func (sws *serverWatchStream) recvLoop() error {
sws.fragment[id] = true
}
sws.mu.Unlock()
} else {
id = clientv3.InvalidWatchID
}
wr := &pb.WatchResponse{
Header: sws.newResponseHeader(wsrev),
WatchId: int64(id),
@@ -321,7 +370,7 @@ func (sws *serverWatchStream) recvLoop() error {
if uv.ProgressRequest != nil {
sws.ctrlStream <- &pb.WatchResponse{
Header: sws.newResponseHeader(sws.watchStream.Rev()),
WatchId: -1, // response is not associated with any WatchId and will be broadcast to all watch channels
WatchId: clientv3.InvalidWatchID, // response is not associated with any WatchId and will be broadcast to all watch channels
}
}
default:
@@ -372,7 +421,7 @@ func (sws *serverWatchStream) sendLoop() {
sws.mu.RUnlock()
for i := range evs {
events[i] = &evs[i]
if needPrevKV {
if needPrevKV && !isCreateEvent(evs[i]) {
opt := mvcc.RangeOptions{Rev: evs[i].Kv.ModRevision - 1}
r, err := sws.watchable.Range(evs[i].Kv.Key, nil, opt)
if err == nil && len(r.KVs) != 0 {
@@ -460,7 +509,12 @@ func (sws *serverWatchStream) sendLoop() {
// track id creation
wid := mvcc.WatchID(c.WatchId)
if c.Canceled {
if !(!(c.Canceled && c.Created) || wid == clientv3.InvalidWatchID) {
panic(fmt.Sprintf("unexpected watchId: %d, wanted: %d, since both 'Canceled' and 'Created' are true", wid, clientv3.InvalidWatchID))
}
if c.Canceled && wid != clientv3.InvalidWatchID {
delete(ids, wid)
continue
}
@@ -506,6 +560,10 @@ func (sws *serverWatchStream) sendLoop() {
}
}
func isCreateEvent(e mvccpb.Event) bool {
return e.Type == mvccpb.PUT && e.Kv.CreateRevision == e.Kv.ModRevision
}
func sendFragments(
wr *pb.WatchResponse,
maxRequestBytes int,

View File

@@ -26,16 +26,13 @@ import (
"go.etcd.io/etcd/lease"
"go.etcd.io/etcd/mvcc"
"go.etcd.io/etcd/mvcc/mvccpb"
"go.etcd.io/etcd/pkg/traceutil"
"go.etcd.io/etcd/pkg/types"
"github.com/gogo/protobuf/proto"
"go.uber.org/zap"
)
const (
warnApplyDuration = 100 * time.Millisecond
)
type applyResult struct {
resp proto.Message
err error
@@ -43,17 +40,18 @@ type applyResult struct {
// to being logically reflected by the node. Currently only used for
// Compaction requests.
physc <-chan struct{}
trace *traceutil.Trace
}
// applierV3 is the interface for processing V3 raft messages
type applierV3 interface {
Apply(r *pb.InternalRaftRequest) *applyResult
Put(txn mvcc.TxnWrite, p *pb.PutRequest) (*pb.PutResponse, error)
Range(txn mvcc.TxnRead, r *pb.RangeRequest) (*pb.RangeResponse, error)
Put(txn mvcc.TxnWrite, p *pb.PutRequest) (*pb.PutResponse, *traceutil.Trace, error)
Range(ctx context.Context, txn mvcc.TxnRead, r *pb.RangeRequest) (*pb.RangeResponse, error)
DeleteRange(txn mvcc.TxnWrite, dr *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error)
Txn(rt *pb.TxnRequest) (*pb.TxnResponse, error)
Compaction(compaction *pb.CompactionRequest) (*pb.CompactionResponse, <-chan struct{}, error)
Compaction(compaction *pb.CompactionRequest) (*pb.CompactionResponse, <-chan struct{}, *traceutil.Trace, error)
LeaseGrant(lc *pb.LeaseGrantRequest) (*pb.LeaseGrantResponse, error)
LeaseRevoke(lc *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error)
@@ -113,21 +111,24 @@ func (s *EtcdServer) newApplierV3() applierV3 {
func (a *applierV3backend) Apply(r *pb.InternalRaftRequest) *applyResult {
ar := &applyResult{}
defer func(start time.Time) {
warnOfExpensiveRequest(a.s.getLogger(), start, &pb.InternalRaftStringer{Request: r}, ar.resp, ar.err)
warnOfExpensiveRequest(a.s.getLogger(), a.s.Cfg.WarningApplyDuration, start, &pb.InternalRaftStringer{Request: r}, ar.resp, ar.err)
if ar.err != nil {
warnOfFailedRequest(a.s.getLogger(), start, &pb.InternalRaftStringer{Request: r}, ar.resp, ar.err)
}
}(time.Now())
// call into a.s.applyV3.F instead of a.F so upper appliers can check individual calls
switch {
case r.Range != nil:
ar.resp, ar.err = a.s.applyV3.Range(nil, r.Range)
ar.resp, ar.err = a.s.applyV3.Range(context.TODO(), nil, r.Range)
case r.Put != nil:
ar.resp, ar.err = a.s.applyV3.Put(nil, r.Put)
ar.resp, ar.trace, ar.err = a.s.applyV3.Put(nil, r.Put)
case r.DeleteRange != nil:
ar.resp, ar.err = a.s.applyV3.DeleteRange(nil, r.DeleteRange)
case r.Txn != nil:
ar.resp, ar.err = a.s.applyV3.Txn(r.Txn)
case r.Compaction != nil:
ar.resp, ar.physc, ar.err = a.s.applyV3.Compaction(r.Compaction)
ar.resp, ar.physc, ar.trace, ar.err = a.s.applyV3.Compaction(r.Compaction)
case r.LeaseGrant != nil:
ar.resp, ar.err = a.s.applyV3.LeaseGrant(r.LeaseGrant)
case r.LeaseRevoke != nil:
@@ -174,32 +175,39 @@ func (a *applierV3backend) Apply(r *pb.InternalRaftRequest) *applyResult {
return ar
}
func (a *applierV3backend) Put(txn mvcc.TxnWrite, p *pb.PutRequest) (resp *pb.PutResponse, err error) {
func (a *applierV3backend) Put(txn mvcc.TxnWrite, p *pb.PutRequest) (resp *pb.PutResponse, trace *traceutil.Trace, err error) {
resp = &pb.PutResponse{}
resp.Header = &pb.ResponseHeader{}
trace = traceutil.New("put",
a.s.getLogger(),
traceutil.Field{Key: "key", Value: string(p.Key)},
traceutil.Field{Key: "req_size", Value: p.Size()},
)
val, leaseID := p.Value, lease.LeaseID(p.Lease)
if txn == nil {
if leaseID != lease.NoLease {
if l := a.s.lessor.Lookup(leaseID); l == nil {
return nil, lease.ErrLeaseNotFound
return nil, nil, lease.ErrLeaseNotFound
}
}
txn = a.s.KV().Write()
txn = a.s.KV().Write(trace)
defer txn.End()
}
var rr *mvcc.RangeResult
if p.IgnoreValue || p.IgnoreLease || p.PrevKv {
trace.DisableStep()
rr, err = txn.Range(p.Key, nil, mvcc.RangeOptions{})
if err != nil {
return nil, err
return nil, nil, err
}
trace.EnableStep()
trace.Step("get previous kv pair")
}
if p.IgnoreValue || p.IgnoreLease {
if rr == nil || len(rr.KVs) == 0 {
// ignore_{lease,value} flag expects previous key-value pair
return nil, ErrKeyNotFound
return nil, nil, ErrKeyNotFound
}
}
if p.IgnoreValue {
@@ -215,7 +223,8 @@ func (a *applierV3backend) Put(txn mvcc.TxnWrite, p *pb.PutRequest) (resp *pb.Pu
}
resp.Header.Revision = txn.Put(p.Key, val, leaseID)
return resp, nil
trace.AddField(traceutil.Field{Key: "response_revision", Value: resp.Header.Revision})
return resp, trace, nil
}
func (a *applierV3backend) DeleteRange(txn mvcc.TxnWrite, dr *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error) {
@@ -224,7 +233,7 @@ func (a *applierV3backend) DeleteRange(txn mvcc.TxnWrite, dr *pb.DeleteRangeRequ
end := mkGteRange(dr.RangeEnd)
if txn == nil {
txn = a.s.kv.Write()
txn = a.s.kv.Write(traceutil.TODO())
defer txn.End()
}
@@ -245,12 +254,14 @@ func (a *applierV3backend) DeleteRange(txn mvcc.TxnWrite, dr *pb.DeleteRangeRequ
return resp, nil
}
func (a *applierV3backend) Range(txn mvcc.TxnRead, r *pb.RangeRequest) (*pb.RangeResponse, error) {
func (a *applierV3backend) Range(ctx context.Context, txn mvcc.TxnRead, r *pb.RangeRequest) (*pb.RangeResponse, error) {
trace := traceutil.Get(ctx)
resp := &pb.RangeResponse{}
resp.Header = &pb.ResponseHeader{}
if txn == nil {
txn = a.s.kv.Read()
txn = a.s.kv.Read(trace)
defer txn.End()
}
@@ -327,7 +338,7 @@ func (a *applierV3backend) Range(txn mvcc.TxnRead, r *pb.RangeRequest) (*pb.Rang
rr.KVs = rr.KVs[:r.Limit]
resp.More = true
}
trace.Step("filter and sort the key-value pairs")
resp.Header.Revision = rr.Rev
resp.Count = int64(rr.Count)
resp.Kvs = make([]*mvccpb.KeyValue, len(rr.KVs))
@@ -337,12 +348,13 @@ func (a *applierV3backend) Range(txn mvcc.TxnRead, r *pb.RangeRequest) (*pb.Rang
}
resp.Kvs[i] = &rr.KVs[i]
}
trace.Step("assemble the response")
return resp, nil
}
func (a *applierV3backend) Txn(rt *pb.TxnRequest) (*pb.TxnResponse, error) {
isWrite := !isTxnReadonly(rt)
txn := mvcc.NewReadOnlyTxnWrite(a.s.KV().Read())
txn := mvcc.NewReadOnlyTxnWrite(a.s.KV().Read(traceutil.TODO()))
txnPath := compareToPath(txn, rt)
if isWrite {
@@ -364,7 +376,7 @@ func (a *applierV3backend) Txn(rt *pb.TxnRequest) (*pb.TxnResponse, error) {
// be the revision of the write txn.
if isWrite {
txn.End()
txn = a.s.KV().Write()
txn = a.s.KV().Write(traceutil.TODO())
}
a.applyTxn(txn, rt, txnPath, txnResp)
rev := txn.Rev()
@@ -516,7 +528,7 @@ func (a *applierV3backend) applyTxn(txn mvcc.TxnWrite, rt *pb.TxnRequest, txnPat
respi := tresp.Responses[i].Response
switch tv := req.Request.(type) {
case *pb.RequestOp_RequestRange:
resp, err := a.Range(txn, tv.RequestRange)
resp, err := a.Range(context.TODO(), txn, tv.RequestRange)
if err != nil {
if lg != nil {
lg.Panic("unexpected error during txn", zap.Error(err))
@@ -526,7 +538,7 @@ func (a *applierV3backend) applyTxn(txn mvcc.TxnWrite, rt *pb.TxnRequest, txnPat
}
respi.(*pb.ResponseOp_ResponseRange).ResponseRange = resp
case *pb.RequestOp_RequestPut:
resp, err := a.Put(txn, tv.RequestPut)
resp, _, err := a.Put(txn, tv.RequestPut)
if err != nil {
if lg != nil {
lg.Panic("unexpected error during txn", zap.Error(err))
@@ -557,17 +569,22 @@ func (a *applierV3backend) applyTxn(txn mvcc.TxnWrite, rt *pb.TxnRequest, txnPat
return txns
}
func (a *applierV3backend) Compaction(compaction *pb.CompactionRequest) (*pb.CompactionResponse, <-chan struct{}, error) {
func (a *applierV3backend) Compaction(compaction *pb.CompactionRequest) (*pb.CompactionResponse, <-chan struct{}, *traceutil.Trace, error) {
resp := &pb.CompactionResponse{}
resp.Header = &pb.ResponseHeader{}
ch, err := a.s.KV().Compact(compaction.Revision)
trace := traceutil.New("compact",
a.s.getLogger(),
traceutil.Field{Key: "revision", Value: compaction.Revision},
)
ch, err := a.s.KV().Compact(trace, compaction.Revision)
if err != nil {
return nil, ch, err
return nil, ch, nil, err
}
// get the current revision. which key to get is not important.
rr, _ := a.s.KV().Range([]byte("compaction"), nil, mvcc.RangeOptions{})
resp.Header.Revision = rr.Rev
return resp, ch, err
return resp, ch, trace, err
}
func (a *applierV3backend) LeaseGrant(lc *pb.LeaseGrantRequest) (*pb.LeaseGrantResponse, error) {
@@ -674,8 +691,8 @@ type applierV3Capped struct {
// with Puts so that the number of keys in the store is capped.
func newApplierV3Capped(base applierV3) applierV3 { return &applierV3Capped{applierV3: base} }
func (a *applierV3Capped) Put(txn mvcc.TxnWrite, p *pb.PutRequest) (*pb.PutResponse, error) {
return nil, ErrNoSpace
func (a *applierV3Capped) Put(txn mvcc.TxnWrite, p *pb.PutRequest) (*pb.PutResponse, *traceutil.Trace, error) {
return nil, nil, ErrNoSpace
}
func (a *applierV3Capped) Txn(r *pb.TxnRequest) (*pb.TxnResponse, error) {
@@ -824,13 +841,13 @@ func newQuotaApplierV3(s *EtcdServer, app applierV3) applierV3 {
return &quotaApplierV3{app, NewBackendQuota(s, "v3-applier")}
}
func (a *quotaApplierV3) Put(txn mvcc.TxnWrite, p *pb.PutRequest) (*pb.PutResponse, error) {
func (a *quotaApplierV3) Put(txn mvcc.TxnWrite, p *pb.PutRequest) (*pb.PutResponse, *traceutil.Trace, error) {
ok := a.q.Available(p)
resp, err := a.applierV3.Put(txn, p)
resp, trace, err := a.applierV3.Put(txn, p)
if err == nil && !ok {
err = ErrNoSpace
}
return resp, err
return resp, trace, err
}
func (a *quotaApplierV3) Txn(rt *pb.TxnRequest) (*pb.TxnResponse, error) {

View File

@@ -15,12 +15,14 @@
package etcdserver
import (
"context"
"sync"
"go.etcd.io/etcd/auth"
pb "go.etcd.io/etcd/etcdserver/etcdserverpb"
"go.etcd.io/etcd/lease"
"go.etcd.io/etcd/mvcc"
"go.etcd.io/etcd/pkg/traceutil"
)
type authApplierV3 struct {
@@ -61,9 +63,9 @@ func (aa *authApplierV3) Apply(r *pb.InternalRaftRequest) *applyResult {
return ret
}
func (aa *authApplierV3) Put(txn mvcc.TxnWrite, r *pb.PutRequest) (*pb.PutResponse, error) {
func (aa *authApplierV3) Put(txn mvcc.TxnWrite, r *pb.PutRequest) (*pb.PutResponse, *traceutil.Trace, error) {
if err := aa.as.IsPutPermitted(&aa.authInfo, r.Key); err != nil {
return nil, err
return nil, nil, err
}
if err := aa.checkLeasePuts(lease.LeaseID(r.Lease)); err != nil {
@@ -71,23 +73,23 @@ func (aa *authApplierV3) Put(txn mvcc.TxnWrite, r *pb.PutRequest) (*pb.PutRespon
// be written by this user. It means the user cannot revoke the
// lease so attaching the lease to the newly written key should
// be forbidden.
return nil, err
return nil, nil, err
}
if r.PrevKv {
err := aa.as.IsRangePermitted(&aa.authInfo, r.Key, nil)
if err != nil {
return nil, err
return nil, nil, err
}
}
return aa.applierV3.Put(txn, r)
}
func (aa *authApplierV3) Range(txn mvcc.TxnRead, r *pb.RangeRequest) (*pb.RangeResponse, error) {
func (aa *authApplierV3) Range(ctx context.Context, txn mvcc.TxnRead, r *pb.RangeRequest) (*pb.RangeResponse, error) {
if err := aa.as.IsRangePermitted(&aa.authInfo, r.Key, r.RangeEnd); err != nil {
return nil, err
}
return aa.applierV3.Range(txn, r)
return aa.applierV3.Range(ctx, txn, r)
}
func (aa *authApplierV3) DeleteRange(txn mvcc.TxnWrite, r *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error) {

View File

@@ -16,6 +16,7 @@ package etcdserver
import (
"encoding/json"
"fmt"
"path"
"time"
@@ -114,7 +115,11 @@ func (a *applierV2store) Sync(r *RequestV2) Response {
// applyV2Request interprets r as a call to v2store.X
// and returns a Response interpreted from v2store.Event
func (s *EtcdServer) applyV2Request(r *RequestV2) Response {
defer warnOfExpensiveRequest(s.getLogger(), time.Now(), r, nil, nil)
stringer := panicAlternativeStringer{
stringer: r,
alternative: func() string { return fmt.Sprintf("id:%d,method:%s,path:%s", r.ID, r.Method, r.Path) },
}
defer warnOfExpensiveRequest(s.getLogger(), s.Cfg.WarningApplyDuration, time.Now(), stringer, nil, nil)
switch r.Method {
case "POST":

Some files were not shown because too many files have changed in this diff Show More