Compare commits

...

212 Commits

Author SHA1 Message Date
Gyu-Ho Lee a48106fbc0
version: bump up to v3.0.17+git 2017-01-20 11:40:18 -08:00
Gyu-Ho Lee cc198e22d3
version: bump to v3.0.17 2017-01-20 11:02:34 -08:00
Gyu-Ho Lee fcf813427b
lease/leasehttp: pass min TTL in TestRenewHTTP 2017-01-20 11:02:00 -08:00
Xiang Li 518efab61c etcdctlv3: snapshot restore works with lease key 2017-01-20 10:36:02 -08:00
Anthony Romano 42f9a5ef74
etcdserver, lease: tie lease min ttl to election timeout 2017-01-20 10:35:46 -08:00
Gyu-Ho Lee 21509633ba
version: bump to v3.0.16+git 2017-01-20 10:19:14 -08:00
Gyu-Ho Lee a23109a0c6
version: bump to v3.0.16 2017-01-13 11:29:12 -08:00
Anthony Romano 219a4e9ad5 clientv3: don't reset keepalive stream on grant failure
Was triggering cancelation errors on outstanding KeepAlives if Grant
had to retry.
2017-01-13 11:28:17 -08:00
Anthony Romano 3d050630f4
v3api, rpctypes: add ErrTimeoutDueToConnectionLost
Lack of GRPC code was causing this to look like a halting error to the client.
2017-01-13 11:27:56 -08:00
Anthony Romano 9c66ed2798 clientv3: don't reset stream on keepaliveonce or revoke failure
Would cause the keepalive loop to cancel out.

Fixes #7082
2017-01-13 10:42:01 -08:00
Gyu-Ho Lee a9e2d3d4d3 *: remove 'tools/etcd-top' to drop pcap.h 2016-12-07 10:34:34 -08:00
Gyu-Ho Lee 41e329cd35 *: drop breaking-changes from master branch 2016-12-07 10:34:28 -08:00
Gyu-Ho Lee 3a8b524d36 travis, test: use Go 1.6.4, skip 'gosimple' 2016-12-07 10:28:53 -08:00
Anthony Romano 11668f53db integration: use RequireLeader for TestV3LeaseFailover
Giving Renew() the default request timeout causes TestV3LeaseFailover
to miss its timing constraints. Since it only needs to wait until the
leader recognizes the leader is lost, use RequireLeader to cancel the
keepalive stream before the request times out.
2016-12-07 10:06:28 -08:00
Anthony Romano 7ceca7e046 clientv3/integration: test lease keepalive works following quorum loss 2016-12-07 10:06:28 -08:00
Anthony Romano 395bd2313c v3rpc, etcdserver, leasehttp: ctxize Renew with request timeout
Would retry a few times before returning a not primary error that
the client should never see. Instead, use proper timeouts and
then return a request timeout error on failure.

Fixes #6922
2016-12-07 10:06:19 -08:00
Gyu-Ho Lee b357569bc6
version: bump to v3.0.15+git 2016-11-11 11:17:31 -08:00
Gyu-Ho Lee fc00305a2e
version: bump to v3.0.15 2016-11-10 13:12:43 -08:00
Gyu-Ho Lee f322fe7f0d
clientv3, ctlv3: document range end requirement 2016-11-10 13:10:18 -08:00
Gyu-Ho Lee 049fcd30ea integration: test wrong watcher range 2016-11-10 13:09:13 -08:00
Gyu-Ho Lee 1b702e79db
mvcc: return -1 for wrong watcher range key >= end
Fix https://github.com/coreos/etcd/issues/6819.
2016-11-10 13:08:51 -08:00
Anthony Romano b87190d9dc
integration: test canceling a watcher on disconnected stream 2016-11-10 13:07:24 -08:00
Anthony Romano 83b493f945
clientv3: let watchers cancel when reconnecting 2016-11-10 13:06:47 -08:00
Gyu-Ho Lee 9b69cbd989 version: bump to v3.0.14+git 2016-11-04 13:06:36 -07:00
Gyu-Ho Lee 8a37349097 version: bump to v3.0.14 2016-11-04 10:54:14 -07:00
Xiang Li 9a0e4dfe4f ctlv3: fix migration 2016-11-03 09:47:41 -07:00
Timothy St. Clair f60469af16 ctlv3: Add a no-ttl flag to etcdctl migrate to discard keys on transform. 2016-11-03 09:47:39 -07:00
Gyu-Ho Lee 932370d8ca version: bump to v3.0.13+git 2016-10-24 11:22:50 -07:00
Gyu-Ho Lee c99d0d4b25 version: bump to v3.0.13 2016-10-24 11:04:43 -07:00
Gyu-Ho Lee d78216f528 e2e: remove 'ctlV3GetFailPerm' 2016-10-24 11:04:13 -07:00
Hongchao Deng c05c027a24 etcdctl: fix migrate in outputing client.Node to json
Using printf will try to parse the string and replace special
characters. In migrate code, we want to just output the raw
json string of client.Node.
For example,
    Printf("%\\") => %!\(MISSING)
    Print("%\\") => %\
Thus, we should use print instead.
2016-10-20 10:51:16 -07:00
Gyu-Ho Lee 3fd64f913a auth: fix return type on 'hasRootRole' 2016-10-12 13:59:27 -07:00
Xiang Li f935290bbc mvcc: fix rev inconsistency
Try:

./etcdctl put foo bar
./etcdctl del foo
./etcdctl compact 3

restart etcd

./etcdctl get foo
mvcc: required revision has been compacted

The error is unexpected when range over the head revision.

Internally, we incorrectly set current revision smaller than the
compacted revision when we remove all keys around compacted revision.

This commit fixes the issue by recovering the current revision at least
to compacted revision.
2016-10-12 13:08:26 -07:00
Hitoshi Mitake ca91f898a2 auth, e2e, clientv3: the root role should be granted access to every key
This commit changes the semantics of the root role. The role should be
able to access to every key.

Partially fixes https://github.com/coreos/etcd/issues/6355
2016-10-11 12:19:46 -07:00
Gyu-Ho Lee fcbada7798 Merge pull request #6622 from luxas/backport_arm_fixes
Backport arm fixes
2016-10-11 12:15:58 -07:00
Jared Hulbert fad9bdc3e1 etcdserver: atomic access alignment
Most fields accessed with sync/atomic functions are 64bit aligned, but a couple
are not.  This makes comments out of date and therefore misleading.

Affected fields reordered, comments scrubbed and updated.
2016-10-11 11:48:43 +03:00
Jared Hulbert 198ccb8b7b raftpb: atomic access alignment
The Entry struct has misaligned fields that are accessed atomically.  The
misalignment is caused by the EntryType enum which the Protocol Buffers
spec forces to be a 32bit int.

Moving the order of the fields without renumbering them in the .proto file
seems to align the go structure without changing the wire format.
2016-10-11 11:48:43 +03:00
Jared Hulbert dc5d5c6ac8 raft: atomic access alignment
The relevant structures are properly aligned, however, there is no comment
highlighting the need to keep it aligned as is present elsewhere in the
codebase.

Adding note to keep alignment, in line with similar comments in the codebase.
2016-10-11 11:48:43 +03:00
Gyu-Ho Lee f771eaca47 version: bump to v3.0.12+git 2016-10-07 16:42:12 -07:00
Gyu-Ho Lee 2d1e2e8e64 version: bump to v3.0.12 2016-10-07 15:14:25 -07:00
Gyu-Ho Lee 6412758177 v3rpc: remove redundant locks 2016-10-07 15:13:56 -07:00
Xiang Li 836c8159f6 v3rpc: lock progress and prevKV map correctly 2016-10-07 15:13:12 -07:00
Gyu-Ho Lee e406e6e8f4 etcdctl/ctlv3: add 'prev-kv' flag to watch command 2016-10-07 14:23:09 -07:00
Gyu-Ho Lee 2fa2c6284e clientv3: add 'prevKV' field to watch request 2016-10-07 14:22:58 -07:00
Gyu-Ho Lee 2862c4fa12 v3rpc: implement 'prev-kv' watch 2016-10-07 14:22:19 -07:00
Gyu-Ho Lee 6f89fbf8b5 etcdserver: use mvcc.WatchableKV for prev-kv watch 2016-10-07 14:22:00 -07:00
Gyu-Ho Lee 6ae7ec9a3f *: regenerate proto 2016-10-07 14:21:19 -07:00
Gyu-Ho Lee 4a35b1b20a etcdserverpb: add 'prev_kb' to WatchCreateRequest 2016-10-07 14:20:46 -07:00
Gyu-Ho Lee c859c97ee2 mvccpb: add 'prev_kv' field 2016-10-07 14:19:59 -07:00
Gyu-Ho Lee a091c629e1 version: bump to v3.0.11+git 2016-10-07 13:25:21 -07:00
Gyu-Ho Lee 96de94a584 version: bump to v3.0.11 2016-10-07 11:27:48 -07:00
Gyu-Ho Lee e9cd8410d7 integration: add 'prevKV' to TestV3DeleteRange 2016-10-07 11:03:19 -07:00
Gyu-Ho Lee e37ede1d2e etcdserver: handle 'PrevKV' 2016-10-07 11:00:48 -07:00
Gyu-Ho Lee 4420a29ac4 etcdctl/ctlv3: add 'prev-kv' flag 2016-10-07 10:56:06 -07:00
Gyu-Ho Lee 0544d4bfd0 clientv3: add WithPrevKV OpOption 2016-10-07 10:54:45 -07:00
Gyu-Ho Lee fe7379f102 clientv3: add Op.prevKV 2016-10-07 10:51:01 -07:00
Gyu-Ho Lee c76df5052b *: update proto to add 'prev_kv' 2016-10-07 10:47:47 -07:00
Xiang Li 3299cad1c3 *: add put prevkv 2016-10-07 10:39:08 -07:00
Anthony Romano d9ab018c49 integration: test a canceled watch won't return a closing error 2016-10-05 14:19:36 -07:00
Anthony Romano e853451cd2 clientv3: only return closing error to watcher if context is not canceled
Fixes #6503
2016-10-05 14:19:32 -07:00
Anthony Romano 1becf9d2f5 clientv3: fix race on watch initial revision
The initial revision was being updated in the substream goroutine defer;
this was racing with the resume path fetching the initial revision when
the substream closes during resume. Instead, update the initial revision
whenever the substream processes a new watch response. Since the substream
cannot receive a watch response while it is resuming, the write to the
initial revision is ordered to always happen after the resume read.

Fixes #6586
2016-10-05 10:56:36 -07:00
Anthony Romano 1a712cf187 clientv3: make IsProgressNotify() false on compact event and closed channel
Fixes #6549
2016-10-04 15:13:02 -07:00
Gyu-Ho Lee 023f335f67 wal: set PageWriter offset in file encoder 2016-10-04 15:12:47 -07:00
Gyu-Ho Lee bf0da78b63 pkg/ioutil: configure pageOffset in NewPageWriter 2016-10-04 15:12:46 -07:00
Anthony Romano e8473850a2 integration: test canceling watchers when disconnected 2016-10-04 15:12:37 -07:00
Anthony Romano b836d187fd clientv3: simplify watch synchronization
Was more complicated than it needed to be and didn't really work in the
first place. Restructured watcher registation to use a queue.
2016-10-04 15:12:18 -07:00
Gyu-Ho Lee 9b09229c4d version: bump to v3.0.10+git 2016-09-23 11:13:45 -07:00
Gyu-Ho Lee 546c0f7ed6 version: bump to v3.0.10 2016-09-23 10:49:03 -07:00
sharat adbad1c9b5 ctlv3: close snapshot file before rename (Windows) 2016-09-23 09:11:02 -07:00
Anthony Romano 273b986751 clientv3: process closed watcherStreams in watcherGrpcStream run loop
Was racing with Watch() when closing the grpc stream on no watchers.

Fixes #6476
2016-09-21 15:52:20 -07:00
Gyu-Ho Lee 5b205729b9 rafthttp: add v3.0.0 to supported streams 2016-09-16 21:54:55 +09:00
Anthony Romano fe900b09dd version: bump to v3.0.9+git 2016-09-15 15:10:23 -07:00
Anthony Romano 494c012659 version: bump to v3.0.9 2016-09-15 12:56:33 -07:00
Anthony Romano 4abc381ebe clientv3: drain buffered WatchResponses before resuming
Otherwise, the watcherStream can receive WatchResponses in the
middle of a resume, corrupting the stream.

Fixes #6364
2016-09-15 12:38:15 -07:00
Anthony Romano 73c8fdac53 integration: fix compilation for backported Election test 2016-09-15 11:45:37 -07:00
sharat ee2717493a ctlv3: fix line parsing for Windows 2016-09-15 11:25:53 -07:00
Xiang Li 2435eb9ecd clientv3: balancer panics when call up after close
Fix the issue by adding a simple guard varable.
2016-09-15 18:46:26 +09:00
Anthony Romano 8fb533dabe embed: warn on domain name in listener 2016-09-15 18:46:19 +09:00
Anthony Romano 2f0f5ac504 Revert "Merge pull request #6365 from heyitsanthony/fix-dns-bind"
This reverts commit af5ab7b351, reversing
changes made to da6a0f0594.
2016-09-15 18:43:46 +09:00
Jason E. Aten 9ab811d478 auth: fix range handling bugs.
Test 15, counting from zero, in TestGetMergedPerms
in etcd/auth/range_perm_cache_test.go, was trying
incorrectly assert that [a, b) merged with [b, "")
should be [a, b). Added a test specifically for
this. This patch fixes the incorrect larger test
and the bugs in the code that it was hiding.

Fixes #6359
2016-09-15 18:41:56 +09:00
Anthony Romano e0a99fb4ba version: bump to v3.0.8+git 2016-09-09 15:56:31 -07:00
Anthony Romano d40982fc91 version: bump to v3.0.8 2016-09-09 13:14:44 -07:00
Gyu-Ho Lee fe3a1cc31b wal: fix error type 2016-09-09 09:11:25 +09:00
Gyu-Ho Lee 70713706a1 wal: fix err shadowing (go vet) 2016-09-09 09:07:48 +09:00
Xiang Li 0054e7e89b etcdctl: restore should create a snapshot
Restore should create a snasphot. So the new db file
can be sent to newly joined member.
2016-09-09 09:03:51 +09:00
Anthony Romano 97f718b504 fileutil: windows OpenDir
Windows needs to open a directory with write access to fsync but the go
runtime won't open directories that way.
2016-09-09 09:01:56 +09:00
Anthony Romano 202da9270e wal: fsync directory after wal file rename
Fixes #6368
2016-09-09 09:01:49 +09:00
Anthony Romano 6e83ec0ed7 etcdmain: reject binding listeners to domain names
Fixes #6336
2016-09-07 08:08:35 +09:00
Jason E. Aten 5c44cdfdaa etcdctl/ctlv3: don't crash when we should prompt for pw.
when 'etcdctl --user name get blah' is invoked to
 prompt for password, don't panic.

 addresses the segfault part of #6343
2016-09-04 09:02:50 +09:00
Anthony Romano 09a239f040 e2e: add quoted key/value to txn test 2016-09-04 09:02:47 +09:00
Anthony Romano 3faff8b2e2 etcdctl: fix quoted string handling in txn and watch
Fixes #6315
2016-09-04 09:02:28 +09:00
Anthony Romano 2345fda18e version: bump to v3.0.7+git 2016-08-31 16:41:06 -07:00
Gyu-Ho Lee 5695120efc version: bump to v3.0.7 2016-08-31 09:49:24 -07:00
Gyu-Ho Lee 183293e061 wal: lowercase segmentSizeBytes 2016-08-31 09:48:30 -07:00
Jason E. Aten 4b48876f0e clientv3/concurrency: allow election on prefixes of keys.
After winning an election or obtaining a lock, we
auto-append a slash after the provided key prefix.
This avoids the previous deadlock due to waiting
on the wrong key.

Fixes #6278

Conflicts:
	clientv3/concurrency/election.go
	clientv3/concurrency/mutex.go
2016-08-31 09:46:05 -07:00
Aaron Lehmann 5089bf58fb wal: hold file lock while renaming WAL directory on non-Windows
Windows requires this lock to be released before the directory is
renamed. But on unix-like operating systems, releasing the lock and
trying to reacquire it immediately can be flaky if a process is forked
around the same time. The file descriptors are marked as close-on-exec
by the Go runtime, but there is a window between the fork and exec where
another process will be holding the lock.
2016-08-31 09:39:57 -07:00
Anthony Romano 480a347179 wal: use page buffered writer for writing records
Forces torn writes to only happen on sector boundaries.

Fixes #6271
2016-08-30 21:06:36 -07:00
Anthony Romano 59e560c7a7 ioutil: add page buffered writer
A buffered writer that only writes full pages or when explicitly flushed.
2016-08-30 21:06:33 -07:00
Xiang Li 0bd9bea2e9 etcdserver: allow zero kv index for cluster upgrade
If a user upgrades etcd from 2.3.x to 3.0 and shutdown the
cluster immediately without triggering any new backend writes,
then the consistent index in backend would be zero.

The user cannot restart etcdserver due to today's strick index
match checking. We now have to lose this a bit for this case.
2016-08-30 21:05:20 -07:00
Anthony Romano bd7581ac59 wal: zero out wal tail past its first zero record
Whenever the WAL is opened for writes, it should write zeroes to its tail
starting from the first zero record. Otherwise, if there are entries past
the first zero record due to a torn write, any new writes that overlap the
old entries will lead to a garbage record on the tail and cause a CRC
mismatch.
2016-08-26 14:27:53 -07:00
Anthony Romano db378c3d26 wal: test for truncation on torn writes 2016-08-26 14:27:51 -07:00
Anthony Romano 23740162dc fileutil: add ZeroToEnd for zeroing files 2016-08-26 14:27:49 -07:00
Anthony Romano 96422a955f discovery: reject IP address records in SRVGetCluster
Was incorrectly trimming the trailing '.' from the target; this in turn
caused the etcd server to accept any SRV record with an IP target
instead of only targets with A records.
2016-08-24 09:14:47 -07:00
Gyu-Ho Lee 6fd996fdac version: bump to v3.0.6+git 2016-08-19 12:38:13 -07:00
Gyu-Ho Lee 9efa00d103 version: bump to v3.0.6 2016-08-19 12:03:02 -07:00
Xiang Li 72d30f4c34 *: minor cleanup for lease 2016-08-19 11:53:38 -07:00
Xiang Li 2e92779777 mvcc: attach keys to leases after recover all state
The previous logic is wrong. When we have hisotry like Put(foo, bar, lease1),
and Put(foo, bar, lease2), we will end up with attaching foo to two leases 1 and
2. Similar things can happen for deattach by clearing the lease of a key.

Now we try to fix this by starting to attach leases at the end of the recovery.
We use a map to keep the last lease attachment state.
2016-08-19 11:49:05 -07:00
Xiang Li 404415b1e3 lease: do lease delection in the kv txn 2016-08-19 11:49:05 -07:00
Xiang Li 07e421d245 lease: delete kvs in a txn 2016-08-19 11:49:05 -07:00
Xiang Li a7d6e29275 etcdserver: always recover lessor first 2016-08-19 11:49:05 -07:00
Gyu-Ho Lee 1a8b295dab vendor: update grpc/grpc-go for clientconn patch 2016-08-19 11:46:51 -07:00
Anthony Romano ffc45cc066 rafthttp: fix race between streamReader.stop() and connection closer 2016-08-19 11:45:39 -07:00
Gyu-Ho Lee 0db1ba8093 version: bump to v3.0.5+git 2016-08-19 11:11:10 -07:00
Gyu-Ho Lee 43f7c94ac8 version: bump to v3.0.5 2016-08-19 10:20:37 -07:00
Hongchao Deng 93d13fb5b4 integration: NewClusterV3 should launch cluster before creating clients 2016-08-18 14:54:45 -07:00
Gyu-Ho Lee 6a1e3e73dd vendor: boltdb/bolt v1.3.0 for Go 1.7
In case somebody wants to build this branch with Go 1.7
2016-08-18 14:41:34 -07:00
Xiang Li ec576ee5ac mvcc: fix count 2016-08-16 12:13:33 -07:00
Anthony Romano 606d79afc4 clientv3: use failfast and retry wrappers for at-most-once rpcs 2016-08-16 12:12:44 -07:00
Anthony Romano f4d15a430c integration: treat client TLS connecting to insecure server as timeout 2016-08-16 12:09:42 -07:00
Anthony Romano 4a841459f1 clientv3: respect up/down notifications from grpc
Fixes #5842
2016-08-16 12:09:38 -07:00
Gyu-Ho Lee ee8c577fc0 vendor: update grpc 2016-08-16 12:09:16 -07:00
Anthony Romano 8ae0f94cd7 clientv3: only block on New() when DialTimeout > 0
Fixes #6162
2016-08-12 12:03:33 -07:00
Anthony Romano 69a97863a9 clientv3: handle watchGrpcStream shutdown if prior to goroutine start
Fixes #6141
2016-08-09 20:59:09 -07:00
Anthony Romano 12c7e4a9f8 clientv3: close watcher stream once all watchers detach
Fixes #6134
2016-08-09 10:44:21 -07:00
Anthony Romano 23cced240b transport: add ServerName to TLSConfig and add ValidateSecureEndpoints
ServerName prevents accepting forged SRV records with cross-domain
credentials. ValidateSecureEndpoints prevents downgrade attacks from SRV
records.
2016-08-04 11:00:28 -07:00
Anthony Romano e73c928d85 etcdctl: set ServerName for TLS when using --discovery-srv 2016-08-04 11:00:25 -07:00
Anthony Romano 779ad90f9a Documentation: update clustering guide about PKI SRV record forging 2016-08-04 11:00:22 -07:00
Anthony Romano dca1740be5 etcdmain: check TLS on gateway SRV records 2016-08-04 11:00:15 -07:00
Anthony Romano 487b34d857 embed: use ServerName on TLS DNS discovery w/o CA file 2016-08-04 10:56:11 -07:00
Gyu-Ho Lee a31283cf51 v2http: use guest access in non-TLS mode
Fix https://github.com/coreos/etcd/issues/6075.
2016-08-04 10:52:42 -07:00
Gyu-Ho Lee b722bedf8a version: bump to v3.0.4+git 2016-07-27 15:30:31 -07:00
Gyu-Ho Lee d53923c636 version: bump to v3.0.4 2016-07-27 13:40:42 -07:00
Gyu-Ho Lee 9356665d60 *: regenerate proto files for grpc-gateway 2016-07-27 13:40:07 -07:00
Gyu-Ho Lee 0932d17395 scripts/genproto: use latest grpc-gateway c8ec92d0 2016-07-27 13:39:00 -07:00
Gyu-Ho Lee 2a3ea3f996 Dockerfile-release: add '/var/lib/etcd/'
We have '/var/etcd/' in Dockerfile for historical reason.
Most cases, user store data in '/var/lib/etcd/'.
2016-07-27 13:38:58 -07:00
Anthony Romano e5a5e5f7c6 etcdserver, api, membership: don't race on setting version
Fixes #6029
2016-07-27 09:39:39 -07:00
Gyu-Ho Lee 00bdd907d5 Documentation: fix links in upgrades 2016-07-26 13:16:15 -07:00
Gyu-Ho Lee 8eab756d3f *: regenerate proto 2016-07-25 21:36:07 -07:00
Xiang Li 3d9b1d1635 scripts:genproto.sh: update grpc-gateway 2016-07-25 21:31:33 -07:00
Xiang Li 4218193dd7 etcdserverpb: add missing deleterange annotation 2016-07-25 21:31:30 -07:00
Dongsu Park 6499d01c9b etcdmain: correctly check return values from SdNotify()
SdNotify() now returns 2 values, sent and err. So startEtcdOrProxyV2()
needs to check the 2 return values correctly. As the 2 values are
independent of each other, error checking needs to be slightly updated
too.

SdNotifyNoSocket, which was previously provided by go-systemd, does not
exist any more. In that case (false, nil) will be returned instead.
2016-07-21 11:00:37 -07:00
Dongsu Park 83b39b4f6b vendor: update go-systemd
Godeps.json and vendor need to be updated according to the newest
go-systemd, as SdNotify() in go-systemd has changed its API.
2016-07-21 11:00:34 -07:00
Anthony Romano 21092ca715 integration: change timeouts for TestWatchWithProgressNotify
a) 2 * progress interval was passing with dropped notifies
b) waitResponse was waiting so long that it expected a dropped notify
2016-07-21 10:59:54 -07:00
Anthony Romano a4e79d7ebf v3rpc: don't elide next progress notification on progress notification
Fixes #5878
2016-07-21 10:59:51 -07:00
Anthony Romano 846883a979 rpctypes, clientv3: retry RPC on EtcdStopped
Fixes #5983
2016-07-21 10:59:27 -07:00
Anthony Romano c7a3edb90f fileutil: rework purge tests so they don't poll
Fixes #5966
2016-07-21 10:57:06 -07:00
Gyu-Ho Lee f308a27e91 e2e: test auth enabled with CN name cert 2016-07-21 10:55:56 -07:00
Gyu-Ho Lee 1d37154793 v2http: test with 'ClientCertAuthEnabled' 2016-07-21 10:55:54 -07:00
Gyu-Ho Lee 092d069d3e v2http: set 'ClientCertAuthEnabled' in client.go 2016-07-21 10:55:51 -07:00
Gyu-Ho Lee ab5c4e23bd v2http: add 'ClientCertAuthEnabled' in handlers 2016-07-21 10:55:44 -07:00
Gyu-Ho Lee 59bf6693c7 embed: set 'ClientCertAuthEnabled' 2016-07-21 10:55:30 -07:00
Gyu-Ho Lee affcbfbf06 etcdserver: add 'ClientCertAuthEnabled' option 2016-07-21 10:52:14 -07:00
Gyu-Ho Lee e81df2648c v2http: move 'testdata' from 'etcdhttp' 2016-07-21 10:52:09 -07:00
rob boll 27a450235a v2http: client cert cn authentication
introduce client certificate authentication using certificate cn.
2016-07-21 10:52:06 -07:00
rob boll 42454f9ed8 v2http: refactor http basic auth
refactor http basic auth code to combine basic auth extraction and validation
2016-07-21 10:52:04 -07:00
Anthony Romano 7ea8860670 e2e: use a single member cluster in TestCtlV3Migrate
Occasionally migrate would fail because a minority node would be missing
v2 keys. Instead, just use a single member cluster.

Fixes #5992
2016-07-21 10:50:49 -07:00
jesse.millan 2fb72029ef etcdctl: Add support for formating output of ls command in json
The ls command will check for and honor json or extended output formats.

Fixes #5993
2016-07-21 10:50:47 -07:00
Xiang Li 77af59796d clientv3/integration: fix race in TestWatchCompactRevision 2016-07-21 10:50:46 -07:00
Anthony Romano b732f96e07 integration: drain keepalives in TestLeaseKeepAliveCloseAfterDisconnectRevoke
Fixes #5900
2016-07-21 10:50:44 -07:00
Gyu-Ho Lee 602198105d *: regenerate proto 2016-07-18 11:08:51 -07:00
Gyu-Ho Lee e513cbd562 vendor: update 'gogo/protobuf' 2016-07-18 11:06:58 -07:00
Gyu-Ho Lee 4198369dd0 scripts: update gogo/protobuf, use 'gofast' plugin
- Fix https://github.com/coreos/etcd/issues/5942
- Partial fix for https://github.com/coreos/etcd/issues/5865
2016-07-18 11:06:55 -07:00
Gyu-Ho Lee debecc1868 vendor: change to 'grpc-ecosystem' from 'gengo' 2016-07-18 11:06:33 -07:00
Gyu-Ho Lee 140fc04c62 *: regenerate proto files 2016-07-18 11:06:17 -07:00
Gyu-Ho Lee 7e34665774 scripts: update genproto with grpc-ecosystem 2016-07-18 11:03:54 -07:00
Gyu-Ho Lee be541f3641 Documentation: change to grpc-ecosystem 2016-07-18 11:03:52 -07:00
Gyu-Ho Lee e582416994 embed: change import path to 'grpc-ecosystem' 2016-07-18 11:03:50 -07:00
Xiang Li 842145ecb3 *: fix issue found in fast lease renew 2016-07-18 11:03:20 -07:00
Gyu-Ho Lee d68936c4da version: bump to v3.0.3+git 2016-07-15 11:51:50 -07:00
Gyu-Ho Lee 24a90baff8 version: bump to v3.0.3 2016-07-15 11:26:14 -07:00
Anthony Romano 6b7891d5f1 integration: add FailFast(false) to failing tests 2016-07-14 19:01:17 -07:00
Anthony Romano 129b271ff8 clientv3: use grpc.FailFast(false) for all calls 2016-07-14 19:00:46 -07:00
Anthony Romano a11ee983c4 vendor: update grpc
Fixes #5871
2016-07-14 18:47:02 -07:00
Anthony Romano bec58d5f58 integration: test grpc error equivalence with Error() 2016-07-14 18:47:00 -07:00
Anthony Romano 4b6f9b79e6 rpctypes: test error equivalence with Error()
grpc.Errorf() now returns *rpcError, which makes comparisons shallow.
2016-07-14 18:46:58 -07:00
Xiang Li f7ec7f025b embed: only get initial cluster setting if the member is not init 2016-07-14 13:01:29 -07:00
Gyu-Ho Lee 34c76a47c1 Revert "Dockerfile: use 'ENTRYPOINT' instead of 'CMD'" 2016-07-14 12:24:06 -07:00
Xiang Li 525653ff51 raft: do not change RecentActive when resetState for progress 2016-07-12 09:59:42 -07:00
Xiang Li a647b79038 etcdserver: fix TestSnap 2016-07-11 13:59:12 -07:00
Xiang Li 9bc1d08753 etcdctl: only takes 127.0.0.1:2379 as default endpoint 2016-07-11 13:41:53 -07:00
Gyu-Ho Lee 6a79bda691 e2e: add basic upgrade tests 2016-07-11 13:41:50 -07:00
Gyu-Ho Lee 1edfcd6859 test: add upgrade test flag 2016-07-11 13:41:47 -07:00
Gyu-Ho Lee f51fdbccec version: bump to v3.0.2+git 2016-07-08 12:09:09 -07:00
Gyu-Ho Lee faeeb2fc75 version: bump to v3.0.2 2016-07-08 11:45:18 -07:00
Xiang Li d50c487132 v3rpc: lock progress and prevKV map correctly 2016-07-08 10:16:10 -07:00
Anthony Romano b837feffe4 client/integration: test v2 client one shot operations 2016-07-07 17:30:09 -07:00
Anthony Romano 4d89640195 client: make set/delete one shot operations
Old behavior would retry set and delete even if there's an error. This
can lead to the client returning an error for deleting twice, instead
of returning an error for an interdeterminate state.

Fixes #5832
2016-07-07 17:30:04 -07:00
westhood 1292d453c3 clientv3: fix sync base
It is not correct to use WithPrefix. Range end will change in every
internal batch.
2016-07-07 14:21:43 -07:00
westhood ec20b381ed clientv3: add public function to get prefix range end 2016-07-07 14:21:41 -07:00
Secret 37cc3f5262 Dockerfile: use 'ENTRYPOINT' instead of 'CMD'
use entrypoint, so people can specify flags to etcd
without providing the binary.

Signed-off-by: Secret <haichuang221@163.com>
2016-07-05 11:40:47 -07:00
Xiang Li 7f1940e5ed etcdserver: commit before sending snapshot 2016-07-05 11:06:54 -07:00
Xiang Li caccf8e5e6 v3rpc: do not panic on user error for watch 2016-07-05 11:06:35 -07:00
Anthony Romano ef65dfe2eb wal: release wal locks before renaming directory on init
Fixes #5852
2016-07-05 11:05:51 -07:00
Gyu-Ho Lee ff6c6916f2 etcdserver/api: print only major.minor version API
Before

2016-07-01 14:57:50.927170 I | api: enabled capabilities for version 3.0.0

After

2016-07-01 14:57:50.927170 I | api: enabled capabilities for version 3.0
2016-07-01 15:19:53 -07:00
Gyu-Ho Lee 3dfe8765d3 version: bump to v3.0.1+git 2016-07-01 14:53:20 -07:00
Gyu-Ho Lee a4a52cb15d version: bump to v3.0.1 2016-07-01 13:58:37 -07:00
Gyu-Ho Lee 014970930a *: test, docs with go1.6+
etcd v3 uses http/2, which doesn't work well with go1.5
2016-07-01 11:59:37 -07:00
Geert-Johan Riemer 4628be982c Documentation: fix typo in api_grpc_gateway.md 2016-07-01 11:59:35 -07:00
Anthony Romano ff55e5a188 etcdserver: exit on missing backend only if semver is >= 3.0.0 2016-07-01 11:59:32 -07:00
Gyu-Ho Lee bf0898266c release: fix Dockerfile etcd binary paths
release script uses binary files in 'release/image-docker',
not the ones in "bin/". Tested with v3.0.0 release.
2016-06-30 12:27:34 -07:00
Gyu-Ho Lee b9d69f7698 version: bump to v3.0.0+git 2016-06-30 11:37:05 -07:00
Gyu-Ho Lee 6f48bda7ac version: bump to v3.0.0 2016-06-30 10:04:59 -07:00
Gyu-Ho Lee 316534e09e *: remove beta from docs 2016-06-30 10:04:34 -07:00
Jeff Zellner 3cecbdb464 hack: install goreman in tls-setup example 2016-06-30 09:33:19 -07:00
Jeff Zellner 62f11e43ee hack: add tls-setup example generated certs to gitignore 2016-06-30 09:33:12 -07:00
Anthony Romano 064c1585ee Merge pull request #5822 from raoofm/patch-9
Doc: fix typo in dev-guide.md
2016-06-30 09:06:32 -07:00
Raoof Mohammed 15300a1eb8 Doc: fix typo in dev-guide.md 2016-06-30 10:36:50 -04:00
Gyu-Ho Lee 58dd047ee4 ctlv3: make flags, commands formats consistent
1. Capitalize first letter
2. Remove period at the end

(followed the pattern in linux coreutil man page)
2016-06-29 16:16:56 -07:00
Anthony Romano 4b42ea6cd7 clientv3: only use closeErr on watch when donec is closed
Fixes #5800
2016-06-28 17:48:44 -07:00
Gyu-Ho Lee 53c27ae621 benchmark: fix Compact request 2016-06-28 14:15:32 -07:00
Xiang Li 269de67bde mvcc: do not hash consistent index 2016-06-28 12:29:36 -07:00
Anthony Romano 8bbccf1047 clientv3, ctl3, clientv3/integration: add compact response to compact 2016-06-28 12:29:32 -07:00
220 changed files with 7310 additions and 3510 deletions

1
.gitignore vendored
View File

@ -10,3 +10,4 @@
/hack/insta-discovery/.env /hack/insta-discovery/.env
*.test *.test
tools/functional-tester/docker/bin tools/functional-tester/docker/bin
hack/tls-setup/certs

View File

@ -4,9 +4,7 @@ go_import_path: github.com/coreos/etcd
sudo: false sudo: false
go: go:
- 1.5 - 1.6.4
- 1.6
- tip
env: env:
global: global:
@ -22,10 +20,6 @@ matrix:
allow_failures: allow_failures:
- go: tip - go: tip
exclude: exclude:
- go: 1.5
env: TARGET=arm
- go: 1.5
env: TARGET=ppc64le
- go: 1.6 - go: 1.6
env: TARGET=arm64 env: TARGET=arm64
- go: tip - go: tip

View File

@ -1,8 +1,9 @@
FROM alpine:latest FROM alpine:latest
ADD bin/etcd /usr/local/bin/ ADD etcd /usr/local/bin/
ADD bin/etcdctl /usr/local/bin/ ADD etcdctl /usr/local/bin/
RUN mkdir -p /var/etcd/ RUN mkdir -p /var/etcd/
RUN mkdir -p /var/lib/etcd/
EXPOSE 2379 2380 EXPOSE 2379 2380

View File

@ -25,13 +25,13 @@ curl -L http://localhost:2379/v3alpha/kv/range \
## Swagger ## Swagger
Generated [Swapper][swagger] API definitions can be found at [rpc.swagger.json][swagger-doc]. Generated [Swagger][swagger] API definitions can be found at [rpc.swagger.json][swagger-doc].
[api-ref]: ./api_reference_v3.md [api-ref]: ./api_reference_v3.md
[go-client]: https://github.com/coreos/etcd/tree/master/clientv3 [go-client]: https://github.com/coreos/etcd/tree/master/clientv3
[etcdctl]: https://github.com/coreos/etcd/tree/master/etcdctl [etcdctl]: https://github.com/coreos/etcd/tree/master/etcdctl
[grpc]: http://www.grpc.io/ [grpc]: http://www.grpc.io/
[grpc-gateway]: https://github.com/gengo/grpc-gateway [grpc-gateway]: https://github.com/grpc-ecosystem/grpc-gateway
[json-mapping]: https://developers.google.com/protocol-buffers/docs/proto3#json [json-mapping]: https://developers.google.com/protocol-buffers/docs/proto3#json
[swagger]: http://swagger.io/ [swagger]: http://swagger.io/
[swagger-doc]: apispec/swagger/rpc.swagger.json [swagger-doc]: apispec/swagger/rpc.swagger.json

View File

@ -427,6 +427,7 @@ Empty field.
| ----- | ----------- | ---- | | ----- | ----------- | ---- |
| key | key is the first key to delete in the range. | bytes | | key | key is the first key to delete in the range. | bytes |
| range_end | range_end is the key following the last key to delete for the range [key, range_end). If range_end is not given, the range is defined to contain only the key argument. If range_end is '\0', the range is all keys greater than or equal to the key argument. | bytes | | range_end | range_end is the key following the last key to delete for the range [key, range_end). If range_end is not given, the range is defined to contain only the key argument. If range_end is '\0', the range is all keys greater than or equal to the key argument. | bytes |
| prev_kv | If prev_kv is set, etcd gets the previous key-value pairs before deleting it. The previous key-value pairs will be returned in the delte response. | bool |
@ -436,6 +437,7 @@ Empty field.
| ----- | ----------- | ---- | | ----- | ----------- | ---- |
| header | | ResponseHeader | | header | | ResponseHeader |
| deleted | deleted is the number of keys deleted by the delete range request. | int64 | | deleted | deleted is the number of keys deleted by the delete range request. | int64 |
| prev_kvs | if prev_kv is set in the request, the previous key-value pairs will be returned. | (slice of) mvccpb.KeyValue |
@ -591,6 +593,7 @@ Empty field.
| key | key is the key, in bytes, to put into the key-value store. | bytes | | key | key is the key, in bytes, to put into the key-value store. | bytes |
| value | value is the value, in bytes, to associate with the key in the key-value store. | bytes | | value | value is the value, in bytes, to associate with the key in the key-value store. | bytes |
| lease | lease is the lease ID to associate with the key in the key-value store. A lease value of 0 indicates no lease. | int64 | | lease | lease is the lease ID to associate with the key in the key-value store. A lease value of 0 indicates no lease. | int64 |
| prev_kv | If prev_kv is set, etcd gets the previous key-value pair before changing it. The previous key-value pair will be returned in the put response. | bool |
@ -599,6 +602,7 @@ Empty field.
| Field | Description | Type | | Field | Description | Type |
| ----- | ----------- | ---- | | ----- | ----------- | ---- |
| header | | ResponseHeader | | header | | ResponseHeader |
| prev_kv | if prev_kv is set in the request, the previous key-value pair will be returned. | mvccpb.KeyValue |
@ -613,6 +617,8 @@ Empty field.
| sort_order | sort_order is the order for returned sorted results. | SortOrder | | sort_order | sort_order is the order for returned sorted results. | SortOrder |
| sort_target | sort_target is the key-value field to use for sorting. | SortTarget | | sort_target | sort_target is the key-value field to use for sorting. | SortTarget |
| serializable | serializable sets the range request to use serializable member-local reads. Range requests are linearizable by default; linearizable requests have higher latency and lower throughput than serializable requests but reflect the current consensus of the cluster. For better performance, in exchange for possible stale reads, a serializable range request is served locally without needing to reach consensus with other nodes in the cluster. | bool | | serializable | serializable sets the range request to use serializable member-local reads. Range requests are linearizable by default; linearizable requests have higher latency and lower throughput than serializable requests but reflect the current consensus of the cluster. For better performance, in exchange for possible stale reads, a serializable range request is served locally without needing to reach consensus with other nodes in the cluster. | bool |
| keys_only | keys_only when set returns only the keys and not the values. | bool |
| count_only | count_only when set returns only the count of the keys in the range. | bool |
@ -621,8 +627,9 @@ Empty field.
| Field | Description | Type | | Field | Description | Type |
| ----- | ----------- | ---- | | ----- | ----------- | ---- |
| header | | ResponseHeader | | header | | ResponseHeader |
| kvs | kvs is the list of key-value pairs matched by the range request. | (slice of) mvccpb.KeyValue | | kvs | kvs is the list of key-value pairs matched by the range request. kvs is empty when count is requested. | (slice of) mvccpb.KeyValue |
| more | more indicates if there are more keys to return in the requested range. | bool | | more | more indicates if there are more keys to return in the requested range. | bool |
| count | count is set to the number of keys within the range when requested. | int64 |
@ -732,6 +739,7 @@ From google paxosdb paper: Our implementation hinges around a powerful primitive
| range_end | range_end is the end of the range [key, range_end) to watch. If range_end is not given, only the key argument is watched. If range_end is equal to '\0', all keys greater than or equal to the key argument are watched. | bytes | | range_end | range_end is the end of the range [key, range_end) to watch. If range_end is not given, only the key argument is watched. If range_end is equal to '\0', all keys greater than or equal to the key argument are watched. | bytes |
| start_revision | start_revision is an optional revision to watch from (inclusive). No start_revision is "now". | int64 | | start_revision | start_revision is an optional revision to watch from (inclusive). No start_revision is "now". | int64 |
| progress_notify | progress_notify is set so that the etcd server will periodically send a WatchResponse with no events to the new watcher if there are no recent events. It is useful when clients wish to recover a disconnected watcher starting from a recent known revision. The etcd server may decide how often it will send notifications based on current load. | bool | | progress_notify | progress_notify is set so that the etcd server will periodically send a WatchResponse with no events to the new watcher if there are no recent events. It is useful when clients wish to recover a disconnected watcher starting from a recent known revision. The etcd server may decide how often it will send notifications based on current load. | bool |
| prev_kv | If prev_kv is set, created watcher gets the previous KV before the event happens. If the previous KV is already compacted, nothing will be returned. | bool |
@ -764,6 +772,7 @@ From google paxosdb paper: Our implementation hinges around a powerful primitive
| ----- | ----------- | ---- | | ----- | ----------- | ---- |
| type | type is the kind of event. If type is a PUT, it indicates new data has been stored to the key. If type is a DELETE, it indicates the key was deleted. | EventType | | type | type is the kind of event. If type is a PUT, it indicates new data has been stored to the key. If type is a DELETE, it indicates the key was deleted. | EventType |
| kv | kv holds the KeyValue for the event. A PUT event contains current kv pair. A PUT event with kv.Version=1 indicates the creation of a key. A DELETE/EXPIRE event contains the deleted key with its modification revision set to the revision of deletion. | KeyValue | | kv | kv holds the KeyValue for the event. A PUT event contains current kv pair. A PUT event with kv.Version=1 indicates the creation of a key. A DELETE/EXPIRE event contains the deleted key with its modification revision set to the revision of deletion. | KeyValue |
| prev_kv | prev_kv holds the key-value pair before the event happens. | KeyValue |

View File

@ -15,13 +15,553 @@
"application/json" "application/json"
], ],
"paths": { "paths": {
"/v3alpha/auth/authenticate": {
"post": {
"summary": "Authenticate processes an authenticate request.",
"operationId": "Authenticate",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbAuthenticateResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbAuthenticateRequest"
}
}
],
"tags": [
"Auth"
]
}
},
"/v3alpha/auth/disable": {
"post": {
"summary": "AuthDisable disables authentication.",
"operationId": "AuthDisable",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbAuthDisableResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbAuthDisableRequest"
}
}
],
"tags": [
"Auth"
]
}
},
"/v3alpha/auth/enable": {
"post": {
"summary": "AuthEnable enables authentication.",
"operationId": "AuthEnable",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbAuthEnableResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbAuthEnableRequest"
}
}
],
"tags": [
"Auth"
]
}
},
"/v3alpha/auth/role/add": {
"post": {
"summary": "RoleAdd adds a new role.",
"operationId": "RoleAdd",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbAuthRoleAddResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbAuthRoleAddRequest"
}
}
],
"tags": [
"Auth"
]
}
},
"/v3alpha/auth/role/delete": {
"post": {
"summary": "RoleDelete deletes a specified role.",
"operationId": "RoleDelete",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbAuthRoleDeleteResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbAuthRoleDeleteRequest"
}
}
],
"tags": [
"Auth"
]
}
},
"/v3alpha/auth/role/get": {
"post": {
"summary": "RoleGet gets detailed role information.",
"operationId": "RoleGet",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbAuthRoleGetResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbAuthRoleGetRequest"
}
}
],
"tags": [
"Auth"
]
}
},
"/v3alpha/auth/role/grant": {
"post": {
"summary": "RoleGrantPermission grants a permission of a specified key or range to a specified role.",
"operationId": "RoleGrantPermission",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbAuthRoleGrantPermissionResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbAuthRoleGrantPermissionRequest"
}
}
],
"tags": [
"Auth"
]
}
},
"/v3alpha/auth/role/list": {
"post": {
"summary": "RoleList gets lists of all roles.",
"operationId": "RoleList",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbAuthRoleListResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbAuthRoleListRequest"
}
}
],
"tags": [
"Auth"
]
}
},
"/v3alpha/auth/role/revoke": {
"post": {
"summary": "RoleRevokePermission revokes a key or range permission of a specified role.",
"operationId": "RoleRevokePermission",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbAuthRoleRevokePermissionResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbAuthRoleRevokePermissionRequest"
}
}
],
"tags": [
"Auth"
]
}
},
"/v3alpha/auth/user/add": {
"post": {
"summary": "UserAdd adds a new user.",
"operationId": "UserAdd",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbAuthUserAddResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbAuthUserAddRequest"
}
}
],
"tags": [
"Auth"
]
}
},
"/v3alpha/auth/user/changepw": {
"post": {
"summary": "UserChangePassword changes the password of a specified user.",
"operationId": "UserChangePassword",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbAuthUserChangePasswordResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbAuthUserChangePasswordRequest"
}
}
],
"tags": [
"Auth"
]
}
},
"/v3alpha/auth/user/delete": {
"post": {
"summary": "UserDelete deletes a specified user.",
"operationId": "UserDelete",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbAuthUserDeleteResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbAuthUserDeleteRequest"
}
}
],
"tags": [
"Auth"
]
}
},
"/v3alpha/auth/user/get": {
"post": {
"summary": "UserGet gets detailed user information.",
"operationId": "UserGet",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbAuthUserGetResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbAuthUserGetRequest"
}
}
],
"tags": [
"Auth"
]
}
},
"/v3alpha/auth/user/grant": {
"post": {
"summary": "UserGrant grants a role to a specified user.",
"operationId": "UserGrantRole",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbAuthUserGrantRoleResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbAuthUserGrantRoleRequest"
}
}
],
"tags": [
"Auth"
]
}
},
"/v3alpha/auth/user/list": {
"post": {
"summary": "UserList gets a list of all users.",
"operationId": "UserList",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbAuthUserListResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbAuthUserListRequest"
}
}
],
"tags": [
"Auth"
]
}
},
"/v3alpha/auth/user/revoke": {
"post": {
"summary": "UserRevokeRole revokes a role of specified user.",
"operationId": "UserRevokeRole",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbAuthUserRevokeRoleResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbAuthUserRevokeRoleRequest"
}
}
],
"tags": [
"Auth"
]
}
},
"/v3alpha/cluster/member/add": {
"post": {
"summary": "MemberAdd adds a member into the cluster.",
"operationId": "MemberAdd",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbMemberAddResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbMemberAddRequest"
}
}
],
"tags": [
"Cluster"
]
}
},
"/v3alpha/cluster/member/list": {
"post": {
"summary": "MemberList lists all the members in the cluster.",
"operationId": "MemberList",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbMemberListResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbMemberListRequest"
}
}
],
"tags": [
"Cluster"
]
}
},
"/v3alpha/cluster/member/remove": {
"post": {
"summary": "MemberRemove removes an existing member from the cluster.",
"operationId": "MemberRemove",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbMemberRemoveResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbMemberRemoveRequest"
}
}
],
"tags": [
"Cluster"
]
}
},
"/v3alpha/cluster/member/update": {
"post": {
"summary": "MemberUpdate updates the member configuration.",
"operationId": "MemberUpdate",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbMemberUpdateResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbMemberUpdateRequest"
}
}
],
"tags": [
"Cluster"
]
}
},
"/v3alpha/kv/compaction": { "/v3alpha/kv/compaction": {
"post": { "post": {
"summary": "Txn processes multiple requests in a single transaction.\nA txn request increments the revision of the key-value store\nand generates events with the same revision for every completed request.\nIt is not allowed to modify the same key several times within one txn.", "summary": "Compact compacts the event history in the etcd key-value store. The key-value\nstore should be periodically compacted or the event history will continue to grow\nindefinitely.",
"operationId": "Compact", "operationId": "Compact",
"responses": { "responses": {
"200": { "200": {
"description": "Description", "description": "",
"schema": { "schema": {
"$ref": "#/definitions/etcdserverpbCompactionResponse" "$ref": "#/definitions/etcdserverpbCompactionResponse"
} }
@ -42,13 +582,67 @@
] ]
} }
}, },
"/v3alpha/kv/deleterange": {
"post": {
"summary": "DeleteRange deletes the given range from the key-value store.\nA delete request increments the revision of the key-value store\nand generates a delete event in the event history for every deleted key.",
"operationId": "DeleteRange",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbDeleteRangeResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbDeleteRangeRequest"
}
}
],
"tags": [
"KV"
]
}
},
"/v3alpha/kv/lease/revoke": {
"post": {
"summary": "LeaseRevoke revokes a lease. All keys attached to the lease will expire and be deleted.",
"operationId": "LeaseRevoke",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbLeaseRevokeResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbLeaseRevokeRequest"
}
}
],
"tags": [
"Lease"
]
}
},
"/v3alpha/kv/put": { "/v3alpha/kv/put": {
"post": { "post": {
"summary": "Put puts the given key into the key-value store.\nA put request increments the revision of the key-value store\nand generates one event in the event history.", "summary": "Put puts the given key into the key-value store.\nA put request increments the revision of the key-value store\nand generates one event in the event history.",
"operationId": "Put", "operationId": "Put",
"responses": { "responses": {
"200": { "200": {
"description": "Description", "description": "",
"schema": { "schema": {
"$ref": "#/definitions/etcdserverpbPutResponse" "$ref": "#/definitions/etcdserverpbPutResponse"
} }
@ -75,7 +669,7 @@
"operationId": "Range", "operationId": "Range",
"responses": { "responses": {
"200": { "200": {
"description": "Description", "description": "",
"schema": { "schema": {
"$ref": "#/definitions/etcdserverpbRangeResponse" "$ref": "#/definitions/etcdserverpbRangeResponse"
} }
@ -98,11 +692,11 @@
}, },
"/v3alpha/kv/txn": { "/v3alpha/kv/txn": {
"post": { "post": {
"summary": "DeleteRange deletes the given range from the key-value store.\nA delete request increments the revision of the key-value store\nand generates a delete event in the event history for every deleted key.", "summary": "Txn processes multiple requests in a single transaction.\nA txn request increments the revision of the key-value store\nand generates events with the same revision for every completed request.\nIt is not allowed to modify the same key several times within one txn.",
"operationId": "Txn", "operationId": "Txn",
"responses": { "responses": {
"200": { "200": {
"description": "Description", "description": "",
"schema": { "schema": {
"$ref": "#/definitions/etcdserverpbTxnResponse" "$ref": "#/definitions/etcdserverpbTxnResponse"
} }
@ -122,6 +716,224 @@
"KV" "KV"
] ]
} }
},
"/v3alpha/lease/grant": {
"post": {
"summary": "LeaseGrant creates a lease which expires if the server does not receive a keepAlive\nwithin a given time to live period. All keys attached to the lease will be expired and\ndeleted if the lease expires. Each expired key generates a delete event in the event history.",
"operationId": "LeaseGrant",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbLeaseGrantResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbLeaseGrantRequest"
}
}
],
"tags": [
"Lease"
]
}
},
"/v3alpha/lease/keepalive": {
"post": {
"summary": "LeaseKeepAlive keeps the lease alive by streaming keep alive requests from the client\nto the server and streaming keep alive responses from the server to the client.",
"operationId": "LeaseKeepAlive",
"responses": {
"200": {
"description": "(streaming responses)",
"schema": {
"$ref": "#/definitions/etcdserverpbLeaseKeepAliveResponse"
}
}
},
"parameters": [
{
"name": "body",
"description": "(streaming inputs)",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbLeaseKeepAliveRequest"
}
}
],
"tags": [
"Lease"
]
}
},
"/v3alpha/maintenance/alarm": {
"post": {
"summary": "Alarm activates, deactivates, and queries alarms regarding cluster health.",
"operationId": "Alarm",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbAlarmResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbAlarmRequest"
}
}
],
"tags": [
"Maintenance"
]
}
},
"/v3alpha/maintenance/defragment": {
"post": {
"summary": "Defragment defragments a member's backend database to recover storage space.",
"operationId": "Defragment",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbDefragmentResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbDefragmentRequest"
}
}
],
"tags": [
"Maintenance"
]
}
},
"/v3alpha/maintenance/hash": {
"post": {
"summary": "Hash returns the hash of the local KV state for consistency checking purpose.\nThis is designed for testing; do not use this in production when there\nare ongoing transactions.",
"operationId": "Hash",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbHashResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbHashRequest"
}
}
],
"tags": [
"Maintenance"
]
}
},
"/v3alpha/maintenance/snapshot": {
"post": {
"summary": "Snapshot sends a snapshot of the entire backend from a member over a stream to a client.",
"operationId": "Snapshot",
"responses": {
"200": {
"description": "(streaming responses)",
"schema": {
"$ref": "#/definitions/etcdserverpbSnapshotResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbSnapshotRequest"
}
}
],
"tags": [
"Maintenance"
]
}
},
"/v3alpha/maintenance/status": {
"post": {
"summary": "Status gets the status of the member.",
"operationId": "Status",
"responses": {
"200": {
"description": "",
"schema": {
"$ref": "#/definitions/etcdserverpbStatusResponse"
}
}
},
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbStatusRequest"
}
}
],
"tags": [
"Maintenance"
]
}
},
"/v3alpha/watch": {
"post": {
"summary": "Watch watches for events happening or that have happened. Both input and output\nare streams; the input stream is for creating and canceling watchers and the output\nstream sends events. One watch RPC can watch on multiple key ranges, streaming events\nfor several watches at once. The entire event history can be watched starting from the\nlast compaction revision.",
"operationId": "Watch",
"responses": {
"200": {
"description": "(streaming responses)",
"schema": {
"$ref": "#/definitions/etcdserverpbWatchResponse"
}
}
},
"parameters": [
{
"name": "body",
"description": "(streaming inputs)",
"in": "body",
"required": true,
"schema": {
"$ref": "#/definitions/etcdserverpbWatchRequest"
}
}
],
"tags": [
"Watch"
]
}
} }
}, },
"definitions": { "definitions": {
@ -662,6 +1474,11 @@
"format": "byte", "format": "byte",
"description": "key is the first key to delete in the range." "description": "key is the first key to delete in the range."
}, },
"prev_kv": {
"type": "boolean",
"format": "boolean",
"description": "If prev_kv is set, etcd gets the previous key-value pairs before deleting it.\nThe previous key-value pairs will be returned in the delte response."
},
"range_end": { "range_end": {
"type": "string", "type": "string",
"format": "byte", "format": "byte",
@ -679,6 +1496,13 @@
}, },
"header": { "header": {
"$ref": "#/definitions/etcdserverpbResponseHeader" "$ref": "#/definitions/etcdserverpbResponseHeader"
},
"prev_kvs": {
"type": "array",
"items": {
"$ref": "#/definitions/mvccpbKeyValue"
},
"description": "if prev_kv is set in the request, the previous key-value pairs will be returned."
} }
} }
}, },
@ -912,6 +1736,11 @@
"format": "int64", "format": "int64",
"description": "lease is the lease ID to associate with the key in the key-value store. A lease\nvalue of 0 indicates no lease." "description": "lease is the lease ID to associate with the key in the key-value store. A lease\nvalue of 0 indicates no lease."
}, },
"prev_kv": {
"type": "boolean",
"format": "boolean",
"description": "If prev_kv is set, etcd gets the previous key-value pair before changing it.\nThe previous key-value pair will be returned in the put response."
},
"value": { "value": {
"type": "string", "type": "string",
"format": "byte", "format": "byte",
@ -924,6 +1753,10 @@
"properties": { "properties": {
"header": { "header": {
"$ref": "#/definitions/etcdserverpbResponseHeader" "$ref": "#/definitions/etcdserverpbResponseHeader"
},
"prev_kv": {
"$ref": "#/definitions/mvccpbKeyValue",
"description": "if prev_kv is set in the request, the previous key-value pair will be returned."
} }
} }
}, },
@ -1176,6 +2009,11 @@
"format": "byte", "format": "byte",
"description": "key is the key to register for watching." "description": "key is the key to register for watching."
}, },
"prev_kv": {
"type": "boolean",
"format": "boolean",
"description": "If prev_kv is set, created watcher gets the previous KV before the event happens.\nIf the previous KV is already compacted, nothing will be returned."
},
"progress_notify": { "progress_notify": {
"type": "boolean", "type": "boolean",
"format": "boolean", "format": "boolean",
@ -1245,6 +2083,10 @@
"$ref": "#/definitions/mvccpbKeyValue", "$ref": "#/definitions/mvccpbKeyValue",
"description": "kv holds the KeyValue for the event.\nA PUT event contains current kv pair.\nA PUT event with kv.Version=1 indicates the creation of a key.\nA DELETE/EXPIRE event contains the deleted key with\nits modification revision set to the revision of deletion." "description": "kv holds the KeyValue for the event.\nA PUT event contains current kv pair.\nA PUT event with kv.Version=1 indicates the creation of a key.\nA DELETE/EXPIRE event contains the deleted key with\nits modification revision set to the revision of deletion."
}, },
"prev_kv": {
"$ref": "#/definitions/mvccpbKeyValue",
"description": "prev_kv holds the key-value pair before the event happens."
},
"type": { "type": {
"$ref": "#/definitions/EventEventType", "$ref": "#/definitions/EventEventType",
"description": "type is the kind of event. If type is a PUT, it indicates\nnew data has been stored to the key. If type is a DELETE,\nit indicates the key was deleted." "description": "type is the kind of event. If type is a PUT, it indicates\nnew data has been stored to the key. If type is a DELETE,\nit indicates the key was deleted."

View File

@ -4,5 +4,5 @@ For the most part, the etcd project is stable, but we are still moving fast! We
## The current experimental API/features are: ## The current experimental API/features are:
- v3 auth API: expect to be stale in 3.1 release - v3 auth API: expect to be stable in 3.1 release
- etcd gateway: expect to be stable in 3.1 release - etcd gateway: expect to be stable in 3.1 release

View File

@ -11,7 +11,7 @@ The easiest way to get etcd is to use one of the pre-built release binaries whic
## Build the latest version ## Build the latest version
For those wanting to try the very latest version, build etcd from the `master` branch. For those wanting to try the very latest version, build etcd from the `master` branch.
[Go](https://golang.org/) version 1.5+ is required to build the latest version of etcd. [Go](https://golang.org/) version 1.6+ (with HTTP2 support) is required to build the latest version of etcd.
Here are the commands to build an etcd binary from the `master` branch: Here are the commands to build an etcd binary from the `master` branch:

View File

@ -357,6 +357,8 @@ To help clients discover the etcd cluster, the following DNS SRV records are loo
If `_etcd-client-ssl._tcp.example.com` is found, clients will attempt to communicate with the etcd cluster over SSL/TLS. If `_etcd-client-ssl._tcp.example.com` is found, clients will attempt to communicate with the etcd cluster over SSL/TLS.
If etcd is using TLS without a custom certificate authority, the discovery domain (e.g., example.com) must match the SRV record domain (e.g., infra1.example.com). This is to mitigate attacks that forge SRV records to point to a different domain; the domain would have a valid certificate under PKI but be controlled by an unknown third party.
#### Create DNS SRV records #### Create DNS SRV records
``` ```

View File

@ -8,7 +8,7 @@ In order to expose the etcd API to clients outside of Docker host, use the host
``` ```
# For each machine # For each machine
ETCD_VERSION=v3.0.0-beta.0 ETCD_VERSION=v3.0.0
TOKEN=my-etcd-token TOKEN=my-etcd-token
CLUSTER_STATE=new CLUSTER_STATE=new
NAME_1=etcd-node-0 NAME_1=etcd-node-0

View File

@ -18,7 +18,7 @@ Also, to ensure a smooth rolling upgrade, the running cluster must be healthy. Y
Before upgrading etcd, always test the services relying on etcd in a staging environment before deploying the upgrade to the production environment. Before upgrading etcd, always test the services relying on etcd in a staging environment before deploying the upgrade to the production environment.
Before beginning, [backup the etcd data directory](admin_guide.md#backing-up-the-datastore). Should something go wrong with the upgrade, it is possible to use this backup to [downgrade](#downgrade) back to existing etcd version. Before beginning, [backup the etcd data directory](../v2/admin_guide.md#backing-up-the-datastore). Should something go wrong with the upgrade, it is possible to use this backup to [downgrade](#downgrade) back to existing etcd version.
#### Mixed Versions #### Mixed Versions
@ -34,7 +34,7 @@ For a much larger total data size, 100MB or more , this one-time process might t
If all members have been upgraded to v3.0, the cluster will be upgraded to v3.0, and downgrade from this completed state is **not possible**. If any single member is still v2.3, however, the cluster and its operations remains “v2.3”, and it is possible from this mixed cluster state to return to using a v2.3 etcd binary on all members. If all members have been upgraded to v3.0, the cluster will be upgraded to v3.0, and downgrade from this completed state is **not possible**. If any single member is still v2.3, however, the cluster and its operations remains “v2.3”, and it is possible from this mixed cluster state to return to using a v2.3 etcd binary on all members.
Please [backup the data directory](admin_guide.md#backing-up-the-datastore) of all etcd members to make downgrading the cluster possible even after it has been completely upgraded. Please [backup the data directory](../v2/admin_guide.md#backing-up-the-datastore) of all etcd members to make downgrading the cluster possible even after it has been completely upgraded.
### Upgrade Procedure ### Upgrade Procedure
@ -64,7 +64,7 @@ When each etcd process is stopped, expected errors will be logged by other clust
2016-06-27 15:21:48.624175 I | rafthttp: the connection with 8211f1d0f64f3269 became inactive 2016-06-27 15:21:48.624175 I | rafthttp: the connection with 8211f1d0f64f3269 became inactive
``` ```
Its a good idea at this point to [backup the etcd data directory](https://github.com/coreos/etcd/blob/master/Documentation/v2/admin_guide.md#backing-up-the-datastore) to provide a downgrade path should any problems occur: Its a good idea at this point to [backup the etcd data directory](../v2/admin_guide.md#backing-up-the-datastore) to provide a downgrade path should any problems occur:
``` ```
$ etcdctl backup \ $ etcdctl backup \

View File

@ -40,7 +40,7 @@ See [etcdctl][etcdctl] for a simple command line client.
The easiest way to get etcd is to use one of the pre-built release binaries which are available for OSX, Linux, Windows, AppC (ACI), and Docker. Instructions for using these binaries are on the [GitHub releases page][github-release]. The easiest way to get etcd is to use one of the pre-built release binaries which are available for OSX, Linux, Windows, AppC (ACI), and Docker. Instructions for using these binaries are on the [GitHub releases page][github-release].
For those wanting to try the very latest version, you can build the latest version of etcd from the `master` branch. For those wanting to try the very latest version, you can build the latest version of etcd from the `master` branch.
You will first need [*Go*](https://golang.org/) installed on your machine (version 1.5+ is required). You will first need [*Go*](https://golang.org/) installed on your machine (version 1.6+ is required).
All development occurs on `master`, including new features and bug fixes. All development occurs on `master`, including new features and bug fixes.
Bug fixes are first targeted at `master` and subsequently ported to release branches, as described in the [branch management][branch-management] guide. Bug fixes are first targeted at `master` and subsequently ported to release branches, as described in the [branch management][branch-management] guide.

View File

@ -18,12 +18,12 @@ package authpb
import ( import (
"fmt" "fmt"
proto "github.com/gogo/protobuf/proto" proto "github.com/golang/protobuf/proto"
math "math" math "math"
)
import io "io" io "io"
)
// Reference imports to suppress errors if they are not otherwise used. // Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal var _ = proto.Marshal
@ -32,7 +32,7 @@ var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file // This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against. // is compatible with the proto package it is being compiled against.
const _ = proto.GoGoProtoPackageIsVersion1 const _ = proto.ProtoPackageIsVersion1
type Permission_Type int32 type Permission_Type int32
@ -798,23 +798,23 @@ var (
) )
var fileDescriptorAuth = []byte{ var fileDescriptorAuth = []byte{
// 276 bytes of a gzipped FileDescriptorProto // 288 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0xe2, 0x4a, 0x2c, 0x2d, 0xc9, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x6c, 0x90, 0xc1, 0x4a, 0xc3, 0x30,
0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x03, 0xb1, 0x0b, 0x92, 0xa4, 0x44, 0xd2, 0xf3, 0x1c, 0xc6, 0x9b, 0xb6, 0x1b, 0xed, 0x5f, 0x27, 0x25, 0x0c, 0x0c, 0x13, 0x42, 0xe9, 0xa9, 0x78,
0xd3, 0xf3, 0xc1, 0x42, 0xfa, 0x20, 0x16, 0x44, 0x56, 0xc9, 0x87, 0x8b, 0x25, 0xb4, 0x38, 0xb5, 0xa8, 0xb0, 0x5d, 0xbc, 0x2a, 0xf6, 0x20, 0x78, 0x90, 0x50, 0xf1, 0x28, 0x1d, 0x0d, 0x75, 0x6c,
0x48, 0x48, 0x88, 0x8b, 0x25, 0x2f, 0x31, 0x37, 0x55, 0x82, 0x51, 0x81, 0x51, 0x83, 0x27, 0x08, 0x6d, 0x4a, 0x32, 0x91, 0xbe, 0x89, 0x07, 0x1f, 0x68, 0xc7, 0x3d, 0x82, 0xab, 0x2f, 0x22, 0x4d,
0xcc, 0x16, 0x92, 0xe2, 0xe2, 0x28, 0x48, 0x2c, 0x2e, 0x2e, 0xcf, 0x2f, 0x4a, 0x91, 0x60, 0x02, 0x64, 0x43, 0xdc, 0xed, 0xfb, 0xbe, 0xff, 0x97, 0xe4, 0x97, 0x3f, 0x40, 0xfe, 0xb6, 0x7e, 0x4d,
0x8b, 0xc3, 0xf9, 0x42, 0x22, 0x5c, 0xac, 0x45, 0xf9, 0x39, 0xa9, 0xc5, 0x12, 0xcc, 0x0a, 0xcc, 0x1a, 0x29, 0xd6, 0x02, 0x0f, 0x7b, 0xdd, 0xcc, 0x27, 0xe3, 0x52, 0x94, 0x42, 0x47, 0x57, 0xbd,
0x1a, 0x9c, 0x41, 0x10, 0x8e, 0xd2, 0x1c, 0x46, 0x2e, 0xae, 0x80, 0xd4, 0xa2, 0xdc, 0xcc, 0xe2, 0x32, 0xd3, 0xe8, 0x01, 0xdc, 0x27, 0xc5, 0x25, 0xc6, 0xe0, 0xd6, 0x79, 0xc5, 0x09, 0x0a, 0x51,
0xe2, 0xcc, 0xfc, 0x3c, 0x21, 0x63, 0xa0, 0x01, 0x40, 0x5e, 0x48, 0x65, 0x01, 0xc4, 0x60, 0x3e, 0x7c, 0xca, 0xb4, 0xc6, 0x13, 0xf0, 0x9a, 0x5c, 0xa9, 0x77, 0x21, 0x0b, 0x62, 0xeb, 0x7c, 0xef,
0x23, 0x71, 0x3d, 0x88, 0x6b, 0xf4, 0x10, 0xaa, 0xf4, 0x40, 0xd2, 0x41, 0x70, 0x85, 0x42, 0x02, 0xf1, 0x18, 0x06, 0x52, 0xac, 0xb8, 0x22, 0x4e, 0xe8, 0xc4, 0x3e, 0x33, 0x26, 0xfa, 0x44, 0x00,
0x5c, 0xcc, 0xd9, 0xa9, 0x95, 0x50, 0x0b, 0x41, 0x4c, 0x21, 0x69, 0x2e, 0xce, 0xa2, 0xc4, 0xbc, 0x8f, 0x5c, 0x56, 0x0b, 0xa5, 0x16, 0xa2, 0xc6, 0x33, 0xf0, 0x1a, 0x2e, 0xab, 0xac, 0x6d, 0xcc,
0xf4, 0xd4, 0xf8, 0xd4, 0xbc, 0x14, 0xa0, 0x7d, 0x60, 0x87, 0x80, 0x05, 0x5c, 0xf3, 0x52, 0x94, 0xc5, 0x67, 0xd3, 0xf3, 0xc4, 0xd0, 0x24, 0x87, 0x56, 0xd2, 0x8f, 0xd9, 0xbe, 0x88, 0x03, 0x70,
0xb4, 0xb8, 0x58, 0xc0, 0xda, 0x38, 0xb8, 0x58, 0x82, 0x5c, 0x1d, 0x5d, 0x04, 0x18, 0x84, 0x38, 0x96, 0xbc, 0xfd, 0x7d, 0xb0, 0x97, 0xf8, 0x02, 0x7c, 0x99, 0xd7, 0x25, 0x7f, 0xe1, 0x75, 0x41,
0xb9, 0x58, 0xc3, 0x83, 0x3c, 0x43, 0x5c, 0x05, 0x18, 0x85, 0x78, 0xb9, 0x38, 0x41, 0x82, 0x10, 0x1c, 0x03, 0xa2, 0x83, 0xb4, 0x2e, 0xa2, 0x4b, 0x70, 0xf5, 0x31, 0x0f, 0x5c, 0x96, 0xde, 0xdc,
0x2e, 0x93, 0x52, 0x08, 0x50, 0x0d, 0xd0, 0x9d, 0x58, 0x3d, 0x6b, 0xc1, 0xc5, 0x0b, 0xb4, 0x0b, 0x05, 0x16, 0xf6, 0x61, 0xf0, 0xcc, 0xee, 0xb3, 0x34, 0x40, 0x78, 0x04, 0x7e, 0x1f, 0x1a, 0x6b,
0xe1, 0x2c, 0xa0, 0x03, 0x98, 0x35, 0xb8, 0x8d, 0x84, 0x30, 0x1d, 0x1c, 0x84, 0xaa, 0xd0, 0x49, 0x47, 0x19, 0xb8, 0x4c, 0xac, 0xf8, 0xd1, 0xcf, 0x5e, 0xc3, 0x68, 0xc9, 0xdb, 0x03, 0x16, 0xb1,
0xe4, 0xc4, 0x43, 0x39, 0x86, 0x0b, 0x40, 0x7c, 0xe2, 0x91, 0x1c, 0xe3, 0x05, 0x20, 0x7e, 0x00, 0x43, 0x27, 0x3e, 0x99, 0xe2, 0xff, 0xc0, 0xec, 0x6f, 0xf1, 0x96, 0x6c, 0x76, 0xd4, 0xda, 0xee,
0xc4, 0x49, 0x6c, 0xe0, 0xf0, 0x35, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0x9e, 0x31, 0x53, 0xfd, 0xa8, 0xb5, 0xe9, 0x28, 0xda, 0x76, 0x14, 0x7d, 0x75, 0x14, 0x7d, 0x7c, 0x53, 0x6b, 0x3e, 0xd4,
0x8b, 0x01, 0x00, 0x00, 0x3b, 0x9e, 0xfd, 0x04, 0x00, 0x00, 0xff, 0xff, 0xcc, 0x76, 0x8d, 0x4f, 0x8f, 0x01, 0x00, 0x00,
} }

View File

@ -22,7 +22,10 @@ import (
"github.com/coreos/etcd/mvcc/backend" "github.com/coreos/etcd/mvcc/backend"
) )
// isSubset returns true if a is a subset of b // isSubset returns true if a is a subset of b.
// If a is a prefix of b, then a is a subset of b.
// Given intervals [a1,a2) and [b1,b2), is
// the a interval a subset of b?
func isSubset(a, b *rangePerm) bool { func isSubset(a, b *rangePerm) bool {
switch { switch {
case len(a.end) == 0 && len(b.end) == 0: case len(a.end) == 0 && len(b.end) == 0:
@ -32,9 +35,11 @@ func isSubset(a, b *rangePerm) bool {
// b is a key, a is a range // b is a key, a is a range
return false return false
case len(a.end) == 0: case len(a.end) == 0:
return 0 <= bytes.Compare(a.begin, b.begin) && bytes.Compare(a.begin, b.end) <= 0 // a is a key, b is a range. need b1 <= a1 and a1 < b2
return bytes.Compare(b.begin, a.begin) <= 0 && bytes.Compare(a.begin, b.end) < 0
default: default:
return 0 <= bytes.Compare(a.begin, b.begin) && bytes.Compare(a.end, b.end) <= 0 // both are ranges. need b1 <= a1 and a2 <= b2
return bytes.Compare(b.begin, a.begin) <= 0 && bytes.Compare(a.end, b.end) <= 0
} }
} }
@ -88,12 +93,18 @@ func mergeRangePerms(perms []*rangePerm) []*rangePerm {
i := 0 i := 0
for i < len(perms) { for i < len(perms) {
begin, next := i, i begin, next := i, i
for next+1 < len(perms) && bytes.Compare(perms[next].end, perms[next+1].begin) != -1 { for next+1 < len(perms) && bytes.Compare(perms[next].end, perms[next+1].begin) >= 0 {
next++ next++
} }
// don't merge ["a", "b") with ["b", ""), because perms[next+1].end is empty.
merged = append(merged, &rangePerm{begin: perms[begin].begin, end: perms[next].end}) if next != begin && len(perms[next].end) > 0 {
merged = append(merged, &rangePerm{begin: perms[begin].begin, end: perms[next].end})
} else {
merged = append(merged, perms[begin])
if next != begin {
merged = append(merged, perms[next])
}
}
i = next + 1 i = next + 1
} }

View File

@ -46,6 +46,10 @@ func TestGetMergedPerms(t *testing.T) {
[]*rangePerm{{[]byte("a"), []byte("b")}}, []*rangePerm{{[]byte("a"), []byte("b")}},
[]*rangePerm{{[]byte("a"), []byte("b")}}, []*rangePerm{{[]byte("a"), []byte("b")}},
}, },
{
[]*rangePerm{{[]byte("a"), []byte("b")}, {[]byte("b"), []byte("")}},
[]*rangePerm{{[]byte("a"), []byte("b")}, {[]byte("b"), []byte("")}},
},
{ {
[]*rangePerm{{[]byte("a"), []byte("b")}, {[]byte("b"), []byte("c")}}, []*rangePerm{{[]byte("a"), []byte("b")}, {[]byte("b"), []byte("c")}},
[]*rangePerm{{[]byte("a"), []byte("c")}}, []*rangePerm{{[]byte("a"), []byte("c")}},
@ -106,7 +110,7 @@ func TestGetMergedPerms(t *testing.T) {
}, },
{ {
[]*rangePerm{{[]byte("a"), []byte("")}, {[]byte("b"), []byte("c")}, {[]byte("b"), []byte("")}, {[]byte("c"), []byte("")}, {[]byte("d"), []byte("")}}, []*rangePerm{{[]byte("a"), []byte("")}, {[]byte("b"), []byte("c")}, {[]byte("b"), []byte("")}, {[]byte("c"), []byte("")}, {[]byte("d"), []byte("")}},
[]*rangePerm{{[]byte("a"), []byte("")}, {[]byte("b"), []byte("c")}, {[]byte("d"), []byte("")}}, []*rangePerm{{[]byte("a"), []byte("")}, {[]byte("b"), []byte("c")}, {[]byte("c"), []byte("")}, {[]byte("d"), []byte("")}},
}, },
// duplicate ranges // duplicate ranges
{ {

View File

@ -603,6 +603,11 @@ func (as *authStore) isOpPermitted(userName string, key, rangeEnd []byte, permTy
return false return false
} }
// root role should have permission on all ranges
if hasRootRole(user) {
return true
}
if as.isRangeOpPermitted(tx, userName, key, rangeEnd, permTyp) { if as.isRangeOpPermitted(tx, userName, key, rangeEnd, permTyp) {
return true return true
} }

View File

@ -37,6 +37,10 @@ var (
ErrClusterUnavailable = errors.New("client: etcd cluster is unavailable or misconfigured") ErrClusterUnavailable = errors.New("client: etcd cluster is unavailable or misconfigured")
ErrNoLeaderEndpoint = errors.New("client: no leader endpoint available") ErrNoLeaderEndpoint = errors.New("client: no leader endpoint available")
errTooManyRedirectChecks = errors.New("client: too many redirect checks") errTooManyRedirectChecks = errors.New("client: too many redirect checks")
// oneShotCtxValue is set on a context using WithValue(&oneShotValue) so
// that Do() will not retry a request
oneShotCtxValue interface{}
) )
var DefaultRequestTimeout = 5 * time.Second var DefaultRequestTimeout = 5 * time.Second
@ -335,6 +339,7 @@ func (c *httpClusterClient) Do(ctx context.Context, act httpAction) (*http.Respo
var body []byte var body []byte
var err error var err error
cerr := &ClusterError{} cerr := &ClusterError{}
isOneShot := ctx.Value(&oneShotCtxValue) != nil
for i := pinned; i < leps+pinned; i++ { for i := pinned; i < leps+pinned; i++ {
k := i % leps k := i % leps
@ -348,6 +353,9 @@ func (c *httpClusterClient) Do(ctx context.Context, act httpAction) (*http.Respo
if err == context.Canceled || err == context.DeadlineExceeded { if err == context.Canceled || err == context.DeadlineExceeded {
return nil, nil, err return nil, nil, err
} }
if isOneShot {
return nil, nil, err
}
continue continue
} }
if resp.StatusCode/100 == 5 { if resp.StatusCode/100 == 5 {
@ -358,6 +366,9 @@ func (c *httpClusterClient) Do(ctx context.Context, act httpAction) (*http.Respo
default: default:
cerr.Errors = append(cerr.Errors, fmt.Errorf("client: etcd member %s returns server error [%s]", eps[k].String(), http.StatusText(resp.StatusCode))) cerr.Errors = append(cerr.Errors, fmt.Errorf("client: etcd member %s returns server error [%s]", eps[k].String(), http.StatusText(resp.StatusCode)))
} }
if isOneShot {
return nil, nil, cerr.Errors[0]
}
continue continue
} }
if k != pinned { if k != pinned {

View File

@ -0,0 +1,134 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package integration
import (
"fmt"
"net/http"
"net/http/httptest"
"os"
"strings"
"sync/atomic"
"testing"
"golang.org/x/net/context"
"github.com/coreos/etcd/client"
"github.com/coreos/etcd/integration"
"github.com/coreos/etcd/pkg/testutil"
)
// TestV2NoRetryEOF tests destructive api calls won't retry on a disconnection.
func TestV2NoRetryEOF(t *testing.T) {
defer testutil.AfterTest(t)
// generate an EOF response; specify address so appears first in sorted ep list
lEOF := integration.NewListenerWithAddr(t, fmt.Sprintf("eof:123.%d.sock", os.Getpid()))
defer lEOF.Close()
tries := uint32(0)
go func() {
for {
conn, err := lEOF.Accept()
if err != nil {
return
}
atomic.AddUint32(&tries, 1)
conn.Close()
}
}()
eofURL := integration.UrlScheme + "://" + lEOF.Addr().String()
cli := integration.MustNewHTTPClient(t, []string{eofURL, eofURL}, nil)
kapi := client.NewKeysAPI(cli)
for i, f := range noRetryList(kapi) {
startTries := atomic.LoadUint32(&tries)
if err := f(); err == nil {
t.Errorf("#%d: expected EOF error, got nil", i)
}
endTries := atomic.LoadUint32(&tries)
if startTries+1 != endTries {
t.Errorf("#%d: expected 1 try, got %d", i, endTries-startTries)
}
}
}
// TestV2NoRetryNoLeader tests destructive api calls won't retry if given an error code.
func TestV2NoRetryNoLeader(t *testing.T) {
defer testutil.AfterTest(t)
lHttp := integration.NewListenerWithAddr(t, fmt.Sprintf("errHttp:123.%d.sock", os.Getpid()))
eh := &errHandler{errCode: http.StatusServiceUnavailable}
srv := httptest.NewUnstartedServer(eh)
defer lHttp.Close()
defer srv.Close()
srv.Listener = lHttp
go srv.Start()
lHttpURL := integration.UrlScheme + "://" + lHttp.Addr().String()
cli := integration.MustNewHTTPClient(t, []string{lHttpURL, lHttpURL}, nil)
kapi := client.NewKeysAPI(cli)
// test error code
for i, f := range noRetryList(kapi) {
reqs := eh.reqs
if err := f(); err == nil || !strings.Contains(err.Error(), "no leader") {
t.Errorf("#%d: expected \"no leader\", got %v", i, err)
}
if eh.reqs != reqs+1 {
t.Errorf("#%d: expected 1 request, got %d", i, eh.reqs-reqs)
}
}
}
// TestV2RetryRefuse tests destructive api calls will retry if a connection is refused.
func TestV2RetryRefuse(t *testing.T) {
defer testutil.AfterTest(t)
cl := integration.NewCluster(t, 1)
cl.Launch(t)
defer cl.Terminate(t)
// test connection refused; expect no error failover
cli := integration.MustNewHTTPClient(t, []string{integration.UrlScheme + "://refuseconn:123", cl.URL(0)}, nil)
kapi := client.NewKeysAPI(cli)
if _, err := kapi.Set(context.Background(), "/delkey", "def", nil); err != nil {
t.Fatal(err)
}
for i, f := range noRetryList(kapi) {
if err := f(); err != nil {
t.Errorf("#%d: unexpected retry failure (%v)", i, err)
}
}
}
type errHandler struct {
errCode int
reqs int
}
func (eh *errHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
req.Body.Close()
eh.reqs++
w.WriteHeader(eh.errCode)
}
func noRetryList(kapi client.KeysAPI) []func() error {
return []func() error{
func() error {
opts := &client.SetOptions{PrevExist: client.PrevNoExist}
_, err := kapi.Set(context.Background(), "/setkey", "bar", opts)
return err
},
func() error {
_, err := kapi.Delete(context.Background(), "/delkey", nil)
return err
},
}
}

View File

@ -0,0 +1,20 @@
// Copyright 2013 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package integration
import (
"os"
"testing"
"github.com/coreos/etcd/pkg/testutil"
)
func TestMain(m *testing.M) {
v := m.Run()
if v == 0 && testutil.CheckLeakedGoroutine() {
os.Exit(1)
}
os.Exit(v)
}

View File

@ -337,7 +337,11 @@ func (k *httpKeysAPI) Set(ctx context.Context, key, val string, opts *SetOptions
act.Dir = opts.Dir act.Dir = opts.Dir
} }
resp, body, err := k.client.Do(ctx, act) doCtx := ctx
if act.PrevExist == PrevNoExist {
doCtx = context.WithValue(doCtx, &oneShotCtxValue, &oneShotCtxValue)
}
resp, body, err := k.client.Do(doCtx, act)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -385,7 +389,8 @@ func (k *httpKeysAPI) Delete(ctx context.Context, key string, opts *DeleteOption
act.Recursive = opts.Recursive act.Recursive = opts.Recursive
} }
resp, body, err := k.client.Do(ctx, act) doCtx := context.WithValue(ctx, &oneShotCtxValue, &oneShotCtxValue)
resp, body, err := k.client.Do(doCtx, act)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -145,12 +145,12 @@ func (auth *auth) UserGrantRole(ctx context.Context, user string, role string) (
} }
func (auth *auth) UserGet(ctx context.Context, name string) (*AuthUserGetResponse, error) { func (auth *auth) UserGet(ctx context.Context, name string) (*AuthUserGetResponse, error) {
resp, err := auth.remote.UserGet(ctx, &pb.AuthUserGetRequest{Name: name}) resp, err := auth.remote.UserGet(ctx, &pb.AuthUserGetRequest{Name: name}, grpc.FailFast(false))
return (*AuthUserGetResponse)(resp), toErr(ctx, err) return (*AuthUserGetResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) UserList(ctx context.Context) (*AuthUserListResponse, error) { func (auth *auth) UserList(ctx context.Context) (*AuthUserListResponse, error) {
resp, err := auth.remote.UserList(ctx, &pb.AuthUserListRequest{}) resp, err := auth.remote.UserList(ctx, &pb.AuthUserListRequest{}, grpc.FailFast(false))
return (*AuthUserListResponse)(resp), toErr(ctx, err) return (*AuthUserListResponse)(resp), toErr(ctx, err)
} }
@ -175,12 +175,12 @@ func (auth *auth) RoleGrantPermission(ctx context.Context, name string, key, ran
} }
func (auth *auth) RoleGet(ctx context.Context, role string) (*AuthRoleGetResponse, error) { func (auth *auth) RoleGet(ctx context.Context, role string) (*AuthRoleGetResponse, error) {
resp, err := auth.remote.RoleGet(ctx, &pb.AuthRoleGetRequest{Role: role}) resp, err := auth.remote.RoleGet(ctx, &pb.AuthRoleGetRequest{Role: role}, grpc.FailFast(false))
return (*AuthRoleGetResponse)(resp), toErr(ctx, err) return (*AuthRoleGetResponse)(resp), toErr(ctx, err)
} }
func (auth *auth) RoleList(ctx context.Context) (*AuthRoleListResponse, error) { func (auth *auth) RoleList(ctx context.Context) (*AuthRoleListResponse, error) {
resp, err := auth.remote.RoleList(ctx, &pb.AuthRoleListRequest{}) resp, err := auth.remote.RoleList(ctx, &pb.AuthRoleListRequest{}, grpc.FailFast(false))
return (*AuthRoleListResponse)(resp), toErr(ctx, err) return (*AuthRoleListResponse)(resp), toErr(ctx, err)
} }
@ -208,7 +208,7 @@ type authenticator struct {
} }
func (auth *authenticator) authenticate(ctx context.Context, name string, password string) (*AuthenticateResponse, error) { func (auth *authenticator) authenticate(ctx context.Context, name string, password string) (*AuthenticateResponse, error) {
resp, err := auth.remote.Authenticate(ctx, &pb.AuthenticateRequest{Name: name, Password: password}) resp, err := auth.remote.Authenticate(ctx, &pb.AuthenticateRequest{Name: name, Password: password}, grpc.FailFast(false))
return (*AuthenticateResponse)(resp), toErr(ctx, err) return (*AuthenticateResponse)(resp), toErr(ctx, err)
} }

View File

@ -17,7 +17,7 @@ package clientv3
import ( import (
"net/url" "net/url"
"strings" "strings"
"sync/atomic" "sync"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
@ -26,32 +26,133 @@ import (
// simpleBalancer does the bare minimum to expose multiple eps // simpleBalancer does the bare minimum to expose multiple eps
// to the grpc reconnection code path // to the grpc reconnection code path
type simpleBalancer struct { type simpleBalancer struct {
// eps are the client's endpoints stripped of any URL scheme // addrs are the client's endpoints for grpc
eps []string addrs []grpc.Address
ch chan []grpc.Address // notifyCh notifies grpc of the set of addresses for connecting
numGets uint32 notifyCh chan []grpc.Address
// readyc closes once the first connection is up
readyc chan struct{}
readyOnce sync.Once
// mu protects upEps, pinAddr, and connectingAddr
mu sync.RWMutex
// upEps holds the current endpoints that have an active connection
upEps map[string]struct{}
// upc closes when upEps transitions from empty to non-zero or the balancer closes.
upc chan struct{}
// pinAddr is the currently pinned address; set to the empty string on
// intialization and shutdown.
pinAddr string
closed bool
} }
func newSimpleBalancer(eps []string) grpc.Balancer { func newSimpleBalancer(eps []string) *simpleBalancer {
ch := make(chan []grpc.Address, 1) notifyCh := make(chan []grpc.Address, 1)
addrs := make([]grpc.Address, len(eps)) addrs := make([]grpc.Address, len(eps))
for i := range eps { for i := range eps {
addrs[i].Addr = getHost(eps[i]) addrs[i].Addr = getHost(eps[i])
} }
ch <- addrs notifyCh <- addrs
return &simpleBalancer{eps: eps, ch: ch} sb := &simpleBalancer{
addrs: addrs,
notifyCh: notifyCh,
readyc: make(chan struct{}),
upEps: make(map[string]struct{}),
upc: make(chan struct{}),
}
return sb
} }
func (b *simpleBalancer) Start(target string) error { return nil } func (b *simpleBalancer) Start(target string) error { return nil }
func (b *simpleBalancer) Up(addr grpc.Address) func(error) { return func(error) {} }
func (b *simpleBalancer) Get(ctx context.Context, opts grpc.BalancerGetOptions) (grpc.Address, func(), error) { func (b *simpleBalancer) ConnectNotify() <-chan struct{} {
v := atomic.AddUint32(&b.numGets, 1) b.mu.Lock()
ep := b.eps[v%uint32(len(b.eps))] defer b.mu.Unlock()
return grpc.Address{Addr: getHost(ep)}, func() {}, nil return b.upc
} }
func (b *simpleBalancer) Notify() <-chan []grpc.Address { return b.ch }
func (b *simpleBalancer) Up(addr grpc.Address) func(error) {
b.mu.Lock()
defer b.mu.Unlock()
// gRPC might call Up after it called Close. We add this check
// to "fix" it up at application layer. Or our simplerBalancer
// might panic since b.upc is closed.
if b.closed {
return func(err error) {}
}
if len(b.upEps) == 0 {
// notify waiting Get()s and pin first connected address
close(b.upc)
b.pinAddr = addr.Addr
}
b.upEps[addr.Addr] = struct{}{}
// notify client that a connection is up
b.readyOnce.Do(func() { close(b.readyc) })
return func(err error) {
b.mu.Lock()
delete(b.upEps, addr.Addr)
if len(b.upEps) == 0 && b.pinAddr != "" {
b.upc = make(chan struct{})
} else if b.pinAddr == addr.Addr {
// choose new random up endpoint
for k := range b.upEps {
b.pinAddr = k
break
}
}
b.mu.Unlock()
}
}
func (b *simpleBalancer) Get(ctx context.Context, opts grpc.BalancerGetOptions) (grpc.Address, func(), error) {
var addr string
for {
b.mu.RLock()
ch := b.upc
b.mu.RUnlock()
select {
case <-ch:
case <-ctx.Done():
return grpc.Address{Addr: ""}, nil, ctx.Err()
}
b.mu.RLock()
addr = b.pinAddr
upEps := len(b.upEps)
b.mu.RUnlock()
if addr == "" {
return grpc.Address{Addr: ""}, nil, grpc.ErrClientConnClosing
}
if upEps > 0 {
break
}
}
return grpc.Address{Addr: addr}, func() {}, nil
}
func (b *simpleBalancer) Notify() <-chan []grpc.Address { return b.notifyCh }
func (b *simpleBalancer) Close() error { func (b *simpleBalancer) Close() error {
close(b.ch) b.mu.Lock()
defer b.mu.Unlock()
// In case gRPC calls close twice. TODO: remove the checking
// when we are sure that gRPC wont call close twice.
if b.closed {
return nil
}
b.closed = true
close(b.notifyCh)
// terminate all waiting Get()s
b.pinAddr = ""
if len(b.upEps) == 0 {
close(b.upc)
}
return nil return nil
} }

View File

@ -46,9 +46,11 @@ type Client struct {
Auth Auth
Maintenance Maintenance
conn *grpc.ClientConn conn *grpc.ClientConn
cfg Config cfg Config
creds *credentials.TransportCredentials creds *credentials.TransportCredentials
balancer *simpleBalancer
retryWrapper retryRpcFunc
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
@ -138,11 +140,10 @@ func (c *Client) dialTarget(endpoint string) (proto string, host string, creds *
return return
} }
// dialSetupOpts gives the dial opts prioer to any authentication // dialSetupOpts gives the dial opts prior to any authentication
func (c *Client) dialSetupOpts(endpoint string, dopts ...grpc.DialOption) []grpc.DialOption { func (c *Client) dialSetupOpts(endpoint string, dopts ...grpc.DialOption) (opts []grpc.DialOption) {
opts := []grpc.DialOption{ if c.cfg.DialTimeout > 0 {
grpc.WithBlock(), opts = []grpc.DialOption{grpc.WithTimeout(c.cfg.DialTimeout)}
grpc.WithTimeout(c.cfg.DialTimeout),
} }
opts = append(opts, dopts...) opts = append(opts, dopts...)
@ -240,12 +241,30 @@ func newClient(cfg *Config) (*Client, error) {
client.Password = cfg.Password client.Password = cfg.Password
} }
b := newSimpleBalancer(cfg.Endpoints) client.balancer = newSimpleBalancer(cfg.Endpoints)
conn, err := client.dial(cfg.Endpoints[0], grpc.WithBalancer(b)) conn, err := client.dial(cfg.Endpoints[0], grpc.WithBalancer(client.balancer))
if err != nil { if err != nil {
return nil, err return nil, err
} }
client.conn = conn client.conn = conn
client.retryWrapper = client.newRetryWrapper()
// wait for a connection
if cfg.DialTimeout > 0 {
hasConn := false
waitc := time.After(cfg.DialTimeout)
select {
case <-client.balancer.readyc:
hasConn = true
case <-ctx.Done():
case <-waitc:
}
if !hasConn {
client.cancel()
conn.Close()
return nil, grpc.ErrClientConnTimeout
}
}
client.Cluster = NewCluster(client) client.Cluster = NewCluster(client)
client.KV = NewKV(client) client.KV = NewKV(client)
@ -275,8 +294,17 @@ func isHaltErr(ctx context.Context, err error) bool {
if err == nil { if err == nil {
return false return false
} }
return strings.HasPrefix(grpc.ErrorDesc(err), "etcdserver: ") || eErr := rpctypes.Error(err)
strings.Contains(err.Error(), grpc.ErrClientConnClosing.Error()) if _, ok := eErr.(rpctypes.EtcdError); ok {
return eErr != rpctypes.ErrStopped && eErr != rpctypes.ErrNoLeader
}
// treat etcdserver errors not recognized by the client as halting
return isConnClosing(err) || strings.Contains(err.Error(), "etcdserver:")
}
// isConnClosing returns true if the error matches a grpc client closing error
func isConnClosing(err error) bool {
return strings.Contains(err.Error(), grpc.ErrClientConnClosing.Error())
} }
func toErr(ctx context.Context, err error) error { func toErr(ctx context.Context, err error) error {
@ -284,9 +312,12 @@ func toErr(ctx context.Context, err error) error {
return nil return nil
} }
err = rpctypes.Error(err) err = rpctypes.Error(err)
if ctx.Err() != nil && strings.Contains(err.Error(), "context") { switch {
case ctx.Err() != nil && strings.Contains(err.Error(), "context"):
err = ctx.Err() err = ctx.Err()
} else if strings.Contains(err.Error(), grpc.ErrClientConnClosing.Error()) { case strings.Contains(err.Error(), ErrNoAvailableEndpoints.Error()):
err = ErrNoAvailableEndpoints
case strings.Contains(err.Error(), grpc.ErrClientConnClosing.Error()):
err = grpc.ErrClientConnClosing err = grpc.ErrClientConnClosing
} }
return err return err

View File

@ -19,11 +19,15 @@ import (
"testing" "testing"
"time" "time"
"github.com/coreos/etcd/etcdserver"
"github.com/coreos/etcd/pkg/testutil"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
) )
func TestDialTimeout(t *testing.T) { func TestDialTimeout(t *testing.T) {
defer testutil.AfterTest(t)
donec := make(chan error) donec := make(chan error)
go func() { go func() {
// without timeout, grpc keeps redialing if connection refused // without timeout, grpc keeps redialing if connection refused
@ -55,9 +59,24 @@ func TestDialTimeout(t *testing.T) {
} }
} }
func TestDialNoTimeout(t *testing.T) {
cfg := Config{Endpoints: []string{"127.0.0.1:12345"}}
c, err := New(cfg)
if c == nil || err != nil {
t.Fatalf("new client with DialNoWait should succeed, got %v", err)
}
c.Close()
}
func TestIsHaltErr(t *testing.T) { func TestIsHaltErr(t *testing.T) {
if !isHaltErr(nil, fmt.Errorf("etcdserver: some etcdserver error")) { if !isHaltErr(nil, fmt.Errorf("etcdserver: some etcdserver error")) {
t.Errorf(`error prefixed with "etcdserver: " should be Halted`) t.Errorf(`error prefixed with "etcdserver: " should be Halted by default`)
}
if isHaltErr(nil, etcdserver.ErrStopped) {
t.Errorf("error %v should not halt", etcdserver.ErrStopped)
}
if isHaltErr(nil, etcdserver.ErrNoLeader) {
t.Errorf("error %v should not halt", etcdserver.ErrNoLeader)
} }
ctx, cancel := context.WithCancel(context.TODO()) ctx, cancel := context.WithCancel(context.TODO())
if isHaltErr(ctx, nil) { if isHaltErr(ctx, nil) {

View File

@ -17,6 +17,7 @@ package clientv3
import ( import (
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc"
) )
type ( type (
@ -46,7 +47,7 @@ type cluster struct {
} }
func NewCluster(c *Client) Cluster { func NewCluster(c *Client) Cluster {
return &cluster{remote: pb.NewClusterClient(c.conn)} return &cluster{remote: RetryClusterClient(c)}
} }
func (c *cluster) MemberAdd(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error) { func (c *cluster) MemberAdd(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error) {
@ -90,7 +91,7 @@ func (c *cluster) MemberUpdate(ctx context.Context, id uint64, peerAddrs []strin
func (c *cluster) MemberList(ctx context.Context) (*MemberListResponse, error) { func (c *cluster) MemberList(ctx context.Context) (*MemberListResponse, error) {
// it is safe to retry on list. // it is safe to retry on list.
for { for {
resp, err := c.remote.MemberList(ctx, &pb.MemberListRequest{}) resp, err := c.remote.MemberList(ctx, &pb.MemberListRequest{}, grpc.FailFast(false))
if err == nil { if err == nil {
return (*MemberListResponse)(resp), nil return (*MemberListResponse)(resp), nil
} }

View File

@ -40,7 +40,7 @@ type Election struct {
// NewElection returns a new election on a given key prefix. // NewElection returns a new election on a given key prefix.
func NewElection(client *v3.Client, pfx string) *Election { func NewElection(client *v3.Client, pfx string) *Election {
return &Election{client: client, keyPrefix: pfx} return &Election{client: client, keyPrefix: pfx + "/"}
} }
// Campaign puts a value as eligible for the election. It blocks until // Campaign puts a value as eligible for the election. It blocks until
@ -59,7 +59,6 @@ func (e *Election) Campaign(ctx context.Context, val string) error {
if err != nil { if err != nil {
return err return err
} }
e.leaderKey, e.leaderRev, e.leaderSession = k, resp.Header.Revision, s e.leaderKey, e.leaderRev, e.leaderSession = k, resp.Header.Revision, s
if !resp.Succeeded { if !resp.Succeeded {
kv := resp.Responses[0].GetResponseRange().Kvs[0] kv := resp.Responses[0].GetResponseRange().Kvs[0]

View File

@ -32,7 +32,7 @@ type Mutex struct {
} }
func NewMutex(client *v3.Client, pfx string) *Mutex { func NewMutex(client *v3.Client, pfx string) *Mutex {
return &Mutex{client, pfx, "", -1} return &Mutex{client, pfx + "/", "", -1}
} }
// Lock locks the mutex with a cancellable context. If the context is cancelled // Lock locks the mutex with a cancellable context. If the context is cancelled
@ -43,7 +43,7 @@ func (m *Mutex) Lock(ctx context.Context) error {
return serr return serr
} }
m.myKey = fmt.Sprintf("%s/%x", m.pfx, s.Lease()) m.myKey = fmt.Sprintf("%s%x", m.pfx, s.Lease())
cmp := v3.Compare(v3.CreateRevision(m.myKey), "=", 0) cmp := v3.Compare(v3.CreateRevision(m.myKey), "=", 0)
// put self in lock waiters via myKey; oldest waiter holds lock // put self in lock waiters via myKey; oldest waiter holds lock
put := v3.OpPut(m.myKey, "", v3.WithLease(s.Lease())) put := v3.OpPut(m.myKey, "", v3.WithLease(s.Lease()))

View File

@ -32,35 +32,63 @@ func ExampleAuth() {
} }
defer cli.Close() defer cli.Close()
authapi := clientv3.NewAuth(cli) if _, err = cli.RoleAdd(context.TODO(), "root"); err != nil {
log.Fatal(err)
if _, err = authapi.RoleAdd(context.TODO(), "root"); err != nil { }
if _, err = cli.UserAdd(context.TODO(), "root", "123"); err != nil {
log.Fatal(err)
}
if _, err = cli.UserGrantRole(context.TODO(), "root", "root"); err != nil {
log.Fatal(err) log.Fatal(err)
} }
if _, err = authapi.RoleGrantPermission( if _, err = cli.RoleAdd(context.TODO(), "r"); err != nil {
log.Fatal(err)
}
if _, err = cli.RoleGrantPermission(
context.TODO(), context.TODO(),
"root", // role name "r", // role name
"foo", // key "foo", // key
"zoo", // range end "zoo", // range end
clientv3.PermissionType(clientv3.PermReadWrite), clientv3.PermissionType(clientv3.PermReadWrite),
); err != nil { ); err != nil {
log.Fatal(err) log.Fatal(err)
} }
if _, err = cli.UserAdd(context.TODO(), "u", "123"); err != nil {
if _, err = authapi.UserAdd(context.TODO(), "root", "123"); err != nil {
log.Fatal(err) log.Fatal(err)
} }
if _, err = cli.UserGrantRole(context.TODO(), "u", "r"); err != nil {
if _, err = authapi.UserGrantRole(context.TODO(), "root", "root"); err != nil {
log.Fatal(err) log.Fatal(err)
} }
if _, err = cli.AuthEnable(context.TODO()); err != nil {
if _, err = authapi.AuthEnable(context.TODO()); err != nil {
log.Fatal(err) log.Fatal(err)
} }
cliAuth, err := clientv3.New(clientv3.Config{ cliAuth, err := clientv3.New(clientv3.Config{
Endpoints: endpoints,
DialTimeout: dialTimeout,
Username: "u",
Password: "123",
})
if err != nil {
log.Fatal(err)
}
defer cliAuth.Close()
if _, err = cliAuth.Put(context.TODO(), "foo1", "bar"); err != nil {
log.Fatal(err)
}
_, err = cliAuth.Txn(context.TODO()).
If(clientv3.Compare(clientv3.Value("zoo1"), ">", "abc")).
Then(clientv3.OpPut("zoo1", "XYZ")).
Else(clientv3.OpPut("zoo1", "ABC")).
Commit()
fmt.Println(err)
// now check the permission with the root account
rootCli, err := clientv3.New(clientv3.Config{
Endpoints: endpoints, Endpoints: endpoints,
DialTimeout: dialTimeout, DialTimeout: dialTimeout,
Username: "root", Username: "root",
@ -69,31 +97,17 @@ func ExampleAuth() {
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
defer cliAuth.Close() defer rootCli.Close()
kv := clientv3.NewKV(cliAuth) resp, err := rootCli.RoleGet(context.TODO(), "r")
if _, err = kv.Put(context.TODO(), "foo1", "bar"); err != nil {
log.Fatal(err)
}
_, err = kv.Txn(context.TODO()).
If(clientv3.Compare(clientv3.Value("zoo1"), ">", "abc")).
Then(clientv3.OpPut("zoo1", "XYZ")).
Else(clientv3.OpPut("zoo1", "ABC")).
Commit()
fmt.Println(err)
// now check the permission
authapi2 := clientv3.NewAuth(cliAuth)
resp, err := authapi2.RoleGet(context.TODO(), "root")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
fmt.Printf("root user permission: key %q, range end %q\n", resp.Perm[0].Key, resp.Perm[0].RangeEnd) fmt.Printf("user u permission: key %q, range end %q\n", resp.Perm[0].Key, resp.Perm[0].RangeEnd)
if _, err = authapi2.AuthDisable(context.TODO()); err != nil { if _, err = rootCli.AuthDisable(context.TODO()); err != nil {
log.Fatal(err) log.Fatal(err)
} }
// Output: etcdserver: permission denied // Output: etcdserver: permission denied
// root user permission: key "foo", range end "zoo" // user u permission: key "foo", range end "zoo"
} }

View File

@ -210,7 +210,7 @@ func ExampleKV_compact() {
compRev := resp.Header.Revision // specify compact revision of your choice compRev := resp.Header.Revision // specify compact revision of your choice
ctx, cancel = context.WithTimeout(context.Background(), requestTimeout) ctx, cancel = context.WithTimeout(context.Background(), requestTimeout)
err = cli.Compact(ctx, compRev) _, err = cli.Compact(ctx, compRev)
cancel() cancel()
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)

View File

@ -16,6 +16,7 @@ package integration
import ( import (
"bytes" "bytes"
"math/rand"
"reflect" "reflect"
"strings" "strings"
"testing" "testing"
@ -470,17 +471,17 @@ func TestKVCompactError(t *testing.T) {
t.Fatalf("couldn't put 'foo' (%v)", err) t.Fatalf("couldn't put 'foo' (%v)", err)
} }
} }
err := kv.Compact(ctx, 6) _, err := kv.Compact(ctx, 6)
if err != nil { if err != nil {
t.Fatalf("couldn't compact 6 (%v)", err) t.Fatalf("couldn't compact 6 (%v)", err)
} }
err = kv.Compact(ctx, 6) _, err = kv.Compact(ctx, 6)
if err != rpctypes.ErrCompacted { if err != rpctypes.ErrCompacted {
t.Fatalf("expected %v, got %v", rpctypes.ErrCompacted, err) t.Fatalf("expected %v, got %v", rpctypes.ErrCompacted, err)
} }
err = kv.Compact(ctx, 100) _, err = kv.Compact(ctx, 100)
if err != rpctypes.ErrFutureRev { if err != rpctypes.ErrFutureRev {
t.Fatalf("expected %v, got %v", rpctypes.ErrFutureRev, err) t.Fatalf("expected %v, got %v", rpctypes.ErrFutureRev, err)
} }
@ -501,11 +502,11 @@ func TestKVCompact(t *testing.T) {
} }
} }
err := kv.Compact(ctx, 7) _, err := kv.Compact(ctx, 7)
if err != nil { if err != nil {
t.Fatalf("couldn't compact kv space (%v)", err) t.Fatalf("couldn't compact kv space (%v)", err)
} }
err = kv.Compact(ctx, 7) _, err = kv.Compact(ctx, 7)
if err == nil || err != rpctypes.ErrCompacted { if err == nil || err != rpctypes.ErrCompacted {
t.Fatalf("error got %v, want %v", err, rpctypes.ErrCompacted) t.Fatalf("error got %v, want %v", err, rpctypes.ErrCompacted)
} }
@ -525,7 +526,7 @@ func TestKVCompact(t *testing.T) {
t.Fatalf("wchan got %v, expected closed", wr) t.Fatalf("wchan got %v, expected closed", wr)
} }
err = kv.Compact(ctx, 1000) _, err = kv.Compact(ctx, 1000)
if err == nil || err != rpctypes.ErrFutureRev { if err == nil || err != rpctypes.ErrFutureRev {
t.Fatalf("error got %v, want %v", err, rpctypes.ErrFutureRev) t.Fatalf("error got %v, want %v", err, rpctypes.ErrFutureRev)
} }
@ -662,3 +663,75 @@ func TestKVPutStoppedServerAndClose(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
} }
// TestKVGetOneEndpointDown ensures a client can connect and get if one endpoint is down
func TestKVPutOneEndpointDown(t *testing.T) {
defer testutil.AfterTest(t)
clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3})
defer clus.Terminate(t)
// get endpoint list
eps := make([]string, 3)
for i := range eps {
eps[i] = clus.Members[i].GRPCAddr()
}
// make a dead node
clus.Members[rand.Intn(len(eps))].Stop(t)
// try to connect with dead node in the endpoint list
cfg := clientv3.Config{Endpoints: eps, DialTimeout: 1 * time.Second}
cli, err := clientv3.New(cfg)
if err != nil {
t.Fatal(err)
}
defer cli.Close()
ctx, cancel := context.WithTimeout(context.TODO(), 3*time.Second)
if _, err := cli.Get(ctx, "abc", clientv3.WithSerializable()); err != nil {
t.Fatal(err)
}
cancel()
}
// TestKVGetResetLoneEndpoint ensures that if an endpoint resets and all other
// endpoints are down, then it will reconnect.
func TestKVGetResetLoneEndpoint(t *testing.T) {
defer testutil.AfterTest(t)
clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 2})
defer clus.Terminate(t)
// get endpoint list
eps := make([]string, 2)
for i := range eps {
eps[i] = clus.Members[i].GRPCAddr()
}
cfg := clientv3.Config{Endpoints: eps, DialTimeout: 500 * time.Millisecond}
cli, err := clientv3.New(cfg)
if err != nil {
t.Fatal(err)
}
defer cli.Close()
// disconnect everything
clus.Members[0].Stop(t)
clus.Members[1].Stop(t)
// have Get try to reconnect
donec := make(chan struct{})
go func() {
ctx, cancel := context.WithTimeout(context.TODO(), 5*time.Second)
if _, err := cli.Get(ctx, "abc", clientv3.WithSerializable()); err != nil {
t.Fatal(err)
}
cancel()
close(donec)
}()
time.Sleep(500 * time.Millisecond)
clus.Members[0].Restart(t)
select {
case <-time.After(10 * time.Second):
t.Fatalf("timed out waiting for Get")
case <-donec:
}
}

View File

@ -359,7 +359,8 @@ func TestLeaseKeepAliveCloseAfterDisconnectRevoke(t *testing.T) {
if kerr != nil { if kerr != nil {
t.Fatal(kerr) t.Fatal(kerr)
} }
if kresp := <-rc; kresp.ID != resp.ID { kresp := <-rc
if kresp.ID != resp.ID {
t.Fatalf("ID = %x, want %x", kresp.ID, resp.ID) t.Fatalf("ID = %x, want %x", kresp.ID, resp.ID)
} }
@ -374,13 +375,14 @@ func TestLeaseKeepAliveCloseAfterDisconnectRevoke(t *testing.T) {
clus.Members[0].Restart(t) clus.Members[0].Restart(t)
select { // some keep-alives may still be buffered; drain until close
case ka, ok := <-rc: timer := time.After(time.Duration(kresp.TTL) * time.Second)
if ok { for kresp != nil {
t.Fatalf("unexpected keepalive %v", ka) select {
case kresp = <-rc:
case <-timer:
t.Fatalf("keepalive channel did not close")
} }
case <-time.After(5 * time.Second):
t.Fatalf("keepalive channel did not close")
} }
} }
@ -453,3 +455,46 @@ func TestLeaseKeepAliveTTLTimeout(t *testing.T) {
clus.Members[0].Restart(t) clus.Members[0].Restart(t)
} }
// TestLeaseRenewLostQuorum ensures keepalives work after losing quorum
// for a while.
func TestLeaseRenewLostQuorum(t *testing.T) {
defer testutil.AfterTest(t)
clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3})
defer clus.Terminate(t)
cli := clus.Client(0)
r, err := cli.Grant(context.TODO(), 4)
if err != nil {
t.Fatal(err)
}
kctx, kcancel := context.WithCancel(context.Background())
defer kcancel()
ka, err := cli.KeepAlive(kctx, r.ID)
if err != nil {
t.Fatal(err)
}
// consume first keepalive so next message sends when cluster is down
<-ka
// force keepalive stream message to timeout
clus.Members[1].Stop(t)
clus.Members[2].Stop(t)
// Use TTL-1 since the client closes the keepalive channel if no
// keepalive arrives before the lease deadline.
// The cluster has 1 second to recover and reply to the keepalive.
time.Sleep(time.Duration(r.TTL-1) * time.Second)
clus.Members[1].Restart(t)
clus.Members[2].Restart(t)
select {
case _, ok := <-ka:
if !ok {
t.Fatalf("keepalive closed")
}
case <-time.After(time.Duration(r.TTL) * time.Second):
t.Fatalf("timed out waiting for keepalive")
}
}

View File

@ -15,7 +15,9 @@
package integration package integration
import ( import (
"fmt"
"reflect" "reflect"
"sync"
"testing" "testing"
"time" "time"
@ -69,3 +71,55 @@ func TestMirrorSync(t *testing.T) {
t.Fatal("failed to receive update in one second") t.Fatal("failed to receive update in one second")
} }
} }
func TestMirrorSyncBase(t *testing.T) {
cluster := integration.NewClusterV3(nil, &integration.ClusterConfig{Size: 1})
defer cluster.Terminate(nil)
cli := cluster.Client(0)
ctx := context.TODO()
keyCh := make(chan string)
var wg sync.WaitGroup
for i := 0; i < 50; i++ {
wg.Add(1)
go func() {
defer wg.Done()
for key := range keyCh {
if _, err := cli.Put(ctx, key, "test"); err != nil {
t.Fatal(err)
}
}
}()
}
for i := 0; i < 2000; i++ {
keyCh <- fmt.Sprintf("test%d", i)
}
close(keyCh)
wg.Wait()
syncer := mirror.NewSyncer(cli, "test", 0)
respCh, errCh := syncer.SyncBase(ctx)
count := 0
for resp := range respCh {
count = count + len(resp.Kvs)
if !resp.More {
break
}
}
for err := range errCh {
t.Fatalf("unexpected error %v", err)
}
if count != 2000 {
t.Errorf("unexpected kv count: %d", count)
}
}

View File

@ -375,7 +375,7 @@ func TestWatchResumeCompacted(t *testing.T) {
t.Fatal(err) t.Fatal(err)
} }
} }
if err := kv.Compact(context.TODO(), 3); err != nil { if _, err := kv.Compact(context.TODO(), 3); err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -400,7 +400,7 @@ func TestWatchResumeCompacted(t *testing.T) {
func TestWatchCompactRevision(t *testing.T) { func TestWatchCompactRevision(t *testing.T) {
defer testutil.AfterTest(t) defer testutil.AfterTest(t)
clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3}) clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1})
defer clus.Terminate(t) defer clus.Terminate(t)
// set some keys // set some keys
@ -414,7 +414,7 @@ func TestWatchCompactRevision(t *testing.T) {
w := clientv3.NewWatcher(clus.RandClient()) w := clientv3.NewWatcher(clus.RandClient())
defer w.Close() defer w.Close()
if err := kv.Compact(context.TODO(), 4); err != nil { if _, err := kv.Compact(context.TODO(), 4); err != nil {
t.Fatal(err) t.Fatal(err)
} }
wch := w.Watch(context.Background(), "foo", clientv3.WithRev(2)) wch := w.Watch(context.Background(), "foo", clientv3.WithRev(2))
@ -487,7 +487,7 @@ func testWatchWithProgressNotify(t *testing.T, watchOnPut bool) {
} else if len(resp.Events) != 0 { // wait for notification otherwise } else if len(resp.Events) != 0 { // wait for notification otherwise
t.Fatalf("expected no events, but got %+v", resp.Events) t.Fatalf("expected no events, but got %+v", resp.Events)
} }
case <-time.After(2 * pi): case <-time.After(time.Duration(1.5 * float64(pi))):
t.Fatalf("watch response expected in %v, but timed out", pi) t.Fatalf("watch response expected in %v, but timed out", pi)
} }
} }
@ -673,3 +673,131 @@ func TestWatchWithRequireLeader(t *testing.T) {
t.Fatalf("expected response, got closed channel") t.Fatalf("expected response, got closed channel")
} }
} }
// TestWatchOverlapContextCancel stresses the watcher stream teardown path by
// creating/canceling watchers to ensure that new watchers are not taken down
// by a torn down watch stream. The sort of race that's being detected:
// 1. create w1 using a cancelable ctx with %v as "ctx"
// 2. cancel ctx
// 3. watcher client begins tearing down watcher grpc stream since no more watchers
// 3. start creating watcher w2 using a new "ctx" (not canceled), attaches to old grpc stream
// 4. watcher client finishes tearing down stream on "ctx"
// 5. w2 comes back canceled
func TestWatchOverlapContextCancel(t *testing.T) {
f := func(clus *integration.ClusterV3) {}
testWatchOverlapContextCancel(t, f)
}
func TestWatchOverlapDropConnContextCancel(t *testing.T) {
f := func(clus *integration.ClusterV3) {
clus.Members[0].DropConnections()
}
testWatchOverlapContextCancel(t, f)
}
func testWatchOverlapContextCancel(t *testing.T, f func(*integration.ClusterV3)) {
defer testutil.AfterTest(t)
clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1})
defer clus.Terminate(t)
// each unique context "%v" has a unique grpc stream
n := 100
ctxs, ctxc := make([]context.Context, 5), make([]chan struct{}, 5)
for i := range ctxs {
// make "%v" unique
ctxs[i] = context.WithValue(context.TODO(), "key", i)
// limits the maximum number of outstanding watchers per stream
ctxc[i] = make(chan struct{}, 2)
}
// issue concurrent watches on "abc" with cancel
cli := clus.RandClient()
if _, err := cli.Put(context.TODO(), "abc", "def"); err != nil {
t.Fatal(err)
}
ch := make(chan struct{}, n)
for i := 0; i < n; i++ {
go func() {
defer func() { ch <- struct{}{} }()
idx := rand.Intn(len(ctxs))
ctx, cancel := context.WithCancel(ctxs[idx])
ctxc[idx] <- struct{}{}
wch := cli.Watch(ctx, "abc", clientv3.WithRev(1))
f(clus)
select {
case _, ok := <-wch:
if !ok {
t.Fatalf("unexpected closed channel %p", wch)
}
// may take a second or two to reestablish a watcher because of
// grpc backoff policies for disconnects
case <-time.After(5 * time.Second):
t.Errorf("timed out waiting for watch on %p", wch)
}
// randomize how cancel overlaps with watch creation
if rand.Intn(2) == 0 {
<-ctxc[idx]
cancel()
} else {
cancel()
<-ctxc[idx]
}
}()
}
// join on watches
for i := 0; i < n; i++ {
select {
case <-ch:
case <-time.After(5 * time.Second):
t.Fatalf("timed out waiting for completed watch")
}
}
}
// TestWatchCanelAndCloseClient ensures that canceling a watcher then immediately
// closing the client does not return a client closing error.
func TestWatchCancelAndCloseClient(t *testing.T) {
defer testutil.AfterTest(t)
clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1})
defer clus.Terminate(t)
cli := clus.Client(0)
ctx, cancel := context.WithCancel(context.Background())
wch := cli.Watch(ctx, "abc")
donec := make(chan struct{})
go func() {
defer close(donec)
select {
case wr, ok := <-wch:
if ok {
t.Fatalf("expected closed watch after cancel(), got resp=%+v err=%v", wr, wr.Err())
}
case <-time.After(5 * time.Second):
t.Fatal("timed out waiting for closed channel")
}
}()
cancel()
if err := cli.Close(); err != nil {
t.Fatal(err)
}
<-donec
clus.TakeClient(0)
}
// TestWatchCancelDisconnected ensures canceling a watcher works when
// its grpc stream is disconnected / reconnecting.
func TestWatchCancelDisconnected(t *testing.T) {
defer testutil.AfterTest(t)
clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 1})
defer clus.Terminate(t)
cli := clus.Client(0)
ctx, cancel := context.WithCancel(context.Background())
// add more watches than can be resumed before the cancel
wch := cli.Watch(ctx, "abc")
clus.Members[0].Stop(t)
cancel()
select {
case <-wch:
case <-time.After(time.Second):
t.Fatal("took too long to cancel disconnected watcher")
}
}

View File

@ -17,13 +17,15 @@ package clientv3
import ( import (
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc"
) )
type ( type (
PutResponse pb.PutResponse CompactResponse pb.CompactionResponse
GetResponse pb.RangeResponse PutResponse pb.PutResponse
DeleteResponse pb.DeleteRangeResponse GetResponse pb.RangeResponse
TxnResponse pb.TxnResponse DeleteResponse pb.DeleteRangeResponse
TxnResponse pb.TxnResponse
) )
type KV interface { type KV interface {
@ -47,7 +49,7 @@ type KV interface {
Delete(ctx context.Context, key string, opts ...OpOption) (*DeleteResponse, error) Delete(ctx context.Context, key string, opts ...OpOption) (*DeleteResponse, error)
// Compact compacts etcd KV history before the given rev. // Compact compacts etcd KV history before the given rev.
Compact(ctx context.Context, rev int64, opts ...CompactOption) error Compact(ctx context.Context, rev int64, opts ...CompactOption) (*CompactResponse, error)
// Do applies a single Op on KV without a transaction. // Do applies a single Op on KV without a transaction.
// Do is useful when declaring operations to be issued at a later time // Do is useful when declaring operations to be issued at a later time
@ -80,7 +82,7 @@ type kv struct {
} }
func NewKV(c *Client) KV { func NewKV(c *Client) KV {
return &kv{remote: pb.NewKVClient(c.conn)} return &kv{remote: RetryKVClient(c)}
} }
func (kv *kv) Put(ctx context.Context, key, val string, opts ...OpOption) (*PutResponse, error) { func (kv *kv) Put(ctx context.Context, key, val string, opts ...OpOption) (*PutResponse, error) {
@ -98,11 +100,12 @@ func (kv *kv) Delete(ctx context.Context, key string, opts ...OpOption) (*Delete
return r.del, toErr(ctx, err) return r.del, toErr(ctx, err)
} }
func (kv *kv) Compact(ctx context.Context, rev int64, opts ...CompactOption) error { func (kv *kv) Compact(ctx context.Context, rev int64, opts ...CompactOption) (*CompactResponse, error) {
if _, err := kv.remote.Compact(ctx, OpCompact(rev, opts...).toRequest()); err != nil { resp, err := kv.remote.Compact(ctx, OpCompact(rev, opts...).toRequest(), grpc.FailFast(false))
return toErr(ctx, err) if err != nil {
return nil, toErr(ctx, err)
} }
return nil return (*CompactResponse)(resp), err
} }
func (kv *kv) Txn(ctx context.Context) Txn { func (kv *kv) Txn(ctx context.Context) Txn {
@ -148,20 +151,20 @@ func (kv *kv) do(ctx context.Context, op Op) (OpResponse, error) {
r.SortTarget = pb.RangeRequest_SortTarget(op.sort.Target) r.SortTarget = pb.RangeRequest_SortTarget(op.sort.Target)
} }
resp, err = kv.remote.Range(ctx, r) resp, err = kv.remote.Range(ctx, r, grpc.FailFast(false))
if err == nil { if err == nil {
return OpResponse{get: (*GetResponse)(resp)}, nil return OpResponse{get: (*GetResponse)(resp)}, nil
} }
case tPut: case tPut:
var resp *pb.PutResponse var resp *pb.PutResponse
r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID)} r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID), PrevKv: op.prevKV}
resp, err = kv.remote.Put(ctx, r) resp, err = kv.remote.Put(ctx, r)
if err == nil { if err == nil {
return OpResponse{put: (*PutResponse)(resp)}, nil return OpResponse{put: (*PutResponse)(resp)}, nil
} }
case tDeleteRange: case tDeleteRange:
var resp *pb.DeleteRangeResponse var resp *pb.DeleteRangeResponse
r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end} r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end, PrevKv: op.prevKV}
resp, err = kv.remote.DeleteRange(ctx, r) resp, err = kv.remote.DeleteRange(ctx, r)
if err == nil { if err == nil {
return OpResponse{del: (*DeleteResponse)(resp)}, nil return OpResponse{del: (*DeleteResponse)(resp)}, nil

View File

@ -21,6 +21,7 @@ import (
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc"
) )
type ( type (
@ -109,7 +110,7 @@ func NewLease(c *Client) Lease {
l := &lessor{ l := &lessor{
donec: make(chan struct{}), donec: make(chan struct{}),
keepAlives: make(map[LeaseID]*keepAlive), keepAlives: make(map[LeaseID]*keepAlive),
remote: pb.NewLeaseClient(c.conn), remote: RetryLeaseClient(c),
firstKeepAliveTimeout: c.cfg.DialTimeout + time.Second, firstKeepAliveTimeout: c.cfg.DialTimeout + time.Second,
} }
if l.firstKeepAliveTimeout == time.Second { if l.firstKeepAliveTimeout == time.Second {
@ -142,9 +143,6 @@ func (l *lessor) Grant(ctx context.Context, ttl int64) (*LeaseGrantResponse, err
if isHaltErr(cctx, err) { if isHaltErr(cctx, err) {
return nil, toErr(ctx, err) return nil, toErr(ctx, err)
} }
if nerr := l.newStream(); nerr != nil {
return nil, nerr
}
} }
} }
@ -163,9 +161,6 @@ func (l *lessor) Revoke(ctx context.Context, id LeaseID) (*LeaseRevokeResponse,
if isHaltErr(ctx, err) { if isHaltErr(ctx, err) {
return nil, toErr(ctx, err) return nil, toErr(ctx, err)
} }
if nerr := l.newStream(); nerr != nil {
return nil, nerr
}
} }
} }
@ -212,10 +207,6 @@ func (l *lessor) KeepAliveOnce(ctx context.Context, id LeaseID) (*LeaseKeepAlive
if isHaltErr(ctx, err) { if isHaltErr(ctx, err) {
return nil, toErr(ctx, err) return nil, toErr(ctx, err)
} }
if nerr := l.newStream(); nerr != nil {
return nil, nerr
}
} }
} }
@ -261,7 +252,7 @@ func (l *lessor) keepAliveOnce(ctx context.Context, id LeaseID) (*LeaseKeepAlive
cctx, cancel := context.WithCancel(ctx) cctx, cancel := context.WithCancel(ctx)
defer cancel() defer cancel()
stream, err := l.remote.LeaseKeepAlive(cctx) stream, err := l.remote.LeaseKeepAlive(cctx, grpc.FailFast(false))
if err != nil { if err != nil {
return nil, toErr(ctx, err) return nil, toErr(ctx, err)
} }
@ -311,10 +302,23 @@ func (l *lessor) recvKeepAliveLoop() {
// resetRecv opens a new lease stream and starts sending LeaseKeepAliveRequests // resetRecv opens a new lease stream and starts sending LeaseKeepAliveRequests
func (l *lessor) resetRecv() (pb.Lease_LeaseKeepAliveClient, error) { func (l *lessor) resetRecv() (pb.Lease_LeaseKeepAliveClient, error) {
if err := l.newStream(); err != nil { sctx, cancel := context.WithCancel(l.stopCtx)
stream, err := l.remote.LeaseKeepAlive(sctx, grpc.FailFast(false))
if err = toErr(sctx, err); err != nil {
cancel()
return nil, err return nil, err
} }
stream := l.getKeepAliveStream()
l.mu.Lock()
defer l.mu.Unlock()
if l.stream != nil && l.streamCancel != nil {
l.stream.CloseSend()
l.streamCancel()
}
l.streamCancel = cancel
l.stream = stream
go l.sendKeepAliveLoop(stream) go l.sendKeepAliveLoop(stream)
return stream, nil return stream, nil
} }
@ -410,32 +414,6 @@ func (l *lessor) sendKeepAliveLoop(stream pb.Lease_LeaseKeepAliveClient) {
} }
} }
func (l *lessor) getKeepAliveStream() pb.Lease_LeaseKeepAliveClient {
l.mu.Lock()
defer l.mu.Unlock()
return l.stream
}
func (l *lessor) newStream() error {
sctx, cancel := context.WithCancel(l.stopCtx)
stream, err := l.remote.LeaseKeepAlive(sctx)
if err != nil {
cancel()
return toErr(sctx, err)
}
l.mu.Lock()
defer l.mu.Unlock()
if l.stream != nil && l.streamCancel != nil {
l.stream.CloseSend()
l.streamCancel()
}
l.streamCancel = cancel
l.stream = stream
return nil
}
func (ka *keepAlive) Close() { func (ka *keepAlive) Close() {
close(ka.donec) close(ka.donec)
for _, ch := range ka.chs { for _, ch := range ka.chs {

View File

@ -19,6 +19,7 @@ import (
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc"
) )
type ( type (
@ -67,7 +68,7 @@ func (m *maintenance) AlarmList(ctx context.Context) (*AlarmResponse, error) {
Alarm: pb.AlarmType_NONE, // all Alarm: pb.AlarmType_NONE, // all
} }
for { for {
resp, err := m.remote.Alarm(ctx, req) resp, err := m.remote.Alarm(ctx, req, grpc.FailFast(false))
if err == nil { if err == nil {
return (*AlarmResponse)(resp), nil return (*AlarmResponse)(resp), nil
} }
@ -100,7 +101,7 @@ func (m *maintenance) AlarmDisarm(ctx context.Context, am *AlarmMember) (*AlarmR
return &ret, nil return &ret, nil
} }
resp, err := m.remote.Alarm(ctx, req) resp, err := m.remote.Alarm(ctx, req, grpc.FailFast(false))
if err == nil { if err == nil {
return (*AlarmResponse)(resp), nil return (*AlarmResponse)(resp), nil
} }
@ -114,7 +115,7 @@ func (m *maintenance) Defragment(ctx context.Context, endpoint string) (*Defragm
} }
defer conn.Close() defer conn.Close()
remote := pb.NewMaintenanceClient(conn) remote := pb.NewMaintenanceClient(conn)
resp, err := remote.Defragment(ctx, &pb.DefragmentRequest{}) resp, err := remote.Defragment(ctx, &pb.DefragmentRequest{}, grpc.FailFast(false))
if err != nil { if err != nil {
return nil, toErr(ctx, err) return nil, toErr(ctx, err)
} }
@ -128,7 +129,7 @@ func (m *maintenance) Status(ctx context.Context, endpoint string) (*StatusRespo
} }
defer conn.Close() defer conn.Close()
remote := pb.NewMaintenanceClient(conn) remote := pb.NewMaintenanceClient(conn)
resp, err := remote.Status(ctx, &pb.StatusRequest{}) resp, err := remote.Status(ctx, &pb.StatusRequest{}, grpc.FailFast(false))
if err != nil { if err != nil {
return nil, toErr(ctx, err) return nil, toErr(ctx, err)
} }
@ -136,7 +137,7 @@ func (m *maintenance) Status(ctx context.Context, endpoint string) (*StatusRespo
} }
func (m *maintenance) Snapshot(ctx context.Context) (io.ReadCloser, error) { func (m *maintenance) Snapshot(ctx context.Context) (io.ReadCloser, error) {
ss, err := m.remote.Snapshot(ctx, &pb.SnapshotRequest{}) ss, err := m.remote.Snapshot(ctx, &pb.SnapshotRequest{}, grpc.FailFast(false))
if err != nil { if err != nil {
return nil, toErr(ctx, err) return nil, toErr(ctx, err)
} }

View File

@ -78,7 +78,7 @@ func (s *syncer) SyncBase(ctx context.Context) (<-chan clientv3.GetResponse, cha
// If len(s.prefix) != 0, we will sync key-value space with given prefix. // If len(s.prefix) != 0, we will sync key-value space with given prefix.
// We then range from the prefix to the next prefix if exists. Or we will // We then range from the prefix to the next prefix if exists. Or we will
// range from the prefix to the end if the next prefix does not exists. // range from the prefix to the end if the next prefix does not exists.
opts = append(opts, clientv3.WithPrefix()) opts = append(opts, clientv3.WithRange(clientv3.GetPrefixRangeEnd(s.prefix)))
key = s.prefix key = s.prefix
} }

View File

@ -47,6 +47,9 @@ type Op struct {
// for range, watch // for range, watch
rev int64 rev int64
// for watch, put, delete
prevKV bool
// progressNotify is for progress updates. // progressNotify is for progress updates.
progressNotify bool progressNotify bool
@ -73,10 +76,10 @@ func (op Op) toRequestOp() *pb.RequestOp {
} }
return &pb.RequestOp{Request: &pb.RequestOp_RequestRange{RequestRange: r}} return &pb.RequestOp{Request: &pb.RequestOp_RequestRange{RequestRange: r}}
case tPut: case tPut:
r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID)} r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID), PrevKv: op.prevKV}
return &pb.RequestOp{Request: &pb.RequestOp_RequestPut{RequestPut: r}} return &pb.RequestOp{Request: &pb.RequestOp_RequestPut{RequestPut: r}}
case tDeleteRange: case tDeleteRange:
r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end} r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end, PrevKv: op.prevKV}
return &pb.RequestOp{Request: &pb.RequestOp_RequestDeleteRange{RequestDeleteRange: r}} return &pb.RequestOp{Request: &pb.RequestOp_RequestDeleteRange{RequestDeleteRange: r}}
default: default:
panic("Unknown Op") panic("Unknown Op")
@ -182,6 +185,12 @@ func WithSort(target SortTarget, order SortOrder) OpOption {
} }
} }
// GetPrefixRangeEnd gets the range end of the prefix.
// 'Get(foo, WithPrefix())' is equal to 'Get(foo, WithRange(GetPrefixRangeEnd(foo))'.
func GetPrefixRangeEnd(prefix string) string {
return string(getPrefix([]byte(prefix)))
}
func getPrefix(key []byte) []byte { func getPrefix(key []byte) []byte {
end := make([]byte, len(key)) end := make([]byte, len(key))
copy(end, key) copy(end, key)
@ -206,14 +215,15 @@ func WithPrefix() OpOption {
} }
} }
// WithRange specifies the range of 'Get' or 'Delete' requests. // WithRange specifies the range of 'Get', 'Delete', 'Watch' requests.
// For example, 'Get' requests with 'WithRange(end)' returns // For example, 'Get' requests with 'WithRange(end)' returns
// the keys in the range [key, end). // the keys in the range [key, end).
// endKey must be lexicographically greater than start key.
func WithRange(endKey string) OpOption { func WithRange(endKey string) OpOption {
return func(op *Op) { op.end = []byte(endKey) } return func(op *Op) { op.end = []byte(endKey) }
} }
// WithFromKey specifies the range of 'Get' or 'Delete' requests // WithFromKey specifies the range of 'Get', 'Delete', 'Watch' requests
// to be equal or greater than the key in the argument. // to be equal or greater than the key in the argument.
func WithFromKey() OpOption { return WithRange("\x00") } func WithFromKey() OpOption { return WithRange("\x00") }
@ -265,3 +275,11 @@ func WithProgressNotify() OpOption {
op.progressNotify = true op.progressNotify = true
} }
} }
// WithPrevKV gets the previous key-value pair before the event happens. If the previous KV is already compacted,
// nothing will be returned.
func WithPrevKV() OpOption {
return func(op *Op) {
op.prevKV = true
}
}

243
clientv3/retry.go Normal file
View File

@ -0,0 +1,243 @@
// Copyright 2016 The etcd Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package clientv3
import (
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
type rpcFunc func(ctx context.Context) error
type retryRpcFunc func(context.Context, rpcFunc)
func (c *Client) newRetryWrapper() retryRpcFunc {
return func(rpcCtx context.Context, f rpcFunc) {
for {
err := f(rpcCtx)
// ignore grpc conn closing on fail-fast calls; they are transient errors
if err == nil || !isConnClosing(err) {
return
}
select {
case <-c.balancer.ConnectNotify():
case <-rpcCtx.Done():
case <-c.ctx.Done():
return
}
}
}
}
type retryKVClient struct {
pb.KVClient
retryf retryRpcFunc
}
// RetryKVClient implements a KVClient that uses the client's FailFast retry policy.
func RetryKVClient(c *Client) pb.KVClient {
return &retryKVClient{pb.NewKVClient(c.conn), c.retryWrapper}
}
func (rkv *retryKVClient) Put(ctx context.Context, in *pb.PutRequest, opts ...grpc.CallOption) (resp *pb.PutResponse, err error) {
rkv.retryf(ctx, func(rctx context.Context) error {
resp, err = rkv.KVClient.Put(rctx, in, opts...)
return err
})
return resp, err
}
func (rkv *retryKVClient) DeleteRange(ctx context.Context, in *pb.DeleteRangeRequest, opts ...grpc.CallOption) (resp *pb.DeleteRangeResponse, err error) {
rkv.retryf(ctx, func(rctx context.Context) error {
resp, err = rkv.KVClient.DeleteRange(rctx, in, opts...)
return err
})
return resp, err
}
func (rkv *retryKVClient) Txn(ctx context.Context, in *pb.TxnRequest, opts ...grpc.CallOption) (resp *pb.TxnResponse, err error) {
rkv.retryf(ctx, func(rctx context.Context) error {
resp, err = rkv.KVClient.Txn(rctx, in, opts...)
return err
})
return resp, err
}
func (rkv *retryKVClient) Compact(ctx context.Context, in *pb.CompactionRequest, opts ...grpc.CallOption) (resp *pb.CompactionResponse, err error) {
rkv.retryf(ctx, func(rctx context.Context) error {
resp, err = rkv.KVClient.Compact(rctx, in, opts...)
return err
})
return resp, err
}
type retryLeaseClient struct {
pb.LeaseClient
retryf retryRpcFunc
}
// RetryLeaseClient implements a LeaseClient that uses the client's FailFast retry policy.
func RetryLeaseClient(c *Client) pb.LeaseClient {
return &retryLeaseClient{pb.NewLeaseClient(c.conn), c.retryWrapper}
}
func (rlc *retryLeaseClient) LeaseGrant(ctx context.Context, in *pb.LeaseGrantRequest, opts ...grpc.CallOption) (resp *pb.LeaseGrantResponse, err error) {
rlc.retryf(ctx, func(rctx context.Context) error {
resp, err = rlc.LeaseClient.LeaseGrant(rctx, in, opts...)
return err
})
return resp, err
}
func (rlc *retryLeaseClient) LeaseRevoke(ctx context.Context, in *pb.LeaseRevokeRequest, opts ...grpc.CallOption) (resp *pb.LeaseRevokeResponse, err error) {
rlc.retryf(ctx, func(rctx context.Context) error {
resp, err = rlc.LeaseClient.LeaseRevoke(rctx, in, opts...)
return err
})
return resp, err
}
type retryClusterClient struct {
pb.ClusterClient
retryf retryRpcFunc
}
// RetryClusterClient implements a ClusterClient that uses the client's FailFast retry policy.
func RetryClusterClient(c *Client) pb.ClusterClient {
return &retryClusterClient{pb.NewClusterClient(c.conn), c.retryWrapper}
}
func (rcc *retryClusterClient) MemberAdd(ctx context.Context, in *pb.MemberAddRequest, opts ...grpc.CallOption) (resp *pb.MemberAddResponse, err error) {
rcc.retryf(ctx, func(rctx context.Context) error {
resp, err = rcc.ClusterClient.MemberAdd(rctx, in, opts...)
return err
})
return resp, err
}
func (rcc *retryClusterClient) MemberRemove(ctx context.Context, in *pb.MemberRemoveRequest, opts ...grpc.CallOption) (resp *pb.MemberRemoveResponse, err error) {
rcc.retryf(ctx, func(rctx context.Context) error {
resp, err = rcc.ClusterClient.MemberRemove(rctx, in, opts...)
return err
})
return resp, err
}
func (rcc *retryClusterClient) MemberUpdate(ctx context.Context, in *pb.MemberUpdateRequest, opts ...grpc.CallOption) (resp *pb.MemberUpdateResponse, err error) {
rcc.retryf(ctx, func(rctx context.Context) error {
resp, err = rcc.ClusterClient.MemberUpdate(rctx, in, opts...)
return err
})
return resp, err
}
type retryAuthClient struct {
pb.AuthClient
retryf retryRpcFunc
}
// RetryAuthClient implements a AuthClient that uses the client's FailFast retry policy.
func RetryAuthClient(c *Client) pb.AuthClient {
return &retryAuthClient{pb.NewAuthClient(c.conn), c.retryWrapper}
}
func (rac *retryAuthClient) AuthEnable(ctx context.Context, in *pb.AuthEnableRequest, opts ...grpc.CallOption) (resp *pb.AuthEnableResponse, err error) {
rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.AuthClient.AuthEnable(rctx, in, opts...)
return err
})
return resp, err
}
func (rac *retryAuthClient) AuthDisable(ctx context.Context, in *pb.AuthDisableRequest, opts ...grpc.CallOption) (resp *pb.AuthDisableResponse, err error) {
rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.AuthClient.AuthDisable(rctx, in, opts...)
return err
})
return resp, err
}
func (rac *retryAuthClient) UserAdd(ctx context.Context, in *pb.AuthUserAddRequest, opts ...grpc.CallOption) (resp *pb.AuthUserAddResponse, err error) {
rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.AuthClient.UserAdd(rctx, in, opts...)
return err
})
return resp, err
}
func (rac *retryAuthClient) UserDelete(ctx context.Context, in *pb.AuthUserDeleteRequest, opts ...grpc.CallOption) (resp *pb.AuthUserDeleteResponse, err error) {
rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.AuthClient.UserDelete(rctx, in, opts...)
return err
})
return resp, err
}
func (rac *retryAuthClient) UserChangePassword(ctx context.Context, in *pb.AuthUserChangePasswordRequest, opts ...grpc.CallOption) (resp *pb.AuthUserChangePasswordResponse, err error) {
rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.AuthClient.UserChangePassword(rctx, in, opts...)
return err
})
return resp, err
}
func (rac *retryAuthClient) UserGrantRole(ctx context.Context, in *pb.AuthUserGrantRoleRequest, opts ...grpc.CallOption) (resp *pb.AuthUserGrantRoleResponse, err error) {
rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.AuthClient.UserGrantRole(rctx, in, opts...)
return err
})
return resp, err
}
func (rac *retryAuthClient) UserRevokeRole(ctx context.Context, in *pb.AuthUserRevokeRoleRequest, opts ...grpc.CallOption) (resp *pb.AuthUserRevokeRoleResponse, err error) {
rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.AuthClient.UserRevokeRole(rctx, in, opts...)
return err
})
return resp, err
}
func (rac *retryAuthClient) RoleAdd(ctx context.Context, in *pb.AuthRoleAddRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleAddResponse, err error) {
rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.AuthClient.RoleAdd(rctx, in, opts...)
return err
})
return resp, err
}
func (rac *retryAuthClient) RoleDelete(ctx context.Context, in *pb.AuthRoleDeleteRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleDeleteResponse, err error) {
rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.AuthClient.RoleDelete(rctx, in, opts...)
return err
})
return resp, err
}
func (rac *retryAuthClient) RoleGrantPermission(ctx context.Context, in *pb.AuthRoleGrantPermissionRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleGrantPermissionResponse, err error) {
rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.AuthClient.RoleGrantPermission(rctx, in, opts...)
return err
})
return resp, err
}
func (rac *retryAuthClient) RoleRevokePermission(ctx context.Context, in *pb.AuthRoleRevokePermissionRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleRevokePermissionResponse, err error) {
rac.retryf(ctx, func(rctx context.Context) error {
resp, err = rac.AuthClient.RoleRevokePermission(rctx, in, opts...)
return err
})
return resp, err
}

View File

@ -23,6 +23,7 @@ import (
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
mvccpb "github.com/coreos/etcd/mvcc/mvccpb" mvccpb "github.com/coreos/etcd/mvcc/mvccpb"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc"
) )
const ( const (
@ -60,6 +61,9 @@ type WatchResponse struct {
// the channel sends a final response that has Canceled set to true with a non-nil Err(). // the channel sends a final response that has Canceled set to true with a non-nil Err().
Canceled bool Canceled bool
// created is used to indicate the creation of the watcher.
created bool
closeErr error closeErr error
} }
@ -88,7 +92,7 @@ func (wr *WatchResponse) Err() error {
// IsProgressNotify returns true if the WatchResponse is progress notification. // IsProgressNotify returns true if the WatchResponse is progress notification.
func (wr *WatchResponse) IsProgressNotify() bool { func (wr *WatchResponse) IsProgressNotify() bool {
return len(wr.Events) == 0 && !wr.Canceled return len(wr.Events) == 0 && !wr.Canceled && !wr.created && wr.CompactRevision == 0 && wr.Header.Revision != 0
} }
// watcher implements the Watcher interface // watcher implements the Watcher interface
@ -101,6 +105,7 @@ type watcher struct {
streams map[string]*watchGrpcStream streams map[string]*watchGrpcStream
} }
// watchGrpcStream tracks all watch resources attached to a single grpc stream.
type watchGrpcStream struct { type watchGrpcStream struct {
owner *watcher owner *watcher
remote pb.WatchClient remote pb.WatchClient
@ -111,23 +116,25 @@ type watchGrpcStream struct {
ctxKey string ctxKey string
cancel context.CancelFunc cancel context.CancelFunc
// mu protects the streams map // substreams holds all active watchers on this grpc stream
mu sync.RWMutex substreams map[int64]*watcherStream
// streams holds all active watchers // resuming holds all resuming watchers on this grpc stream
streams map[int64]*watcherStream resuming []*watcherStream
// reqc sends a watch request from Watch() to the main goroutine // reqc sends a watch request from Watch() to the main goroutine
reqc chan *watchRequest reqc chan *watchRequest
// respc receives data from the watch client // respc receives data from the watch client
respc chan *pb.WatchResponse respc chan *pb.WatchResponse
// stopc is sent to the main goroutine to stop all processing
stopc chan struct{}
// donec closes to broadcast shutdown // donec closes to broadcast shutdown
donec chan struct{} donec chan struct{}
// errc transmits errors from grpc Recv to the watch stream reconn logic // errc transmits errors from grpc Recv to the watch stream reconn logic
errc chan error errc chan error
// closingc gets the watcherStream of closing watchers
closingc chan *watcherStream
// the error that closed the watch stream // resumec closes to signal that all substreams should begin resuming
resumec chan struct{}
// closeErr is the error that closed the watch stream
closeErr error closeErr error
} }
@ -139,6 +146,8 @@ type watchRequest struct {
rev int64 rev int64
// progressNotify is for progress updates. // progressNotify is for progress updates.
progressNotify bool progressNotify bool
// get the previous key-value pair before the event happens
prevKV bool
// retc receives a chan WatchResponse once the watcher is established // retc receives a chan WatchResponse once the watcher is established
retc chan chan WatchResponse retc chan chan WatchResponse
} }
@ -149,15 +158,18 @@ type watcherStream struct {
initReq watchRequest initReq watchRequest
// outc publishes watch responses to subscriber // outc publishes watch responses to subscriber
outc chan<- WatchResponse outc chan WatchResponse
// recvc buffers watch responses before publishing // recvc buffers watch responses before publishing
recvc chan *WatchResponse recvc chan *WatchResponse
id int64 // donec closes when the watcherStream goroutine stops.
donec chan struct{}
// closing is set to true when stream should be scheduled to shutdown.
closing bool
// id is the registered watch id on the grpc stream
id int64
// lastRev is revision last successfully sent over outc // buf holds all events received from etcd but not yet consumed by the client
lastRev int64 buf []*WatchResponse
// resumec indicates the stream must recover at a given revision
resumec chan int64
} }
func NewWatcher(c *Client) Watcher { func NewWatcher(c *Client) Watcher {
@ -181,18 +193,19 @@ func (vc *valCtx) Err() error { return nil }
func (w *watcher) newWatcherGrpcStream(inctx context.Context) *watchGrpcStream { func (w *watcher) newWatcherGrpcStream(inctx context.Context) *watchGrpcStream {
ctx, cancel := context.WithCancel(&valCtx{inctx}) ctx, cancel := context.WithCancel(&valCtx{inctx})
wgs := &watchGrpcStream{ wgs := &watchGrpcStream{
owner: w, owner: w,
remote: w.remote, remote: w.remote,
ctx: ctx, ctx: ctx,
ctxKey: fmt.Sprintf("%v", inctx), ctxKey: fmt.Sprintf("%v", inctx),
cancel: cancel, cancel: cancel,
streams: make(map[int64]*watcherStream), substreams: make(map[int64]*watcherStream),
respc: make(chan *pb.WatchResponse), respc: make(chan *pb.WatchResponse),
reqc: make(chan *watchRequest), reqc: make(chan *watchRequest),
stopc: make(chan struct{}), donec: make(chan struct{}),
donec: make(chan struct{}), errc: make(chan error, 1),
errc: make(chan error, 1), closingc: make(chan *watcherStream),
resumec: make(chan struct{}),
} }
go wgs.run() go wgs.run()
return wgs return wgs
@ -202,14 +215,14 @@ func (w *watcher) newWatcherGrpcStream(inctx context.Context) *watchGrpcStream {
func (w *watcher) Watch(ctx context.Context, key string, opts ...OpOption) WatchChan { func (w *watcher) Watch(ctx context.Context, key string, opts ...OpOption) WatchChan {
ow := opWatch(key, opts...) ow := opWatch(key, opts...)
retc := make(chan chan WatchResponse, 1)
wr := &watchRequest{ wr := &watchRequest{
ctx: ctx, ctx: ctx,
key: string(ow.key), key: string(ow.key),
end: string(ow.end), end: string(ow.end),
rev: ow.rev, rev: ow.rev,
progressNotify: ow.progressNotify, progressNotify: ow.progressNotify,
retc: retc, prevKV: ow.prevKV,
retc: make(chan chan WatchResponse, 1),
} }
ok := false ok := false
@ -253,7 +266,7 @@ func (w *watcher) Watch(ctx context.Context, key string, opts ...OpOption) Watch
// receive channel // receive channel
if ok { if ok {
select { select {
case ret := <-retc: case ret := <-wr.retc:
return ret return ret
case <-ctx.Done(): case <-ctx.Done():
case <-donec: case <-donec:
@ -284,7 +297,7 @@ func (w *watcher) Close() (err error) {
} }
func (w *watchGrpcStream) Close() (err error) { func (w *watchGrpcStream) Close() (err error) {
close(w.stopc) w.cancel()
<-w.donec <-w.donec
select { select {
case err = <-w.errc: case err = <-w.errc:
@ -293,65 +306,57 @@ func (w *watchGrpcStream) Close() (err error) {
return toErr(w.ctx, err) return toErr(w.ctx, err)
} }
func (w *watchGrpcStream) addStream(resp *pb.WatchResponse, pendingReq *watchRequest) { func (w *watcher) closeStream(wgs *watchGrpcStream) {
if pendingReq == nil {
// no pending request; ignore
return
}
if resp.Canceled || resp.CompactRevision != 0 {
// a cancel at id creation time means the start revision has
// been compacted out of the store
ret := make(chan WatchResponse, 1)
ret <- WatchResponse{
Header: *resp.Header,
CompactRevision: resp.CompactRevision,
Canceled: true}
close(ret)
pendingReq.retc <- ret
return
}
ret := make(chan WatchResponse)
if resp.WatchId == -1 {
// failed; no channel
close(ret)
pendingReq.retc <- ret
return
}
ws := &watcherStream{
initReq: *pendingReq,
id: resp.WatchId,
outc: ret,
// buffered so unlikely to block on sending while holding mu
recvc: make(chan *WatchResponse, 4),
resumec: make(chan int64),
}
if pendingReq.rev == 0 {
// note the header revision so that a put following a current watcher
// disconnect will arrive on the watcher channel after reconnect
ws.initReq.rev = resp.Header.Revision
}
w.mu.Lock() w.mu.Lock()
w.streams[ws.id] = ws close(wgs.donec)
wgs.cancel()
if w.streams != nil {
delete(w.streams, wgs.ctxKey)
}
w.mu.Unlock() w.mu.Unlock()
// pass back the subscriber channel for the watcher
pendingReq.retc <- ret
// send messages to subscriber
go w.serveStream(ws)
} }
// closeStream closes the watcher resources and removes it func (w *watchGrpcStream) addSubstream(resp *pb.WatchResponse, ws *watcherStream) {
func (w *watchGrpcStream) closeStream(ws *watcherStream) { if resp.WatchId == -1 {
// cancels request stream; subscriber receives nil channel // failed; no channel
close(ws.initReq.retc) close(ws.recvc)
// close subscriber's channel return
}
ws.id = resp.WatchId
w.substreams[ws.id] = ws
}
func (w *watchGrpcStream) sendCloseSubstream(ws *watcherStream, resp *WatchResponse) {
select {
case ws.outc <- *resp:
case <-ws.initReq.ctx.Done():
case <-time.After(closeSendErrTimeout):
}
close(ws.outc) close(ws.outc)
delete(w.streams, ws.id) }
func (w *watchGrpcStream) closeSubstream(ws *watcherStream) {
// send channel response in case stream was never established
select {
case ws.initReq.retc <- ws.outc:
default:
}
// close subscriber's channel
if closeErr := w.closeErr; closeErr != nil && ws.initReq.ctx.Err() == nil {
go w.sendCloseSubstream(ws, &WatchResponse{closeErr: w.closeErr})
} else if ws.outc != nil {
close(ws.outc)
}
if ws.id != -1 {
delete(w.substreams, ws.id)
return
}
for i := range w.resuming {
if w.resuming[i] == ws {
w.resuming[i] = nil
return
}
}
} }
// run is the root of the goroutines for managing a watcher client // run is the root of the goroutines for managing a watcher client
@ -359,15 +364,29 @@ func (w *watchGrpcStream) run() {
var wc pb.Watch_WatchClient var wc pb.Watch_WatchClient
var closeErr error var closeErr error
// substreams marked to close but goroutine still running; needed for
// avoiding double-closing recvc on grpc stream teardown
closing := make(map[*watcherStream]struct{})
defer func() { defer func() {
w.owner.mu.Lock()
w.closeErr = closeErr w.closeErr = closeErr
if w.owner.streams != nil { // shutdown substreams and resuming substreams
delete(w.owner.streams, w.ctxKey) for _, ws := range w.substreams {
if _, ok := closing[ws]; !ok {
close(ws.recvc)
}
} }
close(w.donec) for _, ws := range w.resuming {
w.owner.mu.Unlock() if _, ok := closing[ws]; ws != nil && !ok {
w.cancel() close(ws.recvc)
}
}
w.joinSubstreams()
for toClose := len(w.substreams) + len(w.resuming); toClose > 0; toClose-- {
w.closeSubstream(<-w.closingc)
}
w.owner.closeStream(w)
}() }()
// start a stream with the etcd grpc server // start a stream with the etcd grpc server
@ -375,42 +394,49 @@ func (w *watchGrpcStream) run() {
return return
} }
var pendingReq, failedReq *watchRequest
curReqC := w.reqc
cancelSet := make(map[int64]struct{}) cancelSet := make(map[int64]struct{})
for { for {
select { select {
// Watch() requested // Watch() requested
case pendingReq = <-curReqC: case wreq := <-w.reqc:
// no more watch requests until there's a response outc := make(chan WatchResponse, 1)
curReqC = nil ws := &watcherStream{
if err := wc.Send(pendingReq.toPB()); err == nil { initReq: *wreq,
// pendingReq now waits on w.respc id: -1,
break outc: outc,
// unbufffered so resumes won't cause repeat events
recvc: make(chan *WatchResponse),
}
ws.donec = make(chan struct{})
go w.serveSubstream(ws, w.resumec)
// queue up for watcher creation/resume
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())
} }
failedReq = pendingReq
// New events from the watch client // New events from the watch client
case pbresp := <-w.respc: case pbresp := <-w.respc:
switch { switch {
case pbresp.Created: case pbresp.Created:
// response to pending req, try to add // response to head of queue creation
w.addStream(pbresp, pendingReq) if ws := w.resuming[0]; ws != nil {
pendingReq = nil w.addSubstream(pbresp, ws)
curReqC = w.reqc w.dispatchEvent(pbresp)
w.resuming[0] = nil
}
if ws := w.nextResume(); ws != nil {
wc.Send(ws.initReq.toPB())
}
case pbresp.Canceled: case pbresp.Canceled:
delete(cancelSet, pbresp.WatchId) delete(cancelSet, pbresp.WatchId)
// shutdown serveStream, if any if ws, ok := w.substreams[pbresp.WatchId]; ok {
w.mu.Lock() // signal to stream goroutine to update closingc
if ws, ok := w.streams[pbresp.WatchId]; ok {
close(ws.recvc) close(ws.recvc)
delete(w.streams, ws.id) closing[ws] = struct{}{}
}
numStreams := len(w.streams)
w.mu.Unlock()
if numStreams == 0 {
// don't leak watcher streams
return
} }
default: default:
// dispatch to appropriate watch stream // dispatch to appropriate watch stream
@ -431,7 +457,6 @@ func (w *watchGrpcStream) run() {
wc.Send(req) wc.Send(req)
} }
// watch client failed to recv; spawn another if possible // watch client failed to recv; spawn another if possible
// TODO report watch client errors from errc?
case err := <-w.errc: case err := <-w.errc:
if toErr(w.ctx, err) == v3rpc.ErrNoLeader { if toErr(w.ctx, err) == v3rpc.ErrNoLeader {
closeErr = err closeErr = err
@ -440,48 +465,58 @@ func (w *watchGrpcStream) run() {
if wc, closeErr = w.newWatchClient(); closeErr != nil { if wc, closeErr = w.newWatchClient(); closeErr != nil {
return return
} }
curReqC = w.reqc if ws := w.nextResume(); ws != nil {
if pendingReq != nil { wc.Send(ws.initReq.toPB())
failedReq = pendingReq
} }
cancelSet = make(map[int64]struct{}) cancelSet = make(map[int64]struct{})
case <-w.stopc: case <-w.ctx.Done():
return return
} case ws := <-w.closingc:
w.closeSubstream(ws)
// send failed; queue for retry delete(closing, ws)
if failedReq != nil { if len(w.substreams)+len(w.resuming) == 0 {
go func(wr *watchRequest) { // no more watchers on this stream, shutdown
select { return
case w.reqc <- wr: }
case <-wr.ctx.Done():
case <-w.donec:
}
}(pendingReq)
failedReq = nil
pendingReq = nil
} }
} }
} }
// 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 {
for len(w.resuming) != 0 {
if w.resuming[0] != nil {
return w.resuming[0]
}
w.resuming = w.resuming[1:len(w.resuming)]
}
return nil
}
// dispatchEvent sends a WatchResponse to the appropriate watcher stream // dispatchEvent sends a WatchResponse to the appropriate watcher stream
func (w *watchGrpcStream) dispatchEvent(pbresp *pb.WatchResponse) bool { func (w *watchGrpcStream) dispatchEvent(pbresp *pb.WatchResponse) bool {
w.mu.RLock() ws, ok := w.substreams[pbresp.WatchId]
defer w.mu.RUnlock() if !ok {
ws, ok := w.streams[pbresp.WatchId] return false
}
events := make([]*Event, len(pbresp.Events)) events := make([]*Event, len(pbresp.Events))
for i, ev := range pbresp.Events { for i, ev := range pbresp.Events {
events[i] = (*Event)(ev) events[i] = (*Event)(ev)
} }
if ok { wr := &WatchResponse{
wr := &WatchResponse{ Header: *pbresp.Header,
Header: *pbresp.Header, Events: events,
Events: events, CompactRevision: pbresp.CompactRevision,
CompactRevision: pbresp.CompactRevision, created: pbresp.Created,
Canceled: pbresp.Canceled} Canceled: pbresp.Canceled,
ws.recvc <- wr
} }
return ok select {
case ws.recvc <- wr:
case <-ws.donec:
return false
}
return true
} }
// serveWatchClient forwards messages from the grpc stream to run() // serveWatchClient forwards messages from the grpc stream to run()
@ -503,125 +538,171 @@ func (w *watchGrpcStream) serveWatchClient(wc pb.Watch_WatchClient) {
} }
} }
// serveStream forwards watch responses from run() to the subscriber // serveSubstream forwards watch responses from run() to the subscriber
func (w *watchGrpcStream) serveStream(ws *watcherStream) { func (w *watchGrpcStream) serveSubstream(ws *watcherStream, resumec chan struct{}) {
emptyWr := &WatchResponse{} if ws.closing {
wrs := []*WatchResponse{} panic("created substream goroutine but substream is closing")
}
// nextRev is the minimum expected next revision
nextRev := ws.initReq.rev
resuming := false resuming := false
closing := false defer func() {
for !closing { if !resuming {
ws.closing = true
}
close(ws.donec)
if !resuming {
w.closingc <- ws
}
}()
emptyWr := &WatchResponse{}
for {
curWr := emptyWr curWr := emptyWr
outc := ws.outc outc := ws.outc
if len(wrs) > 0 {
curWr = wrs[0] if len(ws.buf) > 0 && ws.buf[0].created {
select {
case ws.initReq.retc <- ws.outc:
default:
}
ws.buf = ws.buf[1:]
}
if len(ws.buf) > 0 {
curWr = ws.buf[0]
} else { } else {
outc = nil outc = nil
} }
select { select {
case outc <- *curWr: case outc <- *curWr:
if wrs[0].Err() != nil { if ws.buf[0].Err() != nil {
closing = true
break
}
var newRev int64
if len(wrs[0].Events) > 0 {
newRev = wrs[0].Events[len(wrs[0].Events)-1].Kv.ModRevision
} else {
newRev = wrs[0].Header.Revision
}
if newRev != ws.lastRev {
ws.lastRev = newRev
}
wrs[0] = nil
wrs = wrs[1:]
case wr, ok := <-ws.recvc:
if !ok {
// shutdown from closeStream
return return
} }
// resume up to last seen event if disconnected ws.buf[0] = nil
if resuming && wr.Err() == nil { ws.buf = ws.buf[1:]
resuming = false case wr, ok := <-ws.recvc:
// trim events already seen if !ok {
for i := 0; i < len(wr.Events); i++ { // shutdown from closeSubstream
if wr.Events[i].Kv.ModRevision > ws.lastRev { return
wr.Events = wr.Events[i:]
break
}
}
// only forward new events
if wr.Events[0].Kv.ModRevision == ws.lastRev {
break
}
} }
resuming = false // TODO pause channel if buffer gets too large
// TODO don't keep buffering if subscriber stops reading ws.buf = append(ws.buf, wr)
wrs = append(wrs, wr) nextRev = wr.Header.Revision
case resumeRev := <-ws.resumec: if len(wr.Events) > 0 {
wrs = nil nextRev = wr.Events[len(wr.Events)-1].Kv.ModRevision + 1
resuming = true
if resumeRev == -1 {
// pause serving stream while resume gets set up
break
} }
if resumeRev != ws.lastRev { ws.initReq.rev = nextRev
panic("unexpected resume revision") case <-w.ctx.Done():
} return
case <-w.donec:
closing = true
case <-ws.initReq.ctx.Done(): case <-ws.initReq.ctx.Done():
closing = true return
case <-resumec:
resuming = true
return
} }
} }
// try to send off close error
if w.closeErr != nil {
select {
case ws.outc <- WatchResponse{closeErr: w.closeErr}:
case <-w.donec:
case <-time.After(closeSendErrTimeout):
}
}
w.mu.Lock()
w.closeStream(ws)
w.mu.Unlock()
// lazily send cancel message if events on missing id // lazily send cancel message if events on missing id
} }
func (w *watchGrpcStream) newWatchClient() (pb.Watch_WatchClient, error) { func (w *watchGrpcStream) newWatchClient() (pb.Watch_WatchClient, error) {
ws, rerr := w.resume() // mark all substreams as resuming
if rerr != nil { close(w.resumec)
return nil, rerr w.resumec = make(chan struct{})
w.joinSubstreams()
for _, ws := range w.substreams {
ws.id = -1
w.resuming = append(w.resuming, ws)
} }
go w.serveWatchClient(ws) // strip out nils, if any
return ws, nil var resuming []*watcherStream
} for _, ws := range w.resuming {
if ws != nil {
// resume creates a new WatchClient with all current watchers reestablished resuming = append(resuming, ws)
func (w *watchGrpcStream) resume() (ws pb.Watch_WatchClient, err error) { }
for { }
if ws, err = w.openWatchClient(); err != nil { w.resuming = resuming
break w.substreams = make(map[int64]*watcherStream)
} else if err = w.resumeWatchers(ws); err == nil {
break // connect to grpc stream while accepting watcher cancelation
stopc := make(chan struct{})
donec := w.waitCancelSubstreams(stopc)
wc, err := w.openWatchClient()
close(stopc)
<-donec
// serve all non-closing streams, even if there's a client error
// so that the teardown path can shutdown the streams as expected.
for _, ws := range w.resuming {
if ws.closing {
continue
}
ws.donec = make(chan struct{})
go w.serveSubstream(ws, w.resumec)
}
if err != nil {
return nil, v3rpc.Error(err)
}
// receive data from new grpc stream
go w.serveWatchClient(wc)
return wc, nil
}
func (w *watchGrpcStream) waitCancelSubstreams(stopc <-chan struct{}) <-chan struct{} {
var wg sync.WaitGroup
wg.Add(len(w.resuming))
donec := make(chan struct{})
for i := range w.resuming {
go func(ws *watcherStream) {
defer wg.Done()
if ws.closing {
return
}
select {
case <-ws.initReq.ctx.Done():
// closed ws will be removed from resuming
ws.closing = true
close(ws.outc)
ws.outc = nil
go func() { w.closingc <- ws }()
case <-stopc:
}
}(w.resuming[i])
}
go func() {
defer close(donec)
wg.Wait()
}()
return donec
}
// joinSubstream waits for all substream goroutines to complete
func (w *watchGrpcStream) joinSubstreams() {
for _, ws := range w.substreams {
<-ws.donec
}
for _, ws := range w.resuming {
if ws != nil {
<-ws.donec
} }
} }
return ws, v3rpc.Error(err)
} }
// openWatchClient retries opening a watchclient until retryConnection fails // openWatchClient retries opening a watchclient until retryConnection fails
func (w *watchGrpcStream) openWatchClient() (ws pb.Watch_WatchClient, err error) { func (w *watchGrpcStream) openWatchClient() (ws pb.Watch_WatchClient, err error) {
for { for {
select { select {
case <-w.stopc: case <-w.ctx.Done():
if err == nil { if err == nil {
err = context.Canceled return nil, w.ctx.Err()
} }
return nil, err return nil, err
default: default:
} }
if ws, err = w.remote.Watch(w.ctx); ws != nil && err == nil { if ws, err = w.remote.Watch(w.ctx, grpc.FailFast(false)); ws != nil && err == nil {
break break
} }
if isHaltErr(w.ctx, err) { if isHaltErr(w.ctx, err) {
@ -631,48 +712,6 @@ func (w *watchGrpcStream) openWatchClient() (ws pb.Watch_WatchClient, err error)
return ws, nil return ws, nil
} }
// resumeWatchers rebuilds every registered watcher on a new client
func (w *watchGrpcStream) resumeWatchers(wc pb.Watch_WatchClient) error {
w.mu.RLock()
streams := make([]*watcherStream, 0, len(w.streams))
for _, ws := range w.streams {
streams = append(streams, ws)
}
w.mu.RUnlock()
for _, ws := range streams {
// pause serveStream
ws.resumec <- -1
// reconstruct watcher from initial request
if ws.lastRev != 0 {
ws.initReq.rev = ws.lastRev
}
if err := wc.Send(ws.initReq.toPB()); err != nil {
return err
}
// wait for request ack
resp, err := wc.Recv()
if err != nil {
return err
} else if len(resp.Events) != 0 || !resp.Created {
return fmt.Errorf("watcher: unexpected response (%+v)", resp)
}
// id may be different since new remote watcher; update map
w.mu.Lock()
delete(w.streams, ws.id)
ws.id = resp.WatchId
w.streams[ws.id] = ws
w.mu.Unlock()
// unpause serveStream
ws.resumec <- ws.lastRev
}
return nil
}
// toPB converts an internal watch request structure to its protobuf messagefunc (wr *watchRequest) // toPB converts an internal watch request structure to its protobuf messagefunc (wr *watchRequest)
func (wr *watchRequest) toPB() *pb.WatchRequest { func (wr *watchRequest) toPB() *pb.WatchRequest {
req := &pb.WatchCreateRequest{ req := &pb.WatchCreateRequest{
@ -680,6 +719,7 @@ func (wr *watchRequest) toPB() *pb.WatchRequest {
Key: []byte(wr.key), Key: []byte(wr.key),
RangeEnd: []byte(wr.end), RangeEnd: []byte(wr.end),
ProgressNotify: wr.progressNotify, ProgressNotify: wr.progressNotify,
PrevKv: wr.prevKV,
} }
cr := &pb.WatchRequest_CreateRequest{CreateRequest: req} cr := &pb.WatchRequest_CreateRequest{CreateRequest: req}
return &pb.WatchRequest{RequestUnion: cr} return &pb.WatchRequest{RequestUnion: cr}

80
cmd/Godeps/Godeps.json generated
View File

@ -1,6 +1,6 @@
{ {
"ImportPath": "github.com/coreos/etcd", "ImportPath": "github.com/coreos/etcd",
"GoVersion": "go1.6", "GoVersion": "go1.7",
"GodepVersion": "v74", "GodepVersion": "v74",
"Packages": [ "Packages": [
"./..." "./..."
@ -11,10 +11,6 @@
"Comment": "null-5", "Comment": "null-5",
"Rev": "'75cd24fc2f2c2a2088577d12123ddee5f54e0675'" "Rev": "'75cd24fc2f2c2a2088577d12123ddee5f54e0675'"
}, },
{
"ImportPath": "github.com/akrennmair/gopcap",
"Rev": "00e11033259acb75598ba416495bb708d864a010"
},
{ {
"ImportPath": "github.com/beorn7/perks/quantile", "ImportPath": "github.com/beorn7/perks/quantile",
"Rev": "b965b613227fddccbfffe13eae360ed3fa822f8d" "Rev": "b965b613227fddccbfffe13eae360ed3fa822f8d"
@ -25,8 +21,8 @@
}, },
{ {
"ImportPath": "github.com/boltdb/bolt", "ImportPath": "github.com/boltdb/bolt",
"Comment": "v1.2.1", "Comment": "v1.3.0",
"Rev": "dfb21201d9270c1082d5fb0f07f500311ff72f18" "Rev": "583e8937c61f1af6513608ccc75c97b6abdf4ff9"
}, },
{ {
"ImportPath": "github.com/cockroachdb/cmux", "ImportPath": "github.com/cockroachdb/cmux",
@ -38,18 +34,18 @@
}, },
{ {
"ImportPath": "github.com/coreos/go-systemd/daemon", "ImportPath": "github.com/coreos/go-systemd/daemon",
"Comment": "v3-6-gcea488b", "Comment": "v10-13-gd6c05a1d",
"Rev": "cea488b4e6855fee89b6c22a811e3c5baca861b6" "Rev": "d6c05a1dcbb5ac02b7653da4d99e5db340c20778"
}, },
{ {
"ImportPath": "github.com/coreos/go-systemd/journal", "ImportPath": "github.com/coreos/go-systemd/journal",
"Comment": "v3-6-gcea488b", "Comment": "v10-13-gd6c05a1d",
"Rev": "cea488b4e6855fee89b6c22a811e3c5baca861b6" "Rev": "d6c05a1dcbb5ac02b7653da4d99e5db340c20778"
}, },
{ {
"ImportPath": "github.com/coreos/go-systemd/util", "ImportPath": "github.com/coreos/go-systemd/util",
"Comment": "v3-6-gcea488b", "Comment": "v10-13-gd6c05a1d",
"Rev": "cea488b4e6855fee89b6c22a811e3c5baca861b6" "Rev": "d6c05a1dcbb5ac02b7653da4d99e5db340c20778"
}, },
{ {
"ImportPath": "github.com/coreos/pkg/capnslog", "ImportPath": "github.com/coreos/pkg/capnslog",
@ -65,26 +61,14 @@
"ImportPath": "github.com/dustin/go-humanize", "ImportPath": "github.com/dustin/go-humanize",
"Rev": "8929fe90cee4b2cb9deb468b51fb34eba64d1bf0" "Rev": "8929fe90cee4b2cb9deb468b51fb34eba64d1bf0"
}, },
{
"ImportPath": "github.com/gengo/grpc-gateway/runtime",
"Rev": "dcb844349dc5d2cb0300fdc4d2d374839d0d2e13"
},
{
"ImportPath": "github.com/gengo/grpc-gateway/runtime/internal",
"Rev": "dcb844349dc5d2cb0300fdc4d2d374839d0d2e13"
},
{
"ImportPath": "github.com/gengo/grpc-gateway/utilities",
"Rev": "dcb844349dc5d2cb0300fdc4d2d374839d0d2e13"
},
{ {
"ImportPath": "github.com/ghodss/yaml", "ImportPath": "github.com/ghodss/yaml",
"Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee" "Rev": "73d445a93680fa1a78ae23a5839bad48f32ba1ee"
}, },
{ {
"ImportPath": "github.com/gogo/protobuf/proto", "ImportPath": "github.com/gogo/protobuf/proto",
"Comment": "v0.2-13-gc3995ae", "Comment": "v0.2-33-ge18d7aa",
"Rev": "c3995ae437bb78d1189f4f147dfe5f87ad3596e4" "Rev": "e18d7aa8f8c624c915db340349aad4c49b10d173"
}, },
{ {
"ImportPath": "github.com/golang/glog", "ImportPath": "github.com/golang/glog",
@ -106,6 +90,21 @@
"ImportPath": "github.com/google/btree", "ImportPath": "github.com/google/btree",
"Rev": "7d79101e329e5a3adf994758c578dab82b90c017" "Rev": "7d79101e329e5a3adf994758c578dab82b90c017"
}, },
{
"ImportPath": "github.com/grpc-ecosystem/grpc-gateway/runtime",
"Comment": "v1.0.0-8-gf52d055",
"Rev": "f52d055dc48aec25854ed7d31862f78913cf17d1"
},
{
"ImportPath": "github.com/grpc-ecosystem/grpc-gateway/runtime/internal",
"Comment": "v1.0.0-8-gf52d055",
"Rev": "f52d055dc48aec25854ed7d31862f78913cf17d1"
},
{
"ImportPath": "github.com/grpc-ecosystem/grpc-gateway/utilities",
"Comment": "v1.0.0-8-gf52d055",
"Rev": "f52d055dc48aec25854ed7d31862f78913cf17d1"
},
{ {
"ImportPath": "github.com/inconshreveable/mousetrap", "ImportPath": "github.com/inconshreveable/mousetrap",
"Rev": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75" "Rev": "76626ae9c91c4f2a10f34cad8ce83ea42c93bb75"
@ -234,39 +233,48 @@
}, },
{ {
"ImportPath": "google.golang.org/grpc", "ImportPath": "google.golang.org/grpc",
"Rev": "e78224b060cf3215247b7be455f80ea22e469b66" "Comment": "v1.0.0-183-g231b4cf",
"Rev": "231b4cfea0e79843053a33f5fe90bd4d84b23cd3"
}, },
{ {
"ImportPath": "google.golang.org/grpc/codes", "ImportPath": "google.golang.org/grpc/codes",
"Rev": "e78224b060cf3215247b7be455f80ea22e469b66" "Comment": "v1.0.0-183-g231b4cf",
"Rev": "231b4cfea0e79843053a33f5fe90bd4d84b23cd3"
}, },
{ {
"ImportPath": "google.golang.org/grpc/credentials", "ImportPath": "google.golang.org/grpc/credentials",
"Rev": "e78224b060cf3215247b7be455f80ea22e469b66" "Comment": "v1.0.0-183-g231b4cf",
"Rev": "231b4cfea0e79843053a33f5fe90bd4d84b23cd3"
}, },
{ {
"ImportPath": "google.golang.org/grpc/grpclog", "ImportPath": "google.golang.org/grpc/grpclog",
"Rev": "e78224b060cf3215247b7be455f80ea22e469b66" "Comment": "v1.0.0-183-g231b4cf",
"Rev": "231b4cfea0e79843053a33f5fe90bd4d84b23cd3"
}, },
{ {
"ImportPath": "google.golang.org/grpc/internal", "ImportPath": "google.golang.org/grpc/internal",
"Rev": "e78224b060cf3215247b7be455f80ea22e469b66" "Comment": "v1.0.0-183-g231b4cf",
"Rev": "231b4cfea0e79843053a33f5fe90bd4d84b23cd3"
}, },
{ {
"ImportPath": "google.golang.org/grpc/metadata", "ImportPath": "google.golang.org/grpc/metadata",
"Rev": "e78224b060cf3215247b7be455f80ea22e469b66" "Comment": "v1.0.0-183-g231b4cf",
"Rev": "231b4cfea0e79843053a33f5fe90bd4d84b23cd3"
}, },
{ {
"ImportPath": "google.golang.org/grpc/naming", "ImportPath": "google.golang.org/grpc/naming",
"Rev": "e78224b060cf3215247b7be455f80ea22e469b66" "Comment": "v1.0.0-183-g231b4cf",
"Rev": "231b4cfea0e79843053a33f5fe90bd4d84b23cd3"
}, },
{ {
"ImportPath": "google.golang.org/grpc/peer", "ImportPath": "google.golang.org/grpc/peer",
"Rev": "e78224b060cf3215247b7be455f80ea22e469b66" "Comment": "v1.0.0-183-g231b4cf",
"Rev": "231b4cfea0e79843053a33f5fe90bd4d84b23cd3"
}, },
{ {
"ImportPath": "google.golang.org/grpc/transport", "ImportPath": "google.golang.org/grpc/transport",
"Rev": "e78224b060cf3215247b7be455f80ea22e469b66" "Comment": "v1.0.0-183-g231b4cf",
"Rev": "231b4cfea0e79843053a33f5fe90bd4d84b23cd3"
}, },
{ {
"ImportPath": "gopkg.in/cheggaaa/pb.v1", "ImportPath": "gopkg.in/cheggaaa/pb.v1",

View File

@ -1,5 +0,0 @@
#*
*~
/tools/pass/pass
/tools/pcaptest/pcaptest
/tools/tcpdump/tcpdump

View File

@ -1,27 +0,0 @@
Copyright (c) 2009-2011 Andreas Krennmair. All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
* Neither the name of Andreas Krennmair nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -1,11 +0,0 @@
# PCAP
This is a simple wrapper around libpcap for Go. Originally written by Andreas
Krennmair <ak@synflood.at> and only minorly touched up by Mark Smith <mark@qq.is>.
Please see the included pcaptest.go and tcpdump.go programs for instructions on
how to use this library.
Miek Gieben <miek@miek.nl> has created a more Go-like package and replaced functionality
with standard functions from the standard library. The package has also been renamed to
pcap.

View File

@ -1,527 +0,0 @@
package pcap
import (
"encoding/binary"
"fmt"
"net"
"reflect"
"strings"
)
const (
TYPE_IP = 0x0800
TYPE_ARP = 0x0806
TYPE_IP6 = 0x86DD
TYPE_VLAN = 0x8100
IP_ICMP = 1
IP_INIP = 4
IP_TCP = 6
IP_UDP = 17
)
const (
ERRBUF_SIZE = 256
// According to pcap-linktype(7).
LINKTYPE_NULL = 0
LINKTYPE_ETHERNET = 1
LINKTYPE_TOKEN_RING = 6
LINKTYPE_ARCNET = 7
LINKTYPE_SLIP = 8
LINKTYPE_PPP = 9
LINKTYPE_FDDI = 10
LINKTYPE_ATM_RFC1483 = 100
LINKTYPE_RAW = 101
LINKTYPE_PPP_HDLC = 50
LINKTYPE_PPP_ETHER = 51
LINKTYPE_C_HDLC = 104
LINKTYPE_IEEE802_11 = 105
LINKTYPE_FRELAY = 107
LINKTYPE_LOOP = 108
LINKTYPE_LINUX_SLL = 113
LINKTYPE_LTALK = 104
LINKTYPE_PFLOG = 117
LINKTYPE_PRISM_HEADER = 119
LINKTYPE_IP_OVER_FC = 122
LINKTYPE_SUNATM = 123
LINKTYPE_IEEE802_11_RADIO = 127
LINKTYPE_ARCNET_LINUX = 129
LINKTYPE_LINUX_IRDA = 144
LINKTYPE_LINUX_LAPD = 177
)
type addrHdr interface {
SrcAddr() string
DestAddr() string
Len() int
}
type addrStringer interface {
String(addr addrHdr) string
}
func decodemac(pkt []byte) uint64 {
mac := uint64(0)
for i := uint(0); i < 6; i++ {
mac = (mac << 8) + uint64(pkt[i])
}
return mac
}
// Decode decodes the headers of a Packet.
func (p *Packet) Decode() {
if len(p.Data) <= 14 {
return
}
p.Type = int(binary.BigEndian.Uint16(p.Data[12:14]))
p.DestMac = decodemac(p.Data[0:6])
p.SrcMac = decodemac(p.Data[6:12])
if len(p.Data) >= 15 {
p.Payload = p.Data[14:]
}
switch p.Type {
case TYPE_IP:
p.decodeIp()
case TYPE_IP6:
p.decodeIp6()
case TYPE_ARP:
p.decodeArp()
case TYPE_VLAN:
p.decodeVlan()
}
}
func (p *Packet) headerString(headers []interface{}) string {
// If there's just one header, return that.
if len(headers) == 1 {
if hdr, ok := headers[0].(fmt.Stringer); ok {
return hdr.String()
}
}
// If there are two headers (IPv4/IPv6 -> TCP/UDP/IP..)
if len(headers) == 2 {
// Commonly the first header is an address.
if addr, ok := p.Headers[0].(addrHdr); ok {
if hdr, ok := p.Headers[1].(addrStringer); ok {
return fmt.Sprintf("%s %s", p.Time, hdr.String(addr))
}
}
}
// For IP in IP, we do a recursive call.
if len(headers) >= 2 {
if addr, ok := headers[0].(addrHdr); ok {
if _, ok := headers[1].(addrHdr); ok {
return fmt.Sprintf("%s > %s IP in IP: ",
addr.SrcAddr(), addr.DestAddr(), p.headerString(headers[1:]))
}
}
}
var typeNames []string
for _, hdr := range headers {
typeNames = append(typeNames, reflect.TypeOf(hdr).String())
}
return fmt.Sprintf("unknown [%s]", strings.Join(typeNames, ","))
}
// String prints a one-line representation of the packet header.
// The output is suitable for use in a tcpdump program.
func (p *Packet) String() string {
// If there are no headers, print "unsupported protocol".
if len(p.Headers) == 0 {
return fmt.Sprintf("%s unsupported protocol %d", p.Time, int(p.Type))
}
return fmt.Sprintf("%s %s", p.Time, p.headerString(p.Headers))
}
// Arphdr is a ARP packet header.
type Arphdr struct {
Addrtype uint16
Protocol uint16
HwAddressSize uint8
ProtAddressSize uint8
Operation uint16
SourceHwAddress []byte
SourceProtAddress []byte
DestHwAddress []byte
DestProtAddress []byte
}
func (arp *Arphdr) String() (s string) {
switch arp.Operation {
case 1:
s = "ARP request"
case 2:
s = "ARP Reply"
}
if arp.Addrtype == LINKTYPE_ETHERNET && arp.Protocol == TYPE_IP {
s = fmt.Sprintf("%012x (%s) > %012x (%s)",
decodemac(arp.SourceHwAddress), arp.SourceProtAddress,
decodemac(arp.DestHwAddress), arp.DestProtAddress)
} else {
s = fmt.Sprintf("addrtype = %d protocol = %d", arp.Addrtype, arp.Protocol)
}
return
}
func (p *Packet) decodeArp() {
if len(p.Payload) < 8 {
return
}
pkt := p.Payload
arp := new(Arphdr)
arp.Addrtype = binary.BigEndian.Uint16(pkt[0:2])
arp.Protocol = binary.BigEndian.Uint16(pkt[2:4])
arp.HwAddressSize = pkt[4]
arp.ProtAddressSize = pkt[5]
arp.Operation = binary.BigEndian.Uint16(pkt[6:8])
if len(pkt) < int(8+2*arp.HwAddressSize+2*arp.ProtAddressSize) {
return
}
arp.SourceHwAddress = pkt[8 : 8+arp.HwAddressSize]
arp.SourceProtAddress = pkt[8+arp.HwAddressSize : 8+arp.HwAddressSize+arp.ProtAddressSize]
arp.DestHwAddress = pkt[8+arp.HwAddressSize+arp.ProtAddressSize : 8+2*arp.HwAddressSize+arp.ProtAddressSize]
arp.DestProtAddress = pkt[8+2*arp.HwAddressSize+arp.ProtAddressSize : 8+2*arp.HwAddressSize+2*arp.ProtAddressSize]
p.Headers = append(p.Headers, arp)
if len(pkt) >= int(8+2*arp.HwAddressSize+2*arp.ProtAddressSize) {
p.Payload = p.Payload[8+2*arp.HwAddressSize+2*arp.ProtAddressSize:]
}
}
// IPadr is the header of an IP packet.
type Iphdr struct {
Version uint8
Ihl uint8
Tos uint8
Length uint16
Id uint16
Flags uint8
FragOffset uint16
Ttl uint8
Protocol uint8
Checksum uint16
SrcIp []byte
DestIp []byte
}
func (p *Packet) decodeIp() {
if len(p.Payload) < 20 {
return
}
pkt := p.Payload
ip := new(Iphdr)
ip.Version = uint8(pkt[0]) >> 4
ip.Ihl = uint8(pkt[0]) & 0x0F
ip.Tos = pkt[1]
ip.Length = binary.BigEndian.Uint16(pkt[2:4])
ip.Id = binary.BigEndian.Uint16(pkt[4:6])
flagsfrags := binary.BigEndian.Uint16(pkt[6:8])
ip.Flags = uint8(flagsfrags >> 13)
ip.FragOffset = flagsfrags & 0x1FFF
ip.Ttl = pkt[8]
ip.Protocol = pkt[9]
ip.Checksum = binary.BigEndian.Uint16(pkt[10:12])
ip.SrcIp = pkt[12:16]
ip.DestIp = pkt[16:20]
pEnd := int(ip.Length)
if pEnd > len(pkt) {
pEnd = len(pkt)
}
if len(pkt) >= pEnd && int(ip.Ihl*4) < pEnd {
p.Payload = pkt[ip.Ihl*4 : pEnd]
} else {
p.Payload = []byte{}
}
p.Headers = append(p.Headers, ip)
p.IP = ip
switch ip.Protocol {
case IP_TCP:
p.decodeTcp()
case IP_UDP:
p.decodeUdp()
case IP_ICMP:
p.decodeIcmp()
case IP_INIP:
p.decodeIp()
}
}
func (ip *Iphdr) SrcAddr() string { return net.IP(ip.SrcIp).String() }
func (ip *Iphdr) DestAddr() string { return net.IP(ip.DestIp).String() }
func (ip *Iphdr) Len() int { return int(ip.Length) }
type Vlanhdr struct {
Priority byte
DropEligible bool
VlanIdentifier int
Type int // Not actually part of the vlan header, but the type of the actual packet
}
func (v *Vlanhdr) String() {
fmt.Sprintf("VLAN Priority:%d Drop:%v Tag:%d", v.Priority, v.DropEligible, v.VlanIdentifier)
}
func (p *Packet) decodeVlan() {
pkt := p.Payload
vlan := new(Vlanhdr)
if len(pkt) < 4 {
return
}
vlan.Priority = (pkt[2] & 0xE0) >> 13
vlan.DropEligible = pkt[2]&0x10 != 0
vlan.VlanIdentifier = int(binary.BigEndian.Uint16(pkt[:2])) & 0x0FFF
vlan.Type = int(binary.BigEndian.Uint16(p.Payload[2:4]))
p.Headers = append(p.Headers, vlan)
if len(pkt) >= 5 {
p.Payload = p.Payload[4:]
}
switch vlan.Type {
case TYPE_IP:
p.decodeIp()
case TYPE_IP6:
p.decodeIp6()
case TYPE_ARP:
p.decodeArp()
}
}
type Tcphdr struct {
SrcPort uint16
DestPort uint16
Seq uint32
Ack uint32
DataOffset uint8
Flags uint16
Window uint16
Checksum uint16
Urgent uint16
Data []byte
}
const (
TCP_FIN = 1 << iota
TCP_SYN
TCP_RST
TCP_PSH
TCP_ACK
TCP_URG
TCP_ECE
TCP_CWR
TCP_NS
)
func (p *Packet) decodeTcp() {
if len(p.Payload) < 20 {
return
}
pkt := p.Payload
tcp := new(Tcphdr)
tcp.SrcPort = binary.BigEndian.Uint16(pkt[0:2])
tcp.DestPort = binary.BigEndian.Uint16(pkt[2:4])
tcp.Seq = binary.BigEndian.Uint32(pkt[4:8])
tcp.Ack = binary.BigEndian.Uint32(pkt[8:12])
tcp.DataOffset = (pkt[12] & 0xF0) >> 4
tcp.Flags = binary.BigEndian.Uint16(pkt[12:14]) & 0x1FF
tcp.Window = binary.BigEndian.Uint16(pkt[14:16])
tcp.Checksum = binary.BigEndian.Uint16(pkt[16:18])
tcp.Urgent = binary.BigEndian.Uint16(pkt[18:20])
if len(pkt) >= int(tcp.DataOffset*4) {
p.Payload = pkt[tcp.DataOffset*4:]
}
p.Headers = append(p.Headers, tcp)
p.TCP = tcp
}
func (tcp *Tcphdr) String(hdr addrHdr) string {
return fmt.Sprintf("TCP %s:%d > %s:%d %s SEQ=%d ACK=%d LEN=%d",
hdr.SrcAddr(), int(tcp.SrcPort), hdr.DestAddr(), int(tcp.DestPort),
tcp.FlagsString(), int64(tcp.Seq), int64(tcp.Ack), hdr.Len())
}
func (tcp *Tcphdr) FlagsString() string {
var sflags []string
if 0 != (tcp.Flags & TCP_SYN) {
sflags = append(sflags, "syn")
}
if 0 != (tcp.Flags & TCP_FIN) {
sflags = append(sflags, "fin")
}
if 0 != (tcp.Flags & TCP_ACK) {
sflags = append(sflags, "ack")
}
if 0 != (tcp.Flags & TCP_PSH) {
sflags = append(sflags, "psh")
}
if 0 != (tcp.Flags & TCP_RST) {
sflags = append(sflags, "rst")
}
if 0 != (tcp.Flags & TCP_URG) {
sflags = append(sflags, "urg")
}
if 0 != (tcp.Flags & TCP_NS) {
sflags = append(sflags, "ns")
}
if 0 != (tcp.Flags & TCP_CWR) {
sflags = append(sflags, "cwr")
}
if 0 != (tcp.Flags & TCP_ECE) {
sflags = append(sflags, "ece")
}
return fmt.Sprintf("[%s]", strings.Join(sflags, " "))
}
type Udphdr struct {
SrcPort uint16
DestPort uint16
Length uint16
Checksum uint16
}
func (p *Packet) decodeUdp() {
if len(p.Payload) < 8 {
return
}
pkt := p.Payload
udp := new(Udphdr)
udp.SrcPort = binary.BigEndian.Uint16(pkt[0:2])
udp.DestPort = binary.BigEndian.Uint16(pkt[2:4])
udp.Length = binary.BigEndian.Uint16(pkt[4:6])
udp.Checksum = binary.BigEndian.Uint16(pkt[6:8])
p.Headers = append(p.Headers, udp)
p.UDP = udp
if len(p.Payload) >= 8 {
p.Payload = pkt[8:]
}
}
func (udp *Udphdr) String(hdr addrHdr) string {
return fmt.Sprintf("UDP %s:%d > %s:%d LEN=%d CHKSUM=%d",
hdr.SrcAddr(), int(udp.SrcPort), hdr.DestAddr(), int(udp.DestPort),
int(udp.Length), int(udp.Checksum))
}
type Icmphdr struct {
Type uint8
Code uint8
Checksum uint16
Id uint16
Seq uint16
Data []byte
}
func (p *Packet) decodeIcmp() *Icmphdr {
if len(p.Payload) < 8 {
return nil
}
pkt := p.Payload
icmp := new(Icmphdr)
icmp.Type = pkt[0]
icmp.Code = pkt[1]
icmp.Checksum = binary.BigEndian.Uint16(pkt[2:4])
icmp.Id = binary.BigEndian.Uint16(pkt[4:6])
icmp.Seq = binary.BigEndian.Uint16(pkt[6:8])
p.Payload = pkt[8:]
p.Headers = append(p.Headers, icmp)
return icmp
}
func (icmp *Icmphdr) String(hdr addrHdr) string {
return fmt.Sprintf("ICMP %s > %s Type = %d Code = %d ",
hdr.SrcAddr(), hdr.DestAddr(), icmp.Type, icmp.Code)
}
func (icmp *Icmphdr) TypeString() (result string) {
switch icmp.Type {
case 0:
result = fmt.Sprintf("Echo reply seq=%d", icmp.Seq)
case 3:
switch icmp.Code {
case 0:
result = "Network unreachable"
case 1:
result = "Host unreachable"
case 2:
result = "Protocol unreachable"
case 3:
result = "Port unreachable"
default:
result = "Destination unreachable"
}
case 8:
result = fmt.Sprintf("Echo request seq=%d", icmp.Seq)
case 30:
result = "Traceroute"
}
return
}
type Ip6hdr struct {
// http://www.networksorcery.com/enp/protocol/ipv6.htm
Version uint8 // 4 bits
TrafficClass uint8 // 8 bits
FlowLabel uint32 // 20 bits
Length uint16 // 16 bits
NextHeader uint8 // 8 bits, same as Protocol in Iphdr
HopLimit uint8 // 8 bits
SrcIp []byte // 16 bytes
DestIp []byte // 16 bytes
}
func (p *Packet) decodeIp6() {
if len(p.Payload) < 40 {
return
}
pkt := p.Payload
ip6 := new(Ip6hdr)
ip6.Version = uint8(pkt[0]) >> 4
ip6.TrafficClass = uint8((binary.BigEndian.Uint16(pkt[0:2]) >> 4) & 0x00FF)
ip6.FlowLabel = binary.BigEndian.Uint32(pkt[0:4]) & 0x000FFFFF
ip6.Length = binary.BigEndian.Uint16(pkt[4:6])
ip6.NextHeader = pkt[6]
ip6.HopLimit = pkt[7]
ip6.SrcIp = pkt[8:24]
ip6.DestIp = pkt[24:40]
if len(p.Payload) >= 40 {
p.Payload = pkt[40:]
}
p.Headers = append(p.Headers, ip6)
switch ip6.NextHeader {
case IP_TCP:
p.decodeTcp()
case IP_UDP:
p.decodeUdp()
case IP_ICMP:
p.decodeIcmp()
case IP_INIP:
p.decodeIp()
}
}
func (ip6 *Ip6hdr) SrcAddr() string { return net.IP(ip6.SrcIp).String() }
func (ip6 *Ip6hdr) DestAddr() string { return net.IP(ip6.DestIp).String() }
func (ip6 *Ip6hdr) Len() int { return int(ip6.Length) }

View File

@ -1,206 +0,0 @@
package pcap
import (
"encoding/binary"
"fmt"
"io"
"time"
)
// FileHeader is the parsed header of a pcap file.
// http://wiki.wireshark.org/Development/LibpcapFileFormat
type FileHeader struct {
MagicNumber uint32
VersionMajor uint16
VersionMinor uint16
TimeZone int32
SigFigs uint32
SnapLen uint32
Network uint32
}
type PacketTime struct {
Sec int32
Usec int32
}
// Convert the PacketTime to a go Time struct.
func (p *PacketTime) Time() time.Time {
return time.Unix(int64(p.Sec), int64(p.Usec)*1000)
}
// Packet is a single packet parsed from a pcap file.
//
// Convenient access to IP, TCP, and UDP headers is provided after Decode()
// is called if the packet is of the appropriate type.
type Packet struct {
Time time.Time // packet send/receive time
Caplen uint32 // bytes stored in the file (caplen <= len)
Len uint32 // bytes sent/received
Data []byte // packet data
Type int // protocol type, see LINKTYPE_*
DestMac uint64
SrcMac uint64
Headers []interface{} // decoded headers, in order
Payload []byte // remaining non-header bytes
IP *Iphdr // IP header (for IP packets, after decoding)
TCP *Tcphdr // TCP header (for TCP packets, after decoding)
UDP *Udphdr // UDP header (for UDP packets after decoding)
}
// Reader parses pcap files.
type Reader struct {
flip bool
buf io.Reader
err error
fourBytes []byte
twoBytes []byte
sixteenBytes []byte
Header FileHeader
}
// NewReader reads pcap data from an io.Reader.
func NewReader(reader io.Reader) (*Reader, error) {
r := &Reader{
buf: reader,
fourBytes: make([]byte, 4),
twoBytes: make([]byte, 2),
sixteenBytes: make([]byte, 16),
}
switch magic := r.readUint32(); magic {
case 0xa1b2c3d4:
r.flip = false
case 0xd4c3b2a1:
r.flip = true
default:
return nil, fmt.Errorf("pcap: bad magic number: %0x", magic)
}
r.Header = FileHeader{
MagicNumber: 0xa1b2c3d4,
VersionMajor: r.readUint16(),
VersionMinor: r.readUint16(),
TimeZone: r.readInt32(),
SigFigs: r.readUint32(),
SnapLen: r.readUint32(),
Network: r.readUint32(),
}
return r, nil
}
// Next returns the next packet or nil if no more packets can be read.
func (r *Reader) Next() *Packet {
d := r.sixteenBytes
r.err = r.read(d)
if r.err != nil {
return nil
}
timeSec := asUint32(d[0:4], r.flip)
timeUsec := asUint32(d[4:8], r.flip)
capLen := asUint32(d[8:12], r.flip)
origLen := asUint32(d[12:16], r.flip)
data := make([]byte, capLen)
if r.err = r.read(data); r.err != nil {
return nil
}
return &Packet{
Time: time.Unix(int64(timeSec), int64(timeUsec)),
Caplen: capLen,
Len: origLen,
Data: data,
}
}
func (r *Reader) read(data []byte) error {
var err error
n, err := r.buf.Read(data)
for err == nil && n != len(data) {
var chunk int
chunk, err = r.buf.Read(data[n:])
n += chunk
}
if len(data) == n {
return nil
}
return err
}
func (r *Reader) readUint32() uint32 {
data := r.fourBytes
if r.err = r.read(data); r.err != nil {
return 0
}
return asUint32(data, r.flip)
}
func (r *Reader) readInt32() int32 {
data := r.fourBytes
if r.err = r.read(data); r.err != nil {
return 0
}
return int32(asUint32(data, r.flip))
}
func (r *Reader) readUint16() uint16 {
data := r.twoBytes
if r.err = r.read(data); r.err != nil {
return 0
}
return asUint16(data, r.flip)
}
// Writer writes a pcap file.
type Writer struct {
writer io.Writer
buf []byte
}
// NewWriter creates a Writer that stores output in an io.Writer.
// The FileHeader is written immediately.
func NewWriter(writer io.Writer, header *FileHeader) (*Writer, error) {
w := &Writer{
writer: writer,
buf: make([]byte, 24),
}
binary.LittleEndian.PutUint32(w.buf, header.MagicNumber)
binary.LittleEndian.PutUint16(w.buf[4:], header.VersionMajor)
binary.LittleEndian.PutUint16(w.buf[6:], header.VersionMinor)
binary.LittleEndian.PutUint32(w.buf[8:], uint32(header.TimeZone))
binary.LittleEndian.PutUint32(w.buf[12:], header.SigFigs)
binary.LittleEndian.PutUint32(w.buf[16:], header.SnapLen)
binary.LittleEndian.PutUint32(w.buf[20:], header.Network)
if _, err := writer.Write(w.buf); err != nil {
return nil, err
}
return w, nil
}
// Writer writes a packet to the underlying writer.
func (w *Writer) Write(pkt *Packet) error {
binary.LittleEndian.PutUint32(w.buf, uint32(pkt.Time.Unix()))
binary.LittleEndian.PutUint32(w.buf[4:], uint32(pkt.Time.Nanosecond()))
binary.LittleEndian.PutUint32(w.buf[8:], uint32(pkt.Time.Unix()))
binary.LittleEndian.PutUint32(w.buf[12:], pkt.Len)
if _, err := w.writer.Write(w.buf[:16]); err != nil {
return err
}
_, err := w.writer.Write(pkt.Data)
return err
}
func asUint32(data []byte, flip bool) uint32 {
if flip {
return binary.BigEndian.Uint32(data)
}
return binary.LittleEndian.Uint32(data)
}
func asUint16(data []byte, flip bool) uint16 {
if flip {
return binary.BigEndian.Uint16(data)
}
return binary.LittleEndian.Uint16(data)
}

View File

@ -1,266 +0,0 @@
// Interface to both live and offline pcap parsing.
package pcap
/*
#cgo linux LDFLAGS: -lpcap
#cgo freebsd LDFLAGS: -lpcap
#cgo darwin LDFLAGS: -lpcap
#cgo windows CFLAGS: -I C:/WpdPack/Include
#cgo windows,386 LDFLAGS: -L C:/WpdPack/Lib -lwpcap
#cgo windows,amd64 LDFLAGS: -L C:/WpdPack/Lib/x64 -lwpcap
#include <stdlib.h>
#include <pcap.h>
// Workaround for not knowing how to cast to const u_char**
int hack_pcap_next_ex(pcap_t *p, struct pcap_pkthdr **pkt_header,
u_char **pkt_data) {
return pcap_next_ex(p, pkt_header, (const u_char **)pkt_data);
}
*/
import "C"
import (
"errors"
"net"
"syscall"
"time"
"unsafe"
)
type Pcap struct {
cptr *C.pcap_t
}
type Stat struct {
PacketsReceived uint32
PacketsDropped uint32
PacketsIfDropped uint32
}
type Interface struct {
Name string
Description string
Addresses []IFAddress
// TODO: add more elements
}
type IFAddress struct {
IP net.IP
Netmask net.IPMask
// TODO: add broadcast + PtP dst ?
}
func (p *Pcap) Next() (pkt *Packet) {
rv, _ := p.NextEx()
return rv
}
// Openlive opens a device and returns a *Pcap handler
func Openlive(device string, snaplen int32, promisc bool, timeout_ms int32) (handle *Pcap, err error) {
var buf *C.char
buf = (*C.char)(C.calloc(ERRBUF_SIZE, 1))
h := new(Pcap)
var pro int32
if promisc {
pro = 1
}
dev := C.CString(device)
defer C.free(unsafe.Pointer(dev))
h.cptr = C.pcap_open_live(dev, C.int(snaplen), C.int(pro), C.int(timeout_ms), buf)
if nil == h.cptr {
handle = nil
err = errors.New(C.GoString(buf))
} else {
handle = h
}
C.free(unsafe.Pointer(buf))
return
}
func Openoffline(file string) (handle *Pcap, err error) {
var buf *C.char
buf = (*C.char)(C.calloc(ERRBUF_SIZE, 1))
h := new(Pcap)
cf := C.CString(file)
defer C.free(unsafe.Pointer(cf))
h.cptr = C.pcap_open_offline(cf, buf)
if nil == h.cptr {
handle = nil
err = errors.New(C.GoString(buf))
} else {
handle = h
}
C.free(unsafe.Pointer(buf))
return
}
func (p *Pcap) NextEx() (pkt *Packet, result int32) {
var pkthdr *C.struct_pcap_pkthdr
var buf_ptr *C.u_char
var buf unsafe.Pointer
result = int32(C.hack_pcap_next_ex(p.cptr, &pkthdr, &buf_ptr))
buf = unsafe.Pointer(buf_ptr)
if nil == buf {
return
}
pkt = new(Packet)
pkt.Time = time.Unix(int64(pkthdr.ts.tv_sec), int64(pkthdr.ts.tv_usec)*1000)
pkt.Caplen = uint32(pkthdr.caplen)
pkt.Len = uint32(pkthdr.len)
pkt.Data = C.GoBytes(buf, C.int(pkthdr.caplen))
return
}
func (p *Pcap) Close() {
C.pcap_close(p.cptr)
}
func (p *Pcap) Geterror() error {
return errors.New(C.GoString(C.pcap_geterr(p.cptr)))
}
func (p *Pcap) Getstats() (stat *Stat, err error) {
var cstats _Ctype_struct_pcap_stat
if -1 == C.pcap_stats(p.cptr, &cstats) {
return nil, p.Geterror()
}
stats := new(Stat)
stats.PacketsReceived = uint32(cstats.ps_recv)
stats.PacketsDropped = uint32(cstats.ps_drop)
stats.PacketsIfDropped = uint32(cstats.ps_ifdrop)
return stats, nil
}
func (p *Pcap) Setfilter(expr string) (err error) {
var bpf _Ctype_struct_bpf_program
cexpr := C.CString(expr)
defer C.free(unsafe.Pointer(cexpr))
if -1 == C.pcap_compile(p.cptr, &bpf, cexpr, 1, 0) {
return p.Geterror()
}
if -1 == C.pcap_setfilter(p.cptr, &bpf) {
C.pcap_freecode(&bpf)
return p.Geterror()
}
C.pcap_freecode(&bpf)
return nil
}
func Version() string {
return C.GoString(C.pcap_lib_version())
}
func (p *Pcap) Datalink() int {
return int(C.pcap_datalink(p.cptr))
}
func (p *Pcap) Setdatalink(dlt int) error {
if -1 == C.pcap_set_datalink(p.cptr, C.int(dlt)) {
return p.Geterror()
}
return nil
}
func DatalinkValueToName(dlt int) string {
if name := C.pcap_datalink_val_to_name(C.int(dlt)); name != nil {
return C.GoString(name)
}
return ""
}
func DatalinkValueToDescription(dlt int) string {
if desc := C.pcap_datalink_val_to_description(C.int(dlt)); desc != nil {
return C.GoString(desc)
}
return ""
}
func Findalldevs() (ifs []Interface, err error) {
var buf *C.char
buf = (*C.char)(C.calloc(ERRBUF_SIZE, 1))
defer C.free(unsafe.Pointer(buf))
var alldevsp *C.pcap_if_t
if -1 == C.pcap_findalldevs((**C.pcap_if_t)(&alldevsp), buf) {
return nil, errors.New(C.GoString(buf))
}
defer C.pcap_freealldevs((*C.pcap_if_t)(alldevsp))
dev := alldevsp
var i uint32
for i = 0; dev != nil; dev = (*C.pcap_if_t)(dev.next) {
i++
}
ifs = make([]Interface, i)
dev = alldevsp
for j := uint32(0); dev != nil; dev = (*C.pcap_if_t)(dev.next) {
var iface Interface
iface.Name = C.GoString(dev.name)
iface.Description = C.GoString(dev.description)
iface.Addresses = findalladdresses(dev.addresses)
// TODO: add more elements
ifs[j] = iface
j++
}
return
}
func findalladdresses(addresses *_Ctype_struct_pcap_addr) (retval []IFAddress) {
// TODO - make it support more than IPv4 and IPv6?
retval = make([]IFAddress, 0, 1)
for curaddr := addresses; curaddr != nil; curaddr = (*_Ctype_struct_pcap_addr)(curaddr.next) {
var a IFAddress
var err error
if a.IP, err = sockaddr_to_IP((*syscall.RawSockaddr)(unsafe.Pointer(curaddr.addr))); err != nil {
continue
}
if a.Netmask, err = sockaddr_to_IP((*syscall.RawSockaddr)(unsafe.Pointer(curaddr.addr))); err != nil {
continue
}
retval = append(retval, a)
}
return
}
func sockaddr_to_IP(rsa *syscall.RawSockaddr) (IP []byte, err error) {
switch rsa.Family {
case syscall.AF_INET:
pp := (*syscall.RawSockaddrInet4)(unsafe.Pointer(rsa))
IP = make([]byte, 4)
for i := 0; i < len(IP); i++ {
IP[i] = pp.Addr[i]
}
return
case syscall.AF_INET6:
pp := (*syscall.RawSockaddrInet6)(unsafe.Pointer(rsa))
IP = make([]byte, 16)
for i := 0; i < len(IP); i++ {
IP[i] = pp.Addr[i]
}
return
}
err = errors.New("Unsupported address type")
return
}
func (p *Pcap) Inject(data []byte) (err error) {
buf := (*C.char)(C.malloc((C.size_t)(len(data))))
for i := 0; i < len(data); i++ {
*(*byte)(unsafe.Pointer(uintptr(unsafe.Pointer(buf)) + uintptr(i))) = data[i]
}
if -1 == C.pcap_sendpacket(p.cptr, (*C.u_char)(unsafe.Pointer(buf)), (C.int)(len(data))) {
err = p.Geterror()
}
C.free(unsafe.Pointer(buf))
return
}

View File

@ -1,4 +1,4 @@
Bolt [![Coverage Status](https://coveralls.io/repos/boltdb/bolt/badge.svg?branch=master)](https://coveralls.io/r/boltdb/bolt?branch=master) [![GoDoc](https://godoc.org/github.com/boltdb/bolt?status.svg)](https://godoc.org/github.com/boltdb/bolt) ![Version](https://img.shields.io/badge/version-1.0-green.svg) Bolt [![Coverage Status](https://coveralls.io/repos/boltdb/bolt/badge.svg?branch=master)](https://coveralls.io/r/boltdb/bolt?branch=master) [![GoDoc](https://godoc.org/github.com/boltdb/bolt?status.svg)](https://godoc.org/github.com/boltdb/bolt) ![Version](https://img.shields.io/badge/version-1.2.1-green.svg)
==== ====
Bolt is a pure Go key/value store inspired by [Howard Chu's][hyc_symas] Bolt is a pure Go key/value store inspired by [Howard Chu's][hyc_symas]
@ -313,7 +313,7 @@ func (s *Store) CreateUser(u *User) error {
// Generate ID for the user. // Generate ID for the user.
// This returns an error only if the Tx is closed or not writeable. // This returns an error only if the Tx is closed or not writeable.
// That can't happen in an Update() call so I ignore the error check. // That can't happen in an Update() call so I ignore the error check.
id, _ = b.NextSequence() id, _ := b.NextSequence()
u.ID = int(id) u.ID = int(id)
// Marshal user data into bytes. // Marshal user data into bytes.
@ -557,7 +557,7 @@ if err != nil {
Bolt is able to run on mobile devices by leveraging the binding feature of the Bolt is able to run on mobile devices by leveraging the binding feature of the
[gomobile](https://github.com/golang/mobile) tool. Create a struct that will [gomobile](https://github.com/golang/mobile) tool. Create a struct that will
contain your database logic and a reference to a `*bolt.DB` with a initializing contain your database logic and a reference to a `*bolt.DB` with a initializing
contstructor that takes in a filepath where the database file will be stored. constructor that takes in a filepath where the database file will be stored.
Neither Android nor iOS require extra permissions or cleanup from using this method. Neither Android nor iOS require extra permissions or cleanup from using this method.
```go ```go
@ -807,6 +807,7 @@ them via pull request.
Below is a list of public, open source projects that use Bolt: Below is a list of public, open source projects that use Bolt:
* [BoltDbWeb](https://github.com/evnix/boltdbweb) - A web based GUI for BoltDB files.
* [Operation Go: A Routine Mission](http://gocode.io) - An online programming game for Golang using Bolt for user accounts and a leaderboard. * [Operation Go: A Routine Mission](http://gocode.io) - An online programming game for Golang using Bolt for user accounts and a leaderboard.
* [Bazil](https://bazil.org/) - A file system that lets your data reside where it is most convenient for it to reside. * [Bazil](https://bazil.org/) - A file system that lets your data reside where it is most convenient for it to reside.
* [DVID](https://github.com/janelia-flyem/dvid) - Added Bolt as optional storage engine and testing it against Basho-tuned leveldb. * [DVID](https://github.com/janelia-flyem/dvid) - Added Bolt as optional storage engine and testing it against Basho-tuned leveldb.
@ -825,7 +826,6 @@ Below is a list of public, open source projects that use Bolt:
* [cayley](https://github.com/google/cayley) - Cayley is an open-source graph database using Bolt as optional backend. * [cayley](https://github.com/google/cayley) - Cayley is an open-source graph database using Bolt as optional backend.
* [bleve](http://www.blevesearch.com/) - A pure Go search engine similar to ElasticSearch that uses Bolt as the default storage backend. * [bleve](http://www.blevesearch.com/) - A pure Go search engine similar to ElasticSearch that uses Bolt as the default storage backend.
* [tentacool](https://github.com/optiflows/tentacool) - REST api server to manage system stuff (IP, DNS, Gateway...) on a linux server. * [tentacool](https://github.com/optiflows/tentacool) - REST api server to manage system stuff (IP, DNS, Gateway...) on a linux server.
* [SkyDB](https://github.com/skydb/sky) - Behavioral analytics database.
* [Seaweed File System](https://github.com/chrislusf/seaweedfs) - Highly scalable distributed key~file system with O(1) disk read. * [Seaweed File System](https://github.com/chrislusf/seaweedfs) - Highly scalable distributed key~file system with O(1) disk read.
* [InfluxDB](https://influxdata.com) - Scalable datastore for metrics, events, and real-time analytics. * [InfluxDB](https://influxdata.com) - Scalable datastore for metrics, events, and real-time analytics.
* [Freehold](http://tshannon.bitbucket.org/freehold/) - An open, secure, and lightweight platform for your files and data. * [Freehold](http://tshannon.bitbucket.org/freehold/) - An open, secure, and lightweight platform for your files and data.
@ -842,9 +842,11 @@ Below is a list of public, open source projects that use Bolt:
* [Go Report Card](https://goreportcard.com/) - Go code quality report cards as a (free and open source) service. * [Go Report Card](https://goreportcard.com/) - Go code quality report cards as a (free and open source) service.
* [Boltdb Boilerplate](https://github.com/bobintornado/boltdb-boilerplate) - Boilerplate wrapper around bolt aiming to make simple calls one-liners. * [Boltdb Boilerplate](https://github.com/bobintornado/boltdb-boilerplate) - Boilerplate wrapper around bolt aiming to make simple calls one-liners.
* [lru](https://github.com/crowdriff/lru) - Easy to use Bolt-backed Least-Recently-Used (LRU) read-through cache with chainable remote stores. * [lru](https://github.com/crowdriff/lru) - Easy to use Bolt-backed Least-Recently-Used (LRU) read-through cache with chainable remote stores.
* [Storm](https://github.com/asdine/storm) - A simple ORM around BoltDB. * [Storm](https://github.com/asdine/storm) - Simple and powerful ORM for BoltDB.
* [GoWebApp](https://github.com/josephspurrier/gowebapp) - A basic MVC web application in Go using BoltDB. * [GoWebApp](https://github.com/josephspurrier/gowebapp) - A basic MVC web application in Go using BoltDB.
* [SimpleBolt](https://github.com/xyproto/simplebolt) - A simple way to use BoltDB. Deals mainly with strings. * [SimpleBolt](https://github.com/xyproto/simplebolt) - A simple way to use BoltDB. Deals mainly with strings.
* [Algernon](https://github.com/xyproto/algernon) - A HTTP/2 web server with built-in support for Lua. Uses BoltDB as the default database backend. * [Algernon](https://github.com/xyproto/algernon) - A HTTP/2 web server with built-in support for Lua. Uses BoltDB as the default database backend.
* [MuLiFS](https://github.com/dankomiocevic/mulifs) - Music Library Filesystem creates a filesystem to organise your music files.
* [GoShort](https://github.com/pankajkhairnar/goShort) - GoShort is a URL shortener written in Golang and BoltDB for persistent key/value storage and for routing it's using high performent HTTPRouter.
If you are using Bolt in a project please send a pull request to add it to the list. If you are using Bolt in a project please send a pull request to add it to the list.

View File

@ -166,12 +166,16 @@ func (f *freelist) read(p *page) {
} }
// Copy the list of page ids from the freelist. // Copy the list of page ids from the freelist.
ids := ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[idx:count] if count == 0 {
f.ids = make([]pgid, len(ids)) f.ids = nil
copy(f.ids, ids) } else {
ids := ((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[idx:count]
f.ids = make([]pgid, len(ids))
copy(f.ids, ids)
// Make sure they're sorted. // Make sure they're sorted.
sort.Sort(pgids(f.ids)) sort.Sort(pgids(f.ids))
}
// Rebuild the page cache. // Rebuild the page cache.
f.reindex() f.reindex()
@ -189,7 +193,9 @@ func (f *freelist) write(p *page) error {
// The page.count can only hold up to 64k elements so if we overflow that // The page.count can only hold up to 64k elements so if we overflow that
// number then we handle it by putting the size in the first element. // number then we handle it by putting the size in the first element.
if len(ids) < 0xFFFF { if len(ids) == 0 {
p.count = uint16(len(ids))
} else if len(ids) < 0xFFFF {
p.count = uint16(len(ids)) p.count = uint16(len(ids))
copy(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[:], ids) copy(((*[maxAllocSize]pgid)(unsafe.Pointer(&p.ptr)))[:], ids)
} else { } else {

View File

@ -201,6 +201,11 @@ func (n *node) write(p *page) {
} }
p.count = uint16(len(n.inodes)) p.count = uint16(len(n.inodes))
// Stop here if there are no items to write.
if p.count == 0 {
return
}
// Loop over each item and write it to the page. // Loop over each item and write it to the page.
b := (*[maxAllocSize]byte)(unsafe.Pointer(&p.ptr))[n.pageElementSize()*len(n.inodes):] b := (*[maxAllocSize]byte)(unsafe.Pointer(&p.ptr))[n.pageElementSize()*len(n.inodes):]
for i, item := range n.inodes { for i, item := range n.inodes {

View File

@ -62,6 +62,9 @@ func (p *page) leafPageElement(index uint16) *leafPageElement {
// leafPageElements retrieves a list of leaf nodes. // leafPageElements retrieves a list of leaf nodes.
func (p *page) leafPageElements() []leafPageElement { func (p *page) leafPageElements() []leafPageElement {
if p.count == 0 {
return nil
}
return ((*[0x7FFFFFF]leafPageElement)(unsafe.Pointer(&p.ptr)))[:] return ((*[0x7FFFFFF]leafPageElement)(unsafe.Pointer(&p.ptr)))[:]
} }
@ -72,6 +75,9 @@ func (p *page) branchPageElement(index uint16) *branchPageElement {
// branchPageElements retrieves a list of branch nodes. // branchPageElements retrieves a list of branch nodes.
func (p *page) branchPageElements() []branchPageElement { func (p *page) branchPageElements() []branchPageElement {
if p.count == 0 {
return nil
}
return ((*[0x7FFFFFF]branchPageElement)(unsafe.Pointer(&p.ptr)))[:] return ((*[0x7FFFFFF]branchPageElement)(unsafe.Pointer(&p.ptr)))[:]
} }

View File

@ -2,30 +2,37 @@
package daemon package daemon
import ( import (
"errors"
"net" "net"
"os" "os"
) )
var SdNotifyNoSocket = errors.New("No socket")
// SdNotify sends a message to the init daemon. It is common to ignore the error. // SdNotify sends a message to the init daemon. It is common to ignore the error.
func SdNotify(state string) error { // It returns one of the following:
// (false, nil) - notification not supported (i.e. NOTIFY_SOCKET is unset)
// (false, err) - notification supported, but failure happened (e.g. error connecting to NOTIFY_SOCKET or while sending data)
// (true, nil) - notification supported, data has been sent
func SdNotify(state string) (sent bool, err error) {
socketAddr := &net.UnixAddr{ socketAddr := &net.UnixAddr{
Name: os.Getenv("NOTIFY_SOCKET"), Name: os.Getenv("NOTIFY_SOCKET"),
Net: "unixgram", Net: "unixgram",
} }
// NOTIFY_SOCKET not set
if socketAddr.Name == "" { if socketAddr.Name == "" {
return SdNotifyNoSocket return false, nil
} }
conn, err := net.DialUnix(socketAddr.Net, nil, socketAddr) conn, err := net.DialUnix(socketAddr.Net, nil, socketAddr)
// Error connecting to NOTIFY_SOCKET
if err != nil { if err != nil {
return err return false, err
} }
defer conn.Close() defer conn.Close()
_, err = conn.Write([]byte(state)) _, err = conn.Write([]byte(state))
return err // Error sending the message
if err != nil {
return false, err
}
return true, nil
} }

View File

@ -12,7 +12,14 @@
// See the License for the specific language governing permissions and // See the License for the specific language governing permissions and
// limitations under the License. // limitations under the License.
// Package journal provides write bindings to the systemd journal // Package journal provides write bindings to the local systemd journal.
// It is implemented in pure Go and connects to the journal directly over its
// unix socket.
//
// To read from the journal, see the "sdjournal" package, which wraps the
// sd-journal a C API.
//
// http://www.freedesktop.org/software/systemd/man/systemd-journald.service.html
package journal package journal
import ( import (
@ -53,14 +60,14 @@ func init() {
} }
} }
// Enabled returns true iff the systemd journal is available for logging // Enabled returns true if the local systemd journal is available for logging
func Enabled() bool { func Enabled() bool {
return conn != nil return conn != nil
} }
// Send a message to the systemd journal. vars is a map of journald fields to // Send a message to the local systemd journal. vars is a map of journald
// values. Fields must be composed of uppercase letters, numbers, and // fields to values. Fields must be composed of uppercase letters, numbers,
// underscores, but must not start with an underscore. Within these // and underscores, but must not start with an underscore. Within these
// restrictions, any arbitrary field name may be used. Some names have special // restrictions, any arbitrary field name may be used. Some names have special
// significance: see the journalctl documentation // significance: see the journalctl documentation
// (http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html) // (http://www.freedesktop.org/software/systemd/man/systemd.journal-fields.html)
@ -83,6 +90,7 @@ func Send(message string, priority Priority, vars map[string]string) error {
if err != nil { if err != nil {
return journalError(err.Error()) return journalError(err.Error())
} }
defer file.Close()
_, err = io.Copy(file, data) _, err = io.Copy(file, data)
if err != nil { if err != nil {
return journalError(err.Error()) return journalError(err.Error())
@ -102,6 +110,11 @@ func Send(message string, priority Priority, vars map[string]string) error {
return nil return nil
} }
// Print prints a message to the local systemd journal using Send().
func Print(priority Priority, format string, a ...interface{}) error {
return Send(fmt.Sprintf(format, a...), priority, nil)
}
func appendVariable(w io.Writer, name, value string) { func appendVariable(w io.Writer, name, value string) {
if !validVarName(name) { if !validVarName(name) {
journalError("variable name contains invalid character, ignoring") journalError("variable name contains invalid character, ignoring")

View File

@ -13,15 +13,66 @@
// limitations under the License. // limitations under the License.
// Package util contains utility functions related to systemd that applications // Package util contains utility functions related to systemd that applications
// can use to check things like whether systemd is running. // can use to check things like whether systemd is running. Note that some of
// these functions attempt to manually load systemd libraries at runtime rather
// than linking against them.
package util package util
// #include <stdlib.h>
// #include <sys/types.h>
// #include <unistd.h>
//
// int
// my_sd_pid_get_owner_uid(void *f, pid_t pid, uid_t *uid)
// {
// int (*sd_pid_get_owner_uid)(pid_t, uid_t *);
//
// sd_pid_get_owner_uid = (int (*)(pid_t, uid_t *))f;
// return sd_pid_get_owner_uid(pid, uid);
// }
//
// int
// my_sd_pid_get_unit(void *f, pid_t pid, char **unit)
// {
// int (*sd_pid_get_unit)(pid_t, char **);
//
// sd_pid_get_unit = (int (*)(pid_t, char **))f;
// return sd_pid_get_unit(pid, unit);
// }
//
// int
// my_sd_pid_get_slice(void *f, pid_t pid, char **slice)
// {
// int (*sd_pid_get_slice)(pid_t, char **);
//
// sd_pid_get_slice = (int (*)(pid_t, char **))f;
// return sd_pid_get_slice(pid, slice);
// }
//
// int
// am_session_leader()
// {
// return (getsid(0) == getpid());
// }
import ( import (
"fmt"
"io/ioutil"
"os" "os"
"strings"
) )
var libsystemdNames = []string{
// systemd < 209
"libsystemd-login.so.0",
"libsystemd-login.so",
// systemd >= 209 merged libsystemd-login into libsystemd proper
"libsystemd.so.0",
"libsystemd.so",
}
// IsRunningSystemd checks whether the host was booted with systemd as its init // IsRunningSystemd checks whether the host was booted with systemd as its init
// system. This functions similar to systemd's `sd_booted(3)`: internally, it // system. This functions similarly to systemd's `sd_booted(3)`: internally, it
// checks whether /run/systemd/system/ exists and is a directory. // checks whether /run/systemd/system/ exists and is a directory.
// http://www.freedesktop.org/software/systemd/man/sd_booted.html // http://www.freedesktop.org/software/systemd/man/sd_booted.html
func IsRunningSystemd() bool { func IsRunningSystemd() bool {
@ -31,3 +82,15 @@ func IsRunningSystemd() bool {
} }
return fi.IsDir() return fi.IsDir()
} }
// GetMachineID returns a host's 128-bit machine ID as a string. This functions
// similarly to systemd's `sd_id128_get_machine`: internally, it simply reads
// the contents of /etc/machine-id
// http://www.freedesktop.org/software/systemd/man/sd_id128_get_machine.html
func GetMachineID() (string, error) {
machineID, err := ioutil.ReadFile("/etc/machine-id")
if err != nil {
return "", fmt.Errorf("failed to read /etc/machine-id: %v", err)
}
return strings.TrimSpace(string(machineID)), nil
}

View File

@ -773,10 +773,11 @@ func (o *Buffer) dec_new_map(p *Properties, base structPointer) error {
} }
} }
keyelem, valelem := keyptr.Elem(), valptr.Elem() keyelem, valelem := keyptr.Elem(), valptr.Elem()
if !keyelem.IsValid() || !valelem.IsValid() { if !keyelem.IsValid() {
// We did not decode the key or the value in the map entry. keyelem = reflect.Zero(p.mtype.Key())
// Either way, it's an invalid map entry. }
return fmt.Errorf("proto: bad map data: missing key/val") if !valelem.IsValid() {
valelem = reflect.Zero(p.mtype.Elem())
} }
v.SetMapIndex(keyelem, valelem) v.SetMapIndex(keyelem, valelem)

View File

@ -64,6 +64,10 @@ var (
// a struct with a repeated field containing a nil element. // a struct with a repeated field containing a nil element.
errRepeatedHasNil = errors.New("proto: repeated field has nil element") errRepeatedHasNil = errors.New("proto: repeated field has nil element")
// errOneofHasNil is the error returned if Marshal is called with
// a struct with a oneof field containing a nil element.
errOneofHasNil = errors.New("proto: oneof field has nil value")
// ErrNil is the error returned if Marshal is called with nil. // ErrNil is the error returned if Marshal is called with nil.
ErrNil = errors.New("proto: Marshal called with nil") ErrNil = errors.New("proto: Marshal called with nil")
) )
@ -1222,7 +1226,9 @@ func (o *Buffer) enc_struct(prop *StructProperties, base structPointer) error {
// Do oneof fields. // Do oneof fields.
if prop.oneofMarshaler != nil { if prop.oneofMarshaler != nil {
m := structPointer_Interface(base, prop.stype).(Message) m := structPointer_Interface(base, prop.stype).(Message)
if err := prop.oneofMarshaler(m, o); err != nil { if err := prop.oneofMarshaler(m, o); err == ErrNil {
return errOneofHasNil
} else if err != nil {
return err return err
} }
} }

View File

@ -56,6 +56,10 @@ func (this *Extension) Equal(that *Extension) bool {
return bytes.Equal(this.enc, that.enc) return bytes.Equal(this.enc, that.enc)
} }
func (this *Extension) Compare(that *Extension) int {
return bytes.Compare(this.enc, that.enc)
}
func SizeOfExtensionMap(m map[int32]Extension) (n int) { func SizeOfExtensionMap(m map[int32]Extension) (n int) {
return sizeExtensionMap(m) return sizeExtensionMap(m)
} }

View File

@ -335,7 +335,8 @@ func writeStruct(w *textWriter, sv reflect.Value) error {
} }
inner := fv.Elem().Elem() // interface -> *T -> T inner := fv.Elem().Elem() // interface -> *T -> T
tag := inner.Type().Field(0).Tag.Get("protobuf") tag := inner.Type().Field(0).Tag.Get("protobuf")
props.Parse(tag) // Overwrite the outer props. props = new(Properties) // Overwrite the outer props var, but not its pointee.
props.Parse(tag)
// Write the value in the oneof, not the oneof itself. // Write the value in the oneof, not the oneof itself.
fv = inner.Field(0) fv = inner.Field(0)
@ -727,7 +728,14 @@ func (w *textWriter) writeIndent() {
w.complete = false w.complete = false
} }
func marshalText(w io.Writer, pb Message, compact bool) error { // TextMarshaler is a configurable text format marshaler.
type TextMarshaler struct {
Compact bool // use compact text format (one line).
}
// Marshal writes a given protocol buffer in text format.
// The only errors returned are from w.
func (m *TextMarshaler) Marshal(w io.Writer, pb Message) error {
val := reflect.ValueOf(pb) val := reflect.ValueOf(pb)
if pb == nil || val.IsNil() { if pb == nil || val.IsNil() {
w.Write([]byte("<nil>")) w.Write([]byte("<nil>"))
@ -742,7 +750,7 @@ func marshalText(w io.Writer, pb Message, compact bool) error {
aw := &textWriter{ aw := &textWriter{
w: ww, w: ww,
complete: true, complete: true,
compact: compact, compact: m.Compact,
} }
if tm, ok := pb.(encoding.TextMarshaler); ok { if tm, ok := pb.(encoding.TextMarshaler); ok {
@ -769,25 +777,29 @@ func marshalText(w io.Writer, pb Message, compact bool) error {
return nil return nil
} }
// Text is the same as Marshal, but returns the string directly.
func (m *TextMarshaler) Text(pb Message) string {
var buf bytes.Buffer
m.Marshal(&buf, pb)
return buf.String()
}
var (
defaultTextMarshaler = TextMarshaler{}
compactTextMarshaler = TextMarshaler{Compact: true}
)
// TODO: consider removing some of the Marshal functions below.
// MarshalText writes a given protocol buffer in text format. // MarshalText writes a given protocol buffer in text format.
// The only errors returned are from w. // The only errors returned are from w.
func MarshalText(w io.Writer, pb Message) error { func MarshalText(w io.Writer, pb Message) error { return defaultTextMarshaler.Marshal(w, pb) }
return marshalText(w, pb, false)
}
// MarshalTextString is the same as MarshalText, but returns the string directly. // MarshalTextString is the same as MarshalText, but returns the string directly.
func MarshalTextString(pb Message) string { func MarshalTextString(pb Message) string { return defaultTextMarshaler.Text(pb) }
var buf bytes.Buffer
marshalText(&buf, pb, false)
return buf.String()
}
// CompactText writes a given protocol buffer in compact text format (one line). // CompactText writes a given protocol buffer in compact text format (one line).
func CompactText(w io.Writer, pb Message) error { return marshalText(w, pb, true) } func CompactText(w io.Writer, pb Message) error { return compactTextMarshaler.Marshal(w, pb) }
// CompactTextString is the same as CompactText, but returns the string directly. // CompactTextString is the same as CompactText, but returns the string directly.
func CompactTextString(pb Message) string { func CompactTextString(pb Message) string { return compactTextMarshaler.Text(pb) }
var buf bytes.Buffer
marshalText(&buf, pb, true)
return buf.String()
}

View File

@ -104,6 +104,7 @@ func DefaultHTTPError(ctx context.Context, marshaler Marshaler, w http.ResponseW
} }
handleForwardResponseServerMetadata(w, md) handleForwardResponseServerMetadata(w, md)
handleForwardResponseTrailerHeader(w, md)
st := HTTPStatusFromCode(grpc.Code(err)) st := HTTPStatusFromCode(grpc.Code(err))
w.WriteHeader(st) w.WriteHeader(st)
if _, err := w.Write(buf); err != nil { if _, err := w.Write(buf); err != nil {

View File

@ -6,8 +6,8 @@ import (
"net/http" "net/http"
"net/textproto" "net/textproto"
"github.com/gengo/grpc-gateway/runtime/internal"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/grpc-ecosystem/grpc-gateway/runtime/internal"
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc" "google.golang.org/grpc"
"google.golang.org/grpc/grpclog" "google.golang.org/grpc/grpclog"

View File

@ -43,23 +43,23 @@ func (*StreamError) ProtoMessage() {}
func (*StreamError) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} } func (*StreamError) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func init() { func init() {
proto.RegisterType((*StreamError)(nil), "gengo.grpc.gateway.runtime.StreamError") proto.RegisterType((*StreamError)(nil), "grpc.gateway.runtime.StreamError")
} }
func init() { proto.RegisterFile("runtime/internal/stream_chunk.proto", fileDescriptor0) } func init() { proto.RegisterFile("runtime/internal/stream_chunk.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{ var fileDescriptor0 = []byte{
// 182 bytes of a gzipped FileDescriptorProto // 180 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0x34, 0x8e, 0x3d, 0xef, 0x82, 0x30, 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x09, 0x6e, 0x88, 0x02, 0xff, 0xe2, 0x52, 0x2e, 0x2a, 0xcd, 0x2b,
0x10, 0x87, 0xc3, 0xff, 0x15, 0x8e, 0x8d, 0xa9, 0xd1, 0x41, 0xa3, 0x8b, 0x53, 0x19, 0xfc, 0x06, 0xc9, 0xcc, 0x4d, 0xd5, 0xcf, 0xcc, 0x2b, 0x49, 0x2d, 0xca, 0x4b, 0xcc, 0xd1, 0x2f, 0x2e, 0x29,
0x1a, 0xbf, 0x00, 0x6c, 0x2e, 0xa4, 0xe2, 0xa5, 0x10, 0xa5, 0x25, 0xd7, 0x23, 0xc6, 0xd5, 0x4f, 0x4a, 0x4d, 0xcc, 0x8d, 0x4f, 0xce, 0x28, 0xcd, 0xcb, 0xd6, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17,
0x2e, 0x2d, 0x32, 0xde, 0xf3, 0xdc, 0x93, 0xfc, 0x60, 0x4b, 0x83, 0xe1, 0xb6, 0xc3, 0xbc, 0x35, 0x12, 0x49, 0x2f, 0x2a, 0x48, 0xd6, 0x4b, 0x4f, 0x2c, 0x49, 0x2d, 0x4f, 0xac, 0xd4, 0x83, 0xea,
0x8c, 0x64, 0xd4, 0x3d, 0x77, 0x4c, 0xa8, 0xba, 0xaa, 0x6e, 0x06, 0x73, 0x93, 0x3d, 0x59, 0xb6, 0x50, 0x6a, 0x62, 0xe4, 0xe2, 0x0e, 0x06, 0x2b, 0x76, 0x2d, 0x2a, 0xca, 0x2f, 0x12, 0x92, 0xe6,
0xd9, 0x42, 0xa3, 0xd1, 0x56, 0x6a, 0xea, 0x6b, 0xa9, 0x15, 0xe3, 0x43, 0x3d, 0xe5, 0xa7, 0xdb, 0xe2, 0x04, 0xa9, 0x8b, 0x4f, 0xce, 0x4f, 0x49, 0x95, 0x60, 0x54, 0x60, 0xd4, 0x60, 0x0d, 0xe2,
0xbc, 0x22, 0x48, 0xcb, 0x90, 0x9c, 0x88, 0x2c, 0x65, 0x4b, 0x48, 0xfc, 0x5f, 0x55, 0xdb, 0x2b, 0x00, 0x09, 0x38, 0x03, 0xf9, 0x20, 0xc9, 0x8c, 0x92, 0x92, 0x02, 0x88, 0x24, 0x13, 0x44, 0x12,
0x8a, 0x68, 0x1d, 0xed, 0x7e, 0x8b, 0xd8, 0x83, 0xe3, 0x78, 0x7b, 0xd9, 0x30, 0xf7, 0x93, 0xfc, 0x24, 0x00, 0x96, 0x94, 0xe0, 0x62, 0xcf, 0x4d, 0x2d, 0x2e, 0x4e, 0x4c, 0x4f, 0x95, 0x60, 0x06,
0x9a, 0xa4, 0x07, 0x41, 0x0a, 0xf8, 0xef, 0xd0, 0x39, 0xa5, 0x51, 0x7c, 0x8f, 0x2a, 0x29, 0xe6, 0x4a, 0x71, 0x06, 0xc1, 0xb8, 0x42, 0xf2, 0x5c, 0xdc, 0x60, 0x6d, 0xc5, 0x25, 0x89, 0x25, 0xa5,
0x33, 0x5b, 0x41, 0x1a, 0x32, 0xc7, 0x8a, 0x07, 0x27, 0x7e, 0x82, 0x05, 0x8f, 0xca, 0x40, 0x0e, 0xc5, 0x12, 0x2c, 0x60, 0x59, 0x2e, 0x90, 0x50, 0x30, 0x58, 0xc4, 0x89, 0x2b, 0x8a, 0x03, 0xe6,
0x70, 0x8e, 0xe7, 0xfd, 0x97, 0xbf, 0xb0, 0x79, 0xff, 0x0e, 0x00, 0x00, 0xff, 0xff, 0xf8, 0x7f, 0xf2, 0x24, 0x36, 0xb0, 0x6b, 0x8d, 0x01, 0x01, 0x00, 0x00, 0xff, 0xff, 0xa9, 0x07, 0x92, 0xb6,
0x7d, 0x56, 0xda, 0x00, 0x00, 0x00, 0xd4, 0x00, 0x00, 0x00,
} }

View File

@ -1,5 +1,5 @@
syntax = "proto3"; syntax = "proto3";
package gengo.grpc.gateway.runtime; package grpc.gateway.runtime;
option go_package = "internal"; option go_package = "internal";
// StreamError is a response type which is returned when // StreamError is a response type which is returned when

View File

@ -5,7 +5,7 @@ import (
"fmt" "fmt"
"strings" "strings"
"github.com/gengo/grpc-gateway/utilities" "github.com/grpc-ecosystem/grpc-gateway/utilities"
"google.golang.org/grpc/grpclog" "google.golang.org/grpc/grpclog"
) )

View File

@ -6,8 +6,8 @@ import (
"reflect" "reflect"
"strings" "strings"
"github.com/gengo/grpc-gateway/utilities"
"github.com/golang/protobuf/proto" "github.com/golang/protobuf/proto"
"github.com/grpc-ecosystem/grpc-gateway/utilities"
"google.golang.org/grpc/grpclog" "google.golang.org/grpc/grpclog"
) )

View File

@ -1,17 +1,21 @@
language: go language: go
go: go:
- 1.5.3 - 1.5.4
- 1.6 - 1.6.3
go_import_path: google.golang.org/grpc
before_install: before_install:
- go get golang.org/x/tools/cmd/goimports
- go get github.com/golang/lint/golint
- go get github.com/axw/gocov/gocov - go get github.com/axw/gocov/gocov
- go get github.com/mattn/goveralls - go get github.com/mattn/goveralls
- go get golang.org/x/tools/cmd/cover - go get golang.org/x/tools/cmd/cover
install:
- mkdir -p "$GOPATH/src/google.golang.org"
- mv "$TRAVIS_BUILD_DIR" "$GOPATH/src/google.golang.org/grpc"
script: script:
- '! gofmt -s -d -l . 2>&1 | read'
- '! goimports -l . | read'
- '! golint ./... | grep -vE "(_string|\.pb)\.go:"'
- '! go tool vet -all . 2>&1 | grep -vE "constant [0-9]+ not a string in call to Errorf"'
- make test testrace - make test testrace

View File

@ -40,7 +40,6 @@ import (
"golang.org/x/net/context" "golang.org/x/net/context"
"google.golang.org/grpc/grpclog" "google.golang.org/grpc/grpclog"
"google.golang.org/grpc/naming" "google.golang.org/grpc/naming"
"google.golang.org/grpc/transport"
) )
// Address represents a server the client connects to. // Address represents a server the client connects to.
@ -94,10 +93,10 @@ type Balancer interface {
// instead of blocking. // instead of blocking.
// //
// The function returns put which is called once the rpc has completed or failed. // The function returns put which is called once the rpc has completed or failed.
// put can collect and report RPC stats to a remote load balancer. gRPC internals // put can collect and report RPC stats to a remote load balancer.
// will try to call this again if err is non-nil (unless err is ErrClientConnClosing).
// //
// TODO: Add other non-recoverable errors? // This function should only return the errors Balancer cannot recover by itself.
// gRPC internals will fail the RPC if an error is returned.
Get(ctx context.Context, opts BalancerGetOptions) (addr Address, put func(), err error) Get(ctx context.Context, opts BalancerGetOptions) (addr Address, put func(), err error)
// Notify returns a channel that is used by gRPC internals to watch the addresses // Notify returns a channel that is used by gRPC internals to watch the addresses
// gRPC needs to connect. The addresses might be from a name resolver or remote // gRPC needs to connect. The addresses might be from a name resolver or remote
@ -139,35 +138,40 @@ func RoundRobin(r naming.Resolver) Balancer {
return &roundRobin{r: r} return &roundRobin{r: r}
} }
type addrInfo struct {
addr Address
connected bool
}
type roundRobin struct { type roundRobin struct {
r naming.Resolver r naming.Resolver
w naming.Watcher w naming.Watcher
open []Address // all the addresses the client should potentially connect addrs []*addrInfo // all the addresses the client should potentially connect
mu sync.Mutex mu sync.Mutex
addrCh chan []Address // the channel to notify gRPC internals the list of addresses the client should connect to. addrCh chan []Address // the channel to notify gRPC internals the list of addresses the client should connect to.
connected []Address // all the connected addresses next int // index of the next address to return for Get()
next int // index of the next address to return for Get() waitCh chan struct{} // the channel to block when there is no connected address available
waitCh chan struct{} // the channel to block when there is no connected address available done bool // The Balancer is closed.
done bool // The Balancer is closed.
} }
func (rr *roundRobin) watchAddrUpdates() error { func (rr *roundRobin) watchAddrUpdates() error {
updates, err := rr.w.Next() updates, err := rr.w.Next()
if err != nil { if err != nil {
grpclog.Println("grpc: the naming watcher stops working due to %v.", err) grpclog.Printf("grpc: the naming watcher stops working due to %v.\n", err)
return err return err
} }
rr.mu.Lock() rr.mu.Lock()
defer rr.mu.Unlock() defer rr.mu.Unlock()
for _, update := range updates { for _, update := range updates {
addr := Address{ addr := Address{
Addr: update.Addr, Addr: update.Addr,
Metadata: update.Metadata,
} }
switch update.Op { switch update.Op {
case naming.Add: case naming.Add:
var exist bool var exist bool
for _, v := range rr.open { for _, v := range rr.addrs {
if addr == v { if addr == v.addr {
exist = true exist = true
grpclog.Println("grpc: The name resolver wanted to add an existing address: ", addr) grpclog.Println("grpc: The name resolver wanted to add an existing address: ", addr)
break break
@ -176,12 +180,12 @@ func (rr *roundRobin) watchAddrUpdates() error {
if exist { if exist {
continue continue
} }
rr.open = append(rr.open, addr) rr.addrs = append(rr.addrs, &addrInfo{addr: addr})
case naming.Delete: case naming.Delete:
for i, v := range rr.open { for i, v := range rr.addrs {
if v == addr { if addr == v.addr {
copy(rr.open[i:], rr.open[i+1:]) copy(rr.addrs[i:], rr.addrs[i+1:])
rr.open = rr.open[:len(rr.open)-1] rr.addrs = rr.addrs[:len(rr.addrs)-1]
break break
} }
} }
@ -189,9 +193,11 @@ func (rr *roundRobin) watchAddrUpdates() error {
grpclog.Println("Unknown update.Op ", update.Op) grpclog.Println("Unknown update.Op ", update.Op)
} }
} }
// Make a copy of rr.open and write it onto rr.addrCh so that gRPC internals gets notified. // Make a copy of rr.addrs and write it onto rr.addrCh so that gRPC internals gets notified.
open := make([]Address, len(rr.open), len(rr.open)) open := make([]Address, len(rr.addrs))
copy(open, rr.open) for i, v := range rr.addrs {
open[i] = v.addr
}
if rr.done { if rr.done {
return ErrClientConnClosing return ErrClientConnClosing
} }
@ -202,7 +208,9 @@ func (rr *roundRobin) watchAddrUpdates() error {
func (rr *roundRobin) Start(target string) error { func (rr *roundRobin) Start(target string) error {
if rr.r == nil { if rr.r == nil {
// If there is no name resolver installed, it is not needed to // If there is no name resolver installed, it is not needed to
// do name resolution. In this case, rr.addrCh stays nil. // do name resolution. In this case, target is added into rr.addrs
// as the only address available and rr.addrCh stays nil.
rr.addrs = append(rr.addrs, &addrInfo{addr: Address{Addr: target}})
return nil return nil
} }
w, err := rr.r.Resolve(target) w, err := rr.r.Resolve(target)
@ -221,38 +229,41 @@ func (rr *roundRobin) Start(target string) error {
return nil return nil
} }
// Up appends addr to the end of rr.connected and sends notification if there // Up sets the connected state of addr and sends notification if there are pending
// are pending Get() calls. // Get() calls.
func (rr *roundRobin) Up(addr Address) func(error) { func (rr *roundRobin) Up(addr Address) func(error) {
rr.mu.Lock() rr.mu.Lock()
defer rr.mu.Unlock() defer rr.mu.Unlock()
for _, a := range rr.connected { var cnt int
if a == addr { for _, a := range rr.addrs {
return nil if a.addr == addr {
if a.connected {
return nil
}
a.connected = true
}
if a.connected {
cnt++
} }
} }
rr.connected = append(rr.connected, addr) // addr is only one which is connected. Notify the Get() callers who are blocking.
if len(rr.connected) == 1 { if cnt == 1 && rr.waitCh != nil {
// addr is only one available. Notify the Get() callers who are blocking. close(rr.waitCh)
if rr.waitCh != nil { rr.waitCh = nil
close(rr.waitCh)
rr.waitCh = nil
}
} }
return func(err error) { return func(err error) {
rr.down(addr, err) rr.down(addr, err)
} }
} }
// down removes addr from rr.connected and moves the remaining addrs forward. // down unsets the connected state of addr.
func (rr *roundRobin) down(addr Address, err error) { func (rr *roundRobin) down(addr Address, err error) {
rr.mu.Lock() rr.mu.Lock()
defer rr.mu.Unlock() defer rr.mu.Unlock()
for i, a := range rr.connected { for _, a := range rr.addrs {
if a == addr { if addr == a.addr {
copy(rr.connected[i:], rr.connected[i+1:]) a.connected = false
rr.connected = rr.connected[:len(rr.connected)-1] break
return
} }
} }
} }
@ -266,17 +277,40 @@ func (rr *roundRobin) Get(ctx context.Context, opts BalancerGetOptions) (addr Ad
err = ErrClientConnClosing err = ErrClientConnClosing
return return
} }
if rr.next >= len(rr.connected) {
rr.next = 0 if len(rr.addrs) > 0 {
if rr.next >= len(rr.addrs) {
rr.next = 0
}
next := rr.next
for {
a := rr.addrs[next]
next = (next + 1) % len(rr.addrs)
if a.connected {
addr = a.addr
rr.next = next
rr.mu.Unlock()
return
}
if next == rr.next {
// Has iterated all the possible address but none is connected.
break
}
}
} }
if len(rr.connected) > 0 { if !opts.BlockingWait {
addr = rr.connected[rr.next] if len(rr.addrs) == 0 {
rr.mu.Unlock()
err = fmt.Errorf("there is no address available")
return
}
// Returns the next addr on rr.addrs for failfast RPCs.
addr = rr.addrs[rr.next].addr
rr.next++ rr.next++
rr.mu.Unlock() rr.mu.Unlock()
return return
} }
// There is no address available. Wait on rr.waitCh. // Wait on rr.waitCh for non-failfast RPCs.
// TODO(zhaoq): Handle the case when opts.BlockingWait is false.
if rr.waitCh == nil { if rr.waitCh == nil {
ch = make(chan struct{}) ch = make(chan struct{})
rr.waitCh = ch rr.waitCh = ch
@ -287,7 +321,7 @@ func (rr *roundRobin) Get(ctx context.Context, opts BalancerGetOptions) (addr Ad
for { for {
select { select {
case <-ctx.Done(): case <-ctx.Done():
err = transport.ContextErr(ctx.Err()) err = ctx.Err()
return return
case <-ch: case <-ch:
rr.mu.Lock() rr.mu.Lock()
@ -296,24 +330,35 @@ func (rr *roundRobin) Get(ctx context.Context, opts BalancerGetOptions) (addr Ad
err = ErrClientConnClosing err = ErrClientConnClosing
return return
} }
if len(rr.connected) == 0 {
// The newly added addr got removed by Down() again. if len(rr.addrs) > 0 {
if rr.waitCh == nil { if rr.next >= len(rr.addrs) {
ch = make(chan struct{}) rr.next = 0
rr.waitCh = ch }
} else { next := rr.next
ch = rr.waitCh for {
a := rr.addrs[next]
next = (next + 1) % len(rr.addrs)
if a.connected {
addr = a.addr
rr.next = next
rr.mu.Unlock()
return
}
if next == rr.next {
// Has iterated all the possible address but none is connected.
break
}
} }
rr.mu.Unlock()
continue
} }
if rr.next >= len(rr.connected) { // The newly added addr got removed by Down() again.
rr.next = 0 if rr.waitCh == nil {
ch = make(chan struct{})
rr.waitCh = ch
} else {
ch = rr.waitCh
} }
addr = rr.connected[rr.next]
rr.next++
rr.mu.Unlock() rr.mu.Unlock()
return
} }
} }
} }

View File

@ -36,6 +36,7 @@ package grpc
import ( import (
"bytes" "bytes"
"io" "io"
"math"
"time" "time"
"golang.org/x/net/context" "golang.org/x/net/context"
@ -51,13 +52,20 @@ import (
func recvResponse(dopts dialOptions, t transport.ClientTransport, c *callInfo, stream *transport.Stream, reply interface{}) error { func recvResponse(dopts dialOptions, t transport.ClientTransport, c *callInfo, stream *transport.Stream, reply interface{}) error {
// Try to acquire header metadata from the server if there is any. // Try to acquire header metadata from the server if there is any.
var err error var err error
defer func() {
if err != nil {
if _, ok := err.(transport.ConnectionError); !ok {
t.CloseStream(stream, err)
}
}
}()
c.headerMD, err = stream.Header() c.headerMD, err = stream.Header()
if err != nil { if err != nil {
return err return err
} }
p := &parser{r: stream} p := &parser{r: stream}
for { for {
if err = recv(p, dopts.codec, stream, dopts.dc, reply); err != nil { if err = recv(p, dopts.codec, stream, dopts.dc, reply, math.MaxInt32); err != nil {
if err == io.EOF { if err == io.EOF {
break break
} }
@ -76,6 +84,7 @@ func sendRequest(ctx context.Context, codec Codec, compressor Compressor, callHd
} }
defer func() { defer func() {
if err != nil { if err != nil {
// If err is connection error, t will be closed, no need to close stream here.
if _, ok := err.(transport.ConnectionError); !ok { if _, ok := err.(transport.ConnectionError); !ok {
t.CloseStream(stream, err) t.CloseStream(stream, err)
} }
@ -90,7 +99,10 @@ func sendRequest(ctx context.Context, codec Codec, compressor Compressor, callHd
return nil, transport.StreamErrorf(codes.Internal, "grpc: %v", err) return nil, transport.StreamErrorf(codes.Internal, "grpc: %v", err)
} }
err = t.Write(stream, outBuf, opts) err = t.Write(stream, outBuf, opts)
if err != nil { // t.NewStream(...) could lead to an early rejection of the RPC (e.g., the service/method
// does not exist.) so that t.Write could get io.EOF from wait(...). Leave the following
// recvResponse to get the final status.
if err != nil && err != io.EOF {
return nil, err return nil, err
} }
// Sent successfully. // Sent successfully.
@ -101,7 +113,7 @@ func sendRequest(ctx context.Context, codec Codec, compressor Compressor, callHd
// Invoke is called by generated code. Also users can call Invoke directly when it // Invoke is called by generated code. Also users can call Invoke directly when it
// is really needed in their use cases. // is really needed in their use cases.
func Invoke(ctx context.Context, method string, args, reply interface{}, cc *ClientConn, opts ...CallOption) (err error) { func Invoke(ctx context.Context, method string, args, reply interface{}, cc *ClientConn, opts ...CallOption) (err error) {
var c callInfo c := defaultCallInfo
for _, o := range opts { for _, o := range opts {
if err := o.before(&c); err != nil { if err := o.before(&c); err != nil {
return toRPCErr(err) return toRPCErr(err)
@ -155,19 +167,17 @@ func Invoke(ctx context.Context, method string, args, reply interface{}, cc *Cli
t, put, err = cc.getTransport(ctx, gopts) t, put, err = cc.getTransport(ctx, gopts)
if err != nil { if err != nil {
// TODO(zhaoq): Probably revisit the error handling. // TODO(zhaoq): Probably revisit the error handling.
if err == ErrClientConnClosing { if _, ok := err.(*rpcError); ok {
return Errorf(codes.FailedPrecondition, "%v", err) return err
} }
if _, ok := err.(transport.StreamError); ok { if err == errConnClosing || err == errConnUnavailable {
return toRPCErr(err)
}
if _, ok := err.(transport.ConnectionError); ok {
if c.failFast { if c.failFast {
return toRPCErr(err) return Errorf(codes.Unavailable, "%v", err)
} }
continue
} }
// All the remaining cases are treated as retryable. // All the other errors are treated as Internal errors.
continue return Errorf(codes.Internal, "%v", err)
} }
if c.traceInfo.tr != nil { if c.traceInfo.tr != nil {
c.traceInfo.tr.LazyLog(&payload{sent: true, msg: args}, true) c.traceInfo.tr.LazyLog(&payload{sent: true, msg: args}, true)
@ -178,7 +188,10 @@ func Invoke(ctx context.Context, method string, args, reply interface{}, cc *Cli
put() put()
put = nil put = nil
} }
if _, ok := err.(transport.ConnectionError); ok { // Retry a non-failfast RPC when
// i) there is a connection error; or
// ii) the server started to drain before this RPC was initiated.
if _, ok := err.(transport.ConnectionError); ok || err == transport.ErrStreamDrain {
if c.failFast { if c.failFast {
return toRPCErr(err) return toRPCErr(err)
} }
@ -186,20 +199,18 @@ func Invoke(ctx context.Context, method string, args, reply interface{}, cc *Cli
} }
return toRPCErr(err) return toRPCErr(err)
} }
// Receive the response
err = recvResponse(cc.dopts, t, &c, stream, reply) err = recvResponse(cc.dopts, t, &c, stream, reply)
if err != nil { if err != nil {
if put != nil { if put != nil {
put() put()
put = nil put = nil
} }
if _, ok := err.(transport.ConnectionError); ok { if _, ok := err.(transport.ConnectionError); ok || err == transport.ErrStreamDrain {
if c.failFast { if c.failFast {
return toRPCErr(err) return toRPCErr(err)
} }
continue continue
} }
t.CloseStream(stream, err)
return toRPCErr(err) return toRPCErr(err)
} }
if c.traceInfo.tr != nil { if c.traceInfo.tr != nil {

View File

@ -43,7 +43,6 @@ import (
"golang.org/x/net/context" "golang.org/x/net/context"
"golang.org/x/net/trace" "golang.org/x/net/trace"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/credentials" "google.golang.org/grpc/credentials"
"google.golang.org/grpc/grpclog" "google.golang.org/grpc/grpclog"
"google.golang.org/grpc/transport" "google.golang.org/grpc/transport"
@ -68,13 +67,15 @@ var (
// errCredentialsConflict indicates that grpc.WithTransportCredentials() // errCredentialsConflict indicates that grpc.WithTransportCredentials()
// and grpc.WithInsecure() are both called for a connection. // and grpc.WithInsecure() are both called for a connection.
errCredentialsConflict = errors.New("grpc: transport credentials are set for an insecure connection (grpc.WithTransportCredentials() and grpc.WithInsecure() are both called)") errCredentialsConflict = errors.New("grpc: transport credentials are set for an insecure connection (grpc.WithTransportCredentials() and grpc.WithInsecure() are both called)")
// errNetworkIP indicates that the connection is down due to some network I/O error. // errNetworkIO indicates that the connection is down due to some network I/O error.
errNetworkIO = errors.New("grpc: failed with network I/O error") errNetworkIO = errors.New("grpc: failed with network I/O error")
// errConnDrain indicates that the connection starts to be drained and does not accept any new RPCs. // errConnDrain indicates that the connection starts to be drained and does not accept any new RPCs.
errConnDrain = errors.New("grpc: the connection is drained") errConnDrain = errors.New("grpc: the connection is drained")
// errConnClosing indicates that the connection is closing. // errConnClosing indicates that the connection is closing.
errConnClosing = errors.New("grpc: the connection is closing") errConnClosing = errors.New("grpc: the connection is closing")
errNoAddr = errors.New("grpc: there is no address available to dial") // errConnUnavailable indicates that the connection is unavailable.
errConnUnavailable = errors.New("grpc: the connection is unavailable")
errNoAddr = errors.New("grpc: there is no address available to dial")
// minimum time to give a connection to complete // minimum time to give a connection to complete
minConnectTimeout = 20 * time.Second minConnectTimeout = 20 * time.Second
) )
@ -196,9 +197,14 @@ func WithTimeout(d time.Duration) DialOption {
} }
// WithDialer returns a DialOption that specifies a function to use for dialing network addresses. // WithDialer returns a DialOption that specifies a function to use for dialing network addresses.
func WithDialer(f func(addr string, timeout time.Duration) (net.Conn, error)) DialOption { func WithDialer(f func(string, time.Duration) (net.Conn, error)) DialOption {
return func(o *dialOptions) { return func(o *dialOptions) {
o.copts.Dialer = f o.copts.Dialer = func(ctx context.Context, addr string) (net.Conn, error) {
if deadline, ok := ctx.Deadline(); ok {
return f(addr, deadline.Sub(time.Now()))
}
return f(addr, 0)
}
} }
} }
@ -209,49 +215,57 @@ func WithUserAgent(s string) DialOption {
} }
} }
// Dial creates a client connection the given target. // Dial creates a client connection to the given target.
func Dial(target string, opts ...DialOption) (*ClientConn, error) { func Dial(target string, opts ...DialOption) (*ClientConn, error) {
return DialContext(context.Background(), target, opts...)
}
// DialContext creates a client connection to the given target
// using the supplied context.
func DialContext(ctx context.Context, target string, opts ...DialOption) (*ClientConn, error) {
cc := &ClientConn{ cc := &ClientConn{
target: target, target: target,
conns: make(map[Address]*addrConn), conns: make(map[Address]*addrConn),
} }
cc.ctx, cc.cancel = context.WithCancel(ctx)
for _, opt := range opts { for _, opt := range opts {
opt(&cc.dopts) opt(&cc.dopts)
} }
// Set defaults.
if cc.dopts.codec == nil { if cc.dopts.codec == nil {
// Set the default codec.
cc.dopts.codec = protoCodec{} cc.dopts.codec = protoCodec{}
} }
if cc.dopts.bs == nil { if cc.dopts.bs == nil {
cc.dopts.bs = DefaultBackoffConfig cc.dopts.bs = DefaultBackoffConfig
} }
cc.balancer = cc.dopts.balancer
if cc.balancer == nil {
cc.balancer = RoundRobin(nil)
}
if err := cc.balancer.Start(target); err != nil {
return nil, err
}
var ( var (
ok bool ok bool
addrs []Address addrs []Address
) )
ch := cc.balancer.Notify() if cc.dopts.balancer == nil {
if ch == nil { // Connect to target directly if balancer is nil.
// There is no name resolver installed.
addrs = append(addrs, Address{Addr: target}) addrs = append(addrs, Address{Addr: target})
} else { } else {
addrs, ok = <-ch if err := cc.dopts.balancer.Start(target); err != nil {
if !ok || len(addrs) == 0 { return nil, err
return nil, errNoAddr }
ch := cc.dopts.balancer.Notify()
if ch == nil {
// There is no name resolver installed.
addrs = append(addrs, Address{Addr: target})
} else {
addrs, ok = <-ch
if !ok || len(addrs) == 0 {
return nil, errNoAddr
}
} }
} }
waitC := make(chan error, 1) waitC := make(chan error, 1)
go func() { go func() {
for _, a := range addrs { for _, a := range addrs {
if err := cc.newAddrConn(a, false); err != nil { if err := cc.resetAddrConn(a, false, nil); err != nil {
waitC <- err waitC <- err
return return
} }
@ -268,10 +282,15 @@ func Dial(target string, opts ...DialOption) (*ClientConn, error) {
cc.Close() cc.Close()
return nil, err return nil, err
} }
case <-cc.ctx.Done():
cc.Close()
return nil, cc.ctx.Err()
case <-timeoutCh: case <-timeoutCh:
cc.Close() cc.Close()
return nil, ErrClientConnTimeout return nil, ErrClientConnTimeout
} }
// If balancer is nil or balancer.Notify() is nil, ok will be false here.
// The lbWatcher goroutine will not be created.
if ok { if ok {
go cc.lbWatcher() go cc.lbWatcher()
} }
@ -318,8 +337,10 @@ func (s ConnectivityState) String() string {
// ClientConn represents a client connection to an RPC server. // ClientConn represents a client connection to an RPC server.
type ClientConn struct { type ClientConn struct {
ctx context.Context
cancel context.CancelFunc
target string target string
balancer Balancer
authority string authority string
dopts dialOptions dopts dialOptions
@ -328,7 +349,7 @@ type ClientConn struct {
} }
func (cc *ClientConn) lbWatcher() { func (cc *ClientConn) lbWatcher() {
for addrs := range cc.balancer.Notify() { for addrs := range cc.dopts.balancer.Notify() {
var ( var (
add []Address // Addresses need to setup connections. add []Address // Addresses need to setup connections.
del []*addrConn // Connections need to tear down. del []*addrConn // Connections need to tear down.
@ -349,11 +370,12 @@ func (cc *ClientConn) lbWatcher() {
} }
if !keep { if !keep {
del = append(del, c) del = append(del, c)
delete(cc.conns, c.addr)
} }
} }
cc.mu.Unlock() cc.mu.Unlock()
for _, a := range add { for _, a := range add {
cc.newAddrConn(a, true) cc.resetAddrConn(a, true, nil)
} }
for _, c := range del { for _, c := range del {
c.tearDown(errConnDrain) c.tearDown(errConnDrain)
@ -361,13 +383,17 @@ func (cc *ClientConn) lbWatcher() {
} }
} }
func (cc *ClientConn) newAddrConn(addr Address, skipWait bool) error { // resetAddrConn creates an addrConn for addr and adds it to cc.conns.
// If there is an old addrConn for addr, it will be torn down, using tearDownErr as the reason.
// If tearDownErr is nil, errConnDrain will be used instead.
func (cc *ClientConn) resetAddrConn(addr Address, skipWait bool, tearDownErr error) error {
ac := &addrConn{ ac := &addrConn{
cc: cc, cc: cc,
addr: addr, addr: addr,
dopts: cc.dopts, dopts: cc.dopts,
shutdownChan: make(chan struct{}),
} }
ac.ctx, ac.cancel = context.WithCancel(cc.ctx)
ac.stateCV = sync.NewCond(&ac.mu)
if EnableTracing { if EnableTracing {
ac.events = trace.NewEventLog("grpc.ClientConn", ac.addr.Addr) ac.events = trace.NewEventLog("grpc.ClientConn", ac.addr.Addr)
} }
@ -385,26 +411,44 @@ func (cc *ClientConn) newAddrConn(addr Address, skipWait bool) error {
} }
} }
} }
// Insert ac into ac.cc.conns. This needs to be done before any getTransport(...) is called. // Track ac in cc. This needs to be done before any getTransport(...) is called.
ac.cc.mu.Lock() cc.mu.Lock()
if ac.cc.conns == nil { if cc.conns == nil {
ac.cc.mu.Unlock() cc.mu.Unlock()
return ErrClientConnClosing return ErrClientConnClosing
} }
stale := ac.cc.conns[ac.addr] stale := cc.conns[ac.addr]
ac.cc.conns[ac.addr] = ac cc.conns[ac.addr] = ac
ac.cc.mu.Unlock() cc.mu.Unlock()
if stale != nil { if stale != nil {
// There is an addrConn alive on ac.addr already. This could be due to // There is an addrConn alive on ac.addr already. This could be due to
// i) stale's Close is undergoing; // 1) a buggy Balancer notifies duplicated Addresses;
// ii) a buggy Balancer notifies duplicated Addresses. // 2) goaway was received, a new ac will replace the old ac.
stale.tearDown(errConnDrain) // The old ac should be deleted from cc.conns, but the
// underlying transport should drain rather than close.
if tearDownErr == nil {
// tearDownErr is nil if resetAddrConn is called by
// 1) Dial
// 2) lbWatcher
// In both cases, the stale ac should drain, not close.
stale.tearDown(errConnDrain)
} else {
stale.tearDown(tearDownErr)
}
} }
ac.stateCV = sync.NewCond(&ac.mu)
// skipWait may overwrite the decision in ac.dopts.block. // skipWait may overwrite the decision in ac.dopts.block.
if ac.dopts.block && !skipWait { if ac.dopts.block && !skipWait {
if err := ac.resetTransport(false); err != nil { if err := ac.resetTransport(false); err != nil {
ac.tearDown(err) if err != errConnClosing {
// Tear down ac and delete it from cc.conns.
cc.mu.Lock()
delete(cc.conns, ac.addr)
cc.mu.Unlock()
ac.tearDown(err)
}
if e, ok := err.(transport.ConnectionError); ok && !e.Temporary() {
return e.Origin()
}
return err return err
} }
// Start to monitor the error status of transport. // Start to monitor the error status of transport.
@ -414,7 +458,10 @@ func (cc *ClientConn) newAddrConn(addr Address, skipWait bool) error {
go func() { go func() {
if err := ac.resetTransport(false); err != nil { if err := ac.resetTransport(false); err != nil {
grpclog.Printf("Failed to dial %s: %v; please retry.", ac.addr.Addr, err) grpclog.Printf("Failed to dial %s: %v; please retry.", ac.addr.Addr, err)
ac.tearDown(err) if err != errConnClosing {
// Keep this ac in cc.conns, to get the reason it's torn down.
ac.tearDown(err)
}
return return
} }
ac.transportMonitor() ac.transportMonitor()
@ -424,25 +471,48 @@ func (cc *ClientConn) newAddrConn(addr Address, skipWait bool) error {
} }
func (cc *ClientConn) getTransport(ctx context.Context, opts BalancerGetOptions) (transport.ClientTransport, func(), error) { func (cc *ClientConn) getTransport(ctx context.Context, opts BalancerGetOptions) (transport.ClientTransport, func(), error) {
// TODO(zhaoq): Implement fail-fast logic. var (
addr, put, err := cc.balancer.Get(ctx, opts) ac *addrConn
if err != nil { ok bool
return nil, nil, err put func()
} )
cc.mu.RLock() if cc.dopts.balancer == nil {
if cc.conns == nil { // If balancer is nil, there should be only one addrConn available.
cc.mu.RLock()
if cc.conns == nil {
cc.mu.RUnlock()
return nil, nil, toRPCErr(ErrClientConnClosing)
}
for _, ac = range cc.conns {
// Break after the first iteration to get the first addrConn.
ok = true
break
}
cc.mu.RUnlock()
} else {
var (
addr Address
err error
)
addr, put, err = cc.dopts.balancer.Get(ctx, opts)
if err != nil {
return nil, nil, toRPCErr(err)
}
cc.mu.RLock()
if cc.conns == nil {
cc.mu.RUnlock()
return nil, nil, toRPCErr(ErrClientConnClosing)
}
ac, ok = cc.conns[addr]
cc.mu.RUnlock() cc.mu.RUnlock()
return nil, nil, ErrClientConnClosing
} }
ac, ok := cc.conns[addr]
cc.mu.RUnlock()
if !ok { if !ok {
if put != nil { if put != nil {
put() put()
} }
return nil, nil, transport.StreamErrorf(codes.Internal, "grpc: failed to find the transport to send the rpc") return nil, nil, errConnClosing
} }
t, err := ac.wait(ctx) t, err := ac.wait(ctx, cc.dopts.balancer != nil, !opts.BlockingWait)
if err != nil { if err != nil {
if put != nil { if put != nil {
put() put()
@ -454,6 +524,8 @@ func (cc *ClientConn) getTransport(ctx context.Context, opts BalancerGetOptions)
// Close tears down the ClientConn and all underlying connections. // Close tears down the ClientConn and all underlying connections.
func (cc *ClientConn) Close() error { func (cc *ClientConn) Close() error {
cc.cancel()
cc.mu.Lock() cc.mu.Lock()
if cc.conns == nil { if cc.conns == nil {
cc.mu.Unlock() cc.mu.Unlock()
@ -462,7 +534,9 @@ func (cc *ClientConn) Close() error {
conns := cc.conns conns := cc.conns
cc.conns = nil cc.conns = nil
cc.mu.Unlock() cc.mu.Unlock()
cc.balancer.Close() if cc.dopts.balancer != nil {
cc.dopts.balancer.Close()
}
for _, ac := range conns { for _, ac := range conns {
ac.tearDown(ErrClientConnClosing) ac.tearDown(ErrClientConnClosing)
} }
@ -471,11 +545,13 @@ func (cc *ClientConn) Close() error {
// addrConn is a network connection to a given address. // addrConn is a network connection to a given address.
type addrConn struct { type addrConn struct {
cc *ClientConn ctx context.Context
addr Address cancel context.CancelFunc
dopts dialOptions
shutdownChan chan struct{} cc *ClientConn
events trace.EventLog addr Address
dopts dialOptions
events trace.EventLog
mu sync.Mutex mu sync.Mutex
state ConnectivityState state ConnectivityState
@ -485,6 +561,9 @@ type addrConn struct {
// due to timeout. // due to timeout.
ready chan struct{} ready chan struct{}
transport transport.ClientTransport transport transport.ClientTransport
// The reason this addrConn is torn down.
tearDownErr error
} }
// printf records an event in ac's event log, unless ac has been closed. // printf records an event in ac's event log, unless ac has been closed.
@ -540,8 +619,7 @@ func (ac *addrConn) waitForStateChange(ctx context.Context, sourceState Connecti
} }
func (ac *addrConn) resetTransport(closeTransport bool) error { func (ac *addrConn) resetTransport(closeTransport bool) error {
var retries int for retries := 0; ; retries++ {
for {
ac.mu.Lock() ac.mu.Lock()
ac.printf("connecting") ac.printf("connecting")
if ac.state == Shutdown { if ac.state == Shutdown {
@ -561,13 +639,20 @@ func (ac *addrConn) resetTransport(closeTransport bool) error {
t.Close() t.Close()
} }
sleepTime := ac.dopts.bs.backoff(retries) sleepTime := ac.dopts.bs.backoff(retries)
ac.dopts.copts.Timeout = sleepTime timeout := minConnectTimeout
if sleepTime < minConnectTimeout { if timeout < sleepTime {
ac.dopts.copts.Timeout = minConnectTimeout timeout = sleepTime
} }
ctx, cancel := context.WithTimeout(ac.ctx, timeout)
connectTime := time.Now() connectTime := time.Now()
newTransport, err := transport.NewClientTransport(ac.addr.Addr, &ac.dopts.copts) newTransport, err := transport.NewClientTransport(ctx, ac.addr.Addr, ac.dopts.copts)
if err != nil { if err != nil {
cancel()
if e, ok := err.(transport.ConnectionError); ok && !e.Temporary() {
return err
}
grpclog.Printf("grpc: addrConn.resetTransport failed to create client transport: %v; Reconnecting to %q", err, ac.addr)
ac.mu.Lock() ac.mu.Lock()
if ac.state == Shutdown { if ac.state == Shutdown {
// ac.tearDown(...) has been invoked. // ac.tearDown(...) has been invoked.
@ -582,17 +667,12 @@ func (ac *addrConn) resetTransport(closeTransport bool) error {
ac.ready = nil ac.ready = nil
} }
ac.mu.Unlock() ac.mu.Unlock()
sleepTime -= time.Since(connectTime)
if sleepTime < 0 {
sleepTime = 0
}
closeTransport = false closeTransport = false
select { select {
case <-time.After(sleepTime): case <-time.After(sleepTime - time.Since(connectTime)):
case <-ac.shutdownChan: case <-ac.ctx.Done():
return ac.ctx.Err()
} }
retries++
grpclog.Printf("grpc: addrConn.resetTransport failed to create client transport: %v; Reconnecting to %q", err, ac.addr)
continue continue
} }
ac.mu.Lock() ac.mu.Lock()
@ -610,7 +690,9 @@ func (ac *addrConn) resetTransport(closeTransport bool) error {
close(ac.ready) close(ac.ready)
ac.ready = nil ac.ready = nil
} }
ac.down = ac.cc.balancer.Up(ac.addr) if ac.cc.dopts.balancer != nil {
ac.down = ac.cc.dopts.balancer.Up(ac.addr)
}
ac.mu.Unlock() ac.mu.Unlock()
return nil return nil
} }
@ -624,14 +706,42 @@ func (ac *addrConn) transportMonitor() {
t := ac.transport t := ac.transport
ac.mu.Unlock() ac.mu.Unlock()
select { select {
// shutdownChan is needed to detect the teardown when // This is needed to detect the teardown when
// the addrConn is idle (i.e., no RPC in flight). // the addrConn is idle (i.e., no RPC in flight).
case <-ac.shutdownChan: case <-ac.ctx.Done():
select {
case <-t.Error():
t.Close()
default:
}
return
case <-t.GoAway():
// If GoAway happens without any network I/O error, ac is closed without shutting down the
// underlying transport (the transport will be closed when all the pending RPCs finished or
// failed.).
// If GoAway and some network I/O error happen concurrently, ac and its underlying transport
// are closed.
// In both cases, a new ac is created.
select {
case <-t.Error():
ac.cc.resetAddrConn(ac.addr, true, errNetworkIO)
default:
ac.cc.resetAddrConn(ac.addr, true, errConnDrain)
}
return return
case <-t.Error(): case <-t.Error():
select {
case <-ac.ctx.Done():
t.Close()
return
case <-t.GoAway():
ac.cc.resetAddrConn(ac.addr, true, errNetworkIO)
return
default:
}
ac.mu.Lock() ac.mu.Lock()
if ac.state == Shutdown { if ac.state == Shutdown {
// ac.tearDown(...) has been invoked. // ac has been shutdown.
ac.mu.Unlock() ac.mu.Unlock()
return return
} }
@ -643,38 +753,53 @@ func (ac *addrConn) transportMonitor() {
ac.printf("transport exiting: %v", err) ac.printf("transport exiting: %v", err)
ac.mu.Unlock() ac.mu.Unlock()
grpclog.Printf("grpc: addrConn.transportMonitor exits due to: %v", err) grpclog.Printf("grpc: addrConn.transportMonitor exits due to: %v", err)
if err != errConnClosing {
// Keep this ac in cc.conns, to get the reason it's torn down.
ac.tearDown(err)
}
return return
} }
} }
} }
} }
// wait blocks until i) the new transport is up or ii) ctx is done or iii) ac is closed. // wait blocks until i) the new transport is up or ii) ctx is done or iii) ac is closed or
func (ac *addrConn) wait(ctx context.Context) (transport.ClientTransport, error) { // iv) transport is in TransientFailure and there's no balancer/failfast is true.
func (ac *addrConn) wait(ctx context.Context, hasBalancer, failfast bool) (transport.ClientTransport, error) {
for { for {
ac.mu.Lock() ac.mu.Lock()
switch { switch {
case ac.state == Shutdown: case ac.state == Shutdown:
if failfast || !hasBalancer {
// RPC is failfast or balancer is nil. This RPC should fail with ac.tearDownErr.
err := ac.tearDownErr
ac.mu.Unlock()
return nil, err
}
ac.mu.Unlock() ac.mu.Unlock()
return nil, errConnClosing return nil, errConnClosing
case ac.state == Ready: case ac.state == Ready:
ct := ac.transport ct := ac.transport
ac.mu.Unlock() ac.mu.Unlock()
return ct, nil return ct, nil
default: case ac.state == TransientFailure:
ready := ac.ready if failfast || hasBalancer {
if ready == nil { ac.mu.Unlock()
ready = make(chan struct{}) return nil, errConnUnavailable
ac.ready = ready
}
ac.mu.Unlock()
select {
case <-ctx.Done():
return nil, transport.ContextErr(ctx.Err())
// Wait until the new transport is ready or failed.
case <-ready:
} }
} }
ready := ac.ready
if ready == nil {
ready = make(chan struct{})
ac.ready = ready
}
ac.mu.Unlock()
select {
case <-ctx.Done():
return nil, toRPCErr(ctx.Err())
// Wait until the new transport is ready or failed.
case <-ready:
}
} }
} }
@ -682,24 +807,28 @@ func (ac *addrConn) wait(ctx context.Context) (transport.ClientTransport, error)
// TODO(zhaoq): Make this synchronous to avoid unbounded memory consumption in // TODO(zhaoq): Make this synchronous to avoid unbounded memory consumption in
// some edge cases (e.g., the caller opens and closes many addrConn's in a // some edge cases (e.g., the caller opens and closes many addrConn's in a
// tight loop. // tight loop.
// tearDown doesn't remove ac from ac.cc.conns.
func (ac *addrConn) tearDown(err error) { func (ac *addrConn) tearDown(err error) {
ac.cancel()
ac.mu.Lock() ac.mu.Lock()
defer func() { defer ac.mu.Unlock()
ac.mu.Unlock()
ac.cc.mu.Lock()
if ac.cc.conns != nil {
delete(ac.cc.conns, ac.addr)
}
ac.cc.mu.Unlock()
}()
if ac.state == Shutdown {
return
}
ac.state = Shutdown
if ac.down != nil { if ac.down != nil {
ac.down(downErrorf(false, false, "%v", err)) ac.down(downErrorf(false, false, "%v", err))
ac.down = nil ac.down = nil
} }
if err == errConnDrain && ac.transport != nil {
// GracefulClose(...) may be executed multiple times when
// i) receiving multiple GoAway frames from the server; or
// ii) there are concurrent name resolver/Balancer triggered
// address removal and GoAway.
ac.transport.GracefulClose()
}
if ac.state == Shutdown {
return
}
ac.state = Shutdown
ac.tearDownErr = err
ac.stateCV.Broadcast() ac.stateCV.Broadcast()
if ac.events != nil { if ac.events != nil {
ac.events.Finish() ac.events.Finish()
@ -709,15 +838,8 @@ func (ac *addrConn) tearDown(err error) {
close(ac.ready) close(ac.ready)
ac.ready = nil ac.ready = nil
} }
if ac.transport != nil { if ac.transport != nil && err != errConnDrain {
if err == errConnDrain { ac.transport.Close()
ac.transport.GracefulClose()
} else {
ac.transport.Close()
}
}
if ac.shutdownChan != nil {
close(ac.shutdownChan)
} }
return return
} }

View File

@ -44,7 +44,6 @@ import (
"io/ioutil" "io/ioutil"
"net" "net"
"strings" "strings"
"time"
"golang.org/x/net/context" "golang.org/x/net/context"
) )
@ -66,7 +65,7 @@ type PerRPCCredentials interface {
// TODO(zhaoq): Define the set of the qualified keys instead of leaving // TODO(zhaoq): Define the set of the qualified keys instead of leaving
// it as an arbitrary string. // it as an arbitrary string.
GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error)
// RequireTransportSecurity indicates whether the credentails requires // RequireTransportSecurity indicates whether the credentials requires
// transport security. // transport security.
RequireTransportSecurity() bool RequireTransportSecurity() bool
} }
@ -93,11 +92,12 @@ type TransportCredentials interface {
// ClientHandshake does the authentication handshake specified by the corresponding // ClientHandshake does the authentication handshake specified by the corresponding
// authentication protocol on rawConn for clients. It returns the authenticated // authentication protocol on rawConn for clients. It returns the authenticated
// connection and the corresponding auth information about the connection. // connection and the corresponding auth information about the connection.
ClientHandshake(addr string, rawConn net.Conn, timeout time.Duration) (net.Conn, AuthInfo, error) // Implementations must use the provided context to implement timely cancellation.
ClientHandshake(context.Context, string, net.Conn) (net.Conn, AuthInfo, error)
// ServerHandshake does the authentication handshake for servers. It returns // ServerHandshake does the authentication handshake for servers. It returns
// the authenticated connection and the corresponding auth information about // the authenticated connection and the corresponding auth information about
// the connection. // the connection.
ServerHandshake(rawConn net.Conn) (net.Conn, AuthInfo, error) ServerHandshake(net.Conn) (net.Conn, AuthInfo, error)
// Info provides the ProtocolInfo of this TransportCredentials. // Info provides the ProtocolInfo of this TransportCredentials.
Info() ProtocolInfo Info() ProtocolInfo
} }
@ -116,7 +116,7 @@ func (t TLSInfo) AuthType() string {
// tlsCreds is the credentials required for authenticating a connection using TLS. // tlsCreds is the credentials required for authenticating a connection using TLS.
type tlsCreds struct { type tlsCreds struct {
// TLS configuration // TLS configuration
config tls.Config config *tls.Config
} }
func (c tlsCreds) Info() ProtocolInfo { func (c tlsCreds) Info() ProtocolInfo {
@ -136,40 +136,28 @@ func (c *tlsCreds) RequireTransportSecurity() bool {
return true return true
} }
type timeoutError struct{} func (c *tlsCreds) ClientHandshake(ctx context.Context, addr string, rawConn net.Conn) (_ net.Conn, _ AuthInfo, err error) {
// use local cfg to avoid clobbering ServerName if using multiple endpoints
func (timeoutError) Error() string { return "credentials: Dial timed out" } cfg := cloneTLSConfig(c.config)
func (timeoutError) Timeout() bool { return true } if cfg.ServerName == "" {
func (timeoutError) Temporary() bool { return true }
func (c *tlsCreds) ClientHandshake(addr string, rawConn net.Conn, timeout time.Duration) (_ net.Conn, _ AuthInfo, err error) {
// borrow some code from tls.DialWithDialer
var errChannel chan error
if timeout != 0 {
errChannel = make(chan error, 2)
time.AfterFunc(timeout, func() {
errChannel <- timeoutError{}
})
}
if c.config.ServerName == "" {
colonPos := strings.LastIndex(addr, ":") colonPos := strings.LastIndex(addr, ":")
if colonPos == -1 { if colonPos == -1 {
colonPos = len(addr) colonPos = len(addr)
} }
c.config.ServerName = addr[:colonPos] cfg.ServerName = addr[:colonPos]
} }
conn := tls.Client(rawConn, &c.config) conn := tls.Client(rawConn, cfg)
if timeout == 0 { errChannel := make(chan error, 1)
err = conn.Handshake() go func() {
} else { errChannel <- conn.Handshake()
go func() { }()
errChannel <- conn.Handshake() select {
}() case err := <-errChannel:
err = <-errChannel if err != nil {
} return nil, nil, err
if err != nil { }
rawConn.Close() case <-ctx.Done():
return nil, nil, err return nil, nil, ctx.Err()
} }
// TODO(zhaoq): Omit the auth info for client now. It is more for // TODO(zhaoq): Omit the auth info for client now. It is more for
// information than anything else. // information than anything else.
@ -177,9 +165,8 @@ func (c *tlsCreds) ClientHandshake(addr string, rawConn net.Conn, timeout time.D
} }
func (c *tlsCreds) ServerHandshake(rawConn net.Conn) (net.Conn, AuthInfo, error) { func (c *tlsCreds) ServerHandshake(rawConn net.Conn) (net.Conn, AuthInfo, error) {
conn := tls.Server(rawConn, &c.config) conn := tls.Server(rawConn, c.config)
if err := conn.Handshake(); err != nil { if err := conn.Handshake(); err != nil {
rawConn.Close()
return nil, nil, err return nil, nil, err
} }
return conn, TLSInfo{conn.ConnectionState()}, nil return conn, TLSInfo{conn.ConnectionState()}, nil
@ -187,7 +174,7 @@ func (c *tlsCreds) ServerHandshake(rawConn net.Conn) (net.Conn, AuthInfo, error)
// NewTLS uses c to construct a TransportCredentials based on TLS. // NewTLS uses c to construct a TransportCredentials based on TLS.
func NewTLS(c *tls.Config) TransportCredentials { func NewTLS(c *tls.Config) TransportCredentials {
tc := &tlsCreds{*c} tc := &tlsCreds{cloneTLSConfig(c)}
tc.config.NextProtos = alpnProtoStr tc.config.NextProtos = alpnProtoStr
return tc return tc
} }

View File

@ -0,0 +1,76 @@
// +build go1.7
/*
*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
package credentials
import (
"crypto/tls"
)
// cloneTLSConfig returns a shallow clone of the exported
// fields of cfg, ignoring the unexported sync.Once, which
// contains a mutex and must not be copied.
//
// If cfg is nil, a new zero tls.Config is returned.
//
// TODO replace this function with official clone function.
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
if cfg == nil {
return &tls.Config{}
}
return &tls.Config{
Rand: cfg.Rand,
Time: cfg.Time,
Certificates: cfg.Certificates,
NameToCertificate: cfg.NameToCertificate,
GetCertificate: cfg.GetCertificate,
RootCAs: cfg.RootCAs,
NextProtos: cfg.NextProtos,
ServerName: cfg.ServerName,
ClientAuth: cfg.ClientAuth,
ClientCAs: cfg.ClientCAs,
InsecureSkipVerify: cfg.InsecureSkipVerify,
CipherSuites: cfg.CipherSuites,
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
SessionTicketsDisabled: cfg.SessionTicketsDisabled,
SessionTicketKey: cfg.SessionTicketKey,
ClientSessionCache: cfg.ClientSessionCache,
MinVersion: cfg.MinVersion,
MaxVersion: cfg.MaxVersion,
CurvePreferences: cfg.CurvePreferences,
DynamicRecordSizingDisabled: cfg.DynamicRecordSizingDisabled,
Renegotiation: cfg.Renegotiation,
}
}

View File

@ -0,0 +1,74 @@
// +build !go1.7
/*
*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
package credentials
import (
"crypto/tls"
)
// cloneTLSConfig returns a shallow clone of the exported
// fields of cfg, ignoring the unexported sync.Once, which
// contains a mutex and must not be copied.
//
// If cfg is nil, a new zero tls.Config is returned.
//
// TODO replace this function with official clone function.
func cloneTLSConfig(cfg *tls.Config) *tls.Config {
if cfg == nil {
return &tls.Config{}
}
return &tls.Config{
Rand: cfg.Rand,
Time: cfg.Time,
Certificates: cfg.Certificates,
NameToCertificate: cfg.NameToCertificate,
GetCertificate: cfg.GetCertificate,
RootCAs: cfg.RootCAs,
NextProtos: cfg.NextProtos,
ServerName: cfg.ServerName,
ClientAuth: cfg.ClientAuth,
ClientCAs: cfg.ClientCAs,
InsecureSkipVerify: cfg.InsecureSkipVerify,
CipherSuites: cfg.CipherSuites,
PreferServerCipherSuites: cfg.PreferServerCipherSuites,
SessionTicketsDisabled: cfg.SessionTicketsDisabled,
SessionTicketKey: cfg.SessionTicketKey,
ClientSessionCache: cfg.ClientSessionCache,
MinVersion: cfg.MinVersion,
MaxVersion: cfg.MaxVersion,
CurvePreferences: cfg.CurvePreferences,
}
}

View File

@ -60,15 +60,21 @@ func encodeKeyValue(k, v string) (string, string) {
// DecodeKeyValue returns the original key and value corresponding to the // DecodeKeyValue returns the original key and value corresponding to the
// encoded data in k, v. // encoded data in k, v.
// If k is a binary header and v contains comma, v is split on comma before decoded,
// and the decoded v will be joined with comma before returned.
func DecodeKeyValue(k, v string) (string, string, error) { func DecodeKeyValue(k, v string) (string, string, error) {
if !strings.HasSuffix(k, binHdrSuffix) { if !strings.HasSuffix(k, binHdrSuffix) {
return k, v, nil return k, v, nil
} }
val, err := base64.StdEncoding.DecodeString(v) vvs := strings.Split(v, ",")
if err != nil { for i, vv := range vvs {
return "", "", err val, err := base64.StdEncoding.DecodeString(vv)
if err != nil {
return "", "", err
}
vvs[i] = string(val)
} }
return k, string(val), nil return k, strings.Join(vvs, ","), nil
} }
// MD is a mapping from metadata keys to values. Users should use the following // MD is a mapping from metadata keys to values. Users should use the following

View File

@ -141,6 +141,8 @@ type callInfo struct {
traceInfo traceInfo // in trace.go traceInfo traceInfo // in trace.go
} }
var defaultCallInfo = callInfo{failFast: true}
// CallOption configures a Call before it starts or extracts information from // CallOption configures a Call before it starts or extracts information from
// a Call after it completes. // a Call after it completes.
type CallOption interface { type CallOption interface {
@ -179,6 +181,19 @@ func Trailer(md *metadata.MD) CallOption {
}) })
} }
// FailFast configures the action to take when an RPC is attempted on broken
// connections or unreachable servers. If failfast is true, the RPC will fail
// immediately. Otherwise, the RPC client will block the call until a
// connection is available (or the call is canceled or times out) and will retry
// the call if it fails due to a transient error. Please refer to
// https://github.com/grpc/grpc/blob/master/doc/fail_fast.md
func FailFast(failFast bool) CallOption {
return beforeCall(func(c *callInfo) error {
c.failFast = failFast
return nil
})
}
// The format of the payload: compressed or not? // The format of the payload: compressed or not?
type payloadFormat uint8 type payloadFormat uint8
@ -212,7 +227,7 @@ type parser struct {
// No other error values or types must be returned, which also means // No other error values or types must be returned, which also means
// that the underlying io.Reader must not return an incompatible // that the underlying io.Reader must not return an incompatible
// error. // error.
func (p *parser) recvMsg() (pf payloadFormat, msg []byte, err error) { func (p *parser) recvMsg(maxMsgSize int) (pf payloadFormat, msg []byte, err error) {
if _, err := io.ReadFull(p.r, p.header[:]); err != nil { if _, err := io.ReadFull(p.r, p.header[:]); err != nil {
return 0, nil, err return 0, nil, err
} }
@ -223,6 +238,9 @@ func (p *parser) recvMsg() (pf payloadFormat, msg []byte, err error) {
if length == 0 { if length == 0 {
return pf, nil, nil return pf, nil, nil
} }
if length > uint32(maxMsgSize) {
return 0, nil, Errorf(codes.Internal, "grpc: received message length %d exceeding the max size %d", length, maxMsgSize)
}
// TODO(bradfitz,zhaoq): garbage. reuse buffer after proto decoding instead // TODO(bradfitz,zhaoq): garbage. reuse buffer after proto decoding instead
// of making it for each message: // of making it for each message:
msg = make([]byte, int(length)) msg = make([]byte, int(length))
@ -293,8 +311,8 @@ func checkRecvPayload(pf payloadFormat, recvCompress string, dc Decompressor) er
return nil return nil
} }
func recv(p *parser, c Codec, s *transport.Stream, dc Decompressor, m interface{}) error { func recv(p *parser, c Codec, s *transport.Stream, dc Decompressor, m interface{}, maxMsgSize int) error {
pf, d, err := p.recvMsg() pf, d, err := p.recvMsg(maxMsgSize)
if err != nil { if err != nil {
return err return err
} }
@ -304,11 +322,16 @@ func recv(p *parser, c Codec, s *transport.Stream, dc Decompressor, m interface{
if pf == compressionMade { if pf == compressionMade {
d, err = dc.Do(bytes.NewReader(d)) d, err = dc.Do(bytes.NewReader(d))
if err != nil { if err != nil {
return transport.StreamErrorf(codes.Internal, "grpc: failed to decompress the received message %v", err) return Errorf(codes.Internal, "grpc: failed to decompress the received message %v", err)
} }
} }
if len(d) > maxMsgSize {
// TODO: Revisit the error code. Currently keep it consistent with java
// implementation.
return Errorf(codes.Internal, "grpc: received a message of %d bytes exceeding %d limit", len(d), maxMsgSize)
}
if err := c.Unmarshal(d, m); err != nil { if err := c.Unmarshal(d, m); err != nil {
return transport.StreamErrorf(codes.Internal, "grpc: failed to unmarshal the received message %v", err) return Errorf(codes.Internal, "grpc: failed to unmarshal the received message %v", err)
} }
return nil return nil
} }
@ -319,7 +342,7 @@ type rpcError struct {
desc string desc string
} }
func (e rpcError) Error() string { func (e *rpcError) Error() string {
return fmt.Sprintf("rpc error: code = %d desc = %s", e.code, e.desc) return fmt.Sprintf("rpc error: code = %d desc = %s", e.code, e.desc)
} }
@ -329,7 +352,7 @@ func Code(err error) codes.Code {
if err == nil { if err == nil {
return codes.OK return codes.OK
} }
if e, ok := err.(rpcError); ok { if e, ok := err.(*rpcError); ok {
return e.code return e.code
} }
return codes.Unknown return codes.Unknown
@ -341,7 +364,7 @@ func ErrorDesc(err error) string {
if err == nil { if err == nil {
return "" return ""
} }
if e, ok := err.(rpcError); ok { if e, ok := err.(*rpcError); ok {
return e.desc return e.desc
} }
return err.Error() return err.Error()
@ -353,7 +376,7 @@ func Errorf(c codes.Code, format string, a ...interface{}) error {
if c == codes.OK { if c == codes.OK {
return nil return nil
} }
return rpcError{ return &rpcError{
code: c, code: c,
desc: fmt.Sprintf(format, a...), desc: fmt.Sprintf(format, a...),
} }
@ -362,18 +385,37 @@ func Errorf(c codes.Code, format string, a ...interface{}) error {
// toRPCErr converts an error into a rpcError. // toRPCErr converts an error into a rpcError.
func toRPCErr(err error) error { func toRPCErr(err error) error {
switch e := err.(type) { switch e := err.(type) {
case rpcError: case *rpcError:
return err return err
case transport.StreamError: case transport.StreamError:
return rpcError{ return &rpcError{
code: e.Code, code: e.Code,
desc: e.Desc, desc: e.Desc,
} }
case transport.ConnectionError: case transport.ConnectionError:
return rpcError{ return &rpcError{
code: codes.Internal, code: codes.Internal,
desc: e.Desc, desc: e.Desc,
} }
default:
switch err {
case context.DeadlineExceeded:
return &rpcError{
code: codes.DeadlineExceeded,
desc: err.Error(),
}
case context.Canceled:
return &rpcError{
code: codes.Canceled,
desc: err.Error(),
}
case ErrClientConnClosing:
return &rpcError{
code: codes.FailedPrecondition,
desc: err.Error(),
}
}
} }
return Errorf(codes.Unknown, "%v", err) return Errorf(codes.Unknown, "%v", err)
} }

View File

@ -82,15 +82,20 @@ type service struct {
server interface{} // the server for service methods server interface{} // the server for service methods
md map[string]*MethodDesc md map[string]*MethodDesc
sd map[string]*StreamDesc sd map[string]*StreamDesc
mdata interface{}
} }
// Server is a gRPC server to serve RPC requests. // Server is a gRPC server to serve RPC requests.
type Server struct { type Server struct {
opts options opts options
mu sync.Mutex // guards following mu sync.Mutex // guards following
lis map[net.Listener]bool lis map[net.Listener]bool
conns map[io.Closer]bool conns map[io.Closer]bool
drain bool
// A CondVar to let GracefulStop() blocks until all the pending RPCs are finished
// and all the transport goes away.
cv *sync.Cond
m map[string]*service // service name -> service info m map[string]*service // service name -> service info
events trace.EventLog events trace.EventLog
} }
@ -100,12 +105,15 @@ type options struct {
codec Codec codec Codec
cp Compressor cp Compressor
dc Decompressor dc Decompressor
maxMsgSize int
unaryInt UnaryServerInterceptor unaryInt UnaryServerInterceptor
streamInt StreamServerInterceptor streamInt StreamServerInterceptor
maxConcurrentStreams uint32 maxConcurrentStreams uint32
useHandlerImpl bool // use http.Handler-based server useHandlerImpl bool // use http.Handler-based server
} }
var defaultMaxMsgSize = 1024 * 1024 * 4 // use 4MB as the default message size limit
// A ServerOption sets options. // A ServerOption sets options.
type ServerOption func(*options) type ServerOption func(*options)
@ -116,20 +124,28 @@ func CustomCodec(codec Codec) ServerOption {
} }
} }
// RPCCompressor returns a ServerOption that sets a compressor for outbound message. // RPCCompressor returns a ServerOption that sets a compressor for outbound messages.
func RPCCompressor(cp Compressor) ServerOption { func RPCCompressor(cp Compressor) ServerOption {
return func(o *options) { return func(o *options) {
o.cp = cp o.cp = cp
} }
} }
// RPCDecompressor returns a ServerOption that sets a decompressor for inbound message. // RPCDecompressor returns a ServerOption that sets a decompressor for inbound messages.
func RPCDecompressor(dc Decompressor) ServerOption { func RPCDecompressor(dc Decompressor) ServerOption {
return func(o *options) { return func(o *options) {
o.dc = dc o.dc = dc
} }
} }
// MaxMsgSize returns a ServerOption to set the max message size in bytes for inbound mesages.
// If this is not set, gRPC uses the default 4MB.
func MaxMsgSize(m int) ServerOption {
return func(o *options) {
o.maxMsgSize = m
}
}
// MaxConcurrentStreams returns a ServerOption that will apply a limit on the number // MaxConcurrentStreams returns a ServerOption that will apply a limit on the number
// of concurrent streams to each ServerTransport. // of concurrent streams to each ServerTransport.
func MaxConcurrentStreams(n uint32) ServerOption { func MaxConcurrentStreams(n uint32) ServerOption {
@ -172,6 +188,7 @@ func StreamInterceptor(i StreamServerInterceptor) ServerOption {
// started to accept requests yet. // started to accept requests yet.
func NewServer(opt ...ServerOption) *Server { func NewServer(opt ...ServerOption) *Server {
var opts options var opts options
opts.maxMsgSize = defaultMaxMsgSize
for _, o := range opt { for _, o := range opt {
o(&opts) o(&opts)
} }
@ -185,6 +202,7 @@ func NewServer(opt ...ServerOption) *Server {
conns: make(map[io.Closer]bool), conns: make(map[io.Closer]bool),
m: make(map[string]*service), m: make(map[string]*service),
} }
s.cv = sync.NewCond(&s.mu)
if EnableTracing { if EnableTracing {
_, file, line, _ := runtime.Caller(1) _, file, line, _ := runtime.Caller(1)
s.events = trace.NewEventLog("grpc.Server", fmt.Sprintf("%s:%d", file, line)) s.events = trace.NewEventLog("grpc.Server", fmt.Sprintf("%s:%d", file, line))
@ -231,6 +249,7 @@ func (s *Server) register(sd *ServiceDesc, ss interface{}) {
server: ss, server: ss,
md: make(map[string]*MethodDesc), md: make(map[string]*MethodDesc),
sd: make(map[string]*StreamDesc), sd: make(map[string]*StreamDesc),
mdata: sd.Metadata,
} }
for i := range sd.Methods { for i := range sd.Methods {
d := &sd.Methods[i] d := &sd.Methods[i]
@ -243,6 +262,52 @@ func (s *Server) register(sd *ServiceDesc, ss interface{}) {
s.m[sd.ServiceName] = srv s.m[sd.ServiceName] = srv
} }
// MethodInfo contains the information of an RPC including its method name and type.
type MethodInfo struct {
// Name is the method name only, without the service name or package name.
Name string
// IsClientStream indicates whether the RPC is a client streaming RPC.
IsClientStream bool
// IsServerStream indicates whether the RPC is a server streaming RPC.
IsServerStream bool
}
// ServiceInfo contains unary RPC method info, streaming RPC methid info and metadata for a service.
type ServiceInfo struct {
Methods []MethodInfo
// Metadata is the metadata specified in ServiceDesc when registering service.
Metadata interface{}
}
// GetServiceInfo returns a map from service names to ServiceInfo.
// Service names include the package names, in the form of <package>.<service>.
func (s *Server) GetServiceInfo() map[string]ServiceInfo {
ret := make(map[string]ServiceInfo)
for n, srv := range s.m {
methods := make([]MethodInfo, 0, len(srv.md)+len(srv.sd))
for m := range srv.md {
methods = append(methods, MethodInfo{
Name: m,
IsClientStream: false,
IsServerStream: false,
})
}
for m, d := range srv.sd {
methods = append(methods, MethodInfo{
Name: m,
IsClientStream: d.ClientStreams,
IsServerStream: d.ServerStreams,
})
}
ret[n] = ServiceInfo{
Methods: methods,
Metadata: srv.mdata,
}
}
return ret
}
var ( var (
// ErrServerStopped indicates that the operation is now illegal because of // ErrServerStopped indicates that the operation is now illegal because of
// the server being stopped. // the server being stopped.
@ -272,9 +337,11 @@ func (s *Server) Serve(lis net.Listener) error {
s.lis[lis] = true s.lis[lis] = true
s.mu.Unlock() s.mu.Unlock()
defer func() { defer func() {
lis.Close()
s.mu.Lock() s.mu.Lock()
delete(s.lis, lis) if s.lis != nil && s.lis[lis] {
lis.Close()
delete(s.lis, lis)
}
s.mu.Unlock() s.mu.Unlock()
}() }()
for { for {
@ -418,7 +485,7 @@ func (s *Server) traceInfo(st transport.ServerTransport, stream *transport.Strea
func (s *Server) addConn(c io.Closer) bool { func (s *Server) addConn(c io.Closer) bool {
s.mu.Lock() s.mu.Lock()
defer s.mu.Unlock() defer s.mu.Unlock()
if s.conns == nil { if s.conns == nil || s.drain {
return false return false
} }
s.conns[c] = true s.conns[c] = true
@ -430,6 +497,7 @@ func (s *Server) removeConn(c io.Closer) {
defer s.mu.Unlock() defer s.mu.Unlock()
if s.conns != nil { if s.conns != nil {
delete(s.conns, c) delete(s.conns, c)
s.cv.Signal()
} }
} }
@ -470,7 +538,7 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport.
} }
p := &parser{r: stream} p := &parser{r: stream}
for { for {
pf, req, err := p.recvMsg() pf, req, err := p.recvMsg(s.opts.maxMsgSize)
if err == io.EOF { if err == io.EOF {
// The entire stream is done (for unary RPC only). // The entire stream is done (for unary RPC only).
return err return err
@ -480,6 +548,10 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport.
} }
if err != nil { if err != nil {
switch err := err.(type) { switch err := err.(type) {
case *rpcError:
if err := t.WriteStatus(stream, err.code, err.desc); err != nil {
grpclog.Printf("grpc: Server.processUnaryRPC failed to write status %v", err)
}
case transport.ConnectionError: case transport.ConnectionError:
// Nothing to do here. // Nothing to do here.
case transport.StreamError: case transport.StreamError:
@ -519,6 +591,12 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport.
return err return err
} }
} }
if len(req) > s.opts.maxMsgSize {
// TODO: Revisit the error code. Currently keep it consistent with
// java implementation.
statusCode = codes.Internal
statusDesc = fmt.Sprintf("grpc: server received a message of %d bytes exceeding %d limit", len(req), s.opts.maxMsgSize)
}
if err := s.opts.codec.Unmarshal(req, v); err != nil { if err := s.opts.codec.Unmarshal(req, v); err != nil {
return err return err
} }
@ -529,7 +607,7 @@ func (s *Server) processUnaryRPC(t transport.ServerTransport, stream *transport.
} }
reply, appErr := md.Handler(srv.server, stream.Context(), df, s.opts.unaryInt) reply, appErr := md.Handler(srv.server, stream.Context(), df, s.opts.unaryInt)
if appErr != nil { if appErr != nil {
if err, ok := appErr.(rpcError); ok { if err, ok := appErr.(*rpcError); ok {
statusCode = err.code statusCode = err.code
statusDesc = err.desc statusDesc = err.desc
} else { } else {
@ -578,13 +656,14 @@ func (s *Server) processStreamingRPC(t transport.ServerTransport, stream *transp
stream.SetSendCompress(s.opts.cp.Type()) stream.SetSendCompress(s.opts.cp.Type())
} }
ss := &serverStream{ ss := &serverStream{
t: t, t: t,
s: stream, s: stream,
p: &parser{r: stream}, p: &parser{r: stream},
codec: s.opts.codec, codec: s.opts.codec,
cp: s.opts.cp, cp: s.opts.cp,
dc: s.opts.dc, dc: s.opts.dc,
trInfo: trInfo, maxMsgSize: s.opts.maxMsgSize,
trInfo: trInfo,
} }
if ss.cp != nil { if ss.cp != nil {
ss.cbuf = new(bytes.Buffer) ss.cbuf = new(bytes.Buffer)
@ -614,7 +693,7 @@ func (s *Server) processStreamingRPC(t transport.ServerTransport, stream *transp
appErr = s.opts.streamInt(srv.server, ss, info, sd.Handler) appErr = s.opts.streamInt(srv.server, ss, info, sd.Handler)
} }
if appErr != nil { if appErr != nil {
if err, ok := appErr.(rpcError); ok { if err, ok := appErr.(*rpcError); ok {
ss.statusCode = err.code ss.statusCode = err.code
ss.statusDesc = err.desc ss.statusDesc = err.desc
} else if err, ok := appErr.(transport.StreamError); ok { } else if err, ok := appErr.(transport.StreamError); ok {
@ -716,14 +795,16 @@ func (s *Server) Stop() {
s.mu.Lock() s.mu.Lock()
listeners := s.lis listeners := s.lis
s.lis = nil s.lis = nil
cs := s.conns st := s.conns
s.conns = nil s.conns = nil
// interrupt GracefulStop if Stop and GracefulStop are called concurrently.
s.cv.Signal()
s.mu.Unlock() s.mu.Unlock()
for lis := range listeners { for lis := range listeners {
lis.Close() lis.Close()
} }
for c := range cs { for c := range st {
c.Close() c.Close()
} }
@ -735,6 +816,32 @@ func (s *Server) Stop() {
s.mu.Unlock() s.mu.Unlock()
} }
// GracefulStop stops the gRPC server gracefully. It stops the server to accept new
// connections and RPCs and blocks until all the pending RPCs are finished.
func (s *Server) GracefulStop() {
s.mu.Lock()
defer s.mu.Unlock()
if s.drain == true || s.conns == nil {
return
}
s.drain = true
for lis := range s.lis {
lis.Close()
}
s.lis = nil
for c := range s.conns {
c.(transport.ServerTransport).Drain()
}
for len(s.conns) != 0 {
s.cv.Wait()
}
s.conns = nil
if s.events != nil {
s.events.Finish()
s.events = nil
}
}
func init() { func init() {
internal.TestingCloseConns = func(arg interface{}) { internal.TestingCloseConns = func(arg interface{}) {
arg.(*Server).testingCloseConns() arg.(*Server).testingCloseConns()

View File

@ -37,6 +37,7 @@ import (
"bytes" "bytes"
"errors" "errors"
"io" "io"
"math"
"sync" "sync"
"time" "time"
@ -84,12 +85,9 @@ type ClientStream interface {
// Header returns the header metadata received from the server if there // Header returns the header metadata received from the server if there
// is any. It blocks if the metadata is not ready to read. // is any. It blocks if the metadata is not ready to read.
Header() (metadata.MD, error) Header() (metadata.MD, error)
// Trailer returns the trailer metadata from the server. It must be called // Trailer returns the trailer metadata from the server, if there is any.
// after stream.Recv() returns non-nil error (including io.EOF) for // It must only be called after stream.CloseAndRecv has returned, or
// bi-directional streaming and server streaming or stream.CloseAndRecv() // stream.Recv has returned a non-nil error (including io.EOF).
// returns for client streaming in order to receive trailer metadata if
// present. Otherwise, it could returns an empty MD even though trailer
// is present.
Trailer() metadata.MD Trailer() metadata.MD
// CloseSend closes the send direction of the stream. It closes the stream // CloseSend closes the send direction of the stream. It closes the stream
// when non-nil error is met. // when non-nil error is met.
@ -99,19 +97,17 @@ type ClientStream interface {
// NewClientStream creates a new Stream for the client side. This is called // NewClientStream creates a new Stream for the client side. This is called
// by generated code. // by generated code.
func NewClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, opts ...CallOption) (ClientStream, error) { func NewClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, method string, opts ...CallOption) (_ ClientStream, err error) {
var ( var (
t transport.ClientTransport t transport.ClientTransport
err error s *transport.Stream
put func() put func()
) )
// TODO(zhaoq): CallOption is omitted. Add support when it is needed. c := defaultCallInfo
gopts := BalancerGetOptions{ for _, o := range opts {
BlockingWait: false, if err := o.before(&c); err != nil {
} return nil, toRPCErr(err)
t, put, err = cc.getTransport(ctx, gopts) }
if err != nil {
return nil, toRPCErr(err)
} }
callHdr := &transport.CallHdr{ callHdr := &transport.CallHdr{
Host: cc.authority, Host: cc.authority,
@ -121,41 +117,98 @@ func NewClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth
if cc.dopts.cp != nil { if cc.dopts.cp != nil {
callHdr.SendCompress = cc.dopts.cp.Type() callHdr.SendCompress = cc.dopts.cp.Type()
} }
var trInfo traceInfo
if EnableTracing {
trInfo.tr = trace.New("grpc.Sent."+methodFamily(method), method)
trInfo.firstLine.client = true
if deadline, ok := ctx.Deadline(); ok {
trInfo.firstLine.deadline = deadline.Sub(time.Now())
}
trInfo.tr.LazyLog(&trInfo.firstLine, false)
ctx = trace.NewContext(ctx, trInfo.tr)
defer func() {
if err != nil {
// Need to call tr.finish() if error is returned.
// Because tr will not be returned to caller.
trInfo.tr.LazyPrintf("RPC: [%v]", err)
trInfo.tr.SetError()
trInfo.tr.Finish()
}
}()
}
gopts := BalancerGetOptions{
BlockingWait: !c.failFast,
}
for {
t, put, err = cc.getTransport(ctx, gopts)
if err != nil {
// TODO(zhaoq): Probably revisit the error handling.
if _, ok := err.(*rpcError); ok {
return nil, err
}
if err == errConnClosing || err == errConnUnavailable {
if c.failFast {
return nil, Errorf(codes.Unavailable, "%v", err)
}
continue
}
// All the other errors are treated as Internal errors.
return nil, Errorf(codes.Internal, "%v", err)
}
s, err = t.NewStream(ctx, callHdr)
if err != nil {
if put != nil {
put()
put = nil
}
if _, ok := err.(transport.ConnectionError); ok || err == transport.ErrStreamDrain {
if c.failFast {
return nil, toRPCErr(err)
}
continue
}
return nil, toRPCErr(err)
}
break
}
cs := &clientStream{ cs := &clientStream{
desc: desc, opts: opts,
put: put, c: c,
codec: cc.dopts.codec, desc: desc,
cp: cc.dopts.cp, codec: cc.dopts.codec,
dc: cc.dopts.dc, cp: cc.dopts.cp,
dc: cc.dopts.dc,
put: put,
t: t,
s: s,
p: &parser{r: s},
tracing: EnableTracing, tracing: EnableTracing,
trInfo: trInfo,
} }
if cc.dopts.cp != nil { if cc.dopts.cp != nil {
callHdr.SendCompress = cc.dopts.cp.Type()
cs.cbuf = new(bytes.Buffer) cs.cbuf = new(bytes.Buffer)
} }
if cs.tracing { // Listen on ctx.Done() to detect cancellation and s.Done() to detect normal termination
cs.trInfo.tr = trace.New("grpc.Sent."+methodFamily(method), method) // when there is no pending I/O operations on this stream.
cs.trInfo.firstLine.client = true
if deadline, ok := ctx.Deadline(); ok {
cs.trInfo.firstLine.deadline = deadline.Sub(time.Now())
}
cs.trInfo.tr.LazyLog(&cs.trInfo.firstLine, false)
ctx = trace.NewContext(ctx, cs.trInfo.tr)
}
s, err := t.NewStream(ctx, callHdr)
if err != nil {
cs.finish(err)
return nil, toRPCErr(err)
}
cs.t = t
cs.s = s
cs.p = &parser{r: s}
// Listen on ctx.Done() to detect cancellation when there is no pending
// I/O operations on this stream.
go func() { go func() {
select { select {
case <-t.Error(): case <-t.Error():
// Incur transport error, simply exit. // Incur transport error, simply exit.
case <-s.Done():
// TODO: The trace of the RPC is terminated here when there is no pending
// I/O, which is probably not the optimal solution.
if s.StatusCode() == codes.OK {
cs.finish(nil)
} else {
cs.finish(Errorf(s.StatusCode(), "%s", s.StatusDesc()))
}
cs.closeTransportStream(nil)
case <-s.GoAway():
cs.finish(errConnDrain)
cs.closeTransportStream(errConnDrain)
case <-s.Context().Done(): case <-s.Context().Done():
err := s.Context().Err() err := s.Context().Err()
cs.finish(err) cs.finish(err)
@ -167,6 +220,8 @@ func NewClientStream(ctx context.Context, desc *StreamDesc, cc *ClientConn, meth
// clientStream implements a client side Stream. // clientStream implements a client side Stream.
type clientStream struct { type clientStream struct {
opts []CallOption
c callInfo
t transport.ClientTransport t transport.ClientTransport
s *transport.Stream s *transport.Stream
p *parser p *parser
@ -216,7 +271,17 @@ func (cs *clientStream) SendMsg(m interface{}) (err error) {
if err != nil { if err != nil {
cs.finish(err) cs.finish(err)
} }
if err == nil || err == io.EOF { if err == nil {
return
}
if err == io.EOF {
// Specialize the process for server streaming. SendMesg is only called
// once when creating the stream object. io.EOF needs to be skipped when
// the rpc is early finished (before the stream object is created.).
// TODO: It is probably better to move this into the generated code.
if !cs.desc.ClientStreams && cs.desc.ServerStreams {
err = nil
}
return return
} }
if _, ok := err.(transport.ConnectionError); !ok { if _, ok := err.(transport.ConnectionError); !ok {
@ -237,7 +302,7 @@ func (cs *clientStream) SendMsg(m interface{}) (err error) {
} }
func (cs *clientStream) RecvMsg(m interface{}) (err error) { func (cs *clientStream) RecvMsg(m interface{}) (err error) {
err = recv(cs.p, cs.codec, cs.s, cs.dc, m) err = recv(cs.p, cs.codec, cs.s, cs.dc, m, math.MaxInt32)
defer func() { defer func() {
// err != nil indicates the termination of the stream. // err != nil indicates the termination of the stream.
if err != nil { if err != nil {
@ -256,7 +321,7 @@ func (cs *clientStream) RecvMsg(m interface{}) (err error) {
return return
} }
// Special handling for client streaming rpc. // Special handling for client streaming rpc.
err = recv(cs.p, cs.codec, cs.s, cs.dc, m) err = recv(cs.p, cs.codec, cs.s, cs.dc, m, math.MaxInt32)
cs.closeTransportStream(err) cs.closeTransportStream(err)
if err == nil { if err == nil {
return toRPCErr(errors.New("grpc: client streaming protocol violation: get <nil>, want <EOF>")) return toRPCErr(errors.New("grpc: client streaming protocol violation: get <nil>, want <EOF>"))
@ -291,7 +356,7 @@ func (cs *clientStream) CloseSend() (err error) {
} }
}() }()
if err == nil || err == io.EOF { if err == nil || err == io.EOF {
return return nil
} }
if _, ok := err.(transport.ConnectionError); !ok { if _, ok := err.(transport.ConnectionError); !ok {
cs.closeTransportStream(err) cs.closeTransportStream(err)
@ -312,15 +377,18 @@ func (cs *clientStream) closeTransportStream(err error) {
} }
func (cs *clientStream) finish(err error) { func (cs *clientStream) finish(err error) {
if !cs.tracing {
return
}
cs.mu.Lock() cs.mu.Lock()
defer cs.mu.Unlock() defer cs.mu.Unlock()
for _, o := range cs.opts {
o.after(&cs.c)
}
if cs.put != nil { if cs.put != nil {
cs.put() cs.put()
cs.put = nil cs.put = nil
} }
if !cs.tracing {
return
}
if cs.trInfo.tr != nil { if cs.trInfo.tr != nil {
if err == nil || err == io.EOF { if err == nil || err == io.EOF {
cs.trInfo.tr.LazyPrintf("RPC: [OK]") cs.trInfo.tr.LazyPrintf("RPC: [OK]")
@ -354,6 +422,7 @@ type serverStream struct {
cp Compressor cp Compressor
dc Decompressor dc Decompressor
cbuf *bytes.Buffer cbuf *bytes.Buffer
maxMsgSize int
statusCode codes.Code statusCode codes.Code
statusDesc string statusDesc string
trInfo *traceInfo trInfo *traceInfo
@ -420,5 +489,5 @@ func (ss *serverStream) RecvMsg(m interface{}) (err error) {
ss.mu.Unlock() ss.mu.Unlock()
} }
}() }()
return recv(ss.p, ss.codec, ss.s, ss.dc, m) return recv(ss.p, ss.codec, ss.s, ss.dc, m, ss.maxMsgSize)
} }

View File

@ -72,6 +72,11 @@ type resetStream struct {
func (*resetStream) item() {} func (*resetStream) item() {}
type goAway struct {
}
func (*goAway) item() {}
type flushIO struct { type flushIO struct {
} }

46
cmd/vendor/google.golang.org/grpc/transport/go16.go generated vendored Normal file
View File

@ -0,0 +1,46 @@
// +build go1.6,!go1.7
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
package transport
import (
"net"
"golang.org/x/net/context"
)
// dialContext connects to the address on the named network.
func dialContext(ctx context.Context, network, address string) (net.Conn, error) {
return (&net.Dialer{Cancel: ctx.Done()}).Dial(network, address)
}

46
cmd/vendor/google.golang.org/grpc/transport/go17.go generated vendored Normal file
View File

@ -0,0 +1,46 @@
// +build go1.7
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
package transport
import (
"net"
"golang.org/x/net/context"
)
// dialContext connects to the address on the named network.
func dialContext(ctx context.Context, network, address string) (net.Conn, error) {
return (&net.Dialer{}).DialContext(ctx, network, address)
}

View File

@ -83,7 +83,7 @@ func NewServerHandlerTransport(w http.ResponseWriter, r *http.Request) (ServerTr
} }
if v := r.Header.Get("grpc-timeout"); v != "" { if v := r.Header.Get("grpc-timeout"); v != "" {
to, err := timeoutDecode(v) to, err := decodeTimeout(v)
if err != nil { if err != nil {
return nil, StreamErrorf(codes.Internal, "malformed time-out: %v", err) return nil, StreamErrorf(codes.Internal, "malformed time-out: %v", err)
} }
@ -194,7 +194,7 @@ func (ht *serverHandlerTransport) WriteStatus(s *Stream, statusCode codes.Code,
h := ht.rw.Header() h := ht.rw.Header()
h.Set("Grpc-Status", fmt.Sprintf("%d", statusCode)) h.Set("Grpc-Status", fmt.Sprintf("%d", statusCode))
if statusDesc != "" { if statusDesc != "" {
h.Set("Grpc-Message", statusDesc) h.Set("Grpc-Message", encodeGrpcMessage(statusDesc))
} }
if md := s.Trailer(); len(md) > 0 { if md := s.Trailer(); len(md) > 0 {
for k, vv := range md { for k, vv := range md {
@ -312,7 +312,7 @@ func (ht *serverHandlerTransport) HandleStreams(startStream func(*Stream)) {
Addr: ht.RemoteAddr(), Addr: ht.RemoteAddr(),
} }
if req.TLS != nil { if req.TLS != nil {
pr.AuthInfo = credentials.TLSInfo{*req.TLS} pr.AuthInfo = credentials.TLSInfo{State: *req.TLS}
} }
ctx = metadata.NewContext(ctx, ht.headerMD) ctx = metadata.NewContext(ctx, ht.headerMD)
ctx = peer.NewContext(ctx, pr) ctx = peer.NewContext(ctx, pr)
@ -370,6 +370,10 @@ func (ht *serverHandlerTransport) runStream() {
} }
} }
func (ht *serverHandlerTransport) Drain() {
panic("Drain() is not implemented")
}
// mapRecvMsgError returns the non-nil err into the appropriate // mapRecvMsgError returns the non-nil err into the appropriate
// error value as expected by callers of *grpc.parser.recvMsg. // error value as expected by callers of *grpc.parser.recvMsg.
// In particular, in can only be: // In particular, in can only be:

View File

@ -35,6 +35,7 @@ package transport
import ( import (
"bytes" "bytes"
"fmt"
"io" "io"
"math" "math"
"net" "net"
@ -71,6 +72,9 @@ type http2Client struct {
shutdownChan chan struct{} shutdownChan chan struct{}
// errorChan is closed to notify the I/O error to the caller. // errorChan is closed to notify the I/O error to the caller.
errorChan chan struct{} errorChan chan struct{}
// goAway is closed to notify the upper layer (i.e., addrConn.transportMonitor)
// that the server sent GoAway on this transport.
goAway chan struct{}
framer *framer framer *framer
hBuf *bytes.Buffer // the buffer for HPACK encoding hBuf *bytes.Buffer // the buffer for HPACK encoding
@ -97,41 +101,44 @@ type http2Client struct {
maxStreams int maxStreams int
// the per-stream outbound flow control window size set by the peer. // the per-stream outbound flow control window size set by the peer.
streamSendQuota uint32 streamSendQuota uint32
// goAwayID records the Last-Stream-ID in the GoAway frame from the server.
goAwayID uint32
// prevGoAway ID records the Last-Stream-ID in the previous GOAway frame.
prevGoAwayID uint32
}
func dial(fn func(context.Context, string) (net.Conn, error), ctx context.Context, addr string) (net.Conn, error) {
if fn != nil {
return fn(ctx, addr)
}
return dialContext(ctx, "tcp", addr)
} }
// newHTTP2Client constructs a connected ClientTransport to addr based on HTTP2 // newHTTP2Client constructs a connected ClientTransport to addr based on HTTP2
// and starts to receive messages on it. Non-nil error returns if construction // and starts to receive messages on it. Non-nil error returns if construction
// fails. // fails.
func newHTTP2Client(addr string, opts *ConnectOptions) (_ ClientTransport, err error) { func newHTTP2Client(ctx context.Context, addr string, opts ConnectOptions) (_ ClientTransport, err error) {
if opts.Dialer == nil {
// Set the default Dialer.
opts.Dialer = func(addr string, timeout time.Duration) (net.Conn, error) {
return net.DialTimeout("tcp", addr, timeout)
}
}
scheme := "http" scheme := "http"
startT := time.Now() conn, connErr := dial(opts.Dialer, ctx, addr)
timeout := opts.Timeout
conn, connErr := opts.Dialer(addr, timeout)
if connErr != nil { if connErr != nil {
return nil, ConnectionErrorf("transport: %v", connErr) return nil, ConnectionErrorf(true, connErr, "transport: %v", connErr)
} }
var authInfo credentials.AuthInfo // Any further errors will close the underlying connection
if opts.TransportCredentials != nil { defer func(conn net.Conn) {
scheme = "https"
if timeout > 0 {
timeout -= time.Since(startT)
}
conn, authInfo, connErr = opts.TransportCredentials.ClientHandshake(addr, conn, timeout)
}
if connErr != nil {
return nil, ConnectionErrorf("transport: %v", connErr)
}
defer func() {
if err != nil { if err != nil {
conn.Close() conn.Close()
} }
}() }(conn)
var authInfo credentials.AuthInfo
if creds := opts.TransportCredentials; creds != nil {
scheme = "https"
conn, authInfo, connErr = creds.ClientHandshake(ctx, addr, conn)
}
if connErr != nil {
// Credentials handshake error is not a temporary error (unless the error
// was the connection closing).
return nil, ConnectionErrorf(connErr == io.EOF, connErr, "transport: %v", connErr)
}
ua := primaryUA ua := primaryUA
if opts.UserAgent != "" { if opts.UserAgent != "" {
ua = opts.UserAgent + " " + ua ua = opts.UserAgent + " " + ua
@ -147,6 +154,7 @@ func newHTTP2Client(addr string, opts *ConnectOptions) (_ ClientTransport, err e
writableChan: make(chan int, 1), writableChan: make(chan int, 1),
shutdownChan: make(chan struct{}), shutdownChan: make(chan struct{}),
errorChan: make(chan struct{}), errorChan: make(chan struct{}),
goAway: make(chan struct{}),
framer: newFramer(conn), framer: newFramer(conn),
hBuf: &buf, hBuf: &buf,
hEnc: hpack.NewEncoder(&buf), hEnc: hpack.NewEncoder(&buf),
@ -168,26 +176,29 @@ func newHTTP2Client(addr string, opts *ConnectOptions) (_ ClientTransport, err e
n, err := t.conn.Write(clientPreface) n, err := t.conn.Write(clientPreface)
if err != nil { if err != nil {
t.Close() t.Close()
return nil, ConnectionErrorf("transport: %v", err) return nil, ConnectionErrorf(true, err, "transport: %v", err)
} }
if n != len(clientPreface) { if n != len(clientPreface) {
t.Close() t.Close()
return nil, ConnectionErrorf("transport: preface mismatch, wrote %d bytes; want %d", n, len(clientPreface)) return nil, ConnectionErrorf(true, err, "transport: preface mismatch, wrote %d bytes; want %d", n, len(clientPreface))
} }
if initialWindowSize != defaultWindowSize { if initialWindowSize != defaultWindowSize {
err = t.framer.writeSettings(true, http2.Setting{http2.SettingInitialWindowSize, uint32(initialWindowSize)}) err = t.framer.writeSettings(true, http2.Setting{
ID: http2.SettingInitialWindowSize,
Val: uint32(initialWindowSize),
})
} else { } else {
err = t.framer.writeSettings(true) err = t.framer.writeSettings(true)
} }
if err != nil { if err != nil {
t.Close() t.Close()
return nil, ConnectionErrorf("transport: %v", err) return nil, ConnectionErrorf(true, err, "transport: %v", err)
} }
// Adjust the connection flow control window if needed. // Adjust the connection flow control window if needed.
if delta := uint32(initialConnWindowSize - defaultWindowSize); delta > 0 { if delta := uint32(initialConnWindowSize - defaultWindowSize); delta > 0 {
if err := t.framer.writeWindowUpdate(true, 0, delta); err != nil { if err := t.framer.writeWindowUpdate(true, 0, delta); err != nil {
t.Close() t.Close()
return nil, ConnectionErrorf("transport: %v", err) return nil, ConnectionErrorf(true, err, "transport: %v", err)
} }
} }
go t.controller() go t.controller()
@ -199,6 +210,8 @@ func (t *http2Client) newStream(ctx context.Context, callHdr *CallHdr) *Stream {
// TODO(zhaoq): Handle uint32 overflow of Stream.id. // TODO(zhaoq): Handle uint32 overflow of Stream.id.
s := &Stream{ s := &Stream{
id: t.nextID, id: t.nextID,
done: make(chan struct{}),
goAway: make(chan struct{}),
method: callHdr.Method, method: callHdr.Method,
sendCompress: callHdr.SendCompress, sendCompress: callHdr.SendCompress,
buf: newRecvBuffer(), buf: newRecvBuffer(),
@ -213,8 +226,9 @@ func (t *http2Client) newStream(ctx context.Context, callHdr *CallHdr) *Stream {
// Make a stream be able to cancel the pending operations by itself. // Make a stream be able to cancel the pending operations by itself.
s.ctx, s.cancel = context.WithCancel(ctx) s.ctx, s.cancel = context.WithCancel(ctx)
s.dec = &recvBufferReader{ s.dec = &recvBufferReader{
ctx: s.ctx, ctx: s.ctx,
recv: s.buf, goAway: s.goAway,
recv: s.buf,
} }
return s return s
} }
@ -268,6 +282,10 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea
t.mu.Unlock() t.mu.Unlock()
return nil, ErrConnClosing return nil, ErrConnClosing
} }
if t.state == draining {
t.mu.Unlock()
return nil, ErrStreamDrain
}
if t.state != reachable { if t.state != reachable {
t.mu.Unlock() t.mu.Unlock()
return nil, ErrConnClosing return nil, ErrConnClosing
@ -275,7 +293,7 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea
checkStreamsQuota := t.streamsQuota != nil checkStreamsQuota := t.streamsQuota != nil
t.mu.Unlock() t.mu.Unlock()
if checkStreamsQuota { if checkStreamsQuota {
sq, err := wait(ctx, t.shutdownChan, t.streamsQuota.acquire()) sq, err := wait(ctx, nil, nil, t.shutdownChan, t.streamsQuota.acquire())
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -284,7 +302,7 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea
t.streamsQuota.add(sq - 1) t.streamsQuota.add(sq - 1)
} }
} }
if _, err := wait(ctx, t.shutdownChan, t.writableChan); err != nil { if _, err := wait(ctx, nil, nil, t.shutdownChan, t.writableChan); err != nil {
// Return the quota back now because there is no stream returned to the caller. // Return the quota back now because there is no stream returned to the caller.
if _, ok := err.(StreamError); ok && checkStreamsQuota { if _, ok := err.(StreamError); ok && checkStreamsQuota {
t.streamsQuota.add(1) t.streamsQuota.add(1)
@ -292,6 +310,15 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea
return nil, err return nil, err
} }
t.mu.Lock() t.mu.Lock()
if t.state == draining {
t.mu.Unlock()
if checkStreamsQuota {
t.streamsQuota.add(1)
}
// Need to make t writable again so that the rpc in flight can still proceed.
t.writableChan <- 0
return nil, ErrStreamDrain
}
if t.state != reachable { if t.state != reachable {
t.mu.Unlock() t.mu.Unlock()
return nil, ErrConnClosing return nil, ErrConnClosing
@ -326,7 +353,7 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea
t.hEnc.WriteField(hpack.HeaderField{Name: "grpc-encoding", Value: callHdr.SendCompress}) t.hEnc.WriteField(hpack.HeaderField{Name: "grpc-encoding", Value: callHdr.SendCompress})
} }
if timeout > 0 { if timeout > 0 {
t.hEnc.WriteField(hpack.HeaderField{Name: "grpc-timeout", Value: timeoutEncode(timeout)}) t.hEnc.WriteField(hpack.HeaderField{Name: "grpc-timeout", Value: encodeTimeout(timeout)})
} }
for k, v := range authData { for k, v := range authData {
// Capital header names are illegal in HTTP/2. // Capital header names are illegal in HTTP/2.
@ -381,7 +408,7 @@ func (t *http2Client) NewStream(ctx context.Context, callHdr *CallHdr) (_ *Strea
} }
if err != nil { if err != nil {
t.notifyError(err) t.notifyError(err)
return nil, ConnectionErrorf("transport: %v", err) return nil, ConnectionErrorf(true, err, "transport: %v", err)
} }
} }
t.writableChan <- 0 t.writableChan <- 0
@ -400,22 +427,17 @@ func (t *http2Client) CloseStream(s *Stream, err error) {
if t.streamsQuota != nil { if t.streamsQuota != nil {
updateStreams = true updateStreams = true
} }
if t.state == draining && len(t.activeStreams) == 1 { delete(t.activeStreams, s.id)
if t.state == draining && len(t.activeStreams) == 0 {
// The transport is draining and s is the last live stream on t. // The transport is draining and s is the last live stream on t.
t.mu.Unlock() t.mu.Unlock()
t.Close() t.Close()
return return
} }
delete(t.activeStreams, s.id)
t.mu.Unlock() t.mu.Unlock()
if updateStreams { if updateStreams {
t.streamsQuota.add(1) t.streamsQuota.add(1)
} }
// In case stream sending and receiving are invoked in separate
// goroutines (e.g., bi-directional streaming), the caller needs
// to call cancel on the stream to interrupt the blocking on
// other goroutines.
s.cancel()
s.mu.Lock() s.mu.Lock()
if q := s.fc.resetPendingData(); q > 0 { if q := s.fc.resetPendingData(); q > 0 {
if n := t.fc.onRead(q); n > 0 { if n := t.fc.onRead(q); n > 0 {
@ -442,13 +464,13 @@ func (t *http2Client) CloseStream(s *Stream, err error) {
// accessed any more. // accessed any more.
func (t *http2Client) Close() (err error) { func (t *http2Client) Close() (err error) {
t.mu.Lock() t.mu.Lock()
if t.state == reachable {
close(t.errorChan)
}
if t.state == closing { if t.state == closing {
t.mu.Unlock() t.mu.Unlock()
return return
} }
if t.state == reachable || t.state == draining {
close(t.errorChan)
}
t.state = closing t.state = closing
t.mu.Unlock() t.mu.Unlock()
close(t.shutdownChan) close(t.shutdownChan)
@ -472,10 +494,35 @@ func (t *http2Client) Close() (err error) {
func (t *http2Client) GracefulClose() error { func (t *http2Client) GracefulClose() error {
t.mu.Lock() t.mu.Lock()
if t.state == closing { switch t.state {
case unreachable:
// The server may close the connection concurrently. t is not available for
// any streams. Close it now.
t.mu.Unlock()
t.Close()
return nil
case closing:
t.mu.Unlock() t.mu.Unlock()
return nil return nil
} }
// Notify the streams which were initiated after the server sent GOAWAY.
select {
case <-t.goAway:
n := t.prevGoAwayID
if n == 0 && t.nextID > 1 {
n = t.nextID - 2
}
m := t.goAwayID + 2
if m == 2 {
m = 1
}
for i := m; i <= n; i += 2 {
if s, ok := t.activeStreams[i]; ok {
close(s.goAway)
}
}
default:
}
if t.state == draining { if t.state == draining {
t.mu.Unlock() t.mu.Unlock()
return nil return nil
@ -501,15 +548,15 @@ func (t *http2Client) Write(s *Stream, data []byte, opts *Options) error {
size := http2MaxFrameLen size := http2MaxFrameLen
s.sendQuotaPool.add(0) s.sendQuotaPool.add(0)
// Wait until the stream has some quota to send the data. // Wait until the stream has some quota to send the data.
sq, err := wait(s.ctx, t.shutdownChan, s.sendQuotaPool.acquire()) sq, err := wait(s.ctx, s.done, s.goAway, t.shutdownChan, s.sendQuotaPool.acquire())
if err != nil { if err != nil {
return err return err
} }
t.sendQuotaPool.add(0) t.sendQuotaPool.add(0)
// Wait until the transport has some quota to send the data. // Wait until the transport has some quota to send the data.
tq, err := wait(s.ctx, t.shutdownChan, t.sendQuotaPool.acquire()) tq, err := wait(s.ctx, s.done, s.goAway, t.shutdownChan, t.sendQuotaPool.acquire())
if err != nil { if err != nil {
if _, ok := err.(StreamError); ok { if _, ok := err.(StreamError); ok || err == io.EOF {
t.sendQuotaPool.cancel() t.sendQuotaPool.cancel()
} }
return err return err
@ -541,8 +588,8 @@ func (t *http2Client) Write(s *Stream, data []byte, opts *Options) error {
// Indicate there is a writer who is about to write a data frame. // Indicate there is a writer who is about to write a data frame.
t.framer.adjustNumWriters(1) t.framer.adjustNumWriters(1)
// Got some quota. Try to acquire writing privilege on the transport. // Got some quota. Try to acquire writing privilege on the transport.
if _, err := wait(s.ctx, t.shutdownChan, t.writableChan); err != nil { if _, err := wait(s.ctx, s.done, s.goAway, t.shutdownChan, t.writableChan); err != nil {
if _, ok := err.(StreamError); ok { if _, ok := err.(StreamError); ok || err == io.EOF {
// Return the connection quota back. // Return the connection quota back.
t.sendQuotaPool.add(len(p)) t.sendQuotaPool.add(len(p))
} }
@ -575,7 +622,7 @@ func (t *http2Client) Write(s *Stream, data []byte, opts *Options) error {
// invoked. // invoked.
if err := t.framer.writeData(forceFlush, s.id, endStream, p); err != nil { if err := t.framer.writeData(forceFlush, s.id, endStream, p); err != nil {
t.notifyError(err) t.notifyError(err)
return ConnectionErrorf("transport: %v", err) return ConnectionErrorf(true, err, "transport: %v", err)
} }
if t.framer.adjustNumWriters(-1) == 0 { if t.framer.adjustNumWriters(-1) == 0 {
t.framer.flushWrite() t.framer.flushWrite()
@ -590,11 +637,7 @@ func (t *http2Client) Write(s *Stream, data []byte, opts *Options) error {
} }
s.mu.Lock() s.mu.Lock()
if s.state != streamDone { if s.state != streamDone {
if s.state == streamReadDone { s.state = streamWriteDone
s.state = streamDone
} else {
s.state = streamWriteDone
}
} }
s.mu.Unlock() s.mu.Unlock()
return nil return nil
@ -627,7 +670,7 @@ func (t *http2Client) updateWindow(s *Stream, n uint32) {
func (t *http2Client) handleData(f *http2.DataFrame) { func (t *http2Client) handleData(f *http2.DataFrame) {
size := len(f.Data()) size := len(f.Data())
if err := t.fc.onData(uint32(size)); err != nil { if err := t.fc.onData(uint32(size)); err != nil {
t.notifyError(ConnectionErrorf("%v", err)) t.notifyError(ConnectionErrorf(true, err, "%v", err))
return return
} }
// Select the right stream to dispatch. // Select the right stream to dispatch.
@ -652,6 +695,7 @@ func (t *http2Client) handleData(f *http2.DataFrame) {
s.state = streamDone s.state = streamDone
s.statusCode = codes.Internal s.statusCode = codes.Internal
s.statusDesc = err.Error() s.statusDesc = err.Error()
close(s.done)
s.mu.Unlock() s.mu.Unlock()
s.write(recvMsg{err: io.EOF}) s.write(recvMsg{err: io.EOF})
t.controlBuf.put(&resetStream{s.id, http2.ErrCodeFlowControl}) t.controlBuf.put(&resetStream{s.id, http2.ErrCodeFlowControl})
@ -669,13 +713,14 @@ func (t *http2Client) handleData(f *http2.DataFrame) {
// the read direction is closed, and set the status appropriately. // the read direction is closed, and set the status appropriately.
if f.FrameHeader.Flags.Has(http2.FlagDataEndStream) { if f.FrameHeader.Flags.Has(http2.FlagDataEndStream) {
s.mu.Lock() s.mu.Lock()
if s.state == streamWriteDone { if s.state == streamDone {
s.state = streamDone s.mu.Unlock()
} else { return
s.state = streamReadDone
} }
s.state = streamDone
s.statusCode = codes.Internal s.statusCode = codes.Internal
s.statusDesc = "server closed the stream without sending trailers" s.statusDesc = "server closed the stream without sending trailers"
close(s.done)
s.mu.Unlock() s.mu.Unlock()
s.write(recvMsg{err: io.EOF}) s.write(recvMsg{err: io.EOF})
} }
@ -701,6 +746,8 @@ func (t *http2Client) handleRSTStream(f *http2.RSTStreamFrame) {
grpclog.Println("transport: http2Client.handleRSTStream found no mapped gRPC status for the received http2 error ", f.ErrCode) grpclog.Println("transport: http2Client.handleRSTStream found no mapped gRPC status for the received http2 error ", f.ErrCode)
s.statusCode = codes.Unknown s.statusCode = codes.Unknown
} }
s.statusDesc = fmt.Sprintf("stream terminated by RST_STREAM with error code: %d", f.ErrCode)
close(s.done)
s.mu.Unlock() s.mu.Unlock()
s.write(recvMsg{err: io.EOF}) s.write(recvMsg{err: io.EOF})
} }
@ -725,7 +772,32 @@ func (t *http2Client) handlePing(f *http2.PingFrame) {
} }
func (t *http2Client) handleGoAway(f *http2.GoAwayFrame) { func (t *http2Client) handleGoAway(f *http2.GoAwayFrame) {
// TODO(zhaoq): GoAwayFrame handler to be implemented t.mu.Lock()
if t.state == reachable || t.state == draining {
if f.LastStreamID > 0 && f.LastStreamID%2 != 1 {
t.mu.Unlock()
t.notifyError(ConnectionErrorf(true, nil, "received illegal http2 GOAWAY frame: stream ID %d is even", f.LastStreamID))
return
}
select {
case <-t.goAway:
id := t.goAwayID
// t.goAway has been closed (i.e.,multiple GoAways).
if id < f.LastStreamID {
t.mu.Unlock()
t.notifyError(ConnectionErrorf(true, nil, "received illegal http2 GOAWAY frame: previously recv GOAWAY frame with LastStramID %d, currently recv %d", id, f.LastStreamID))
return
}
t.prevGoAwayID = id
t.goAwayID = f.LastStreamID
t.mu.Unlock()
return
default:
}
t.goAwayID = f.LastStreamID
close(t.goAway)
}
t.mu.Unlock()
} }
func (t *http2Client) handleWindowUpdate(f *http2.WindowUpdateFrame) { func (t *http2Client) handleWindowUpdate(f *http2.WindowUpdateFrame) {
@ -777,11 +849,11 @@ func (t *http2Client) operateHeaders(frame *http2.MetaHeadersFrame) {
if len(state.mdata) > 0 { if len(state.mdata) > 0 {
s.trailer = state.mdata s.trailer = state.mdata
} }
s.state = streamDone
s.statusCode = state.statusCode s.statusCode = state.statusCode
s.statusDesc = state.statusDesc s.statusDesc = state.statusDesc
close(s.done)
s.state = streamDone
s.mu.Unlock() s.mu.Unlock()
s.write(recvMsg{err: io.EOF}) s.write(recvMsg{err: io.EOF})
} }
@ -934,13 +1006,22 @@ func (t *http2Client) Error() <-chan struct{} {
return t.errorChan return t.errorChan
} }
func (t *http2Client) GoAway() <-chan struct{} {
return t.goAway
}
func (t *http2Client) notifyError(err error) { func (t *http2Client) notifyError(err error) {
t.mu.Lock() t.mu.Lock()
defer t.mu.Unlock()
// make sure t.errorChan is closed only once. // make sure t.errorChan is closed only once.
if t.state == draining {
t.mu.Unlock()
t.Close()
return
}
if t.state == reachable { if t.state == reachable {
t.state = unreachable t.state = unreachable
close(t.errorChan) close(t.errorChan)
grpclog.Printf("transport: http2Client.notifyError got notified that the client transport was broken %v.", err) grpclog.Printf("transport: http2Client.notifyError got notified that the client transport was broken %v.", err)
} }
t.mu.Unlock()
} }

View File

@ -100,18 +100,23 @@ func newHTTP2Server(conn net.Conn, maxStreams uint32, authInfo credentials.AuthI
if maxStreams == 0 { if maxStreams == 0 {
maxStreams = math.MaxUint32 maxStreams = math.MaxUint32
} else { } else {
settings = append(settings, http2.Setting{http2.SettingMaxConcurrentStreams, maxStreams}) settings = append(settings, http2.Setting{
ID: http2.SettingMaxConcurrentStreams,
Val: maxStreams,
})
} }
if initialWindowSize != defaultWindowSize { if initialWindowSize != defaultWindowSize {
settings = append(settings, http2.Setting{http2.SettingInitialWindowSize, uint32(initialWindowSize)}) settings = append(settings, http2.Setting{
ID: http2.SettingInitialWindowSize,
Val: uint32(initialWindowSize)})
} }
if err := framer.writeSettings(true, settings...); err != nil { if err := framer.writeSettings(true, settings...); err != nil {
return nil, ConnectionErrorf("transport: %v", err) return nil, ConnectionErrorf(true, err, "transport: %v", err)
} }
// Adjust the connection flow control window if needed. // Adjust the connection flow control window if needed.
if delta := uint32(initialConnWindowSize - defaultWindowSize); delta > 0 { if delta := uint32(initialConnWindowSize - defaultWindowSize); delta > 0 {
if err := framer.writeWindowUpdate(true, 0, delta); err != nil { if err := framer.writeWindowUpdate(true, 0, delta); err != nil {
return nil, ConnectionErrorf("transport: %v", err) return nil, ConnectionErrorf(true, err, "transport: %v", err)
} }
} }
var buf bytes.Buffer var buf bytes.Buffer
@ -137,7 +142,7 @@ func newHTTP2Server(conn net.Conn, maxStreams uint32, authInfo credentials.AuthI
} }
// operateHeader takes action on the decoded headers. // operateHeader takes action on the decoded headers.
func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func(*Stream)) { func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func(*Stream)) (close bool) {
buf := newRecvBuffer() buf := newRecvBuffer()
s := &Stream{ s := &Stream{
id: frame.Header().StreamID, id: frame.Header().StreamID,
@ -200,6 +205,13 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func(
t.controlBuf.put(&resetStream{s.id, http2.ErrCodeRefusedStream}) t.controlBuf.put(&resetStream{s.id, http2.ErrCodeRefusedStream})
return return
} }
if s.id%2 != 1 || s.id <= t.maxStreamID {
t.mu.Unlock()
// illegal gRPC stream id.
grpclog.Println("transport: http2Server.HandleStreams received an illegal stream id: ", s.id)
return true
}
t.maxStreamID = s.id
s.sendQuotaPool = newQuotaPool(int(t.streamSendQuota)) s.sendQuotaPool = newQuotaPool(int(t.streamSendQuota))
t.activeStreams[s.id] = s t.activeStreams[s.id] = s
t.mu.Unlock() t.mu.Unlock()
@ -207,6 +219,7 @@ func (t *http2Server) operateHeaders(frame *http2.MetaHeadersFrame, handle func(
t.updateWindow(s, uint32(n)) t.updateWindow(s, uint32(n))
} }
handle(s) handle(s)
return
} }
// HandleStreams receives incoming streams using the given handler. This is // HandleStreams receives incoming streams using the given handler. This is
@ -226,6 +239,10 @@ func (t *http2Server) HandleStreams(handle func(*Stream)) {
} }
frame, err := t.framer.readFrame() frame, err := t.framer.readFrame()
if err == io.EOF || err == io.ErrUnexpectedEOF {
t.Close()
return
}
if err != nil { if err != nil {
grpclog.Printf("transport: http2Server.HandleStreams failed to read frame: %v", err) grpclog.Printf("transport: http2Server.HandleStreams failed to read frame: %v", err)
t.Close() t.Close()
@ -252,20 +269,20 @@ func (t *http2Server) HandleStreams(handle func(*Stream)) {
t.controlBuf.put(&resetStream{se.StreamID, se.Code}) t.controlBuf.put(&resetStream{se.StreamID, se.Code})
continue continue
} }
if err == io.EOF || err == io.ErrUnexpectedEOF {
t.Close()
return
}
grpclog.Printf("transport: http2Server.HandleStreams failed to read frame: %v", err)
t.Close() t.Close()
return return
} }
switch frame := frame.(type) { switch frame := frame.(type) {
case *http2.MetaHeadersFrame: case *http2.MetaHeadersFrame:
id := frame.Header().StreamID if t.operateHeaders(frame, handle) {
if id%2 != 1 || id <= t.maxStreamID {
// illegal gRPC stream id.
grpclog.Println("transport: http2Server.HandleStreams received an illegal stream id: ", id)
t.Close() t.Close()
break break
} }
t.maxStreamID = id
t.operateHeaders(frame, handle)
case *http2.DataFrame: case *http2.DataFrame:
t.handleData(frame) t.handleData(frame)
case *http2.RSTStreamFrame: case *http2.RSTStreamFrame:
@ -277,7 +294,7 @@ func (t *http2Server) HandleStreams(handle func(*Stream)) {
case *http2.WindowUpdateFrame: case *http2.WindowUpdateFrame:
t.handleWindowUpdate(frame) t.handleWindowUpdate(frame)
case *http2.GoAwayFrame: case *http2.GoAwayFrame:
break // TODO: Handle GoAway from the client appropriately.
default: default:
grpclog.Printf("transport: http2Server.HandleStreams found unhandled frame type %v.", frame) grpclog.Printf("transport: http2Server.HandleStreams found unhandled frame type %v.", frame)
} }
@ -359,11 +376,7 @@ func (t *http2Server) handleData(f *http2.DataFrame) {
// Received the end of stream from the client. // Received the end of stream from the client.
s.mu.Lock() s.mu.Lock()
if s.state != streamDone { if s.state != streamDone {
if s.state == streamWriteDone { s.state = streamReadDone
s.state = streamDone
} else {
s.state = streamReadDone
}
} }
s.mu.Unlock() s.mu.Unlock()
s.write(recvMsg{err: io.EOF}) s.write(recvMsg{err: io.EOF})
@ -435,7 +448,7 @@ func (t *http2Server) writeHeaders(s *Stream, b *bytes.Buffer, endStream bool) e
} }
if err != nil { if err != nil {
t.Close() t.Close()
return ConnectionErrorf("transport: %v", err) return ConnectionErrorf(true, err, "transport: %v", err)
} }
} }
return nil return nil
@ -450,7 +463,7 @@ func (t *http2Server) WriteHeader(s *Stream, md metadata.MD) error {
} }
s.headerOk = true s.headerOk = true
s.mu.Unlock() s.mu.Unlock()
if _, err := wait(s.ctx, t.shutdownChan, t.writableChan); err != nil { if _, err := wait(s.ctx, nil, nil, t.shutdownChan, t.writableChan); err != nil {
return err return err
} }
t.hBuf.Reset() t.hBuf.Reset()
@ -490,7 +503,7 @@ func (t *http2Server) WriteStatus(s *Stream, statusCode codes.Code, statusDesc s
headersSent = true headersSent = true
} }
s.mu.Unlock() s.mu.Unlock()
if _, err := wait(s.ctx, t.shutdownChan, t.writableChan); err != nil { if _, err := wait(s.ctx, nil, nil, t.shutdownChan, t.writableChan); err != nil {
return err return err
} }
t.hBuf.Reset() t.hBuf.Reset()
@ -503,7 +516,7 @@ func (t *http2Server) WriteStatus(s *Stream, statusCode codes.Code, statusDesc s
Name: "grpc-status", Name: "grpc-status",
Value: strconv.Itoa(int(statusCode)), Value: strconv.Itoa(int(statusCode)),
}) })
t.hEnc.WriteField(hpack.HeaderField{Name: "grpc-message", Value: statusDesc}) t.hEnc.WriteField(hpack.HeaderField{Name: "grpc-message", Value: encodeGrpcMessage(statusDesc)})
// Attach the trailer metadata. // Attach the trailer metadata.
for k, v := range s.trailer { for k, v := range s.trailer {
// Clients don't tolerate reading restricted headers after some non restricted ones were sent. // Clients don't tolerate reading restricted headers after some non restricted ones were sent.
@ -539,7 +552,7 @@ func (t *http2Server) Write(s *Stream, data []byte, opts *Options) error {
} }
s.mu.Unlock() s.mu.Unlock()
if writeHeaderFrame { if writeHeaderFrame {
if _, err := wait(s.ctx, t.shutdownChan, t.writableChan); err != nil { if _, err := wait(s.ctx, nil, nil, t.shutdownChan, t.writableChan); err != nil {
return err return err
} }
t.hBuf.Reset() t.hBuf.Reset()
@ -555,7 +568,7 @@ func (t *http2Server) Write(s *Stream, data []byte, opts *Options) error {
} }
if err := t.framer.writeHeaders(false, p); err != nil { if err := t.framer.writeHeaders(false, p); err != nil {
t.Close() t.Close()
return ConnectionErrorf("transport: %v", err) return ConnectionErrorf(true, err, "transport: %v", err)
} }
t.writableChan <- 0 t.writableChan <- 0
} }
@ -567,13 +580,13 @@ func (t *http2Server) Write(s *Stream, data []byte, opts *Options) error {
size := http2MaxFrameLen size := http2MaxFrameLen
s.sendQuotaPool.add(0) s.sendQuotaPool.add(0)
// Wait until the stream has some quota to send the data. // Wait until the stream has some quota to send the data.
sq, err := wait(s.ctx, t.shutdownChan, s.sendQuotaPool.acquire()) sq, err := wait(s.ctx, nil, nil, t.shutdownChan, s.sendQuotaPool.acquire())
if err != nil { if err != nil {
return err return err
} }
t.sendQuotaPool.add(0) t.sendQuotaPool.add(0)
// Wait until the transport has some quota to send the data. // Wait until the transport has some quota to send the data.
tq, err := wait(s.ctx, t.shutdownChan, t.sendQuotaPool.acquire()) tq, err := wait(s.ctx, nil, nil, t.shutdownChan, t.sendQuotaPool.acquire())
if err != nil { if err != nil {
if _, ok := err.(StreamError); ok { if _, ok := err.(StreamError); ok {
t.sendQuotaPool.cancel() t.sendQuotaPool.cancel()
@ -599,7 +612,7 @@ func (t *http2Server) Write(s *Stream, data []byte, opts *Options) error {
t.framer.adjustNumWriters(1) t.framer.adjustNumWriters(1)
// Got some quota. Try to acquire writing privilege on the // Got some quota. Try to acquire writing privilege on the
// transport. // transport.
if _, err := wait(s.ctx, t.shutdownChan, t.writableChan); err != nil { if _, err := wait(s.ctx, nil, nil, t.shutdownChan, t.writableChan); err != nil {
if _, ok := err.(StreamError); ok { if _, ok := err.(StreamError); ok {
// Return the connection quota back. // Return the connection quota back.
t.sendQuotaPool.add(ps) t.sendQuotaPool.add(ps)
@ -629,7 +642,7 @@ func (t *http2Server) Write(s *Stream, data []byte, opts *Options) error {
} }
if err := t.framer.writeData(forceFlush, s.id, false, p); err != nil { if err := t.framer.writeData(forceFlush, s.id, false, p); err != nil {
t.Close() t.Close()
return ConnectionErrorf("transport: %v", err) return ConnectionErrorf(true, err, "transport: %v", err)
} }
if t.framer.adjustNumWriters(-1) == 0 { if t.framer.adjustNumWriters(-1) == 0 {
t.framer.flushWrite() t.framer.flushWrite()
@ -674,6 +687,17 @@ func (t *http2Server) controller() {
} }
case *resetStream: case *resetStream:
t.framer.writeRSTStream(true, i.streamID, i.code) t.framer.writeRSTStream(true, i.streamID, i.code)
case *goAway:
t.mu.Lock()
if t.state == closing {
t.mu.Unlock()
// The transport is closing.
return
}
sid := t.maxStreamID
t.state = draining
t.mu.Unlock()
t.framer.writeGoAway(true, sid, http2.ErrCodeNo, nil)
case *flushIO: case *flushIO:
t.framer.flushWrite() t.framer.flushWrite()
case *ping: case *ping:
@ -719,6 +743,9 @@ func (t *http2Server) Close() (err error) {
func (t *http2Server) closeStream(s *Stream) { func (t *http2Server) closeStream(s *Stream) {
t.mu.Lock() t.mu.Lock()
delete(t.activeStreams, s.id) delete(t.activeStreams, s.id)
if t.state == draining && len(t.activeStreams) == 0 {
defer t.Close()
}
t.mu.Unlock() t.mu.Unlock()
// In case stream sending and receiving are invoked in separate // In case stream sending and receiving are invoked in separate
// goroutines (e.g., bi-directional streaming), cancel needs to be // goroutines (e.g., bi-directional streaming), cancel needs to be
@ -741,3 +768,7 @@ func (t *http2Server) closeStream(s *Stream) {
func (t *http2Server) RemoteAddr() net.Addr { func (t *http2Server) RemoteAddr() net.Addr {
return t.conn.RemoteAddr() return t.conn.RemoteAddr()
} }
func (t *http2Server) Drain() {
t.controlBuf.put(&goAway{})
}

View File

@ -35,6 +35,7 @@ package transport
import ( import (
"bufio" "bufio"
"bytes"
"fmt" "fmt"
"io" "io"
"net" "net"
@ -174,11 +175,11 @@ func (d *decodeState) processHeaderField(f hpack.HeaderField) {
} }
d.statusCode = codes.Code(code) d.statusCode = codes.Code(code)
case "grpc-message": case "grpc-message":
d.statusDesc = f.Value d.statusDesc = decodeGrpcMessage(f.Value)
case "grpc-timeout": case "grpc-timeout":
d.timeoutSet = true d.timeoutSet = true
var err error var err error
d.timeout, err = timeoutDecode(f.Value) d.timeout, err = decodeTimeout(f.Value)
if err != nil { if err != nil {
d.setErr(StreamErrorf(codes.Internal, "transport: malformed time-out: %v", err)) d.setErr(StreamErrorf(codes.Internal, "transport: malformed time-out: %v", err))
return return
@ -251,7 +252,7 @@ func div(d, r time.Duration) int64 {
} }
// TODO(zhaoq): It is the simplistic and not bandwidth efficient. Improve it. // TODO(zhaoq): It is the simplistic and not bandwidth efficient. Improve it.
func timeoutEncode(t time.Duration) string { func encodeTimeout(t time.Duration) string {
if d := div(t, time.Nanosecond); d <= maxTimeoutValue { if d := div(t, time.Nanosecond); d <= maxTimeoutValue {
return strconv.FormatInt(d, 10) + "n" return strconv.FormatInt(d, 10) + "n"
} }
@ -271,7 +272,7 @@ func timeoutEncode(t time.Duration) string {
return strconv.FormatInt(div(t, time.Hour), 10) + "H" return strconv.FormatInt(div(t, time.Hour), 10) + "H"
} }
func timeoutDecode(s string) (time.Duration, error) { func decodeTimeout(s string) (time.Duration, error) {
size := len(s) size := len(s)
if size < 2 { if size < 2 {
return 0, fmt.Errorf("transport: timeout string is too short: %q", s) return 0, fmt.Errorf("transport: timeout string is too short: %q", s)
@ -288,6 +289,80 @@ func timeoutDecode(s string) (time.Duration, error) {
return d * time.Duration(t), nil return d * time.Duration(t), nil
} }
const (
spaceByte = ' '
tildaByte = '~'
percentByte = '%'
)
// encodeGrpcMessage is used to encode status code in header field
// "grpc-message".
// It checks to see if each individual byte in msg is an
// allowable byte, and then either percent encoding or passing it through.
// When percent encoding, the byte is converted into hexadecimal notation
// with a '%' prepended.
func encodeGrpcMessage(msg string) string {
if msg == "" {
return ""
}
lenMsg := len(msg)
for i := 0; i < lenMsg; i++ {
c := msg[i]
if !(c >= spaceByte && c < tildaByte && c != percentByte) {
return encodeGrpcMessageUnchecked(msg)
}
}
return msg
}
func encodeGrpcMessageUnchecked(msg string) string {
var buf bytes.Buffer
lenMsg := len(msg)
for i := 0; i < lenMsg; i++ {
c := msg[i]
if c >= spaceByte && c < tildaByte && c != percentByte {
buf.WriteByte(c)
} else {
buf.WriteString(fmt.Sprintf("%%%02X", c))
}
}
return buf.String()
}
// decodeGrpcMessage decodes the msg encoded by encodeGrpcMessage.
func decodeGrpcMessage(msg string) string {
if msg == "" {
return ""
}
lenMsg := len(msg)
for i := 0; i < lenMsg; i++ {
if msg[i] == percentByte && i+2 < lenMsg {
return decodeGrpcMessageUnchecked(msg)
}
}
return msg
}
func decodeGrpcMessageUnchecked(msg string) string {
var buf bytes.Buffer
lenMsg := len(msg)
for i := 0; i < lenMsg; i++ {
c := msg[i]
if c == percentByte && i+2 < lenMsg {
parsed, err := strconv.ParseInt(msg[i+1:i+3], 16, 8)
if err != nil {
buf.WriteByte(c)
} else {
buf.WriteByte(byte(parsed))
i += 2
}
} else {
buf.WriteByte(c)
}
}
return buf.String()
}
type framer struct { type framer struct {
numWriters int32 numWriters int32
reader io.Reader reader io.Reader

View File

@ -0,0 +1,51 @@
// +build !go1.6
/*
* Copyright 2016, Google Inc.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*
*/
package transport
import (
"net"
"time"
"golang.org/x/net/context"
)
// dialContext connects to the address on the named network.
func dialContext(ctx context.Context, network, address string) (net.Conn, error) {
var dialer net.Dialer
if deadline, ok := ctx.Deadline(); ok {
dialer.Timeout = deadline.Sub(time.Now())
}
return dialer.Dial(network, address)
}

View File

@ -44,7 +44,6 @@ import (
"io" "io"
"net" "net"
"sync" "sync"
"time"
"golang.org/x/net/context" "golang.org/x/net/context"
"golang.org/x/net/trace" "golang.org/x/net/trace"
@ -120,10 +119,11 @@ func (b *recvBuffer) get() <-chan item {
// recvBufferReader implements io.Reader interface to read the data from // recvBufferReader implements io.Reader interface to read the data from
// recvBuffer. // recvBuffer.
type recvBufferReader struct { type recvBufferReader struct {
ctx context.Context ctx context.Context
recv *recvBuffer goAway chan struct{}
last *bytes.Reader // Stores the remaining data in the previous calls. recv *recvBuffer
err error last *bytes.Reader // Stores the remaining data in the previous calls.
err error
} }
// Read reads the next len(p) bytes from last. If last is drained, it tries to // Read reads the next len(p) bytes from last. If last is drained, it tries to
@ -141,6 +141,8 @@ func (r *recvBufferReader) Read(p []byte) (n int, err error) {
select { select {
case <-r.ctx.Done(): case <-r.ctx.Done():
return 0, ContextErr(r.ctx.Err()) return 0, ContextErr(r.ctx.Err())
case <-r.goAway:
return 0, ErrStreamDrain
case i := <-r.recv.get(): case i := <-r.recv.get():
r.recv.load() r.recv.load()
m := i.(*recvMsg) m := i.(*recvMsg)
@ -158,7 +160,7 @@ const (
streamActive streamState = iota streamActive streamState = iota
streamWriteDone // EndStream sent streamWriteDone // EndStream sent
streamReadDone // EndStream received streamReadDone // EndStream received
streamDone // sendDone and recvDone or RSTStreamFrame is sent or received. streamDone // the entire stream is finished.
) )
// Stream represents an RPC in the transport layer. // Stream represents an RPC in the transport layer.
@ -169,6 +171,10 @@ type Stream struct {
// ctx is the associated context of the stream. // ctx is the associated context of the stream.
ctx context.Context ctx context.Context
cancel context.CancelFunc cancel context.CancelFunc
// done is closed when the final status arrives.
done chan struct{}
// goAway is closed when the server sent GoAways signal before this stream was initiated.
goAway chan struct{}
// method records the associated RPC method of the stream. // method records the associated RPC method of the stream.
method string method string
recvCompress string recvCompress string
@ -214,6 +220,18 @@ func (s *Stream) SetSendCompress(str string) {
s.sendCompress = str s.sendCompress = str
} }
// Done returns a chanel which is closed when it receives the final status
// from the server.
func (s *Stream) Done() <-chan struct{} {
return s.done
}
// GoAway returns a channel which is closed when the server sent GoAways signal
// before this stream was initiated.
func (s *Stream) GoAway() <-chan struct{} {
return s.goAway
}
// Header acquires the key-value pairs of header metadata once it // Header acquires the key-value pairs of header metadata once it
// is available. It blocks until i) the metadata is ready or ii) there is no // is available. It blocks until i) the metadata is ready or ii) there is no
// header metadata or iii) the stream is cancelled/expired. // header metadata or iii) the stream is cancelled/expired.
@ -221,6 +239,8 @@ func (s *Stream) Header() (metadata.MD, error) {
select { select {
case <-s.ctx.Done(): case <-s.ctx.Done():
return nil, ContextErr(s.ctx.Err()) return nil, ContextErr(s.ctx.Err())
case <-s.goAway:
return nil, ErrStreamDrain
case <-s.headerChan: case <-s.headerChan:
return s.header.Copy(), nil return s.header.Copy(), nil
} }
@ -335,19 +355,17 @@ type ConnectOptions struct {
// UserAgent is the application user agent. // UserAgent is the application user agent.
UserAgent string UserAgent string
// Dialer specifies how to dial a network address. // Dialer specifies how to dial a network address.
Dialer func(string, time.Duration) (net.Conn, error) Dialer func(context.Context, string) (net.Conn, error)
// PerRPCCredentials stores the PerRPCCredentials required to issue RPCs. // PerRPCCredentials stores the PerRPCCredentials required to issue RPCs.
PerRPCCredentials []credentials.PerRPCCredentials PerRPCCredentials []credentials.PerRPCCredentials
// TransportCredentials stores the Authenticator required to setup a client connection. // TransportCredentials stores the Authenticator required to setup a client connection.
TransportCredentials credentials.TransportCredentials TransportCredentials credentials.TransportCredentials
// Timeout specifies the timeout for dialing a ClientTransport.
Timeout time.Duration
} }
// NewClientTransport establishes the transport with the required ConnectOptions // NewClientTransport establishes the transport with the required ConnectOptions
// and returns it to the caller. // and returns it to the caller.
func NewClientTransport(target string, opts *ConnectOptions) (ClientTransport, error) { func NewClientTransport(ctx context.Context, target string, opts ConnectOptions) (ClientTransport, error) {
return newHTTP2Client(target, opts) return newHTTP2Client(ctx, target, opts)
} }
// Options provides additional hints and information for message // Options provides additional hints and information for message
@ -417,6 +435,11 @@ type ClientTransport interface {
// and create a new one) in error case. It should not return nil // and create a new one) in error case. It should not return nil
// once the transport is initiated. // once the transport is initiated.
Error() <-chan struct{} Error() <-chan struct{}
// GoAway returns a channel that is closed when ClientTranspor
// receives the draining signal from the server (e.g., GOAWAY frame in
// HTTP/2).
GoAway() <-chan struct{}
} }
// ServerTransport is the common interface for all gRPC server-side transport // ServerTransport is the common interface for all gRPC server-side transport
@ -448,6 +471,9 @@ type ServerTransport interface {
// RemoteAddr returns the remote network address. // RemoteAddr returns the remote network address.
RemoteAddr() net.Addr RemoteAddr() net.Addr
// Drain notifies the client this ServerTransport stops accepting new RPCs.
Drain()
} }
// StreamErrorf creates an StreamError with the specified error code and description. // StreamErrorf creates an StreamError with the specified error code and description.
@ -459,9 +485,11 @@ func StreamErrorf(c codes.Code, format string, a ...interface{}) StreamError {
} }
// ConnectionErrorf creates an ConnectionError with the specified error description. // ConnectionErrorf creates an ConnectionError with the specified error description.
func ConnectionErrorf(format string, a ...interface{}) ConnectionError { func ConnectionErrorf(temp bool, e error, format string, a ...interface{}) ConnectionError {
return ConnectionError{ return ConnectionError{
Desc: fmt.Sprintf(format, a...), Desc: fmt.Sprintf(format, a...),
temp: temp,
err: e,
} }
} }
@ -469,14 +497,36 @@ func ConnectionErrorf(format string, a ...interface{}) ConnectionError {
// entire connection and the retry of all the active streams. // entire connection and the retry of all the active streams.
type ConnectionError struct { type ConnectionError struct {
Desc string Desc string
temp bool
err error
} }
func (e ConnectionError) Error() string { func (e ConnectionError) Error() string {
return fmt.Sprintf("connection error: desc = %q", e.Desc) return fmt.Sprintf("connection error: desc = %q", e.Desc)
} }
// ErrConnClosing indicates that the transport is closing. // Temporary indicates if this connection error is temporary or fatal.
var ErrConnClosing = ConnectionError{Desc: "transport is closing"} func (e ConnectionError) Temporary() bool {
return e.temp
}
// Origin returns the original error of this connection error.
func (e ConnectionError) Origin() error {
// Never return nil error here.
// If the original error is nil, return itself.
if e.err == nil {
return e
}
return e.err
}
var (
// ErrConnClosing indicates that the transport is closing.
ErrConnClosing = ConnectionError{Desc: "transport is closing", temp: true}
// ErrStreamDrain indicates that the stream is rejected by the server because
// the server stops accepting new RPCs.
ErrStreamDrain = StreamErrorf(codes.Unavailable, "the server stops accepting new RPCs")
)
// StreamError is an error that only affects one stream within a connection. // StreamError is an error that only affects one stream within a connection.
type StreamError struct { type StreamError struct {
@ -501,12 +551,25 @@ func ContextErr(err error) StreamError {
// wait blocks until it can receive from ctx.Done, closing, or proceed. // wait blocks until it can receive from ctx.Done, closing, or proceed.
// If it receives from ctx.Done, it returns 0, the StreamError for ctx.Err. // If it receives from ctx.Done, it returns 0, the StreamError for ctx.Err.
// If it receives from done, it returns 0, io.EOF if ctx is not done; otherwise
// it return the StreamError for ctx.Err.
// If it receives from goAway, it returns 0, ErrStreamDrain.
// If it receives from closing, it returns 0, ErrConnClosing. // If it receives from closing, it returns 0, ErrConnClosing.
// If it receives from proceed, it returns the received integer, nil. // If it receives from proceed, it returns the received integer, nil.
func wait(ctx context.Context, closing <-chan struct{}, proceed <-chan int) (int, error) { func wait(ctx context.Context, done, goAway, closing <-chan struct{}, proceed <-chan int) (int, error) {
select { select {
case <-ctx.Done(): case <-ctx.Done():
return 0, ContextErr(ctx.Err()) return 0, ContextErr(ctx.Err())
case <-done:
// User cancellation has precedence.
select {
case <-ctx.Done():
return 0, ContextErr(ctx.Err())
default:
}
return 0, io.EOF
case <-goAway:
return 0, ErrStreamDrain
case <-closing: case <-closing:
return 0, ErrConnClosing return 0, ErrConnClosing
case i := <-proceed: case i := <-proceed:

View File

@ -53,8 +53,8 @@ func SRVGetCluster(name, dns string, defaultToken string, apurls types.URLs) (st
return err return err
} }
for _, srv := range addrs { for _, srv := range addrs {
target := strings.TrimSuffix(srv.Target, ".") port := fmt.Sprintf("%d", srv.Port)
host := net.JoinHostPort(target, fmt.Sprintf("%d", srv.Port)) host := net.JoinHostPort(srv.Target, port)
tcpAddr, err := resolveTCPAddr("tcp", host) tcpAddr, err := resolveTCPAddr("tcp", host)
if err != nil { if err != nil {
plog.Warningf("couldn't resolve host %s during SRV discovery", host) plog.Warningf("couldn't resolve host %s during SRV discovery", host)
@ -70,8 +70,11 @@ func SRVGetCluster(name, dns string, defaultToken string, apurls types.URLs) (st
n = fmt.Sprintf("%d", tempName) n = fmt.Sprintf("%d", tempName)
tempName += 1 tempName += 1
} }
stringParts = append(stringParts, fmt.Sprintf("%s=%s%s", n, prefix, host)) // SRV records have a trailing dot but URL shouldn't.
plog.Noticef("got bootstrap from DNS for %s at %s%s", service, prefix, host) shortHost := strings.TrimSuffix(srv.Target, ".")
urlHost := net.JoinHostPort(shortHost, port)
stringParts = append(stringParts, fmt.Sprintf("%s=%s%s", n, prefix, urlHost))
plog.Noticef("got bootstrap from DNS for %s at %s%s", service, prefix, urlHost)
} }
return nil return nil
} }

View File

@ -17,6 +17,7 @@ package discovery
import ( import (
"errors" "errors"
"net" "net"
"strings"
"testing" "testing"
"github.com/coreos/etcd/pkg/testutil" "github.com/coreos/etcd/pkg/testutil"
@ -29,11 +30,22 @@ func TestSRVGetCluster(t *testing.T) {
}() }()
name := "dnsClusterTest" name := "dnsClusterTest"
dns := map[string]string{
"1.example.com.:2480": "10.0.0.1:2480",
"2.example.com.:2480": "10.0.0.2:2480",
"3.example.com.:2480": "10.0.0.3:2480",
"4.example.com.:2380": "10.0.0.3:2380",
}
srvAll := []*net.SRV{
{Target: "1.example.com.", Port: 2480},
{Target: "2.example.com.", Port: 2480},
{Target: "3.example.com.", Port: 2480},
}
tests := []struct { tests := []struct {
withSSL []*net.SRV withSSL []*net.SRV
withoutSSL []*net.SRV withoutSSL []*net.SRV
urls []string urls []string
dns map[string]string
expected string expected string
}{ }{
@ -41,61 +53,50 @@ func TestSRVGetCluster(t *testing.T) {
[]*net.SRV{}, []*net.SRV{},
[]*net.SRV{}, []*net.SRV{},
nil, nil,
nil,
"", "",
}, },
{ {
[]*net.SRV{ srvAll,
{Target: "10.0.0.1", Port: 2480},
{Target: "10.0.0.2", Port: 2480},
{Target: "10.0.0.3", Port: 2480},
},
[]*net.SRV{}, []*net.SRV{},
nil, nil,
"0=https://1.example.com:2480,1=https://2.example.com:2480,2=https://3.example.com:2480",
},
{
srvAll,
[]*net.SRV{{Target: "4.example.com.", Port: 2380}},
nil, nil,
"0=https://10.0.0.1:2480,1=https://10.0.0.2:2480,2=https://10.0.0.3:2480", "0=https://1.example.com:2480,1=https://2.example.com:2480,2=https://3.example.com:2480,3=http://4.example.com:2380",
}, },
{ {
[]*net.SRV{ srvAll,
{Target: "10.0.0.1", Port: 2480}, []*net.SRV{{Target: "4.example.com.", Port: 2380}},
{Target: "10.0.0.2", Port: 2480},
{Target: "10.0.0.3", Port: 2480},
},
[]*net.SRV{
{Target: "10.0.0.1", Port: 2380},
},
nil,
nil,
"0=https://10.0.0.1:2480,1=https://10.0.0.2:2480,2=https://10.0.0.3:2480,3=http://10.0.0.1:2380",
},
{
[]*net.SRV{
{Target: "10.0.0.1", Port: 2480},
{Target: "10.0.0.2", Port: 2480},
{Target: "10.0.0.3", Port: 2480},
},
[]*net.SRV{
{Target: "10.0.0.1", Port: 2380},
},
[]string{"https://10.0.0.1:2480"}, []string{"https://10.0.0.1:2480"},
nil,
"dnsClusterTest=https://10.0.0.1:2480,0=https://10.0.0.2:2480,1=https://10.0.0.3:2480,2=http://10.0.0.1:2380", "dnsClusterTest=https://1.example.com:2480,0=https://2.example.com:2480,1=https://3.example.com:2480,2=http://4.example.com:2380",
}, },
// matching local member with resolved addr and return unresolved hostnames // matching local member with resolved addr and return unresolved hostnames
{ {
[]*net.SRV{ srvAll,
{Target: "1.example.com.", Port: 2480},
{Target: "2.example.com.", Port: 2480},
{Target: "3.example.com.", Port: 2480},
},
nil, nil,
[]string{"https://10.0.0.1:2480"}, []string{"https://10.0.0.1:2480"},
map[string]string{"1.example.com:2480": "10.0.0.1:2480", "2.example.com:2480": "10.0.0.2:2480", "3.example.com:2480": "10.0.0.3:2480"},
"dnsClusterTest=https://1.example.com:2480,0=https://2.example.com:2480,1=https://3.example.com:2480", "dnsClusterTest=https://1.example.com:2480,0=https://2.example.com:2480,1=https://3.example.com:2480",
}, },
// invalid
}
resolveTCPAddr = func(network, addr string) (*net.TCPAddr, error) {
if strings.Contains(addr, "10.0.0.") {
// accept IP addresses when resolving apurls
return net.ResolveTCPAddr(network, addr)
}
if dns[addr] == "" {
return nil, errors.New("missing dns record")
}
return net.ResolveTCPAddr(network, dns[addr])
} }
for i, tt := range tests { for i, tt := range tests {
@ -108,12 +109,6 @@ func TestSRVGetCluster(t *testing.T) {
} }
return "", nil, errors.New("Unknown service in mock") return "", nil, errors.New("Unknown service in mock")
} }
resolveTCPAddr = func(network, addr string) (*net.TCPAddr, error) {
if tt.dns == nil || tt.dns[addr] == "" {
return net.ResolveTCPAddr(network, addr)
}
return net.ResolveTCPAddr(network, tt.dns[addr])
}
urls := testutil.MustNewURLs(t, tt.urls) urls := testutil.MustNewURLs(t, tt.urls)
str, token, err := SRVGetCluster(name, "example.com", "token", urls) str, token, err := SRVGetCluster(name, "example.com", "token", urls)
if err != nil { if err != nil {

View File

@ -280,6 +280,42 @@ func TestCtlV2Backup(t *testing.T) { // For https://github.com/coreos/etcd/issue
} }
} }
func TestCtlV2AuthWithCommonName(t *testing.T) {
defer testutil.AfterTest(t)
copiedCfg := configClientTLS
copiedCfg.clientCertAuthEnabled = true
epc := setupEtcdctlTest(t, &copiedCfg, false)
defer func() {
if err := epc.Close(); err != nil {
t.Fatalf("error closing etcd processes (%v)", err)
}
}()
if err := etcdctlRoleAdd(epc, "testrole"); err != nil {
t.Fatalf("failed to add role (%v)", err)
}
if err := etcdctlRoleGrant(epc, "testrole", "--rw", "--path=/foo"); err != nil {
t.Fatalf("failed to grant role (%v)", err)
}
if err := etcdctlUserAdd(epc, "root", "123"); err != nil {
t.Fatalf("failed to add user (%v)", err)
}
if err := etcdctlUserAdd(epc, "Autogenerated CA", "123"); err != nil {
t.Fatalf("failed to add user (%v)", err)
}
if err := etcdctlUserGrant(epc, "Autogenerated CA", "testrole"); err != nil {
t.Fatalf("failed to grant role (%v)", err)
}
if err := etcdctlAuthEnable(epc); err != nil {
t.Fatalf("failed to enable auth (%v)", err)
}
if err := etcdctlSet(epc, "foo", "bar"); err != nil {
t.Fatalf("failed to write (%v)", err)
}
}
func etcdctlPrefixArgs(clus *etcdProcessCluster) []string { func etcdctlPrefixArgs(clus *etcdProcessCluster) []string {
endpoints := "" endpoints := ""
if proxies := clus.proxies(); len(proxies) != 0 { if proxies := clus.proxies(); len(proxies) != 0 {
@ -352,6 +388,13 @@ func etcdctlRoleAdd(clus *etcdProcessCluster, role string) error {
return spawnWithExpect(cmdArgs, role) return spawnWithExpect(cmdArgs, role)
} }
func etcdctlRoleGrant(clus *etcdProcessCluster, role string, perms ...string) error {
cmdArgs := append(etcdctlPrefixArgs(clus), "role", "grant")
cmdArgs = append(cmdArgs, perms...)
cmdArgs = append(cmdArgs, role)
return spawnWithExpect(cmdArgs, role)
}
func etcdctlRoleList(clus *etcdProcessCluster, expectedRole string) error { func etcdctlRoleList(clus *etcdProcessCluster, expectedRole string) error {
cmdArgs := append(etcdctlPrefixArgs(clus), "role", "list") cmdArgs := append(etcdctlPrefixArgs(clus), "role", "list")
return spawnWithExpect(cmdArgs, expectedRole) return spawnWithExpect(cmdArgs, expectedRole)

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