mirror of
https://github.com/optim-enterprises-bv/kubernetes.git
synced 2025-11-07 22:03:22 +00:00
Move deps from _workspace/ to vendor/
godep restore pushd $GOPATH/src/github.com/appc/spec git co master popd go get go4.org/errorutil rm -rf Godeps godep save ./... git add vendor git add -f $(git ls-files --other vendor/) git co -- Godeps/LICENSES Godeps/.license_file_state Godeps/OWNERS
This commit is contained in:
202
vendor/github.com/coreos/etcd/LICENSE
generated
vendored
Normal file
202
vendor/github.com/coreos/etcd/LICENSE
generated
vendored
Normal file
@@ -0,0 +1,202 @@
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
5
vendor/github.com/coreos/etcd/NOTICE
generated
vendored
Normal file
5
vendor/github.com/coreos/etcd/NOTICE
generated
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
CoreOS Project
|
||||
Copyright 2014 CoreOS, Inc
|
||||
|
||||
This product includes software developed at CoreOS, Inc.
|
||||
(http://www.coreos.com/).
|
||||
152
vendor/github.com/coreos/etcd/alarm/alarms.go
generated
vendored
Normal file
152
vendor/github.com/coreos/etcd/alarm/alarms.go
generated
vendored
Normal file
@@ -0,0 +1,152 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 alarm manages health status alarms in etcd.
|
||||
package alarm
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/etcd/storage/backend"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
)
|
||||
|
||||
var (
|
||||
alarmBucketName = []byte("alarm")
|
||||
plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "alarm")
|
||||
)
|
||||
|
||||
type BackendGetter interface {
|
||||
Backend() backend.Backend
|
||||
}
|
||||
|
||||
type alarmSet map[types.ID]*pb.AlarmMember
|
||||
|
||||
// AlarmStore persists alarms to the backend.
|
||||
type AlarmStore struct {
|
||||
mu sync.Mutex
|
||||
types map[pb.AlarmType]alarmSet
|
||||
|
||||
bg BackendGetter
|
||||
}
|
||||
|
||||
func NewAlarmStore(bg BackendGetter) (*AlarmStore, error) {
|
||||
ret := &AlarmStore{types: make(map[pb.AlarmType]alarmSet), bg: bg}
|
||||
err := ret.restore()
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (a *AlarmStore) Activate(id types.ID, at pb.AlarmType) *pb.AlarmMember {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
newAlarm := &pb.AlarmMember{MemberID: uint64(id), Alarm: at}
|
||||
if m := a.addToMap(newAlarm); m != newAlarm {
|
||||
return m
|
||||
}
|
||||
|
||||
v, err := newAlarm.Marshal()
|
||||
if err != nil {
|
||||
plog.Panicf("failed to marshal alarm member")
|
||||
}
|
||||
|
||||
b := a.bg.Backend()
|
||||
b.BatchTx().Lock()
|
||||
b.BatchTx().UnsafePut(alarmBucketName, v, nil)
|
||||
b.BatchTx().Unlock()
|
||||
|
||||
return newAlarm
|
||||
}
|
||||
|
||||
func (a *AlarmStore) Deactivate(id types.ID, at pb.AlarmType) *pb.AlarmMember {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
|
||||
t := a.types[at]
|
||||
if t == nil {
|
||||
t = make(alarmSet)
|
||||
a.types[at] = t
|
||||
}
|
||||
m := t[id]
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
delete(t, id)
|
||||
|
||||
v, err := m.Marshal()
|
||||
if err != nil {
|
||||
plog.Panicf("failed to marshal alarm member")
|
||||
}
|
||||
|
||||
b := a.bg.Backend()
|
||||
b.BatchTx().Lock()
|
||||
b.BatchTx().UnsafeDelete(alarmBucketName, v)
|
||||
b.BatchTx().Unlock()
|
||||
|
||||
return m
|
||||
}
|
||||
|
||||
func (a *AlarmStore) Get(at pb.AlarmType) (ret []*pb.AlarmMember) {
|
||||
a.mu.Lock()
|
||||
defer a.mu.Unlock()
|
||||
if at == pb.AlarmType_NONE {
|
||||
for _, t := range a.types {
|
||||
for _, m := range t {
|
||||
ret = append(ret, m)
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
for _, m := range a.types[at] {
|
||||
ret = append(ret, m)
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (a *AlarmStore) restore() error {
|
||||
b := a.bg.Backend()
|
||||
tx := b.BatchTx()
|
||||
|
||||
tx.Lock()
|
||||
tx.UnsafeCreateBucket(alarmBucketName)
|
||||
err := tx.UnsafeForEach(alarmBucketName, func(k, v []byte) error {
|
||||
var m pb.AlarmMember
|
||||
if err := m.Unmarshal(k); err != nil {
|
||||
return err
|
||||
}
|
||||
a.addToMap(&m)
|
||||
return nil
|
||||
})
|
||||
tx.Unlock()
|
||||
|
||||
b.ForceCommit()
|
||||
return err
|
||||
}
|
||||
|
||||
func (a *AlarmStore) addToMap(newAlarm *pb.AlarmMember) *pb.AlarmMember {
|
||||
t := a.types[newAlarm.Alarm]
|
||||
if t == nil {
|
||||
t = make(alarmSet)
|
||||
a.types[newAlarm.Alarm] = t
|
||||
}
|
||||
m := t[types.ID(newAlarm.MemberID)]
|
||||
if m != nil {
|
||||
return m
|
||||
}
|
||||
t[types.ID(newAlarm.MemberID)] = newAlarm
|
||||
return newAlarm
|
||||
}
|
||||
509
vendor/github.com/coreos/etcd/auth/authpb/auth.pb.go
generated
vendored
Normal file
509
vendor/github.com/coreos/etcd/auth/authpb/auth.pb.go
generated
vendored
Normal file
@@ -0,0 +1,509 @@
|
||||
// Code generated by protoc-gen-gogo.
|
||||
// source: auth.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package authpb is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
auth.proto
|
||||
|
||||
It has these top-level messages:
|
||||
User
|
||||
Role
|
||||
*/
|
||||
package authpb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
|
||||
math "math"
|
||||
)
|
||||
|
||||
import io "io"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
// User is a single entry in the bucket authUsers
|
||||
type User struct {
|
||||
Name []byte `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
|
||||
Password []byte `protobuf:"bytes,2,opt,name=password,proto3" json:"password,omitempty"`
|
||||
Tombstone int64 `protobuf:"varint,3,opt,name=tombstone,proto3" json:"tombstone,omitempty"`
|
||||
}
|
||||
|
||||
func (m *User) Reset() { *m = User{} }
|
||||
func (m *User) String() string { return proto.CompactTextString(m) }
|
||||
func (*User) ProtoMessage() {}
|
||||
|
||||
// Role is a single entry in the bucket authRoles
|
||||
type Role struct {
|
||||
Name []byte `protobuf:"bytes,2,opt,name=name,proto3" json:"name,omitempty"`
|
||||
}
|
||||
|
||||
func (m *Role) Reset() { *m = Role{} }
|
||||
func (m *Role) String() string { return proto.CompactTextString(m) }
|
||||
func (*Role) ProtoMessage() {}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*User)(nil), "authpb.User")
|
||||
proto.RegisterType((*Role)(nil), "authpb.Role")
|
||||
}
|
||||
func (m *User) Marshal() (data []byte, err error) {
|
||||
size := m.Size()
|
||||
data = make([]byte, size)
|
||||
n, err := m.MarshalTo(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data[:n], nil
|
||||
}
|
||||
|
||||
func (m *User) MarshalTo(data []byte) (int, error) {
|
||||
var i int
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.Name != nil {
|
||||
if len(m.Name) > 0 {
|
||||
data[i] = 0xa
|
||||
i++
|
||||
i = encodeVarintAuth(data, i, uint64(len(m.Name)))
|
||||
i += copy(data[i:], m.Name)
|
||||
}
|
||||
}
|
||||
if m.Password != nil {
|
||||
if len(m.Password) > 0 {
|
||||
data[i] = 0x12
|
||||
i++
|
||||
i = encodeVarintAuth(data, i, uint64(len(m.Password)))
|
||||
i += copy(data[i:], m.Password)
|
||||
}
|
||||
}
|
||||
if m.Tombstone != 0 {
|
||||
data[i] = 0x18
|
||||
i++
|
||||
i = encodeVarintAuth(data, i, uint64(m.Tombstone))
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (m *Role) Marshal() (data []byte, err error) {
|
||||
size := m.Size()
|
||||
data = make([]byte, size)
|
||||
n, err := m.MarshalTo(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data[:n], nil
|
||||
}
|
||||
|
||||
func (m *Role) MarshalTo(data []byte) (int, error) {
|
||||
var i int
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if m.Name != nil {
|
||||
if len(m.Name) > 0 {
|
||||
data[i] = 0x12
|
||||
i++
|
||||
i = encodeVarintAuth(data, i, uint64(len(m.Name)))
|
||||
i += copy(data[i:], m.Name)
|
||||
}
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func encodeFixed64Auth(data []byte, offset int, v uint64) int {
|
||||
data[offset] = uint8(v)
|
||||
data[offset+1] = uint8(v >> 8)
|
||||
data[offset+2] = uint8(v >> 16)
|
||||
data[offset+3] = uint8(v >> 24)
|
||||
data[offset+4] = uint8(v >> 32)
|
||||
data[offset+5] = uint8(v >> 40)
|
||||
data[offset+6] = uint8(v >> 48)
|
||||
data[offset+7] = uint8(v >> 56)
|
||||
return offset + 8
|
||||
}
|
||||
func encodeFixed32Auth(data []byte, offset int, v uint32) int {
|
||||
data[offset] = uint8(v)
|
||||
data[offset+1] = uint8(v >> 8)
|
||||
data[offset+2] = uint8(v >> 16)
|
||||
data[offset+3] = uint8(v >> 24)
|
||||
return offset + 4
|
||||
}
|
||||
func encodeVarintAuth(data []byte, offset int, v uint64) int {
|
||||
for v >= 1<<7 {
|
||||
data[offset] = uint8(v&0x7f | 0x80)
|
||||
v >>= 7
|
||||
offset++
|
||||
}
|
||||
data[offset] = uint8(v)
|
||||
return offset + 1
|
||||
}
|
||||
func (m *User) Size() (n int) {
|
||||
var l int
|
||||
_ = l
|
||||
if m.Name != nil {
|
||||
l = len(m.Name)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovAuth(uint64(l))
|
||||
}
|
||||
}
|
||||
if m.Password != nil {
|
||||
l = len(m.Password)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovAuth(uint64(l))
|
||||
}
|
||||
}
|
||||
if m.Tombstone != 0 {
|
||||
n += 1 + sovAuth(uint64(m.Tombstone))
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *Role) Size() (n int) {
|
||||
var l int
|
||||
_ = l
|
||||
if m.Name != nil {
|
||||
l = len(m.Name)
|
||||
if l > 0 {
|
||||
n += 1 + l + sovAuth(uint64(l))
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func sovAuth(x uint64) (n int) {
|
||||
for {
|
||||
n++
|
||||
x >>= 7
|
||||
if x == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
func sozAuth(x uint64) (n int) {
|
||||
return sovAuth(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
}
|
||||
func (m *User) Unmarshal(data []byte) error {
|
||||
l := len(data)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAuth
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: User: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: User: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
|
||||
}
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAuth
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if byteLen < 0 {
|
||||
return ErrInvalidLengthAuth
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Name = append(m.Name[:0], data[iNdEx:postIndex]...)
|
||||
if m.Name == nil {
|
||||
m.Name = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Password", wireType)
|
||||
}
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAuth
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if byteLen < 0 {
|
||||
return ErrInvalidLengthAuth
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Password = append(m.Password[:0], data[iNdEx:postIndex]...)
|
||||
if m.Password == nil {
|
||||
m.Password = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 3:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Tombstone", wireType)
|
||||
}
|
||||
m.Tombstone = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAuth
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
m.Tombstone |= (int64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipAuth(data[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthAuth
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *Role) Unmarshal(data []byte) error {
|
||||
l := len(data)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAuth
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: Role: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: Role: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
|
||||
}
|
||||
var byteLen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowAuth
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
byteLen |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if byteLen < 0 {
|
||||
return ErrInvalidLengthAuth
|
||||
}
|
||||
postIndex := iNdEx + byteLen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Name = append(m.Name[:0], data[iNdEx:postIndex]...)
|
||||
if m.Name == nil {
|
||||
m.Name = []byte{}
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipAuth(data[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthAuth
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func skipAuth(data []byte) (n int, err error) {
|
||||
l := len(data)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowAuth
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
wireType := int(wire & 0x7)
|
||||
switch wireType {
|
||||
case 0:
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowAuth
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx++
|
||||
if data[iNdEx-1] < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 1:
|
||||
iNdEx += 8
|
||||
return iNdEx, nil
|
||||
case 2:
|
||||
var length int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowAuth
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
length |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
iNdEx += length
|
||||
if length < 0 {
|
||||
return 0, ErrInvalidLengthAuth
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 3:
|
||||
for {
|
||||
var innerWire uint64
|
||||
var start int = iNdEx
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowAuth
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
innerWire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
innerWireType := int(innerWire & 0x7)
|
||||
if innerWireType == 4 {
|
||||
break
|
||||
}
|
||||
next, err := skipAuth(data[start:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
iNdEx = start + next
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 4:
|
||||
return iNdEx, nil
|
||||
case 5:
|
||||
iNdEx += 4
|
||||
return iNdEx, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
|
||||
}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidLengthAuth = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowAuth = fmt.Errorf("proto: integer overflow")
|
||||
)
|
||||
22
vendor/github.com/coreos/etcd/auth/authpb/auth.proto
generated
vendored
Normal file
22
vendor/github.com/coreos/etcd/auth/authpb/auth.proto
generated
vendored
Normal file
@@ -0,0 +1,22 @@
|
||||
syntax = "proto3";
|
||||
package authpb;
|
||||
|
||||
import "gogoproto/gogo.proto";
|
||||
|
||||
option (gogoproto.marshaler_all) = true;
|
||||
option (gogoproto.sizer_all) = true;
|
||||
option (gogoproto.unmarshaler_all) = true;
|
||||
option (gogoproto.goproto_getters_all) = false;
|
||||
option (gogoproto.goproto_enum_prefix_all) = false;
|
||||
|
||||
// User is a single entry in the bucket authUsers
|
||||
message User {
|
||||
bytes name = 1;
|
||||
bytes password = 2;
|
||||
int64 tombstone = 3;
|
||||
}
|
||||
|
||||
// Role is a single entry in the bucket authRoles
|
||||
message Role {
|
||||
bytes name = 2;
|
||||
}
|
||||
212
vendor/github.com/coreos/etcd/auth/store.go
generated
vendored
Normal file
212
vendor/github.com/coreos/etcd/auth/store.go
generated
vendored
Normal file
@@ -0,0 +1,212 @@
|
||||
// Copyright 2016 Nippon Telegraph and Telephone Corporation.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/coreos/etcd/auth/authpb"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/storage/backend"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
var (
|
||||
enableFlagKey = []byte("authEnabled")
|
||||
|
||||
authBucketName = []byte("auth")
|
||||
authUsersBucketName = []byte("authUsers")
|
||||
authRolesBucketName = []byte("authRoles")
|
||||
|
||||
plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "auth")
|
||||
|
||||
ErrUserAlreadyExist = errors.New("auth: user already exists")
|
||||
ErrUserNotFound = errors.New("auth: user not found")
|
||||
ErrRoleAlreadyExist = errors.New("auth: role already exists")
|
||||
)
|
||||
|
||||
type AuthStore interface {
|
||||
// AuthEnable() turns on the authentication feature
|
||||
AuthEnable()
|
||||
|
||||
// Recover recovers the state of auth store from the given backend
|
||||
Recover(b backend.Backend)
|
||||
|
||||
// UserAdd adds a new user
|
||||
UserAdd(r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error)
|
||||
|
||||
// UserDelete deletes a user
|
||||
UserDelete(r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error)
|
||||
|
||||
// UserChangePassword changes a password of a user
|
||||
UserChangePassword(r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error)
|
||||
|
||||
// RoleAdd adds a new role
|
||||
RoleAdd(r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error)
|
||||
}
|
||||
|
||||
type authStore struct {
|
||||
be backend.Backend
|
||||
}
|
||||
|
||||
func (as *authStore) AuthEnable() {
|
||||
value := []byte{1}
|
||||
|
||||
b := as.be
|
||||
tx := b.BatchTx()
|
||||
tx.Lock()
|
||||
tx.UnsafePut(authBucketName, enableFlagKey, value)
|
||||
tx.Unlock()
|
||||
b.ForceCommit()
|
||||
|
||||
plog.Noticef("Authentication enabled")
|
||||
}
|
||||
|
||||
func (as *authStore) Recover(be backend.Backend) {
|
||||
as.be = be
|
||||
// TODO(mitake): recovery process
|
||||
}
|
||||
|
||||
func (as *authStore) UserAdd(r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error) {
|
||||
plog.Noticef("adding a new user: %s", r.Name)
|
||||
|
||||
hashed, err := bcrypt.GenerateFromPassword([]byte(r.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
plog.Errorf("failed to hash password: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx := as.be.BatchTx()
|
||||
tx.Lock()
|
||||
defer tx.Unlock()
|
||||
|
||||
_, vs := tx.UnsafeRange(authUsersBucketName, []byte(r.Name), nil, 0)
|
||||
if len(vs) != 0 {
|
||||
return &pb.AuthUserAddResponse{}, ErrUserAlreadyExist
|
||||
}
|
||||
|
||||
newUser := authpb.User{
|
||||
Name: []byte(r.Name),
|
||||
Password: hashed,
|
||||
}
|
||||
|
||||
marshaledUser, merr := newUser.Marshal()
|
||||
if merr != nil {
|
||||
plog.Errorf("failed to marshal a new user data: %s", merr)
|
||||
return nil, merr
|
||||
}
|
||||
|
||||
tx.UnsafePut(authUsersBucketName, []byte(r.Name), marshaledUser)
|
||||
|
||||
plog.Noticef("added a new user: %s", r.Name)
|
||||
|
||||
return &pb.AuthUserAddResponse{}, nil
|
||||
}
|
||||
|
||||
func (as *authStore) UserDelete(r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error) {
|
||||
tx := as.be.BatchTx()
|
||||
tx.Lock()
|
||||
defer tx.Unlock()
|
||||
|
||||
_, vs := tx.UnsafeRange(authUsersBucketName, []byte(r.Name), nil, 0)
|
||||
if len(vs) != 1 {
|
||||
return &pb.AuthUserDeleteResponse{}, ErrUserNotFound
|
||||
}
|
||||
|
||||
tx.UnsafeDelete(authUsersBucketName, []byte(r.Name))
|
||||
|
||||
plog.Noticef("deleted a user: %s", r.Name)
|
||||
|
||||
return &pb.AuthUserDeleteResponse{}, nil
|
||||
}
|
||||
|
||||
func (as *authStore) UserChangePassword(r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error) {
|
||||
// TODO(mitake): measure the cost of bcrypt.GenerateFromPassword()
|
||||
// If the cost is too high, we should move the encryption to outside of the raft
|
||||
hashed, err := bcrypt.GenerateFromPassword([]byte(r.Password), bcrypt.DefaultCost)
|
||||
if err != nil {
|
||||
plog.Errorf("failed to hash password: %s", err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx := as.be.BatchTx()
|
||||
tx.Lock()
|
||||
defer tx.Unlock()
|
||||
|
||||
_, vs := tx.UnsafeRange(authUsersBucketName, []byte(r.Name), nil, 0)
|
||||
if len(vs) != 1 {
|
||||
return &pb.AuthUserChangePasswordResponse{}, ErrUserNotFound
|
||||
}
|
||||
|
||||
updatedUser := authpb.User{
|
||||
Name: []byte(r.Name),
|
||||
Password: hashed,
|
||||
}
|
||||
|
||||
marshaledUser, merr := updatedUser.Marshal()
|
||||
if merr != nil {
|
||||
plog.Errorf("failed to marshal a new user data: %s", merr)
|
||||
return nil, merr
|
||||
}
|
||||
|
||||
tx.UnsafePut(authUsersBucketName, []byte(r.Name), marshaledUser)
|
||||
|
||||
plog.Noticef("changed a password of a user: %s", r.Name)
|
||||
|
||||
return &pb.AuthUserChangePasswordResponse{}, nil
|
||||
}
|
||||
|
||||
func (as *authStore) RoleAdd(r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) {
|
||||
tx := as.be.BatchTx()
|
||||
tx.Lock()
|
||||
defer tx.Unlock()
|
||||
|
||||
_, vs := tx.UnsafeRange(authRolesBucketName, []byte(r.Name), nil, 0)
|
||||
if len(vs) != 0 {
|
||||
return nil, ErrRoleAlreadyExist
|
||||
}
|
||||
|
||||
newRole := &authpb.Role{
|
||||
Name: []byte(r.Name),
|
||||
}
|
||||
|
||||
marshaledRole, err := newRole.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
tx.UnsafePut(authRolesBucketName, []byte(r.Name), marshaledRole)
|
||||
|
||||
plog.Noticef("Role %s is created", r.Name)
|
||||
|
||||
return &pb.AuthRoleAddResponse{}, nil
|
||||
}
|
||||
|
||||
func NewAuthStore(be backend.Backend) *authStore {
|
||||
tx := be.BatchTx()
|
||||
tx.Lock()
|
||||
|
||||
tx.UnsafeCreateBucket(authBucketName)
|
||||
tx.UnsafeCreateBucket(authUsersBucketName)
|
||||
tx.UnsafeCreateBucket(authRolesBucketName)
|
||||
|
||||
tx.Unlock()
|
||||
be.ForceCommit()
|
||||
|
||||
return &authStore{
|
||||
be: be,
|
||||
}
|
||||
}
|
||||
115
vendor/github.com/coreos/etcd/client/README.md
generated
vendored
Normal file
115
vendor/github.com/coreos/etcd/client/README.md
generated
vendored
Normal file
@@ -0,0 +1,115 @@
|
||||
# etcd/client
|
||||
|
||||
etcd/client is the Go client library for etcd.
|
||||
|
||||
[](https://godoc.org/github.com/coreos/etcd/client)
|
||||
|
||||
etcd uses go's `vendor` directory to manage external dependencies. If `client` is imported
|
||||
outside of etcd, simply copy `client` to the `vendor` directory or use tools like godep to
|
||||
manage your own dependency, as in [vendor directories](https://golang.org/cmd/go/#hdr-Vendor_Directories).
|
||||
For more detail, please read [Go vendor design](https://golang.org/s/go15vendor).
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get github.com/coreos/etcd/client
|
||||
```
|
||||
|
||||
## Usage
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"github.com/coreos/etcd/client"
|
||||
)
|
||||
|
||||
func main() {
|
||||
cfg := client.Config{
|
||||
Endpoints: []string{"http://127.0.0.1:2379"},
|
||||
Transport: client.DefaultTransport,
|
||||
// set timeout per request to fail fast when the target endpoint is unavailable
|
||||
HeaderTimeoutPerRequest: time.Second,
|
||||
}
|
||||
c, err := client.New(cfg)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
kapi := client.NewKeysAPI(c)
|
||||
// set "/foo" key with "bar" value
|
||||
log.Print("Setting '/foo' key with 'bar' value")
|
||||
resp, err := kapi.Set(context.Background(), "/foo", "bar", nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
} else {
|
||||
// print common key info
|
||||
log.Printf("Set is done. Metadata is %q\n", resp)
|
||||
}
|
||||
// get "/foo" key's value
|
||||
log.Print("Getting '/foo' key value")
|
||||
resp, err = kapi.Get(context.Background(), "/foo", nil)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
} else {
|
||||
// print common key info
|
||||
log.Printf("Get is done. Metadata is %q\n", resp)
|
||||
// print value
|
||||
log.Printf("%q key has %q value\n", resp.Node.Key, resp.Node.Value)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Error Handling
|
||||
|
||||
etcd client might return three types of errors.
|
||||
|
||||
- context error
|
||||
|
||||
Each API call has its first parameter as `context`. A context can be canceled or have an attached deadline. If the context is canceled or reaches its deadline, the responding context error will be returned no matter what internal errors the API call has already encountered.
|
||||
|
||||
- cluster error
|
||||
|
||||
Each API call tries to send request to the cluster endpoints one by one until it successfully gets a response. If a requests to an endpoint fails, due to exceeding per request timeout or connection issues, the error will be added into a list of errors. If all possible endpoints fail, a cluster error that includes all encountered errors will be returned.
|
||||
|
||||
- response error
|
||||
|
||||
If the response gets from the cluster is invalid, a plain string error will be returned. For example, it might be a invalid JSON error.
|
||||
|
||||
Here is the example code to handle client errors:
|
||||
|
||||
```go
|
||||
cfg := client.Config{Endpoints: []string{"http://etcd1:2379","http://etcd2:2379","http://etcd3:2379"}}
|
||||
c, err := client.New(cfg)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
kapi := client.NewKeysAPI(c)
|
||||
resp, err := kapi.Set(ctx, "test", "bar", nil)
|
||||
if err != nil {
|
||||
if err == context.Canceled {
|
||||
// ctx is canceled by another routine
|
||||
} else if err == context.DeadlineExceeded {
|
||||
// ctx is attached with a deadline and it exceeded
|
||||
} else if cerr, ok := err.(*client.ClusterError); ok {
|
||||
// process (cerr.Errors)
|
||||
} else {
|
||||
// bad cluster endpoints, which are not etcd servers
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Caveat
|
||||
|
||||
1. etcd/client prefers to use the same endpoint as long as the endpoint continues to work well. This saves socket resources, and improves efficiency for both client and server side. This preference doesn't remove consistency from the data consumed by the client because data replicated to each etcd member has already passed through the consensus process.
|
||||
|
||||
2. etcd/client does round-robin rotation on other available endpoints if the preferred endpoint isn't functioning properly. For example, if the member that etcd/client connects to is hard killed, etcd/client will fail on the first attempt with the killed member, and succeed on the second attempt with another member. If it fails to talk to all available endpoints, it will return all errors happened.
|
||||
|
||||
3. Default etcd/client cannot handle the case that the remote server is SIGSTOPed now. TCP keepalive mechanism doesn't help in this scenario because operating system may still send TCP keep-alive packets. Over time we'd like to improve this functionality, but solving this issue isn't high priority because a real-life case in which a server is stopped, but the connection is kept alive, hasn't been brought to our attention.
|
||||
|
||||
4. etcd/client cannot detect whether the member in use is healthy when doing read requests. If the member is isolated from the cluster, etcd/client may retrieve outdated data. As a workaround, users could monitor experimental /health endpoint for member healthy information. We are improving it at [#3265](https://github.com/coreos/etcd/issues/3265).
|
||||
237
vendor/github.com/coreos/etcd/client/auth_role.go
generated
vendored
Normal file
237
vendor/github.com/coreos/etcd/client/auth_role.go
generated
vendored
Normal file
@@ -0,0 +1,237 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type Role struct {
|
||||
Role string `json:"role"`
|
||||
Permissions Permissions `json:"permissions"`
|
||||
Grant *Permissions `json:"grant,omitempty"`
|
||||
Revoke *Permissions `json:"revoke,omitempty"`
|
||||
}
|
||||
|
||||
type Permissions struct {
|
||||
KV rwPermission `json:"kv"`
|
||||
}
|
||||
|
||||
type rwPermission struct {
|
||||
Read []string `json:"read"`
|
||||
Write []string `json:"write"`
|
||||
}
|
||||
|
||||
type PermissionType int
|
||||
|
||||
const (
|
||||
ReadPermission PermissionType = iota
|
||||
WritePermission
|
||||
ReadWritePermission
|
||||
)
|
||||
|
||||
// NewAuthRoleAPI constructs a new AuthRoleAPI that uses HTTP to
|
||||
// interact with etcd's role creation and modification features.
|
||||
func NewAuthRoleAPI(c Client) AuthRoleAPI {
|
||||
return &httpAuthRoleAPI{
|
||||
client: c,
|
||||
}
|
||||
}
|
||||
|
||||
type AuthRoleAPI interface {
|
||||
// AddRole adds a role.
|
||||
AddRole(ctx context.Context, role string) error
|
||||
|
||||
// RemoveRole removes a role.
|
||||
RemoveRole(ctx context.Context, role string) error
|
||||
|
||||
// GetRole retrieves role details.
|
||||
GetRole(ctx context.Context, role string) (*Role, error)
|
||||
|
||||
// GrantRoleKV grants a role some permission prefixes for the KV store.
|
||||
GrantRoleKV(ctx context.Context, role string, prefixes []string, permType PermissionType) (*Role, error)
|
||||
|
||||
// RevokeRoleKV revokes some permission prefixes for a role on the KV store.
|
||||
RevokeRoleKV(ctx context.Context, role string, prefixes []string, permType PermissionType) (*Role, error)
|
||||
|
||||
// ListRoles lists roles.
|
||||
ListRoles(ctx context.Context) ([]string, error)
|
||||
}
|
||||
|
||||
type httpAuthRoleAPI struct {
|
||||
client httpClient
|
||||
}
|
||||
|
||||
type authRoleAPIAction struct {
|
||||
verb string
|
||||
name string
|
||||
role *Role
|
||||
}
|
||||
|
||||
type authRoleAPIList struct{}
|
||||
|
||||
func (list *authRoleAPIList) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2AuthURL(ep, "roles", "")
|
||||
req, _ := http.NewRequest("GET", u.String(), nil)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return req
|
||||
}
|
||||
|
||||
func (l *authRoleAPIAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2AuthURL(ep, "roles", l.name)
|
||||
if l.role == nil {
|
||||
req, _ := http.NewRequest(l.verb, u.String(), nil)
|
||||
return req
|
||||
}
|
||||
b, err := json.Marshal(l.role)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
body := bytes.NewReader(b)
|
||||
req, _ := http.NewRequest(l.verb, u.String(), body)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return req
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) ListRoles(ctx context.Context) ([]string, error) {
|
||||
resp, body, err := r.client.Do(ctx, &authRoleAPIList{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var roleList struct {
|
||||
Roles []Role `json:"roles"`
|
||||
}
|
||||
if err = json.Unmarshal(body, &roleList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := make([]string, 0, len(roleList.Roles))
|
||||
for _, r := range roleList.Roles {
|
||||
ret = append(ret, r.Role)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) AddRole(ctx context.Context, rolename string) error {
|
||||
role := &Role{
|
||||
Role: rolename,
|
||||
}
|
||||
return r.addRemoveRole(ctx, &authRoleAPIAction{
|
||||
verb: "PUT",
|
||||
name: rolename,
|
||||
role: role,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) RemoveRole(ctx context.Context, rolename string) error {
|
||||
return r.addRemoveRole(ctx, &authRoleAPIAction{
|
||||
verb: "DELETE",
|
||||
name: rolename,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) addRemoveRole(ctx context.Context, req *authRoleAPIAction) error {
|
||||
resp, body, err := r.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := assertStatusCode(resp.StatusCode, http.StatusOK, http.StatusCreated); err != nil {
|
||||
var sec authError
|
||||
err := json.Unmarshal(body, &sec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sec
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) GetRole(ctx context.Context, rolename string) (*Role, error) {
|
||||
return r.modRole(ctx, &authRoleAPIAction{
|
||||
verb: "GET",
|
||||
name: rolename,
|
||||
})
|
||||
}
|
||||
|
||||
func buildRWPermission(prefixes []string, permType PermissionType) rwPermission {
|
||||
var out rwPermission
|
||||
switch permType {
|
||||
case ReadPermission:
|
||||
out.Read = prefixes
|
||||
case WritePermission:
|
||||
out.Write = prefixes
|
||||
case ReadWritePermission:
|
||||
out.Read = prefixes
|
||||
out.Write = prefixes
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) GrantRoleKV(ctx context.Context, rolename string, prefixes []string, permType PermissionType) (*Role, error) {
|
||||
rwp := buildRWPermission(prefixes, permType)
|
||||
role := &Role{
|
||||
Role: rolename,
|
||||
Grant: &Permissions{
|
||||
KV: rwp,
|
||||
},
|
||||
}
|
||||
return r.modRole(ctx, &authRoleAPIAction{
|
||||
verb: "PUT",
|
||||
name: rolename,
|
||||
role: role,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) RevokeRoleKV(ctx context.Context, rolename string, prefixes []string, permType PermissionType) (*Role, error) {
|
||||
rwp := buildRWPermission(prefixes, permType)
|
||||
role := &Role{
|
||||
Role: rolename,
|
||||
Revoke: &Permissions{
|
||||
KV: rwp,
|
||||
},
|
||||
}
|
||||
return r.modRole(ctx, &authRoleAPIAction{
|
||||
verb: "PUT",
|
||||
name: rolename,
|
||||
role: role,
|
||||
})
|
||||
}
|
||||
|
||||
func (r *httpAuthRoleAPI) modRole(ctx context.Context, req *authRoleAPIAction) (*Role, error) {
|
||||
resp, body, err := r.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
var sec authError
|
||||
err = json.Unmarshal(body, &sec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, sec
|
||||
}
|
||||
var role Role
|
||||
if err = json.Unmarshal(body, &role); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &role, nil
|
||||
}
|
||||
314
vendor/github.com/coreos/etcd/client/auth_user.go
generated
vendored
Normal file
314
vendor/github.com/coreos/etcd/client/auth_user.go
generated
vendored
Normal file
@@ -0,0 +1,314 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultV2AuthPrefix = "/v2/auth"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
User string `json:"user"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Roles []string `json:"roles"`
|
||||
Grant []string `json:"grant,omitempty"`
|
||||
Revoke []string `json:"revoke,omitempty"`
|
||||
}
|
||||
|
||||
type UserRoles struct {
|
||||
User string `json:"user"`
|
||||
Roles []Role `json:"roles"`
|
||||
}
|
||||
|
||||
func v2AuthURL(ep url.URL, action string, name string) *url.URL {
|
||||
if name != "" {
|
||||
ep.Path = path.Join(ep.Path, defaultV2AuthPrefix, action, name)
|
||||
return &ep
|
||||
}
|
||||
ep.Path = path.Join(ep.Path, defaultV2AuthPrefix, action)
|
||||
return &ep
|
||||
}
|
||||
|
||||
// NewAuthAPI constructs a new AuthAPI that uses HTTP to
|
||||
// interact with etcd's general auth features.
|
||||
func NewAuthAPI(c Client) AuthAPI {
|
||||
return &httpAuthAPI{
|
||||
client: c,
|
||||
}
|
||||
}
|
||||
|
||||
type AuthAPI interface {
|
||||
// Enable auth.
|
||||
Enable(ctx context.Context) error
|
||||
|
||||
// Disable auth.
|
||||
Disable(ctx context.Context) error
|
||||
}
|
||||
|
||||
type httpAuthAPI struct {
|
||||
client httpClient
|
||||
}
|
||||
|
||||
func (s *httpAuthAPI) Enable(ctx context.Context) error {
|
||||
return s.enableDisable(ctx, &authAPIAction{"PUT"})
|
||||
}
|
||||
|
||||
func (s *httpAuthAPI) Disable(ctx context.Context) error {
|
||||
return s.enableDisable(ctx, &authAPIAction{"DELETE"})
|
||||
}
|
||||
|
||||
func (s *httpAuthAPI) enableDisable(ctx context.Context, req httpAction) error {
|
||||
resp, body, err := s.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK, http.StatusCreated); err != nil {
|
||||
var sec authError
|
||||
err = json.Unmarshal(body, &sec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sec
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type authAPIAction struct {
|
||||
verb string
|
||||
}
|
||||
|
||||
func (l *authAPIAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2AuthURL(ep, "enable", "")
|
||||
req, _ := http.NewRequest(l.verb, u.String(), nil)
|
||||
return req
|
||||
}
|
||||
|
||||
type authError struct {
|
||||
Message string `json:"message"`
|
||||
Code int `json:"-"`
|
||||
}
|
||||
|
||||
func (e authError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
// NewAuthUserAPI constructs a new AuthUserAPI that uses HTTP to
|
||||
// interact with etcd's user creation and modification features.
|
||||
func NewAuthUserAPI(c Client) AuthUserAPI {
|
||||
return &httpAuthUserAPI{
|
||||
client: c,
|
||||
}
|
||||
}
|
||||
|
||||
type AuthUserAPI interface {
|
||||
// AddUser adds a user.
|
||||
AddUser(ctx context.Context, username string, password string) error
|
||||
|
||||
// RemoveUser removes a user.
|
||||
RemoveUser(ctx context.Context, username string) error
|
||||
|
||||
// GetUser retrieves user details.
|
||||
GetUser(ctx context.Context, username string) (*User, error)
|
||||
|
||||
// GrantUser grants a user some permission roles.
|
||||
GrantUser(ctx context.Context, username string, roles []string) (*User, error)
|
||||
|
||||
// RevokeUser revokes some permission roles from a user.
|
||||
RevokeUser(ctx context.Context, username string, roles []string) (*User, error)
|
||||
|
||||
// ChangePassword changes the user's password.
|
||||
ChangePassword(ctx context.Context, username string, password string) (*User, error)
|
||||
|
||||
// ListUsers lists the users.
|
||||
ListUsers(ctx context.Context) ([]string, error)
|
||||
}
|
||||
|
||||
type httpAuthUserAPI struct {
|
||||
client httpClient
|
||||
}
|
||||
|
||||
type authUserAPIAction struct {
|
||||
verb string
|
||||
username string
|
||||
user *User
|
||||
}
|
||||
|
||||
type authUserAPIList struct{}
|
||||
|
||||
func (list *authUserAPIList) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2AuthURL(ep, "users", "")
|
||||
req, _ := http.NewRequest("GET", u.String(), nil)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return req
|
||||
}
|
||||
|
||||
func (l *authUserAPIAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2AuthURL(ep, "users", l.username)
|
||||
if l.user == nil {
|
||||
req, _ := http.NewRequest(l.verb, u.String(), nil)
|
||||
return req
|
||||
}
|
||||
b, err := json.Marshal(l.user)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
body := bytes.NewReader(b)
|
||||
req, _ := http.NewRequest(l.verb, u.String(), body)
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return req
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) ListUsers(ctx context.Context) ([]string, error) {
|
||||
resp, body, err := u.client.Do(ctx, &authUserAPIList{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
var sec authError
|
||||
err = json.Unmarshal(body, &sec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, sec
|
||||
}
|
||||
|
||||
var userList struct {
|
||||
Users []User `json:"users"`
|
||||
}
|
||||
|
||||
if err = json.Unmarshal(body, &userList); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := make([]string, 0, len(userList.Users))
|
||||
for _, u := range userList.Users {
|
||||
ret = append(ret, u.User)
|
||||
}
|
||||
return ret, nil
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) AddUser(ctx context.Context, username string, password string) error {
|
||||
user := &User{
|
||||
User: username,
|
||||
Password: password,
|
||||
}
|
||||
return u.addRemoveUser(ctx, &authUserAPIAction{
|
||||
verb: "PUT",
|
||||
username: username,
|
||||
user: user,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) RemoveUser(ctx context.Context, username string) error {
|
||||
return u.addRemoveUser(ctx, &authUserAPIAction{
|
||||
verb: "DELETE",
|
||||
username: username,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) addRemoveUser(ctx context.Context, req *authUserAPIAction) error {
|
||||
resp, body, err := u.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK, http.StatusCreated); err != nil {
|
||||
var sec authError
|
||||
err = json.Unmarshal(body, &sec)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return sec
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) GetUser(ctx context.Context, username string) (*User, error) {
|
||||
return u.modUser(ctx, &authUserAPIAction{
|
||||
verb: "GET",
|
||||
username: username,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) GrantUser(ctx context.Context, username string, roles []string) (*User, error) {
|
||||
user := &User{
|
||||
User: username,
|
||||
Grant: roles,
|
||||
}
|
||||
return u.modUser(ctx, &authUserAPIAction{
|
||||
verb: "PUT",
|
||||
username: username,
|
||||
user: user,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) RevokeUser(ctx context.Context, username string, roles []string) (*User, error) {
|
||||
user := &User{
|
||||
User: username,
|
||||
Revoke: roles,
|
||||
}
|
||||
return u.modUser(ctx, &authUserAPIAction{
|
||||
verb: "PUT",
|
||||
username: username,
|
||||
user: user,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) ChangePassword(ctx context.Context, username string, password string) (*User, error) {
|
||||
user := &User{
|
||||
User: username,
|
||||
Password: password,
|
||||
}
|
||||
return u.modUser(ctx, &authUserAPIAction{
|
||||
verb: "PUT",
|
||||
username: username,
|
||||
user: user,
|
||||
})
|
||||
}
|
||||
|
||||
func (u *httpAuthUserAPI) modUser(ctx context.Context, req *authUserAPIAction) (*User, error) {
|
||||
resp, body, err := u.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
var sec authError
|
||||
err = json.Unmarshal(body, &sec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, sec
|
||||
}
|
||||
var user User
|
||||
if err = json.Unmarshal(body, &user); err != nil {
|
||||
var userR UserRoles
|
||||
if urerr := json.Unmarshal(body, &userR); urerr != nil {
|
||||
return nil, err
|
||||
}
|
||||
user.User = userR.User
|
||||
for _, r := range userR.Roles {
|
||||
user.Roles = append(user.Roles, r.Role)
|
||||
}
|
||||
}
|
||||
return &user, nil
|
||||
}
|
||||
18
vendor/github.com/coreos/etcd/client/cancelreq.go
generated
vendored
Normal file
18
vendor/github.com/coreos/etcd/client/cancelreq.go
generated
vendored
Normal file
@@ -0,0 +1,18 @@
|
||||
// Copyright 2015 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.
|
||||
|
||||
// borrowed from golang/net/context/ctxhttp/cancelreq.go
|
||||
|
||||
package client
|
||||
|
||||
import "net/http"
|
||||
|
||||
func requestCanceler(tr CancelableTransport, req *http.Request) func() {
|
||||
ch := make(chan struct{})
|
||||
req.Cancel = ch
|
||||
|
||||
return func() {
|
||||
close(ch)
|
||||
}
|
||||
}
|
||||
598
vendor/github.com/coreos/etcd/client/client.go
generated
vendored
Normal file
598
vendor/github.com/coreos/etcd/client/client.go
generated
vendored
Normal file
@@ -0,0 +1,598 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 client
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoEndpoints = errors.New("client: no endpoints available")
|
||||
ErrTooManyRedirects = errors.New("client: too many redirects")
|
||||
ErrClusterUnavailable = errors.New("client: etcd cluster is unavailable or misconfigured")
|
||||
ErrNoLeaderEndpoint = errors.New("client: no leader endpoint available")
|
||||
errTooManyRedirectChecks = errors.New("client: too many redirect checks")
|
||||
)
|
||||
|
||||
var DefaultRequestTimeout = 5 * time.Second
|
||||
|
||||
var DefaultTransport CancelableTransport = &http.Transport{
|
||||
Proxy: http.ProxyFromEnvironment,
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
}
|
||||
|
||||
type EndpointSelectionMode int
|
||||
|
||||
const (
|
||||
// EndpointSelectionRandom is the default value of the 'SelectionMode'.
|
||||
// As the name implies, the client object will pick a node from the members
|
||||
// of the cluster in a random fashion. If the cluster has three members, A, B,
|
||||
// and C, the client picks any node from its three members as its request
|
||||
// destination.
|
||||
EndpointSelectionRandom EndpointSelectionMode = iota
|
||||
|
||||
// If 'SelectionMode' is set to 'EndpointSelectionPrioritizeLeader',
|
||||
// requests are sent directly to the cluster leader. This reduces
|
||||
// forwarding roundtrips compared to making requests to etcd followers
|
||||
// who then forward them to the cluster leader. In the event of a leader
|
||||
// failure, however, clients configured this way cannot prioritize among
|
||||
// the remaining etcd followers. Therefore, when a client sets 'SelectionMode'
|
||||
// to 'EndpointSelectionPrioritizeLeader', it must use 'client.AutoSync()' to
|
||||
// maintain its knowledge of current cluster state.
|
||||
//
|
||||
// This mode should be used with Client.AutoSync().
|
||||
EndpointSelectionPrioritizeLeader
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
// Endpoints defines a set of URLs (schemes, hosts and ports only)
|
||||
// that can be used to communicate with a logical etcd cluster. For
|
||||
// example, a three-node cluster could be provided like so:
|
||||
//
|
||||
// Endpoints: []string{
|
||||
// "http://node1.example.com:2379",
|
||||
// "http://node2.example.com:2379",
|
||||
// "http://node3.example.com:2379",
|
||||
// }
|
||||
//
|
||||
// If multiple endpoints are provided, the Client will attempt to
|
||||
// use them all in the event that one or more of them are unusable.
|
||||
//
|
||||
// If Client.Sync is ever called, the Client may cache an alternate
|
||||
// set of endpoints to continue operation.
|
||||
Endpoints []string
|
||||
|
||||
// Transport is used by the Client to drive HTTP requests. If not
|
||||
// provided, DefaultTransport will be used.
|
||||
Transport CancelableTransport
|
||||
|
||||
// CheckRedirect specifies the policy for handling HTTP redirects.
|
||||
// If CheckRedirect is not nil, the Client calls it before
|
||||
// following an HTTP redirect. The sole argument is the number of
|
||||
// requests that have already been made. If CheckRedirect returns
|
||||
// an error, Client.Do will not make any further requests and return
|
||||
// the error back it to the caller.
|
||||
//
|
||||
// If CheckRedirect is nil, the Client uses its default policy,
|
||||
// which is to stop after 10 consecutive requests.
|
||||
CheckRedirect CheckRedirectFunc
|
||||
|
||||
// Username specifies the user credential to add as an authorization header
|
||||
Username string
|
||||
|
||||
// Password is the password for the specified user to add as an authorization header
|
||||
// to the request.
|
||||
Password string
|
||||
|
||||
// HeaderTimeoutPerRequest specifies the time limit to wait for response
|
||||
// header in a single request made by the Client. The timeout includes
|
||||
// connection time, any redirects, and header wait time.
|
||||
//
|
||||
// For non-watch GET request, server returns the response body immediately.
|
||||
// For PUT/POST/DELETE request, server will attempt to commit request
|
||||
// before responding, which is expected to take `100ms + 2 * RTT`.
|
||||
// For watch request, server returns the header immediately to notify Client
|
||||
// watch start. But if server is behind some kind of proxy, the response
|
||||
// header may be cached at proxy, and Client cannot rely on this behavior.
|
||||
//
|
||||
// Especially, wait request will ignore this timeout.
|
||||
//
|
||||
// One API call may send multiple requests to different etcd servers until it
|
||||
// succeeds. Use context of the API to specify the overall timeout.
|
||||
//
|
||||
// A HeaderTimeoutPerRequest of zero means no timeout.
|
||||
HeaderTimeoutPerRequest time.Duration
|
||||
|
||||
// SelectionMode is an EndpointSelectionMode enum that specifies the
|
||||
// policy for choosing the etcd cluster node to which requests are sent.
|
||||
SelectionMode EndpointSelectionMode
|
||||
}
|
||||
|
||||
func (cfg *Config) transport() CancelableTransport {
|
||||
if cfg.Transport == nil {
|
||||
return DefaultTransport
|
||||
}
|
||||
return cfg.Transport
|
||||
}
|
||||
|
||||
func (cfg *Config) checkRedirect() CheckRedirectFunc {
|
||||
if cfg.CheckRedirect == nil {
|
||||
return DefaultCheckRedirect
|
||||
}
|
||||
return cfg.CheckRedirect
|
||||
}
|
||||
|
||||
// CancelableTransport mimics net/http.Transport, but requires that
|
||||
// the object also support request cancellation.
|
||||
type CancelableTransport interface {
|
||||
http.RoundTripper
|
||||
CancelRequest(req *http.Request)
|
||||
}
|
||||
|
||||
type CheckRedirectFunc func(via int) error
|
||||
|
||||
// DefaultCheckRedirect follows up to 10 redirects, but no more.
|
||||
var DefaultCheckRedirect CheckRedirectFunc = func(via int) error {
|
||||
if via > 10 {
|
||||
return ErrTooManyRedirects
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Client interface {
|
||||
// Sync updates the internal cache of the etcd cluster's membership.
|
||||
Sync(context.Context) error
|
||||
|
||||
// AutoSync periodically calls Sync() every given interval.
|
||||
// The recommended sync interval is 10 seconds to 1 minute, which does
|
||||
// not bring too much overhead to server and makes client catch up the
|
||||
// cluster change in time.
|
||||
//
|
||||
// The example to use it:
|
||||
//
|
||||
// for {
|
||||
// err := client.AutoSync(ctx, 10*time.Second)
|
||||
// if err == context.DeadlineExceeded || err == context.Canceled {
|
||||
// break
|
||||
// }
|
||||
// log.Print(err)
|
||||
// }
|
||||
AutoSync(context.Context, time.Duration) error
|
||||
|
||||
// Endpoints returns a copy of the current set of API endpoints used
|
||||
// by Client to resolve HTTP requests. If Sync has ever been called,
|
||||
// this may differ from the initial Endpoints provided in the Config.
|
||||
Endpoints() []string
|
||||
|
||||
// SetEndpoints sets the set of API endpoints used by Client to resolve
|
||||
// HTTP requests. If the given endpoints are not valid, an error will be
|
||||
// returned
|
||||
SetEndpoints(eps []string) error
|
||||
|
||||
httpClient
|
||||
}
|
||||
|
||||
func New(cfg Config) (Client, error) {
|
||||
c := &httpClusterClient{
|
||||
clientFactory: newHTTPClientFactory(cfg.transport(), cfg.checkRedirect(), cfg.HeaderTimeoutPerRequest),
|
||||
rand: rand.New(rand.NewSource(int64(time.Now().Nanosecond()))),
|
||||
selectionMode: cfg.SelectionMode,
|
||||
}
|
||||
if cfg.Username != "" {
|
||||
c.credentials = &credentials{
|
||||
username: cfg.Username,
|
||||
password: cfg.Password,
|
||||
}
|
||||
}
|
||||
if err := c.SetEndpoints(cfg.Endpoints); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
type httpClient interface {
|
||||
Do(context.Context, httpAction) (*http.Response, []byte, error)
|
||||
}
|
||||
|
||||
func newHTTPClientFactory(tr CancelableTransport, cr CheckRedirectFunc, headerTimeout time.Duration) httpClientFactory {
|
||||
return func(ep url.URL) httpClient {
|
||||
return &redirectFollowingHTTPClient{
|
||||
checkRedirect: cr,
|
||||
client: &simpleHTTPClient{
|
||||
transport: tr,
|
||||
endpoint: ep,
|
||||
headerTimeout: headerTimeout,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type credentials struct {
|
||||
username string
|
||||
password string
|
||||
}
|
||||
|
||||
type httpClientFactory func(url.URL) httpClient
|
||||
|
||||
type httpAction interface {
|
||||
HTTPRequest(url.URL) *http.Request
|
||||
}
|
||||
|
||||
type httpClusterClient struct {
|
||||
clientFactory httpClientFactory
|
||||
endpoints []url.URL
|
||||
pinned int
|
||||
credentials *credentials
|
||||
sync.RWMutex
|
||||
rand *rand.Rand
|
||||
selectionMode EndpointSelectionMode
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) getLeaderEndpoint() (string, error) {
|
||||
mAPI := NewMembersAPI(c)
|
||||
leader, err := mAPI.Leader(context.Background())
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return leader.ClientURLs[0], nil // TODO: how to handle multiple client URLs?
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) SetEndpoints(eps []string) error {
|
||||
if len(eps) == 0 {
|
||||
return ErrNoEndpoints
|
||||
}
|
||||
|
||||
neps := make([]url.URL, len(eps))
|
||||
for i, ep := range eps {
|
||||
u, err := url.Parse(ep)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
neps[i] = *u
|
||||
}
|
||||
|
||||
switch c.selectionMode {
|
||||
case EndpointSelectionRandom:
|
||||
c.endpoints = shuffleEndpoints(c.rand, neps)
|
||||
c.pinned = 0
|
||||
case EndpointSelectionPrioritizeLeader:
|
||||
c.endpoints = neps
|
||||
lep, err := c.getLeaderEndpoint()
|
||||
if err != nil {
|
||||
return ErrNoLeaderEndpoint
|
||||
}
|
||||
|
||||
for i := range c.endpoints {
|
||||
if c.endpoints[i].String() == lep {
|
||||
c.pinned = i
|
||||
break
|
||||
}
|
||||
}
|
||||
// If endpoints doesn't have the lu, just keep c.pinned = 0.
|
||||
// Forwarding between follower and leader would be required but it works.
|
||||
default:
|
||||
return errors.New(fmt.Sprintf("invalid endpoint selection mode: %d", c.selectionMode))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) Do(ctx context.Context, act httpAction) (*http.Response, []byte, error) {
|
||||
action := act
|
||||
c.RLock()
|
||||
leps := len(c.endpoints)
|
||||
eps := make([]url.URL, leps)
|
||||
n := copy(eps, c.endpoints)
|
||||
pinned := c.pinned
|
||||
|
||||
if c.credentials != nil {
|
||||
action = &authedAction{
|
||||
act: act,
|
||||
credentials: *c.credentials,
|
||||
}
|
||||
}
|
||||
c.RUnlock()
|
||||
|
||||
if leps == 0 {
|
||||
return nil, nil, ErrNoEndpoints
|
||||
}
|
||||
|
||||
if leps != n {
|
||||
return nil, nil, errors.New("unable to pick endpoint: copy failed")
|
||||
}
|
||||
|
||||
var resp *http.Response
|
||||
var body []byte
|
||||
var err error
|
||||
cerr := &ClusterError{}
|
||||
|
||||
for i := pinned; i < leps+pinned; i++ {
|
||||
k := i % leps
|
||||
hc := c.clientFactory(eps[k])
|
||||
resp, body, err = hc.Do(ctx, action)
|
||||
if err != nil {
|
||||
cerr.Errors = append(cerr.Errors, err)
|
||||
if err == ctx.Err() {
|
||||
return nil, nil, ctx.Err()
|
||||
}
|
||||
if err == context.Canceled || err == context.DeadlineExceeded {
|
||||
return nil, nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
if resp.StatusCode/100 == 5 {
|
||||
switch resp.StatusCode {
|
||||
case http.StatusInternalServerError, http.StatusServiceUnavailable:
|
||||
// TODO: make sure this is a no leader response
|
||||
cerr.Errors = append(cerr.Errors, fmt.Errorf("client: etcd member %s has no leader", eps[k].String()))
|
||||
default:
|
||||
cerr.Errors = append(cerr.Errors, fmt.Errorf("client: etcd member %s returns server error [%s]", eps[k].String(), http.StatusText(resp.StatusCode)))
|
||||
}
|
||||
continue
|
||||
}
|
||||
if k != pinned {
|
||||
c.Lock()
|
||||
c.pinned = k
|
||||
c.Unlock()
|
||||
}
|
||||
return resp, body, nil
|
||||
}
|
||||
|
||||
return nil, nil, cerr
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) Endpoints() []string {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
eps := make([]string, len(c.endpoints))
|
||||
for i, ep := range c.endpoints {
|
||||
eps[i] = ep.String()
|
||||
}
|
||||
|
||||
return eps
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) Sync(ctx context.Context) error {
|
||||
mAPI := NewMembersAPI(c)
|
||||
ms, err := mAPI.List(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
eps := make([]string, 0)
|
||||
for _, m := range ms {
|
||||
eps = append(eps, m.ClientURLs...)
|
||||
}
|
||||
sort.Sort(sort.StringSlice(eps))
|
||||
|
||||
ceps := make([]string, len(c.endpoints))
|
||||
for i, cep := range c.endpoints {
|
||||
ceps[i] = cep.String()
|
||||
}
|
||||
sort.Sort(sort.StringSlice(ceps))
|
||||
// fast path if no change happens
|
||||
// this helps client to pin the endpoint when no cluster change
|
||||
if reflect.DeepEqual(eps, ceps) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return c.SetEndpoints(eps)
|
||||
}
|
||||
|
||||
func (c *httpClusterClient) AutoSync(ctx context.Context, interval time.Duration) error {
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
err := c.Sync(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return ctx.Err()
|
||||
case <-ticker.C:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type roundTripResponse struct {
|
||||
resp *http.Response
|
||||
err error
|
||||
}
|
||||
|
||||
type simpleHTTPClient struct {
|
||||
transport CancelableTransport
|
||||
endpoint url.URL
|
||||
headerTimeout time.Duration
|
||||
}
|
||||
|
||||
func (c *simpleHTTPClient) Do(ctx context.Context, act httpAction) (*http.Response, []byte, error) {
|
||||
req := act.HTTPRequest(c.endpoint)
|
||||
|
||||
if err := printcURL(req); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
isWait := false
|
||||
if req != nil && req.URL != nil {
|
||||
ws := req.URL.Query().Get("wait")
|
||||
if len(ws) != 0 {
|
||||
var err error
|
||||
isWait, err = strconv.ParseBool(ws)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("wrong wait value %s (%v for %+v)", ws, err, req)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var hctx context.Context
|
||||
var hcancel context.CancelFunc
|
||||
if !isWait && c.headerTimeout > 0 {
|
||||
hctx, hcancel = context.WithTimeout(ctx, c.headerTimeout)
|
||||
} else {
|
||||
hctx, hcancel = context.WithCancel(ctx)
|
||||
}
|
||||
defer hcancel()
|
||||
|
||||
reqcancel := requestCanceler(c.transport, req)
|
||||
|
||||
rtchan := make(chan roundTripResponse, 1)
|
||||
go func() {
|
||||
resp, err := c.transport.RoundTrip(req)
|
||||
rtchan <- roundTripResponse{resp: resp, err: err}
|
||||
close(rtchan)
|
||||
}()
|
||||
|
||||
var resp *http.Response
|
||||
var err error
|
||||
|
||||
select {
|
||||
case rtresp := <-rtchan:
|
||||
resp, err = rtresp.resp, rtresp.err
|
||||
case <-hctx.Done():
|
||||
// cancel and wait for request to actually exit before continuing
|
||||
reqcancel()
|
||||
rtresp := <-rtchan
|
||||
resp = rtresp.resp
|
||||
switch {
|
||||
case ctx.Err() != nil:
|
||||
err = ctx.Err()
|
||||
case hctx.Err() != nil:
|
||||
err = fmt.Errorf("client: endpoint %s exceeded header timeout", c.endpoint.String())
|
||||
default:
|
||||
panic("failed to get error from context")
|
||||
}
|
||||
}
|
||||
|
||||
// always check for resp nil-ness to deal with possible
|
||||
// race conditions between channels above
|
||||
defer func() {
|
||||
if resp != nil {
|
||||
resp.Body.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
|
||||
var body []byte
|
||||
done := make(chan struct{})
|
||||
go func() {
|
||||
body, err = ioutil.ReadAll(resp.Body)
|
||||
done <- struct{}{}
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
resp.Body.Close()
|
||||
<-done
|
||||
return nil, nil, ctx.Err()
|
||||
case <-done:
|
||||
}
|
||||
|
||||
return resp, body, err
|
||||
}
|
||||
|
||||
type authedAction struct {
|
||||
act httpAction
|
||||
credentials credentials
|
||||
}
|
||||
|
||||
func (a *authedAction) HTTPRequest(url url.URL) *http.Request {
|
||||
r := a.act.HTTPRequest(url)
|
||||
r.SetBasicAuth(a.credentials.username, a.credentials.password)
|
||||
return r
|
||||
}
|
||||
|
||||
type redirectFollowingHTTPClient struct {
|
||||
client httpClient
|
||||
checkRedirect CheckRedirectFunc
|
||||
}
|
||||
|
||||
func (r *redirectFollowingHTTPClient) Do(ctx context.Context, act httpAction) (*http.Response, []byte, error) {
|
||||
next := act
|
||||
for i := 0; i < 100; i++ {
|
||||
if i > 0 {
|
||||
if err := r.checkRedirect(i); err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
}
|
||||
resp, body, err := r.client.Do(ctx, next)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
if resp.StatusCode/100 == 3 {
|
||||
hdr := resp.Header.Get("Location")
|
||||
if hdr == "" {
|
||||
return nil, nil, fmt.Errorf("Location header not set")
|
||||
}
|
||||
loc, err := url.Parse(hdr)
|
||||
if err != nil {
|
||||
return nil, nil, fmt.Errorf("Location header not valid URL: %s", hdr)
|
||||
}
|
||||
next = &redirectedHTTPAction{
|
||||
action: act,
|
||||
location: *loc,
|
||||
}
|
||||
continue
|
||||
}
|
||||
return resp, body, nil
|
||||
}
|
||||
|
||||
return nil, nil, errTooManyRedirectChecks
|
||||
}
|
||||
|
||||
type redirectedHTTPAction struct {
|
||||
action httpAction
|
||||
location url.URL
|
||||
}
|
||||
|
||||
func (r *redirectedHTTPAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
orig := r.action.HTTPRequest(ep)
|
||||
orig.URL = &r.location
|
||||
return orig
|
||||
}
|
||||
|
||||
func shuffleEndpoints(r *rand.Rand, eps []url.URL) []url.URL {
|
||||
p := r.Perm(len(eps))
|
||||
neps := make([]url.URL, len(eps))
|
||||
for i, k := range p {
|
||||
neps[i] = eps[k]
|
||||
}
|
||||
return neps
|
||||
}
|
||||
33
vendor/github.com/coreos/etcd/client/cluster_error.go
generated
vendored
Normal file
33
vendor/github.com/coreos/etcd/client/cluster_error.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 client
|
||||
|
||||
import "fmt"
|
||||
|
||||
type ClusterError struct {
|
||||
Errors []error
|
||||
}
|
||||
|
||||
func (ce *ClusterError) Error() string {
|
||||
return ErrClusterUnavailable.Error()
|
||||
}
|
||||
|
||||
func (ce *ClusterError) Detail() string {
|
||||
s := ""
|
||||
for i, e := range ce.Errors {
|
||||
s += fmt.Sprintf("error #%d: %s\n", i, e)
|
||||
}
|
||||
return s
|
||||
}
|
||||
70
vendor/github.com/coreos/etcd/client/curl.go
generated
vendored
Normal file
70
vendor/github.com/coreos/etcd/client/curl.go
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"os"
|
||||
)
|
||||
|
||||
var (
|
||||
cURLDebug = false
|
||||
)
|
||||
|
||||
func EnablecURLDebug() {
|
||||
cURLDebug = true
|
||||
}
|
||||
|
||||
func DisablecURLDebug() {
|
||||
cURLDebug = false
|
||||
}
|
||||
|
||||
// printcURL prints the cURL equivalent request to stderr.
|
||||
// It returns an error if the body of the request cannot
|
||||
// be read.
|
||||
// The caller MUST cancel the request if there is an error.
|
||||
func printcURL(req *http.Request) error {
|
||||
if !cURLDebug {
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
command string
|
||||
b []byte
|
||||
err error
|
||||
)
|
||||
|
||||
if req.URL != nil {
|
||||
command = fmt.Sprintf("curl -X %s %s", req.Method, req.URL.String())
|
||||
}
|
||||
|
||||
if req.Body != nil {
|
||||
b, err = ioutil.ReadAll(req.Body)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
command += fmt.Sprintf(" -d %q", string(b))
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "cURL Command: %s\n", command)
|
||||
|
||||
// reset body
|
||||
body := bytes.NewBuffer(b)
|
||||
req.Body = ioutil.NopCloser(body)
|
||||
|
||||
return nil
|
||||
}
|
||||
21
vendor/github.com/coreos/etcd/client/discover.go
generated
vendored
Normal file
21
vendor/github.com/coreos/etcd/client/discover.go
generated
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 client
|
||||
|
||||
// Discoverer is an interface that wraps the Discover method.
|
||||
type Discoverer interface {
|
||||
// Discover looks up the etcd servers for the domain.
|
||||
Discover(domain string) ([]string, error)
|
||||
}
|
||||
73
vendor/github.com/coreos/etcd/client/doc.go
generated
vendored
Normal file
73
vendor/github.com/coreos/etcd/client/doc.go
generated
vendored
Normal file
@@ -0,0 +1,73 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 client provides bindings for the etcd APIs.
|
||||
|
||||
Create a Config and exchange it for a Client:
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/coreos/etcd/client"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
cfg := client.Config{
|
||||
Endpoints: []string{"http://127.0.0.1:2379"},
|
||||
Transport: DefaultTransport,
|
||||
}
|
||||
|
||||
c, err := client.New(cfg)
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
Clients are safe for concurrent use by multiple goroutines.
|
||||
|
||||
Create a KeysAPI using the Client, then use it to interact with etcd:
|
||||
|
||||
kAPI := client.NewKeysAPI(c)
|
||||
|
||||
// create a new key /foo with the value "bar"
|
||||
_, err = kAPI.Create(context.Background(), "/foo", "bar")
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
// delete the newly created key only if the value is still "bar"
|
||||
_, err = kAPI.Delete(context.Background(), "/foo", &DeleteOptions{PrevValue: "bar"})
|
||||
if err != nil {
|
||||
// handle error
|
||||
}
|
||||
|
||||
Use a custom context to set timeouts on your operations:
|
||||
|
||||
import "time"
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
|
||||
defer cancel()
|
||||
|
||||
// set a new key, ignoring it's previous state
|
||||
_, err := kAPI.Set(ctx, "/ping", "pong", nil)
|
||||
if err != nil {
|
||||
if err == context.DeadlineExceeded {
|
||||
// request took longer than 5s
|
||||
} else {
|
||||
// handle error
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
package client
|
||||
1000
vendor/github.com/coreos/etcd/client/keys.generated.go
generated
vendored
Normal file
1000
vendor/github.com/coreos/etcd/client/keys.generated.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
663
vendor/github.com/coreos/etcd/client/keys.go
generated
vendored
Normal file
663
vendor/github.com/coreos/etcd/client/keys.go
generated
vendored
Normal file
@@ -0,0 +1,663 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 client
|
||||
|
||||
//go:generate codecgen -d 1819 -r "Node|Response|Nodes" -o keys.generated.go keys.go
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/pkg/pathutil"
|
||||
"github.com/ugorji/go/codec"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const (
|
||||
ErrorCodeKeyNotFound = 100
|
||||
ErrorCodeTestFailed = 101
|
||||
ErrorCodeNotFile = 102
|
||||
ErrorCodeNotDir = 104
|
||||
ErrorCodeNodeExist = 105
|
||||
ErrorCodeRootROnly = 107
|
||||
ErrorCodeDirNotEmpty = 108
|
||||
ErrorCodeUnauthorized = 110
|
||||
|
||||
ErrorCodePrevValueRequired = 201
|
||||
ErrorCodeTTLNaN = 202
|
||||
ErrorCodeIndexNaN = 203
|
||||
ErrorCodeInvalidField = 209
|
||||
ErrorCodeInvalidForm = 210
|
||||
|
||||
ErrorCodeRaftInternal = 300
|
||||
ErrorCodeLeaderElect = 301
|
||||
|
||||
ErrorCodeWatcherCleared = 400
|
||||
ErrorCodeEventIndexCleared = 401
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
Code int `json:"errorCode"`
|
||||
Message string `json:"message"`
|
||||
Cause string `json:"cause"`
|
||||
Index uint64 `json:"index"`
|
||||
}
|
||||
|
||||
func (e Error) Error() string {
|
||||
return fmt.Sprintf("%v: %v (%v) [%v]", e.Code, e.Message, e.Cause, e.Index)
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidJSON = errors.New("client: response is invalid json. The endpoint is probably not valid etcd cluster endpoint.")
|
||||
ErrEmptyBody = errors.New("client: response body is empty")
|
||||
)
|
||||
|
||||
// PrevExistType is used to define an existence condition when setting
|
||||
// or deleting Nodes.
|
||||
type PrevExistType string
|
||||
|
||||
const (
|
||||
PrevIgnore = PrevExistType("")
|
||||
PrevExist = PrevExistType("true")
|
||||
PrevNoExist = PrevExistType("false")
|
||||
)
|
||||
|
||||
var (
|
||||
defaultV2KeysPrefix = "/v2/keys"
|
||||
)
|
||||
|
||||
// NewKeysAPI builds a KeysAPI that interacts with etcd's key-value
|
||||
// API over HTTP.
|
||||
func NewKeysAPI(c Client) KeysAPI {
|
||||
return NewKeysAPIWithPrefix(c, defaultV2KeysPrefix)
|
||||
}
|
||||
|
||||
// NewKeysAPIWithPrefix acts like NewKeysAPI, but allows the caller
|
||||
// to provide a custom base URL path. This should only be used in
|
||||
// very rare cases.
|
||||
func NewKeysAPIWithPrefix(c Client, p string) KeysAPI {
|
||||
return &httpKeysAPI{
|
||||
client: c,
|
||||
prefix: p,
|
||||
}
|
||||
}
|
||||
|
||||
type KeysAPI interface {
|
||||
// Get retrieves a set of Nodes from etcd
|
||||
Get(ctx context.Context, key string, opts *GetOptions) (*Response, error)
|
||||
|
||||
// Set assigns a new value to a Node identified by a given key. The caller
|
||||
// may define a set of conditions in the SetOptions. If SetOptions.Dir=true
|
||||
// then value is ignored.
|
||||
Set(ctx context.Context, key, value string, opts *SetOptions) (*Response, error)
|
||||
|
||||
// Delete removes a Node identified by the given key, optionally destroying
|
||||
// all of its children as well. The caller may define a set of required
|
||||
// conditions in an DeleteOptions object.
|
||||
Delete(ctx context.Context, key string, opts *DeleteOptions) (*Response, error)
|
||||
|
||||
// Create is an alias for Set w/ PrevExist=false
|
||||
Create(ctx context.Context, key, value string) (*Response, error)
|
||||
|
||||
// CreateInOrder is used to atomically create in-order keys within the given directory.
|
||||
CreateInOrder(ctx context.Context, dir, value string, opts *CreateInOrderOptions) (*Response, error)
|
||||
|
||||
// Update is an alias for Set w/ PrevExist=true
|
||||
Update(ctx context.Context, key, value string) (*Response, error)
|
||||
|
||||
// Watcher builds a new Watcher targeted at a specific Node identified
|
||||
// by the given key. The Watcher may be configured at creation time
|
||||
// through a WatcherOptions object. The returned Watcher is designed
|
||||
// to emit events that happen to a Node, and optionally to its children.
|
||||
Watcher(key string, opts *WatcherOptions) Watcher
|
||||
}
|
||||
|
||||
type WatcherOptions struct {
|
||||
// AfterIndex defines the index after-which the Watcher should
|
||||
// start emitting events. For example, if a value of 5 is
|
||||
// provided, the first event will have an index >= 6.
|
||||
//
|
||||
// Setting AfterIndex to 0 (default) means that the Watcher
|
||||
// should start watching for events starting at the current
|
||||
// index, whatever that may be.
|
||||
AfterIndex uint64
|
||||
|
||||
// Recursive specifies whether or not the Watcher should emit
|
||||
// events that occur in children of the given keyspace. If set
|
||||
// to false (default), events will be limited to those that
|
||||
// occur for the exact key.
|
||||
Recursive bool
|
||||
}
|
||||
|
||||
type CreateInOrderOptions struct {
|
||||
// TTL defines a period of time after-which the Node should
|
||||
// expire and no longer exist. Values <= 0 are ignored. Given
|
||||
// that the zero-value is ignored, TTL cannot be used to set
|
||||
// a TTL of 0.
|
||||
TTL time.Duration
|
||||
}
|
||||
|
||||
type SetOptions struct {
|
||||
// PrevValue specifies what the current value of the Node must
|
||||
// be in order for the Set operation to succeed.
|
||||
//
|
||||
// Leaving this field empty means that the caller wishes to
|
||||
// ignore the current value of the Node. This cannot be used
|
||||
// to compare the Node's current value to an empty string.
|
||||
//
|
||||
// PrevValue is ignored if Dir=true
|
||||
PrevValue string
|
||||
|
||||
// PrevIndex indicates what the current ModifiedIndex of the
|
||||
// Node must be in order for the Set operation to succeed.
|
||||
//
|
||||
// If PrevIndex is set to 0 (default), no comparison is made.
|
||||
PrevIndex uint64
|
||||
|
||||
// PrevExist specifies whether the Node must currently exist
|
||||
// (PrevExist) or not (PrevNoExist). If the caller does not
|
||||
// care about existence, set PrevExist to PrevIgnore, or simply
|
||||
// leave it unset.
|
||||
PrevExist PrevExistType
|
||||
|
||||
// TTL defines a period of time after-which the Node should
|
||||
// expire and no longer exist. Values <= 0 are ignored. Given
|
||||
// that the zero-value is ignored, TTL cannot be used to set
|
||||
// a TTL of 0.
|
||||
TTL time.Duration
|
||||
|
||||
// Refresh set to true means a TTL value can be updated
|
||||
// without firing a watch or changing the node value. A
|
||||
// value must not be provided when refreshing a key.
|
||||
Refresh bool
|
||||
|
||||
// Dir specifies whether or not this Node should be created as a directory.
|
||||
Dir bool
|
||||
}
|
||||
|
||||
type GetOptions struct {
|
||||
// Recursive defines whether or not all children of the Node
|
||||
// should be returned.
|
||||
Recursive bool
|
||||
|
||||
// Sort instructs the server whether or not to sort the Nodes.
|
||||
// If true, the Nodes are sorted alphabetically by key in
|
||||
// ascending order (A to z). If false (default), the Nodes will
|
||||
// not be sorted and the ordering used should not be considered
|
||||
// predictable.
|
||||
Sort bool
|
||||
|
||||
// Quorum specifies whether it gets the latest committed value that
|
||||
// has been applied in quorum of members, which ensures external
|
||||
// consistency (or linearizability).
|
||||
Quorum bool
|
||||
}
|
||||
|
||||
type DeleteOptions struct {
|
||||
// PrevValue specifies what the current value of the Node must
|
||||
// be in order for the Delete operation to succeed.
|
||||
//
|
||||
// Leaving this field empty means that the caller wishes to
|
||||
// ignore the current value of the Node. This cannot be used
|
||||
// to compare the Node's current value to an empty string.
|
||||
PrevValue string
|
||||
|
||||
// PrevIndex indicates what the current ModifiedIndex of the
|
||||
// Node must be in order for the Delete operation to succeed.
|
||||
//
|
||||
// If PrevIndex is set to 0 (default), no comparison is made.
|
||||
PrevIndex uint64
|
||||
|
||||
// Recursive defines whether or not all children of the Node
|
||||
// should be deleted. If set to true, all children of the Node
|
||||
// identified by the given key will be deleted. If left unset
|
||||
// or explicitly set to false, only a single Node will be
|
||||
// deleted.
|
||||
Recursive bool
|
||||
|
||||
// Dir specifies whether or not this Node should be removed as a directory.
|
||||
Dir bool
|
||||
}
|
||||
|
||||
type Watcher interface {
|
||||
// Next blocks until an etcd event occurs, then returns a Response
|
||||
// representing that event. The behavior of Next depends on the
|
||||
// WatcherOptions used to construct the Watcher. Next is designed to
|
||||
// be called repeatedly, each time blocking until a subsequent event
|
||||
// is available.
|
||||
//
|
||||
// If the provided context is cancelled, Next will return a non-nil
|
||||
// error. Any other failures encountered while waiting for the next
|
||||
// event (connection issues, deserialization failures, etc) will
|
||||
// also result in a non-nil error.
|
||||
Next(context.Context) (*Response, error)
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
// Action is the name of the operation that occurred. Possible values
|
||||
// include get, set, delete, update, create, compareAndSwap,
|
||||
// compareAndDelete and expire.
|
||||
Action string `json:"action"`
|
||||
|
||||
// Node represents the state of the relevant etcd Node.
|
||||
Node *Node `json:"node"`
|
||||
|
||||
// PrevNode represents the previous state of the Node. PrevNode is non-nil
|
||||
// only if the Node existed before the action occurred and the action
|
||||
// caused a change to the Node.
|
||||
PrevNode *Node `json:"prevNode"`
|
||||
|
||||
// Index holds the cluster-level index at the time the Response was generated.
|
||||
// This index is not tied to the Node(s) contained in this Response.
|
||||
Index uint64 `json:"-"`
|
||||
}
|
||||
|
||||
type Node struct {
|
||||
// Key represents the unique location of this Node (e.g. "/foo/bar").
|
||||
Key string `json:"key"`
|
||||
|
||||
// Dir reports whether node describes a directory.
|
||||
Dir bool `json:"dir,omitempty"`
|
||||
|
||||
// Value is the current data stored on this Node. If this Node
|
||||
// is a directory, Value will be empty.
|
||||
Value string `json:"value"`
|
||||
|
||||
// Nodes holds the children of this Node, only if this Node is a directory.
|
||||
// This slice of will be arbitrarily deep (children, grandchildren, great-
|
||||
// grandchildren, etc.) if a recursive Get or Watch request were made.
|
||||
Nodes Nodes `json:"nodes"`
|
||||
|
||||
// CreatedIndex is the etcd index at-which this Node was created.
|
||||
CreatedIndex uint64 `json:"createdIndex"`
|
||||
|
||||
// ModifiedIndex is the etcd index at-which this Node was last modified.
|
||||
ModifiedIndex uint64 `json:"modifiedIndex"`
|
||||
|
||||
// Expiration is the server side expiration time of the key.
|
||||
Expiration *time.Time `json:"expiration,omitempty"`
|
||||
|
||||
// TTL is the time to live of the key in second.
|
||||
TTL int64 `json:"ttl,omitempty"`
|
||||
}
|
||||
|
||||
func (n *Node) String() string {
|
||||
return fmt.Sprintf("{Key: %s, CreatedIndex: %d, ModifiedIndex: %d, TTL: %d}", n.Key, n.CreatedIndex, n.ModifiedIndex, n.TTL)
|
||||
}
|
||||
|
||||
// TTLDuration returns the Node's TTL as a time.Duration object
|
||||
func (n *Node) TTLDuration() time.Duration {
|
||||
return time.Duration(n.TTL) * time.Second
|
||||
}
|
||||
|
||||
type Nodes []*Node
|
||||
|
||||
// interfaces for sorting
|
||||
|
||||
func (ns Nodes) Len() int { return len(ns) }
|
||||
func (ns Nodes) Less(i, j int) bool { return ns[i].Key < ns[j].Key }
|
||||
func (ns Nodes) Swap(i, j int) { ns[i], ns[j] = ns[j], ns[i] }
|
||||
|
||||
type httpKeysAPI struct {
|
||||
client httpClient
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (k *httpKeysAPI) Set(ctx context.Context, key, val string, opts *SetOptions) (*Response, error) {
|
||||
act := &setAction{
|
||||
Prefix: k.prefix,
|
||||
Key: key,
|
||||
Value: val,
|
||||
}
|
||||
|
||||
if opts != nil {
|
||||
act.PrevValue = opts.PrevValue
|
||||
act.PrevIndex = opts.PrevIndex
|
||||
act.PrevExist = opts.PrevExist
|
||||
act.TTL = opts.TTL
|
||||
act.Refresh = opts.Refresh
|
||||
act.Dir = opts.Dir
|
||||
}
|
||||
|
||||
resp, body, err := k.client.Do(ctx, act)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body)
|
||||
}
|
||||
|
||||
func (k *httpKeysAPI) Create(ctx context.Context, key, val string) (*Response, error) {
|
||||
return k.Set(ctx, key, val, &SetOptions{PrevExist: PrevNoExist})
|
||||
}
|
||||
|
||||
func (k *httpKeysAPI) CreateInOrder(ctx context.Context, dir, val string, opts *CreateInOrderOptions) (*Response, error) {
|
||||
act := &createInOrderAction{
|
||||
Prefix: k.prefix,
|
||||
Dir: dir,
|
||||
Value: val,
|
||||
}
|
||||
|
||||
if opts != nil {
|
||||
act.TTL = opts.TTL
|
||||
}
|
||||
|
||||
resp, body, err := k.client.Do(ctx, act)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body)
|
||||
}
|
||||
|
||||
func (k *httpKeysAPI) Update(ctx context.Context, key, val string) (*Response, error) {
|
||||
return k.Set(ctx, key, val, &SetOptions{PrevExist: PrevExist})
|
||||
}
|
||||
|
||||
func (k *httpKeysAPI) Delete(ctx context.Context, key string, opts *DeleteOptions) (*Response, error) {
|
||||
act := &deleteAction{
|
||||
Prefix: k.prefix,
|
||||
Key: key,
|
||||
}
|
||||
|
||||
if opts != nil {
|
||||
act.PrevValue = opts.PrevValue
|
||||
act.PrevIndex = opts.PrevIndex
|
||||
act.Dir = opts.Dir
|
||||
act.Recursive = opts.Recursive
|
||||
}
|
||||
|
||||
resp, body, err := k.client.Do(ctx, act)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body)
|
||||
}
|
||||
|
||||
func (k *httpKeysAPI) Get(ctx context.Context, key string, opts *GetOptions) (*Response, error) {
|
||||
act := &getAction{
|
||||
Prefix: k.prefix,
|
||||
Key: key,
|
||||
}
|
||||
|
||||
if opts != nil {
|
||||
act.Recursive = opts.Recursive
|
||||
act.Sorted = opts.Sort
|
||||
act.Quorum = opts.Quorum
|
||||
}
|
||||
|
||||
resp, body, err := k.client.Do(ctx, act)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return unmarshalHTTPResponse(resp.StatusCode, resp.Header, body)
|
||||
}
|
||||
|
||||
func (k *httpKeysAPI) Watcher(key string, opts *WatcherOptions) Watcher {
|
||||
act := waitAction{
|
||||
Prefix: k.prefix,
|
||||
Key: key,
|
||||
}
|
||||
|
||||
if opts != nil {
|
||||
act.Recursive = opts.Recursive
|
||||
if opts.AfterIndex > 0 {
|
||||
act.WaitIndex = opts.AfterIndex + 1
|
||||
}
|
||||
}
|
||||
|
||||
return &httpWatcher{
|
||||
client: k.client,
|
||||
nextWait: act,
|
||||
}
|
||||
}
|
||||
|
||||
type httpWatcher struct {
|
||||
client httpClient
|
||||
nextWait waitAction
|
||||
}
|
||||
|
||||
func (hw *httpWatcher) Next(ctx context.Context) (*Response, error) {
|
||||
for {
|
||||
httpresp, body, err := hw.client.Do(ctx, &hw.nextWait)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := unmarshalHTTPResponse(httpresp.StatusCode, httpresp.Header, body)
|
||||
if err != nil {
|
||||
if err == ErrEmptyBody {
|
||||
continue
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
hw.nextWait.WaitIndex = resp.Node.ModifiedIndex + 1
|
||||
return resp, nil
|
||||
}
|
||||
}
|
||||
|
||||
// v2KeysURL forms a URL representing the location of a key.
|
||||
// The endpoint argument represents the base URL of an etcd
|
||||
// server. The prefix is the path needed to route from the
|
||||
// provided endpoint's path to the root of the keys API
|
||||
// (typically "/v2/keys").
|
||||
func v2KeysURL(ep url.URL, prefix, key string) *url.URL {
|
||||
// We concatenate all parts together manually. We cannot use
|
||||
// path.Join because it does not reserve trailing slash.
|
||||
// We call CanonicalURLPath to further cleanup the path.
|
||||
if prefix != "" && prefix[0] != '/' {
|
||||
prefix = "/" + prefix
|
||||
}
|
||||
if key != "" && key[0] != '/' {
|
||||
key = "/" + key
|
||||
}
|
||||
ep.Path = pathutil.CanonicalURLPath(ep.Path + prefix + key)
|
||||
return &ep
|
||||
}
|
||||
|
||||
type getAction struct {
|
||||
Prefix string
|
||||
Key string
|
||||
Recursive bool
|
||||
Sorted bool
|
||||
Quorum bool
|
||||
}
|
||||
|
||||
func (g *getAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2KeysURL(ep, g.Prefix, g.Key)
|
||||
|
||||
params := u.Query()
|
||||
params.Set("recursive", strconv.FormatBool(g.Recursive))
|
||||
params.Set("sorted", strconv.FormatBool(g.Sorted))
|
||||
params.Set("quorum", strconv.FormatBool(g.Quorum))
|
||||
u.RawQuery = params.Encode()
|
||||
|
||||
req, _ := http.NewRequest("GET", u.String(), nil)
|
||||
return req
|
||||
}
|
||||
|
||||
type waitAction struct {
|
||||
Prefix string
|
||||
Key string
|
||||
WaitIndex uint64
|
||||
Recursive bool
|
||||
}
|
||||
|
||||
func (w *waitAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2KeysURL(ep, w.Prefix, w.Key)
|
||||
|
||||
params := u.Query()
|
||||
params.Set("wait", "true")
|
||||
params.Set("waitIndex", strconv.FormatUint(w.WaitIndex, 10))
|
||||
params.Set("recursive", strconv.FormatBool(w.Recursive))
|
||||
u.RawQuery = params.Encode()
|
||||
|
||||
req, _ := http.NewRequest("GET", u.String(), nil)
|
||||
return req
|
||||
}
|
||||
|
||||
type setAction struct {
|
||||
Prefix string
|
||||
Key string
|
||||
Value string
|
||||
PrevValue string
|
||||
PrevIndex uint64
|
||||
PrevExist PrevExistType
|
||||
TTL time.Duration
|
||||
Refresh bool
|
||||
Dir bool
|
||||
}
|
||||
|
||||
func (a *setAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2KeysURL(ep, a.Prefix, a.Key)
|
||||
|
||||
params := u.Query()
|
||||
form := url.Values{}
|
||||
|
||||
// we're either creating a directory or setting a key
|
||||
if a.Dir {
|
||||
params.Set("dir", strconv.FormatBool(a.Dir))
|
||||
} else {
|
||||
// These options are only valid for setting a key
|
||||
if a.PrevValue != "" {
|
||||
params.Set("prevValue", a.PrevValue)
|
||||
}
|
||||
form.Add("value", a.Value)
|
||||
}
|
||||
|
||||
// Options which apply to both setting a key and creating a dir
|
||||
if a.PrevIndex != 0 {
|
||||
params.Set("prevIndex", strconv.FormatUint(a.PrevIndex, 10))
|
||||
}
|
||||
if a.PrevExist != PrevIgnore {
|
||||
params.Set("prevExist", string(a.PrevExist))
|
||||
}
|
||||
if a.TTL > 0 {
|
||||
form.Add("ttl", strconv.FormatUint(uint64(a.TTL.Seconds()), 10))
|
||||
}
|
||||
|
||||
if a.Refresh {
|
||||
form.Add("refresh", "true")
|
||||
}
|
||||
|
||||
u.RawQuery = params.Encode()
|
||||
body := strings.NewReader(form.Encode())
|
||||
|
||||
req, _ := http.NewRequest("PUT", u.String(), body)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
type deleteAction struct {
|
||||
Prefix string
|
||||
Key string
|
||||
PrevValue string
|
||||
PrevIndex uint64
|
||||
Dir bool
|
||||
Recursive bool
|
||||
}
|
||||
|
||||
func (a *deleteAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2KeysURL(ep, a.Prefix, a.Key)
|
||||
|
||||
params := u.Query()
|
||||
if a.PrevValue != "" {
|
||||
params.Set("prevValue", a.PrevValue)
|
||||
}
|
||||
if a.PrevIndex != 0 {
|
||||
params.Set("prevIndex", strconv.FormatUint(a.PrevIndex, 10))
|
||||
}
|
||||
if a.Dir {
|
||||
params.Set("dir", "true")
|
||||
}
|
||||
if a.Recursive {
|
||||
params.Set("recursive", "true")
|
||||
}
|
||||
u.RawQuery = params.Encode()
|
||||
|
||||
req, _ := http.NewRequest("DELETE", u.String(), nil)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
|
||||
return req
|
||||
}
|
||||
|
||||
type createInOrderAction struct {
|
||||
Prefix string
|
||||
Dir string
|
||||
Value string
|
||||
TTL time.Duration
|
||||
}
|
||||
|
||||
func (a *createInOrderAction) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2KeysURL(ep, a.Prefix, a.Dir)
|
||||
|
||||
form := url.Values{}
|
||||
form.Add("value", a.Value)
|
||||
if a.TTL > 0 {
|
||||
form.Add("ttl", strconv.FormatUint(uint64(a.TTL.Seconds()), 10))
|
||||
}
|
||||
body := strings.NewReader(form.Encode())
|
||||
|
||||
req, _ := http.NewRequest("POST", u.String(), body)
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||
return req
|
||||
}
|
||||
|
||||
func unmarshalHTTPResponse(code int, header http.Header, body []byte) (res *Response, err error) {
|
||||
switch code {
|
||||
case http.StatusOK, http.StatusCreated:
|
||||
if len(body) == 0 {
|
||||
return nil, ErrEmptyBody
|
||||
}
|
||||
res, err = unmarshalSuccessfulKeysResponse(header, body)
|
||||
default:
|
||||
err = unmarshalFailedKeysResponse(body)
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
func unmarshalSuccessfulKeysResponse(header http.Header, body []byte) (*Response, error) {
|
||||
var res Response
|
||||
err := codec.NewDecoderBytes(body, new(codec.JsonHandle)).Decode(&res)
|
||||
if err != nil {
|
||||
return nil, ErrInvalidJSON
|
||||
}
|
||||
if header.Get("X-Etcd-Index") != "" {
|
||||
res.Index, err = strconv.ParseUint(header.Get("X-Etcd-Index"), 10, 64)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &res, nil
|
||||
}
|
||||
|
||||
func unmarshalFailedKeysResponse(body []byte) error {
|
||||
var etcdErr Error
|
||||
if err := json.Unmarshal(body, &etcdErr); err != nil {
|
||||
return ErrInvalidJSON
|
||||
}
|
||||
return etcdErr
|
||||
}
|
||||
304
vendor/github.com/coreos/etcd/client/members.go
generated
vendored
Normal file
304
vendor/github.com/coreos/etcd/client/members.go
generated
vendored
Normal file
@@ -0,0 +1,304 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 client
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultV2MembersPrefix = "/v2/members"
|
||||
defaultLeaderSuffix = "/leader"
|
||||
)
|
||||
|
||||
type Member struct {
|
||||
// ID is the unique identifier of this Member.
|
||||
ID string `json:"id"`
|
||||
|
||||
// Name is a human-readable, non-unique identifier of this Member.
|
||||
Name string `json:"name"`
|
||||
|
||||
// PeerURLs represents the HTTP(S) endpoints this Member uses to
|
||||
// participate in etcd's consensus protocol.
|
||||
PeerURLs []string `json:"peerURLs"`
|
||||
|
||||
// ClientURLs represents the HTTP(S) endpoints on which this Member
|
||||
// serves it's client-facing APIs.
|
||||
ClientURLs []string `json:"clientURLs"`
|
||||
}
|
||||
|
||||
type memberCollection []Member
|
||||
|
||||
func (c *memberCollection) UnmarshalJSON(data []byte) error {
|
||||
d := struct {
|
||||
Members []Member
|
||||
}{}
|
||||
|
||||
if err := json.Unmarshal(data, &d); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if d.Members == nil {
|
||||
*c = make([]Member, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
*c = d.Members
|
||||
return nil
|
||||
}
|
||||
|
||||
type memberCreateOrUpdateRequest struct {
|
||||
PeerURLs types.URLs
|
||||
}
|
||||
|
||||
func (m *memberCreateOrUpdateRequest) MarshalJSON() ([]byte, error) {
|
||||
s := struct {
|
||||
PeerURLs []string `json:"peerURLs"`
|
||||
}{
|
||||
PeerURLs: make([]string, len(m.PeerURLs)),
|
||||
}
|
||||
|
||||
for i, u := range m.PeerURLs {
|
||||
s.PeerURLs[i] = u.String()
|
||||
}
|
||||
|
||||
return json.Marshal(&s)
|
||||
}
|
||||
|
||||
// NewMembersAPI constructs a new MembersAPI that uses HTTP to
|
||||
// interact with etcd's membership API.
|
||||
func NewMembersAPI(c Client) MembersAPI {
|
||||
return &httpMembersAPI{
|
||||
client: c,
|
||||
}
|
||||
}
|
||||
|
||||
type MembersAPI interface {
|
||||
// List enumerates the current cluster membership.
|
||||
List(ctx context.Context) ([]Member, error)
|
||||
|
||||
// Add instructs etcd to accept a new Member into the cluster.
|
||||
Add(ctx context.Context, peerURL string) (*Member, error)
|
||||
|
||||
// Remove demotes an existing Member out of the cluster.
|
||||
Remove(ctx context.Context, mID string) error
|
||||
|
||||
// Update instructs etcd to update an existing Member in the cluster.
|
||||
Update(ctx context.Context, mID string, peerURLs []string) error
|
||||
|
||||
// Leader gets current leader of the cluster
|
||||
Leader(ctx context.Context) (*Member, error)
|
||||
}
|
||||
|
||||
type httpMembersAPI struct {
|
||||
client httpClient
|
||||
}
|
||||
|
||||
func (m *httpMembersAPI) List(ctx context.Context) ([]Member, error) {
|
||||
req := &membersAPIActionList{}
|
||||
resp, body, err := m.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var mCollection memberCollection
|
||||
if err := json.Unmarshal(body, &mCollection); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return []Member(mCollection), nil
|
||||
}
|
||||
|
||||
func (m *httpMembersAPI) Add(ctx context.Context, peerURL string) (*Member, error) {
|
||||
urls, err := types.NewURLs([]string{peerURL})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
req := &membersAPIActionAdd{peerURLs: urls}
|
||||
resp, body, err := m.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := assertStatusCode(resp.StatusCode, http.StatusCreated, http.StatusConflict); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusCreated {
|
||||
var merr membersError
|
||||
if err := json.Unmarshal(body, &merr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return nil, merr
|
||||
}
|
||||
|
||||
var memb Member
|
||||
if err := json.Unmarshal(body, &memb); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &memb, nil
|
||||
}
|
||||
|
||||
func (m *httpMembersAPI) Update(ctx context.Context, memberID string, peerURLs []string) error {
|
||||
urls, err := types.NewURLs(peerURLs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
req := &membersAPIActionUpdate{peerURLs: urls, memberID: memberID}
|
||||
resp, body, err := m.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := assertStatusCode(resp.StatusCode, http.StatusNoContent, http.StatusNotFound, http.StatusConflict); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusNoContent {
|
||||
var merr membersError
|
||||
if err := json.Unmarshal(body, &merr); err != nil {
|
||||
return err
|
||||
}
|
||||
return merr
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *httpMembersAPI) Remove(ctx context.Context, memberID string) error {
|
||||
req := &membersAPIActionRemove{memberID: memberID}
|
||||
resp, _, err := m.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return assertStatusCode(resp.StatusCode, http.StatusNoContent, http.StatusGone)
|
||||
}
|
||||
|
||||
func (m *httpMembersAPI) Leader(ctx context.Context) (*Member, error) {
|
||||
req := &membersAPIActionLeader{}
|
||||
resp, body, err := m.client.Do(ctx, req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := assertStatusCode(resp.StatusCode, http.StatusOK); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var leader Member
|
||||
if err := json.Unmarshal(body, &leader); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &leader, nil
|
||||
}
|
||||
|
||||
type membersAPIActionList struct{}
|
||||
|
||||
func (l *membersAPIActionList) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2MembersURL(ep)
|
||||
req, _ := http.NewRequest("GET", u.String(), nil)
|
||||
return req
|
||||
}
|
||||
|
||||
type membersAPIActionRemove struct {
|
||||
memberID string
|
||||
}
|
||||
|
||||
func (d *membersAPIActionRemove) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2MembersURL(ep)
|
||||
u.Path = path.Join(u.Path, d.memberID)
|
||||
req, _ := http.NewRequest("DELETE", u.String(), nil)
|
||||
return req
|
||||
}
|
||||
|
||||
type membersAPIActionAdd struct {
|
||||
peerURLs types.URLs
|
||||
}
|
||||
|
||||
func (a *membersAPIActionAdd) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2MembersURL(ep)
|
||||
m := memberCreateOrUpdateRequest{PeerURLs: a.peerURLs}
|
||||
b, _ := json.Marshal(&m)
|
||||
req, _ := http.NewRequest("POST", u.String(), bytes.NewReader(b))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return req
|
||||
}
|
||||
|
||||
type membersAPIActionUpdate struct {
|
||||
memberID string
|
||||
peerURLs types.URLs
|
||||
}
|
||||
|
||||
func (a *membersAPIActionUpdate) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2MembersURL(ep)
|
||||
m := memberCreateOrUpdateRequest{PeerURLs: a.peerURLs}
|
||||
u.Path = path.Join(u.Path, a.memberID)
|
||||
b, _ := json.Marshal(&m)
|
||||
req, _ := http.NewRequest("PUT", u.String(), bytes.NewReader(b))
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
return req
|
||||
}
|
||||
|
||||
func assertStatusCode(got int, want ...int) (err error) {
|
||||
for _, w := range want {
|
||||
if w == got {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("unexpected status code %d", got)
|
||||
}
|
||||
|
||||
type membersAPIActionLeader struct{}
|
||||
|
||||
func (l *membersAPIActionLeader) HTTPRequest(ep url.URL) *http.Request {
|
||||
u := v2MembersURL(ep)
|
||||
u.Path = path.Join(u.Path, defaultLeaderSuffix)
|
||||
req, _ := http.NewRequest("GET", u.String(), nil)
|
||||
return req
|
||||
}
|
||||
|
||||
// v2MembersURL add the necessary path to the provided endpoint
|
||||
// to route requests to the default v2 members API.
|
||||
func v2MembersURL(ep url.URL) *url.URL {
|
||||
ep.Path = path.Join(ep.Path, defaultV2MembersPrefix)
|
||||
return &ep
|
||||
}
|
||||
|
||||
type membersError struct {
|
||||
Message string `json:"message"`
|
||||
Code int `json:"-"`
|
||||
}
|
||||
|
||||
func (e membersError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
65
vendor/github.com/coreos/etcd/client/srv.go
generated
vendored
Normal file
65
vendor/github.com/coreos/etcd/client/srv.go
generated
vendored
Normal file
@@ -0,0 +1,65 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 client
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
var (
|
||||
// indirection for testing
|
||||
lookupSRV = net.LookupSRV
|
||||
)
|
||||
|
||||
type srvDiscover struct{}
|
||||
|
||||
// NewSRVDiscover constructs a new Discoverer that uses the stdlib to lookup SRV records.
|
||||
func NewSRVDiscover() Discoverer {
|
||||
return &srvDiscover{}
|
||||
}
|
||||
|
||||
// Discover looks up the etcd servers for the domain.
|
||||
func (d *srvDiscover) Discover(domain string) ([]string, error) {
|
||||
var urls []*url.URL
|
||||
|
||||
updateURLs := func(service, scheme string) error {
|
||||
_, addrs, err := lookupSRV(service, "tcp", domain)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, srv := range addrs {
|
||||
urls = append(urls, &url.URL{
|
||||
Scheme: scheme,
|
||||
Host: net.JoinHostPort(srv.Target, fmt.Sprintf("%d", srv.Port)),
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
errHTTPS := updateURLs("etcd-client-ssl", "https")
|
||||
errHTTP := updateURLs("etcd-client", "http")
|
||||
|
||||
if errHTTPS != nil && errHTTP != nil {
|
||||
return nil, fmt.Errorf("dns lookup errors: %s and %s", errHTTPS, errHTTP)
|
||||
}
|
||||
|
||||
endpoints := make([]string, len(urls))
|
||||
for i := range urls {
|
||||
endpoints[i] = urls[i].String()
|
||||
}
|
||||
return endpoints, nil
|
||||
}
|
||||
23
vendor/github.com/coreos/etcd/client/util.go
generated
vendored
Normal file
23
vendor/github.com/coreos/etcd/client/util.go
generated
vendored
Normal file
@@ -0,0 +1,23 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 client
|
||||
|
||||
// IsKeyNotFound returns true if the error code is ErrorCodeKeyNotFound.
|
||||
func IsKeyNotFound(err error) bool {
|
||||
if cErr, ok := err.(Error); ok {
|
||||
return cErr.Code == ErrorCodeKeyNotFound
|
||||
}
|
||||
return false
|
||||
}
|
||||
74
vendor/github.com/coreos/etcd/clientv3/README.md
generated
vendored
Normal file
74
vendor/github.com/coreos/etcd/clientv3/README.md
generated
vendored
Normal file
@@ -0,0 +1,74 @@
|
||||
# etcd/clientv3
|
||||
|
||||
[](https://godoc.org/github.com/coreos/etcd/clientv3)
|
||||
|
||||
`etcd/clientv3` is the official Go etcd client for v3.
|
||||
|
||||
## Install
|
||||
|
||||
```bash
|
||||
go get github.com/coreos/etcd/clientv3
|
||||
```
|
||||
|
||||
## Get started
|
||||
|
||||
Create client using `clientv3.New`:
|
||||
|
||||
```go
|
||||
cli, err := clientv3.New(clientv3.Config{
|
||||
Endpoints: []string{"localhost:2379", "localhost:22379", "localhost:32379"},
|
||||
DialTimeout: 5 * time.Second,
|
||||
})
|
||||
if err != nil {
|
||||
// handle error!
|
||||
}
|
||||
defer cli.Close()
|
||||
```
|
||||
|
||||
etcd v3 uses [`gRPC`](http://www.grpc.io) for remote procedure calls. And `clientv3` uses
|
||||
[`grpc-go`](https://github.com/grpc/grpc-go) to connect to etcd. Make sure to close the client after using it.
|
||||
If the client is not closed, the connection will have leaky goroutines. To specify client request timeout,
|
||||
pass `context.WithTimeout` to APIs:
|
||||
|
||||
```go
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
resp, err := kvc.Put(ctx, "sample_key", "sample_value")
|
||||
cancel()
|
||||
if err != nil {
|
||||
// handle error!
|
||||
}
|
||||
// use the response
|
||||
```
|
||||
|
||||
etcd uses go's `vendor` directory to manage external dependencies. If `clientv3` is imported
|
||||
outside of etcd, simply copy `clientv3` to the `vendor` directory or use tools like godep to
|
||||
manage your own dependency, as in [vendor directories](https://golang.org/cmd/go/#hdr-Vendor_Directories).
|
||||
For more detail, please read [Go vendor design](https://golang.org/s/go15vendor).
|
||||
|
||||
## Error Handling
|
||||
|
||||
etcd client returns 2 types of errors:
|
||||
|
||||
1. context error: canceled or deadline exceeded.
|
||||
2. gRPC error: see [v3rpc/error](https://github.com/coreos/etcd/blob/master/etcdserver/api/v3rpc/error.go).
|
||||
|
||||
Here is the example code to handle client errors:
|
||||
|
||||
```go
|
||||
resp, err := kvc.Put(ctx, "", "")
|
||||
if err != nil {
|
||||
if err == context.Canceled {
|
||||
// ctx is canceled by another routine
|
||||
} else if err == context.DeadlineExceeded {
|
||||
// ctx is attached with a deadline and it exceeded
|
||||
} else if verr, ok := err.(*v3rpc.ErrEmptyKey); ok {
|
||||
// process (verr.Errors)
|
||||
} else {
|
||||
// bad cluster endpoints, which are not etcd servers
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
More code examples can be found at [GoDoc](https://godoc.org/github.com/coreos/etcd/clientv3).
|
||||
87
vendor/github.com/coreos/etcd/clientv3/auth.go
generated
vendored
Normal file
87
vendor/github.com/coreos/etcd/clientv3/auth.go
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright 2016 Nippon Telegraph and Telephone Corporation.
|
||||
//
|
||||
// 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 (
|
||||
AuthEnableResponse pb.AuthEnableResponse
|
||||
AuthUserAddResponse pb.AuthUserAddResponse
|
||||
AuthUserDeleteResponse pb.AuthUserDeleteResponse
|
||||
AuthUserChangePasswordResponse pb.AuthUserChangePasswordResponse
|
||||
AuthRoleAddResponse pb.AuthRoleAddResponse
|
||||
)
|
||||
|
||||
type Auth interface {
|
||||
// AuthEnable enables auth of an etcd cluster.
|
||||
AuthEnable(ctx context.Context) (*AuthEnableResponse, error)
|
||||
|
||||
// UserAdd adds a new user to an etcd cluster.
|
||||
UserAdd(ctx context.Context, name string, password string) (*AuthUserAddResponse, error)
|
||||
|
||||
// UserDelete deletes a user from an etcd cluster.
|
||||
UserDelete(ctx context.Context, name string) (*AuthUserDeleteResponse, error)
|
||||
|
||||
// UserChangePassword changes a password of a user.
|
||||
UserChangePassword(ctx context.Context, name string, password string) (*AuthUserChangePasswordResponse, error)
|
||||
|
||||
// RoleAdd adds a new user to an etcd cluster.
|
||||
RoleAdd(ctx context.Context, name string) (*AuthRoleAddResponse, error)
|
||||
}
|
||||
|
||||
type auth struct {
|
||||
c *Client
|
||||
|
||||
conn *grpc.ClientConn // conn in-use
|
||||
remote pb.AuthClient
|
||||
}
|
||||
|
||||
func NewAuth(c *Client) Auth {
|
||||
conn := c.ActiveConnection()
|
||||
return &auth{
|
||||
conn: c.ActiveConnection(),
|
||||
remote: pb.NewAuthClient(conn),
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (auth *auth) AuthEnable(ctx context.Context) (*AuthEnableResponse, error) {
|
||||
resp, err := auth.remote.AuthEnable(ctx, &pb.AuthEnableRequest{})
|
||||
return (*AuthEnableResponse)(resp), err
|
||||
}
|
||||
|
||||
func (auth *auth) UserAdd(ctx context.Context, name string, password string) (*AuthUserAddResponse, error) {
|
||||
resp, err := auth.remote.UserAdd(ctx, &pb.AuthUserAddRequest{Name: name, Password: password})
|
||||
return (*AuthUserAddResponse)(resp), err
|
||||
}
|
||||
|
||||
func (auth *auth) UserDelete(ctx context.Context, name string) (*AuthUserDeleteResponse, error) {
|
||||
resp, err := auth.remote.UserDelete(ctx, &pb.AuthUserDeleteRequest{Name: name})
|
||||
return (*AuthUserDeleteResponse)(resp), err
|
||||
}
|
||||
|
||||
func (auth *auth) UserChangePassword(ctx context.Context, name string, password string) (*AuthUserChangePasswordResponse, error) {
|
||||
resp, err := auth.remote.UserChangePassword(ctx, &pb.AuthUserChangePasswordRequest{Name: name, Password: password})
|
||||
return (*AuthUserChangePasswordResponse)(resp), err
|
||||
}
|
||||
|
||||
func (auth *auth) RoleAdd(ctx context.Context, name string) (*AuthRoleAddResponse, error) {
|
||||
resp, err := auth.remote.RoleAdd(ctx, &pb.AuthRoleAddRequest{Name: name})
|
||||
return (*AuthRoleAddResponse)(resp), err
|
||||
}
|
||||
243
vendor/github.com/coreos/etcd/clientv3/client.go
generated
vendored
Normal file
243
vendor/github.com/coreos/etcd/clientv3/client.go
generated
vendored
Normal file
@@ -0,0 +1,243 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"log"
|
||||
"net"
|
||||
"net/url"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoAvailableEndpoints = errors.New("etcdclient: no available endpoints")
|
||||
)
|
||||
|
||||
// Client provides and manages an etcd v3 client session.
|
||||
type Client struct {
|
||||
Cluster
|
||||
KV
|
||||
Lease
|
||||
Watcher
|
||||
Auth
|
||||
Maintenance
|
||||
|
||||
conn *grpc.ClientConn
|
||||
cfg Config
|
||||
creds *credentials.TransportAuthenticator
|
||||
mu sync.RWMutex // protects connection selection and error list
|
||||
errors []error // errors passed to retryConnection
|
||||
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
// New creates a new etcdv3 client from a given configuration.
|
||||
func New(cfg Config) (*Client, error) {
|
||||
if cfg.RetryDialer == nil {
|
||||
cfg.RetryDialer = dialEndpointList
|
||||
}
|
||||
if len(cfg.Endpoints) == 0 {
|
||||
return nil, ErrNoAvailableEndpoints
|
||||
}
|
||||
|
||||
return newClient(&cfg)
|
||||
}
|
||||
|
||||
// NewFromURL creates a new etcdv3 client from a URL.
|
||||
func NewFromURL(url string) (*Client, error) {
|
||||
return New(Config{Endpoints: []string{url}})
|
||||
}
|
||||
|
||||
// NewFromConfigFile creates a new etcdv3 client from a configuration file.
|
||||
func NewFromConfigFile(path string) (*Client, error) {
|
||||
cfg, err := configFromFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return New(*cfg)
|
||||
}
|
||||
|
||||
// Close shuts down the client's etcd connections.
|
||||
func (c *Client) Close() error {
|
||||
c.mu.Lock()
|
||||
if c.cancel == nil {
|
||||
c.mu.Unlock()
|
||||
return nil
|
||||
}
|
||||
c.cancel()
|
||||
c.cancel = nil
|
||||
c.mu.Unlock()
|
||||
c.Watcher.Close()
|
||||
c.Lease.Close()
|
||||
return c.conn.Close()
|
||||
}
|
||||
|
||||
// Ctx is a context for "out of band" messages (e.g., for sending
|
||||
// "clean up" message when another context is canceled). It is
|
||||
// canceled on client Close().
|
||||
func (c *Client) Ctx() context.Context { return c.ctx }
|
||||
|
||||
// Endpoints lists the registered endpoints for the client.
|
||||
func (c *Client) Endpoints() []string { return c.cfg.Endpoints }
|
||||
|
||||
// Errors returns all errors that have been observed since called last.
|
||||
func (c *Client) Errors() (errs []error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
errs = c.errors
|
||||
c.errors = nil
|
||||
return errs
|
||||
}
|
||||
|
||||
// Dial establishes a connection for a given endpoint using the client's config
|
||||
func (c *Client) Dial(endpoint string) (*grpc.ClientConn, error) {
|
||||
opts := []grpc.DialOption{
|
||||
grpc.WithBlock(),
|
||||
grpc.WithTimeout(c.cfg.DialTimeout),
|
||||
}
|
||||
if c.creds != nil {
|
||||
opts = append(opts, grpc.WithTransportCredentials(*c.creds))
|
||||
} else {
|
||||
opts = append(opts, grpc.WithInsecure())
|
||||
}
|
||||
|
||||
proto := "tcp"
|
||||
if url, uerr := url.Parse(endpoint); uerr == nil && url.Scheme == "unix" {
|
||||
proto = "unix"
|
||||
// strip unix:// prefix so certs work
|
||||
endpoint = url.Host
|
||||
}
|
||||
f := func(a string, t time.Duration) (net.Conn, error) {
|
||||
select {
|
||||
case <-c.ctx.Done():
|
||||
return nil, c.ctx.Err()
|
||||
default:
|
||||
}
|
||||
return net.DialTimeout(proto, a, t)
|
||||
}
|
||||
opts = append(opts, grpc.WithDialer(f))
|
||||
|
||||
conn, err := grpc.Dial(endpoint, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return conn, nil
|
||||
}
|
||||
|
||||
func newClient(cfg *Config) (*Client, error) {
|
||||
if cfg == nil {
|
||||
cfg = &Config{RetryDialer: dialEndpointList}
|
||||
}
|
||||
var creds *credentials.TransportAuthenticator
|
||||
if cfg.TLS != nil {
|
||||
c := credentials.NewTLS(cfg.TLS)
|
||||
creds = &c
|
||||
}
|
||||
// use a temporary skeleton client to bootstrap first connection
|
||||
ctx, cancel := context.WithCancel(context.TODO())
|
||||
conn, err := cfg.RetryDialer(&Client{cfg: *cfg, creds: creds, ctx: ctx})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client := &Client{
|
||||
conn: conn,
|
||||
cfg: *cfg,
|
||||
creds: creds,
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
}
|
||||
client.Cluster = NewCluster(client)
|
||||
client.KV = NewKV(client)
|
||||
client.Lease = NewLease(client)
|
||||
client.Watcher = NewWatcher(client)
|
||||
client.Auth = NewAuth(client)
|
||||
client.Maintenance = NewMaintenance(client)
|
||||
if cfg.Logger != nil {
|
||||
logger.Set(cfg.Logger)
|
||||
} else {
|
||||
// disable client side grpc by default
|
||||
logger.Set(log.New(ioutil.Discard, "", 0))
|
||||
}
|
||||
|
||||
return client, nil
|
||||
}
|
||||
|
||||
// ActiveConnection returns the current in-use connection
|
||||
func (c *Client) ActiveConnection() *grpc.ClientConn {
|
||||
c.mu.RLock()
|
||||
defer c.mu.RUnlock()
|
||||
return c.conn
|
||||
}
|
||||
|
||||
// retryConnection establishes a new connection
|
||||
func (c *Client) retryConnection(oldConn *grpc.ClientConn, err error) (*grpc.ClientConn, error) {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
if err != nil {
|
||||
c.errors = append(c.errors, err)
|
||||
}
|
||||
if c.cancel == nil {
|
||||
return nil, c.ctx.Err()
|
||||
}
|
||||
if oldConn != c.conn {
|
||||
// conn has already been updated
|
||||
return c.conn, nil
|
||||
}
|
||||
|
||||
oldConn.Close()
|
||||
if st, _ := oldConn.State(); st != grpc.Shutdown {
|
||||
// wait for shutdown so grpc doesn't leak sleeping goroutines
|
||||
oldConn.WaitForStateChange(c.ctx, st)
|
||||
}
|
||||
|
||||
conn, dialErr := c.cfg.RetryDialer(c)
|
||||
if dialErr != nil {
|
||||
c.errors = append(c.errors, dialErr)
|
||||
return nil, dialErr
|
||||
}
|
||||
c.conn = conn
|
||||
return c.conn, nil
|
||||
}
|
||||
|
||||
// dialEndpointList attempts to connect to each endpoint in order until a
|
||||
// connection is established.
|
||||
func dialEndpointList(c *Client) (*grpc.ClientConn, error) {
|
||||
var err error
|
||||
for _, ep := range c.Endpoints() {
|
||||
conn, curErr := c.Dial(ep)
|
||||
if curErr != nil {
|
||||
err = curErr
|
||||
} else {
|
||||
return conn, nil
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// isHalted returns true if the given error and context indicate no forward
|
||||
// progress can be made, even after reconnecting.
|
||||
func isHalted(ctx context.Context, err error) bool {
|
||||
isRPCError := strings.HasPrefix(grpc.ErrorDesc(err), "etcdserver: ")
|
||||
return isRPCError || ctx.Err() != nil
|
||||
}
|
||||
170
vendor/github.com/coreos/etcd/clientv3/cluster.go
generated
vendored
Normal file
170
vendor/github.com/coreos/etcd/clientv3/cluster.go
generated
vendored
Normal file
@@ -0,0 +1,170 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 (
|
||||
"sync"
|
||||
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type (
|
||||
Member pb.Member
|
||||
MemberListResponse pb.MemberListResponse
|
||||
MemberAddResponse pb.MemberAddResponse
|
||||
MemberRemoveResponse pb.MemberRemoveResponse
|
||||
MemberUpdateResponse pb.MemberUpdateResponse
|
||||
)
|
||||
|
||||
type Cluster interface {
|
||||
// MemberList lists the current cluster membership.
|
||||
MemberList(ctx context.Context) (*MemberListResponse, error)
|
||||
|
||||
// MemberLeader returns the current leader member.
|
||||
MemberLeader(ctx context.Context) (*Member, error)
|
||||
|
||||
// MemberAdd adds a new member into the cluster.
|
||||
MemberAdd(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error)
|
||||
|
||||
// MemberRemove removes an existing member from the cluster.
|
||||
MemberRemove(ctx context.Context, id uint64) (*MemberRemoveResponse, error)
|
||||
|
||||
// MemberUpdate updates the peer addresses of the member.
|
||||
MemberUpdate(ctx context.Context, id uint64, peerAddrs []string) (*MemberUpdateResponse, error)
|
||||
}
|
||||
|
||||
type cluster struct {
|
||||
c *Client
|
||||
|
||||
mu sync.Mutex
|
||||
conn *grpc.ClientConn // conn in-use
|
||||
remote pb.ClusterClient
|
||||
}
|
||||
|
||||
func NewCluster(c *Client) Cluster {
|
||||
conn := c.ActiveConnection()
|
||||
|
||||
return &cluster{
|
||||
c: c,
|
||||
|
||||
conn: conn,
|
||||
remote: pb.NewClusterClient(conn),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cluster) MemberAdd(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error) {
|
||||
r := &pb.MemberAddRequest{PeerURLs: peerAddrs}
|
||||
resp, err := c.getRemote().MemberAdd(ctx, r)
|
||||
if err == nil {
|
||||
return (*MemberAddResponse)(resp), nil
|
||||
}
|
||||
|
||||
if isHalted(ctx, err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go c.switchRemote(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (c *cluster) MemberRemove(ctx context.Context, id uint64) (*MemberRemoveResponse, error) {
|
||||
r := &pb.MemberRemoveRequest{ID: id}
|
||||
resp, err := c.getRemote().MemberRemove(ctx, r)
|
||||
if err == nil {
|
||||
return (*MemberRemoveResponse)(resp), nil
|
||||
}
|
||||
|
||||
if isHalted(ctx, err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
go c.switchRemote(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (c *cluster) MemberUpdate(ctx context.Context, id uint64, peerAddrs []string) (*MemberUpdateResponse, error) {
|
||||
// it is safe to retry on update.
|
||||
for {
|
||||
r := &pb.MemberUpdateRequest{ID: id, PeerURLs: peerAddrs}
|
||||
resp, err := c.getRemote().MemberUpdate(ctx, r)
|
||||
if err == nil {
|
||||
return (*MemberUpdateResponse)(resp), nil
|
||||
}
|
||||
|
||||
if isHalted(ctx, err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = c.switchRemote(err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cluster) MemberList(ctx context.Context) (*MemberListResponse, error) {
|
||||
// it is safe to retry on list.
|
||||
for {
|
||||
resp, err := c.getRemote().MemberList(ctx, &pb.MemberListRequest{})
|
||||
if err == nil {
|
||||
return (*MemberListResponse)(resp), nil
|
||||
}
|
||||
|
||||
if isHalted(ctx, err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = c.switchRemote(err)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cluster) MemberLeader(ctx context.Context) (*Member, error) {
|
||||
resp, err := c.MemberList(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, m := range resp.Members {
|
||||
if m.IsLeader {
|
||||
return (*Member)(m), nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *cluster) getRemote() pb.ClusterClient {
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
return c.remote
|
||||
}
|
||||
|
||||
func (c *cluster) switchRemote(prevErr error) error {
|
||||
newConn, err := c.c.retryConnection(c.conn, prevErr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.mu.Lock()
|
||||
defer c.mu.Unlock()
|
||||
|
||||
c.conn = newConn
|
||||
c.remote = pb.NewClusterClient(c.conn)
|
||||
return nil
|
||||
}
|
||||
91
vendor/github.com/coreos/etcd/clientv3/compare.go
generated
vendored
Normal file
91
vendor/github.com/coreos/etcd/clientv3/compare.go
generated
vendored
Normal file
@@ -0,0 +1,91 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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"
|
||||
)
|
||||
|
||||
type CompareTarget int
|
||||
type CompareResult int
|
||||
|
||||
const (
|
||||
CompareVersion CompareTarget = iota
|
||||
CompareCreated
|
||||
CompareModified
|
||||
CompareValue
|
||||
)
|
||||
|
||||
type Cmp pb.Compare
|
||||
|
||||
func Compare(cmp Cmp, result string, v interface{}) Cmp {
|
||||
var r pb.Compare_CompareResult
|
||||
|
||||
switch result {
|
||||
case "=":
|
||||
r = pb.Compare_EQUAL
|
||||
case ">":
|
||||
r = pb.Compare_GREATER
|
||||
case "<":
|
||||
r = pb.Compare_LESS
|
||||
default:
|
||||
panic("Unknown result op")
|
||||
}
|
||||
|
||||
cmp.Result = r
|
||||
switch cmp.Target {
|
||||
case pb.Compare_VALUE:
|
||||
val, ok := v.(string)
|
||||
if !ok {
|
||||
panic("bad compare value")
|
||||
}
|
||||
cmp.TargetUnion = &pb.Compare_Value{Value: []byte(val)}
|
||||
case pb.Compare_VERSION:
|
||||
cmp.TargetUnion = &pb.Compare_Version{Version: mustInt64(v)}
|
||||
case pb.Compare_CREATE:
|
||||
cmp.TargetUnion = &pb.Compare_CreateRevision{CreateRevision: mustInt64(v)}
|
||||
case pb.Compare_MOD:
|
||||
cmp.TargetUnion = &pb.Compare_ModRevision{ModRevision: mustInt64(v)}
|
||||
default:
|
||||
panic("Unknown compare type")
|
||||
}
|
||||
return cmp
|
||||
}
|
||||
|
||||
func Value(key string) Cmp {
|
||||
return Cmp{Key: []byte(key), Target: pb.Compare_VALUE}
|
||||
}
|
||||
|
||||
func Version(key string) Cmp {
|
||||
return Cmp{Key: []byte(key), Target: pb.Compare_VERSION}
|
||||
}
|
||||
|
||||
func CreateRevision(key string) Cmp {
|
||||
return Cmp{Key: []byte(key), Target: pb.Compare_CREATE}
|
||||
}
|
||||
|
||||
func ModRevision(key string) Cmp {
|
||||
return Cmp{Key: []byte(key), Target: pb.Compare_MOD}
|
||||
}
|
||||
|
||||
func mustInt64(val interface{}) int64 {
|
||||
if v, ok := val.(int64); ok {
|
||||
return v
|
||||
}
|
||||
if v, ok := val.(int); ok {
|
||||
return int64(v)
|
||||
}
|
||||
panic("bad value")
|
||||
}
|
||||
17
vendor/github.com/coreos/etcd/clientv3/concurrency/doc.go
generated
vendored
Normal file
17
vendor/github.com/coreos/etcd/clientv3/concurrency/doc.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 concurrency implements concurrency operations on top of
|
||||
// etcd such as distributed locks, barriers, and elections.
|
||||
package concurrency
|
||||
183
vendor/github.com/coreos/etcd/clientv3/concurrency/election.go
generated
vendored
Normal file
183
vendor/github.com/coreos/etcd/clientv3/concurrency/election.go
generated
vendored
Normal file
@@ -0,0 +1,183 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 concurrency
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
v3 "github.com/coreos/etcd/clientv3"
|
||||
"github.com/coreos/etcd/storage/storagepb"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrElectionNotLeader = errors.New("election: not leader")
|
||||
ErrElectionNoLeader = errors.New("election: no leader")
|
||||
)
|
||||
|
||||
type Election struct {
|
||||
client *v3.Client
|
||||
|
||||
keyPrefix string
|
||||
|
||||
leaderKey string
|
||||
leaderRev int64
|
||||
leaderSession *Session
|
||||
}
|
||||
|
||||
// NewElection returns a new election on a given key prefix.
|
||||
func NewElection(client *v3.Client, pfx string) *Election {
|
||||
return &Election{client: client, keyPrefix: pfx}
|
||||
}
|
||||
|
||||
// Campaign puts a value as eligible for the election. It blocks until
|
||||
// it is elected, an error occurs, or the context is cancelled.
|
||||
func (e *Election) Campaign(ctx context.Context, val string) error {
|
||||
s, serr := NewSession(e.client)
|
||||
if serr != nil {
|
||||
return serr
|
||||
}
|
||||
|
||||
k, rev, err := NewUniqueKV(ctx, e.client, e.keyPrefix, val, v3.WithLease(s.Lease()))
|
||||
if err == nil {
|
||||
err = waitDeletes(ctx, e.client, e.keyPrefix, v3.WithPrefix(), v3.WithRev(rev-1))
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
// clean up in case of context cancel
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
e.client.Delete(e.client.Ctx(), k)
|
||||
default:
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
e.leaderKey, e.leaderRev, e.leaderSession = k, rev, s
|
||||
return nil
|
||||
}
|
||||
|
||||
// Proclaim lets the leader announce a new value without another election.
|
||||
func (e *Election) Proclaim(ctx context.Context, val string) error {
|
||||
if e.leaderSession == nil {
|
||||
return ErrElectionNotLeader
|
||||
}
|
||||
cmp := v3.Compare(v3.CreateRevision(e.leaderKey), "=", e.leaderRev)
|
||||
txn := e.client.Txn(ctx).If(cmp)
|
||||
txn = txn.Then(v3.OpPut(e.leaderKey, val, v3.WithLease(e.leaderSession.Lease())))
|
||||
tresp, terr := txn.Commit()
|
||||
if terr != nil {
|
||||
return terr
|
||||
}
|
||||
if !tresp.Succeeded {
|
||||
e.leaderKey = ""
|
||||
return ErrElectionNotLeader
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resign lets a leader start a new election.
|
||||
func (e *Election) Resign() (err error) {
|
||||
if e.leaderSession == nil {
|
||||
return nil
|
||||
}
|
||||
_, err = e.client.Delete(e.client.Ctx(), e.leaderKey)
|
||||
e.leaderKey = ""
|
||||
e.leaderSession = nil
|
||||
return err
|
||||
}
|
||||
|
||||
// Leader returns the leader value for the current election.
|
||||
func (e *Election) Leader() (string, error) {
|
||||
resp, err := e.client.Get(e.client.Ctx(), e.keyPrefix, v3.WithFirstCreate()...)
|
||||
if err != nil {
|
||||
return "", err
|
||||
} else if len(resp.Kvs) == 0 {
|
||||
// no leader currently elected
|
||||
return "", ErrElectionNoLeader
|
||||
}
|
||||
return string(resp.Kvs[0].Value), nil
|
||||
}
|
||||
|
||||
// Observe returns a channel that observes all leader proposal values as
|
||||
// GetResponse values on the current leader key. The channel closes when
|
||||
// the context is cancelled or the underlying watcher is otherwise disrupted.
|
||||
func (e *Election) Observe(ctx context.Context) <-chan v3.GetResponse {
|
||||
retc := make(chan v3.GetResponse)
|
||||
go e.observe(ctx, retc)
|
||||
return retc
|
||||
}
|
||||
|
||||
func (e *Election) observe(ctx context.Context, ch chan<- v3.GetResponse) {
|
||||
defer close(ch)
|
||||
for {
|
||||
resp, err := e.client.Get(ctx, e.keyPrefix, v3.WithFirstCreate()...)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
var kv *storagepb.KeyValue
|
||||
|
||||
cctx, cancel := context.WithCancel(ctx)
|
||||
if len(resp.Kvs) == 0 {
|
||||
// wait for first key put on prefix
|
||||
opts := []v3.OpOption{v3.WithRev(resp.Header.Revision), v3.WithPrefix()}
|
||||
wch := e.client.Watch(cctx, e.keyPrefix, opts...)
|
||||
|
||||
for kv == nil {
|
||||
wr, ok := <-wch
|
||||
if !ok || wr.Err() != nil {
|
||||
cancel()
|
||||
return
|
||||
}
|
||||
// only accept PUTs; a DELETE will make observe() spin
|
||||
for _, ev := range wr.Events {
|
||||
if ev.Type == storagepb.PUT {
|
||||
kv = ev.Kv
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
kv = resp.Kvs[0]
|
||||
}
|
||||
|
||||
wch := e.client.Watch(cctx, string(kv.Key), v3.WithRev(kv.ModRevision))
|
||||
keyDeleted := false
|
||||
for !keyDeleted {
|
||||
wr, ok := <-wch
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
for _, ev := range wr.Events {
|
||||
if ev.Type == storagepb.DELETE {
|
||||
keyDeleted = true
|
||||
break
|
||||
}
|
||||
resp.Header = &wr.Header
|
||||
resp.Kvs = []*storagepb.KeyValue{ev.Kv}
|
||||
select {
|
||||
case ch <- *resp:
|
||||
case <-cctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
cancel()
|
||||
}
|
||||
}
|
||||
|
||||
// Key returns the leader key if elected, empty string otherwise.
|
||||
func (e *Election) Key() string { return e.leaderKey }
|
||||
103
vendor/github.com/coreos/etcd/clientv3/concurrency/key.go
generated
vendored
Normal file
103
vendor/github.com/coreos/etcd/clientv3/concurrency/key.go
generated
vendored
Normal file
@@ -0,0 +1,103 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 concurrency
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math"
|
||||
"time"
|
||||
|
||||
v3 "github.com/coreos/etcd/clientv3"
|
||||
"github.com/coreos/etcd/storage/storagepb"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// NewUniqueKey creates a new key from a given prefix.
|
||||
func NewUniqueKey(ctx context.Context, kv v3.KV, pfx string, opts ...v3.OpOption) (string, int64, error) {
|
||||
return NewUniqueKV(ctx, kv, pfx, "", opts...)
|
||||
}
|
||||
|
||||
func NewUniqueKV(ctx context.Context, kv v3.KV, pfx, val string, opts ...v3.OpOption) (string, int64, error) {
|
||||
for {
|
||||
newKey := fmt.Sprintf("%s/%v", pfx, time.Now().UnixNano())
|
||||
put := v3.OpPut(newKey, val, opts...)
|
||||
cmp := v3.Compare(v3.ModRevision(newKey), "=", 0)
|
||||
resp, err := kv.Txn(ctx).If(cmp).Then(put).Commit()
|
||||
if err != nil {
|
||||
return "", 0, err
|
||||
}
|
||||
if !resp.Succeeded {
|
||||
continue
|
||||
}
|
||||
return newKey, resp.Header.Revision, nil
|
||||
}
|
||||
}
|
||||
|
||||
func waitUpdate(ctx context.Context, client *v3.Client, key string, opts ...v3.OpOption) error {
|
||||
cctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
wresp, ok := <-client.Watch(cctx, key, opts...)
|
||||
if !ok {
|
||||
return ctx.Err()
|
||||
}
|
||||
return wresp.Err()
|
||||
}
|
||||
|
||||
func waitDelete(ctx context.Context, client *v3.Client, key string, rev int64) error {
|
||||
cctx, cancel := context.WithCancel(ctx)
|
||||
defer cancel()
|
||||
wch := client.Watch(cctx, key, v3.WithRev(rev))
|
||||
for wr := range wch {
|
||||
for _, ev := range wr.Events {
|
||||
if ev.Type == storagepb.DELETE {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
if err := ctx.Err(); err != nil {
|
||||
return err
|
||||
}
|
||||
return fmt.Errorf("lost watcher waiting for delete")
|
||||
}
|
||||
|
||||
// waitDeletes efficiently waits until all keys matched by Get(key, opts...) are deleted
|
||||
func waitDeletes(ctx context.Context, client *v3.Client, key string, opts ...v3.OpOption) error {
|
||||
getOpts := []v3.OpOption{v3.WithSort(v3.SortByCreateRevision, v3.SortAscend)}
|
||||
getOpts = append(getOpts, opts...)
|
||||
resp, err := client.Get(ctx, key, getOpts...)
|
||||
maxRev := int64(math.MaxInt64)
|
||||
getOpts = append(getOpts, v3.WithRev(0))
|
||||
for err == nil {
|
||||
for len(resp.Kvs) > 0 {
|
||||
i := len(resp.Kvs) - 1
|
||||
if resp.Kvs[i].CreateRevision <= maxRev {
|
||||
break
|
||||
}
|
||||
resp.Kvs = resp.Kvs[:i]
|
||||
}
|
||||
if len(resp.Kvs) == 0 {
|
||||
break
|
||||
}
|
||||
lastKV := resp.Kvs[len(resp.Kvs)-1]
|
||||
maxRev = lastKV.CreateRevision
|
||||
err = waitDelete(ctx, client, string(lastKV.Key), maxRev)
|
||||
if err != nil || len(resp.Kvs) == 1 {
|
||||
break
|
||||
}
|
||||
getOpts = append(getOpts, v3.WithLimit(int64(len(resp.Kvs)-1)))
|
||||
resp, err = client.Get(ctx, key, getOpts...)
|
||||
}
|
||||
return err
|
||||
}
|
||||
88
vendor/github.com/coreos/etcd/clientv3/concurrency/mutex.go
generated
vendored
Normal file
88
vendor/github.com/coreos/etcd/clientv3/concurrency/mutex.go
generated
vendored
Normal file
@@ -0,0 +1,88 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 concurrency
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
v3 "github.com/coreos/etcd/clientv3"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// Mutex implements the sync Locker interface with etcd
|
||||
type Mutex struct {
|
||||
client *v3.Client
|
||||
|
||||
pfx string
|
||||
myKey string
|
||||
myRev int64
|
||||
}
|
||||
|
||||
func NewMutex(client *v3.Client, pfx string) *Mutex {
|
||||
return &Mutex{client, pfx, "", -1}
|
||||
}
|
||||
|
||||
// Lock locks the mutex with a cancellable context. If the context is cancelled
|
||||
// while trying to acquire the lock, the mutex tries to clean its stale lock entry.
|
||||
func (m *Mutex) Lock(ctx context.Context) error {
|
||||
s, err := NewSession(m.client)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// put self in lock waiters via myKey; oldest waiter holds lock
|
||||
m.myKey, m.myRev, err = NewUniqueKey(ctx, m.client, m.pfx, v3.WithLease(s.Lease()))
|
||||
// wait for deletion revisions prior to myKey
|
||||
err = waitDeletes(ctx, m.client, m.pfx, v3.WithPrefix(), v3.WithRev(m.myRev-1))
|
||||
// release lock key if cancelled
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
m.Unlock()
|
||||
default:
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (m *Mutex) Unlock() error {
|
||||
if _, err := m.client.Delete(m.client.Ctx(), m.myKey); err != nil {
|
||||
return err
|
||||
}
|
||||
m.myKey = "\x00"
|
||||
m.myRev = -1
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *Mutex) IsOwner() v3.Cmp {
|
||||
return v3.Compare(v3.CreateRevision(m.myKey), "=", m.myRev)
|
||||
}
|
||||
|
||||
func (m *Mutex) Key() string { return m.myKey }
|
||||
|
||||
type lockerMutex struct{ *Mutex }
|
||||
|
||||
func (lm *lockerMutex) Lock() {
|
||||
if err := lm.Mutex.Lock(lm.client.Ctx()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
func (lm *lockerMutex) Unlock() {
|
||||
if err := lm.Mutex.Unlock(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
// NewLocker creates a sync.Locker backed by an etcd mutex.
|
||||
func NewLocker(client *v3.Client, pfx string) sync.Locker {
|
||||
return &lockerMutex{NewMutex(client, pfx)}
|
||||
}
|
||||
104
vendor/github.com/coreos/etcd/clientv3/concurrency/session.go
generated
vendored
Normal file
104
vendor/github.com/coreos/etcd/clientv3/concurrency/session.go
generated
vendored
Normal file
@@ -0,0 +1,104 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 concurrency
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
v3 "github.com/coreos/etcd/clientv3"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// only keep one ephemeral lease per client
|
||||
var clientSessions clientSessionMgr = clientSessionMgr{sessions: make(map[*v3.Client]*Session)}
|
||||
|
||||
const sessionTTL = 60
|
||||
|
||||
type clientSessionMgr struct {
|
||||
sessions map[*v3.Client]*Session
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
// Session represents a lease kept alive for the lifetime of a client.
|
||||
// Fault-tolerant applications may use sessions to reason about liveness.
|
||||
type Session struct {
|
||||
client *v3.Client
|
||||
id v3.LeaseID
|
||||
|
||||
cancel context.CancelFunc
|
||||
donec <-chan struct{}
|
||||
}
|
||||
|
||||
// NewSession gets the leased session for a client.
|
||||
func NewSession(client *v3.Client) (*Session, error) {
|
||||
clientSessions.mu.Lock()
|
||||
defer clientSessions.mu.Unlock()
|
||||
if s, ok := clientSessions.sessions[client]; ok {
|
||||
return s, nil
|
||||
}
|
||||
|
||||
resp, err := client.Grant(client.Ctx(), sessionTTL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
id := v3.LeaseID(resp.ID)
|
||||
|
||||
ctx, cancel := context.WithCancel(client.Ctx())
|
||||
keepAlive, err := client.KeepAlive(ctx, id)
|
||||
if err != nil || keepAlive == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
donec := make(chan struct{})
|
||||
s := &Session{client: client, id: id, cancel: cancel, donec: donec}
|
||||
clientSessions.sessions[client] = s
|
||||
|
||||
// keep the lease alive until client error or cancelled context
|
||||
go func() {
|
||||
defer func() {
|
||||
clientSessions.mu.Lock()
|
||||
delete(clientSessions.sessions, client)
|
||||
clientSessions.mu.Unlock()
|
||||
close(donec)
|
||||
}()
|
||||
for range keepAlive {
|
||||
// eat messages until keep alive channel closes
|
||||
}
|
||||
}()
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
||||
// Lease is the lease ID for keys bound to the session.
|
||||
func (s *Session) Lease() v3.LeaseID { return s.id }
|
||||
|
||||
// Done returns a channel that closes when the lease is orphaned, expires, or
|
||||
// is otherwise no longer being refreshed.
|
||||
func (s *Session) Done() <-chan struct{} { return s.donec }
|
||||
|
||||
// Orphan ends the refresh for the session lease. This is useful
|
||||
// in case the state of the client connection is indeterminate (revoke
|
||||
// would fail) or when transferring lease ownership.
|
||||
func (s *Session) Orphan() {
|
||||
s.cancel()
|
||||
<-s.donec
|
||||
}
|
||||
|
||||
// Close orphans the session and revokes the session lease.
|
||||
func (s *Session) Close() error {
|
||||
s.Orphan()
|
||||
_, err := s.client.Revoke(s.client.Ctx(), s.id)
|
||||
return err
|
||||
}
|
||||
246
vendor/github.com/coreos/etcd/clientv3/concurrency/stm.go
generated
vendored
Normal file
246
vendor/github.com/coreos/etcd/clientv3/concurrency/stm.go
generated
vendored
Normal file
@@ -0,0 +1,246 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 concurrency
|
||||
|
||||
import (
|
||||
v3 "github.com/coreos/etcd/clientv3"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
// STM is an interface for software transactional memory.
|
||||
type STM interface {
|
||||
// Get returns the value for a key and inserts the key in the txn's read set.
|
||||
// If Get fails, it aborts the transaction with an error, never returning.
|
||||
Get(key string) string
|
||||
// Put adds a value for a key to the write set.
|
||||
Put(key, val string, opts ...v3.OpOption)
|
||||
// Rev returns the revision of a key in the read set.
|
||||
Rev(key string) int64
|
||||
// Del deletes a key.
|
||||
Del(key string)
|
||||
|
||||
// commit attempts to apply the txn's changes to the server.
|
||||
commit() *v3.TxnResponse
|
||||
reset()
|
||||
}
|
||||
|
||||
// stmError safely passes STM errors through panic to the STM error channel.
|
||||
type stmError struct{ err error }
|
||||
|
||||
// NewSTMRepeatable initiates new repeatable read transaction; reads within
|
||||
// the same transaction attempt always return the same data.
|
||||
func NewSTMRepeatable(ctx context.Context, c *v3.Client, apply func(STM) error) (*v3.TxnResponse, error) {
|
||||
s := &stm{client: c, ctx: ctx, getOpts: []v3.OpOption{v3.WithSerializable()}}
|
||||
return runSTM(s, apply)
|
||||
}
|
||||
|
||||
// NewSTMSerializable initiates a new serialized transaction; reads within the
|
||||
// same transactiona attempt return data from the revision of the first read.
|
||||
func NewSTMSerializable(ctx context.Context, c *v3.Client, apply func(STM) error) (*v3.TxnResponse, error) {
|
||||
s := &stmSerializable{
|
||||
stm: stm{client: c, ctx: ctx},
|
||||
prefetch: make(map[string]*v3.GetResponse),
|
||||
}
|
||||
return runSTM(s, apply)
|
||||
}
|
||||
|
||||
type stmResponse struct {
|
||||
resp *v3.TxnResponse
|
||||
err error
|
||||
}
|
||||
|
||||
func runSTM(s STM, apply func(STM) error) (*v3.TxnResponse, error) {
|
||||
outc := make(chan stmResponse, 1)
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
e, ok := r.(stmError)
|
||||
if !ok {
|
||||
// client apply panicked
|
||||
panic(r)
|
||||
}
|
||||
outc <- stmResponse{nil, e.err}
|
||||
}
|
||||
}()
|
||||
var out stmResponse
|
||||
for {
|
||||
s.reset()
|
||||
if out.err = apply(s); out.err != nil {
|
||||
break
|
||||
}
|
||||
if out.resp = s.commit(); out.resp != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
outc <- out
|
||||
}()
|
||||
r := <-outc
|
||||
return r.resp, r.err
|
||||
}
|
||||
|
||||
// stm implements repeatable-read software transactional memory over etcd
|
||||
type stm struct {
|
||||
client *v3.Client
|
||||
ctx context.Context
|
||||
// rset holds read key values and revisions
|
||||
rset map[string]*v3.GetResponse
|
||||
// wset holds overwritten keys and their values
|
||||
wset map[string]stmPut
|
||||
// getOpts are the opts used for gets
|
||||
getOpts []v3.OpOption
|
||||
}
|
||||
|
||||
type stmPut struct {
|
||||
val string
|
||||
op v3.Op
|
||||
}
|
||||
|
||||
func (s *stm) Get(key string) string {
|
||||
if wv, ok := s.wset[key]; ok {
|
||||
return wv.val
|
||||
}
|
||||
return respToValue(s.fetch(key))
|
||||
}
|
||||
|
||||
func (s *stm) Put(key, val string, opts ...v3.OpOption) {
|
||||
s.wset[key] = stmPut{val, v3.OpPut(key, val, opts...)}
|
||||
}
|
||||
|
||||
func (s *stm) Del(key string) { s.wset[key] = stmPut{"", v3.OpDelete(key)} }
|
||||
|
||||
func (s *stm) Rev(key string) int64 {
|
||||
if resp := s.fetch(key); resp != nil && len(resp.Kvs) != 0 {
|
||||
return resp.Kvs[0].ModRevision
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (s *stm) commit() *v3.TxnResponse {
|
||||
txnresp, err := s.client.Txn(s.ctx).If(s.cmps()...).Then(s.puts()...).Commit()
|
||||
if err != nil {
|
||||
panic(stmError{err})
|
||||
}
|
||||
if txnresp.Succeeded {
|
||||
return txnresp
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// cmps guards the txn from updates to read set
|
||||
func (s *stm) cmps() (cmps []v3.Cmp) {
|
||||
for k, rk := range s.rset {
|
||||
cmps = append(cmps, isKeyCurrent(k, rk))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *stm) fetch(key string) *v3.GetResponse {
|
||||
if resp, ok := s.rset[key]; ok {
|
||||
return resp
|
||||
}
|
||||
resp, err := s.client.Get(s.ctx, key, s.getOpts...)
|
||||
if err != nil {
|
||||
panic(stmError{err})
|
||||
}
|
||||
s.rset[key] = resp
|
||||
return resp
|
||||
}
|
||||
|
||||
// puts is the list of ops for all pending writes
|
||||
func (s *stm) puts() (puts []v3.Op) {
|
||||
for _, v := range s.wset {
|
||||
puts = append(puts, v.op)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *stm) reset() {
|
||||
s.rset = make(map[string]*v3.GetResponse)
|
||||
s.wset = make(map[string]stmPut)
|
||||
}
|
||||
|
||||
type stmSerializable struct {
|
||||
stm
|
||||
prefetch map[string]*v3.GetResponse
|
||||
}
|
||||
|
||||
func (s *stmSerializable) Get(key string) string {
|
||||
if wv, ok := s.wset[key]; ok {
|
||||
return wv.val
|
||||
}
|
||||
firstRead := len(s.rset) == 0
|
||||
if resp, ok := s.prefetch[key]; ok {
|
||||
delete(s.prefetch, key)
|
||||
s.rset[key] = resp
|
||||
}
|
||||
resp := s.stm.fetch(key)
|
||||
if firstRead {
|
||||
// txn's base revision is defined by the first read
|
||||
s.getOpts = []v3.OpOption{
|
||||
v3.WithRev(resp.Header.Revision),
|
||||
v3.WithSerializable(),
|
||||
}
|
||||
}
|
||||
return respToValue(resp)
|
||||
}
|
||||
|
||||
func (s *stmSerializable) Rev(key string) int64 {
|
||||
s.Get(key)
|
||||
return s.stm.Rev(key)
|
||||
}
|
||||
|
||||
func (s *stmSerializable) gets() (keys []string, ops []v3.Op) {
|
||||
for k := range s.rset {
|
||||
keys = append(keys, k)
|
||||
ops = append(ops, v3.OpGet(k))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (s *stmSerializable) commit() *v3.TxnResponse {
|
||||
keys, getops := s.gets()
|
||||
txn := s.client.Txn(s.ctx).If(s.cmps()...).Then(s.puts()...)
|
||||
// use Else to prefetch keys in case of conflict to save a round trip
|
||||
txnresp, err := txn.Else(getops...).Commit()
|
||||
if err != nil {
|
||||
panic(stmError{err})
|
||||
}
|
||||
if txnresp.Succeeded {
|
||||
return txnresp
|
||||
}
|
||||
// load prefetch with Else data
|
||||
for i := range keys {
|
||||
resp := txnresp.Responses[i].GetResponseRange()
|
||||
s.rset[keys[i]] = (*v3.GetResponse)(resp)
|
||||
}
|
||||
s.prefetch = s.rset
|
||||
s.getOpts = nil
|
||||
return nil
|
||||
}
|
||||
|
||||
func isKeyCurrent(k string, r *v3.GetResponse) v3.Cmp {
|
||||
rev := r.Header.Revision + 1
|
||||
if len(r.Kvs) != 0 {
|
||||
rev = r.Kvs[0].ModRevision + 1
|
||||
}
|
||||
return v3.Compare(v3.ModRevision(k), "<", rev)
|
||||
}
|
||||
|
||||
func respToValue(resp *v3.GetResponse) string {
|
||||
if len(resp.Kvs) == 0 {
|
||||
return ""
|
||||
}
|
||||
return string(resp.Kvs[0].Value)
|
||||
}
|
||||
111
vendor/github.com/coreos/etcd/clientv3/config.go
generated
vendored
Normal file
111
vendor/github.com/coreos/etcd/clientv3/config.go
generated
vendored
Normal file
@@ -0,0 +1,111 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 (
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"io/ioutil"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/pkg/tlsutil"
|
||||
"github.com/ghodss/yaml"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
// EndpointDialer is a policy for choosing which endpoint to dial next
|
||||
type EndpointDialer func(*Client) (*grpc.ClientConn, error)
|
||||
|
||||
type Config struct {
|
||||
// Endpoints is a list of URLs
|
||||
Endpoints []string
|
||||
|
||||
// RetryDialer chooses the next endpoint to use
|
||||
RetryDialer EndpointDialer
|
||||
|
||||
// DialTimeout is the timeout for failing to establish a connection.
|
||||
DialTimeout time.Duration
|
||||
|
||||
// TLS holds the client secure credentials, if any.
|
||||
TLS *tls.Config
|
||||
|
||||
// Logger is the logger used by client library.
|
||||
Logger Logger
|
||||
}
|
||||
|
||||
type YamlConfig struct {
|
||||
Endpoints []string `json:"endpoints"`
|
||||
DialTimeout time.Duration `json:"dial-timeout"`
|
||||
InsecureTransport bool `json:"insecure-transport"`
|
||||
InsecureSkipTLSVerify bool `json:"insecure-skip-tls-verify"`
|
||||
Certfile string `json:"cert-file"`
|
||||
Keyfile string `json:"key-file"`
|
||||
CAfile string `json:"ca-file"`
|
||||
}
|
||||
|
||||
func configFromFile(fpath string) (*Config, error) {
|
||||
b, err := ioutil.ReadFile(fpath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
yc := &YamlConfig{}
|
||||
|
||||
err = yaml.Unmarshal(b, yc)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
cfg := &Config{
|
||||
Endpoints: yc.Endpoints,
|
||||
DialTimeout: yc.DialTimeout,
|
||||
}
|
||||
|
||||
if yc.InsecureTransport {
|
||||
cfg.TLS = nil
|
||||
return cfg, nil
|
||||
}
|
||||
|
||||
var (
|
||||
cert *tls.Certificate
|
||||
cp *x509.CertPool
|
||||
)
|
||||
|
||||
if yc.Certfile != "" && yc.Keyfile != "" {
|
||||
cert, err = tlsutil.NewCert(yc.Certfile, yc.Keyfile, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if yc.CAfile != "" {
|
||||
cp, err = tlsutil.NewCertPool([]string{yc.CAfile})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
tlscfg := &tls.Config{
|
||||
MinVersion: tls.VersionTLS10,
|
||||
InsecureSkipVerify: yc.InsecureSkipTLSVerify,
|
||||
RootCAs: cp,
|
||||
}
|
||||
if cert != nil {
|
||||
tlscfg.Certificates = []tls.Certificate{*cert}
|
||||
}
|
||||
cfg.TLS = tlscfg
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
64
vendor/github.com/coreos/etcd/clientv3/doc.go
generated
vendored
Normal file
64
vendor/github.com/coreos/etcd/clientv3/doc.go
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 implements the official Go etcd client for v3.
|
||||
//
|
||||
// Create client using `clientv3.New`:
|
||||
//
|
||||
// cli, err := clientv3.New(clientv3.Config{
|
||||
// Endpoints: []string{"localhost:2379", "localhost:22379", "localhost:32379"},
|
||||
// DialTimeout: 5 * time.Second,
|
||||
// })
|
||||
// if err != nil {
|
||||
// // handle error!
|
||||
// }
|
||||
// defer cli.Close()
|
||||
//
|
||||
// Make sure to close the client after using it. If the client is not closed, the
|
||||
// connection will have leaky goroutines.
|
||||
//
|
||||
// To specify client request timeout, pass context.WithTimeout to APIs:
|
||||
//
|
||||
// ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
// resp, err := kvc.Put(ctx, "sample_key", "sample_value")
|
||||
// cancel()
|
||||
// if err != nil {
|
||||
// // handle error!
|
||||
// }
|
||||
// // use the response
|
||||
//
|
||||
// The Client has internal state (watchers and leases), so Clients should be reused instead of created as needed.
|
||||
// Clients are safe for concurrent use by multiple goroutines.
|
||||
//
|
||||
// etcd client returns 2 types of errors:
|
||||
//
|
||||
// 1. context error: canceled or deadline exceeded.
|
||||
// 2. gRPC error: see https://github.com/coreos/etcd/blob/master/etcdserver/api/v3rpc/error.go.
|
||||
//
|
||||
// Here is the example code to handle client errors:
|
||||
//
|
||||
// resp, err := kvc.Put(ctx, "", "")
|
||||
// if err != nil {
|
||||
// if err == context.Canceled {
|
||||
// // ctx is canceled by another routine
|
||||
// } else if err == context.DeadlineExceeded {
|
||||
// // ctx is attached with a deadline and it exceeded
|
||||
// } else if verr, ok := err.(*v3rpc.ErrEmptyKey); ok {
|
||||
// // process (verr.Errors)
|
||||
// } else {
|
||||
// // bad cluster endpoints, which are not etcd servers
|
||||
// }
|
||||
// }
|
||||
//
|
||||
package clientv3
|
||||
17
vendor/github.com/coreos/etcd/clientv3/integration/doc.go
generated
vendored
Normal file
17
vendor/github.com/coreos/etcd/clientv3/integration/doc.go
generated
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package integration implements tests built upon embedded etcd, and focuses on
|
||||
// correctness of etcd client.
|
||||
package integration
|
||||
207
vendor/github.com/coreos/etcd/clientv3/kv.go
generated
vendored
Normal file
207
vendor/github.com/coreos/etcd/clientv3/kv.go
generated
vendored
Normal file
@@ -0,0 +1,207 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 (
|
||||
"sync"
|
||||
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type (
|
||||
PutResponse pb.PutResponse
|
||||
GetResponse pb.RangeResponse
|
||||
DeleteResponse pb.DeleteRangeResponse
|
||||
TxnResponse pb.TxnResponse
|
||||
)
|
||||
|
||||
type KV interface {
|
||||
// Put puts a key-value pair into etcd.
|
||||
// Note that key,value can be plain bytes array and string is
|
||||
// an immutable representation of that bytes array.
|
||||
// To get a string of bytes, do string([]byte(0x10, 0x20)).
|
||||
Put(ctx context.Context, key, val string, opts ...OpOption) (*PutResponse, error)
|
||||
|
||||
// Get retrieves keys.
|
||||
// By default, Get will return the value for "key", if any.
|
||||
// When passed WithRange(end), Get will return the keys in the range [key, end).
|
||||
// When passed WithFromKey(), Get returns keys greater than or equal to key.
|
||||
// When passed WithRev(rev) with rev > 0, Get retrieves keys at the given revision;
|
||||
// if the required revision is compacted, the request will fail with ErrCompacted .
|
||||
// When passed WithLimit(limit), the number of returned keys is bounded by limit.
|
||||
// When passed WithSort(), the keys will be sorted.
|
||||
Get(ctx context.Context, key string, opts ...OpOption) (*GetResponse, error)
|
||||
|
||||
// Delete deletes a key, or optionally using WithRange(end), [key, end).
|
||||
Delete(ctx context.Context, key string, opts ...OpOption) (*DeleteResponse, error)
|
||||
|
||||
// Compact compacts etcd KV history before the given rev.
|
||||
Compact(ctx context.Context, rev int64) error
|
||||
|
||||
// Do applies a single Op on KV without a transaction.
|
||||
// Do is useful when declaring operations to be issued at a later time
|
||||
// whereas Get/Put/Delete are for better suited for when the operation
|
||||
// should be immediately issued at time of declaration.
|
||||
|
||||
// Do applies a single Op on KV without a transaction.
|
||||
// Do is useful when creating arbitrary operations to be issued at a
|
||||
// later time; the user can range over the operations, calling Do to
|
||||
// execute them. Get/Put/Delete, on the other hand, are best suited
|
||||
// for when the operation should be issued at the time of declaration.
|
||||
Do(ctx context.Context, op Op) (OpResponse, error)
|
||||
|
||||
// Txn creates a transaction.
|
||||
Txn(ctx context.Context) Txn
|
||||
}
|
||||
|
||||
type OpResponse struct {
|
||||
put *PutResponse
|
||||
get *GetResponse
|
||||
del *DeleteResponse
|
||||
}
|
||||
|
||||
type kv struct {
|
||||
c *Client
|
||||
|
||||
mu sync.Mutex // guards all fields
|
||||
conn *grpc.ClientConn // conn in-use
|
||||
remote pb.KVClient
|
||||
}
|
||||
|
||||
func NewKV(c *Client) KV {
|
||||
conn := c.ActiveConnection()
|
||||
remote := pb.NewKVClient(conn)
|
||||
|
||||
return &kv{
|
||||
conn: c.ActiveConnection(),
|
||||
remote: remote,
|
||||
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (kv *kv) Put(ctx context.Context, key, val string, opts ...OpOption) (*PutResponse, error) {
|
||||
r, err := kv.Do(ctx, OpPut(key, val, opts...))
|
||||
return r.put, err
|
||||
}
|
||||
|
||||
func (kv *kv) Get(ctx context.Context, key string, opts ...OpOption) (*GetResponse, error) {
|
||||
r, err := kv.Do(ctx, OpGet(key, opts...))
|
||||
return r.get, err
|
||||
}
|
||||
|
||||
func (kv *kv) Delete(ctx context.Context, key string, opts ...OpOption) (*DeleteResponse, error) {
|
||||
r, err := kv.Do(ctx, OpDelete(key, opts...))
|
||||
return r.del, err
|
||||
}
|
||||
|
||||
func (kv *kv) Compact(ctx context.Context, rev int64) error {
|
||||
r := &pb.CompactionRequest{Revision: rev}
|
||||
_, err := kv.getRemote().Compact(ctx, r)
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if isHalted(ctx, err) {
|
||||
return err
|
||||
}
|
||||
|
||||
go kv.switchRemote(err)
|
||||
return err
|
||||
}
|
||||
|
||||
func (kv *kv) Txn(ctx context.Context) Txn {
|
||||
return &txn{
|
||||
kv: kv,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
func (kv *kv) Do(ctx context.Context, op Op) (OpResponse, error) {
|
||||
for {
|
||||
var err error
|
||||
switch op.t {
|
||||
// TODO: handle other ops
|
||||
case tRange:
|
||||
var resp *pb.RangeResponse
|
||||
r := &pb.RangeRequest{Key: op.key, RangeEnd: op.end, Limit: op.limit, Revision: op.rev, Serializable: op.serializable}
|
||||
if op.sort != nil {
|
||||
r.SortOrder = pb.RangeRequest_SortOrder(op.sort.Order)
|
||||
r.SortTarget = pb.RangeRequest_SortTarget(op.sort.Target)
|
||||
}
|
||||
|
||||
resp, err = kv.getRemote().Range(ctx, r)
|
||||
if err == nil {
|
||||
return OpResponse{get: (*GetResponse)(resp)}, nil
|
||||
}
|
||||
case tPut:
|
||||
var resp *pb.PutResponse
|
||||
r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID)}
|
||||
resp, err = kv.getRemote().Put(ctx, r)
|
||||
if err == nil {
|
||||
return OpResponse{put: (*PutResponse)(resp)}, nil
|
||||
}
|
||||
case tDeleteRange:
|
||||
var resp *pb.DeleteRangeResponse
|
||||
r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end}
|
||||
resp, err = kv.getRemote().DeleteRange(ctx, r)
|
||||
if err == nil {
|
||||
return OpResponse{del: (*DeleteResponse)(resp)}, nil
|
||||
}
|
||||
default:
|
||||
panic("Unknown op")
|
||||
}
|
||||
|
||||
if isHalted(ctx, err) {
|
||||
return OpResponse{}, err
|
||||
}
|
||||
|
||||
// do not retry on modifications
|
||||
if op.isWrite() {
|
||||
go kv.switchRemote(err)
|
||||
return OpResponse{}, err
|
||||
}
|
||||
|
||||
if nerr := kv.switchRemote(err); nerr != nil {
|
||||
return OpResponse{}, nerr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (kv *kv) switchRemote(prevErr error) error {
|
||||
// Usually it's a bad idea to lock on network i/o but here it's OK
|
||||
// since the link is down and new requests can't be processed anyway.
|
||||
// Likewise, if connecting stalls, closing the Client can break the
|
||||
// lock via context cancelation.
|
||||
kv.mu.Lock()
|
||||
defer kv.mu.Unlock()
|
||||
|
||||
newConn, err := kv.c.retryConnection(kv.conn, prevErr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
kv.conn = newConn
|
||||
kv.remote = pb.NewKVClient(kv.conn)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (kv *kv) getRemote() pb.KVClient {
|
||||
kv.mu.Lock()
|
||||
defer kv.mu.Unlock()
|
||||
return kv.remote
|
||||
}
|
||||
433
vendor/github.com/coreos/etcd/clientv3/lease.go
generated
vendored
Normal file
433
vendor/github.com/coreos/etcd/clientv3/lease.go
generated
vendored
Normal file
@@ -0,0 +1,433 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type (
|
||||
LeaseGrantResponse pb.LeaseGrantResponse
|
||||
LeaseRevokeResponse pb.LeaseRevokeResponse
|
||||
LeaseKeepAliveResponse pb.LeaseKeepAliveResponse
|
||||
LeaseID int64
|
||||
)
|
||||
|
||||
const (
|
||||
// a small buffer to store unsent lease responses.
|
||||
leaseResponseChSize = 16
|
||||
// NoLease is a lease ID for the absence of a lease.
|
||||
NoLease LeaseID = 0
|
||||
)
|
||||
|
||||
type Lease interface {
|
||||
// Grant creates a new lease.
|
||||
Grant(ctx context.Context, ttl int64) (*LeaseGrantResponse, error)
|
||||
|
||||
// Revoke revokes the given lease.
|
||||
Revoke(ctx context.Context, id LeaseID) (*LeaseRevokeResponse, error)
|
||||
|
||||
// KeepAlive keeps the given lease alive forever.
|
||||
KeepAlive(ctx context.Context, id LeaseID) (<-chan *LeaseKeepAliveResponse, error)
|
||||
|
||||
// KeepAliveOnce renews the lease once. In most of the cases, Keepalive
|
||||
// should be used instead of KeepAliveOnce.
|
||||
KeepAliveOnce(ctx context.Context, id LeaseID) (*LeaseKeepAliveResponse, error)
|
||||
|
||||
// Close releases all resources Lease keeps for efficient communication
|
||||
// with the etcd server.
|
||||
Close() error
|
||||
}
|
||||
|
||||
type lessor struct {
|
||||
c *Client
|
||||
|
||||
mu sync.Mutex // guards all fields
|
||||
conn *grpc.ClientConn // conn in-use
|
||||
|
||||
// donec is closed when recvKeepAliveLoop stops
|
||||
donec chan struct{}
|
||||
|
||||
remote pb.LeaseClient
|
||||
|
||||
stream pb.Lease_LeaseKeepAliveClient
|
||||
streamCancel context.CancelFunc
|
||||
|
||||
stopCtx context.Context
|
||||
stopCancel context.CancelFunc
|
||||
|
||||
keepAlives map[LeaseID]*keepAlive
|
||||
}
|
||||
|
||||
// keepAlive multiplexes a keepalive for a lease over multiple channels
|
||||
type keepAlive struct {
|
||||
chs []chan<- *LeaseKeepAliveResponse
|
||||
ctxs []context.Context
|
||||
// deadline is the next time to send a keep alive message
|
||||
deadline time.Time
|
||||
// donec is closed on lease revoke, expiration, or cancel.
|
||||
donec chan struct{}
|
||||
}
|
||||
|
||||
func NewLease(c *Client) Lease {
|
||||
l := &lessor{
|
||||
c: c,
|
||||
conn: c.ActiveConnection(),
|
||||
|
||||
donec: make(chan struct{}),
|
||||
keepAlives: make(map[LeaseID]*keepAlive),
|
||||
}
|
||||
|
||||
l.remote = pb.NewLeaseClient(l.conn)
|
||||
l.stopCtx, l.stopCancel = context.WithCancel(context.Background())
|
||||
|
||||
go l.recvKeepAliveLoop()
|
||||
|
||||
return l
|
||||
}
|
||||
|
||||
func (l *lessor) Grant(ctx context.Context, ttl int64) (*LeaseGrantResponse, error) {
|
||||
cctx, cancel := context.WithCancel(ctx)
|
||||
done := cancelWhenStop(cancel, l.stopCtx.Done())
|
||||
defer close(done)
|
||||
|
||||
for {
|
||||
r := &pb.LeaseGrantRequest{TTL: ttl}
|
||||
resp, err := l.getRemote().LeaseGrant(cctx, r)
|
||||
if err == nil {
|
||||
return (*LeaseGrantResponse)(resp), nil
|
||||
}
|
||||
if isHalted(cctx, err) {
|
||||
return nil, err
|
||||
}
|
||||
if nerr := l.switchRemoteAndStream(err); nerr != nil {
|
||||
return nil, nerr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lessor) Revoke(ctx context.Context, id LeaseID) (*LeaseRevokeResponse, error) {
|
||||
cctx, cancel := context.WithCancel(ctx)
|
||||
done := cancelWhenStop(cancel, l.stopCtx.Done())
|
||||
defer close(done)
|
||||
|
||||
for {
|
||||
r := &pb.LeaseRevokeRequest{ID: int64(id)}
|
||||
resp, err := l.getRemote().LeaseRevoke(cctx, r)
|
||||
|
||||
if err == nil {
|
||||
return (*LeaseRevokeResponse)(resp), nil
|
||||
}
|
||||
if isHalted(ctx, err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if nerr := l.switchRemoteAndStream(err); nerr != nil {
|
||||
return nil, nerr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lessor) KeepAlive(ctx context.Context, id LeaseID) (<-chan *LeaseKeepAliveResponse, error) {
|
||||
ch := make(chan *LeaseKeepAliveResponse, leaseResponseChSize)
|
||||
|
||||
l.mu.Lock()
|
||||
ka, ok := l.keepAlives[id]
|
||||
if !ok {
|
||||
// create fresh keep alive
|
||||
ka = &keepAlive{
|
||||
chs: []chan<- *LeaseKeepAliveResponse{ch},
|
||||
ctxs: []context.Context{ctx},
|
||||
deadline: time.Now(),
|
||||
donec: make(chan struct{}),
|
||||
}
|
||||
l.keepAlives[id] = ka
|
||||
} else {
|
||||
// add channel and context to existing keep alive
|
||||
ka.ctxs = append(ka.ctxs, ctx)
|
||||
ka.chs = append(ka.chs, ch)
|
||||
}
|
||||
l.mu.Unlock()
|
||||
|
||||
go l.keepAliveCtxCloser(id, ctx, ka.donec)
|
||||
|
||||
return ch, nil
|
||||
}
|
||||
|
||||
func (l *lessor) KeepAliveOnce(ctx context.Context, id LeaseID) (*LeaseKeepAliveResponse, error) {
|
||||
cctx, cancel := context.WithCancel(ctx)
|
||||
done := cancelWhenStop(cancel, l.stopCtx.Done())
|
||||
defer close(done)
|
||||
|
||||
for {
|
||||
resp, err := l.keepAliveOnce(cctx, id)
|
||||
if err == nil {
|
||||
return resp, err
|
||||
}
|
||||
|
||||
nerr := l.switchRemoteAndStream(err)
|
||||
if nerr != nil {
|
||||
return nil, nerr
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lessor) Close() error {
|
||||
l.stopCancel()
|
||||
<-l.donec
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *lessor) keepAliveCtxCloser(id LeaseID, ctx context.Context, donec <-chan struct{}) {
|
||||
select {
|
||||
case <-donec:
|
||||
return
|
||||
case <-l.donec:
|
||||
return
|
||||
case <-ctx.Done():
|
||||
}
|
||||
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
ka, ok := l.keepAlives[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// close channel and remove context if still associated with keep alive
|
||||
for i, c := range ka.ctxs {
|
||||
if c == ctx {
|
||||
close(ka.chs[i])
|
||||
ka.ctxs = append(ka.ctxs[:i], ka.ctxs[i+1:]...)
|
||||
ka.chs = append(ka.chs[:i], ka.chs[i+1:]...)
|
||||
break
|
||||
}
|
||||
}
|
||||
// remove if no one more listeners
|
||||
if len(ka.chs) == 0 {
|
||||
delete(l.keepAlives, id)
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lessor) keepAliveOnce(ctx context.Context, id LeaseID) (*LeaseKeepAliveResponse, error) {
|
||||
stream, err := l.getRemote().LeaseKeepAlive(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = stream.Send(&pb.LeaseKeepAliveRequest{ID: int64(id)})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, rerr := stream.Recv()
|
||||
if rerr != nil {
|
||||
return nil, rerr
|
||||
}
|
||||
return (*LeaseKeepAliveResponse)(resp), nil
|
||||
}
|
||||
|
||||
func (l *lessor) recvKeepAliveLoop() {
|
||||
defer func() {
|
||||
l.stopCancel()
|
||||
l.mu.Lock()
|
||||
close(l.donec)
|
||||
for _, ka := range l.keepAlives {
|
||||
ka.Close()
|
||||
}
|
||||
l.keepAlives = make(map[LeaseID]*keepAlive)
|
||||
l.mu.Unlock()
|
||||
}()
|
||||
|
||||
stream, serr := l.resetRecv()
|
||||
for serr == nil {
|
||||
resp, err := stream.Recv()
|
||||
if err != nil {
|
||||
if isHalted(l.stopCtx, err) {
|
||||
return
|
||||
}
|
||||
stream, serr = l.resetRecv()
|
||||
continue
|
||||
}
|
||||
l.recvKeepAlive(resp)
|
||||
}
|
||||
}
|
||||
|
||||
// resetRecv opens a new lease stream and starts sending LeaseKeepAliveRequests
|
||||
func (l *lessor) resetRecv() (pb.Lease_LeaseKeepAliveClient, error) {
|
||||
if err := l.switchRemoteAndStream(nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
stream := l.getKeepAliveStream()
|
||||
go l.sendKeepAliveLoop(stream)
|
||||
return stream, nil
|
||||
}
|
||||
|
||||
// recvKeepAlive updates a lease based on its LeaseKeepAliveResponse
|
||||
func (l *lessor) recvKeepAlive(resp *pb.LeaseKeepAliveResponse) {
|
||||
id := LeaseID(resp.ID)
|
||||
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
|
||||
ka, ok := l.keepAlives[id]
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if resp.TTL <= 0 {
|
||||
// lease expired; close all keep alive channels
|
||||
delete(l.keepAlives, id)
|
||||
ka.Close()
|
||||
return
|
||||
}
|
||||
|
||||
// send update to all channels
|
||||
nextDeadline := time.Now().Add(1 + time.Duration(resp.TTL/3)*time.Second)
|
||||
for _, ch := range ka.chs {
|
||||
select {
|
||||
case ch <- (*LeaseKeepAliveResponse)(resp):
|
||||
ka.deadline = nextDeadline
|
||||
default:
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// sendKeepAliveLoop sends LeaseKeepAliveRequests for the lifetime of a lease stream
|
||||
func (l *lessor) sendKeepAliveLoop(stream pb.Lease_LeaseKeepAliveClient) {
|
||||
for {
|
||||
select {
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
case <-l.donec:
|
||||
return
|
||||
case <-l.stopCtx.Done():
|
||||
return
|
||||
}
|
||||
|
||||
tosend := make([]LeaseID, 0)
|
||||
|
||||
now := time.Now()
|
||||
l.mu.Lock()
|
||||
for id, ka := range l.keepAlives {
|
||||
if ka.deadline.Before(now) {
|
||||
tosend = append(tosend, id)
|
||||
}
|
||||
}
|
||||
l.mu.Unlock()
|
||||
|
||||
for _, id := range tosend {
|
||||
r := &pb.LeaseKeepAliveRequest{ID: int64(id)}
|
||||
if err := stream.Send(r); err != nil {
|
||||
// TODO do something with this error?
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lessor) getRemote() pb.LeaseClient {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
return l.remote
|
||||
}
|
||||
|
||||
func (l *lessor) getKeepAliveStream() pb.Lease_LeaseKeepAliveClient {
|
||||
l.mu.Lock()
|
||||
defer l.mu.Unlock()
|
||||
return l.stream
|
||||
}
|
||||
|
||||
func (l *lessor) switchRemoteAndStream(prevErr error) error {
|
||||
l.mu.Lock()
|
||||
conn := l.conn
|
||||
l.mu.Unlock()
|
||||
|
||||
var (
|
||||
err error
|
||||
newConn *grpc.ClientConn
|
||||
)
|
||||
|
||||
if prevErr != nil {
|
||||
conn.Close()
|
||||
newConn, err = l.c.retryConnection(conn, prevErr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
l.mu.Lock()
|
||||
if newConn != nil {
|
||||
l.conn = newConn
|
||||
}
|
||||
|
||||
l.remote = pb.NewLeaseClient(l.conn)
|
||||
l.mu.Unlock()
|
||||
|
||||
serr := l.newStream()
|
||||
if serr != nil {
|
||||
return serr
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *lessor) newStream() error {
|
||||
sctx, cancel := context.WithCancel(l.stopCtx)
|
||||
stream, err := l.getRemote().LeaseKeepAlive(sctx)
|
||||
if err != nil {
|
||||
cancel()
|
||||
return 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() {
|
||||
close(ka.donec)
|
||||
for _, ch := range ka.chs {
|
||||
close(ch)
|
||||
}
|
||||
}
|
||||
|
||||
// cancelWhenStop calls cancel when the given stopc fires. It returns a done chan. done
|
||||
// should be closed when the work is finished. When done fires, cancelWhenStop will release
|
||||
// its internal resource.
|
||||
func cancelWhenStop(cancel context.CancelFunc, stopc <-chan struct{}) chan<- struct{} {
|
||||
done := make(chan struct{}, 1)
|
||||
|
||||
go func() {
|
||||
select {
|
||||
case <-stopc:
|
||||
case <-done:
|
||||
}
|
||||
cancel()
|
||||
}()
|
||||
|
||||
return done
|
||||
}
|
||||
64
vendor/github.com/coreos/etcd/clientv3/logger.go
generated
vendored
Normal file
64
vendor/github.com/coreos/etcd/clientv3/logger.go
generated
vendored
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 (
|
||||
"log"
|
||||
"os"
|
||||
"sync"
|
||||
|
||||
"google.golang.org/grpc/grpclog"
|
||||
)
|
||||
|
||||
type Logger grpclog.Logger
|
||||
|
||||
var (
|
||||
logger settableLogger
|
||||
)
|
||||
|
||||
type settableLogger struct {
|
||||
l grpclog.Logger
|
||||
mu sync.RWMutex
|
||||
}
|
||||
|
||||
func init() {
|
||||
// use go's standard logger by default like grpc
|
||||
logger.mu.Lock()
|
||||
logger.l = log.New(os.Stderr, "", log.LstdFlags)
|
||||
grpclog.SetLogger(&logger)
|
||||
logger.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *settableLogger) Set(l Logger) {
|
||||
s.mu.Lock()
|
||||
logger.l = l
|
||||
s.mu.Unlock()
|
||||
}
|
||||
|
||||
func (s *settableLogger) Get() Logger {
|
||||
s.mu.RLock()
|
||||
l := logger.l
|
||||
s.mu.RUnlock()
|
||||
return l
|
||||
}
|
||||
|
||||
// implement the grpclog.Logger interface
|
||||
|
||||
func (s *settableLogger) Fatal(args ...interface{}) { s.Get().Fatal(args...) }
|
||||
func (s *settableLogger) Fatalf(format string, args ...interface{}) { s.Get().Fatalf(format, args...) }
|
||||
func (s *settableLogger) Fatalln(args ...interface{}) { s.Get().Fatalln(args...) }
|
||||
func (s *settableLogger) Print(args ...interface{}) { s.Get().Print(args...) }
|
||||
func (s *settableLogger) Printf(format string, args ...interface{}) { s.Get().Printf(format, args...) }
|
||||
func (s *settableLogger) Println(args ...interface{}) { s.Get().Println(args...) }
|
||||
164
vendor/github.com/coreos/etcd/clientv3/maintenance.go
generated
vendored
Normal file
164
vendor/github.com/coreos/etcd/clientv3/maintenance.go
generated
vendored
Normal file
@@ -0,0 +1,164 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 (
|
||||
"sync"
|
||||
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
type (
|
||||
DefragmentResponse pb.DefragmentResponse
|
||||
AlarmResponse pb.AlarmResponse
|
||||
AlarmMember pb.AlarmMember
|
||||
StatusResponse pb.StatusResponse
|
||||
)
|
||||
|
||||
type Maintenance interface {
|
||||
// AlarmList gets all active alarms.
|
||||
AlarmList(ctx context.Context) (*AlarmResponse, error)
|
||||
|
||||
// AlarmDisarm disarms a given alarm.
|
||||
AlarmDisarm(ctx context.Context, m *AlarmMember) (*AlarmResponse, error)
|
||||
|
||||
// Defragment defragments storage backend of the etcd member with given endpoint.
|
||||
// Defragment is only needed when deleting a large number of keys and want to reclaim
|
||||
// the resources.
|
||||
// Defragment is an expensive operation. User should avoid defragmenting multiple members
|
||||
// at the same time.
|
||||
// To defragment multiple members in the cluster, user need to call defragment multiple
|
||||
// times with different endpoints.
|
||||
Defragment(ctx context.Context, endpoint string) (*DefragmentResponse, error)
|
||||
|
||||
// Status gets the status of the member.
|
||||
Status(ctx context.Context, endpoint string) (*StatusResponse, error)
|
||||
}
|
||||
|
||||
type maintenance struct {
|
||||
c *Client
|
||||
|
||||
mu sync.Mutex
|
||||
conn *grpc.ClientConn // conn in-use
|
||||
remote pb.MaintenanceClient
|
||||
}
|
||||
|
||||
func NewMaintenance(c *Client) Maintenance {
|
||||
conn := c.ActiveConnection()
|
||||
return &maintenance{
|
||||
c: c,
|
||||
conn: conn,
|
||||
remote: pb.NewMaintenanceClient(conn),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *maintenance) AlarmList(ctx context.Context) (*AlarmResponse, error) {
|
||||
req := &pb.AlarmRequest{
|
||||
Action: pb.AlarmRequest_GET,
|
||||
MemberID: 0, // all
|
||||
Alarm: pb.AlarmType_NONE, // all
|
||||
}
|
||||
for {
|
||||
resp, err := m.getRemote().Alarm(ctx, req)
|
||||
if err == nil {
|
||||
return (*AlarmResponse)(resp), nil
|
||||
}
|
||||
if isHalted(ctx, err) {
|
||||
return nil, err
|
||||
}
|
||||
if err = m.switchRemote(err); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (m *maintenance) AlarmDisarm(ctx context.Context, am *AlarmMember) (*AlarmResponse, error) {
|
||||
req := &pb.AlarmRequest{
|
||||
Action: pb.AlarmRequest_DEACTIVATE,
|
||||
MemberID: am.MemberID,
|
||||
Alarm: am.Alarm,
|
||||
}
|
||||
|
||||
if req.MemberID == 0 && req.Alarm == pb.AlarmType_NONE {
|
||||
ar, err := m.AlarmList(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ret := AlarmResponse{}
|
||||
for _, am := range ar.Alarms {
|
||||
dresp, derr := m.AlarmDisarm(ctx, (*AlarmMember)(am))
|
||||
if derr != nil {
|
||||
return nil, derr
|
||||
}
|
||||
ret.Alarms = append(ret.Alarms, dresp.Alarms...)
|
||||
}
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
resp, err := m.getRemote().Alarm(ctx, req)
|
||||
if err == nil {
|
||||
return (*AlarmResponse)(resp), nil
|
||||
}
|
||||
if !isHalted(ctx, err) {
|
||||
go m.switchRemote(err)
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (m *maintenance) Defragment(ctx context.Context, endpoint string) (*DefragmentResponse, error) {
|
||||
conn, err := m.c.Dial(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
remote := pb.NewMaintenanceClient(conn)
|
||||
resp, err := remote.Defragment(ctx, &pb.DefragmentRequest{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return (*DefragmentResponse)(resp), nil
|
||||
}
|
||||
|
||||
func (m *maintenance) Status(ctx context.Context, endpoint string) (*StatusResponse, error) {
|
||||
conn, err := m.c.Dial(endpoint)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
remote := pb.NewMaintenanceClient(conn)
|
||||
resp, err := remote.Status(ctx, &pb.StatusRequest{})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return (*StatusResponse)(resp), nil
|
||||
}
|
||||
|
||||
func (m *maintenance) getRemote() pb.MaintenanceClient {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
return m.remote
|
||||
}
|
||||
|
||||
func (m *maintenance) switchRemote(prevErr error) error {
|
||||
m.mu.Lock()
|
||||
defer m.mu.Unlock()
|
||||
newConn, err := m.c.retryConnection(m.conn, prevErr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
m.conn = newConn
|
||||
m.remote = pb.NewMaintenanceClient(m.conn)
|
||||
return nil
|
||||
}
|
||||
110
vendor/github.com/coreos/etcd/clientv3/mirror/syncer.go
generated
vendored
Normal file
110
vendor/github.com/coreos/etcd/clientv3/mirror/syncer.go
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 mirror implements etcd mirroring operations.
|
||||
package mirror
|
||||
|
||||
import (
|
||||
"github.com/coreos/etcd/clientv3"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const (
|
||||
batchLimit = 1000
|
||||
)
|
||||
|
||||
// Syncer syncs with the key-value state of an etcd cluster.
|
||||
type Syncer interface {
|
||||
// SyncBase syncs the base state of the key-value state.
|
||||
// The key-value state are sent through the returned chan.
|
||||
SyncBase(ctx context.Context) (<-chan clientv3.GetResponse, chan error)
|
||||
// SyncUpdates syncs the updates of the key-value state.
|
||||
// The update events are sent through the returned chan.
|
||||
SyncUpdates(ctx context.Context) clientv3.WatchChan
|
||||
}
|
||||
|
||||
// NewSyncer creates a Syncer.
|
||||
func NewSyncer(c *clientv3.Client, prefix string, rev int64) Syncer {
|
||||
return &syncer{c: c, prefix: prefix, rev: rev}
|
||||
}
|
||||
|
||||
type syncer struct {
|
||||
c *clientv3.Client
|
||||
rev int64
|
||||
prefix string
|
||||
}
|
||||
|
||||
func (s *syncer) SyncBase(ctx context.Context) (<-chan clientv3.GetResponse, chan error) {
|
||||
respchan := make(chan clientv3.GetResponse, 1024)
|
||||
errchan := make(chan error, 1)
|
||||
|
||||
// if rev is not specified, we will choose the most recent revision.
|
||||
if s.rev == 0 {
|
||||
resp, err := s.c.Get(ctx, "foo")
|
||||
if err != nil {
|
||||
errchan <- err
|
||||
close(respchan)
|
||||
close(errchan)
|
||||
return respchan, errchan
|
||||
}
|
||||
s.rev = resp.Header.Revision
|
||||
}
|
||||
|
||||
go func() {
|
||||
defer close(respchan)
|
||||
defer close(errchan)
|
||||
|
||||
var key string
|
||||
|
||||
opts := []clientv3.OpOption{clientv3.WithLimit(batchLimit), clientv3.WithRev(s.rev)}
|
||||
|
||||
if len(s.prefix) == 0 {
|
||||
// If len(s.prefix) == 0, we will sync the entire key-value space.
|
||||
// We then range from the smallest key (0x00) to the end.
|
||||
opts = append(opts, clientv3.WithFromKey())
|
||||
key = "\x00"
|
||||
} else {
|
||||
// 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
|
||||
// range from the prefix to the end if the next prefix does not exists.
|
||||
opts = append(opts, clientv3.WithPrefix())
|
||||
key = s.prefix
|
||||
}
|
||||
|
||||
for {
|
||||
resp, err := s.c.Get(ctx, key, opts...)
|
||||
if err != nil {
|
||||
errchan <- err
|
||||
return
|
||||
}
|
||||
|
||||
respchan <- (clientv3.GetResponse)(*resp)
|
||||
|
||||
if !resp.More {
|
||||
return
|
||||
}
|
||||
// move to next key
|
||||
key = string(append(resp.Kvs[len(resp.Kvs)-1].Key, 0))
|
||||
}
|
||||
}()
|
||||
|
||||
return respchan, errchan
|
||||
}
|
||||
|
||||
func (s *syncer) SyncUpdates(ctx context.Context) clientv3.WatchChan {
|
||||
if s.rev == 0 {
|
||||
panic("unexpected revision = 0. Calling SyncUpdates before SyncBase finishes?")
|
||||
}
|
||||
return s.c.Watch(ctx, s.prefix, clientv3.WithPrefix(), clientv3.WithRev(s.rev+1))
|
||||
}
|
||||
240
vendor/github.com/coreos/etcd/clientv3/op.go
generated
vendored
Normal file
240
vendor/github.com/coreos/etcd/clientv3/op.go
generated
vendored
Normal file
@@ -0,0 +1,240 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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"
|
||||
)
|
||||
|
||||
type opType int
|
||||
|
||||
const (
|
||||
// A default Op has opType 0, which is invalid.
|
||||
tRange opType = iota + 1
|
||||
tPut
|
||||
tDeleteRange
|
||||
)
|
||||
|
||||
var (
|
||||
noPrefixEnd = []byte{0}
|
||||
)
|
||||
|
||||
// Op represents an Operation that kv can execute.
|
||||
type Op struct {
|
||||
t opType
|
||||
key []byte
|
||||
end []byte
|
||||
|
||||
// for range
|
||||
limit int64
|
||||
sort *SortOption
|
||||
serializable bool
|
||||
|
||||
// for range, watch
|
||||
rev int64
|
||||
|
||||
// progressNotify is for progress updates.
|
||||
progressNotify bool
|
||||
|
||||
// for put
|
||||
val []byte
|
||||
leaseID LeaseID
|
||||
}
|
||||
|
||||
func (op Op) toRequestUnion() *pb.RequestUnion {
|
||||
switch op.t {
|
||||
case tRange:
|
||||
r := &pb.RangeRequest{Key: op.key, RangeEnd: op.end, Limit: op.limit, Revision: op.rev, Serializable: op.serializable}
|
||||
if op.sort != nil {
|
||||
r.SortOrder = pb.RangeRequest_SortOrder(op.sort.Order)
|
||||
r.SortTarget = pb.RangeRequest_SortTarget(op.sort.Target)
|
||||
}
|
||||
return &pb.RequestUnion{Request: &pb.RequestUnion_RequestRange{RequestRange: r}}
|
||||
case tPut:
|
||||
r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID)}
|
||||
return &pb.RequestUnion{Request: &pb.RequestUnion_RequestPut{RequestPut: r}}
|
||||
case tDeleteRange:
|
||||
r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end}
|
||||
return &pb.RequestUnion{Request: &pb.RequestUnion_RequestDeleteRange{RequestDeleteRange: r}}
|
||||
default:
|
||||
panic("Unknown Op")
|
||||
}
|
||||
}
|
||||
|
||||
func (op Op) isWrite() bool {
|
||||
return op.t != tRange
|
||||
}
|
||||
|
||||
func OpGet(key string, opts ...OpOption) Op {
|
||||
ret := Op{t: tRange, key: []byte(key)}
|
||||
ret.applyOpts(opts)
|
||||
return ret
|
||||
}
|
||||
|
||||
func OpDelete(key string, opts ...OpOption) Op {
|
||||
ret := Op{t: tDeleteRange, key: []byte(key)}
|
||||
ret.applyOpts(opts)
|
||||
switch {
|
||||
case ret.leaseID != 0:
|
||||
panic("unexpected lease in delete")
|
||||
case ret.limit != 0:
|
||||
panic("unexpected limit in delete")
|
||||
case ret.rev != 0:
|
||||
panic("unexpected revision in delete")
|
||||
case ret.sort != nil:
|
||||
panic("unexpected sort in delete")
|
||||
case ret.serializable:
|
||||
panic("unexpected serializable in delete")
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func OpPut(key, val string, opts ...OpOption) Op {
|
||||
ret := Op{t: tPut, key: []byte(key), val: []byte(val)}
|
||||
ret.applyOpts(opts)
|
||||
switch {
|
||||
case ret.end != nil:
|
||||
panic("unexpected range in put")
|
||||
case ret.limit != 0:
|
||||
panic("unexpected limit in put")
|
||||
case ret.rev != 0:
|
||||
panic("unexpected revision in put")
|
||||
case ret.sort != nil:
|
||||
panic("unexpected sort in put")
|
||||
case ret.serializable:
|
||||
panic("unexpected serializable in delete")
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func opWatch(key string, opts ...OpOption) Op {
|
||||
ret := Op{t: tRange, key: []byte(key)}
|
||||
ret.applyOpts(opts)
|
||||
switch {
|
||||
case ret.leaseID != 0:
|
||||
panic("unexpected lease in watch")
|
||||
case ret.limit != 0:
|
||||
panic("unexpected limit in watch")
|
||||
case ret.sort != nil:
|
||||
panic("unexpected sort in watch")
|
||||
case ret.serializable:
|
||||
panic("unexpected serializable in watch")
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
func (op *Op) applyOpts(opts []OpOption) {
|
||||
for _, opt := range opts {
|
||||
opt(op)
|
||||
}
|
||||
}
|
||||
|
||||
// OpOption configures Operations like Get, Put, Delete.
|
||||
type OpOption func(*Op)
|
||||
|
||||
// WithLease attaches a lease ID to a key in 'Put' request.
|
||||
func WithLease(leaseID LeaseID) OpOption {
|
||||
return func(op *Op) { op.leaseID = leaseID }
|
||||
}
|
||||
|
||||
// WithLimit limits the number of results to return from 'Get' request.
|
||||
func WithLimit(n int64) OpOption { return func(op *Op) { op.limit = n } }
|
||||
|
||||
// WithRev specifies the store revision for 'Get' request.
|
||||
// Or the start revision of 'Watch' request.
|
||||
func WithRev(rev int64) OpOption { return func(op *Op) { op.rev = rev } }
|
||||
|
||||
// WithSort specifies the ordering in 'Get' request. It requires
|
||||
// 'WithRange' and/or 'WithPrefix' to be specified too.
|
||||
// 'target' specifies the target to sort by: key, version, revisions, value.
|
||||
// 'order' can be either 'SortNone', 'SortAscend', 'SortDescend'.
|
||||
func WithSort(target SortTarget, order SortOrder) OpOption {
|
||||
return func(op *Op) {
|
||||
op.sort = &SortOption{target, order}
|
||||
}
|
||||
}
|
||||
|
||||
func getPrefix(key []byte) []byte {
|
||||
end := make([]byte, len(key))
|
||||
copy(end, key)
|
||||
for i := len(end) - 1; i >= 0; i-- {
|
||||
if end[i] < 0xff {
|
||||
end[i] = end[i] + 1
|
||||
end = end[:i+1]
|
||||
return end
|
||||
}
|
||||
}
|
||||
// next prefix does not exist (e.g., 0xffff);
|
||||
// default to WithFromKey policy
|
||||
return noPrefixEnd
|
||||
}
|
||||
|
||||
// WithPrefix enables 'Get', 'Delete', or 'Watch' requests to operate
|
||||
// on the keys with matching prefix. For example, 'Get(foo, WithPrefix())'
|
||||
// can return 'foo1', 'foo2', and so on.
|
||||
func WithPrefix() OpOption {
|
||||
return func(op *Op) {
|
||||
op.end = getPrefix(op.key)
|
||||
}
|
||||
}
|
||||
|
||||
// WithRange specifies the range of 'Get' or 'Delete' requests.
|
||||
// For example, 'Get' requests with 'WithRange(end)' returns
|
||||
// the keys in the range [key, end).
|
||||
func WithRange(endKey string) OpOption {
|
||||
return func(op *Op) { op.end = []byte(endKey) }
|
||||
}
|
||||
|
||||
// WithFromKey specifies the range of 'Get' or 'Delete' requests
|
||||
// to be equal or greater than they key in the argument.
|
||||
func WithFromKey() OpOption { return WithRange("\x00") }
|
||||
|
||||
// WithSerializable makes 'Get' request serializable. By default,
|
||||
// it's linearizable. Serializable requests are better for lower latency
|
||||
// requirement.
|
||||
func WithSerializable() OpOption {
|
||||
return func(op *Op) { op.serializable = true }
|
||||
}
|
||||
|
||||
// WithFirstCreate gets the key with the oldest creation revision in the request range.
|
||||
func WithFirstCreate() []OpOption { return withTop(SortByCreateRevision, SortAscend) }
|
||||
|
||||
// WithLastCreate gets the key with the latest creation revision in the request range.
|
||||
func WithLastCreate() []OpOption { return withTop(SortByCreateRevision, SortDescend) }
|
||||
|
||||
// WithFirstKey gets the lexically first key in the request range.
|
||||
func WithFirstKey() []OpOption { return withTop(SortByKey, SortAscend) }
|
||||
|
||||
// WithLastKey gets the lexically last key in the request range.
|
||||
func WithLastKey() []OpOption { return withTop(SortByKey, SortDescend) }
|
||||
|
||||
// WithFirstRev gets the key with the oldest modification revision in the request range.
|
||||
func WithFirstRev() []OpOption { return withTop(SortByModRevision, SortAscend) }
|
||||
|
||||
// WithLastRev gets the key with the latest modification revision in the request range.
|
||||
func WithLastRev() []OpOption { return withTop(SortByModRevision, SortDescend) }
|
||||
|
||||
// withTop gets the first key over the get's prefix given a sort order
|
||||
func withTop(target SortTarget, order SortOrder) []OpOption {
|
||||
return []OpOption{WithPrefix(), WithSort(target, order), WithLimit(1)}
|
||||
}
|
||||
|
||||
// WithProgressNotify makes watch server send periodic progress updates.
|
||||
// Progress updates have zero events in WatchResponse.
|
||||
func WithProgressNotify() OpOption {
|
||||
return func(op *Op) {
|
||||
op.progressNotify = true
|
||||
}
|
||||
}
|
||||
37
vendor/github.com/coreos/etcd/clientv3/sort.go
generated
vendored
Normal file
37
vendor/github.com/coreos/etcd/clientv3/sort.go
generated
vendored
Normal file
@@ -0,0 +1,37 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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
|
||||
|
||||
type SortTarget int
|
||||
type SortOrder int
|
||||
|
||||
const (
|
||||
SortNone SortOrder = iota
|
||||
SortAscend
|
||||
SortDescend
|
||||
)
|
||||
|
||||
const (
|
||||
SortByKey SortTarget = iota
|
||||
SortByVersion
|
||||
SortByCreateRevision
|
||||
SortByModRevision
|
||||
SortByValue
|
||||
)
|
||||
|
||||
type SortOption struct {
|
||||
Target SortTarget
|
||||
Order SortOrder
|
||||
}
|
||||
160
vendor/github.com/coreos/etcd/clientv3/txn.go
generated
vendored
Normal file
160
vendor/github.com/coreos/etcd/clientv3/txn.go
generated
vendored
Normal file
@@ -0,0 +1,160 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 (
|
||||
"sync"
|
||||
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
//
|
||||
// Tx.If(
|
||||
// Compare(Value(k1), ">", v1),
|
||||
// Compare(Version(k1), "=", 2)
|
||||
// ).Then(
|
||||
// OpPut(k2,v2), OpPut(k3,v3)
|
||||
// ).Else(
|
||||
// OpPut(k4,v4), OpPut(k5,v5)
|
||||
// ).Commit()
|
||||
type Txn interface {
|
||||
// If takes a list of comparison. If all comparisons passed in succeed,
|
||||
// the operations passed into Then() will be executed. Or the operations
|
||||
// passed into Else() will be executed.
|
||||
If(cs ...Cmp) Txn
|
||||
|
||||
// Then takes a list of operations. The Ops list will be executed, if the
|
||||
// comparisons passed in If() succeed.
|
||||
Then(ops ...Op) Txn
|
||||
|
||||
// Else takes a list of operations. The Ops list will be executed, if the
|
||||
// comparisons passed in If() fail.
|
||||
Else(ops ...Op) Txn
|
||||
|
||||
// Commit tries to commit the transaction.
|
||||
Commit() (*TxnResponse, error)
|
||||
|
||||
// TODO: add a Do for shortcut the txn without any condition?
|
||||
}
|
||||
|
||||
type txn struct {
|
||||
kv *kv
|
||||
ctx context.Context
|
||||
|
||||
mu sync.Mutex
|
||||
cif bool
|
||||
cthen bool
|
||||
celse bool
|
||||
|
||||
isWrite bool
|
||||
|
||||
cmps []*pb.Compare
|
||||
|
||||
sus []*pb.RequestUnion
|
||||
fas []*pb.RequestUnion
|
||||
}
|
||||
|
||||
func (txn *txn) If(cs ...Cmp) Txn {
|
||||
txn.mu.Lock()
|
||||
defer txn.mu.Unlock()
|
||||
|
||||
if txn.cif {
|
||||
panic("cannot call If twice!")
|
||||
}
|
||||
|
||||
if txn.cthen {
|
||||
panic("cannot call If after Then!")
|
||||
}
|
||||
|
||||
if txn.celse {
|
||||
panic("cannot call If after Else!")
|
||||
}
|
||||
|
||||
txn.cif = true
|
||||
|
||||
for i := range cs {
|
||||
txn.cmps = append(txn.cmps, (*pb.Compare)(&cs[i]))
|
||||
}
|
||||
|
||||
return txn
|
||||
}
|
||||
|
||||
func (txn *txn) Then(ops ...Op) Txn {
|
||||
txn.mu.Lock()
|
||||
defer txn.mu.Unlock()
|
||||
|
||||
if txn.cthen {
|
||||
panic("cannot call Then twice!")
|
||||
}
|
||||
if txn.celse {
|
||||
panic("cannot call Then after Else!")
|
||||
}
|
||||
|
||||
txn.cthen = true
|
||||
|
||||
for _, op := range ops {
|
||||
txn.isWrite = txn.isWrite || op.isWrite()
|
||||
txn.sus = append(txn.sus, op.toRequestUnion())
|
||||
}
|
||||
|
||||
return txn
|
||||
}
|
||||
|
||||
func (txn *txn) Else(ops ...Op) Txn {
|
||||
txn.mu.Lock()
|
||||
defer txn.mu.Unlock()
|
||||
|
||||
if txn.celse {
|
||||
panic("cannot call Else twice!")
|
||||
}
|
||||
|
||||
txn.celse = true
|
||||
|
||||
for _, op := range ops {
|
||||
txn.isWrite = txn.isWrite || op.isWrite()
|
||||
txn.fas = append(txn.fas, op.toRequestUnion())
|
||||
}
|
||||
|
||||
return txn
|
||||
}
|
||||
|
||||
func (txn *txn) Commit() (*TxnResponse, error) {
|
||||
txn.mu.Lock()
|
||||
defer txn.mu.Unlock()
|
||||
|
||||
kv := txn.kv
|
||||
|
||||
for {
|
||||
r := &pb.TxnRequest{Compare: txn.cmps, Success: txn.sus, Failure: txn.fas}
|
||||
resp, err := kv.getRemote().Txn(txn.ctx, r)
|
||||
if err == nil {
|
||||
return (*TxnResponse)(resp), nil
|
||||
}
|
||||
|
||||
if isHalted(txn.ctx, err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if txn.isWrite {
|
||||
go kv.switchRemote(err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if nerr := kv.switchRemote(err); nerr != nil {
|
||||
return nil, nerr
|
||||
}
|
||||
}
|
||||
}
|
||||
572
vendor/github.com/coreos/etcd/clientv3/watch.go
generated
vendored
Normal file
572
vendor/github.com/coreos/etcd/clientv3/watch.go
generated
vendored
Normal file
@@ -0,0 +1,572 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
v3rpc "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
storagepb "github.com/coreos/etcd/storage/storagepb"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
const (
|
||||
EventTypeDelete = storagepb.DELETE
|
||||
EventTypePut = storagepb.PUT
|
||||
)
|
||||
|
||||
type Event storagepb.Event
|
||||
|
||||
type WatchChan <-chan WatchResponse
|
||||
|
||||
type Watcher interface {
|
||||
// Watch watches on a key or prefix. The watched events will be returned
|
||||
// through the returned channel.
|
||||
// If the watch is slow or the required rev is compacted, the watch request
|
||||
// might be canceled from the server-side and the chan will be closed.
|
||||
// 'opts' can be: 'WithRev' and/or 'WitchPrefix'.
|
||||
Watch(ctx context.Context, key string, opts ...OpOption) WatchChan
|
||||
|
||||
// Close closes the watcher and cancels all watch requests.
|
||||
Close() error
|
||||
}
|
||||
|
||||
type WatchResponse struct {
|
||||
Header pb.ResponseHeader
|
||||
Events []*Event
|
||||
|
||||
// CompactRevision is the minimum revision the watcher may receive.
|
||||
CompactRevision int64
|
||||
|
||||
// Canceled is used to indicate watch failure.
|
||||
// If the watch failed and the stream was about to close, before the channel is closed,
|
||||
// the channel sends a final response that has Canceled set to true with a non-nil Err().
|
||||
Canceled bool
|
||||
}
|
||||
|
||||
// IsCreate returns true if the event tells that the key is newly created.
|
||||
func (e *Event) IsCreate() bool {
|
||||
return e.Type == EventTypePut && e.Kv.CreateRevision == e.Kv.ModRevision
|
||||
}
|
||||
|
||||
// IsModify returns true if the event tells that a new value is put on existing key.
|
||||
func (e *Event) IsModify() bool {
|
||||
return e.Type == EventTypePut && e.Kv.CreateRevision != e.Kv.ModRevision
|
||||
}
|
||||
|
||||
// Err is the error value if this WatchResponse holds an error.
|
||||
func (wr *WatchResponse) Err() error {
|
||||
if wr.CompactRevision != 0 {
|
||||
return v3rpc.ErrCompacted
|
||||
}
|
||||
if wr.Canceled {
|
||||
return v3rpc.ErrFutureRev
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsProgressNotify returns true if the WatchResponse is progress notification.
|
||||
func (wr *WatchResponse) IsProgressNotify() bool {
|
||||
return len(wr.Events) == 0 && !wr.Canceled
|
||||
}
|
||||
|
||||
// watcher implements the Watcher interface
|
||||
type watcher struct {
|
||||
c *Client
|
||||
conn *grpc.ClientConn
|
||||
remote pb.WatchClient
|
||||
|
||||
// ctx controls internal remote.Watch requests
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
// streams holds all active watchers
|
||||
streams map[int64]*watcherStream
|
||||
// mu protects the streams map
|
||||
mu sync.RWMutex
|
||||
|
||||
// reqc sends a watch request from Watch() to the main goroutine
|
||||
reqc chan *watchRequest
|
||||
// respc receives data from the watch client
|
||||
respc chan *pb.WatchResponse
|
||||
// stopc is sent to the main goroutine to stop all processing
|
||||
stopc chan struct{}
|
||||
// donec closes to broadcast shutdown
|
||||
donec chan struct{}
|
||||
// errc transmits errors from grpc Recv
|
||||
errc chan error
|
||||
}
|
||||
|
||||
// watchRequest is issued by the subscriber to start a new watcher
|
||||
type watchRequest struct {
|
||||
ctx context.Context
|
||||
key string
|
||||
end string
|
||||
rev int64
|
||||
// progressNotify is for progress updates.
|
||||
progressNotify bool
|
||||
// retc receives a chan WatchResponse once the watcher is established
|
||||
retc chan chan WatchResponse
|
||||
}
|
||||
|
||||
// watcherStream represents a registered watcher
|
||||
type watcherStream struct {
|
||||
initReq watchRequest
|
||||
|
||||
// outc publishes watch responses to subscriber
|
||||
outc chan<- WatchResponse
|
||||
// recvc buffers watch responses before publishing
|
||||
recvc chan *WatchResponse
|
||||
id int64
|
||||
|
||||
// lastRev is revision last successfully sent over outc
|
||||
lastRev int64
|
||||
// resumec indicates the stream must recover at a given revision
|
||||
resumec chan int64
|
||||
}
|
||||
|
||||
func NewWatcher(c *Client) Watcher {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
conn := c.ActiveConnection()
|
||||
|
||||
w := &watcher{
|
||||
c: c,
|
||||
conn: conn,
|
||||
remote: pb.NewWatchClient(conn),
|
||||
|
||||
ctx: ctx,
|
||||
cancel: cancel,
|
||||
streams: make(map[int64]*watcherStream),
|
||||
|
||||
respc: make(chan *pb.WatchResponse),
|
||||
reqc: make(chan *watchRequest),
|
||||
stopc: make(chan struct{}),
|
||||
donec: make(chan struct{}),
|
||||
errc: make(chan error, 1),
|
||||
}
|
||||
go w.run()
|
||||
return w
|
||||
}
|
||||
|
||||
// Watch posts a watch request to run() and waits for a new watcher channel
|
||||
func (w *watcher) Watch(ctx context.Context, key string, opts ...OpOption) WatchChan {
|
||||
ow := opWatch(key, opts...)
|
||||
|
||||
retc := make(chan chan WatchResponse, 1)
|
||||
wr := &watchRequest{
|
||||
ctx: ctx,
|
||||
key: string(ow.key),
|
||||
end: string(ow.end),
|
||||
rev: ow.rev,
|
||||
progressNotify: ow.progressNotify,
|
||||
retc: retc,
|
||||
}
|
||||
|
||||
ok := false
|
||||
|
||||
// submit request
|
||||
select {
|
||||
case w.reqc <- wr:
|
||||
ok = true
|
||||
case <-wr.ctx.Done():
|
||||
case <-w.donec:
|
||||
}
|
||||
|
||||
// receive channel
|
||||
if ok {
|
||||
select {
|
||||
case ret := <-retc:
|
||||
return ret
|
||||
case <-ctx.Done():
|
||||
case <-w.donec:
|
||||
}
|
||||
}
|
||||
|
||||
// couldn't create channel; return closed channel
|
||||
ch := make(chan WatchResponse)
|
||||
close(ch)
|
||||
return ch
|
||||
}
|
||||
|
||||
func (w *watcher) Close() error {
|
||||
select {
|
||||
case w.stopc <- struct{}{}:
|
||||
case <-w.donec:
|
||||
}
|
||||
<-w.donec
|
||||
return <-w.errc
|
||||
}
|
||||
|
||||
func (w *watcher) addStream(resp *pb.WatchResponse, pendingReq *watchRequest) {
|
||||
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.streams[ws.id] = ws
|
||||
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 *watcher) closeStream(ws *watcherStream) {
|
||||
// cancels request stream; subscriber receives nil channel
|
||||
close(ws.initReq.retc)
|
||||
// close subscriber's channel
|
||||
close(ws.outc)
|
||||
// shutdown serveStream
|
||||
close(ws.recvc)
|
||||
delete(w.streams, ws.id)
|
||||
}
|
||||
|
||||
// run is the root of the goroutines for managing a watcher client
|
||||
func (w *watcher) run() {
|
||||
defer func() {
|
||||
close(w.donec)
|
||||
w.cancel()
|
||||
}()
|
||||
|
||||
// start a stream with the etcd grpc server
|
||||
wc, wcerr := w.newWatchClient()
|
||||
if wcerr != nil {
|
||||
w.errc <- wcerr
|
||||
return
|
||||
}
|
||||
|
||||
var pendingReq, failedReq *watchRequest
|
||||
curReqC := w.reqc
|
||||
cancelSet := make(map[int64]struct{})
|
||||
|
||||
for {
|
||||
select {
|
||||
// Watch() requested
|
||||
case pendingReq = <-curReqC:
|
||||
// no more watch requests until there's a response
|
||||
curReqC = nil
|
||||
if err := wc.Send(pendingReq.toPB()); err == nil {
|
||||
// pendingReq now waits on w.respc
|
||||
break
|
||||
}
|
||||
failedReq = pendingReq
|
||||
// New events from the watch client
|
||||
case pbresp := <-w.respc:
|
||||
switch {
|
||||
case pbresp.Created:
|
||||
// response to pending req, try to add
|
||||
w.addStream(pbresp, pendingReq)
|
||||
pendingReq = nil
|
||||
curReqC = w.reqc
|
||||
case pbresp.Canceled:
|
||||
delete(cancelSet, pbresp.WatchId)
|
||||
default:
|
||||
// dispatch to appropriate watch stream
|
||||
if ok := w.dispatchEvent(pbresp); ok {
|
||||
break
|
||||
}
|
||||
// watch response on unexpected watch id; cancel id
|
||||
if _, ok := cancelSet[pbresp.WatchId]; ok {
|
||||
break
|
||||
}
|
||||
cancelSet[pbresp.WatchId] = struct{}{}
|
||||
cr := &pb.WatchRequest_CancelRequest{
|
||||
CancelRequest: &pb.WatchCancelRequest{
|
||||
WatchId: pbresp.WatchId,
|
||||
},
|
||||
}
|
||||
req := &pb.WatchRequest{RequestUnion: cr}
|
||||
wc.Send(req)
|
||||
}
|
||||
// watch client failed to recv; spawn another if possible
|
||||
// TODO report watch client errors from errc?
|
||||
case <-w.errc:
|
||||
if wc, wcerr = w.newWatchClient(); wcerr != nil {
|
||||
w.errc <- wcerr
|
||||
return
|
||||
}
|
||||
curReqC = w.reqc
|
||||
if pendingReq != nil {
|
||||
failedReq = pendingReq
|
||||
}
|
||||
cancelSet = make(map[int64]struct{})
|
||||
case <-w.stopc:
|
||||
w.errc <- nil
|
||||
return
|
||||
}
|
||||
|
||||
// send failed; queue for retry
|
||||
if failedReq != nil {
|
||||
go func(wr *watchRequest) {
|
||||
select {
|
||||
case w.reqc <- wr:
|
||||
case <-wr.ctx.Done():
|
||||
case <-w.donec:
|
||||
}
|
||||
}(pendingReq)
|
||||
failedReq = nil
|
||||
pendingReq = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// dispatchEvent sends a WatchResponse to the appropriate watcher stream
|
||||
func (w *watcher) dispatchEvent(pbresp *pb.WatchResponse) bool {
|
||||
w.mu.RLock()
|
||||
defer w.mu.RUnlock()
|
||||
ws, ok := w.streams[pbresp.WatchId]
|
||||
events := make([]*Event, len(pbresp.Events))
|
||||
for i, ev := range pbresp.Events {
|
||||
events[i] = (*Event)(ev)
|
||||
}
|
||||
if ok {
|
||||
wr := &WatchResponse{
|
||||
Header: *pbresp.Header,
|
||||
Events: events,
|
||||
CompactRevision: pbresp.CompactRevision,
|
||||
Canceled: pbresp.Canceled}
|
||||
ws.recvc <- wr
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// serveWatchClient forwards messages from the grpc stream to run()
|
||||
func (w *watcher) serveWatchClient(wc pb.Watch_WatchClient) {
|
||||
for {
|
||||
resp, err := wc.Recv()
|
||||
if err != nil {
|
||||
select {
|
||||
case w.errc <- err:
|
||||
case <-w.donec:
|
||||
}
|
||||
return
|
||||
}
|
||||
select {
|
||||
case w.respc <- resp:
|
||||
case <-w.donec:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// serveStream forwards watch responses from run() to the subscriber
|
||||
func (w *watcher) serveStream(ws *watcherStream) {
|
||||
emptyWr := &WatchResponse{}
|
||||
wrs := []*WatchResponse{}
|
||||
resuming := false
|
||||
closing := false
|
||||
for !closing {
|
||||
curWr := emptyWr
|
||||
outc := ws.outc
|
||||
if len(wrs) > 0 {
|
||||
curWr = wrs[0]
|
||||
} else {
|
||||
outc = nil
|
||||
}
|
||||
select {
|
||||
case outc <- *curWr:
|
||||
if wrs[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
|
||||
}
|
||||
// resume up to last seen event if disconnected
|
||||
if resuming {
|
||||
resuming = false
|
||||
// trim events already seen
|
||||
for i := 0; i < len(wr.Events); i++ {
|
||||
if wr.Events[i].Kv.ModRevision > ws.lastRev {
|
||||
wr.Events = wr.Events[i:]
|
||||
break
|
||||
}
|
||||
}
|
||||
// only forward new events
|
||||
if wr.Events[0].Kv.ModRevision == ws.lastRev {
|
||||
break
|
||||
}
|
||||
}
|
||||
// TODO don't keep buffering if subscriber stops reading
|
||||
wrs = append(wrs, wr)
|
||||
case resumeRev := <-ws.resumec:
|
||||
wrs = nil
|
||||
resuming = true
|
||||
if resumeRev == -1 {
|
||||
// pause serving stream while resume gets set up
|
||||
break
|
||||
}
|
||||
if resumeRev != ws.lastRev {
|
||||
panic("unexpected resume revision")
|
||||
}
|
||||
case <-w.donec:
|
||||
closing = true
|
||||
case <-ws.initReq.ctx.Done():
|
||||
closing = true
|
||||
}
|
||||
}
|
||||
w.mu.Lock()
|
||||
w.closeStream(ws)
|
||||
w.mu.Unlock()
|
||||
// lazily send cancel message if events on missing id
|
||||
}
|
||||
|
||||
func (w *watcher) newWatchClient() (pb.Watch_WatchClient, error) {
|
||||
ws, rerr := w.resume()
|
||||
if rerr != nil {
|
||||
return nil, rerr
|
||||
}
|
||||
go w.serveWatchClient(ws)
|
||||
return ws, nil
|
||||
}
|
||||
|
||||
// resume creates a new WatchClient with all current watchers reestablished
|
||||
func (w *watcher) resume() (ws pb.Watch_WatchClient, err error) {
|
||||
for {
|
||||
if ws, err = w.openWatchClient(); err != nil {
|
||||
break
|
||||
} else if err = w.resumeWatchers(ws); err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return ws, err
|
||||
}
|
||||
|
||||
// openWatchClient retries opening a watchclient until retryConnection fails
|
||||
func (w *watcher) openWatchClient() (ws pb.Watch_WatchClient, err error) {
|
||||
for {
|
||||
if ws, err = w.remote.Watch(w.ctx); ws != nil {
|
||||
break
|
||||
} else if isHalted(w.ctx, err) {
|
||||
return nil, err
|
||||
}
|
||||
newConn, nerr := w.c.retryConnection(w.conn, nil)
|
||||
if nerr != nil {
|
||||
return nil, nerr
|
||||
}
|
||||
w.conn = newConn
|
||||
w.remote = pb.NewWatchClient(w.conn)
|
||||
}
|
||||
return ws, nil
|
||||
}
|
||||
|
||||
// resumeWatchers rebuilds every registered watcher on a new client
|
||||
func (w *watcher) resumeWatchers(wc pb.Watch_WatchClient) error {
|
||||
streams := []*watcherStream{}
|
||||
w.mu.RLock()
|
||||
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)
|
||||
func (wr *watchRequest) toPB() *pb.WatchRequest {
|
||||
req := &pb.WatchCreateRequest{
|
||||
StartRevision: wr.rev,
|
||||
Key: []byte(wr.key),
|
||||
RangeEnd: []byte(wr.end),
|
||||
ProgressNotify: wr.progressNotify,
|
||||
}
|
||||
cr := &pb.WatchRequest_CreateRequest{CreateRequest: req}
|
||||
return &pb.WatchRequest{RequestUnion: cr}
|
||||
}
|
||||
133
vendor/github.com/coreos/etcd/compactor/compactor.go
generated
vendored
Normal file
133
vendor/github.com/coreos/etcd/compactor/compactor.go
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 compactor
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/storage"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
"github.com/jonboulle/clockwork"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var (
|
||||
plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "etcdserver")
|
||||
)
|
||||
|
||||
const (
|
||||
checkCompactionInterval = 5 * time.Minute
|
||||
)
|
||||
|
||||
type Compactable interface {
|
||||
Compact(ctx context.Context, r *pb.CompactionRequest) (*pb.CompactionResponse, error)
|
||||
}
|
||||
|
||||
type RevGetter interface {
|
||||
Rev() int64
|
||||
}
|
||||
|
||||
type Periodic struct {
|
||||
clock clockwork.Clock
|
||||
periodInHour int
|
||||
|
||||
rg RevGetter
|
||||
c Compactable
|
||||
|
||||
revs []int64
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
|
||||
mu sync.Mutex
|
||||
paused bool
|
||||
}
|
||||
|
||||
func NewPeriodic(h int, rg RevGetter, c Compactable) *Periodic {
|
||||
return &Periodic{
|
||||
clock: clockwork.NewRealClock(),
|
||||
periodInHour: h,
|
||||
rg: rg,
|
||||
c: c,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *Periodic) Run() {
|
||||
t.ctx, t.cancel = context.WithCancel(context.Background())
|
||||
t.revs = make([]int64, 0)
|
||||
clock := t.clock
|
||||
|
||||
go func() {
|
||||
last := clock.Now()
|
||||
for {
|
||||
t.revs = append(t.revs, t.rg.Rev())
|
||||
select {
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
case <-clock.After(checkCompactionInterval):
|
||||
t.mu.Lock()
|
||||
p := t.paused
|
||||
t.mu.Unlock()
|
||||
if p {
|
||||
continue
|
||||
}
|
||||
}
|
||||
if clock.Now().Sub(last) < time.Duration(t.periodInHour)*time.Hour {
|
||||
continue
|
||||
}
|
||||
|
||||
rev := t.getRev(t.periodInHour)
|
||||
if rev < 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
plog.Noticef("Starting auto-compaction at revision %d", rev)
|
||||
_, err := t.c.Compact(t.ctx, &pb.CompactionRequest{Revision: rev})
|
||||
if err == nil || err == storage.ErrCompacted {
|
||||
t.revs = make([]int64, 0)
|
||||
last = clock.Now()
|
||||
plog.Noticef("Finished auto-compaction at revision %d", rev)
|
||||
} else {
|
||||
plog.Noticef("Failed auto-compaction at revision %d (%v)", err, rev)
|
||||
plog.Noticef("Retry after %v", checkCompactionInterval)
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (t *Periodic) Stop() {
|
||||
t.cancel()
|
||||
}
|
||||
|
||||
func (t *Periodic) Pause() {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.paused = true
|
||||
}
|
||||
|
||||
func (t *Periodic) Resume() {
|
||||
t.mu.Lock()
|
||||
defer t.mu.Unlock()
|
||||
t.paused = false
|
||||
}
|
||||
|
||||
func (t *Periodic) getRev(h int) int64 {
|
||||
i := len(t.revs) - int(time.Duration(h)*time.Hour/checkCompactionInterval)
|
||||
if i < 0 {
|
||||
return -1
|
||||
}
|
||||
return t.revs[i]
|
||||
}
|
||||
357
vendor/github.com/coreos/etcd/discovery/discovery.go
generated
vendored
Normal file
357
vendor/github.com/coreos/etcd/discovery/discovery.go
generated
vendored
Normal file
@@ -0,0 +1,357 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 discovery provides an implementation of the cluster discovery that
|
||||
// is used by etcd.
|
||||
package discovery
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"path"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/client"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
"github.com/jonboulle/clockwork"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var (
|
||||
plog = capnslog.NewPackageLogger("github.com/coreos/etcd", "discovery")
|
||||
|
||||
ErrInvalidURL = errors.New("discovery: invalid URL")
|
||||
ErrBadSizeKey = errors.New("discovery: size key is bad")
|
||||
ErrSizeNotFound = errors.New("discovery: size key not found")
|
||||
ErrTokenNotFound = errors.New("discovery: token not found")
|
||||
ErrDuplicateID = errors.New("discovery: found duplicate id")
|
||||
ErrDuplicateName = errors.New("discovery: found duplicate name")
|
||||
ErrFullCluster = errors.New("discovery: cluster is full")
|
||||
ErrTooManyRetries = errors.New("discovery: too many retries")
|
||||
ErrBadDiscoveryEndpoint = errors.New("discovery: bad discovery endpoint")
|
||||
)
|
||||
|
||||
var (
|
||||
// Number of retries discovery will attempt before giving up and erroring out.
|
||||
nRetries = uint(math.MaxUint32)
|
||||
)
|
||||
|
||||
// JoinCluster will connect to the discovery service at the given url, and
|
||||
// register the server represented by the given id and config to the cluster
|
||||
func JoinCluster(durl, dproxyurl string, id types.ID, config string) (string, error) {
|
||||
d, err := newDiscovery(durl, dproxyurl, id)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return d.joinCluster(config)
|
||||
}
|
||||
|
||||
// GetCluster will connect to the discovery service at the given url and
|
||||
// retrieve a string describing the cluster
|
||||
func GetCluster(durl, dproxyurl string) (string, error) {
|
||||
d, err := newDiscovery(durl, dproxyurl, 0)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return d.getCluster()
|
||||
}
|
||||
|
||||
type discovery struct {
|
||||
cluster string
|
||||
id types.ID
|
||||
c client.KeysAPI
|
||||
retries uint
|
||||
url *url.URL
|
||||
|
||||
clock clockwork.Clock
|
||||
}
|
||||
|
||||
// newProxyFunc builds a proxy function from the given string, which should
|
||||
// represent a URL that can be used as a proxy. It performs basic
|
||||
// sanitization of the URL and returns any error encountered.
|
||||
func newProxyFunc(proxy string) (func(*http.Request) (*url.URL, error), error) {
|
||||
if proxy == "" {
|
||||
return nil, nil
|
||||
}
|
||||
// Do a small amount of URL sanitization to help the user
|
||||
// Derived from net/http.ProxyFromEnvironment
|
||||
proxyURL, err := url.Parse(proxy)
|
||||
if err != nil || !strings.HasPrefix(proxyURL.Scheme, "http") {
|
||||
// proxy was bogus. Try prepending "http://" to it and
|
||||
// see if that parses correctly. If not, we ignore the
|
||||
// error and complain about the original one
|
||||
var err2 error
|
||||
proxyURL, err2 = url.Parse("http://" + proxy)
|
||||
if err2 == nil {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid proxy address %q: %v", proxy, err)
|
||||
}
|
||||
|
||||
plog.Infof("using proxy %q", proxyURL.String())
|
||||
return http.ProxyURL(proxyURL), nil
|
||||
}
|
||||
|
||||
func newDiscovery(durl, dproxyurl string, id types.ID) (*discovery, error) {
|
||||
u, err := url.Parse(durl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
token := u.Path
|
||||
u.Path = ""
|
||||
pf, err := newProxyFunc(dproxyurl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg := client.Config{
|
||||
Transport: &http.Transport{
|
||||
Proxy: pf,
|
||||
Dial: (&net.Dialer{
|
||||
Timeout: 30 * time.Second,
|
||||
KeepAlive: 30 * time.Second,
|
||||
}).Dial,
|
||||
TLSHandshakeTimeout: 10 * time.Second,
|
||||
// TODO: add ResponseHeaderTimeout back when watch on discovery service writes header early
|
||||
},
|
||||
Endpoints: []string{u.String()},
|
||||
}
|
||||
c, err := client.New(cfg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dc := client.NewKeysAPIWithPrefix(c, "")
|
||||
return &discovery{
|
||||
cluster: token,
|
||||
c: dc,
|
||||
id: id,
|
||||
url: u,
|
||||
clock: clockwork.NewRealClock(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *discovery) joinCluster(config string) (string, error) {
|
||||
// fast path: if the cluster is full, return the error
|
||||
// do not need to register to the cluster in this case.
|
||||
if _, _, _, err := d.checkCluster(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
if err := d.createSelf(config); err != nil {
|
||||
// Fails, even on a timeout, if createSelf times out.
|
||||
// TODO(barakmich): Retrying the same node might want to succeed here
|
||||
// (ie, createSelf should be idempotent for discovery).
|
||||
return "", err
|
||||
}
|
||||
|
||||
nodes, size, index, err := d.checkCluster()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
all, err := d.waitNodes(nodes, size, index)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return nodesToCluster(all, size)
|
||||
}
|
||||
|
||||
func (d *discovery) getCluster() (string, error) {
|
||||
nodes, size, index, err := d.checkCluster()
|
||||
if err != nil {
|
||||
if err == ErrFullCluster {
|
||||
return nodesToCluster(nodes, size)
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
all, err := d.waitNodes(nodes, size, index)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return nodesToCluster(all, size)
|
||||
}
|
||||
|
||||
func (d *discovery) createSelf(contents string) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
|
||||
resp, err := d.c.Create(ctx, d.selfKey(), contents)
|
||||
cancel()
|
||||
if err != nil {
|
||||
if eerr, ok := err.(client.Error); ok && eerr.Code == client.ErrorCodeNodeExist {
|
||||
return ErrDuplicateID
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// ensure self appears on the server we connected to
|
||||
w := d.c.Watcher(d.selfKey(), &client.WatcherOptions{AfterIndex: resp.Node.CreatedIndex - 1})
|
||||
_, err = w.Next(context.Background())
|
||||
return err
|
||||
}
|
||||
|
||||
func (d *discovery) checkCluster() ([]*client.Node, int, uint64, error) {
|
||||
configKey := path.Join("/", d.cluster, "_config")
|
||||
ctx, cancel := context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
|
||||
// find cluster size
|
||||
resp, err := d.c.Get(ctx, path.Join(configKey, "size"), nil)
|
||||
cancel()
|
||||
if err != nil {
|
||||
if eerr, ok := err.(*client.Error); ok && eerr.Code == client.ErrorCodeKeyNotFound {
|
||||
return nil, 0, 0, ErrSizeNotFound
|
||||
}
|
||||
if err == client.ErrInvalidJSON {
|
||||
return nil, 0, 0, ErrBadDiscoveryEndpoint
|
||||
}
|
||||
if ce, ok := err.(*client.ClusterError); ok {
|
||||
plog.Error(ce.Detail())
|
||||
return d.checkClusterRetry()
|
||||
}
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
size, err := strconv.Atoi(resp.Node.Value)
|
||||
if err != nil {
|
||||
return nil, 0, 0, ErrBadSizeKey
|
||||
}
|
||||
|
||||
ctx, cancel = context.WithTimeout(context.Background(), client.DefaultRequestTimeout)
|
||||
resp, err = d.c.Get(ctx, d.cluster, nil)
|
||||
cancel()
|
||||
if err != nil {
|
||||
if ce, ok := err.(*client.ClusterError); ok {
|
||||
plog.Error(ce.Detail())
|
||||
return d.checkClusterRetry()
|
||||
}
|
||||
return nil, 0, 0, err
|
||||
}
|
||||
nodes := make([]*client.Node, 0)
|
||||
// append non-config keys to nodes
|
||||
for _, n := range resp.Node.Nodes {
|
||||
if !(path.Base(n.Key) == path.Base(configKey)) {
|
||||
nodes = append(nodes, n)
|
||||
}
|
||||
}
|
||||
|
||||
snodes := sortableNodes{nodes}
|
||||
sort.Sort(snodes)
|
||||
|
||||
// find self position
|
||||
for i := range nodes {
|
||||
if path.Base(nodes[i].Key) == path.Base(d.selfKey()) {
|
||||
break
|
||||
}
|
||||
if i >= size-1 {
|
||||
return nodes[:size], size, resp.Index, ErrFullCluster
|
||||
}
|
||||
}
|
||||
return nodes, size, resp.Index, nil
|
||||
}
|
||||
|
||||
func (d *discovery) logAndBackoffForRetry(step string) {
|
||||
d.retries++
|
||||
retryTime := time.Second * (0x1 << d.retries)
|
||||
plog.Infof("%s: error connecting to %s, retrying in %s", step, d.url, retryTime)
|
||||
d.clock.Sleep(retryTime)
|
||||
}
|
||||
|
||||
func (d *discovery) checkClusterRetry() ([]*client.Node, int, uint64, error) {
|
||||
if d.retries < nRetries {
|
||||
d.logAndBackoffForRetry("cluster status check")
|
||||
return d.checkCluster()
|
||||
}
|
||||
return nil, 0, 0, ErrTooManyRetries
|
||||
}
|
||||
|
||||
func (d *discovery) waitNodesRetry() ([]*client.Node, error) {
|
||||
if d.retries < nRetries {
|
||||
d.logAndBackoffForRetry("waiting for other nodes")
|
||||
nodes, n, index, err := d.checkCluster()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return d.waitNodes(nodes, n, index)
|
||||
}
|
||||
return nil, ErrTooManyRetries
|
||||
}
|
||||
|
||||
func (d *discovery) waitNodes(nodes []*client.Node, size int, index uint64) ([]*client.Node, error) {
|
||||
if len(nodes) > size {
|
||||
nodes = nodes[:size]
|
||||
}
|
||||
// watch from the next index
|
||||
w := d.c.Watcher(d.cluster, &client.WatcherOptions{AfterIndex: index, Recursive: true})
|
||||
all := make([]*client.Node, len(nodes))
|
||||
copy(all, nodes)
|
||||
for _, n := range all {
|
||||
if path.Base(n.Key) == path.Base(d.selfKey()) {
|
||||
plog.Noticef("found self %s in the cluster", path.Base(d.selfKey()))
|
||||
} else {
|
||||
plog.Noticef("found peer %s in the cluster", path.Base(n.Key))
|
||||
}
|
||||
}
|
||||
|
||||
// wait for others
|
||||
for len(all) < size {
|
||||
plog.Noticef("found %d peer(s), waiting for %d more", len(all), size-len(all))
|
||||
resp, err := w.Next(context.Background())
|
||||
if err != nil {
|
||||
if ce, ok := err.(*client.ClusterError); ok {
|
||||
plog.Error(ce.Detail())
|
||||
return d.waitNodesRetry()
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
plog.Noticef("found peer %s in the cluster", path.Base(resp.Node.Key))
|
||||
all = append(all, resp.Node)
|
||||
}
|
||||
plog.Noticef("found %d needed peer(s)", len(all))
|
||||
return all, nil
|
||||
}
|
||||
|
||||
func (d *discovery) selfKey() string {
|
||||
return path.Join("/", d.cluster, d.id.String())
|
||||
}
|
||||
|
||||
func nodesToCluster(ns []*client.Node, size int) (string, error) {
|
||||
s := make([]string, len(ns))
|
||||
for i, n := range ns {
|
||||
s[i] = n.Value
|
||||
}
|
||||
us := strings.Join(s, ",")
|
||||
m, err := types.NewURLsMap(us)
|
||||
if err != nil {
|
||||
return us, ErrInvalidURL
|
||||
}
|
||||
if m.Len() != size {
|
||||
return us, ErrDuplicateName
|
||||
}
|
||||
return us, nil
|
||||
}
|
||||
|
||||
type sortableNodes struct{ Nodes []*client.Node }
|
||||
|
||||
func (ns sortableNodes) Len() int { return len(ns.Nodes) }
|
||||
func (ns sortableNodes) Less(i, j int) bool {
|
||||
return ns.Nodes[i].CreatedIndex < ns.Nodes[j].CreatedIndex
|
||||
}
|
||||
func (ns sortableNodes) Swap(i, j int) { ns.Nodes[i], ns.Nodes[j] = ns.Nodes[j], ns.Nodes[i] }
|
||||
98
vendor/github.com/coreos/etcd/discovery/srv.go
generated
vendored
Normal file
98
vendor/github.com/coreos/etcd/discovery/srv.go
generated
vendored
Normal file
@@ -0,0 +1,98 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 discovery
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
)
|
||||
|
||||
var (
|
||||
// indirection for testing
|
||||
lookupSRV = net.LookupSRV
|
||||
resolveTCPAddr = net.ResolveTCPAddr
|
||||
)
|
||||
|
||||
// SRVGetCluster gets the cluster information via DNS discovery.
|
||||
// TODO(barakmich): Currently ignores priority and weight (as they don't make as much sense for a bootstrap)
|
||||
// Also doesn't do any lookups for the token (though it could)
|
||||
// Also sees each entry as a separate instance.
|
||||
func SRVGetCluster(name, dns string, defaultToken string, apurls types.URLs) (string, string, error) {
|
||||
stringParts := make([]string, 0)
|
||||
tempName := int(0)
|
||||
tcpAPUrls := make([]string, 0)
|
||||
|
||||
// First, resolve the apurls
|
||||
for _, url := range apurls {
|
||||
tcpAddr, err := resolveTCPAddr("tcp", url.Host)
|
||||
if err != nil {
|
||||
plog.Errorf("couldn't resolve host %s during SRV discovery", url.Host)
|
||||
return "", "", err
|
||||
}
|
||||
tcpAPUrls = append(tcpAPUrls, tcpAddr.String())
|
||||
}
|
||||
|
||||
updateNodeMap := func(service, prefix string) error {
|
||||
_, addrs, err := lookupSRV(service, "tcp", dns)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, srv := range addrs {
|
||||
target := strings.TrimSuffix(srv.Target, ".")
|
||||
host := net.JoinHostPort(target, fmt.Sprintf("%d", srv.Port))
|
||||
tcpAddr, err := resolveTCPAddr("tcp", host)
|
||||
if err != nil {
|
||||
plog.Warningf("couldn't resolve host %s during SRV discovery", host)
|
||||
continue
|
||||
}
|
||||
n := ""
|
||||
for _, url := range tcpAPUrls {
|
||||
if url == tcpAddr.String() {
|
||||
n = name
|
||||
}
|
||||
}
|
||||
if n == "" {
|
||||
n = fmt.Sprintf("%d", tempName)
|
||||
tempName += 1
|
||||
}
|
||||
stringParts = append(stringParts, fmt.Sprintf("%s=%s%s", n, prefix, host))
|
||||
plog.Noticef("got bootstrap from DNS for %s at %s%s", service, prefix, host)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
failCount := 0
|
||||
err := updateNodeMap("etcd-server-ssl", "https://")
|
||||
srvErr := make([]string, 2)
|
||||
if err != nil {
|
||||
srvErr[0] = fmt.Sprintf("error querying DNS SRV records for _etcd-server-ssl %s", err)
|
||||
failCount += 1
|
||||
}
|
||||
err = updateNodeMap("etcd-server", "http://")
|
||||
if err != nil {
|
||||
srvErr[1] = fmt.Sprintf("error querying DNS SRV records for _etcd-server %s", err)
|
||||
failCount += 1
|
||||
}
|
||||
if failCount == 2 {
|
||||
plog.Warningf(srvErr[0])
|
||||
plog.Warningf(srvErr[1])
|
||||
plog.Errorf("SRV discovery failed: too many errors querying DNS SRV records")
|
||||
return "", "", err
|
||||
}
|
||||
return strings.Join(stringParts, ","), defaultToken, nil
|
||||
}
|
||||
162
vendor/github.com/coreos/etcd/error/error.go
generated
vendored
Normal file
162
vendor/github.com/coreos/etcd/error/error.go
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 error describes errors in etcd project. When any change happens,
|
||||
// Documentation/errorcode.md needs to be updated correspondingly.
|
||||
package error
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
var errors = map[int]string{
|
||||
// command related errors
|
||||
EcodeKeyNotFound: "Key not found",
|
||||
EcodeTestFailed: "Compare failed", //test and set
|
||||
EcodeNotFile: "Not a file",
|
||||
ecodeNoMorePeer: "Reached the max number of peers in the cluster",
|
||||
EcodeNotDir: "Not a directory",
|
||||
EcodeNodeExist: "Key already exists", // create
|
||||
ecodeKeyIsPreserved: "The prefix of given key is a keyword in etcd",
|
||||
EcodeRootROnly: "Root is read only",
|
||||
EcodeDirNotEmpty: "Directory not empty",
|
||||
ecodeExistingPeerAddr: "Peer address has existed",
|
||||
EcodeUnauthorized: "The request requires user authentication",
|
||||
|
||||
// Post form related errors
|
||||
ecodeValueRequired: "Value is Required in POST form",
|
||||
EcodePrevValueRequired: "PrevValue is Required in POST form",
|
||||
EcodeTTLNaN: "The given TTL in POST form is not a number",
|
||||
EcodeIndexNaN: "The given index in POST form is not a number",
|
||||
ecodeValueOrTTLRequired: "Value or TTL is required in POST form",
|
||||
ecodeTimeoutNaN: "The given timeout in POST form is not a number",
|
||||
ecodeNameRequired: "Name is required in POST form",
|
||||
ecodeIndexOrValueRequired: "Index or value is required",
|
||||
ecodeIndexValueMutex: "Index and value cannot both be specified",
|
||||
EcodeInvalidField: "Invalid field",
|
||||
EcodeInvalidForm: "Invalid POST form",
|
||||
EcodeRefreshValue: "Value provided on refresh",
|
||||
EcodeRefreshTTLRequired: "A TTL must be provided on refresh",
|
||||
|
||||
// raft related errors
|
||||
EcodeRaftInternal: "Raft Internal Error",
|
||||
EcodeLeaderElect: "During Leader Election",
|
||||
|
||||
// etcd related errors
|
||||
EcodeWatcherCleared: "watcher is cleared due to etcd recovery",
|
||||
EcodeEventIndexCleared: "The event in requested index is outdated and cleared",
|
||||
ecodeStandbyInternal: "Standby Internal Error",
|
||||
ecodeInvalidActiveSize: "Invalid active size",
|
||||
ecodeInvalidRemoveDelay: "Standby remove delay",
|
||||
|
||||
// client related errors
|
||||
ecodeClientInternal: "Client Internal Error",
|
||||
}
|
||||
|
||||
var errorStatus = map[int]int{
|
||||
EcodeKeyNotFound: http.StatusNotFound,
|
||||
EcodeNotFile: http.StatusForbidden,
|
||||
EcodeDirNotEmpty: http.StatusForbidden,
|
||||
EcodeUnauthorized: http.StatusUnauthorized,
|
||||
EcodeTestFailed: http.StatusPreconditionFailed,
|
||||
EcodeNodeExist: http.StatusPreconditionFailed,
|
||||
EcodeRaftInternal: http.StatusInternalServerError,
|
||||
EcodeLeaderElect: http.StatusInternalServerError,
|
||||
}
|
||||
|
||||
const (
|
||||
EcodeKeyNotFound = 100
|
||||
EcodeTestFailed = 101
|
||||
EcodeNotFile = 102
|
||||
ecodeNoMorePeer = 103
|
||||
EcodeNotDir = 104
|
||||
EcodeNodeExist = 105
|
||||
ecodeKeyIsPreserved = 106
|
||||
EcodeRootROnly = 107
|
||||
EcodeDirNotEmpty = 108
|
||||
ecodeExistingPeerAddr = 109
|
||||
EcodeUnauthorized = 110
|
||||
|
||||
ecodeValueRequired = 200
|
||||
EcodePrevValueRequired = 201
|
||||
EcodeTTLNaN = 202
|
||||
EcodeIndexNaN = 203
|
||||
ecodeValueOrTTLRequired = 204
|
||||
ecodeTimeoutNaN = 205
|
||||
ecodeNameRequired = 206
|
||||
ecodeIndexOrValueRequired = 207
|
||||
ecodeIndexValueMutex = 208
|
||||
EcodeInvalidField = 209
|
||||
EcodeInvalidForm = 210
|
||||
EcodeRefreshValue = 211
|
||||
EcodeRefreshTTLRequired = 212
|
||||
|
||||
EcodeRaftInternal = 300
|
||||
EcodeLeaderElect = 301
|
||||
|
||||
EcodeWatcherCleared = 400
|
||||
EcodeEventIndexCleared = 401
|
||||
ecodeStandbyInternal = 402
|
||||
ecodeInvalidActiveSize = 403
|
||||
ecodeInvalidRemoveDelay = 404
|
||||
|
||||
ecodeClientInternal = 500
|
||||
)
|
||||
|
||||
type Error struct {
|
||||
ErrorCode int `json:"errorCode"`
|
||||
Message string `json:"message"`
|
||||
Cause string `json:"cause,omitempty"`
|
||||
Index uint64 `json:"index"`
|
||||
}
|
||||
|
||||
func NewRequestError(errorCode int, cause string) *Error {
|
||||
return NewError(errorCode, cause, 0)
|
||||
}
|
||||
|
||||
func NewError(errorCode int, cause string, index uint64) *Error {
|
||||
return &Error{
|
||||
ErrorCode: errorCode,
|
||||
Message: errors[errorCode],
|
||||
Cause: cause,
|
||||
Index: index,
|
||||
}
|
||||
}
|
||||
|
||||
// Error is for the error interface
|
||||
func (e Error) Error() string {
|
||||
return e.Message + " (" + e.Cause + ")"
|
||||
}
|
||||
|
||||
func (e Error) toJsonString() string {
|
||||
b, _ := json.Marshal(e)
|
||||
return string(b)
|
||||
}
|
||||
|
||||
func (e Error) StatusCode() int {
|
||||
status, ok := errorStatus[e.ErrorCode]
|
||||
if !ok {
|
||||
status = http.StatusBadRequest
|
||||
}
|
||||
return status
|
||||
}
|
||||
|
||||
func (e Error) WriteTo(w http.ResponseWriter) {
|
||||
w.Header().Add("X-Etcd-Index", fmt.Sprint(e.Index))
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(e.StatusCode())
|
||||
fmt.Fprintln(w, e.toJsonString())
|
||||
}
|
||||
41
vendor/github.com/coreos/etcd/etcdserver/api/cluster.go
generated
vendored
Normal file
41
vendor/github.com/coreos/etcd/etcdserver/api/cluster.go
generated
vendored
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 api
|
||||
|
||||
import (
|
||||
"github.com/coreos/etcd/etcdserver/membership"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
|
||||
"github.com/coreos/go-semver/semver"
|
||||
)
|
||||
|
||||
// Cluster is an interface representing a collection of members in one etcd cluster.
|
||||
type Cluster interface {
|
||||
// ID returns the cluster ID
|
||||
ID() types.ID
|
||||
// ClientURLs returns an aggregate set of all URLs on which this
|
||||
// cluster is listening for client requests
|
||||
ClientURLs() []string
|
||||
// Members returns a slice of members sorted by their ID
|
||||
Members() []*membership.Member
|
||||
// Member retrieves a particular member based on ID, or nil if the
|
||||
// member does not exist in the cluster
|
||||
Member(id types.ID) *membership.Member
|
||||
// IsIDRemoved checks whether the given ID has been removed from this
|
||||
// cluster at some point in the past
|
||||
IsIDRemoved(id types.ID) bool
|
||||
// Version is the cluster-wide minimum major.minor version.
|
||||
Version() *semver.Version
|
||||
}
|
||||
101
vendor/github.com/coreos/etcd/etcdserver/api/v2http/capability.go
generated
vendored
Normal file
101
vendor/github.com/coreos/etcd/etcdserver/api/v2http/capability.go
generated
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 v2http
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/api/v2http/httptypes"
|
||||
"github.com/coreos/go-semver/semver"
|
||||
)
|
||||
|
||||
type capability string
|
||||
|
||||
const (
|
||||
authCapability capability = "auth"
|
||||
)
|
||||
|
||||
var (
|
||||
// capabilityMaps is a static map of version to capability map.
|
||||
// the base capabilities is the set of capability 2.0 supports.
|
||||
capabilityMaps = map[string]map[capability]bool{
|
||||
"2.1.0": {authCapability: true},
|
||||
"2.2.0": {authCapability: true},
|
||||
"2.3.0": {authCapability: true},
|
||||
}
|
||||
|
||||
enableMapMu sync.Mutex
|
||||
// enabledMap points to a map in capabilityMaps
|
||||
enabledMap map[capability]bool
|
||||
)
|
||||
|
||||
// capabilityLoop checks the cluster version every 500ms and updates
|
||||
// the enabledMap when the cluster version increased.
|
||||
// capabilityLoop MUST be ran in a goroutine before checking capability
|
||||
// or using capabilityHandler.
|
||||
func capabilityLoop(s *etcdserver.EtcdServer) {
|
||||
stopped := s.StopNotify()
|
||||
|
||||
var pv *semver.Version
|
||||
for {
|
||||
if v := s.ClusterVersion(); v != pv {
|
||||
if pv == nil {
|
||||
pv = v
|
||||
} else if v != nil && pv.LessThan(*v) {
|
||||
pv = v
|
||||
}
|
||||
enableMapMu.Lock()
|
||||
enabledMap = capabilityMaps[pv.String()]
|
||||
enableMapMu.Unlock()
|
||||
plog.Infof("enabled capabilities for version %s", pv)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-stopped:
|
||||
return
|
||||
case <-time.After(500 * time.Millisecond):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isCapabilityEnabled(c capability) bool {
|
||||
enableMapMu.Lock()
|
||||
defer enableMapMu.Unlock()
|
||||
if enabledMap == nil {
|
||||
return false
|
||||
}
|
||||
return enabledMap[c]
|
||||
}
|
||||
|
||||
func capabilityHandler(c capability, fn func(http.ResponseWriter, *http.Request)) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if !isCapabilityEnabled(c) {
|
||||
notCapable(w, r, c)
|
||||
return
|
||||
}
|
||||
fn(w, r)
|
||||
}
|
||||
}
|
||||
|
||||
func notCapable(w http.ResponseWriter, r *http.Request, c capability) {
|
||||
herr := httptypes.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("Not capable of accessing %s feature during rolling upgrades.", c))
|
||||
if err := herr.WriteTo(w); err != nil {
|
||||
plog.Debugf("error writing HTTPError (%v) to %s", err, r.RemoteAddr)
|
||||
}
|
||||
}
|
||||
830
vendor/github.com/coreos/etcd/etcdserver/api/v2http/client.go
generated
vendored
Normal file
830
vendor/github.com/coreos/etcd/etcdserver/api/v2http/client.go
generated
vendored
Normal file
@@ -0,0 +1,830 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 v2http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"expvar"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"net/http/pprof"
|
||||
"net/url"
|
||||
"path"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
etcdErr "github.com/coreos/etcd/error"
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/api"
|
||||
"github.com/coreos/etcd/etcdserver/api/v2http/httptypes"
|
||||
"github.com/coreos/etcd/etcdserver/auth"
|
||||
"github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/etcdserver/membership"
|
||||
"github.com/coreos/etcd/etcdserver/stats"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/etcd/raft"
|
||||
"github.com/coreos/etcd/store"
|
||||
"github.com/coreos/etcd/version"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
"github.com/jonboulle/clockwork"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const (
|
||||
authPrefix = "/v2/auth"
|
||||
keysPrefix = "/v2/keys"
|
||||
deprecatedMachinesPrefix = "/v2/machines"
|
||||
membersPrefix = "/v2/members"
|
||||
statsPrefix = "/v2/stats"
|
||||
varsPath = "/debug/vars"
|
||||
metricsPath = "/metrics"
|
||||
healthPath = "/health"
|
||||
versionPath = "/version"
|
||||
configPath = "/config"
|
||||
pprofPrefix = "/debug/pprof"
|
||||
)
|
||||
|
||||
// NewClientHandler generates a muxed http.Handler with the given parameters to serve etcd client requests.
|
||||
func NewClientHandler(server *etcdserver.EtcdServer, timeout time.Duration) http.Handler {
|
||||
go capabilityLoop(server)
|
||||
|
||||
sec := auth.NewStore(server, timeout)
|
||||
|
||||
kh := &keysHandler{
|
||||
sec: sec,
|
||||
server: server,
|
||||
cluster: server.Cluster(),
|
||||
timer: server,
|
||||
timeout: timeout,
|
||||
}
|
||||
|
||||
sh := &statsHandler{
|
||||
stats: server,
|
||||
}
|
||||
|
||||
mh := &membersHandler{
|
||||
sec: sec,
|
||||
server: server,
|
||||
cluster: server.Cluster(),
|
||||
timeout: timeout,
|
||||
clock: clockwork.NewRealClock(),
|
||||
}
|
||||
|
||||
dmh := &deprecatedMachinesHandler{
|
||||
cluster: server.Cluster(),
|
||||
}
|
||||
|
||||
sech := &authHandler{
|
||||
sec: sec,
|
||||
cluster: server.Cluster(),
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", http.NotFound)
|
||||
mux.Handle(healthPath, healthHandler(server))
|
||||
mux.HandleFunc(versionPath, versionHandler(server.Cluster(), serveVersion))
|
||||
mux.Handle(keysPrefix, kh)
|
||||
mux.Handle(keysPrefix+"/", kh)
|
||||
mux.HandleFunc(statsPrefix+"/store", sh.serveStore)
|
||||
mux.HandleFunc(statsPrefix+"/self", sh.serveSelf)
|
||||
mux.HandleFunc(statsPrefix+"/leader", sh.serveLeader)
|
||||
mux.HandleFunc(varsPath, serveVars)
|
||||
mux.HandleFunc(configPath+"/local/log", logHandleFunc)
|
||||
mux.Handle(metricsPath, prometheus.Handler())
|
||||
mux.Handle(membersPrefix, mh)
|
||||
mux.Handle(membersPrefix+"/", mh)
|
||||
mux.Handle(deprecatedMachinesPrefix, dmh)
|
||||
handleAuth(mux, sech)
|
||||
|
||||
if server.IsPprofEnabled() {
|
||||
plog.Infof("pprof is enabled under %s", pprofPrefix)
|
||||
|
||||
mux.HandleFunc(pprofPrefix, pprof.Index)
|
||||
mux.HandleFunc(pprofPrefix+"/profile", pprof.Profile)
|
||||
mux.HandleFunc(pprofPrefix+"/symbol", pprof.Symbol)
|
||||
mux.HandleFunc(pprofPrefix+"/cmdline", pprof.Cmdline)
|
||||
// TODO: currently, we don't create an entry for pprof.Trace,
|
||||
// because go 1.4 doesn't provide it. After support of go 1.4 is dropped,
|
||||
// we should add the entry.
|
||||
|
||||
mux.Handle(pprofPrefix+"/heap", pprof.Handler("heap"))
|
||||
mux.Handle(pprofPrefix+"/goroutine", pprof.Handler("goroutine"))
|
||||
mux.Handle(pprofPrefix+"/threadcreate", pprof.Handler("threadcreate"))
|
||||
mux.Handle(pprofPrefix+"/block", pprof.Handler("block"))
|
||||
}
|
||||
|
||||
return requestLogger(mux)
|
||||
}
|
||||
|
||||
type keysHandler struct {
|
||||
sec auth.Store
|
||||
server etcdserver.Server
|
||||
cluster api.Cluster
|
||||
timer etcdserver.RaftTimer
|
||||
timeout time.Duration
|
||||
}
|
||||
|
||||
func (h *keysHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if !allowMethod(w, r.Method, "HEAD", "GET", "PUT", "POST", "DELETE") {
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("X-Etcd-Cluster-ID", h.cluster.ID().String())
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), h.timeout)
|
||||
defer cancel()
|
||||
clock := clockwork.NewRealClock()
|
||||
startTime := clock.Now()
|
||||
rr, err := parseKeyRequest(r, clock)
|
||||
if err != nil {
|
||||
writeKeyError(w, err)
|
||||
return
|
||||
}
|
||||
// The path must be valid at this point (we've parsed the request successfully).
|
||||
if !hasKeyPrefixAccess(h.sec, r, r.URL.Path[len(keysPrefix):], rr.Recursive) {
|
||||
writeKeyNoAuth(w)
|
||||
return
|
||||
}
|
||||
if !rr.Wait {
|
||||
reportRequestReceived(rr)
|
||||
}
|
||||
resp, err := h.server.Do(ctx, rr)
|
||||
if err != nil {
|
||||
err = trimErrorPrefix(err, etcdserver.StoreKeysPrefix)
|
||||
writeKeyError(w, err)
|
||||
reportRequestFailed(rr, err)
|
||||
return
|
||||
}
|
||||
switch {
|
||||
case resp.Event != nil:
|
||||
if err := writeKeyEvent(w, resp.Event, h.timer); err != nil {
|
||||
// Should never be reached
|
||||
plog.Errorf("error writing event (%v)", err)
|
||||
}
|
||||
reportRequestCompleted(rr, resp, startTime)
|
||||
case resp.Watcher != nil:
|
||||
ctx, cancel := context.WithTimeout(context.Background(), defaultWatchTimeout)
|
||||
defer cancel()
|
||||
handleKeyWatch(ctx, w, resp.Watcher, rr.Stream, h.timer)
|
||||
default:
|
||||
writeKeyError(w, errors.New("received response with no Event/Watcher!"))
|
||||
}
|
||||
}
|
||||
|
||||
type deprecatedMachinesHandler struct {
|
||||
cluster api.Cluster
|
||||
}
|
||||
|
||||
func (h *deprecatedMachinesHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if !allowMethod(w, r.Method, "GET", "HEAD") {
|
||||
return
|
||||
}
|
||||
endpoints := h.cluster.ClientURLs()
|
||||
w.Write([]byte(strings.Join(endpoints, ", ")))
|
||||
}
|
||||
|
||||
type membersHandler struct {
|
||||
sec auth.Store
|
||||
server etcdserver.Server
|
||||
cluster api.Cluster
|
||||
timeout time.Duration
|
||||
clock clockwork.Clock
|
||||
}
|
||||
|
||||
func (h *membersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if !allowMethod(w, r.Method, "GET", "POST", "DELETE", "PUT") {
|
||||
return
|
||||
}
|
||||
if !hasWriteRootAccess(h.sec, r) {
|
||||
writeNoAuth(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("X-Etcd-Cluster-ID", h.cluster.ID().String())
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), h.timeout)
|
||||
defer cancel()
|
||||
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
switch trimPrefix(r.URL.Path, membersPrefix) {
|
||||
case "":
|
||||
mc := newMemberCollection(h.cluster.Members())
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(mc); err != nil {
|
||||
plog.Warningf("failed to encode members response (%v)", err)
|
||||
}
|
||||
case "leader":
|
||||
id := h.server.Leader()
|
||||
if id == 0 {
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusServiceUnavailable, "During election"))
|
||||
return
|
||||
}
|
||||
m := newMember(h.cluster.Member(id))
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(m); err != nil {
|
||||
plog.Warningf("failed to encode members response (%v)", err)
|
||||
}
|
||||
default:
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusNotFound, "Not found"))
|
||||
}
|
||||
case "POST":
|
||||
req := httptypes.MemberCreateRequest{}
|
||||
if ok := unmarshalRequest(r, &req, w); !ok {
|
||||
return
|
||||
}
|
||||
now := h.clock.Now()
|
||||
m := membership.NewMember("", req.PeerURLs, "", &now)
|
||||
err := h.server.AddMember(ctx, *m)
|
||||
switch {
|
||||
case err == membership.ErrIDExists || err == membership.ErrPeerURLexists:
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusConflict, err.Error()))
|
||||
return
|
||||
case err != nil:
|
||||
plog.Errorf("error adding member %s (%v)", m.ID, err)
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
res := newMember(m)
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
if err := json.NewEncoder(w).Encode(res); err != nil {
|
||||
plog.Warningf("failed to encode members response (%v)", err)
|
||||
}
|
||||
case "DELETE":
|
||||
id, ok := getID(r.URL.Path, w)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
err := h.server.RemoveMember(ctx, uint64(id))
|
||||
switch {
|
||||
case err == membership.ErrIDRemoved:
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusGone, fmt.Sprintf("Member permanently removed: %s", id)))
|
||||
case err == membership.ErrIDNotFound:
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", id)))
|
||||
case err != nil:
|
||||
plog.Errorf("error removing member %s (%v)", id, err)
|
||||
writeError(w, r, err)
|
||||
default:
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
case "PUT":
|
||||
id, ok := getID(r.URL.Path, w)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
req := httptypes.MemberUpdateRequest{}
|
||||
if ok := unmarshalRequest(r, &req, w); !ok {
|
||||
return
|
||||
}
|
||||
m := membership.Member{
|
||||
ID: id,
|
||||
RaftAttributes: membership.RaftAttributes{PeerURLs: req.PeerURLs.StringSlice()},
|
||||
}
|
||||
err := h.server.UpdateMember(ctx, m)
|
||||
switch {
|
||||
case err == membership.ErrPeerURLexists:
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusConflict, err.Error()))
|
||||
case err == membership.ErrIDNotFound:
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", id)))
|
||||
case err != nil:
|
||||
plog.Errorf("error updating member %s (%v)", m.ID, err)
|
||||
writeError(w, r, err)
|
||||
default:
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type statsHandler struct {
|
||||
stats stats.Stats
|
||||
}
|
||||
|
||||
func (h *statsHandler) serveStore(w http.ResponseWriter, r *http.Request) {
|
||||
if !allowMethod(w, r.Method, "GET") {
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(h.stats.StoreStats())
|
||||
}
|
||||
|
||||
func (h *statsHandler) serveSelf(w http.ResponseWriter, r *http.Request) {
|
||||
if !allowMethod(w, r.Method, "GET") {
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(h.stats.SelfStats())
|
||||
}
|
||||
|
||||
func (h *statsHandler) serveLeader(w http.ResponseWriter, r *http.Request) {
|
||||
if !allowMethod(w, r.Method, "GET") {
|
||||
return
|
||||
}
|
||||
stats := h.stats.LeaderStats()
|
||||
if stats == nil {
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusForbidden, "not current leader"))
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Write(stats)
|
||||
}
|
||||
|
||||
func serveVars(w http.ResponseWriter, r *http.Request) {
|
||||
if !allowMethod(w, r.Method, "GET") {
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json; charset=utf-8")
|
||||
fmt.Fprintf(w, "{\n")
|
||||
first := true
|
||||
expvar.Do(func(kv expvar.KeyValue) {
|
||||
if !first {
|
||||
fmt.Fprintf(w, ",\n")
|
||||
}
|
||||
first = false
|
||||
fmt.Fprintf(w, "%q: %s", kv.Key, kv.Value)
|
||||
})
|
||||
fmt.Fprintf(w, "\n}\n")
|
||||
}
|
||||
|
||||
// TODO: change etcdserver to raft interface when we have it.
|
||||
// add test for healthHandler when we have the interface ready.
|
||||
func healthHandler(server *etcdserver.EtcdServer) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
if !allowMethod(w, r.Method, "GET") {
|
||||
return
|
||||
}
|
||||
|
||||
if uint64(server.Leader()) == raft.None {
|
||||
http.Error(w, `{"health": "false"}`, http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
|
||||
// wait for raft's progress
|
||||
index := server.Index()
|
||||
for i := 0; i < 3; i++ {
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
if server.Index() > index {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write([]byte(`{"health": "true"}`))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
http.Error(w, `{"health": "false"}`, http.StatusServiceUnavailable)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func versionHandler(c api.Cluster, fn func(http.ResponseWriter, *http.Request, string)) http.HandlerFunc {
|
||||
return func(w http.ResponseWriter, r *http.Request) {
|
||||
v := c.Version()
|
||||
if v != nil {
|
||||
fn(w, r, v.String())
|
||||
} else {
|
||||
fn(w, r, "not_decided")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func serveVersion(w http.ResponseWriter, r *http.Request, clusterV string) {
|
||||
if !allowMethod(w, r.Method, "GET") {
|
||||
return
|
||||
}
|
||||
vs := version.Versions{
|
||||
Server: version.Version,
|
||||
Cluster: clusterV,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
b, err := json.Marshal(&vs)
|
||||
if err != nil {
|
||||
plog.Panicf("cannot marshal versions to json (%v)", err)
|
||||
}
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
func logHandleFunc(w http.ResponseWriter, r *http.Request) {
|
||||
if !allowMethod(w, r.Method, "PUT") {
|
||||
return
|
||||
}
|
||||
|
||||
in := struct{ Level string }{}
|
||||
|
||||
d := json.NewDecoder(r.Body)
|
||||
if err := d.Decode(&in); err != nil {
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid json body"))
|
||||
return
|
||||
}
|
||||
|
||||
logl, err := capnslog.ParseLevel(strings.ToUpper(in.Level))
|
||||
if err != nil {
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid log level "+in.Level))
|
||||
return
|
||||
}
|
||||
|
||||
plog.Noticef("globalLogLevel set to %q", logl.String())
|
||||
capnslog.SetGlobalLogLevel(logl)
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
// parseKeyRequest converts a received http.Request on keysPrefix to
|
||||
// a server Request, performing validation of supplied fields as appropriate.
|
||||
// If any validation fails, an empty Request and non-nil error is returned.
|
||||
func parseKeyRequest(r *http.Request, clock clockwork.Clock) (etcdserverpb.Request, error) {
|
||||
emptyReq := etcdserverpb.Request{}
|
||||
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
return emptyReq, etcdErr.NewRequestError(
|
||||
etcdErr.EcodeInvalidForm,
|
||||
err.Error(),
|
||||
)
|
||||
}
|
||||
|
||||
if !strings.HasPrefix(r.URL.Path, keysPrefix) {
|
||||
return emptyReq, etcdErr.NewRequestError(
|
||||
etcdErr.EcodeInvalidForm,
|
||||
"incorrect key prefix",
|
||||
)
|
||||
}
|
||||
p := path.Join(etcdserver.StoreKeysPrefix, r.URL.Path[len(keysPrefix):])
|
||||
|
||||
var pIdx, wIdx uint64
|
||||
if pIdx, err = getUint64(r.Form, "prevIndex"); err != nil {
|
||||
return emptyReq, etcdErr.NewRequestError(
|
||||
etcdErr.EcodeIndexNaN,
|
||||
`invalid value for "prevIndex"`,
|
||||
)
|
||||
}
|
||||
if wIdx, err = getUint64(r.Form, "waitIndex"); err != nil {
|
||||
return emptyReq, etcdErr.NewRequestError(
|
||||
etcdErr.EcodeIndexNaN,
|
||||
`invalid value for "waitIndex"`,
|
||||
)
|
||||
}
|
||||
|
||||
var rec, sort, wait, dir, quorum, stream bool
|
||||
if rec, err = getBool(r.Form, "recursive"); err != nil {
|
||||
return emptyReq, etcdErr.NewRequestError(
|
||||
etcdErr.EcodeInvalidField,
|
||||
`invalid value for "recursive"`,
|
||||
)
|
||||
}
|
||||
if sort, err = getBool(r.Form, "sorted"); err != nil {
|
||||
return emptyReq, etcdErr.NewRequestError(
|
||||
etcdErr.EcodeInvalidField,
|
||||
`invalid value for "sorted"`,
|
||||
)
|
||||
}
|
||||
if wait, err = getBool(r.Form, "wait"); err != nil {
|
||||
return emptyReq, etcdErr.NewRequestError(
|
||||
etcdErr.EcodeInvalidField,
|
||||
`invalid value for "wait"`,
|
||||
)
|
||||
}
|
||||
// TODO(jonboulle): define what parameters dir is/isn't compatible with?
|
||||
if dir, err = getBool(r.Form, "dir"); err != nil {
|
||||
return emptyReq, etcdErr.NewRequestError(
|
||||
etcdErr.EcodeInvalidField,
|
||||
`invalid value for "dir"`,
|
||||
)
|
||||
}
|
||||
if quorum, err = getBool(r.Form, "quorum"); err != nil {
|
||||
return emptyReq, etcdErr.NewRequestError(
|
||||
etcdErr.EcodeInvalidField,
|
||||
`invalid value for "quorum"`,
|
||||
)
|
||||
}
|
||||
if stream, err = getBool(r.Form, "stream"); err != nil {
|
||||
return emptyReq, etcdErr.NewRequestError(
|
||||
etcdErr.EcodeInvalidField,
|
||||
`invalid value for "stream"`,
|
||||
)
|
||||
}
|
||||
|
||||
if wait && r.Method != "GET" {
|
||||
return emptyReq, etcdErr.NewRequestError(
|
||||
etcdErr.EcodeInvalidField,
|
||||
`"wait" can only be used with GET requests`,
|
||||
)
|
||||
}
|
||||
|
||||
pV := r.FormValue("prevValue")
|
||||
if _, ok := r.Form["prevValue"]; ok && pV == "" {
|
||||
return emptyReq, etcdErr.NewRequestError(
|
||||
etcdErr.EcodePrevValueRequired,
|
||||
`"prevValue" cannot be empty`,
|
||||
)
|
||||
}
|
||||
|
||||
// TTL is nullable, so leave it null if not specified
|
||||
// or an empty string
|
||||
var ttl *uint64
|
||||
if len(r.FormValue("ttl")) > 0 {
|
||||
i, err := getUint64(r.Form, "ttl")
|
||||
if err != nil {
|
||||
return emptyReq, etcdErr.NewRequestError(
|
||||
etcdErr.EcodeTTLNaN,
|
||||
`invalid value for "ttl"`,
|
||||
)
|
||||
}
|
||||
ttl = &i
|
||||
}
|
||||
|
||||
// prevExist is nullable, so leave it null if not specified
|
||||
var pe *bool
|
||||
if _, ok := r.Form["prevExist"]; ok {
|
||||
bv, err := getBool(r.Form, "prevExist")
|
||||
if err != nil {
|
||||
return emptyReq, etcdErr.NewRequestError(
|
||||
etcdErr.EcodeInvalidField,
|
||||
"invalid value for prevExist",
|
||||
)
|
||||
}
|
||||
pe = &bv
|
||||
}
|
||||
|
||||
// refresh is nullable, so leave it null if not specified
|
||||
var refresh *bool
|
||||
if _, ok := r.Form["refresh"]; ok {
|
||||
bv, err := getBool(r.Form, "refresh")
|
||||
if err != nil {
|
||||
return emptyReq, etcdErr.NewRequestError(
|
||||
etcdErr.EcodeInvalidField,
|
||||
"invalid value for refresh",
|
||||
)
|
||||
}
|
||||
refresh = &bv
|
||||
if refresh != nil && *refresh {
|
||||
val := r.FormValue("value")
|
||||
if _, ok := r.Form["value"]; ok && val != "" {
|
||||
return emptyReq, etcdErr.NewRequestError(
|
||||
etcdErr.EcodeRefreshValue,
|
||||
`A value was provided on a refresh`,
|
||||
)
|
||||
}
|
||||
if ttl == nil {
|
||||
return emptyReq, etcdErr.NewRequestError(
|
||||
etcdErr.EcodeRefreshTTLRequired,
|
||||
`No TTL value set`,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
rr := etcdserverpb.Request{
|
||||
Method: r.Method,
|
||||
Path: p,
|
||||
Val: r.FormValue("value"),
|
||||
Dir: dir,
|
||||
PrevValue: pV,
|
||||
PrevIndex: pIdx,
|
||||
PrevExist: pe,
|
||||
Wait: wait,
|
||||
Since: wIdx,
|
||||
Recursive: rec,
|
||||
Sorted: sort,
|
||||
Quorum: quorum,
|
||||
Stream: stream,
|
||||
}
|
||||
|
||||
if pe != nil {
|
||||
rr.PrevExist = pe
|
||||
}
|
||||
|
||||
if refresh != nil {
|
||||
rr.Refresh = refresh
|
||||
}
|
||||
|
||||
// Null TTL is equivalent to unset Expiration
|
||||
if ttl != nil {
|
||||
expr := time.Duration(*ttl) * time.Second
|
||||
rr.Expiration = clock.Now().Add(expr).UnixNano()
|
||||
}
|
||||
|
||||
return rr, nil
|
||||
}
|
||||
|
||||
// writeKeyEvent trims the prefix of key path in a single Event under
|
||||
// StoreKeysPrefix, serializes it and writes the resulting JSON to the given
|
||||
// ResponseWriter, along with the appropriate headers.
|
||||
func writeKeyEvent(w http.ResponseWriter, ev *store.Event, rt etcdserver.RaftTimer) error {
|
||||
if ev == nil {
|
||||
return errors.New("cannot write empty Event!")
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("X-Etcd-Index", fmt.Sprint(ev.EtcdIndex))
|
||||
w.Header().Set("X-Raft-Index", fmt.Sprint(rt.Index()))
|
||||
w.Header().Set("X-Raft-Term", fmt.Sprint(rt.Term()))
|
||||
|
||||
if ev.IsCreated() {
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
}
|
||||
|
||||
ev = trimEventPrefix(ev, etcdserver.StoreKeysPrefix)
|
||||
return json.NewEncoder(w).Encode(ev)
|
||||
}
|
||||
|
||||
func writeKeyNoAuth(w http.ResponseWriter) {
|
||||
e := etcdErr.NewError(etcdErr.EcodeUnauthorized, "Insufficient credentials", 0)
|
||||
e.WriteTo(w)
|
||||
}
|
||||
|
||||
// writeKeyError logs and writes the given Error to the ResponseWriter.
|
||||
// If Error is not an etcdErr, the error will be converted to an etcd error.
|
||||
func writeKeyError(w http.ResponseWriter, err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
switch e := err.(type) {
|
||||
case *etcdErr.Error:
|
||||
e.WriteTo(w)
|
||||
default:
|
||||
switch err {
|
||||
case etcdserver.ErrTimeoutDueToLeaderFail, etcdserver.ErrTimeoutDueToConnectionLost:
|
||||
mlog.MergeError(err)
|
||||
default:
|
||||
mlog.MergeErrorf("got unexpected response error (%v)", err)
|
||||
}
|
||||
ee := etcdErr.NewError(etcdErr.EcodeRaftInternal, err.Error(), 0)
|
||||
ee.WriteTo(w)
|
||||
}
|
||||
}
|
||||
|
||||
func handleKeyWatch(ctx context.Context, w http.ResponseWriter, wa store.Watcher, stream bool, rt etcdserver.RaftTimer) {
|
||||
defer wa.Remove()
|
||||
ech := wa.EventChan()
|
||||
var nch <-chan bool
|
||||
if x, ok := w.(http.CloseNotifier); ok {
|
||||
nch = x.CloseNotify()
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.Header().Set("X-Etcd-Index", fmt.Sprint(wa.StartIndex()))
|
||||
w.Header().Set("X-Raft-Index", fmt.Sprint(rt.Index()))
|
||||
w.Header().Set("X-Raft-Term", fmt.Sprint(rt.Term()))
|
||||
w.WriteHeader(http.StatusOK)
|
||||
|
||||
// Ensure headers are flushed early, in case of long polling
|
||||
w.(http.Flusher).Flush()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-nch:
|
||||
// Client closed connection. Nothing to do.
|
||||
return
|
||||
case <-ctx.Done():
|
||||
// Timed out. net/http will close the connection for us, so nothing to do.
|
||||
return
|
||||
case ev, ok := <-ech:
|
||||
if !ok {
|
||||
// If the channel is closed this may be an indication of
|
||||
// that notifications are much more than we are able to
|
||||
// send to the client in time. Then we simply end streaming.
|
||||
return
|
||||
}
|
||||
ev = trimEventPrefix(ev, etcdserver.StoreKeysPrefix)
|
||||
if err := json.NewEncoder(w).Encode(ev); err != nil {
|
||||
// Should never be reached
|
||||
plog.Warningf("error writing event (%v)", err)
|
||||
return
|
||||
}
|
||||
if !stream {
|
||||
return
|
||||
}
|
||||
w.(http.Flusher).Flush()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func trimEventPrefix(ev *store.Event, prefix string) *store.Event {
|
||||
if ev == nil {
|
||||
return nil
|
||||
}
|
||||
// Since the *Event may reference one in the store history
|
||||
// history, we must copy it before modifying
|
||||
e := ev.Clone()
|
||||
e.Node = trimNodeExternPrefix(e.Node, prefix)
|
||||
e.PrevNode = trimNodeExternPrefix(e.PrevNode, prefix)
|
||||
return e
|
||||
}
|
||||
|
||||
func trimNodeExternPrefix(n *store.NodeExtern, prefix string) *store.NodeExtern {
|
||||
if n == nil {
|
||||
return nil
|
||||
}
|
||||
n.Key = strings.TrimPrefix(n.Key, prefix)
|
||||
for _, nn := range n.Nodes {
|
||||
nn = trimNodeExternPrefix(nn, prefix)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func trimErrorPrefix(err error, prefix string) error {
|
||||
if e, ok := err.(*etcdErr.Error); ok {
|
||||
e.Cause = strings.TrimPrefix(e.Cause, prefix)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func unmarshalRequest(r *http.Request, req json.Unmarshaler, w http.ResponseWriter) bool {
|
||||
ctype := r.Header.Get("Content-Type")
|
||||
if ctype != "application/json" {
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusUnsupportedMediaType, fmt.Sprintf("Bad Content-Type %s, accept application/json", ctype)))
|
||||
return false
|
||||
}
|
||||
b, err := ioutil.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, err.Error()))
|
||||
return false
|
||||
}
|
||||
if err := req.UnmarshalJSON(b); err != nil {
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, err.Error()))
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func getID(p string, w http.ResponseWriter) (types.ID, bool) {
|
||||
idStr := trimPrefix(p, membersPrefix)
|
||||
if idStr == "" {
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return 0, false
|
||||
}
|
||||
id, err := types.IDFromString(idStr)
|
||||
if err != nil {
|
||||
writeError(w, nil, httptypes.NewHTTPError(http.StatusNotFound, fmt.Sprintf("No such member: %s", idStr)))
|
||||
return 0, false
|
||||
}
|
||||
return id, true
|
||||
}
|
||||
|
||||
// getUint64 extracts a uint64 by the given key from a Form. If the key does
|
||||
// not exist in the form, 0 is returned. If the key exists but the value is
|
||||
// badly formed, an error is returned. If multiple values are present only the
|
||||
// first is considered.
|
||||
func getUint64(form url.Values, key string) (i uint64, err error) {
|
||||
if vals, ok := form[key]; ok {
|
||||
i, err = strconv.ParseUint(vals[0], 10, 64)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// getBool extracts a bool by the given key from a Form. If the key does not
|
||||
// exist in the form, false is returned. If the key exists but the value is
|
||||
// badly formed, an error is returned. If multiple values are present only the
|
||||
// first is considered.
|
||||
func getBool(form url.Values, key string) (b bool, err error) {
|
||||
if vals, ok := form[key]; ok {
|
||||
b, err = strconv.ParseBool(vals[0])
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// trimPrefix removes a given prefix and any slash following the prefix
|
||||
// e.g.: trimPrefix("foo", "foo") == trimPrefix("foo/", "foo") == ""
|
||||
func trimPrefix(p, prefix string) (s string) {
|
||||
s = strings.TrimPrefix(p, prefix)
|
||||
s = strings.TrimPrefix(s, "/")
|
||||
return
|
||||
}
|
||||
|
||||
func newMemberCollection(ms []*membership.Member) *httptypes.MemberCollection {
|
||||
c := httptypes.MemberCollection(make([]httptypes.Member, len(ms)))
|
||||
|
||||
for i, m := range ms {
|
||||
c[i] = newMember(m)
|
||||
}
|
||||
|
||||
return &c
|
||||
}
|
||||
|
||||
func newMember(m *membership.Member) httptypes.Member {
|
||||
tm := httptypes.Member{
|
||||
ID: m.ID.String(),
|
||||
Name: m.Name,
|
||||
PeerURLs: make([]string, len(m.PeerURLs)),
|
||||
ClientURLs: make([]string, len(m.ClientURLs)),
|
||||
}
|
||||
|
||||
copy(tm.PeerURLs, m.PeerURLs)
|
||||
copy(tm.ClientURLs, m.ClientURLs)
|
||||
|
||||
return tm
|
||||
}
|
||||
507
vendor/github.com/coreos/etcd/etcdserver/api/v2http/client_auth.go
generated
vendored
Normal file
507
vendor/github.com/coreos/etcd/etcdserver/api/v2http/client_auth.go
generated
vendored
Normal file
@@ -0,0 +1,507 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 v2http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/coreos/etcd/etcdserver/api"
|
||||
"github.com/coreos/etcd/etcdserver/api/v2http/httptypes"
|
||||
"github.com/coreos/etcd/etcdserver/auth"
|
||||
)
|
||||
|
||||
type authHandler struct {
|
||||
sec auth.Store
|
||||
cluster api.Cluster
|
||||
}
|
||||
|
||||
func hasWriteRootAccess(sec auth.Store, r *http.Request) bool {
|
||||
if r.Method == "GET" || r.Method == "HEAD" {
|
||||
return true
|
||||
}
|
||||
return hasRootAccess(sec, r)
|
||||
}
|
||||
|
||||
func hasRootAccess(sec auth.Store, r *http.Request) bool {
|
||||
if sec == nil {
|
||||
// No store means no auth available, eg, tests.
|
||||
return true
|
||||
}
|
||||
if !sec.AuthEnabled() {
|
||||
return true
|
||||
}
|
||||
username, password, ok := r.BasicAuth()
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
rootUser, err := sec.GetUser(username)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
|
||||
ok = sec.CheckPassword(rootUser, password)
|
||||
if !ok {
|
||||
plog.Warningf("auth: wrong password for user %s", username)
|
||||
return false
|
||||
}
|
||||
for _, role := range rootUser.Roles {
|
||||
if role == auth.RootRoleName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
plog.Warningf("auth: user %s does not have the %s role for resource %s.", username, auth.RootRoleName, r.URL.Path)
|
||||
return false
|
||||
}
|
||||
|
||||
func hasKeyPrefixAccess(sec auth.Store, r *http.Request, key string, recursive bool) bool {
|
||||
if sec == nil {
|
||||
// No store means no auth available, eg, tests.
|
||||
return true
|
||||
}
|
||||
if !sec.AuthEnabled() {
|
||||
return true
|
||||
}
|
||||
if r.Header.Get("Authorization") == "" {
|
||||
plog.Warningf("auth: no authorization provided, checking guest access")
|
||||
return hasGuestAccess(sec, r, key)
|
||||
}
|
||||
username, password, ok := r.BasicAuth()
|
||||
if !ok {
|
||||
plog.Warningf("auth: malformed basic auth encoding")
|
||||
return false
|
||||
}
|
||||
user, err := sec.GetUser(username)
|
||||
if err != nil {
|
||||
plog.Warningf("auth: no such user: %s.", username)
|
||||
return false
|
||||
}
|
||||
authAsUser := sec.CheckPassword(user, password)
|
||||
if !authAsUser {
|
||||
plog.Warningf("auth: incorrect password for user: %s.", username)
|
||||
return false
|
||||
}
|
||||
writeAccess := r.Method != "GET" && r.Method != "HEAD"
|
||||
for _, roleName := range user.Roles {
|
||||
role, err := sec.GetRole(roleName)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if recursive {
|
||||
if role.HasRecursiveAccess(key, writeAccess) {
|
||||
return true
|
||||
}
|
||||
} else if role.HasKeyAccess(key, writeAccess) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
plog.Warningf("auth: invalid access for user %s on key %s.", username, key)
|
||||
return false
|
||||
}
|
||||
|
||||
func hasGuestAccess(sec auth.Store, r *http.Request, key string) bool {
|
||||
writeAccess := r.Method != "GET" && r.Method != "HEAD"
|
||||
role, err := sec.GetRole(auth.GuestRoleName)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if role.HasKeyAccess(key, writeAccess) {
|
||||
return true
|
||||
}
|
||||
plog.Warningf("auth: invalid access for unauthenticated user on resource %s.", key)
|
||||
return false
|
||||
}
|
||||
|
||||
func writeNoAuth(w http.ResponseWriter, r *http.Request) {
|
||||
herr := httptypes.NewHTTPError(http.StatusUnauthorized, "Insufficient credentials")
|
||||
if err := herr.WriteTo(w); err != nil {
|
||||
plog.Debugf("error writing HTTPError (%v) to %s", err, r.RemoteAddr)
|
||||
}
|
||||
}
|
||||
|
||||
func handleAuth(mux *http.ServeMux, sh *authHandler) {
|
||||
mux.HandleFunc(authPrefix+"/roles", capabilityHandler(authCapability, sh.baseRoles))
|
||||
mux.HandleFunc(authPrefix+"/roles/", capabilityHandler(authCapability, sh.handleRoles))
|
||||
mux.HandleFunc(authPrefix+"/users", capabilityHandler(authCapability, sh.baseUsers))
|
||||
mux.HandleFunc(authPrefix+"/users/", capabilityHandler(authCapability, sh.handleUsers))
|
||||
mux.HandleFunc(authPrefix+"/enable", capabilityHandler(authCapability, sh.enableDisable))
|
||||
}
|
||||
|
||||
func (sh *authHandler) baseRoles(w http.ResponseWriter, r *http.Request) {
|
||||
if !allowMethod(w, r.Method, "GET") {
|
||||
return
|
||||
}
|
||||
if !hasRootAccess(sh.sec, r) {
|
||||
writeNoAuth(w, r)
|
||||
return
|
||||
}
|
||||
|
||||
w.Header().Set("X-Etcd-Cluster-ID", sh.cluster.ID().String())
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
roles, err := sh.sec.AllRoles()
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
if roles == nil {
|
||||
roles = make([]string, 0)
|
||||
}
|
||||
|
||||
err = r.ParseForm()
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
var rolesCollections struct {
|
||||
Roles []auth.Role `json:"roles"`
|
||||
}
|
||||
for _, roleName := range roles {
|
||||
var role auth.Role
|
||||
role, err = sh.sec.GetRole(roleName)
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
rolesCollections.Roles = append(rolesCollections.Roles, role)
|
||||
}
|
||||
err = json.NewEncoder(w).Encode(rolesCollections)
|
||||
|
||||
if err != nil {
|
||||
plog.Warningf("baseRoles error encoding on %s", r.URL)
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (sh *authHandler) handleRoles(w http.ResponseWriter, r *http.Request) {
|
||||
subpath := path.Clean(r.URL.Path[len(authPrefix):])
|
||||
// Split "/roles/rolename/command".
|
||||
// First item is an empty string, second is "roles"
|
||||
pieces := strings.Split(subpath, "/")
|
||||
if len(pieces) == 2 {
|
||||
sh.baseRoles(w, r)
|
||||
return
|
||||
}
|
||||
if len(pieces) != 3 {
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid path"))
|
||||
return
|
||||
}
|
||||
sh.forRole(w, r, pieces[2])
|
||||
}
|
||||
|
||||
func (sh *authHandler) forRole(w http.ResponseWriter, r *http.Request, role string) {
|
||||
if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") {
|
||||
return
|
||||
}
|
||||
if !hasRootAccess(sh.sec, r) {
|
||||
writeNoAuth(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("X-Etcd-Cluster-ID", sh.cluster.ID().String())
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
data, err := sh.sec.GetRole(role)
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
err = json.NewEncoder(w).Encode(data)
|
||||
if err != nil {
|
||||
plog.Warningf("forRole error encoding on %s", r.URL)
|
||||
return
|
||||
}
|
||||
return
|
||||
case "PUT":
|
||||
var in auth.Role
|
||||
err := json.NewDecoder(r.Body).Decode(&in)
|
||||
if err != nil {
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid JSON in request body."))
|
||||
return
|
||||
}
|
||||
if in.Role != role {
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Role JSON name does not match the name in the URL"))
|
||||
return
|
||||
}
|
||||
|
||||
var out auth.Role
|
||||
|
||||
// create
|
||||
if in.Grant.IsEmpty() && in.Revoke.IsEmpty() {
|
||||
err = sh.sec.CreateRole(in)
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
out = in
|
||||
} else {
|
||||
if !in.Permissions.IsEmpty() {
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Role JSON contains both permissions and grant/revoke"))
|
||||
return
|
||||
}
|
||||
out, err = sh.sec.UpdateRole(in)
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
err = json.NewEncoder(w).Encode(out)
|
||||
if err != nil {
|
||||
plog.Warningf("forRole error encoding on %s", r.URL)
|
||||
return
|
||||
}
|
||||
return
|
||||
case "DELETE":
|
||||
err := sh.sec.DeleteRole(role)
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type userWithRoles struct {
|
||||
User string `json:"user"`
|
||||
Roles []auth.Role `json:"roles,omitempty"`
|
||||
}
|
||||
|
||||
func (sh *authHandler) baseUsers(w http.ResponseWriter, r *http.Request) {
|
||||
if !allowMethod(w, r.Method, "GET") {
|
||||
return
|
||||
}
|
||||
if !hasRootAccess(sh.sec, r) {
|
||||
writeNoAuth(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("X-Etcd-Cluster-ID", sh.cluster.ID().String())
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
users, err := sh.sec.AllUsers()
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
if users == nil {
|
||||
users = make([]string, 0)
|
||||
}
|
||||
|
||||
err = r.ParseForm()
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
var usersCollections struct {
|
||||
Users []userWithRoles `json:"users"`
|
||||
}
|
||||
for _, userName := range users {
|
||||
var user auth.User
|
||||
user, err = sh.sec.GetUser(userName)
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
uwr := userWithRoles{User: user.User}
|
||||
for _, roleName := range user.Roles {
|
||||
var role auth.Role
|
||||
role, err = sh.sec.GetRole(roleName)
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
uwr.Roles = append(uwr.Roles, role)
|
||||
}
|
||||
|
||||
usersCollections.Users = append(usersCollections.Users, uwr)
|
||||
}
|
||||
err = json.NewEncoder(w).Encode(usersCollections)
|
||||
|
||||
if err != nil {
|
||||
plog.Warningf("baseUsers error encoding on %s", r.URL)
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func (sh *authHandler) handleUsers(w http.ResponseWriter, r *http.Request) {
|
||||
subpath := path.Clean(r.URL.Path[len(authPrefix):])
|
||||
// Split "/users/username".
|
||||
// First item is an empty string, second is "users"
|
||||
pieces := strings.Split(subpath, "/")
|
||||
if len(pieces) == 2 {
|
||||
sh.baseUsers(w, r)
|
||||
return
|
||||
}
|
||||
if len(pieces) != 3 {
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid path"))
|
||||
return
|
||||
}
|
||||
sh.forUser(w, r, pieces[2])
|
||||
}
|
||||
|
||||
func (sh *authHandler) forUser(w http.ResponseWriter, r *http.Request, user string) {
|
||||
if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") {
|
||||
return
|
||||
}
|
||||
if !hasRootAccess(sh.sec, r) {
|
||||
writeNoAuth(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("X-Etcd-Cluster-ID", sh.cluster.ID().String())
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
u, err := sh.sec.GetUser(user)
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
err = r.ParseForm()
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
|
||||
uwr := userWithRoles{User: u.User}
|
||||
for _, roleName := range u.Roles {
|
||||
var role auth.Role
|
||||
role, err = sh.sec.GetRole(roleName)
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
uwr.Roles = append(uwr.Roles, role)
|
||||
}
|
||||
err = json.NewEncoder(w).Encode(uwr)
|
||||
|
||||
if err != nil {
|
||||
plog.Warningf("forUser error encoding on %s", r.URL)
|
||||
return
|
||||
}
|
||||
return
|
||||
case "PUT":
|
||||
var u auth.User
|
||||
err := json.NewDecoder(r.Body).Decode(&u)
|
||||
if err != nil {
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "Invalid JSON in request body."))
|
||||
return
|
||||
}
|
||||
if u.User != user {
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "User JSON name does not match the name in the URL"))
|
||||
return
|
||||
}
|
||||
|
||||
var (
|
||||
out auth.User
|
||||
created bool
|
||||
)
|
||||
|
||||
if len(u.Grant) == 0 && len(u.Revoke) == 0 {
|
||||
// create or update
|
||||
if len(u.Roles) != 0 {
|
||||
out, err = sh.sec.CreateUser(u)
|
||||
} else {
|
||||
// if user passes in both password and roles, we are unsure about his/her
|
||||
// intention.
|
||||
out, created, err = sh.sec.CreateOrUpdateUser(u)
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
// update case
|
||||
if len(u.Roles) != 0 {
|
||||
writeError(w, r, httptypes.NewHTTPError(http.StatusBadRequest, "User JSON contains both roles and grant/revoke"))
|
||||
return
|
||||
}
|
||||
out, err = sh.sec.UpdateUser(u)
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if created {
|
||||
w.WriteHeader(http.StatusCreated)
|
||||
} else {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}
|
||||
|
||||
out.Password = ""
|
||||
|
||||
err = json.NewEncoder(w).Encode(out)
|
||||
if err != nil {
|
||||
plog.Warningf("forUser error encoding on %s", r.URL)
|
||||
return
|
||||
}
|
||||
return
|
||||
case "DELETE":
|
||||
err := sh.sec.DeleteUser(user)
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type enabled struct {
|
||||
Enabled bool `json:"enabled"`
|
||||
}
|
||||
|
||||
func (sh *authHandler) enableDisable(w http.ResponseWriter, r *http.Request) {
|
||||
if !allowMethod(w, r.Method, "GET", "PUT", "DELETE") {
|
||||
return
|
||||
}
|
||||
if !hasWriteRootAccess(sh.sec, r) {
|
||||
writeNoAuth(w, r)
|
||||
return
|
||||
}
|
||||
w.Header().Set("X-Etcd-Cluster-ID", sh.cluster.ID().String())
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
isEnabled := sh.sec.AuthEnabled()
|
||||
switch r.Method {
|
||||
case "GET":
|
||||
jsonDict := enabled{isEnabled}
|
||||
err := json.NewEncoder(w).Encode(jsonDict)
|
||||
if err != nil {
|
||||
plog.Warningf("error encoding auth state on %s", r.URL)
|
||||
}
|
||||
case "PUT":
|
||||
err := sh.sec.EnableAuth()
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
case "DELETE":
|
||||
err := sh.sec.DisableAuth()
|
||||
if err != nil {
|
||||
writeError(w, r, err)
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
16
vendor/github.com/coreos/etcd/etcdserver/api/v2http/doc.go
generated
vendored
Normal file
16
vendor/github.com/coreos/etcd/etcdserver/api/v2http/doc.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 v2http provides etcd client and server implementations.
|
||||
package v2http
|
||||
94
vendor/github.com/coreos/etcd/etcdserver/api/v2http/http.go
generated
vendored
Normal file
94
vendor/github.com/coreos/etcd/etcdserver/api/v2http/http.go
generated
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 v2http
|
||||
|
||||
import (
|
||||
"math"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
etcdErr "github.com/coreos/etcd/error"
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/api/v2http/httptypes"
|
||||
|
||||
"github.com/coreos/etcd/etcdserver/auth"
|
||||
"github.com/coreos/etcd/pkg/logutil"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
)
|
||||
|
||||
const (
|
||||
// time to wait for a Watch request
|
||||
defaultWatchTimeout = time.Duration(math.MaxInt64)
|
||||
)
|
||||
|
||||
var (
|
||||
plog = capnslog.NewPackageLogger("github.com/coreos/etcd/etcdserver/api", "v2http")
|
||||
mlog = logutil.NewMergeLogger(plog)
|
||||
)
|
||||
|
||||
// writeError logs and writes the given Error to the ResponseWriter
|
||||
// If Error is an etcdErr, it is rendered to the ResponseWriter
|
||||
// Otherwise, it is assumed to be a StatusInternalServerError
|
||||
func writeError(w http.ResponseWriter, r *http.Request, err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
switch e := err.(type) {
|
||||
case *etcdErr.Error:
|
||||
e.WriteTo(w)
|
||||
case *httptypes.HTTPError:
|
||||
if et := e.WriteTo(w); et != nil {
|
||||
plog.Debugf("error writing HTTPError (%v) to %s", et, r.RemoteAddr)
|
||||
}
|
||||
case auth.Error:
|
||||
herr := httptypes.NewHTTPError(e.HTTPStatus(), e.Error())
|
||||
if et := herr.WriteTo(w); et != nil {
|
||||
plog.Debugf("error writing HTTPError (%v) to %s", et, r.RemoteAddr)
|
||||
}
|
||||
default:
|
||||
switch err {
|
||||
case etcdserver.ErrTimeoutDueToLeaderFail, etcdserver.ErrTimeoutDueToConnectionLost, etcdserver.ErrNotEnoughStartedMembers:
|
||||
mlog.MergeError(err)
|
||||
default:
|
||||
mlog.MergeErrorf("got unexpected response error (%v)", err)
|
||||
}
|
||||
herr := httptypes.NewHTTPError(http.StatusInternalServerError, "Internal Server Error")
|
||||
if et := herr.WriteTo(w); et != nil {
|
||||
plog.Debugf("error writing HTTPError (%v) to %s", et, r.RemoteAddr)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// allowMethod verifies that the given method is one of the allowed methods,
|
||||
// and if not, it writes an error to w. A boolean is returned indicating
|
||||
// whether or not the method is allowed.
|
||||
func allowMethod(w http.ResponseWriter, m string, ms ...string) bool {
|
||||
for _, meth := range ms {
|
||||
if m == meth {
|
||||
return true
|
||||
}
|
||||
}
|
||||
w.Header().Set("Allow", strings.Join(ms, ","))
|
||||
http.Error(w, "Method Not Allowed", http.StatusMethodNotAllowed)
|
||||
return false
|
||||
}
|
||||
|
||||
func requestLogger(handler http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
plog.Debugf("[%s] %s remote:%s", r.Method, r.RequestURI, r.RemoteAddr)
|
||||
handler.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
56
vendor/github.com/coreos/etcd/etcdserver/api/v2http/httptypes/errors.go
generated
vendored
Normal file
56
vendor/github.com/coreos/etcd/etcdserver/api/v2http/httptypes/errors.go
generated
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 httptypes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
)
|
||||
|
||||
var (
|
||||
plog = capnslog.NewPackageLogger("github.com/coreos/etcd/etcdserver/api/v2http", "httptypes")
|
||||
)
|
||||
|
||||
type HTTPError struct {
|
||||
Message string `json:"message"`
|
||||
// Code is the HTTP status code
|
||||
Code int `json:"-"`
|
||||
}
|
||||
|
||||
func (e HTTPError) Error() string {
|
||||
return e.Message
|
||||
}
|
||||
|
||||
func (e HTTPError) WriteTo(w http.ResponseWriter) error {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(e.Code)
|
||||
b, err := json.Marshal(e)
|
||||
if err != nil {
|
||||
plog.Panicf("marshal HTTPError should never fail (%v)", err)
|
||||
}
|
||||
if _, err := w.Write(b); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewHTTPError(code int, m string) *HTTPError {
|
||||
return &HTTPError{
|
||||
Message: m,
|
||||
Code: code,
|
||||
}
|
||||
}
|
||||
69
vendor/github.com/coreos/etcd/etcdserver/api/v2http/httptypes/member.go
generated
vendored
Normal file
69
vendor/github.com/coreos/etcd/etcdserver/api/v2http/httptypes/member.go
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 httptypes defines how etcd's HTTP API entities are serialized to and
|
||||
// deserialized from JSON.
|
||||
package httptypes
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
)
|
||||
|
||||
type Member struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
PeerURLs []string `json:"peerURLs"`
|
||||
ClientURLs []string `json:"clientURLs"`
|
||||
}
|
||||
|
||||
type MemberCreateRequest struct {
|
||||
PeerURLs types.URLs
|
||||
}
|
||||
|
||||
type MemberUpdateRequest struct {
|
||||
MemberCreateRequest
|
||||
}
|
||||
|
||||
func (m *MemberCreateRequest) UnmarshalJSON(data []byte) error {
|
||||
s := struct {
|
||||
PeerURLs []string `json:"peerURLs"`
|
||||
}{}
|
||||
|
||||
err := json.Unmarshal(data, &s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
urls, err := types.NewURLs(s.PeerURLs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
m.PeerURLs = urls
|
||||
return nil
|
||||
}
|
||||
|
||||
type MemberCollection []Member
|
||||
|
||||
func (c *MemberCollection) MarshalJSON() ([]byte, error) {
|
||||
d := struct {
|
||||
Members []Member `json:"members"`
|
||||
}{
|
||||
Members: []Member(*c),
|
||||
}
|
||||
|
||||
return json.Marshal(d)
|
||||
}
|
||||
96
vendor/github.com/coreos/etcd/etcdserver/api/v2http/metrics.go
generated
vendored
Normal file
96
vendor/github.com/coreos/etcd/etcdserver/api/v2http/metrics.go
generated
vendored
Normal file
@@ -0,0 +1,96 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 v2http
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"net/http"
|
||||
|
||||
etcdErr "github.com/coreos/etcd/error"
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/api/v2http/httptypes"
|
||||
"github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
incomingEvents = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: "etcd",
|
||||
Subsystem: "http",
|
||||
Name: "received_total",
|
||||
Help: "Counter of requests received into the system (successfully parsed and authd).",
|
||||
}, []string{"method"})
|
||||
|
||||
failedEvents = prometheus.NewCounterVec(
|
||||
prometheus.CounterOpts{
|
||||
Namespace: "etcd",
|
||||
Subsystem: "http",
|
||||
Name: "failed_total",
|
||||
Help: "Counter of handle failures of requests (non-watches), by method (GET/PUT etc.) and code (400, 500 etc.).",
|
||||
}, []string{"method", "code"})
|
||||
|
||||
successfulEventsHandlingTime = prometheus.NewHistogramVec(
|
||||
prometheus.HistogramOpts{
|
||||
Namespace: "etcd",
|
||||
Subsystem: "http",
|
||||
Name: "successful_duration_second",
|
||||
Help: "Bucketed histogram of processing time (s) of successfully handled requests (non-watches), by method (GET/PUT etc.).",
|
||||
Buckets: prometheus.ExponentialBuckets(0.0005, 2, 13),
|
||||
}, []string{"method"})
|
||||
)
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(incomingEvents)
|
||||
prometheus.MustRegister(failedEvents)
|
||||
prometheus.MustRegister(successfulEventsHandlingTime)
|
||||
}
|
||||
|
||||
func reportRequestReceived(request etcdserverpb.Request) {
|
||||
incomingEvents.WithLabelValues(methodFromRequest(request)).Inc()
|
||||
}
|
||||
|
||||
func reportRequestCompleted(request etcdserverpb.Request, response etcdserver.Response, startTime time.Time) {
|
||||
method := methodFromRequest(request)
|
||||
successfulEventsHandlingTime.WithLabelValues(method).Observe(time.Since(startTime).Seconds())
|
||||
}
|
||||
|
||||
func reportRequestFailed(request etcdserverpb.Request, err error) {
|
||||
method := methodFromRequest(request)
|
||||
failedEvents.WithLabelValues(method, strconv.Itoa(codeFromError(err))).Inc()
|
||||
}
|
||||
|
||||
func methodFromRequest(request etcdserverpb.Request) string {
|
||||
if request.Method == "GET" && request.Quorum {
|
||||
return "QGET"
|
||||
}
|
||||
return request.Method
|
||||
}
|
||||
|
||||
func codeFromError(err error) int {
|
||||
if err == nil {
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
switch e := err.(type) {
|
||||
case *etcdErr.Error:
|
||||
return (*etcdErr.Error)(e).StatusCode()
|
||||
case *httptypes.HTTPError:
|
||||
return (*httptypes.HTTPError)(e).Code
|
||||
default:
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
}
|
||||
77
vendor/github.com/coreos/etcd/etcdserver/api/v2http/peer.go
generated
vendored
Normal file
77
vendor/github.com/coreos/etcd/etcdserver/api/v2http/peer.go
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 v2http
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/api"
|
||||
"github.com/coreos/etcd/lease/leasehttp"
|
||||
"github.com/coreos/etcd/rafthttp"
|
||||
)
|
||||
|
||||
const (
|
||||
peerMembersPrefix = "/members"
|
||||
leasesPrefix = "/leases"
|
||||
)
|
||||
|
||||
// NewPeerHandler generates an http.Handler to handle etcd peer requests.
|
||||
func NewPeerHandler(s *etcdserver.EtcdServer) http.Handler {
|
||||
var lh http.Handler
|
||||
if l := s.Lessor(); l != nil {
|
||||
lh = leasehttp.NewHandler(l)
|
||||
}
|
||||
return newPeerHandler(s.Cluster(), s.RaftHandler(), lh)
|
||||
}
|
||||
|
||||
func newPeerHandler(cluster api.Cluster, raftHandler http.Handler, leaseHandler http.Handler) http.Handler {
|
||||
mh := &peerMembersHandler{
|
||||
cluster: cluster,
|
||||
}
|
||||
|
||||
mux := http.NewServeMux()
|
||||
mux.HandleFunc("/", http.NotFound)
|
||||
mux.Handle(rafthttp.RaftPrefix, raftHandler)
|
||||
mux.Handle(rafthttp.RaftPrefix+"/", raftHandler)
|
||||
mux.Handle(peerMembersPrefix, mh)
|
||||
if leaseHandler != nil {
|
||||
mux.Handle(leasesPrefix, leaseHandler)
|
||||
}
|
||||
mux.HandleFunc(versionPath, versionHandler(cluster, serveVersion))
|
||||
return mux
|
||||
}
|
||||
|
||||
type peerMembersHandler struct {
|
||||
cluster api.Cluster
|
||||
}
|
||||
|
||||
func (h *peerMembersHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
if !allowMethod(w, r.Method, "GET") {
|
||||
return
|
||||
}
|
||||
w.Header().Set("X-Etcd-Cluster-ID", h.cluster.ID().String())
|
||||
|
||||
if r.URL.Path != peerMembersPrefix {
|
||||
http.Error(w, "bad path", http.StatusBadRequest)
|
||||
return
|
||||
}
|
||||
ms := h.cluster.Members()
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(ms); err != nil {
|
||||
plog.Warningf("failed to encode members response (%v)", err)
|
||||
}
|
||||
}
|
||||
114
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/auth.go
generated
vendored
Normal file
114
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/auth.go
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright 2016 Nippon Telegraph and Telephone Corporation.
|
||||
//
|
||||
// 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 v3rpc
|
||||
|
||||
import (
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type AuthServer struct {
|
||||
authenticator etcdserver.Authenticator
|
||||
}
|
||||
|
||||
func NewAuthServer(s *etcdserver.EtcdServer) *AuthServer {
|
||||
return &AuthServer{authenticator: s}
|
||||
}
|
||||
|
||||
func (as *AuthServer) AuthEnable(ctx context.Context, r *pb.AuthEnableRequest) (*pb.AuthEnableResponse, error) {
|
||||
resp, err := as.authenticator.AuthEnable(ctx, r)
|
||||
if err != nil {
|
||||
return nil, togRPCError(err)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) AuthDisable(ctx context.Context, r *pb.AuthDisableRequest) (*pb.AuthDisableResponse, error) {
|
||||
plog.Info("not implemented yet")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) Authenticate(ctx context.Context, r *pb.AuthenticateRequest) (*pb.AuthenticateResponse, error) {
|
||||
plog.Info("not implemented yet")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) RoleAdd(ctx context.Context, r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) {
|
||||
resp, err := as.authenticator.RoleAdd(ctx, r)
|
||||
if err != nil {
|
||||
return nil, togRPCError(err)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) RoleDelete(ctx context.Context, r *pb.AuthRoleDeleteRequest) (*pb.AuthRoleDeleteResponse, error) {
|
||||
plog.Info("not implemented yet")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) RoleGet(ctx context.Context, r *pb.AuthRoleGetRequest) (*pb.AuthRoleGetResponse, error) {
|
||||
plog.Info("not implemented yet")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) RoleRevoke(ctx context.Context, r *pb.AuthRoleRevokeRequest) (*pb.AuthRoleRevokeResponse, error) {
|
||||
plog.Info("not implemented yet")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) RoleGrant(ctx context.Context, r *pb.AuthRoleGrantRequest) (*pb.AuthRoleGrantResponse, error) {
|
||||
plog.Info("not implemented yet")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) UserAdd(ctx context.Context, r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error) {
|
||||
resp, err := as.authenticator.UserAdd(ctx, r)
|
||||
if err != nil {
|
||||
return nil, togRPCError(err)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) UserDelete(ctx context.Context, r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error) {
|
||||
resp, err := as.authenticator.UserDelete(ctx, r)
|
||||
if err != nil {
|
||||
return nil, togRPCError(err)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) UserGet(ctx context.Context, r *pb.AuthUserGetRequest) (*pb.AuthUserGetResponse, error) {
|
||||
plog.Info("not implemented yet")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) UserGrant(ctx context.Context, r *pb.AuthUserGrantRequest) (*pb.AuthUserGrantResponse, error) {
|
||||
plog.Info("not implemented yet")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) UserRevoke(ctx context.Context, r *pb.AuthUserRevokeRequest) (*pb.AuthUserRevokeResponse, error) {
|
||||
plog.Info("not implemented yet")
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (as *AuthServer) UserChangePassword(ctx context.Context, r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error) {
|
||||
resp, err := as.authenticator.UserChangePassword(ctx, r)
|
||||
if err != nil {
|
||||
return nil, togRPCError(err)
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
40
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/grpc.go
generated
vendored
Normal file
40
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/grpc.go
generated
vendored
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 v3rpc
|
||||
|
||||
import (
|
||||
"crypto/tls"
|
||||
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/credentials"
|
||||
)
|
||||
|
||||
func Server(s *etcdserver.EtcdServer, tls *tls.Config) *grpc.Server {
|
||||
var opts []grpc.ServerOption
|
||||
if tls != nil {
|
||||
opts = append(opts, grpc.Creds(credentials.NewTLS(tls)))
|
||||
}
|
||||
|
||||
grpcServer := grpc.NewServer(opts...)
|
||||
pb.RegisterKVServer(grpcServer, NewQuotaKVServer(s))
|
||||
pb.RegisterWatchServer(grpcServer, NewWatchServer(s))
|
||||
pb.RegisterLeaseServer(grpcServer, NewQuotaLeaseServer(s))
|
||||
pb.RegisterClusterServer(grpcServer, NewClusterServer(s))
|
||||
pb.RegisterAuthServer(grpcServer, NewAuthServer(s))
|
||||
pb.RegisterMaintenanceServer(grpcServer, NewMaintenanceServer(s))
|
||||
return grpcServer
|
||||
}
|
||||
43
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/header.go
generated
vendored
Normal file
43
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/header.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 v3rpc
|
||||
|
||||
import (
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
)
|
||||
|
||||
type header struct {
|
||||
clusterID int64
|
||||
memberID int64
|
||||
raftTimer etcdserver.RaftTimer
|
||||
rev func() int64
|
||||
}
|
||||
|
||||
func newHeader(s *etcdserver.EtcdServer) header {
|
||||
return header{
|
||||
clusterID: int64(s.Cluster().ID()),
|
||||
memberID: int64(s.ID()),
|
||||
raftTimer: s,
|
||||
rev: func() int64 { return s.KV().Rev() },
|
||||
}
|
||||
}
|
||||
|
||||
// fill populates pb.ResponseHeader using etcdserver information
|
||||
func (h *header) fill(rh *pb.ResponseHeader) {
|
||||
rh.ClusterId = uint64(h.clusterID)
|
||||
rh.MemberId = uint64(h.memberID)
|
||||
rh.RaftTerm = h.raftTimer.Term()
|
||||
}
|
||||
259
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/key.go
generated
vendored
Normal file
259
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/key.go
generated
vendored
Normal file
@@ -0,0 +1,259 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 v3rpc implements etcd v3 RPC system based on gRPC.
|
||||
package v3rpc
|
||||
|
||||
import (
|
||||
"sort"
|
||||
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
var (
|
||||
plog = capnslog.NewPackageLogger("github.com/coreos/etcd/etcdserver/api", "v3rpc")
|
||||
|
||||
// Max operations per txn list. For example, Txn.Success can have at most 128 operations,
|
||||
// and Txn.Failure can have at most 128 operations.
|
||||
MaxOpsPerTxn = 128
|
||||
)
|
||||
|
||||
type kvServer struct {
|
||||
hdr header
|
||||
kv etcdserver.RaftKV
|
||||
}
|
||||
|
||||
func NewKVServer(s *etcdserver.EtcdServer) pb.KVServer {
|
||||
return &kvServer{hdr: newHeader(s), kv: s}
|
||||
}
|
||||
|
||||
func (s *kvServer) Range(ctx context.Context, r *pb.RangeRequest) (*pb.RangeResponse, error) {
|
||||
if err := checkRangeRequest(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.kv.Range(ctx, r)
|
||||
if err != nil {
|
||||
return nil, togRPCError(err)
|
||||
}
|
||||
|
||||
if resp.Header == nil {
|
||||
plog.Panic("unexpected nil resp.Header")
|
||||
}
|
||||
s.hdr.fill(resp.Header)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (s *kvServer) Put(ctx context.Context, r *pb.PutRequest) (*pb.PutResponse, error) {
|
||||
if err := checkPutRequest(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.kv.Put(ctx, r)
|
||||
if err != nil {
|
||||
return nil, togRPCError(err)
|
||||
}
|
||||
|
||||
if resp.Header == nil {
|
||||
plog.Panic("unexpected nil resp.Header")
|
||||
}
|
||||
s.hdr.fill(resp.Header)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (s *kvServer) DeleteRange(ctx context.Context, r *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error) {
|
||||
if err := checkDeleteRequest(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.kv.DeleteRange(ctx, r)
|
||||
if err != nil {
|
||||
return nil, togRPCError(err)
|
||||
}
|
||||
|
||||
if resp.Header == nil {
|
||||
plog.Panic("unexpected nil resp.Header")
|
||||
}
|
||||
s.hdr.fill(resp.Header)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (s *kvServer) Txn(ctx context.Context, r *pb.TxnRequest) (*pb.TxnResponse, error) {
|
||||
if err := checkTxnRequest(r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
resp, err := s.kv.Txn(ctx, r)
|
||||
if err != nil {
|
||||
return nil, togRPCError(err)
|
||||
}
|
||||
|
||||
if resp.Header == nil {
|
||||
plog.Panic("unexpected nil resp.Header")
|
||||
}
|
||||
s.hdr.fill(resp.Header)
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (s *kvServer) Compact(ctx context.Context, r *pb.CompactionRequest) (*pb.CompactionResponse, error) {
|
||||
resp, err := s.kv.Compact(ctx, r)
|
||||
if err != nil {
|
||||
return nil, togRPCError(err)
|
||||
}
|
||||
|
||||
if resp.Header == nil {
|
||||
plog.Panic("unexpected nil resp.Header")
|
||||
}
|
||||
s.hdr.fill(resp.Header)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func checkRangeRequest(r *pb.RangeRequest) error {
|
||||
if len(r.Key) == 0 {
|
||||
return rpctypes.ErrEmptyKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkPutRequest(r *pb.PutRequest) error {
|
||||
if len(r.Key) == 0 {
|
||||
return rpctypes.ErrEmptyKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkDeleteRequest(r *pb.DeleteRangeRequest) error {
|
||||
if len(r.Key) == 0 {
|
||||
return rpctypes.ErrEmptyKey
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkTxnRequest(r *pb.TxnRequest) error {
|
||||
if len(r.Compare) > MaxOpsPerTxn || len(r.Success) > MaxOpsPerTxn || len(r.Failure) > MaxOpsPerTxn {
|
||||
return rpctypes.ErrTooManyOps
|
||||
}
|
||||
|
||||
for _, c := range r.Compare {
|
||||
if len(c.Key) == 0 {
|
||||
return rpctypes.ErrEmptyKey
|
||||
}
|
||||
}
|
||||
|
||||
for _, u := range r.Success {
|
||||
if err := checkRequestUnion(u); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := checkRequestDupKeys(r.Success); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, u := range r.Failure {
|
||||
if err := checkRequestUnion(u); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := checkRequestDupKeys(r.Failure); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// checkRequestDupKeys gives rpctypes.ErrDuplicateKey if the same key is modified twice
|
||||
func checkRequestDupKeys(reqs []*pb.RequestUnion) error {
|
||||
// check put overlap
|
||||
keys := make(map[string]struct{})
|
||||
for _, requ := range reqs {
|
||||
tv, ok := requ.Request.(*pb.RequestUnion_RequestPut)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
preq := tv.RequestPut
|
||||
if preq == nil {
|
||||
continue
|
||||
}
|
||||
key := string(preq.Key)
|
||||
if _, ok := keys[key]; ok {
|
||||
return rpctypes.ErrDuplicateKey
|
||||
}
|
||||
keys[key] = struct{}{}
|
||||
}
|
||||
|
||||
// no need to check deletes if no puts; delete overlaps are permitted
|
||||
if len(keys) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// sort keys for range checking
|
||||
sortedKeys := []string{}
|
||||
for k := range keys {
|
||||
sortedKeys = append(sortedKeys, k)
|
||||
}
|
||||
sort.Strings(sortedKeys)
|
||||
|
||||
// check put overlap with deletes
|
||||
for _, requ := range reqs {
|
||||
tv, ok := requ.Request.(*pb.RequestUnion_RequestDeleteRange)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
dreq := tv.RequestDeleteRange
|
||||
if dreq == nil {
|
||||
continue
|
||||
}
|
||||
key := string(dreq.Key)
|
||||
if dreq.RangeEnd == nil {
|
||||
if _, found := keys[key]; found {
|
||||
return rpctypes.ErrDuplicateKey
|
||||
}
|
||||
} else {
|
||||
lo := sort.SearchStrings(sortedKeys, key)
|
||||
hi := sort.SearchStrings(sortedKeys, string(dreq.RangeEnd))
|
||||
if lo != hi {
|
||||
// element between lo and hi => overlap
|
||||
return rpctypes.ErrDuplicateKey
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func checkRequestUnion(u *pb.RequestUnion) error {
|
||||
// TODO: ensure only one of the field is set.
|
||||
switch uv := u.Request.(type) {
|
||||
case *pb.RequestUnion_RequestRange:
|
||||
if uv.RequestRange != nil {
|
||||
return checkRangeRequest(uv.RequestRange)
|
||||
}
|
||||
case *pb.RequestUnion_RequestPut:
|
||||
if uv.RequestPut != nil {
|
||||
return checkPutRequest(uv.RequestPut)
|
||||
}
|
||||
case *pb.RequestUnion_RequestDeleteRange:
|
||||
if uv.RequestDeleteRange != nil {
|
||||
return checkDeleteRequest(uv.RequestDeleteRange)
|
||||
}
|
||||
default:
|
||||
// empty union
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
}
|
||||
76
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/lease.go
generated
vendored
Normal file
76
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/lease.go
generated
vendored
Normal file
@@ -0,0 +1,76 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 v3rpc
|
||||
|
||||
import (
|
||||
"io"
|
||||
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/lease"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type LeaseServer struct {
|
||||
le etcdserver.Lessor
|
||||
}
|
||||
|
||||
func NewLeaseServer(le etcdserver.Lessor) pb.LeaseServer {
|
||||
return &LeaseServer{le: le}
|
||||
}
|
||||
|
||||
func (ls *LeaseServer) LeaseGrant(ctx context.Context, cr *pb.LeaseGrantRequest) (*pb.LeaseGrantResponse, error) {
|
||||
resp, err := ls.le.LeaseGrant(ctx, cr)
|
||||
if err == lease.ErrLeaseExists {
|
||||
return nil, rpctypes.ErrLeaseExist
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (ls *LeaseServer) LeaseRevoke(ctx context.Context, rr *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error) {
|
||||
r, err := ls.le.LeaseRevoke(ctx, rr)
|
||||
if err != nil {
|
||||
return nil, rpctypes.ErrLeaseNotFound
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (ls *LeaseServer) LeaseKeepAlive(stream pb.Lease_LeaseKeepAliveServer) error {
|
||||
for {
|
||||
req, err := stream.Recv()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ttl, err := ls.le.LeaseRenew(lease.LeaseID(req.ID))
|
||||
if err == lease.ErrLeaseNotFound {
|
||||
return rpctypes.ErrLeaseNotFound
|
||||
}
|
||||
|
||||
if err != nil && err != lease.ErrLeaseNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
resp := &pb.LeaseKeepAliveResponse{ID: req.ID, TTL: ttl}
|
||||
err = stream.Send(resp)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
72
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/maintenance.go
generated
vendored
Normal file
72
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/maintenance.go
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 v3rpc
|
||||
|
||||
import (
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/storage/backend"
|
||||
"github.com/coreos/etcd/version"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type BackendGetter interface {
|
||||
Backend() backend.Backend
|
||||
}
|
||||
|
||||
type Alarmer interface {
|
||||
Alarm(ctx context.Context, ar *pb.AlarmRequest) (*pb.AlarmResponse, error)
|
||||
}
|
||||
|
||||
type maintenanceServer struct {
|
||||
bg BackendGetter
|
||||
a Alarmer
|
||||
hdr header
|
||||
}
|
||||
|
||||
func NewMaintenanceServer(s *etcdserver.EtcdServer) pb.MaintenanceServer {
|
||||
return &maintenanceServer{bg: s, a: s, hdr: newHeader(s)}
|
||||
}
|
||||
|
||||
func (ms *maintenanceServer) Defragment(ctx context.Context, sr *pb.DefragmentRequest) (*pb.DefragmentResponse, error) {
|
||||
plog.Noticef("starting to defragment the storage backend...")
|
||||
err := ms.bg.Backend().Defrag()
|
||||
if err != nil {
|
||||
plog.Errorf("failed to deframent the storage backend (%v)", err)
|
||||
return nil, err
|
||||
}
|
||||
plog.Noticef("finished defragmenting the storage backend")
|
||||
return &pb.DefragmentResponse{}, nil
|
||||
}
|
||||
|
||||
func (ms *maintenanceServer) Hash(ctx context.Context, r *pb.HashRequest) (*pb.HashResponse, error) {
|
||||
h, err := ms.bg.Backend().Hash()
|
||||
if err != nil {
|
||||
return nil, togRPCError(err)
|
||||
}
|
||||
resp := &pb.HashResponse{Header: &pb.ResponseHeader{Revision: ms.hdr.rev()}, Hash: h}
|
||||
ms.hdr.fill(resp.Header)
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (ms *maintenanceServer) Alarm(ctx context.Context, ar *pb.AlarmRequest) (*pb.AlarmResponse, error) {
|
||||
return ms.a.Alarm(ctx, ar)
|
||||
}
|
||||
|
||||
func (ms *maintenanceServer) Status(ctx context.Context, ar *pb.StatusRequest) (*pb.StatusResponse, error) {
|
||||
resp := &pb.StatusResponse{Header: &pb.ResponseHeader{Revision: ms.hdr.rev()}, Version: version.Version}
|
||||
ms.hdr.fill(resp.Header)
|
||||
return resp, nil
|
||||
}
|
||||
120
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/member.go
generated
vendored
Normal file
120
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/member.go
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 v3rpc
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/api"
|
||||
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/etcdserver/membership"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
type ClusterServer struct {
|
||||
cluster api.Cluster
|
||||
server etcdserver.Server
|
||||
raftTimer etcdserver.RaftTimer
|
||||
}
|
||||
|
||||
func NewClusterServer(s *etcdserver.EtcdServer) *ClusterServer {
|
||||
return &ClusterServer{
|
||||
cluster: s.Cluster(),
|
||||
server: s,
|
||||
raftTimer: s,
|
||||
}
|
||||
}
|
||||
|
||||
func (cs *ClusterServer) MemberAdd(ctx context.Context, r *pb.MemberAddRequest) (*pb.MemberAddResponse, error) {
|
||||
urls, err := types.NewURLs(r.PeerURLs)
|
||||
if err != nil {
|
||||
return nil, rpctypes.ErrMemberBadURLs
|
||||
}
|
||||
|
||||
now := time.Now()
|
||||
m := membership.NewMember("", urls, "", &now)
|
||||
err = cs.server.AddMember(ctx, *m)
|
||||
switch {
|
||||
case err == membership.ErrIDExists:
|
||||
return nil, rpctypes.ErrMemberExist
|
||||
case err == membership.ErrPeerURLexists:
|
||||
return nil, rpctypes.ErrPeerURLExist
|
||||
case err != nil:
|
||||
return nil, grpc.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
|
||||
return &pb.MemberAddResponse{
|
||||
Header: cs.header(),
|
||||
Member: &pb.Member{ID: uint64(m.ID), IsLeader: m.ID == cs.server.Leader(), PeerURLs: m.PeerURLs},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (cs *ClusterServer) MemberRemove(ctx context.Context, r *pb.MemberRemoveRequest) (*pb.MemberRemoveResponse, error) {
|
||||
err := cs.server.RemoveMember(ctx, r.ID)
|
||||
switch {
|
||||
case err == membership.ErrIDRemoved:
|
||||
fallthrough
|
||||
case err == membership.ErrIDNotFound:
|
||||
return nil, rpctypes.ErrMemberNotFound
|
||||
case err != nil:
|
||||
return nil, grpc.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
|
||||
return &pb.MemberRemoveResponse{Header: cs.header()}, nil
|
||||
}
|
||||
|
||||
func (cs *ClusterServer) MemberUpdate(ctx context.Context, r *pb.MemberUpdateRequest) (*pb.MemberUpdateResponse, error) {
|
||||
m := membership.Member{
|
||||
ID: types.ID(r.ID),
|
||||
RaftAttributes: membership.RaftAttributes{PeerURLs: r.PeerURLs},
|
||||
}
|
||||
err := cs.server.UpdateMember(ctx, m)
|
||||
switch {
|
||||
case err == membership.ErrPeerURLexists:
|
||||
return nil, rpctypes.ErrPeerURLExist
|
||||
case err == membership.ErrIDNotFound:
|
||||
return nil, rpctypes.ErrMemberNotFound
|
||||
case err != nil:
|
||||
return nil, grpc.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
|
||||
return &pb.MemberUpdateResponse{Header: cs.header()}, nil
|
||||
}
|
||||
|
||||
func (cs *ClusterServer) MemberList(ctx context.Context, r *pb.MemberListRequest) (*pb.MemberListResponse, error) {
|
||||
membs := cs.cluster.Members()
|
||||
|
||||
protoMembs := make([]*pb.Member, len(membs))
|
||||
for i := range membs {
|
||||
protoMembs[i] = &pb.Member{
|
||||
Name: membs[i].Name,
|
||||
ID: uint64(membs[i].ID),
|
||||
IsLeader: membs[i].ID == cs.server.Leader(),
|
||||
PeerURLs: membs[i].PeerURLs,
|
||||
ClientURLs: membs[i].ClientURLs,
|
||||
}
|
||||
}
|
||||
|
||||
return &pb.MemberListResponse{Header: cs.header(), Members: protoMembs}, nil
|
||||
}
|
||||
|
||||
func (cs *ClusterServer) header() *pb.ResponseHeader {
|
||||
return &pb.ResponseHeader{ClusterId: uint64(cs.cluster.ID()), MemberId: uint64(cs.server.ID()), RaftTerm: cs.raftTimer.Term()}
|
||||
}
|
||||
89
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/quota.go
generated
vendored
Normal file
89
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/quota.go
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 v3rpc
|
||||
|
||||
import (
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type quotaKVServer struct {
|
||||
pb.KVServer
|
||||
qa quotaAlarmer
|
||||
}
|
||||
|
||||
type quotaAlarmer struct {
|
||||
q etcdserver.Quota
|
||||
a Alarmer
|
||||
id types.ID
|
||||
}
|
||||
|
||||
// check whether request satisfies the quota. If there is not enough space,
|
||||
// ignore request and raise the free space alarm.
|
||||
func (qa *quotaAlarmer) check(ctx context.Context, r interface{}) error {
|
||||
if qa.q.Available(r) {
|
||||
return nil
|
||||
}
|
||||
req := &pb.AlarmRequest{
|
||||
MemberID: uint64(qa.id),
|
||||
Action: pb.AlarmRequest_ACTIVATE,
|
||||
Alarm: pb.AlarmType_NOSPACE,
|
||||
}
|
||||
qa.a.Alarm(ctx, req)
|
||||
return rpctypes.ErrNoSpace
|
||||
}
|
||||
|
||||
func NewQuotaKVServer(s *etcdserver.EtcdServer) pb.KVServer {
|
||||
return "aKVServer{
|
||||
NewKVServer(s),
|
||||
quotaAlarmer{etcdserver.NewBackendQuota(s), s, s.ID()},
|
||||
}
|
||||
}
|
||||
|
||||
func (s *quotaKVServer) Put(ctx context.Context, r *pb.PutRequest) (*pb.PutResponse, error) {
|
||||
if err := s.qa.check(ctx, r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.KVServer.Put(ctx, r)
|
||||
}
|
||||
|
||||
func (s *quotaKVServer) Txn(ctx context.Context, r *pb.TxnRequest) (*pb.TxnResponse, error) {
|
||||
if err := s.qa.check(ctx, r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.KVServer.Txn(ctx, r)
|
||||
}
|
||||
|
||||
type quotaLeaseServer struct {
|
||||
pb.LeaseServer
|
||||
qa quotaAlarmer
|
||||
}
|
||||
|
||||
func (s *quotaLeaseServer) LeaseGrant(ctx context.Context, cr *pb.LeaseGrantRequest) (*pb.LeaseGrantResponse, error) {
|
||||
if err := s.qa.check(ctx, cr); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return s.LeaseServer.LeaseGrant(ctx, cr)
|
||||
}
|
||||
|
||||
func NewQuotaLeaseServer(s *etcdserver.EtcdServer) pb.LeaseServer {
|
||||
return "aLeaseServer{
|
||||
NewLeaseServer(s),
|
||||
quotaAlarmer{etcdserver.NewBackendQuota(s), s, s.ID()},
|
||||
}
|
||||
}
|
||||
43
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes/error.go
generated
vendored
Normal file
43
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes/error.go
generated
vendored
Normal file
@@ -0,0 +1,43 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 rpctypes
|
||||
|
||||
import (
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrEmptyKey = grpc.Errorf(codes.InvalidArgument, "etcdserver: key is not provided")
|
||||
ErrTooManyOps = grpc.Errorf(codes.InvalidArgument, "etcdserver: too many operations in txn request")
|
||||
ErrDuplicateKey = grpc.Errorf(codes.InvalidArgument, "etcdserver: duplicate key given in txn request")
|
||||
ErrCompacted = grpc.Errorf(codes.OutOfRange, "etcdserver: storage: required revision has been compacted")
|
||||
ErrFutureRev = grpc.Errorf(codes.OutOfRange, "etcdserver: storage: required revision is a future revision")
|
||||
ErrNoSpace = grpc.Errorf(codes.ResourceExhausted, "etcdserver: storage: database space exceeded")
|
||||
|
||||
ErrLeaseNotFound = grpc.Errorf(codes.NotFound, "etcdserver: requested lease not found")
|
||||
ErrLeaseExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: lease already exists")
|
||||
|
||||
ErrMemberExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: member ID already exist")
|
||||
ErrPeerURLExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: Peer URLs already exists")
|
||||
ErrMemberBadURLs = grpc.Errorf(codes.InvalidArgument, "etcdserver: given member URLs are invalid")
|
||||
ErrMemberNotFound = grpc.Errorf(codes.NotFound, "etcdserver: member not found")
|
||||
|
||||
ErrRequestTooLarge = grpc.Errorf(codes.InvalidArgument, "etcdserver: request is too large")
|
||||
|
||||
ErrUserAlreadyExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: user name already exists")
|
||||
ErrUserNotFound = grpc.Errorf(codes.FailedPrecondition, "etcdserver: user name not found")
|
||||
ErrRoleAlreadyExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: role name already exists")
|
||||
)
|
||||
49
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/util.go
generated
vendored
Normal file
49
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/util.go
generated
vendored
Normal file
@@ -0,0 +1,49 @@
|
||||
// Copyright 2016 Nippon Telegraph and Telephone Corporation.
|
||||
//
|
||||
// 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 v3rpc
|
||||
|
||||
import (
|
||||
"github.com/coreos/etcd/auth"
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes"
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/storage"
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
)
|
||||
|
||||
func togRPCError(err error) error {
|
||||
switch err {
|
||||
case storage.ErrCompacted:
|
||||
return rpctypes.ErrCompacted
|
||||
case storage.ErrFutureRev:
|
||||
return rpctypes.ErrFutureRev
|
||||
case lease.ErrLeaseNotFound:
|
||||
return rpctypes.ErrLeaseNotFound
|
||||
// TODO: handle error from raft and timeout
|
||||
case etcdserver.ErrRequestTooLarge:
|
||||
return rpctypes.ErrRequestTooLarge
|
||||
case etcdserver.ErrNoSpace:
|
||||
return rpctypes.ErrNoSpace
|
||||
case auth.ErrUserAlreadyExist:
|
||||
return rpctypes.ErrUserAlreadyExist
|
||||
case auth.ErrUserNotFound:
|
||||
return rpctypes.ErrUserNotFound
|
||||
case auth.ErrRoleAlreadyExist:
|
||||
return rpctypes.ErrRoleAlreadyExist
|
||||
default:
|
||||
return grpc.Errorf(codes.Internal, err.Error())
|
||||
}
|
||||
}
|
||||
287
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/watch.go
generated
vendored
Normal file
287
vendor/github.com/coreos/etcd/etcdserver/api/v3rpc/watch.go
generated
vendored
Normal file
@@ -0,0 +1,287 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 v3rpc
|
||||
|
||||
import (
|
||||
"io"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/storage"
|
||||
"github.com/coreos/etcd/storage/storagepb"
|
||||
)
|
||||
|
||||
type watchServer struct {
|
||||
clusterID int64
|
||||
memberID int64
|
||||
raftTimer etcdserver.RaftTimer
|
||||
watchable storage.Watchable
|
||||
}
|
||||
|
||||
func NewWatchServer(s *etcdserver.EtcdServer) pb.WatchServer {
|
||||
return &watchServer{
|
||||
clusterID: int64(s.Cluster().ID()),
|
||||
memberID: int64(s.ID()),
|
||||
raftTimer: s,
|
||||
watchable: s.Watchable(),
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// External test can read this with GetProgressReportInterval()
|
||||
// and change this to a small value to finish fast with
|
||||
// SetProgressReportInterval().
|
||||
progressReportInterval = 10 * time.Minute
|
||||
progressReportIntervalMu sync.RWMutex
|
||||
)
|
||||
|
||||
func GetProgressReportInterval() time.Duration {
|
||||
progressReportIntervalMu.RLock()
|
||||
defer progressReportIntervalMu.RUnlock()
|
||||
return progressReportInterval
|
||||
}
|
||||
|
||||
func SetProgressReportInterval(newTimeout time.Duration) {
|
||||
progressReportIntervalMu.Lock()
|
||||
defer progressReportIntervalMu.Unlock()
|
||||
progressReportInterval = newTimeout
|
||||
}
|
||||
|
||||
const (
|
||||
// We send ctrl response inside the read loop. We do not want
|
||||
// send to block read, but we still want ctrl response we sent to
|
||||
// be serialized. Thus we use a buffered chan to solve the problem.
|
||||
// A small buffer should be OK for most cases, since we expect the
|
||||
// ctrl requests are infrequent.
|
||||
ctrlStreamBufLen = 16
|
||||
)
|
||||
|
||||
// serverWatchStream is an etcd server side stream. It receives requests
|
||||
// from client side gRPC stream. It receives watch events from storage.WatchStream,
|
||||
// and creates responses that forwarded to gRPC stream.
|
||||
// It also forwards control message like watch created and canceled.
|
||||
type serverWatchStream struct {
|
||||
clusterID int64
|
||||
memberID int64
|
||||
raftTimer etcdserver.RaftTimer
|
||||
|
||||
gRPCStream pb.Watch_WatchServer
|
||||
watchStream storage.WatchStream
|
||||
ctrlStream chan *pb.WatchResponse
|
||||
|
||||
// progress tracks the watchID that stream might need to send
|
||||
// progress to.
|
||||
progress map[storage.WatchID]bool
|
||||
// mu protects progress
|
||||
mu sync.Mutex
|
||||
|
||||
// closec indicates the stream is closed.
|
||||
closec chan struct{}
|
||||
}
|
||||
|
||||
func (ws *watchServer) Watch(stream pb.Watch_WatchServer) error {
|
||||
sws := serverWatchStream{
|
||||
clusterID: ws.clusterID,
|
||||
memberID: ws.memberID,
|
||||
raftTimer: ws.raftTimer,
|
||||
gRPCStream: stream,
|
||||
watchStream: ws.watchable.NewWatchStream(),
|
||||
// chan for sending control response like watcher created and canceled.
|
||||
ctrlStream: make(chan *pb.WatchResponse, ctrlStreamBufLen),
|
||||
progress: make(map[storage.WatchID]bool),
|
||||
closec: make(chan struct{}),
|
||||
}
|
||||
defer sws.close()
|
||||
|
||||
go sws.sendLoop()
|
||||
return sws.recvLoop()
|
||||
}
|
||||
|
||||
func (sws *serverWatchStream) recvLoop() error {
|
||||
for {
|
||||
req, err := sws.gRPCStream.Recv()
|
||||
if err == io.EOF {
|
||||
return nil
|
||||
}
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch uv := req.RequestUnion.(type) {
|
||||
case *pb.WatchRequest_CreateRequest:
|
||||
if uv.CreateRequest == nil {
|
||||
break
|
||||
}
|
||||
|
||||
creq := uv.CreateRequest
|
||||
if len(creq.Key) == 0 {
|
||||
// \x00 is the smallest key
|
||||
creq.Key = []byte{0}
|
||||
}
|
||||
if len(creq.RangeEnd) == 1 && creq.RangeEnd[0] == 0 {
|
||||
// support >= key queries
|
||||
creq.RangeEnd = []byte{}
|
||||
}
|
||||
wsrev := sws.watchStream.Rev()
|
||||
rev := creq.StartRevision
|
||||
if rev == 0 {
|
||||
rev = wsrev + 1
|
||||
}
|
||||
id := sws.watchStream.Watch(creq.Key, creq.RangeEnd, rev)
|
||||
if id != -1 && creq.ProgressNotify {
|
||||
sws.progress[id] = true
|
||||
}
|
||||
sws.ctrlStream <- &pb.WatchResponse{
|
||||
Header: sws.newResponseHeader(wsrev),
|
||||
WatchId: int64(id),
|
||||
Created: true,
|
||||
Canceled: id == -1,
|
||||
}
|
||||
case *pb.WatchRequest_CancelRequest:
|
||||
if uv.CancelRequest != nil {
|
||||
id := uv.CancelRequest.WatchId
|
||||
err := sws.watchStream.Cancel(storage.WatchID(id))
|
||||
if err == nil {
|
||||
sws.ctrlStream <- &pb.WatchResponse{
|
||||
Header: sws.newResponseHeader(sws.watchStream.Rev()),
|
||||
WatchId: id,
|
||||
Canceled: true,
|
||||
}
|
||||
sws.mu.Lock()
|
||||
delete(sws.progress, storage.WatchID(id))
|
||||
sws.mu.Unlock()
|
||||
}
|
||||
}
|
||||
// TODO: do we need to return error back to client?
|
||||
default:
|
||||
panic("not implemented")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sws *serverWatchStream) sendLoop() {
|
||||
// watch ids that are currently active
|
||||
ids := make(map[storage.WatchID]struct{})
|
||||
// watch responses pending on a watch id creation message
|
||||
pending := make(map[storage.WatchID][]*pb.WatchResponse)
|
||||
|
||||
interval := GetProgressReportInterval()
|
||||
progressTicker := time.NewTicker(interval)
|
||||
defer progressTicker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case wresp, ok := <-sws.watchStream.Chan():
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
// TODO: evs is []storagepb.Event type
|
||||
// either return []*storagepb.Event from storage package
|
||||
// or define protocol buffer with []storagepb.Event.
|
||||
evs := wresp.Events
|
||||
events := make([]*storagepb.Event, len(evs))
|
||||
for i := range evs {
|
||||
events[i] = &evs[i]
|
||||
}
|
||||
|
||||
wr := &pb.WatchResponse{
|
||||
Header: sws.newResponseHeader(wresp.Revision),
|
||||
WatchId: int64(wresp.WatchID),
|
||||
Events: events,
|
||||
CompactRevision: wresp.CompactRevision,
|
||||
}
|
||||
|
||||
if _, hasId := ids[wresp.WatchID]; !hasId {
|
||||
// buffer if id not yet announced
|
||||
wrs := append(pending[wresp.WatchID], wr)
|
||||
pending[wresp.WatchID] = wrs
|
||||
continue
|
||||
}
|
||||
|
||||
storage.ReportEventReceived()
|
||||
if err := sws.gRPCStream.Send(wr); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
sws.mu.Lock()
|
||||
if _, ok := sws.progress[wresp.WatchID]; ok {
|
||||
sws.progress[wresp.WatchID] = false
|
||||
}
|
||||
sws.mu.Unlock()
|
||||
|
||||
case c, ok := <-sws.ctrlStream:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
|
||||
if err := sws.gRPCStream.Send(c); err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
// track id creation
|
||||
wid := storage.WatchID(c.WatchId)
|
||||
if c.Canceled {
|
||||
delete(ids, wid)
|
||||
continue
|
||||
}
|
||||
if c.Created {
|
||||
// flush buffered events
|
||||
ids[wid] = struct{}{}
|
||||
for _, v := range pending[wid] {
|
||||
storage.ReportEventReceived()
|
||||
if err := sws.gRPCStream.Send(v); err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
delete(pending, wid)
|
||||
}
|
||||
case <-progressTicker.C:
|
||||
for id, ok := range sws.progress {
|
||||
if ok {
|
||||
sws.watchStream.RequestProgress(id)
|
||||
}
|
||||
sws.progress[id] = true
|
||||
}
|
||||
case <-sws.closec:
|
||||
// drain the chan to clean up pending events
|
||||
for range sws.watchStream.Chan() {
|
||||
storage.ReportEventReceived()
|
||||
}
|
||||
for _, wrs := range pending {
|
||||
for range wrs {
|
||||
storage.ReportEventReceived()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (sws *serverWatchStream) close() {
|
||||
sws.watchStream.Close()
|
||||
close(sws.closec)
|
||||
close(sws.ctrlStream)
|
||||
}
|
||||
|
||||
func (sws *serverWatchStream) newResponseHeader(rev int64) *pb.ResponseHeader {
|
||||
return &pb.ResponseHeader{
|
||||
ClusterId: uint64(sws.clusterID),
|
||||
MemberId: uint64(sws.memberID),
|
||||
Revision: rev,
|
||||
RaftTerm: sws.raftTimer.Term(),
|
||||
}
|
||||
}
|
||||
627
vendor/github.com/coreos/etcd/etcdserver/apply.go
generated
vendored
Normal file
627
vendor/github.com/coreos/etcd/etcdserver/apply.go
generated
vendored
Normal file
@@ -0,0 +1,627 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 etcdserver
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"sort"
|
||||
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
dstorage "github.com/coreos/etcd/storage"
|
||||
"github.com/coreos/etcd/storage/storagepb"
|
||||
"github.com/gogo/protobuf/proto"
|
||||
)
|
||||
|
||||
const (
|
||||
// noTxn is an invalid txn ID.
|
||||
// To apply with independent Range, Put, Delete, you can pass noTxn
|
||||
// to apply functions instead of a valid txn ID.
|
||||
noTxn = -1
|
||||
)
|
||||
|
||||
type applyResult struct {
|
||||
resp proto.Message
|
||||
err error
|
||||
// physc signals the physical effect of the request has completed in addition
|
||||
// to being logically reflected by the node. Currently only used for
|
||||
// Compaction requests.
|
||||
physc <-chan struct{}
|
||||
}
|
||||
|
||||
// applierV3 is the interface for processing V3 raft messages
|
||||
type applierV3 interface {
|
||||
Put(txnID int64, p *pb.PutRequest) (*pb.PutResponse, error)
|
||||
Range(txnID int64, r *pb.RangeRequest) (*pb.RangeResponse, error)
|
||||
DeleteRange(txnID int64, dr *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error)
|
||||
Txn(rt *pb.TxnRequest) (*pb.TxnResponse, error)
|
||||
Compaction(compaction *pb.CompactionRequest) (*pb.CompactionResponse, <-chan struct{}, error)
|
||||
LeaseGrant(lc *pb.LeaseGrantRequest) (*pb.LeaseGrantResponse, error)
|
||||
LeaseRevoke(lc *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error)
|
||||
Alarm(*pb.AlarmRequest) (*pb.AlarmResponse, error)
|
||||
AuthEnable() (*pb.AuthEnableResponse, error)
|
||||
UserAdd(ua *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error)
|
||||
UserDelete(ua *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error)
|
||||
UserChangePassword(ua *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error)
|
||||
RoleAdd(ua *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error)
|
||||
}
|
||||
|
||||
type applierV3backend struct {
|
||||
s *EtcdServer
|
||||
}
|
||||
|
||||
func (s *EtcdServer) applyV3Request(r *pb.InternalRaftRequest) *applyResult {
|
||||
ar := &applyResult{}
|
||||
switch {
|
||||
case r.Range != nil:
|
||||
ar.resp, ar.err = s.applyV3.Range(noTxn, r.Range)
|
||||
case r.Put != nil:
|
||||
ar.resp, ar.err = s.applyV3.Put(noTxn, r.Put)
|
||||
case r.DeleteRange != nil:
|
||||
ar.resp, ar.err = s.applyV3.DeleteRange(noTxn, r.DeleteRange)
|
||||
case r.Txn != nil:
|
||||
ar.resp, ar.err = s.applyV3.Txn(r.Txn)
|
||||
case r.Compaction != nil:
|
||||
ar.resp, ar.physc, ar.err = s.applyV3.Compaction(r.Compaction)
|
||||
case r.LeaseGrant != nil:
|
||||
ar.resp, ar.err = s.applyV3.LeaseGrant(r.LeaseGrant)
|
||||
case r.LeaseRevoke != nil:
|
||||
ar.resp, ar.err = s.applyV3.LeaseRevoke(r.LeaseRevoke)
|
||||
case r.Alarm != nil:
|
||||
ar.resp, ar.err = s.applyV3.Alarm(r.Alarm)
|
||||
case r.AuthEnable != nil:
|
||||
ar.resp, ar.err = s.applyV3.AuthEnable()
|
||||
case r.AuthUserAdd != nil:
|
||||
ar.resp, ar.err = s.applyV3.UserAdd(r.AuthUserAdd)
|
||||
case r.AuthUserDelete != nil:
|
||||
ar.resp, ar.err = s.applyV3.UserDelete(r.AuthUserDelete)
|
||||
case r.AuthUserChangePassword != nil:
|
||||
ar.resp, ar.err = s.applyV3.UserChangePassword(r.AuthUserChangePassword)
|
||||
case r.AuthRoleAdd != nil:
|
||||
ar.resp, ar.err = s.applyV3.RoleAdd(r.AuthRoleAdd)
|
||||
default:
|
||||
panic("not implemented")
|
||||
}
|
||||
return ar
|
||||
}
|
||||
|
||||
func (a *applierV3backend) Put(txnID int64, p *pb.PutRequest) (*pb.PutResponse, error) {
|
||||
resp := &pb.PutResponse{}
|
||||
resp.Header = &pb.ResponseHeader{}
|
||||
var (
|
||||
rev int64
|
||||
err error
|
||||
)
|
||||
if txnID != noTxn {
|
||||
rev, err = a.s.KV().TxnPut(txnID, p.Key, p.Value, lease.LeaseID(p.Lease))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
leaseID := lease.LeaseID(p.Lease)
|
||||
if leaseID != lease.NoLease {
|
||||
if l := a.s.lessor.Lookup(leaseID); l == nil {
|
||||
return nil, lease.ErrLeaseNotFound
|
||||
}
|
||||
}
|
||||
rev = a.s.KV().Put(p.Key, p.Value, leaseID)
|
||||
}
|
||||
resp.Header.Revision = rev
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (a *applierV3backend) DeleteRange(txnID int64, dr *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error) {
|
||||
resp := &pb.DeleteRangeResponse{}
|
||||
resp.Header = &pb.ResponseHeader{}
|
||||
|
||||
var (
|
||||
n int64
|
||||
rev int64
|
||||
err error
|
||||
)
|
||||
|
||||
if isGteRange(dr.RangeEnd) {
|
||||
dr.RangeEnd = []byte{}
|
||||
}
|
||||
|
||||
if txnID != noTxn {
|
||||
n, rev, err = a.s.KV().TxnDeleteRange(txnID, dr.Key, dr.RangeEnd)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
n, rev = a.s.KV().DeleteRange(dr.Key, dr.RangeEnd)
|
||||
}
|
||||
|
||||
resp.Deleted = n
|
||||
resp.Header.Revision = rev
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (a *applierV3backend) Range(txnID int64, r *pb.RangeRequest) (*pb.RangeResponse, error) {
|
||||
resp := &pb.RangeResponse{}
|
||||
resp.Header = &pb.ResponseHeader{}
|
||||
|
||||
var (
|
||||
kvs []storagepb.KeyValue
|
||||
rev int64
|
||||
err error
|
||||
)
|
||||
|
||||
if isGteRange(r.RangeEnd) {
|
||||
r.RangeEnd = []byte{}
|
||||
}
|
||||
|
||||
limit := r.Limit
|
||||
if r.SortOrder != pb.RangeRequest_NONE {
|
||||
// fetch everything; sort and truncate afterwards
|
||||
limit = 0
|
||||
}
|
||||
if limit > 0 {
|
||||
// fetch one extra for 'more' flag
|
||||
limit = limit + 1
|
||||
}
|
||||
|
||||
if txnID != noTxn {
|
||||
kvs, rev, err = a.s.KV().TxnRange(txnID, r.Key, r.RangeEnd, limit, r.Revision)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
kvs, rev, err = a.s.KV().Range(r.Key, r.RangeEnd, limit, r.Revision)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
if r.SortOrder != pb.RangeRequest_NONE {
|
||||
var sorter sort.Interface
|
||||
switch {
|
||||
case r.SortTarget == pb.RangeRequest_KEY:
|
||||
sorter = &kvSortByKey{&kvSort{kvs}}
|
||||
case r.SortTarget == pb.RangeRequest_VERSION:
|
||||
sorter = &kvSortByVersion{&kvSort{kvs}}
|
||||
case r.SortTarget == pb.RangeRequest_CREATE:
|
||||
sorter = &kvSortByCreate{&kvSort{kvs}}
|
||||
case r.SortTarget == pb.RangeRequest_MOD:
|
||||
sorter = &kvSortByMod{&kvSort{kvs}}
|
||||
case r.SortTarget == pb.RangeRequest_VALUE:
|
||||
sorter = &kvSortByValue{&kvSort{kvs}}
|
||||
}
|
||||
switch {
|
||||
case r.SortOrder == pb.RangeRequest_ASCEND:
|
||||
sort.Sort(sorter)
|
||||
case r.SortOrder == pb.RangeRequest_DESCEND:
|
||||
sort.Sort(sort.Reverse(sorter))
|
||||
}
|
||||
}
|
||||
|
||||
if r.Limit > 0 && len(kvs) > int(r.Limit) {
|
||||
kvs = kvs[:r.Limit]
|
||||
resp.More = true
|
||||
}
|
||||
|
||||
resp.Header.Revision = rev
|
||||
for i := range kvs {
|
||||
resp.Kvs = append(resp.Kvs, &kvs[i])
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (a *applierV3backend) Txn(rt *pb.TxnRequest) (*pb.TxnResponse, error) {
|
||||
var revision int64
|
||||
|
||||
ok := true
|
||||
for _, c := range rt.Compare {
|
||||
if revision, ok = a.applyCompare(c); !ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
var reqs []*pb.RequestUnion
|
||||
if ok {
|
||||
reqs = rt.Success
|
||||
} else {
|
||||
reqs = rt.Failure
|
||||
}
|
||||
|
||||
if err := a.checkRequestLeases(reqs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := a.checkRequestRange(reqs); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// When executing the operations of txn, we need to hold the txn lock.
|
||||
// So the reader will not see any intermediate results.
|
||||
txnID := a.s.KV().TxnBegin()
|
||||
defer func() {
|
||||
err := a.s.KV().TxnEnd(txnID)
|
||||
if err != nil {
|
||||
panic(fmt.Sprint("unexpected error when closing txn", txnID))
|
||||
}
|
||||
}()
|
||||
|
||||
resps := make([]*pb.ResponseUnion, len(reqs))
|
||||
for i := range reqs {
|
||||
resps[i] = a.applyUnion(txnID, reqs[i])
|
||||
}
|
||||
|
||||
if len(resps) != 0 {
|
||||
revision += 1
|
||||
}
|
||||
|
||||
txnResp := &pb.TxnResponse{}
|
||||
txnResp.Header = &pb.ResponseHeader{}
|
||||
txnResp.Header.Revision = revision
|
||||
txnResp.Responses = resps
|
||||
txnResp.Succeeded = ok
|
||||
return txnResp, nil
|
||||
}
|
||||
|
||||
// applyCompare applies the compare request.
|
||||
// It returns the revision at which the comparison happens. If the comparison
|
||||
// succeeds, the it returns true. Otherwise it returns false.
|
||||
func (a *applierV3backend) applyCompare(c *pb.Compare) (int64, bool) {
|
||||
ckvs, rev, err := a.s.KV().Range(c.Key, nil, 1, 0)
|
||||
if err != nil {
|
||||
if err == dstorage.ErrTxnIDMismatch {
|
||||
panic("unexpected txn ID mismatch error")
|
||||
}
|
||||
return rev, false
|
||||
}
|
||||
var ckv storagepb.KeyValue
|
||||
if len(ckvs) != 0 {
|
||||
ckv = ckvs[0]
|
||||
} else {
|
||||
// Use the zero value of ckv normally. However...
|
||||
if c.Target == pb.Compare_VALUE {
|
||||
// Always fail if we're comparing a value on a key that doesn't exist.
|
||||
// We can treat non-existence as the empty set explicitly, such that
|
||||
// even a key with a value of length 0 bytes is still a real key
|
||||
// that was written that way
|
||||
return rev, false
|
||||
}
|
||||
}
|
||||
|
||||
// -1 is less, 0 is equal, 1 is greater
|
||||
var result int
|
||||
switch c.Target {
|
||||
case pb.Compare_VALUE:
|
||||
tv, _ := c.TargetUnion.(*pb.Compare_Value)
|
||||
if tv != nil {
|
||||
result = bytes.Compare(ckv.Value, tv.Value)
|
||||
}
|
||||
case pb.Compare_CREATE:
|
||||
tv, _ := c.TargetUnion.(*pb.Compare_CreateRevision)
|
||||
if tv != nil {
|
||||
result = compareInt64(ckv.CreateRevision, tv.CreateRevision)
|
||||
}
|
||||
|
||||
case pb.Compare_MOD:
|
||||
tv, _ := c.TargetUnion.(*pb.Compare_ModRevision)
|
||||
if tv != nil {
|
||||
result = compareInt64(ckv.ModRevision, tv.ModRevision)
|
||||
}
|
||||
case pb.Compare_VERSION:
|
||||
tv, _ := c.TargetUnion.(*pb.Compare_Version)
|
||||
if tv != nil {
|
||||
result = compareInt64(ckv.Version, tv.Version)
|
||||
}
|
||||
}
|
||||
|
||||
switch c.Result {
|
||||
case pb.Compare_EQUAL:
|
||||
if result != 0 {
|
||||
return rev, false
|
||||
}
|
||||
case pb.Compare_GREATER:
|
||||
if result != 1 {
|
||||
return rev, false
|
||||
}
|
||||
case pb.Compare_LESS:
|
||||
if result != -1 {
|
||||
return rev, false
|
||||
}
|
||||
}
|
||||
return rev, true
|
||||
}
|
||||
|
||||
func (a *applierV3backend) applyUnion(txnID int64, union *pb.RequestUnion) *pb.ResponseUnion {
|
||||
switch tv := union.Request.(type) {
|
||||
case *pb.RequestUnion_RequestRange:
|
||||
if tv.RequestRange != nil {
|
||||
resp, err := a.Range(txnID, tv.RequestRange)
|
||||
if err != nil {
|
||||
panic("unexpected error during txn")
|
||||
}
|
||||
return &pb.ResponseUnion{Response: &pb.ResponseUnion_ResponseRange{ResponseRange: resp}}
|
||||
}
|
||||
case *pb.RequestUnion_RequestPut:
|
||||
if tv.RequestPut != nil {
|
||||
resp, err := a.Put(txnID, tv.RequestPut)
|
||||
if err != nil {
|
||||
panic("unexpected error during txn")
|
||||
}
|
||||
return &pb.ResponseUnion{Response: &pb.ResponseUnion_ResponsePut{ResponsePut: resp}}
|
||||
}
|
||||
case *pb.RequestUnion_RequestDeleteRange:
|
||||
if tv.RequestDeleteRange != nil {
|
||||
resp, err := a.DeleteRange(txnID, tv.RequestDeleteRange)
|
||||
if err != nil {
|
||||
panic("unexpected error during txn")
|
||||
}
|
||||
return &pb.ResponseUnion{Response: &pb.ResponseUnion_ResponseDeleteRange{ResponseDeleteRange: resp}}
|
||||
}
|
||||
default:
|
||||
// empty union
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
func (a *applierV3backend) Compaction(compaction *pb.CompactionRequest) (*pb.CompactionResponse, <-chan struct{}, error) {
|
||||
resp := &pb.CompactionResponse{}
|
||||
resp.Header = &pb.ResponseHeader{}
|
||||
ch, err := a.s.KV().Compact(compaction.Revision)
|
||||
if err != nil {
|
||||
return nil, ch, err
|
||||
}
|
||||
// get the current revision. which key to get is not important.
|
||||
_, resp.Header.Revision, _ = a.s.KV().Range([]byte("compaction"), nil, 1, 0)
|
||||
return resp, ch, err
|
||||
}
|
||||
|
||||
func (a *applierV3backend) LeaseGrant(lc *pb.LeaseGrantRequest) (*pb.LeaseGrantResponse, error) {
|
||||
l, err := a.s.lessor.Grant(lease.LeaseID(lc.ID), lc.TTL)
|
||||
resp := &pb.LeaseGrantResponse{}
|
||||
if err == nil {
|
||||
resp.ID = int64(l.ID)
|
||||
resp.TTL = l.TTL
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (a *applierV3backend) LeaseRevoke(lc *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error) {
|
||||
err := a.s.lessor.Revoke(lease.LeaseID(lc.ID))
|
||||
return &pb.LeaseRevokeResponse{}, err
|
||||
}
|
||||
|
||||
func (a *applierV3backend) Alarm(ar *pb.AlarmRequest) (*pb.AlarmResponse, error) {
|
||||
resp := &pb.AlarmResponse{}
|
||||
oldCount := len(a.s.alarmStore.Get(ar.Alarm))
|
||||
|
||||
switch ar.Action {
|
||||
case pb.AlarmRequest_GET:
|
||||
resp.Alarms = a.s.alarmStore.Get(ar.Alarm)
|
||||
case pb.AlarmRequest_ACTIVATE:
|
||||
m := a.s.alarmStore.Activate(types.ID(ar.MemberID), ar.Alarm)
|
||||
if m == nil {
|
||||
break
|
||||
}
|
||||
resp.Alarms = append(resp.Alarms, m)
|
||||
activated := oldCount == 0 && len(a.s.alarmStore.Get(m.Alarm)) == 1
|
||||
if !activated {
|
||||
break
|
||||
}
|
||||
|
||||
switch m.Alarm {
|
||||
case pb.AlarmType_NOSPACE:
|
||||
plog.Warningf("alarm raised %+v", m)
|
||||
a.s.applyV3 = newApplierV3Capped(a)
|
||||
default:
|
||||
plog.Errorf("unimplemented alarm activation (%+v)", m)
|
||||
}
|
||||
case pb.AlarmRequest_DEACTIVATE:
|
||||
m := a.s.alarmStore.Deactivate(types.ID(ar.MemberID), ar.Alarm)
|
||||
if m == nil {
|
||||
break
|
||||
}
|
||||
resp.Alarms = append(resp.Alarms, m)
|
||||
deactivated := oldCount > 0 && len(a.s.alarmStore.Get(ar.Alarm)) == 0
|
||||
if !deactivated {
|
||||
break
|
||||
}
|
||||
|
||||
switch m.Alarm {
|
||||
case pb.AlarmType_NOSPACE:
|
||||
plog.Infof("alarm disarmed %+v", ar)
|
||||
a.s.applyV3 = newQuotaApplierV3(a.s, &applierV3backend{a.s})
|
||||
default:
|
||||
plog.Errorf("unimplemented alarm deactivation (%+v)", m)
|
||||
}
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
type applierV3Capped struct {
|
||||
applierV3
|
||||
q backendQuota
|
||||
}
|
||||
|
||||
// newApplierV3Capped creates an applyV3 that will reject Puts and transactions
|
||||
// with Puts so that the number of keys in the store is capped.
|
||||
func newApplierV3Capped(base applierV3) applierV3 { return &applierV3Capped{applierV3: base} }
|
||||
|
||||
func (a *applierV3Capped) Put(txnID int64, p *pb.PutRequest) (*pb.PutResponse, error) {
|
||||
return nil, ErrNoSpace
|
||||
}
|
||||
|
||||
func (a *applierV3Capped) Txn(r *pb.TxnRequest) (*pb.TxnResponse, error) {
|
||||
if a.q.Cost(r) > 0 {
|
||||
return nil, ErrNoSpace
|
||||
}
|
||||
return a.applierV3.Txn(r)
|
||||
}
|
||||
|
||||
func (a *applierV3Capped) LeaseGrant(lc *pb.LeaseGrantRequest) (*pb.LeaseGrantResponse, error) {
|
||||
return nil, ErrNoSpace
|
||||
}
|
||||
|
||||
func (a *applierV3backend) AuthEnable() (*pb.AuthEnableResponse, error) {
|
||||
a.s.AuthStore().AuthEnable()
|
||||
return &pb.AuthEnableResponse{}, nil
|
||||
}
|
||||
|
||||
func (a *applierV3backend) UserAdd(r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error) {
|
||||
return a.s.AuthStore().UserAdd(r)
|
||||
}
|
||||
|
||||
func (a *applierV3backend) UserDelete(r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error) {
|
||||
return a.s.AuthStore().UserDelete(r)
|
||||
}
|
||||
|
||||
func (a *applierV3backend) UserChangePassword(r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error) {
|
||||
return a.s.AuthStore().UserChangePassword(r)
|
||||
}
|
||||
|
||||
func (a *applierV3backend) RoleAdd(r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) {
|
||||
return a.s.AuthStore().RoleAdd(r)
|
||||
}
|
||||
|
||||
type quotaApplierV3 struct {
|
||||
applierV3
|
||||
q Quota
|
||||
}
|
||||
|
||||
func newQuotaApplierV3(s *EtcdServer, app applierV3) applierV3 {
|
||||
return "aApplierV3{app, NewBackendQuota(s)}
|
||||
}
|
||||
|
||||
func (a *quotaApplierV3) Put(txnID int64, p *pb.PutRequest) (*pb.PutResponse, error) {
|
||||
ok := a.q.Available(p)
|
||||
resp, err := a.applierV3.Put(txnID, p)
|
||||
if err == nil && !ok {
|
||||
err = ErrNoSpace
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (a *quotaApplierV3) Txn(rt *pb.TxnRequest) (*pb.TxnResponse, error) {
|
||||
ok := a.q.Available(rt)
|
||||
resp, err := a.applierV3.Txn(rt)
|
||||
if err == nil && !ok {
|
||||
err = ErrNoSpace
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
func (a *quotaApplierV3) LeaseGrant(lc *pb.LeaseGrantRequest) (*pb.LeaseGrantResponse, error) {
|
||||
ok := a.q.Available(lc)
|
||||
resp, err := a.applierV3.LeaseGrant(lc)
|
||||
if err == nil && !ok {
|
||||
err = ErrNoSpace
|
||||
}
|
||||
return resp, err
|
||||
}
|
||||
|
||||
type kvSort struct{ kvs []storagepb.KeyValue }
|
||||
|
||||
func (s *kvSort) Swap(i, j int) {
|
||||
t := s.kvs[i]
|
||||
s.kvs[i] = s.kvs[j]
|
||||
s.kvs[j] = t
|
||||
}
|
||||
func (s *kvSort) Len() int { return len(s.kvs) }
|
||||
|
||||
type kvSortByKey struct{ *kvSort }
|
||||
|
||||
func (s *kvSortByKey) Less(i, j int) bool {
|
||||
return bytes.Compare(s.kvs[i].Key, s.kvs[j].Key) < 0
|
||||
}
|
||||
|
||||
type kvSortByVersion struct{ *kvSort }
|
||||
|
||||
func (s *kvSortByVersion) Less(i, j int) bool {
|
||||
return (s.kvs[i].Version - s.kvs[j].Version) < 0
|
||||
}
|
||||
|
||||
type kvSortByCreate struct{ *kvSort }
|
||||
|
||||
func (s *kvSortByCreate) Less(i, j int) bool {
|
||||
return (s.kvs[i].CreateRevision - s.kvs[j].CreateRevision) < 0
|
||||
}
|
||||
|
||||
type kvSortByMod struct{ *kvSort }
|
||||
|
||||
func (s *kvSortByMod) Less(i, j int) bool {
|
||||
return (s.kvs[i].ModRevision - s.kvs[j].ModRevision) < 0
|
||||
}
|
||||
|
||||
type kvSortByValue struct{ *kvSort }
|
||||
|
||||
func (s *kvSortByValue) Less(i, j int) bool {
|
||||
return bytes.Compare(s.kvs[i].Value, s.kvs[j].Value) < 0
|
||||
}
|
||||
|
||||
func (a *applierV3backend) checkRequestLeases(reqs []*pb.RequestUnion) error {
|
||||
for _, requ := range reqs {
|
||||
tv, ok := requ.Request.(*pb.RequestUnion_RequestPut)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
preq := tv.RequestPut
|
||||
if preq == nil || lease.LeaseID(preq.Lease) == lease.NoLease {
|
||||
continue
|
||||
}
|
||||
if l := a.s.lessor.Lookup(lease.LeaseID(preq.Lease)); l == nil {
|
||||
return lease.ErrLeaseNotFound
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *applierV3backend) checkRequestRange(reqs []*pb.RequestUnion) error {
|
||||
for _, requ := range reqs {
|
||||
tv, ok := requ.Request.(*pb.RequestUnion_RequestRange)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
greq := tv.RequestRange
|
||||
if greq == nil || greq.Revision == 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
if greq.Revision > a.s.KV().Rev() {
|
||||
return dstorage.ErrFutureRev
|
||||
}
|
||||
if greq.Revision < a.s.KV().FirstRev() {
|
||||
return dstorage.ErrCompacted
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func compareInt64(a, b int64) int {
|
||||
switch {
|
||||
case a < b:
|
||||
return -1
|
||||
case a > b:
|
||||
return 1
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
// isGteRange determines if the range end is a >= range. This works around grpc
|
||||
// sending empty byte strings as nil; >= is encoded in the range end as '\0'.
|
||||
func isGteRange(rangeEnd []byte) bool {
|
||||
return len(rangeEnd) == 1 && rangeEnd[0] == 0
|
||||
}
|
||||
652
vendor/github.com/coreos/etcd/etcdserver/auth/auth.go
generated
vendored
Normal file
652
vendor/github.com/coreos/etcd/etcdserver/auth/auth.go
generated
vendored
Normal file
@@ -0,0 +1,652 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
// Package auth implements etcd authentication.
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"path"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
etcderr "github.com/coreos/etcd/error"
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const (
|
||||
// StorePermsPrefix is the internal prefix of the storage layer dedicated to storing user data.
|
||||
StorePermsPrefix = "/2"
|
||||
|
||||
// RootRoleName is the name of the ROOT role, with privileges to manage the cluster.
|
||||
RootRoleName = "root"
|
||||
|
||||
// GuestRoleName is the name of the role that defines the privileges of an unauthenticated user.
|
||||
GuestRoleName = "guest"
|
||||
)
|
||||
|
||||
var (
|
||||
plog = capnslog.NewPackageLogger("github.com/coreos/etcd/etcdserver", "auth")
|
||||
)
|
||||
|
||||
var rootRole = Role{
|
||||
Role: RootRoleName,
|
||||
Permissions: Permissions{
|
||||
KV: RWPermission{
|
||||
Read: []string{"/*"},
|
||||
Write: []string{"/*"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
var guestRole = Role{
|
||||
Role: GuestRoleName,
|
||||
Permissions: Permissions{
|
||||
KV: RWPermission{
|
||||
Read: []string{"/*"},
|
||||
Write: []string{"/*"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
type doer interface {
|
||||
Do(context.Context, etcdserverpb.Request) (etcdserver.Response, error)
|
||||
}
|
||||
|
||||
type Store interface {
|
||||
AllUsers() ([]string, error)
|
||||
GetUser(name string) (User, error)
|
||||
CreateOrUpdateUser(user User) (out User, created bool, err error)
|
||||
CreateUser(user User) (User, error)
|
||||
DeleteUser(name string) error
|
||||
UpdateUser(user User) (User, error)
|
||||
AllRoles() ([]string, error)
|
||||
GetRole(name string) (Role, error)
|
||||
CreateRole(role Role) error
|
||||
DeleteRole(name string) error
|
||||
UpdateRole(role Role) (Role, error)
|
||||
AuthEnabled() bool
|
||||
EnableAuth() error
|
||||
DisableAuth() error
|
||||
PasswordStore
|
||||
}
|
||||
|
||||
type PasswordStore interface {
|
||||
CheckPassword(user User, password string) bool
|
||||
HashPassword(password string) (string, error)
|
||||
}
|
||||
|
||||
type store struct {
|
||||
server doer
|
||||
timeout time.Duration
|
||||
ensuredOnce bool
|
||||
|
||||
PasswordStore
|
||||
}
|
||||
|
||||
type User struct {
|
||||
User string `json:"user"`
|
||||
Password string `json:"password,omitempty"`
|
||||
Roles []string `json:"roles"`
|
||||
Grant []string `json:"grant,omitempty"`
|
||||
Revoke []string `json:"revoke,omitempty"`
|
||||
}
|
||||
|
||||
type Role struct {
|
||||
Role string `json:"role"`
|
||||
Permissions Permissions `json:"permissions"`
|
||||
Grant *Permissions `json:"grant,omitempty"`
|
||||
Revoke *Permissions `json:"revoke,omitempty"`
|
||||
}
|
||||
|
||||
type Permissions struct {
|
||||
KV RWPermission `json:"kv"`
|
||||
}
|
||||
|
||||
func (p *Permissions) IsEmpty() bool {
|
||||
return p == nil || (len(p.KV.Read) == 0 && len(p.KV.Write) == 0)
|
||||
}
|
||||
|
||||
type RWPermission struct {
|
||||
Read []string `json:"read"`
|
||||
Write []string `json:"write"`
|
||||
}
|
||||
|
||||
type Error struct {
|
||||
Status int
|
||||
Errmsg string
|
||||
}
|
||||
|
||||
func (ae Error) Error() string { return ae.Errmsg }
|
||||
func (ae Error) HTTPStatus() int { return ae.Status }
|
||||
|
||||
func authErr(hs int, s string, v ...interface{}) Error {
|
||||
return Error{Status: hs, Errmsg: fmt.Sprintf("auth: "+s, v...)}
|
||||
}
|
||||
|
||||
func NewStore(server doer, timeout time.Duration) Store {
|
||||
s := &store{
|
||||
server: server,
|
||||
timeout: timeout,
|
||||
PasswordStore: passwordStore{},
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// passwordStore implements PasswordStore using bcrypt to hash user passwords
|
||||
type passwordStore struct{}
|
||||
|
||||
func (_ passwordStore) CheckPassword(user User, password string) bool {
|
||||
err := bcrypt.CompareHashAndPassword([]byte(user.Password), []byte(password))
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (_ passwordStore) HashPassword(password string) (string, error) {
|
||||
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
|
||||
return string(hash), err
|
||||
}
|
||||
|
||||
func (s *store) AllUsers() ([]string, error) {
|
||||
resp, err := s.requestResource("/users/", false)
|
||||
if err != nil {
|
||||
if e, ok := err.(*etcderr.Error); ok {
|
||||
if e.ErrorCode == etcderr.EcodeKeyNotFound {
|
||||
return []string{}, nil
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
var nodes []string
|
||||
for _, n := range resp.Event.Node.Nodes {
|
||||
_, user := path.Split(n.Key)
|
||||
nodes = append(nodes, user)
|
||||
}
|
||||
sort.Strings(nodes)
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func (s *store) GetUser(name string) (User, error) {
|
||||
resp, err := s.requestResource("/users/"+name, false)
|
||||
if err != nil {
|
||||
if e, ok := err.(*etcderr.Error); ok {
|
||||
if e.ErrorCode == etcderr.EcodeKeyNotFound {
|
||||
return User{}, authErr(http.StatusNotFound, "User %s does not exist.", name)
|
||||
}
|
||||
}
|
||||
return User{}, err
|
||||
}
|
||||
var u User
|
||||
err = json.Unmarshal([]byte(*resp.Event.Node.Value), &u)
|
||||
if err != nil {
|
||||
return u, err
|
||||
}
|
||||
// Attach root role to root user.
|
||||
if u.User == "root" {
|
||||
u = attachRootRole(u)
|
||||
}
|
||||
return u, nil
|
||||
}
|
||||
|
||||
// CreateOrUpdateUser should be only used for creating the new user or when you are not
|
||||
// sure if it is a create or update. (When only password is passed in, we are not sure
|
||||
// if it is a update or create)
|
||||
func (s *store) CreateOrUpdateUser(user User) (out User, created bool, err error) {
|
||||
_, err = s.GetUser(user.User)
|
||||
if err == nil {
|
||||
out, err = s.UpdateUser(user)
|
||||
return out, false, err
|
||||
}
|
||||
u, err := s.CreateUser(user)
|
||||
return u, true, err
|
||||
}
|
||||
|
||||
func (s *store) CreateUser(user User) (User, error) {
|
||||
// Attach root role to root user.
|
||||
if user.User == "root" {
|
||||
user = attachRootRole(user)
|
||||
}
|
||||
u, err := s.createUserInternal(user)
|
||||
if err == nil {
|
||||
plog.Noticef("created user %s", user.User)
|
||||
}
|
||||
return u, err
|
||||
}
|
||||
|
||||
func (s *store) createUserInternal(user User) (User, error) {
|
||||
if user.Password == "" {
|
||||
return user, authErr(http.StatusBadRequest, "Cannot create user %s with an empty password", user.User)
|
||||
}
|
||||
hash, err := s.HashPassword(user.Password)
|
||||
if err != nil {
|
||||
return user, err
|
||||
}
|
||||
user.Password = hash
|
||||
|
||||
_, err = s.createResource("/users/"+user.User, user)
|
||||
if err != nil {
|
||||
if e, ok := err.(*etcderr.Error); ok {
|
||||
if e.ErrorCode == etcderr.EcodeNodeExist {
|
||||
return user, authErr(http.StatusConflict, "User %s already exists.", user.User)
|
||||
}
|
||||
}
|
||||
}
|
||||
return user, err
|
||||
}
|
||||
|
||||
func (s *store) DeleteUser(name string) error {
|
||||
if s.AuthEnabled() && name == "root" {
|
||||
return authErr(http.StatusForbidden, "Cannot delete root user while auth is enabled.")
|
||||
}
|
||||
_, err := s.deleteResource("/users/" + name)
|
||||
if err != nil {
|
||||
if e, ok := err.(*etcderr.Error); ok {
|
||||
if e.ErrorCode == etcderr.EcodeKeyNotFound {
|
||||
return authErr(http.StatusNotFound, "User %s does not exist", name)
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
plog.Noticef("deleted user %s", name)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) UpdateUser(user User) (User, error) {
|
||||
old, err := s.GetUser(user.User)
|
||||
if err != nil {
|
||||
if e, ok := err.(*etcderr.Error); ok {
|
||||
if e.ErrorCode == etcderr.EcodeKeyNotFound {
|
||||
return user, authErr(http.StatusNotFound, "User %s doesn't exist.", user.User)
|
||||
}
|
||||
}
|
||||
return old, err
|
||||
}
|
||||
|
||||
hash, err := s.HashPassword(user.Password)
|
||||
if err != nil {
|
||||
return old, err
|
||||
}
|
||||
user.Password = hash
|
||||
|
||||
newUser, err := old.merge(user)
|
||||
if err != nil {
|
||||
return old, err
|
||||
}
|
||||
if reflect.DeepEqual(old, newUser) {
|
||||
return old, authErr(http.StatusBadRequest, "User not updated. Use grant/revoke/password to update the user.")
|
||||
}
|
||||
_, err = s.updateResource("/users/"+user.User, newUser)
|
||||
if err == nil {
|
||||
plog.Noticef("updated user %s", user.User)
|
||||
}
|
||||
return newUser, err
|
||||
}
|
||||
|
||||
func (s *store) AllRoles() ([]string, error) {
|
||||
nodes := []string{RootRoleName}
|
||||
resp, err := s.requestResource("/roles/", false)
|
||||
if err != nil {
|
||||
if e, ok := err.(*etcderr.Error); ok {
|
||||
if e.ErrorCode == etcderr.EcodeKeyNotFound {
|
||||
return nodes, nil
|
||||
}
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
for _, n := range resp.Event.Node.Nodes {
|
||||
_, role := path.Split(n.Key)
|
||||
nodes = append(nodes, role)
|
||||
}
|
||||
sort.Strings(nodes)
|
||||
return nodes, nil
|
||||
}
|
||||
|
||||
func (s *store) GetRole(name string) (Role, error) {
|
||||
if name == RootRoleName {
|
||||
return rootRole, nil
|
||||
}
|
||||
resp, err := s.requestResource("/roles/"+name, false)
|
||||
if err != nil {
|
||||
if e, ok := err.(*etcderr.Error); ok {
|
||||
if e.ErrorCode == etcderr.EcodeKeyNotFound {
|
||||
return Role{}, authErr(http.StatusNotFound, "Role %s does not exist.", name)
|
||||
}
|
||||
}
|
||||
return Role{}, err
|
||||
}
|
||||
var r Role
|
||||
err = json.Unmarshal([]byte(*resp.Event.Node.Value), &r)
|
||||
if err != nil {
|
||||
return r, err
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
func (s *store) CreateRole(role Role) error {
|
||||
if role.Role == RootRoleName {
|
||||
return authErr(http.StatusForbidden, "Cannot modify role %s: is root role.", role.Role)
|
||||
}
|
||||
_, err := s.createResource("/roles/"+role.Role, role)
|
||||
if err != nil {
|
||||
if e, ok := err.(*etcderr.Error); ok {
|
||||
if e.ErrorCode == etcderr.EcodeNodeExist {
|
||||
return authErr(http.StatusConflict, "Role %s already exists.", role.Role)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
plog.Noticef("created new role %s", role.Role)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *store) DeleteRole(name string) error {
|
||||
if name == RootRoleName {
|
||||
return authErr(http.StatusForbidden, "Cannot modify role %s: is root role.", name)
|
||||
}
|
||||
_, err := s.deleteResource("/roles/" + name)
|
||||
if err != nil {
|
||||
if e, ok := err.(*etcderr.Error); ok {
|
||||
if e.ErrorCode == etcderr.EcodeKeyNotFound {
|
||||
return authErr(http.StatusNotFound, "Role %s doesn't exist.", name)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err == nil {
|
||||
plog.Noticef("deleted role %s", name)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *store) UpdateRole(role Role) (Role, error) {
|
||||
if role.Role == RootRoleName {
|
||||
return Role{}, authErr(http.StatusForbidden, "Cannot modify role %s: is root role.", role.Role)
|
||||
}
|
||||
old, err := s.GetRole(role.Role)
|
||||
if err != nil {
|
||||
if e, ok := err.(*etcderr.Error); ok {
|
||||
if e.ErrorCode == etcderr.EcodeKeyNotFound {
|
||||
return role, authErr(http.StatusNotFound, "Role %s doesn't exist.", role.Role)
|
||||
}
|
||||
}
|
||||
return old, err
|
||||
}
|
||||
newRole, err := old.merge(role)
|
||||
if err != nil {
|
||||
return old, err
|
||||
}
|
||||
if reflect.DeepEqual(old, newRole) {
|
||||
return old, authErr(http.StatusBadRequest, "Role not updated. Use grant/revoke to update the role.")
|
||||
}
|
||||
_, err = s.updateResource("/roles/"+role.Role, newRole)
|
||||
if err == nil {
|
||||
plog.Noticef("updated role %s", role.Role)
|
||||
}
|
||||
return newRole, err
|
||||
}
|
||||
|
||||
func (s *store) AuthEnabled() bool {
|
||||
return s.detectAuth()
|
||||
}
|
||||
|
||||
func (s *store) EnableAuth() error {
|
||||
if s.AuthEnabled() {
|
||||
return authErr(http.StatusConflict, "already enabled")
|
||||
}
|
||||
|
||||
if _, err := s.GetUser("root"); err != nil {
|
||||
return authErr(http.StatusConflict, "No root user available, please create one")
|
||||
}
|
||||
if _, err := s.GetRole(GuestRoleName); err != nil {
|
||||
plog.Printf("no guest role access found, creating default")
|
||||
if err := s.CreateRole(guestRole); err != nil {
|
||||
plog.Errorf("error creating guest role. aborting auth enable.")
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := s.enableAuth(); err != nil {
|
||||
plog.Errorf("error enabling auth (%v)", err)
|
||||
return err
|
||||
}
|
||||
|
||||
plog.Noticef("auth: enabled auth")
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) DisableAuth() error {
|
||||
if !s.AuthEnabled() {
|
||||
return authErr(http.StatusConflict, "already disabled")
|
||||
}
|
||||
|
||||
err := s.disableAuth()
|
||||
if err == nil {
|
||||
plog.Noticef("auth: disabled auth")
|
||||
} else {
|
||||
plog.Errorf("error disabling auth (%v)", err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// merge applies the properties of the passed-in User to the User on which it
|
||||
// is called and returns a new User with these modifications applied. Think of
|
||||
// all Users as immutable sets of data. Merge allows you to perform the set
|
||||
// operations (desired grants and revokes) atomically
|
||||
func (u User) merge(n User) (User, error) {
|
||||
var out User
|
||||
if u.User != n.User {
|
||||
return out, authErr(http.StatusConflict, "Merging user data with conflicting usernames: %s %s", u.User, n.User)
|
||||
}
|
||||
out.User = u.User
|
||||
if n.Password != "" {
|
||||
out.Password = n.Password
|
||||
} else {
|
||||
out.Password = u.Password
|
||||
}
|
||||
currentRoles := types.NewUnsafeSet(u.Roles...)
|
||||
for _, g := range n.Grant {
|
||||
if currentRoles.Contains(g) {
|
||||
plog.Noticef("granting duplicate role %s for user %s", g, n.User)
|
||||
return User{}, authErr(http.StatusConflict, fmt.Sprintf("Granting duplicate role %s for user %s", g, n.User))
|
||||
}
|
||||
currentRoles.Add(g)
|
||||
}
|
||||
for _, r := range n.Revoke {
|
||||
if !currentRoles.Contains(r) {
|
||||
plog.Noticef("revoking ungranted role %s for user %s", r, n.User)
|
||||
return User{}, authErr(http.StatusConflict, fmt.Sprintf("Revoking ungranted role %s for user %s", r, n.User))
|
||||
}
|
||||
currentRoles.Remove(r)
|
||||
}
|
||||
out.Roles = currentRoles.Values()
|
||||
sort.Strings(out.Roles)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// merge for a role works the same as User above -- atomic Role application to
|
||||
// each of the substructures.
|
||||
func (r Role) merge(n Role) (Role, error) {
|
||||
var out Role
|
||||
var err error
|
||||
if r.Role != n.Role {
|
||||
return out, authErr(http.StatusConflict, "Merging role with conflicting names: %s %s", r.Role, n.Role)
|
||||
}
|
||||
out.Role = r.Role
|
||||
out.Permissions, err = r.Permissions.Grant(n.Grant)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
out.Permissions, err = out.Permissions.Revoke(n.Revoke)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (r Role) HasKeyAccess(key string, write bool) bool {
|
||||
if r.Role == RootRoleName {
|
||||
return true
|
||||
}
|
||||
return r.Permissions.KV.HasAccess(key, write)
|
||||
}
|
||||
|
||||
func (r Role) HasRecursiveAccess(key string, write bool) bool {
|
||||
if r.Role == RootRoleName {
|
||||
return true
|
||||
}
|
||||
return r.Permissions.KV.HasRecursiveAccess(key, write)
|
||||
}
|
||||
|
||||
// Grant adds a set of permissions to the permission object on which it is called,
|
||||
// returning a new permission object.
|
||||
func (p Permissions) Grant(n *Permissions) (Permissions, error) {
|
||||
var out Permissions
|
||||
var err error
|
||||
if n == nil {
|
||||
return p, nil
|
||||
}
|
||||
out.KV, err = p.KV.Grant(n.KV)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// Revoke removes a set of permissions to the permission object on which it is called,
|
||||
// returning a new permission object.
|
||||
func (p Permissions) Revoke(n *Permissions) (Permissions, error) {
|
||||
var out Permissions
|
||||
var err error
|
||||
if n == nil {
|
||||
return p, nil
|
||||
}
|
||||
out.KV, err = p.KV.Revoke(n.KV)
|
||||
return out, err
|
||||
}
|
||||
|
||||
// Grant adds a set of permissions to the permission object on which it is called,
|
||||
// returning a new permission object.
|
||||
func (rw RWPermission) Grant(n RWPermission) (RWPermission, error) {
|
||||
var out RWPermission
|
||||
currentRead := types.NewUnsafeSet(rw.Read...)
|
||||
for _, r := range n.Read {
|
||||
if currentRead.Contains(r) {
|
||||
return out, authErr(http.StatusConflict, "Granting duplicate read permission %s", r)
|
||||
}
|
||||
currentRead.Add(r)
|
||||
}
|
||||
currentWrite := types.NewUnsafeSet(rw.Write...)
|
||||
for _, w := range n.Write {
|
||||
if currentWrite.Contains(w) {
|
||||
return out, authErr(http.StatusConflict, "Granting duplicate write permission %s", w)
|
||||
}
|
||||
currentWrite.Add(w)
|
||||
}
|
||||
out.Read = currentRead.Values()
|
||||
out.Write = currentWrite.Values()
|
||||
sort.Strings(out.Read)
|
||||
sort.Strings(out.Write)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// Revoke removes a set of permissions to the permission object on which it is called,
|
||||
// returning a new permission object.
|
||||
func (rw RWPermission) Revoke(n RWPermission) (RWPermission, error) {
|
||||
var out RWPermission
|
||||
currentRead := types.NewUnsafeSet(rw.Read...)
|
||||
for _, r := range n.Read {
|
||||
if !currentRead.Contains(r) {
|
||||
plog.Noticef("revoking ungranted read permission %s", r)
|
||||
continue
|
||||
}
|
||||
currentRead.Remove(r)
|
||||
}
|
||||
currentWrite := types.NewUnsafeSet(rw.Write...)
|
||||
for _, w := range n.Write {
|
||||
if !currentWrite.Contains(w) {
|
||||
plog.Noticef("revoking ungranted write permission %s", w)
|
||||
continue
|
||||
}
|
||||
currentWrite.Remove(w)
|
||||
}
|
||||
out.Read = currentRead.Values()
|
||||
out.Write = currentWrite.Values()
|
||||
sort.Strings(out.Read)
|
||||
sort.Strings(out.Write)
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (rw RWPermission) HasAccess(key string, write bool) bool {
|
||||
var list []string
|
||||
if write {
|
||||
list = rw.Write
|
||||
} else {
|
||||
list = rw.Read
|
||||
}
|
||||
for _, pat := range list {
|
||||
match, err := simpleMatch(pat, key)
|
||||
if err == nil && match {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (rw RWPermission) HasRecursiveAccess(key string, write bool) bool {
|
||||
list := rw.Read
|
||||
if write {
|
||||
list = rw.Write
|
||||
}
|
||||
for _, pat := range list {
|
||||
match, err := prefixMatch(pat, key)
|
||||
if err == nil && match {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func simpleMatch(pattern string, key string) (match bool, err error) {
|
||||
if pattern[len(pattern)-1] == '*' {
|
||||
return strings.HasPrefix(key, pattern[:len(pattern)-1]), nil
|
||||
}
|
||||
return key == pattern, nil
|
||||
}
|
||||
|
||||
func prefixMatch(pattern string, key string) (match bool, err error) {
|
||||
if pattern[len(pattern)-1] != '*' {
|
||||
return false, nil
|
||||
}
|
||||
return strings.HasPrefix(key, pattern[:len(pattern)-1]), nil
|
||||
}
|
||||
|
||||
func attachRootRole(u User) User {
|
||||
inRoles := false
|
||||
for _, r := range u.Roles {
|
||||
if r == RootRoleName {
|
||||
inRoles = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !inRoles {
|
||||
u.Roles = append(u.Roles, RootRoleName)
|
||||
}
|
||||
return u
|
||||
}
|
||||
162
vendor/github.com/coreos/etcd/etcdserver/auth/auth_requests.go
generated
vendored
Normal file
162
vendor/github.com/coreos/etcd/etcdserver/auth/auth_requests.go
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package auth
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"path"
|
||||
|
||||
etcderr "github.com/coreos/etcd/error"
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
func (s *store) ensureAuthDirectories() error {
|
||||
if s.ensuredOnce {
|
||||
return nil
|
||||
}
|
||||
for _, res := range []string{StorePermsPrefix, StorePermsPrefix + "/users/", StorePermsPrefix + "/roles/"} {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), s.timeout)
|
||||
defer cancel()
|
||||
pe := false
|
||||
rr := etcdserverpb.Request{
|
||||
Method: "PUT",
|
||||
Path: res,
|
||||
Dir: true,
|
||||
PrevExist: &pe,
|
||||
}
|
||||
_, err := s.server.Do(ctx, rr)
|
||||
if err != nil {
|
||||
if e, ok := err.(*etcderr.Error); ok {
|
||||
if e.ErrorCode == etcderr.EcodeNodeExist {
|
||||
continue
|
||||
}
|
||||
}
|
||||
plog.Errorf("failed to create auth directories in the store (%v)", err)
|
||||
return err
|
||||
}
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), s.timeout)
|
||||
defer cancel()
|
||||
pe := false
|
||||
rr := etcdserverpb.Request{
|
||||
Method: "PUT",
|
||||
Path: StorePermsPrefix + "/enabled",
|
||||
Val: "false",
|
||||
PrevExist: &pe,
|
||||
}
|
||||
_, err := s.server.Do(ctx, rr)
|
||||
if err != nil {
|
||||
if e, ok := err.(*etcderr.Error); ok {
|
||||
if e.ErrorCode == etcderr.EcodeNodeExist {
|
||||
s.ensuredOnce = true
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return err
|
||||
}
|
||||
s.ensuredOnce = true
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *store) enableAuth() error {
|
||||
_, err := s.updateResource("/enabled", true)
|
||||
return err
|
||||
}
|
||||
func (s *store) disableAuth() error {
|
||||
_, err := s.updateResource("/enabled", false)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *store) detectAuth() bool {
|
||||
if s.server == nil {
|
||||
return false
|
||||
}
|
||||
value, err := s.requestResource("/enabled", false)
|
||||
if err != nil {
|
||||
if e, ok := err.(*etcderr.Error); ok {
|
||||
if e.ErrorCode == etcderr.EcodeKeyNotFound {
|
||||
return false
|
||||
}
|
||||
}
|
||||
plog.Errorf("failed to detect auth settings (%s)", err)
|
||||
return false
|
||||
}
|
||||
|
||||
var u bool
|
||||
err = json.Unmarshal([]byte(*value.Event.Node.Value), &u)
|
||||
if err != nil {
|
||||
plog.Errorf("internal bookkeeping value for enabled isn't valid JSON (%v)", err)
|
||||
return false
|
||||
}
|
||||
return u
|
||||
}
|
||||
|
||||
func (s *store) requestResource(res string, dir bool) (etcdserver.Response, error) {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), s.timeout)
|
||||
defer cancel()
|
||||
p := path.Join(StorePermsPrefix, res)
|
||||
rr := etcdserverpb.Request{
|
||||
Method: "GET",
|
||||
Path: p,
|
||||
Dir: dir,
|
||||
}
|
||||
return s.server.Do(ctx, rr)
|
||||
}
|
||||
|
||||
func (s *store) updateResource(res string, value interface{}) (etcdserver.Response, error) {
|
||||
return s.setResource(res, value, true)
|
||||
}
|
||||
func (s *store) createResource(res string, value interface{}) (etcdserver.Response, error) {
|
||||
return s.setResource(res, value, false)
|
||||
}
|
||||
func (s *store) setResource(res string, value interface{}, prevexist bool) (etcdserver.Response, error) {
|
||||
err := s.ensureAuthDirectories()
|
||||
if err != nil {
|
||||
return etcdserver.Response{}, err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), s.timeout)
|
||||
defer cancel()
|
||||
data, err := json.Marshal(value)
|
||||
if err != nil {
|
||||
return etcdserver.Response{}, err
|
||||
}
|
||||
p := path.Join(StorePermsPrefix, res)
|
||||
rr := etcdserverpb.Request{
|
||||
Method: "PUT",
|
||||
Path: p,
|
||||
Val: string(data),
|
||||
PrevExist: &prevexist,
|
||||
}
|
||||
return s.server.Do(ctx, rr)
|
||||
}
|
||||
|
||||
func (s *store) deleteResource(res string) (etcdserver.Response, error) {
|
||||
err := s.ensureAuthDirectories()
|
||||
if err != nil {
|
||||
return etcdserver.Response{}, err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), s.timeout)
|
||||
defer cancel()
|
||||
pex := true
|
||||
p := path.Join(StorePermsPrefix, res)
|
||||
rr := etcdserverpb.Request{
|
||||
Method: "DELETE",
|
||||
Path: p,
|
||||
PrevExist: &pex,
|
||||
}
|
||||
return s.server.Do(ctx, rr)
|
||||
}
|
||||
258
vendor/github.com/coreos/etcd/etcdserver/cluster_util.go
generated
vendored
Normal file
258
vendor/github.com/coreos/etcd/etcdserver/cluster_util.go
generated
vendored
Normal file
@@ -0,0 +1,258 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 etcdserver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/etcdserver/membership"
|
||||
"github.com/coreos/etcd/pkg/httputil"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/etcd/version"
|
||||
"github.com/coreos/go-semver/semver"
|
||||
)
|
||||
|
||||
// isMemberBootstrapped tries to check if the given member has been bootstrapped
|
||||
// in the given cluster.
|
||||
func isMemberBootstrapped(cl *membership.RaftCluster, member string, rt http.RoundTripper, timeout time.Duration) bool {
|
||||
rcl, err := getClusterFromRemotePeers(getRemotePeerURLs(cl, member), timeout, false, rt)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
id := cl.MemberByName(member).ID
|
||||
m := rcl.Member(id)
|
||||
if m == nil {
|
||||
return false
|
||||
}
|
||||
if len(m.ClientURLs) > 0 {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// GetClusterFromRemotePeers takes a set of URLs representing etcd peers, and
|
||||
// attempts to construct a Cluster by accessing the members endpoint on one of
|
||||
// these URLs. The first URL to provide a response is used. If no URLs provide
|
||||
// a response, or a Cluster cannot be successfully created from a received
|
||||
// response, an error is returned.
|
||||
// Each request has a 10-second timeout. Because the upper limit of TTL is 5s,
|
||||
// 10 second is enough for building connection and finishing request.
|
||||
func GetClusterFromRemotePeers(urls []string, rt http.RoundTripper) (*membership.RaftCluster, error) {
|
||||
return getClusterFromRemotePeers(urls, 10*time.Second, true, rt)
|
||||
}
|
||||
|
||||
// If logerr is true, it prints out more error messages.
|
||||
func getClusterFromRemotePeers(urls []string, timeout time.Duration, logerr bool, rt http.RoundTripper) (*membership.RaftCluster, error) {
|
||||
cc := &http.Client{
|
||||
Transport: rt,
|
||||
Timeout: timeout,
|
||||
}
|
||||
for _, u := range urls {
|
||||
resp, err := cc.Get(u + "/members")
|
||||
if err != nil {
|
||||
if logerr {
|
||||
plog.Warningf("could not get cluster response from %s: %v", u, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
b, err := ioutil.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
if logerr {
|
||||
plog.Warningf("could not read the body of cluster response: %v", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
var membs []*membership.Member
|
||||
if err = json.Unmarshal(b, &membs); err != nil {
|
||||
if logerr {
|
||||
plog.Warningf("could not unmarshal cluster response: %v", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
id, err := types.IDFromString(resp.Header.Get("X-Etcd-Cluster-ID"))
|
||||
if err != nil {
|
||||
if logerr {
|
||||
plog.Warningf("could not parse the cluster ID from cluster res: %v", err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
return membership.NewClusterFromMembers("", id, membs), nil
|
||||
}
|
||||
return nil, fmt.Errorf("could not retrieve cluster information from the given urls")
|
||||
}
|
||||
|
||||
// getRemotePeerURLs returns peer urls of remote members in the cluster. The
|
||||
// returned list is sorted in ascending lexicographical order.
|
||||
func getRemotePeerURLs(cl *membership.RaftCluster, local string) []string {
|
||||
us := make([]string, 0)
|
||||
for _, m := range cl.Members() {
|
||||
if m.Name == local {
|
||||
continue
|
||||
}
|
||||
us = append(us, m.PeerURLs...)
|
||||
}
|
||||
sort.Strings(us)
|
||||
return us
|
||||
}
|
||||
|
||||
// getVersions returns the versions of the members in the given cluster.
|
||||
// The key of the returned map is the member's ID. The value of the returned map
|
||||
// is the semver versions string, including server and cluster.
|
||||
// If it fails to get the version of a member, the key will be nil.
|
||||
func getVersions(cl *membership.RaftCluster, local types.ID, rt http.RoundTripper) map[string]*version.Versions {
|
||||
members := cl.Members()
|
||||
vers := make(map[string]*version.Versions)
|
||||
for _, m := range members {
|
||||
if m.ID == local {
|
||||
cv := "not_decided"
|
||||
if cl.Version() != nil {
|
||||
cv = cl.Version().String()
|
||||
}
|
||||
vers[m.ID.String()] = &version.Versions{Server: version.Version, Cluster: cv}
|
||||
continue
|
||||
}
|
||||
ver, err := getVersion(m, rt)
|
||||
if err != nil {
|
||||
plog.Warningf("cannot get the version of member %s (%v)", m.ID, err)
|
||||
vers[m.ID.String()] = nil
|
||||
} else {
|
||||
vers[m.ID.String()] = ver
|
||||
}
|
||||
}
|
||||
return vers
|
||||
}
|
||||
|
||||
// decideClusterVersion decides the cluster version based on the versions map.
|
||||
// The returned version is the min server version in the map, or nil if the min
|
||||
// version in unknown.
|
||||
func decideClusterVersion(vers map[string]*version.Versions) *semver.Version {
|
||||
var cv *semver.Version
|
||||
lv := semver.Must(semver.NewVersion(version.Version))
|
||||
|
||||
for mid, ver := range vers {
|
||||
if ver == nil {
|
||||
return nil
|
||||
}
|
||||
v, err := semver.NewVersion(ver.Server)
|
||||
if err != nil {
|
||||
plog.Errorf("cannot understand the version of member %s (%v)", mid, err)
|
||||
return nil
|
||||
}
|
||||
if lv.LessThan(*v) {
|
||||
plog.Warningf("the local etcd version %s is not up-to-date", lv.String())
|
||||
plog.Warningf("member %s has a higher version %s", mid, ver.Server)
|
||||
}
|
||||
if cv == nil {
|
||||
cv = v
|
||||
} else if v.LessThan(*cv) {
|
||||
cv = v
|
||||
}
|
||||
}
|
||||
return cv
|
||||
}
|
||||
|
||||
// isCompatibleWithCluster return true if the local member has a compatible version with
|
||||
// the current running cluster.
|
||||
// The version is considered as compatible when at least one of the other members in the cluster has a
|
||||
// cluster version in the range of [MinClusterVersion, Version] and no known members has a cluster version
|
||||
// out of the range.
|
||||
// We set this rule since when the local member joins, another member might be offline.
|
||||
func isCompatibleWithCluster(cl *membership.RaftCluster, local types.ID, rt http.RoundTripper) bool {
|
||||
vers := getVersions(cl, local, rt)
|
||||
minV := semver.Must(semver.NewVersion(version.MinClusterVersion))
|
||||
maxV := semver.Must(semver.NewVersion(version.Version))
|
||||
maxV = &semver.Version{
|
||||
Major: maxV.Major,
|
||||
Minor: maxV.Minor,
|
||||
}
|
||||
|
||||
return isCompatibleWithVers(vers, local, minV, maxV)
|
||||
}
|
||||
|
||||
func isCompatibleWithVers(vers map[string]*version.Versions, local types.ID, minV, maxV *semver.Version) bool {
|
||||
var ok bool
|
||||
for id, v := range vers {
|
||||
// ignore comparison with local version
|
||||
if id == local.String() {
|
||||
continue
|
||||
}
|
||||
if v == nil {
|
||||
continue
|
||||
}
|
||||
clusterv, err := semver.NewVersion(v.Cluster)
|
||||
if err != nil {
|
||||
plog.Errorf("cannot understand the cluster version of member %s (%v)", id, err)
|
||||
continue
|
||||
}
|
||||
if clusterv.LessThan(*minV) {
|
||||
plog.Warningf("the running cluster version(%v) is lower than the minimal cluster version(%v) supported", clusterv.String(), minV.String())
|
||||
return false
|
||||
}
|
||||
if maxV.LessThan(*clusterv) {
|
||||
plog.Warningf("the running cluster version(%v) is higher than the maximum cluster version(%v) supported", clusterv.String(), maxV.String())
|
||||
return false
|
||||
}
|
||||
ok = true
|
||||
}
|
||||
return ok
|
||||
}
|
||||
|
||||
// getVersion returns the Versions of the given member via its
|
||||
// peerURLs. Returns the last error if it fails to get the version.
|
||||
func getVersion(m *membership.Member, rt http.RoundTripper) (*version.Versions, error) {
|
||||
cc := &http.Client{
|
||||
Transport: rt,
|
||||
}
|
||||
var (
|
||||
err error
|
||||
resp *http.Response
|
||||
)
|
||||
|
||||
for _, u := range m.PeerURLs {
|
||||
resp, err = cc.Get(u + "/version")
|
||||
if err != nil {
|
||||
plog.Warningf("failed to reach the peerURL(%s) of member %s (%v)", u, m.ID, err)
|
||||
continue
|
||||
}
|
||||
// etcd 2.0 does not have version endpoint on peer url.
|
||||
if resp.StatusCode == http.StatusNotFound {
|
||||
httputil.GracefulClose(resp)
|
||||
return &version.Versions{
|
||||
Server: "2.0.0",
|
||||
Cluster: "2.0.0",
|
||||
}, nil
|
||||
}
|
||||
|
||||
var b []byte
|
||||
b, err = ioutil.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
plog.Warningf("failed to read out the response body from the peerURL(%s) of member %s (%v)", u, m.ID, err)
|
||||
continue
|
||||
}
|
||||
var vers version.Versions
|
||||
if err = json.Unmarshal(b, &vers); err != nil {
|
||||
plog.Warningf("failed to unmarshal the response body got from the peerURL(%s) of member %s (%v)", u, m.ID, err)
|
||||
continue
|
||||
}
|
||||
return &vers, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
196
vendor/github.com/coreos/etcd/etcdserver/config.go
generated
vendored
Normal file
196
vendor/github.com/coreos/etcd/etcdserver/config.go
generated
vendored
Normal file
@@ -0,0 +1,196 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 etcdserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/pkg/netutil"
|
||||
"github.com/coreos/etcd/pkg/transport"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
)
|
||||
|
||||
// ServerConfig holds the configuration of etcd as taken from the command line or discovery.
|
||||
type ServerConfig struct {
|
||||
Name string
|
||||
DiscoveryURL string
|
||||
DiscoveryProxy string
|
||||
ClientURLs types.URLs
|
||||
PeerURLs types.URLs
|
||||
DataDir string
|
||||
// DedicatedWALDir config will make the etcd to write the WAL to the WALDir
|
||||
// rather than the dataDir/member/wal.
|
||||
DedicatedWALDir string
|
||||
SnapCount uint64
|
||||
MaxSnapFiles uint
|
||||
MaxWALFiles uint
|
||||
InitialPeerURLsMap types.URLsMap
|
||||
InitialClusterToken string
|
||||
NewCluster bool
|
||||
ForceNewCluster bool
|
||||
PeerTLSInfo transport.TLSInfo
|
||||
|
||||
TickMs uint
|
||||
ElectionTicks int
|
||||
BootstrapTimeout time.Duration
|
||||
|
||||
AutoCompactionRetention int
|
||||
QuotaBackendBytes int64
|
||||
|
||||
StrictReconfigCheck bool
|
||||
|
||||
EnablePprof bool
|
||||
}
|
||||
|
||||
// VerifyBootstrap sanity-checks the initial config for bootstrap case
|
||||
// and returns an error for things that should never happen.
|
||||
func (c *ServerConfig) VerifyBootstrap() error {
|
||||
if err := c.verifyLocalMember(true); err != nil {
|
||||
return err
|
||||
}
|
||||
if checkDuplicateURL(c.InitialPeerURLsMap) {
|
||||
return fmt.Errorf("initial cluster %s has duplicate url", c.InitialPeerURLsMap)
|
||||
}
|
||||
if c.InitialPeerURLsMap.String() == "" && c.DiscoveryURL == "" {
|
||||
return fmt.Errorf("initial cluster unset and no discovery URL found")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyJoinExisting sanity-checks the initial config for join existing cluster
|
||||
// case and returns an error for things that should never happen.
|
||||
func (c *ServerConfig) VerifyJoinExisting() error {
|
||||
// no need for strict checking since the member have announced its
|
||||
// peer urls to the cluster before starting and do not have to set
|
||||
// it in the configuration again.
|
||||
if err := c.verifyLocalMember(false); err != nil {
|
||||
return err
|
||||
}
|
||||
if checkDuplicateURL(c.InitialPeerURLsMap) {
|
||||
return fmt.Errorf("initial cluster %s has duplicate url", c.InitialPeerURLsMap)
|
||||
}
|
||||
if c.DiscoveryURL != "" {
|
||||
return fmt.Errorf("discovery URL should not be set when joining existing initial cluster")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// verifyLocalMember verifies the configured member is in configured
|
||||
// cluster. If strict is set, it also verifies the configured member
|
||||
// has the same peer urls as configured advertised peer urls.
|
||||
func (c *ServerConfig) verifyLocalMember(strict bool) error {
|
||||
urls := c.InitialPeerURLsMap[c.Name]
|
||||
// Make sure the cluster at least contains the local server.
|
||||
if urls == nil {
|
||||
return fmt.Errorf("couldn't find local name %q in the initial cluster configuration", c.Name)
|
||||
}
|
||||
|
||||
// Advertised peer URLs must match those in the cluster peer list
|
||||
apurls := c.PeerURLs.StringSlice()
|
||||
sort.Strings(apurls)
|
||||
urls.Sort()
|
||||
if strict {
|
||||
if !netutil.URLStringsEqual(apurls, urls.StringSlice()) {
|
||||
umap := map[string]types.URLs{c.Name: c.PeerURLs}
|
||||
return fmt.Errorf("--initial-cluster must include %s given --initial-advertise-peer-urls=%s", types.URLsMap(umap).String(), strings.Join(apurls, ","))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *ServerConfig) MemberDir() string { return path.Join(c.DataDir, "member") }
|
||||
|
||||
func (c *ServerConfig) WALDir() string {
|
||||
if c.DedicatedWALDir != "" {
|
||||
return c.DedicatedWALDir
|
||||
}
|
||||
return path.Join(c.MemberDir(), "wal")
|
||||
}
|
||||
|
||||
func (c *ServerConfig) SnapDir() string { return path.Join(c.MemberDir(), "snap") }
|
||||
|
||||
func (c *ServerConfig) ShouldDiscover() bool { return c.DiscoveryURL != "" }
|
||||
|
||||
// ReqTimeout returns timeout for request to finish.
|
||||
func (c *ServerConfig) ReqTimeout() time.Duration {
|
||||
// 5s for queue waiting, computation and disk IO delay
|
||||
// + 2 * election timeout for possible leader election
|
||||
return 5*time.Second + 2*time.Duration(c.ElectionTicks)*time.Duration(c.TickMs)*time.Millisecond
|
||||
}
|
||||
|
||||
func (c *ServerConfig) electionTimeout() time.Duration {
|
||||
return time.Duration(c.ElectionTicks) * time.Duration(c.TickMs) * time.Millisecond
|
||||
}
|
||||
|
||||
func (c *ServerConfig) peerDialTimeout() time.Duration {
|
||||
// 1s for queue wait and system delay
|
||||
// + one RTT, which is smaller than 1/5 election timeout
|
||||
return time.Second + time.Duration(c.ElectionTicks)*time.Duration(c.TickMs)*time.Millisecond/5
|
||||
}
|
||||
|
||||
func (c *ServerConfig) PrintWithInitial() { c.print(true) }
|
||||
|
||||
func (c *ServerConfig) Print() { c.print(false) }
|
||||
|
||||
func (c *ServerConfig) print(initial bool) {
|
||||
plog.Infof("name = %s", c.Name)
|
||||
if c.ForceNewCluster {
|
||||
plog.Infof("force new cluster")
|
||||
}
|
||||
plog.Infof("data dir = %s", c.DataDir)
|
||||
plog.Infof("member dir = %s", c.MemberDir())
|
||||
if c.DedicatedWALDir != "" {
|
||||
plog.Infof("dedicated WAL dir = %s", c.DedicatedWALDir)
|
||||
}
|
||||
plog.Infof("heartbeat = %dms", c.TickMs)
|
||||
plog.Infof("election = %dms", c.ElectionTicks*int(c.TickMs))
|
||||
plog.Infof("snapshot count = %d", c.SnapCount)
|
||||
if len(c.DiscoveryURL) != 0 {
|
||||
plog.Infof("discovery URL= %s", c.DiscoveryURL)
|
||||
if len(c.DiscoveryProxy) != 0 {
|
||||
plog.Infof("discovery proxy = %s", c.DiscoveryProxy)
|
||||
}
|
||||
}
|
||||
plog.Infof("advertise client URLs = %s", c.ClientURLs)
|
||||
if initial {
|
||||
plog.Infof("initial advertise peer URLs = %s", c.PeerURLs)
|
||||
plog.Infof("initial cluster = %s", c.InitialPeerURLsMap)
|
||||
}
|
||||
}
|
||||
|
||||
func checkDuplicateURL(urlsmap types.URLsMap) bool {
|
||||
um := make(map[string]bool)
|
||||
for _, urls := range urlsmap {
|
||||
for _, url := range urls {
|
||||
u := url.String()
|
||||
if um[u] {
|
||||
return true
|
||||
}
|
||||
um[u] = true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *ServerConfig) bootstrapTimeout() time.Duration {
|
||||
if c.BootstrapTimeout != 0 {
|
||||
return c.BootstrapTimeout
|
||||
}
|
||||
return time.Second
|
||||
}
|
||||
25
vendor/github.com/coreos/etcd/etcdserver/consistent_index.go
generated
vendored
Normal file
25
vendor/github.com/coreos/etcd/etcdserver/consistent_index.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 etcdserver
|
||||
|
||||
// consistentIndex represents the offset of an entry in a consistent replica log.
|
||||
// It implements the storage.ConsistentIndexGetter interface.
|
||||
// It is always set to the offset of current entry before executing the entry,
|
||||
// so ConsistentWatchableKV could get the consistent index from it.
|
||||
type consistentIndex uint64
|
||||
|
||||
func (i *consistentIndex) setConsistentIndex(v uint64) { *i = consistentIndex(v) }
|
||||
|
||||
func (i *consistentIndex) ConsistentIndex() uint64 { return uint64(*i) }
|
||||
16
vendor/github.com/coreos/etcd/etcdserver/doc.go
generated
vendored
Normal file
16
vendor/github.com/coreos/etcd/etcdserver/doc.go
generated
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 etcdserver defines how etcd servers interact and store their states.
|
||||
package etcdserver
|
||||
42
vendor/github.com/coreos/etcd/etcdserver/errors.go
generated
vendored
Normal file
42
vendor/github.com/coreos/etcd/etcdserver/errors.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 etcdserver
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrUnknownMethod = errors.New("etcdserver: unknown method")
|
||||
ErrStopped = errors.New("etcdserver: server stopped")
|
||||
ErrCanceled = errors.New("etcdserver: request cancelled")
|
||||
ErrTimeout = errors.New("etcdserver: request timed out")
|
||||
ErrTimeoutDueToLeaderFail = errors.New("etcdserver: request timed out, possibly due to previous leader failure")
|
||||
ErrTimeoutDueToConnectionLost = errors.New("etcdserver: request timed out, possibly due to connection lost")
|
||||
ErrNotEnoughStartedMembers = errors.New("etcdserver: re-configuration failed due to not enough started members")
|
||||
ErrNoLeader = errors.New("etcdserver: no leader")
|
||||
ErrRequestTooLarge = errors.New("etcdserver: request is too large")
|
||||
ErrNoSpace = errors.New("etcdserver: no space")
|
||||
)
|
||||
|
||||
type DiscoveryError struct {
|
||||
Op string
|
||||
Err error
|
||||
}
|
||||
|
||||
func (e DiscoveryError) Error() string {
|
||||
return fmt.Sprintf("failed to %s discovery cluster (%v)", e.Op, e.Err)
|
||||
}
|
||||
997
vendor/github.com/coreos/etcd/etcdserver/etcdserverpb/etcdserver.pb.go
generated
vendored
Normal file
997
vendor/github.com/coreos/etcd/etcdserver/etcdserverpb/etcdserver.pb.go
generated
vendored
Normal file
@@ -0,0 +1,997 @@
|
||||
// Code generated by protoc-gen-gogo.
|
||||
// source: etcdserver.proto
|
||||
// DO NOT EDIT!
|
||||
|
||||
/*
|
||||
Package etcdserverpb is a generated protocol buffer package.
|
||||
|
||||
It is generated from these files:
|
||||
etcdserver.proto
|
||||
raft_internal.proto
|
||||
rpc.proto
|
||||
|
||||
It has these top-level messages:
|
||||
Request
|
||||
Metadata
|
||||
InternalRaftRequest
|
||||
EmptyResponse
|
||||
ResponseHeader
|
||||
RangeRequest
|
||||
RangeResponse
|
||||
PutRequest
|
||||
PutResponse
|
||||
DeleteRangeRequest
|
||||
DeleteRangeResponse
|
||||
RequestUnion
|
||||
ResponseUnion
|
||||
Compare
|
||||
TxnRequest
|
||||
TxnResponse
|
||||
CompactionRequest
|
||||
CompactionResponse
|
||||
HashRequest
|
||||
HashResponse
|
||||
WatchRequest
|
||||
WatchCreateRequest
|
||||
WatchCancelRequest
|
||||
WatchResponse
|
||||
LeaseGrantRequest
|
||||
LeaseGrantResponse
|
||||
LeaseRevokeRequest
|
||||
LeaseRevokeResponse
|
||||
LeaseKeepAliveRequest
|
||||
LeaseKeepAliveResponse
|
||||
Member
|
||||
MemberAddRequest
|
||||
MemberAddResponse
|
||||
MemberRemoveRequest
|
||||
MemberRemoveResponse
|
||||
MemberUpdateRequest
|
||||
MemberUpdateResponse
|
||||
MemberListRequest
|
||||
MemberListResponse
|
||||
DefragmentRequest
|
||||
DefragmentResponse
|
||||
AlarmRequest
|
||||
AlarmMember
|
||||
AlarmResponse
|
||||
StatusRequest
|
||||
StatusResponse
|
||||
AuthEnableRequest
|
||||
AuthDisableRequest
|
||||
AuthenticateRequest
|
||||
AuthUserAddRequest
|
||||
AuthUserGetRequest
|
||||
AuthUserDeleteRequest
|
||||
AuthUserChangePasswordRequest
|
||||
AuthUserGrantRequest
|
||||
AuthUserRevokeRequest
|
||||
AuthRoleAddRequest
|
||||
AuthRoleGetRequest
|
||||
AuthRoleDeleteRequest
|
||||
AuthRoleGrantRequest
|
||||
AuthRoleRevokeRequest
|
||||
AuthEnableResponse
|
||||
AuthDisableResponse
|
||||
AuthenticateResponse
|
||||
AuthUserAddResponse
|
||||
AuthUserGetResponse
|
||||
AuthUserDeleteResponse
|
||||
AuthUserChangePasswordResponse
|
||||
AuthUserGrantResponse
|
||||
AuthUserRevokeResponse
|
||||
AuthRoleAddResponse
|
||||
AuthRoleGetResponse
|
||||
AuthRoleDeleteResponse
|
||||
AuthRoleGrantResponse
|
||||
AuthRoleRevokeResponse
|
||||
*/
|
||||
package etcdserverpb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
proto "github.com/gogo/protobuf/proto"
|
||||
|
||||
math "math"
|
||||
)
|
||||
|
||||
import io "io"
|
||||
|
||||
// Reference imports to suppress errors if they are not otherwise used.
|
||||
var _ = proto.Marshal
|
||||
var _ = fmt.Errorf
|
||||
var _ = math.Inf
|
||||
|
||||
type Request struct {
|
||||
ID uint64 `protobuf:"varint,1,opt,name=ID" json:"ID"`
|
||||
Method string `protobuf:"bytes,2,opt,name=Method" json:"Method"`
|
||||
Path string `protobuf:"bytes,3,opt,name=Path" json:"Path"`
|
||||
Val string `protobuf:"bytes,4,opt,name=Val" json:"Val"`
|
||||
Dir bool `protobuf:"varint,5,opt,name=Dir" json:"Dir"`
|
||||
PrevValue string `protobuf:"bytes,6,opt,name=PrevValue" json:"PrevValue"`
|
||||
PrevIndex uint64 `protobuf:"varint,7,opt,name=PrevIndex" json:"PrevIndex"`
|
||||
PrevExist *bool `protobuf:"varint,8,opt,name=PrevExist" json:"PrevExist,omitempty"`
|
||||
Expiration int64 `protobuf:"varint,9,opt,name=Expiration" json:"Expiration"`
|
||||
Wait bool `protobuf:"varint,10,opt,name=Wait" json:"Wait"`
|
||||
Since uint64 `protobuf:"varint,11,opt,name=Since" json:"Since"`
|
||||
Recursive bool `protobuf:"varint,12,opt,name=Recursive" json:"Recursive"`
|
||||
Sorted bool `protobuf:"varint,13,opt,name=Sorted" json:"Sorted"`
|
||||
Quorum bool `protobuf:"varint,14,opt,name=Quorum" json:"Quorum"`
|
||||
Time int64 `protobuf:"varint,15,opt,name=Time" json:"Time"`
|
||||
Stream bool `protobuf:"varint,16,opt,name=Stream" json:"Stream"`
|
||||
Refresh *bool `protobuf:"varint,17,opt,name=Refresh" json:"Refresh,omitempty"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Request) Reset() { *m = Request{} }
|
||||
func (m *Request) String() string { return proto.CompactTextString(m) }
|
||||
func (*Request) ProtoMessage() {}
|
||||
|
||||
type Metadata struct {
|
||||
NodeID uint64 `protobuf:"varint,1,opt,name=NodeID" json:"NodeID"`
|
||||
ClusterID uint64 `protobuf:"varint,2,opt,name=ClusterID" json:"ClusterID"`
|
||||
XXX_unrecognized []byte `json:"-"`
|
||||
}
|
||||
|
||||
func (m *Metadata) Reset() { *m = Metadata{} }
|
||||
func (m *Metadata) String() string { return proto.CompactTextString(m) }
|
||||
func (*Metadata) ProtoMessage() {}
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*Request)(nil), "etcdserverpb.Request")
|
||||
proto.RegisterType((*Metadata)(nil), "etcdserverpb.Metadata")
|
||||
}
|
||||
func (m *Request) Marshal() (data []byte, err error) {
|
||||
size := m.Size()
|
||||
data = make([]byte, size)
|
||||
n, err := m.MarshalTo(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data[:n], nil
|
||||
}
|
||||
|
||||
func (m *Request) MarshalTo(data []byte) (int, error) {
|
||||
var i int
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
data[i] = 0x8
|
||||
i++
|
||||
i = encodeVarintEtcdserver(data, i, uint64(m.ID))
|
||||
data[i] = 0x12
|
||||
i++
|
||||
i = encodeVarintEtcdserver(data, i, uint64(len(m.Method)))
|
||||
i += copy(data[i:], m.Method)
|
||||
data[i] = 0x1a
|
||||
i++
|
||||
i = encodeVarintEtcdserver(data, i, uint64(len(m.Path)))
|
||||
i += copy(data[i:], m.Path)
|
||||
data[i] = 0x22
|
||||
i++
|
||||
i = encodeVarintEtcdserver(data, i, uint64(len(m.Val)))
|
||||
i += copy(data[i:], m.Val)
|
||||
data[i] = 0x28
|
||||
i++
|
||||
if m.Dir {
|
||||
data[i] = 1
|
||||
} else {
|
||||
data[i] = 0
|
||||
}
|
||||
i++
|
||||
data[i] = 0x32
|
||||
i++
|
||||
i = encodeVarintEtcdserver(data, i, uint64(len(m.PrevValue)))
|
||||
i += copy(data[i:], m.PrevValue)
|
||||
data[i] = 0x38
|
||||
i++
|
||||
i = encodeVarintEtcdserver(data, i, uint64(m.PrevIndex))
|
||||
if m.PrevExist != nil {
|
||||
data[i] = 0x40
|
||||
i++
|
||||
if *m.PrevExist {
|
||||
data[i] = 1
|
||||
} else {
|
||||
data[i] = 0
|
||||
}
|
||||
i++
|
||||
}
|
||||
data[i] = 0x48
|
||||
i++
|
||||
i = encodeVarintEtcdserver(data, i, uint64(m.Expiration))
|
||||
data[i] = 0x50
|
||||
i++
|
||||
if m.Wait {
|
||||
data[i] = 1
|
||||
} else {
|
||||
data[i] = 0
|
||||
}
|
||||
i++
|
||||
data[i] = 0x58
|
||||
i++
|
||||
i = encodeVarintEtcdserver(data, i, uint64(m.Since))
|
||||
data[i] = 0x60
|
||||
i++
|
||||
if m.Recursive {
|
||||
data[i] = 1
|
||||
} else {
|
||||
data[i] = 0
|
||||
}
|
||||
i++
|
||||
data[i] = 0x68
|
||||
i++
|
||||
if m.Sorted {
|
||||
data[i] = 1
|
||||
} else {
|
||||
data[i] = 0
|
||||
}
|
||||
i++
|
||||
data[i] = 0x70
|
||||
i++
|
||||
if m.Quorum {
|
||||
data[i] = 1
|
||||
} else {
|
||||
data[i] = 0
|
||||
}
|
||||
i++
|
||||
data[i] = 0x78
|
||||
i++
|
||||
i = encodeVarintEtcdserver(data, i, uint64(m.Time))
|
||||
data[i] = 0x80
|
||||
i++
|
||||
data[i] = 0x1
|
||||
i++
|
||||
if m.Stream {
|
||||
data[i] = 1
|
||||
} else {
|
||||
data[i] = 0
|
||||
}
|
||||
i++
|
||||
if m.Refresh != nil {
|
||||
data[i] = 0x88
|
||||
i++
|
||||
data[i] = 0x1
|
||||
i++
|
||||
if *m.Refresh {
|
||||
data[i] = 1
|
||||
} else {
|
||||
data[i] = 0
|
||||
}
|
||||
i++
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
i += copy(data[i:], m.XXX_unrecognized)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func (m *Metadata) Marshal() (data []byte, err error) {
|
||||
size := m.Size()
|
||||
data = make([]byte, size)
|
||||
n, err := m.MarshalTo(data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return data[:n], nil
|
||||
}
|
||||
|
||||
func (m *Metadata) MarshalTo(data []byte) (int, error) {
|
||||
var i int
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
data[i] = 0x8
|
||||
i++
|
||||
i = encodeVarintEtcdserver(data, i, uint64(m.NodeID))
|
||||
data[i] = 0x10
|
||||
i++
|
||||
i = encodeVarintEtcdserver(data, i, uint64(m.ClusterID))
|
||||
if m.XXX_unrecognized != nil {
|
||||
i += copy(data[i:], m.XXX_unrecognized)
|
||||
}
|
||||
return i, nil
|
||||
}
|
||||
|
||||
func encodeFixed64Etcdserver(data []byte, offset int, v uint64) int {
|
||||
data[offset] = uint8(v)
|
||||
data[offset+1] = uint8(v >> 8)
|
||||
data[offset+2] = uint8(v >> 16)
|
||||
data[offset+3] = uint8(v >> 24)
|
||||
data[offset+4] = uint8(v >> 32)
|
||||
data[offset+5] = uint8(v >> 40)
|
||||
data[offset+6] = uint8(v >> 48)
|
||||
data[offset+7] = uint8(v >> 56)
|
||||
return offset + 8
|
||||
}
|
||||
func encodeFixed32Etcdserver(data []byte, offset int, v uint32) int {
|
||||
data[offset] = uint8(v)
|
||||
data[offset+1] = uint8(v >> 8)
|
||||
data[offset+2] = uint8(v >> 16)
|
||||
data[offset+3] = uint8(v >> 24)
|
||||
return offset + 4
|
||||
}
|
||||
func encodeVarintEtcdserver(data []byte, offset int, v uint64) int {
|
||||
for v >= 1<<7 {
|
||||
data[offset] = uint8(v&0x7f | 0x80)
|
||||
v >>= 7
|
||||
offset++
|
||||
}
|
||||
data[offset] = uint8(v)
|
||||
return offset + 1
|
||||
}
|
||||
func (m *Request) Size() (n int) {
|
||||
var l int
|
||||
_ = l
|
||||
n += 1 + sovEtcdserver(uint64(m.ID))
|
||||
l = len(m.Method)
|
||||
n += 1 + l + sovEtcdserver(uint64(l))
|
||||
l = len(m.Path)
|
||||
n += 1 + l + sovEtcdserver(uint64(l))
|
||||
l = len(m.Val)
|
||||
n += 1 + l + sovEtcdserver(uint64(l))
|
||||
n += 2
|
||||
l = len(m.PrevValue)
|
||||
n += 1 + l + sovEtcdserver(uint64(l))
|
||||
n += 1 + sovEtcdserver(uint64(m.PrevIndex))
|
||||
if m.PrevExist != nil {
|
||||
n += 2
|
||||
}
|
||||
n += 1 + sovEtcdserver(uint64(m.Expiration))
|
||||
n += 2
|
||||
n += 1 + sovEtcdserver(uint64(m.Since))
|
||||
n += 2
|
||||
n += 2
|
||||
n += 2
|
||||
n += 1 + sovEtcdserver(uint64(m.Time))
|
||||
n += 3
|
||||
if m.Refresh != nil {
|
||||
n += 3
|
||||
}
|
||||
if m.XXX_unrecognized != nil {
|
||||
n += len(m.XXX_unrecognized)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *Metadata) Size() (n int) {
|
||||
var l int
|
||||
_ = l
|
||||
n += 1 + sovEtcdserver(uint64(m.NodeID))
|
||||
n += 1 + sovEtcdserver(uint64(m.ClusterID))
|
||||
if m.XXX_unrecognized != nil {
|
||||
n += len(m.XXX_unrecognized)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
func sovEtcdserver(x uint64) (n int) {
|
||||
for {
|
||||
n++
|
||||
x >>= 7
|
||||
if x == 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
func sozEtcdserver(x uint64) (n int) {
|
||||
return sovEtcdserver(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
}
|
||||
func (m *Request) Unmarshal(data []byte) error {
|
||||
l := len(data)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: Request: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: Request: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field ID", wireType)
|
||||
}
|
||||
m.ID = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
m.ID |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Method", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthEtcdserver
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Method = string(data[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
case 3:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Path", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthEtcdserver
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Path = string(data[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
case 4:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Val", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthEtcdserver
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Val = string(data[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
case 5:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Dir", wireType)
|
||||
}
|
||||
var v int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
v |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.Dir = bool(v != 0)
|
||||
case 6:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field PrevValue", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthEtcdserver
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.PrevValue = string(data[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
case 7:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field PrevIndex", wireType)
|
||||
}
|
||||
m.PrevIndex = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
m.PrevIndex |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 8:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field PrevExist", wireType)
|
||||
}
|
||||
var v int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
v |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
b := bool(v != 0)
|
||||
m.PrevExist = &b
|
||||
case 9:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Expiration", wireType)
|
||||
}
|
||||
m.Expiration = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
m.Expiration |= (int64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 10:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Wait", wireType)
|
||||
}
|
||||
var v int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
v |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.Wait = bool(v != 0)
|
||||
case 11:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Since", wireType)
|
||||
}
|
||||
m.Since = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
m.Since |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 12:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Recursive", wireType)
|
||||
}
|
||||
var v int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
v |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.Recursive = bool(v != 0)
|
||||
case 13:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Sorted", wireType)
|
||||
}
|
||||
var v int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
v |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.Sorted = bool(v != 0)
|
||||
case 14:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Quorum", wireType)
|
||||
}
|
||||
var v int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
v |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.Quorum = bool(v != 0)
|
||||
case 15:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Time", wireType)
|
||||
}
|
||||
m.Time = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
m.Time |= (int64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 16:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Stream", wireType)
|
||||
}
|
||||
var v int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
v |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
m.Stream = bool(v != 0)
|
||||
case 17:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Refresh", wireType)
|
||||
}
|
||||
var v int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
v |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
b := bool(v != 0)
|
||||
m.Refresh = &b
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipEtcdserver(data[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthEtcdserver
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.XXX_unrecognized = append(m.XXX_unrecognized, data[iNdEx:iNdEx+skippy]...)
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *Metadata) Unmarshal(data []byte) error {
|
||||
l := len(data)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: Metadata: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: Metadata: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field NodeID", wireType)
|
||||
}
|
||||
m.NodeID = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
m.NodeID |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
case 2:
|
||||
if wireType != 0 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field ClusterID", wireType)
|
||||
}
|
||||
m.ClusterID = 0
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
m.ClusterID |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipEtcdserver(data[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if skippy < 0 {
|
||||
return ErrInvalidLengthEtcdserver
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.XXX_unrecognized = append(m.XXX_unrecognized, data[iNdEx:iNdEx+skippy]...)
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func skipEtcdserver(data []byte) (n int, err error) {
|
||||
l := len(data)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
wire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
wireType := int(wire & 0x7)
|
||||
switch wireType {
|
||||
case 0:
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx++
|
||||
if data[iNdEx-1] < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 1:
|
||||
iNdEx += 8
|
||||
return iNdEx, nil
|
||||
case 2:
|
||||
var length int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
length |= (int(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
iNdEx += length
|
||||
if length < 0 {
|
||||
return 0, ErrInvalidLengthEtcdserver
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 3:
|
||||
for {
|
||||
var innerWire uint64
|
||||
var start int = iNdEx
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return 0, ErrIntOverflowEtcdserver
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return 0, io.ErrUnexpectedEOF
|
||||
}
|
||||
b := data[iNdEx]
|
||||
iNdEx++
|
||||
innerWire |= (uint64(b) & 0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
innerWireType := int(innerWire & 0x7)
|
||||
if innerWireType == 4 {
|
||||
break
|
||||
}
|
||||
next, err := skipEtcdserver(data[start:])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
iNdEx = start + next
|
||||
}
|
||||
return iNdEx, nil
|
||||
case 4:
|
||||
return iNdEx, nil
|
||||
case 5:
|
||||
iNdEx += 4
|
||||
return iNdEx, nil
|
||||
default:
|
||||
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
|
||||
}
|
||||
}
|
||||
panic("unreachable")
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidLengthEtcdserver = fmt.Errorf("proto: negative length found during unmarshaling")
|
||||
ErrIntOverflowEtcdserver = fmt.Errorf("proto: integer overflow")
|
||||
)
|
||||
34
vendor/github.com/coreos/etcd/etcdserver/etcdserverpb/etcdserver.proto
generated
vendored
Normal file
34
vendor/github.com/coreos/etcd/etcdserver/etcdserverpb/etcdserver.proto
generated
vendored
Normal file
@@ -0,0 +1,34 @@
|
||||
syntax = "proto2";
|
||||
package etcdserverpb;
|
||||
|
||||
import "gogoproto/gogo.proto";
|
||||
|
||||
option (gogoproto.marshaler_all) = true;
|
||||
option (gogoproto.sizer_all) = true;
|
||||
option (gogoproto.unmarshaler_all) = true;
|
||||
option (gogoproto.goproto_getters_all) = false;
|
||||
|
||||
message Request {
|
||||
optional uint64 ID = 1 [(gogoproto.nullable) = false];
|
||||
optional string Method = 2 [(gogoproto.nullable) = false];
|
||||
optional string Path = 3 [(gogoproto.nullable) = false];
|
||||
optional string Val = 4 [(gogoproto.nullable) = false];
|
||||
optional bool Dir = 5 [(gogoproto.nullable) = false];
|
||||
optional string PrevValue = 6 [(gogoproto.nullable) = false];
|
||||
optional uint64 PrevIndex = 7 [(gogoproto.nullable) = false];
|
||||
optional bool PrevExist = 8 [(gogoproto.nullable) = true];
|
||||
optional int64 Expiration = 9 [(gogoproto.nullable) = false];
|
||||
optional bool Wait = 10 [(gogoproto.nullable) = false];
|
||||
optional uint64 Since = 11 [(gogoproto.nullable) = false];
|
||||
optional bool Recursive = 12 [(gogoproto.nullable) = false];
|
||||
optional bool Sorted = 13 [(gogoproto.nullable) = false];
|
||||
optional bool Quorum = 14 [(gogoproto.nullable) = false];
|
||||
optional int64 Time = 15 [(gogoproto.nullable) = false];
|
||||
optional bool Stream = 16 [(gogoproto.nullable) = false];
|
||||
optional bool Refresh = 17 [(gogoproto.nullable) = true];
|
||||
}
|
||||
|
||||
message Metadata {
|
||||
optional uint64 NodeID = 1 [(gogoproto.nullable) = false];
|
||||
optional uint64 ClusterID = 2 [(gogoproto.nullable) = false];
|
||||
}
|
||||
1033
vendor/github.com/coreos/etcd/etcdserver/etcdserverpb/raft_internal.pb.go
generated
vendored
Normal file
1033
vendor/github.com/coreos/etcd/etcdserver/etcdserverpb/raft_internal.pb.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
38
vendor/github.com/coreos/etcd/etcdserver/etcdserverpb/raft_internal.proto
generated
vendored
Normal file
38
vendor/github.com/coreos/etcd/etcdserver/etcdserverpb/raft_internal.proto
generated
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
syntax = "proto3";
|
||||
package etcdserverpb;
|
||||
|
||||
import "gogoproto/gogo.proto";
|
||||
import "etcdserver.proto";
|
||||
import "rpc.proto";
|
||||
|
||||
option (gogoproto.marshaler_all) = true;
|
||||
option (gogoproto.sizer_all) = true;
|
||||
option (gogoproto.unmarshaler_all) = true;
|
||||
option (gogoproto.goproto_getters_all) = false;
|
||||
|
||||
// An InternalRaftRequest is the union of all requests which can be
|
||||
// sent via raft.
|
||||
message InternalRaftRequest {
|
||||
uint64 ID = 1;
|
||||
Request v2 = 2;
|
||||
|
||||
RangeRequest range = 3;
|
||||
PutRequest put = 4;
|
||||
DeleteRangeRequest delete_range = 5;
|
||||
TxnRequest txn = 6;
|
||||
CompactionRequest compaction = 7;
|
||||
|
||||
LeaseGrantRequest lease_grant = 8;
|
||||
LeaseRevokeRequest lease_revoke = 9;
|
||||
|
||||
AuthEnableRequest auth_enable = 10;
|
||||
AuthUserAddRequest auth_user_add = 11;
|
||||
AuthUserDeleteRequest auth_user_delete = 12;
|
||||
AuthUserChangePasswordRequest auth_user_change_password = 13;
|
||||
AuthRoleAddRequest auth_role_add = 14;
|
||||
|
||||
AlarmRequest alarm = 15;
|
||||
}
|
||||
|
||||
message EmptyResponse {
|
||||
}
|
||||
12842
vendor/github.com/coreos/etcd/etcdserver/etcdserverpb/rpc.pb.go
generated
vendored
Normal file
12842
vendor/github.com/coreos/etcd/etcdserver/etcdserverpb/rpc.pb.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
587
vendor/github.com/coreos/etcd/etcdserver/etcdserverpb/rpc.proto
generated
vendored
Normal file
587
vendor/github.com/coreos/etcd/etcdserver/etcdserverpb/rpc.proto
generated
vendored
Normal file
@@ -0,0 +1,587 @@
|
||||
syntax = "proto3";
|
||||
package etcdserverpb;
|
||||
|
||||
import "gogoproto/gogo.proto";
|
||||
import "etcd/storage/storagepb/kv.proto";
|
||||
|
||||
option (gogoproto.marshaler_all) = true;
|
||||
option (gogoproto.unmarshaler_all) = true;
|
||||
|
||||
service KV {
|
||||
// Range gets the keys in the range from the store.
|
||||
rpc Range(RangeRequest) returns (RangeResponse) {}
|
||||
|
||||
// Put puts the given key into the store.
|
||||
// A put request increases the revision of the store,
|
||||
// and generates one event in the event history.
|
||||
rpc Put(PutRequest) returns (PutResponse) {}
|
||||
|
||||
// Delete deletes the given range from the store.
|
||||
// A delete request increase the revision of the store,
|
||||
// and generates one event in the event history.
|
||||
rpc DeleteRange(DeleteRangeRequest) returns (DeleteRangeResponse) {}
|
||||
|
||||
// Txn processes all the requests in one transaction.
|
||||
// A txn request increases the revision of the store,
|
||||
// and generates events with the same revision in the event history.
|
||||
// It is not allowed to modify the same key several times within one txn.
|
||||
rpc Txn(TxnRequest) returns (TxnResponse) {}
|
||||
|
||||
// Compact compacts the event history in etcd. User should compact the
|
||||
// event history periodically, or it will grow infinitely.
|
||||
rpc Compact(CompactionRequest) returns (CompactionResponse) {}
|
||||
}
|
||||
|
||||
service Watch {
|
||||
// Watch watches the events happening or happened. Both input and output
|
||||
// are stream. One watch rpc can watch for multiple keys or prefixs and
|
||||
// get a stream of events. The whole events history can be watched unless
|
||||
// compacted.
|
||||
rpc Watch(stream WatchRequest) returns (stream WatchResponse) {}
|
||||
}
|
||||
|
||||
service Lease {
|
||||
// LeaseGrant creates a lease. A lease has a TTL. The lease will expire if the
|
||||
// server does not receive a keepAlive within TTL from the lease holder.
|
||||
// All keys attached to the lease will be expired and deleted if the lease expires.
|
||||
// The key expiration generates an event in event history.
|
||||
rpc LeaseGrant(LeaseGrantRequest) returns (LeaseGrantResponse) {}
|
||||
|
||||
// LeaseRevoke revokes a lease. All the key attached to the lease will be expired and deleted.
|
||||
rpc LeaseRevoke(LeaseRevokeRequest) returns (LeaseRevokeResponse) {}
|
||||
|
||||
// KeepAlive keeps the lease alive.
|
||||
rpc LeaseKeepAlive(stream LeaseKeepAliveRequest) returns (stream LeaseKeepAliveResponse) {}
|
||||
|
||||
// TODO(xiangli) List all existing Leases?
|
||||
// TODO(xiangli) Get details information (expirations, leased keys, etc.) of a lease?
|
||||
}
|
||||
|
||||
service Cluster {
|
||||
// MemberAdd adds a member into the cluster.
|
||||
rpc MemberAdd(MemberAddRequest) returns (MemberAddResponse) {}
|
||||
|
||||
// MemberRemove removes an existing member from the cluster.
|
||||
rpc MemberRemove(MemberRemoveRequest) returns (MemberRemoveResponse) {}
|
||||
|
||||
// MemberUpdate updates the member configuration.
|
||||
rpc MemberUpdate(MemberUpdateRequest) returns (MemberUpdateResponse) {}
|
||||
|
||||
// MemberList lists all the members in the cluster.
|
||||
rpc MemberList(MemberListRequest) returns (MemberListResponse) {}
|
||||
}
|
||||
|
||||
service Maintenance {
|
||||
// Alarm activates, deactivates, and queries alarms regarding cluster health.
|
||||
rpc Alarm(AlarmRequest) returns (AlarmResponse) {}
|
||||
|
||||
// Status gets the status of the member.
|
||||
rpc Status(StatusRequest) returns (StatusResponse) {}
|
||||
|
||||
rpc Defragment(DefragmentRequest) returns (DefragmentResponse) {}
|
||||
|
||||
// Hash returns the hash of the local KV state for consistency checking purpose.
|
||||
// This is designed for testing; do not use this in production when there
|
||||
// are ongoing transactions.
|
||||
rpc Hash(HashRequest) returns (HashResponse) {}
|
||||
}
|
||||
|
||||
service Auth {
|
||||
// AuthEnable enables authentication.
|
||||
rpc AuthEnable(AuthEnableRequest) returns (AuthEnableResponse) {}
|
||||
|
||||
// AuthDisable disables authentication.
|
||||
rpc AuthDisable(AuthDisableRequest) returns (AuthDisableResponse) {}
|
||||
|
||||
// Authenticate processes authenticate request.
|
||||
rpc Authenticate(AuthenticateRequest) returns (AuthenticateResponse) {}
|
||||
|
||||
// UserAdd adds a new user.
|
||||
rpc UserAdd(AuthUserAddRequest) returns (AuthUserAddResponse) {}
|
||||
|
||||
// UserGet gets a detailed information of a user or lists entire users.
|
||||
rpc UserGet(AuthUserGetRequest) returns (AuthUserGetResponse) {}
|
||||
|
||||
// UserDelete deletes a specified user.
|
||||
rpc UserDelete(AuthUserDeleteRequest) returns (AuthUserDeleteResponse) {}
|
||||
|
||||
// UserChangePassword changes password of a specified user.
|
||||
rpc UserChangePassword(AuthUserChangePasswordRequest) returns (AuthUserChangePasswordResponse) {}
|
||||
|
||||
// UserGrant grants a role to a specified user.
|
||||
rpc UserGrant(AuthUserGrantRequest) returns (AuthUserGrantResponse) {}
|
||||
|
||||
// UserRevoke revokes a role of specified user.
|
||||
rpc UserRevoke(AuthUserRevokeRequest) returns (AuthUserRevokeResponse) {}
|
||||
|
||||
// RoleAdd adds a new role.
|
||||
rpc RoleAdd(AuthRoleAddRequest) returns (AuthRoleAddResponse) {}
|
||||
|
||||
// RoleGet gets a detailed information of a role or lists entire roles.
|
||||
rpc RoleGet(AuthRoleGetRequest) returns (AuthRoleGetResponse) {}
|
||||
|
||||
// RoleDelete deletes a specified role.
|
||||
rpc RoleDelete(AuthRoleDeleteRequest) returns (AuthRoleDeleteResponse) {}
|
||||
|
||||
// RoleGrant grants a permission of a specified key or range to a specified role.
|
||||
rpc RoleGrant(AuthRoleGrantRequest) returns (AuthRoleGrantResponse) {}
|
||||
|
||||
// RoleRevoke revokes a key or range permission of a specified role.
|
||||
rpc RoleRevoke(AuthRoleRevokeRequest) returns (AuthRoleRevokeResponse) {}
|
||||
}
|
||||
|
||||
message ResponseHeader {
|
||||
uint64 cluster_id = 1;
|
||||
uint64 member_id = 2;
|
||||
// revision of the store when the request was applied.
|
||||
int64 revision = 3;
|
||||
// term of raft when the request was applied.
|
||||
uint64 raft_term = 4;
|
||||
}
|
||||
|
||||
message RangeRequest {
|
||||
enum SortOrder {
|
||||
NONE = 0; // default, no sorting
|
||||
ASCEND = 1; // lowest target value first
|
||||
DESCEND = 2; // highest target value first
|
||||
}
|
||||
enum SortTarget {
|
||||
KEY = 0;
|
||||
VERSION = 1;
|
||||
CREATE = 2;
|
||||
MOD = 3;
|
||||
VALUE = 4;
|
||||
}
|
||||
|
||||
// if the range_end is not given, the request returns the key.
|
||||
bytes key = 1;
|
||||
// if the range_end is given, it gets the keys in range [key, range_end)
|
||||
// if range_end is nonempty, otherwise it returns all keys >= key.
|
||||
bytes range_end = 2;
|
||||
// limit the number of keys returned.
|
||||
int64 limit = 3;
|
||||
// range over the store at the given revision.
|
||||
// if revision is less or equal to zero, range over the newest store.
|
||||
// if the revision has been compacted, ErrCompaction will be returned in
|
||||
// response.
|
||||
int64 revision = 4;
|
||||
|
||||
// sort_order is the requested order for returned the results
|
||||
SortOrder sort_order = 5;
|
||||
|
||||
// sort_target is the kv field to use for sorting
|
||||
SortTarget sort_target = 6;
|
||||
|
||||
// range request is linearizable by default. Linearizable requests has a higher
|
||||
// latency and lower throughput than serializable request.
|
||||
// To reduce latency, serializable can be set. If serializable is set, range request
|
||||
// will be serializable, but not linearizable with other requests.
|
||||
// Serializable range can be served locally without waiting for other nodes in the cluster.
|
||||
bool serializable = 7;
|
||||
}
|
||||
|
||||
message RangeResponse {
|
||||
ResponseHeader header = 1;
|
||||
repeated storagepb.KeyValue kvs = 2;
|
||||
// more indicates if there are more keys to return in the requested range.
|
||||
bool more = 3;
|
||||
}
|
||||
|
||||
message PutRequest {
|
||||
bytes key = 1;
|
||||
bytes value = 2;
|
||||
int64 lease = 3;
|
||||
}
|
||||
|
||||
message PutResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message DeleteRangeRequest {
|
||||
// if the range_end is not given, the request deletes the key.
|
||||
bytes key = 1;
|
||||
// if the range_end is given, it deletes the keys in range [key, range_end).
|
||||
bytes range_end = 2;
|
||||
}
|
||||
|
||||
message DeleteRangeResponse {
|
||||
ResponseHeader header = 1;
|
||||
// Deleted is the number of keys that got deleted.
|
||||
int64 deleted = 2;
|
||||
}
|
||||
|
||||
message RequestUnion {
|
||||
oneof request {
|
||||
RangeRequest request_range = 1;
|
||||
PutRequest request_put = 2;
|
||||
DeleteRangeRequest request_delete_range = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message ResponseUnion {
|
||||
oneof response {
|
||||
RangeResponse response_range = 1;
|
||||
PutResponse response_put = 2;
|
||||
DeleteRangeResponse response_delete_range = 3;
|
||||
}
|
||||
}
|
||||
|
||||
message Compare {
|
||||
enum CompareResult {
|
||||
EQUAL = 0;
|
||||
GREATER = 1;
|
||||
LESS = 2;
|
||||
}
|
||||
enum CompareTarget {
|
||||
VERSION = 0;
|
||||
CREATE = 1;
|
||||
MOD = 2;
|
||||
VALUE= 3;
|
||||
}
|
||||
CompareResult result = 1;
|
||||
CompareTarget target = 2;
|
||||
// key path
|
||||
bytes key = 3;
|
||||
oneof target_union {
|
||||
// version of the given key
|
||||
int64 version = 4;
|
||||
// create revision of the given key
|
||||
int64 create_revision = 5;
|
||||
// last modified revision of the given key
|
||||
int64 mod_revision = 6;
|
||||
// value of the given key
|
||||
bytes value = 7;
|
||||
}
|
||||
}
|
||||
|
||||
// If the comparisons succeed, then the success requests will be processed in order,
|
||||
// and the response will contain their respective responses in order.
|
||||
// If the comparisons fail, then the failure requests will be processed in order,
|
||||
// and the response will contain their respective responses in order.
|
||||
|
||||
// From google paxosdb paper:
|
||||
// Our implementation hinges around a powerful primitive which we call MultiOp. All other database
|
||||
// operations except for iteration are implemented as a single call to MultiOp. A MultiOp is applied atomically
|
||||
// and consists of three components:
|
||||
// 1. A list of tests called guard. Each test in guard checks a single entry in the database. It may check
|
||||
// for the absence or presence of a value, or compare with a given value. Two different tests in the guard
|
||||
// may apply to the same or different entries in the database. All tests in the guard are applied and
|
||||
// MultiOp returns the results. If all tests are true, MultiOp executes t op (see item 2 below), otherwise
|
||||
// it executes f op (see item 3 below).
|
||||
// 2. A list of database operations called t op. Each operation in the list is either an insert, delete, or
|
||||
// lookup operation, and applies to a single database entry. Two different operations in the list may apply
|
||||
// to the same or different entries in the database. These operations are executed
|
||||
// if guard evaluates to
|
||||
// true.
|
||||
// 3. A list of database operations called f op. Like t op, but executed if guard evaluates to false.
|
||||
message TxnRequest {
|
||||
repeated Compare compare = 1;
|
||||
repeated RequestUnion success = 2;
|
||||
repeated RequestUnion failure = 3;
|
||||
}
|
||||
|
||||
message TxnResponse {
|
||||
ResponseHeader header = 1;
|
||||
bool succeeded = 2;
|
||||
repeated ResponseUnion responses = 3;
|
||||
}
|
||||
|
||||
// Compaction compacts the kv store upto the given revision (including).
|
||||
// It removes the old versions of a key. It keeps the newest version of
|
||||
// the key even if its latest modification revision is smaller than the given
|
||||
// revision.
|
||||
message CompactionRequest {
|
||||
int64 revision = 1;
|
||||
// physical is set so the RPC will wait until the compaction is physically
|
||||
// applied to the local database such that compacted entries are totally
|
||||
// removed from the backing store.
|
||||
bool physical = 2;
|
||||
}
|
||||
|
||||
message CompactionResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message HashRequest {
|
||||
}
|
||||
|
||||
message HashResponse {
|
||||
ResponseHeader header = 1;
|
||||
uint32 hash = 2;
|
||||
}
|
||||
|
||||
message WatchRequest {
|
||||
oneof request_union {
|
||||
WatchCreateRequest create_request = 1;
|
||||
WatchCancelRequest cancel_request = 2;
|
||||
}
|
||||
}
|
||||
|
||||
message WatchCreateRequest {
|
||||
// the key to be watched
|
||||
bytes key = 1;
|
||||
// if the range_end is given, keys in [key, range_end) are watched
|
||||
// NOTE: only range_end == prefixEnd(key) is accepted now
|
||||
bytes range_end = 2;
|
||||
// start_revision is an optional revision (including) to watch from. No start_revision is "now".
|
||||
int64 start_revision = 3;
|
||||
// if progress_notify is set, etcd server sends WatchResponse with empty events to the
|
||||
// created watcher when there are no recent events. It is useful when clients want always to be
|
||||
// able to recover a disconnected watcher from a recent known revision.
|
||||
// etcdsever can decide how long it should send a notification based on current load.
|
||||
bool progress_notify = 4;
|
||||
}
|
||||
|
||||
message WatchCancelRequest {
|
||||
int64 watch_id = 1;
|
||||
}
|
||||
|
||||
message WatchResponse {
|
||||
ResponseHeader header = 1;
|
||||
// watch_id is the ID of the watching the response sent to.
|
||||
int64 watch_id = 2;
|
||||
// If the response is for a create watch request, created is set to true.
|
||||
// Client should record the watch_id and prepare for receiving events for
|
||||
// that watching from the same stream.
|
||||
// All events sent to the created watching will attach with the same watch_id.
|
||||
bool created = 3;
|
||||
// If the response is for a cancel watch request, cancel is set to true.
|
||||
// No further events will be sent to the canceled watching.
|
||||
bool canceled = 4;
|
||||
// CompactRevision is set to the minimum index if a watching tries to watch
|
||||
// at a compacted index.
|
||||
//
|
||||
// This happens when creating a watching at a compacted revision or the watching cannot
|
||||
// catch up with the progress of the KV.
|
||||
//
|
||||
// Client should treat the watching as canceled and should not try to create any
|
||||
// watching with same start_revision again.
|
||||
int64 compact_revision = 5;
|
||||
|
||||
repeated storagepb.Event events = 11;
|
||||
}
|
||||
|
||||
message LeaseGrantRequest {
|
||||
// advisory ttl in seconds
|
||||
int64 TTL = 1;
|
||||
// requested ID to create; 0 lets lessor choose
|
||||
int64 ID = 2;
|
||||
}
|
||||
|
||||
message LeaseGrantResponse {
|
||||
ResponseHeader header = 1;
|
||||
int64 ID = 2;
|
||||
// server decided ttl in second
|
||||
int64 TTL = 3;
|
||||
string error = 4;
|
||||
}
|
||||
|
||||
message LeaseRevokeRequest {
|
||||
int64 ID = 1;
|
||||
}
|
||||
|
||||
message LeaseRevokeResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message LeaseKeepAliveRequest {
|
||||
int64 ID = 1;
|
||||
}
|
||||
|
||||
message LeaseKeepAliveResponse {
|
||||
ResponseHeader header = 1;
|
||||
int64 ID = 2;
|
||||
int64 TTL = 3;
|
||||
}
|
||||
|
||||
message Member {
|
||||
uint64 ID = 1;
|
||||
// If the member is not started, name will be an empty string.
|
||||
string name = 2;
|
||||
bool IsLeader = 3;
|
||||
repeated string peerURLs = 4;
|
||||
// If the member is not started, client_URLs will be an zero length
|
||||
// string array.
|
||||
repeated string clientURLs = 5;
|
||||
}
|
||||
|
||||
message MemberAddRequest {
|
||||
repeated string peerURLs = 1;
|
||||
}
|
||||
|
||||
message MemberAddResponse {
|
||||
ResponseHeader header = 1;
|
||||
Member member = 2;
|
||||
}
|
||||
|
||||
message MemberRemoveRequest {
|
||||
uint64 ID = 1;
|
||||
}
|
||||
|
||||
message MemberRemoveResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message MemberUpdateRequest {
|
||||
uint64 ID = 1;
|
||||
repeated string peerURLs = 2;
|
||||
}
|
||||
|
||||
message MemberUpdateResponse{
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message MemberListRequest {
|
||||
}
|
||||
|
||||
message MemberListResponse {
|
||||
ResponseHeader header = 1;
|
||||
repeated Member members = 2;
|
||||
}
|
||||
|
||||
message DefragmentRequest {
|
||||
|
||||
}
|
||||
|
||||
message DefragmentResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
enum AlarmType {
|
||||
NONE = 0; // default, used to query if any alarm is active
|
||||
NOSPACE = 1;
|
||||
}
|
||||
|
||||
message AlarmRequest {
|
||||
enum AlarmAction {
|
||||
GET = 0;
|
||||
ACTIVATE = 1;
|
||||
DEACTIVATE = 2;
|
||||
}
|
||||
AlarmAction action = 1;
|
||||
// MemberID is the member raising the alarm request
|
||||
uint64 memberID = 2;
|
||||
AlarmType alarm = 3;
|
||||
}
|
||||
|
||||
message AlarmMember {
|
||||
uint64 memberID = 1;
|
||||
AlarmType alarm = 2;
|
||||
}
|
||||
|
||||
message AlarmResponse {
|
||||
ResponseHeader header = 1;
|
||||
repeated AlarmMember alarms = 2;
|
||||
}
|
||||
|
||||
message StatusRequest {
|
||||
}
|
||||
|
||||
message StatusResponse {
|
||||
ResponseHeader header = 1;
|
||||
string version = 2;
|
||||
}
|
||||
|
||||
message AuthEnableRequest {
|
||||
}
|
||||
|
||||
message AuthDisableRequest {
|
||||
}
|
||||
|
||||
message AuthenticateRequest {
|
||||
}
|
||||
|
||||
message AuthUserAddRequest {
|
||||
string name = 1;
|
||||
string password = 2;
|
||||
}
|
||||
|
||||
message AuthUserGetRequest {
|
||||
}
|
||||
|
||||
message AuthUserDeleteRequest {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message AuthUserChangePasswordRequest {
|
||||
string name = 1;
|
||||
string password = 2;
|
||||
}
|
||||
|
||||
message AuthUserGrantRequest {
|
||||
}
|
||||
|
||||
message AuthUserRevokeRequest {
|
||||
}
|
||||
|
||||
message AuthRoleAddRequest {
|
||||
string name = 1;
|
||||
}
|
||||
|
||||
message AuthRoleGetRequest {
|
||||
}
|
||||
|
||||
message AuthRoleDeleteRequest {
|
||||
}
|
||||
|
||||
message AuthRoleGrantRequest {
|
||||
}
|
||||
|
||||
message AuthRoleRevokeRequest {
|
||||
}
|
||||
|
||||
message AuthEnableResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message AuthDisableResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message AuthenticateResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message AuthUserAddResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message AuthUserGetResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message AuthUserDeleteResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message AuthUserChangePasswordResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message AuthUserGrantResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message AuthUserRevokeResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message AuthRoleAddResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message AuthRoleGetResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message AuthRoleDeleteResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message AuthRoleGrantResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
|
||||
message AuthRoleRevokeResponse {
|
||||
ResponseHeader header = 1;
|
||||
}
|
||||
489
vendor/github.com/coreos/etcd/etcdserver/membership/cluster.go
generated
vendored
Normal file
489
vendor/github.com/coreos/etcd/etcdserver/membership/cluster.go
generated
vendored
Normal file
@@ -0,0 +1,489 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 membership
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/sha1"
|
||||
"encoding/binary"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
"sort"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/coreos/etcd/pkg/netutil"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/etcd/raft"
|
||||
"github.com/coreos/etcd/raft/raftpb"
|
||||
"github.com/coreos/etcd/storage/backend"
|
||||
"github.com/coreos/etcd/store"
|
||||
"github.com/coreos/etcd/version"
|
||||
"github.com/coreos/go-semver/semver"
|
||||
)
|
||||
|
||||
// RaftCluster is a list of Members that belong to the same raft cluster
|
||||
type RaftCluster struct {
|
||||
id types.ID
|
||||
token string
|
||||
|
||||
store store.Store
|
||||
be backend.Backend
|
||||
|
||||
sync.Mutex // guards the fields below
|
||||
version *semver.Version
|
||||
members map[types.ID]*Member
|
||||
// removed contains the ids of removed members in the cluster.
|
||||
// removed id cannot be reused.
|
||||
removed map[types.ID]bool
|
||||
}
|
||||
|
||||
func NewClusterFromURLsMap(token string, urlsmap types.URLsMap) (*RaftCluster, error) {
|
||||
c := NewCluster(token)
|
||||
for name, urls := range urlsmap {
|
||||
m := NewMember(name, urls, token, nil)
|
||||
if _, ok := c.members[m.ID]; ok {
|
||||
return nil, fmt.Errorf("member exists with identical ID %v", m)
|
||||
}
|
||||
if uint64(m.ID) == raft.None {
|
||||
return nil, fmt.Errorf("cannot use %x as member id", raft.None)
|
||||
}
|
||||
c.members[m.ID] = m
|
||||
}
|
||||
c.genID()
|
||||
return c, nil
|
||||
}
|
||||
|
||||
func NewClusterFromMembers(token string, id types.ID, membs []*Member) *RaftCluster {
|
||||
c := NewCluster(token)
|
||||
c.id = id
|
||||
for _, m := range membs {
|
||||
c.members[m.ID] = m
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func NewCluster(token string) *RaftCluster {
|
||||
return &RaftCluster{
|
||||
token: token,
|
||||
members: make(map[types.ID]*Member),
|
||||
removed: make(map[types.ID]bool),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RaftCluster) ID() types.ID { return c.id }
|
||||
|
||||
func (c *RaftCluster) Members() []*Member {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
var ms MembersByID
|
||||
for _, m := range c.members {
|
||||
ms = append(ms, m.Clone())
|
||||
}
|
||||
sort.Sort(ms)
|
||||
return []*Member(ms)
|
||||
}
|
||||
|
||||
func (c *RaftCluster) Member(id types.ID) *Member {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
return c.members[id].Clone()
|
||||
}
|
||||
|
||||
// MemberByName returns a Member with the given name if exists.
|
||||
// If more than one member has the given name, it will panic.
|
||||
func (c *RaftCluster) MemberByName(name string) *Member {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
var memb *Member
|
||||
for _, m := range c.members {
|
||||
if m.Name == name {
|
||||
if memb != nil {
|
||||
plog.Panicf("two members with the given name %q exist", name)
|
||||
}
|
||||
memb = m
|
||||
}
|
||||
}
|
||||
return memb.Clone()
|
||||
}
|
||||
|
||||
func (c *RaftCluster) MemberIDs() []types.ID {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
var ids []types.ID
|
||||
for _, m := range c.members {
|
||||
ids = append(ids, m.ID)
|
||||
}
|
||||
sort.Sort(types.IDSlice(ids))
|
||||
return ids
|
||||
}
|
||||
|
||||
func (c *RaftCluster) IsIDRemoved(id types.ID) bool {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
return c.removed[id]
|
||||
}
|
||||
|
||||
// PeerURLs returns a list of all peer addresses.
|
||||
// The returned list is sorted in ascending lexicographical order.
|
||||
func (c *RaftCluster) PeerURLs() []string {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
urls := make([]string, 0)
|
||||
for _, p := range c.members {
|
||||
for _, addr := range p.PeerURLs {
|
||||
urls = append(urls, addr)
|
||||
}
|
||||
}
|
||||
sort.Strings(urls)
|
||||
return urls
|
||||
}
|
||||
|
||||
// ClientURLs returns a list of all client addresses.
|
||||
// The returned list is sorted in ascending lexicographical order.
|
||||
func (c *RaftCluster) ClientURLs() []string {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
urls := make([]string, 0)
|
||||
for _, p := range c.members {
|
||||
for _, url := range p.ClientURLs {
|
||||
urls = append(urls, url)
|
||||
}
|
||||
}
|
||||
sort.Strings(urls)
|
||||
return urls
|
||||
}
|
||||
|
||||
func (c *RaftCluster) String() string {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
b := &bytes.Buffer{}
|
||||
fmt.Fprintf(b, "{ClusterID:%s ", c.id)
|
||||
var ms []string
|
||||
for _, m := range c.members {
|
||||
ms = append(ms, fmt.Sprintf("%+v", m))
|
||||
}
|
||||
fmt.Fprintf(b, "Members:[%s] ", strings.Join(ms, " "))
|
||||
var ids []string
|
||||
for id := range c.removed {
|
||||
ids = append(ids, fmt.Sprintf("%s", id))
|
||||
}
|
||||
fmt.Fprintf(b, "RemovedMemberIDs:[%s]}", strings.Join(ids, " "))
|
||||
return b.String()
|
||||
}
|
||||
|
||||
func (c *RaftCluster) genID() {
|
||||
mIDs := c.MemberIDs()
|
||||
b := make([]byte, 8*len(mIDs))
|
||||
for i, id := range mIDs {
|
||||
binary.BigEndian.PutUint64(b[8*i:], uint64(id))
|
||||
}
|
||||
hash := sha1.Sum(b)
|
||||
c.id = types.ID(binary.BigEndian.Uint64(hash[:8]))
|
||||
}
|
||||
|
||||
func (c *RaftCluster) SetID(id types.ID) { c.id = id }
|
||||
|
||||
func (c *RaftCluster) SetStore(st store.Store) { c.store = st }
|
||||
|
||||
func (c *RaftCluster) Recover() {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.members, c.removed = membersFromStore(c.store)
|
||||
c.version = clusterVersionFromStore(c.store)
|
||||
mustDetectDowngrade(c.version)
|
||||
|
||||
for _, m := range c.members {
|
||||
plog.Infof("added member %s %v to cluster %s from store", m.ID, m.PeerURLs, c.id)
|
||||
}
|
||||
if c.version != nil {
|
||||
plog.Infof("set the cluster version to %v from store", version.Cluster(c.version.String()))
|
||||
}
|
||||
}
|
||||
|
||||
// ValidateConfigurationChange takes a proposed ConfChange and
|
||||
// ensures that it is still valid.
|
||||
func (c *RaftCluster) ValidateConfigurationChange(cc raftpb.ConfChange) error {
|
||||
members, removed := membersFromStore(c.store)
|
||||
id := types.ID(cc.NodeID)
|
||||
if removed[id] {
|
||||
return ErrIDRemoved
|
||||
}
|
||||
switch cc.Type {
|
||||
case raftpb.ConfChangeAddNode:
|
||||
if members[id] != nil {
|
||||
return ErrIDExists
|
||||
}
|
||||
urls := make(map[string]bool)
|
||||
for _, m := range members {
|
||||
for _, u := range m.PeerURLs {
|
||||
urls[u] = true
|
||||
}
|
||||
}
|
||||
m := new(Member)
|
||||
if err := json.Unmarshal(cc.Context, m); err != nil {
|
||||
plog.Panicf("unmarshal member should never fail: %v", err)
|
||||
}
|
||||
for _, u := range m.PeerURLs {
|
||||
if urls[u] {
|
||||
return ErrPeerURLexists
|
||||
}
|
||||
}
|
||||
case raftpb.ConfChangeRemoveNode:
|
||||
if members[id] == nil {
|
||||
return ErrIDNotFound
|
||||
}
|
||||
case raftpb.ConfChangeUpdateNode:
|
||||
if members[id] == nil {
|
||||
return ErrIDNotFound
|
||||
}
|
||||
urls := make(map[string]bool)
|
||||
for _, m := range members {
|
||||
if m.ID == id {
|
||||
continue
|
||||
}
|
||||
for _, u := range m.PeerURLs {
|
||||
urls[u] = true
|
||||
}
|
||||
}
|
||||
m := new(Member)
|
||||
if err := json.Unmarshal(cc.Context, m); err != nil {
|
||||
plog.Panicf("unmarshal member should never fail: %v", err)
|
||||
}
|
||||
for _, u := range m.PeerURLs {
|
||||
if urls[u] {
|
||||
return ErrPeerURLexists
|
||||
}
|
||||
}
|
||||
default:
|
||||
plog.Panicf("ConfChange type should be either AddNode, RemoveNode or UpdateNode")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddMember adds a new Member into the cluster, and saves the given member's
|
||||
// raftAttributes into the store. The given member should have empty attributes.
|
||||
// A Member with a matching id must not exist.
|
||||
func (c *RaftCluster) AddMember(m *Member) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
if c.store != nil {
|
||||
mustSaveMemberToStore(c.store, m)
|
||||
}
|
||||
if c.be != nil {
|
||||
mustSaveMemberToBackend(c.be, m)
|
||||
}
|
||||
|
||||
c.members[m.ID] = m
|
||||
}
|
||||
|
||||
// RemoveMember removes a member from the store.
|
||||
// The given id MUST exist, or the function panics.
|
||||
func (c *RaftCluster) RemoveMember(id types.ID) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
if c.store != nil {
|
||||
mustDeleteMemberFromStore(c.store, id)
|
||||
}
|
||||
if c.be != nil {
|
||||
mustDeleteMemberFromBackend(c.be, id)
|
||||
}
|
||||
|
||||
delete(c.members, id)
|
||||
c.removed[id] = true
|
||||
}
|
||||
|
||||
func (c *RaftCluster) UpdateAttributes(id types.ID, attr Attributes) bool {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
if m, ok := c.members[id]; ok {
|
||||
m.Attributes = attr
|
||||
return true
|
||||
}
|
||||
_, ok := c.removed[id]
|
||||
if ok {
|
||||
plog.Warningf("skipped updating attributes of removed member %s", id)
|
||||
} else {
|
||||
plog.Panicf("error updating attributes of unknown member %s", id)
|
||||
}
|
||||
// TODO: update store in this function
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *RaftCluster) UpdateRaftAttributes(id types.ID, raftAttr RaftAttributes) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
|
||||
c.members[id].RaftAttributes = raftAttr
|
||||
if c.store != nil {
|
||||
mustUpdateMemberInStore(c.store, c.members[id])
|
||||
}
|
||||
if c.be != nil {
|
||||
mustSaveMemberToBackend(c.be, c.members[id])
|
||||
}
|
||||
}
|
||||
|
||||
func (c *RaftCluster) Version() *semver.Version {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
if c.version == nil {
|
||||
return nil
|
||||
}
|
||||
return semver.Must(semver.NewVersion(c.version.String()))
|
||||
}
|
||||
|
||||
func (c *RaftCluster) SetVersion(ver *semver.Version) {
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
if c.version != nil {
|
||||
plog.Noticef("updated the cluster version from %v to %v", version.Cluster(c.version.String()), version.Cluster(ver.String()))
|
||||
} else {
|
||||
plog.Noticef("set the initial cluster version to %v", version.Cluster(ver.String()))
|
||||
}
|
||||
c.version = ver
|
||||
mustDetectDowngrade(c.version)
|
||||
}
|
||||
|
||||
func (c *RaftCluster) IsReadyToAddNewMember() bool {
|
||||
nmembers := 1
|
||||
nstarted := 0
|
||||
|
||||
for _, member := range c.members {
|
||||
if member.IsStarted() {
|
||||
nstarted++
|
||||
}
|
||||
nmembers++
|
||||
}
|
||||
|
||||
if nstarted == 1 && nmembers == 2 {
|
||||
// a case of adding a new node to 1-member cluster for restoring cluster data
|
||||
// https://github.com/coreos/etcd/blob/master/Documentation/admin_guide.md#restoring-the-cluster
|
||||
|
||||
plog.Debugf("The number of started member is 1. This cluster can accept add member request.")
|
||||
return true
|
||||
}
|
||||
|
||||
nquorum := nmembers/2 + 1
|
||||
if nstarted < nquorum {
|
||||
plog.Warningf("Reject add member request: the number of started member (%d) will be less than the quorum number of the cluster (%d)", nstarted, nquorum)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *RaftCluster) IsReadyToRemoveMember(id uint64) bool {
|
||||
nmembers := 0
|
||||
nstarted := 0
|
||||
|
||||
for _, member := range c.members {
|
||||
if uint64(member.ID) == id {
|
||||
continue
|
||||
}
|
||||
|
||||
if member.IsStarted() {
|
||||
nstarted++
|
||||
}
|
||||
nmembers++
|
||||
}
|
||||
|
||||
nquorum := nmembers/2 + 1
|
||||
if nstarted < nquorum {
|
||||
plog.Warningf("Reject remove member request: the number of started member (%d) will be less than the quorum number of the cluster (%d)", nstarted, nquorum)
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func membersFromStore(st store.Store) (map[types.ID]*Member, map[types.ID]bool) {
|
||||
members := make(map[types.ID]*Member)
|
||||
removed := make(map[types.ID]bool)
|
||||
e, err := st.Get(StoreMembersPrefix, true, true)
|
||||
if err != nil {
|
||||
if isKeyNotFound(err) {
|
||||
return members, removed
|
||||
}
|
||||
plog.Panicf("get storeMembers should never fail: %v", err)
|
||||
}
|
||||
for _, n := range e.Node.Nodes {
|
||||
var m *Member
|
||||
m, err = nodeToMember(n)
|
||||
if err != nil {
|
||||
plog.Panicf("nodeToMember should never fail: %v", err)
|
||||
}
|
||||
members[m.ID] = m
|
||||
}
|
||||
|
||||
e, err = st.Get(storeRemovedMembersPrefix, true, true)
|
||||
if err != nil {
|
||||
if isKeyNotFound(err) {
|
||||
return members, removed
|
||||
}
|
||||
plog.Panicf("get storeRemovedMembers should never fail: %v", err)
|
||||
}
|
||||
for _, n := range e.Node.Nodes {
|
||||
removed[MustParseMemberIDFromKey(n.Key)] = true
|
||||
}
|
||||
return members, removed
|
||||
}
|
||||
|
||||
func clusterVersionFromStore(st store.Store) *semver.Version {
|
||||
e, err := st.Get(path.Join(storePrefix, "version"), false, false)
|
||||
if err != nil {
|
||||
if isKeyNotFound(err) {
|
||||
return nil
|
||||
}
|
||||
plog.Panicf("unexpected error (%v) when getting cluster version from store", err)
|
||||
}
|
||||
return semver.Must(semver.NewVersion(*e.Node.Value))
|
||||
}
|
||||
|
||||
// ValidateClusterAndAssignIDs validates the local cluster by matching the PeerURLs
|
||||
// with the existing cluster. If the validation succeeds, it assigns the IDs
|
||||
// from the existing cluster to the local cluster.
|
||||
// If the validation fails, an error will be returned.
|
||||
func ValidateClusterAndAssignIDs(local *RaftCluster, existing *RaftCluster) error {
|
||||
ems := existing.Members()
|
||||
lms := local.Members()
|
||||
if len(ems) != len(lms) {
|
||||
return fmt.Errorf("member count is unequal")
|
||||
}
|
||||
sort.Sort(MembersByPeerURLs(ems))
|
||||
sort.Sort(MembersByPeerURLs(lms))
|
||||
|
||||
for i := range ems {
|
||||
if !netutil.URLStringsEqual(ems[i].PeerURLs, lms[i].PeerURLs) {
|
||||
return fmt.Errorf("unmatched member while checking PeerURLs")
|
||||
}
|
||||
lms[i].ID = ems[i].ID
|
||||
}
|
||||
local.members = make(map[types.ID]*Member)
|
||||
for _, m := range lms {
|
||||
local.members[m.ID] = m
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func mustDetectDowngrade(cv *semver.Version) {
|
||||
lv := semver.Must(semver.NewVersion(version.Version))
|
||||
// only keep major.minor version for comparison against cluster version
|
||||
lv = &semver.Version{Major: lv.Major, Minor: lv.Minor}
|
||||
if cv != nil && lv.LessThan(*cv) {
|
||||
plog.Fatalf("cluster cannot be downgraded (current version: %s is lower than determined cluster version: %s).", version.Version, version.Cluster(cv.String()))
|
||||
}
|
||||
}
|
||||
33
vendor/github.com/coreos/etcd/etcdserver/membership/errors.go
generated
vendored
Normal file
33
vendor/github.com/coreos/etcd/etcdserver/membership/errors.go
generated
vendored
Normal file
@@ -0,0 +1,33 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 membership
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
etcdErr "github.com/coreos/etcd/error"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrIDRemoved = errors.New("membership: ID removed")
|
||||
ErrIDExists = errors.New("membership: ID exists")
|
||||
ErrIDNotFound = errors.New("membership: ID not found")
|
||||
ErrPeerURLexists = errors.New("membership: peerURL exists")
|
||||
)
|
||||
|
||||
func isKeyNotFound(err error) bool {
|
||||
e, ok := err.(*etcdErr.Error)
|
||||
return ok && e.ErrorCode == etcdErr.EcodeKeyNotFound
|
||||
}
|
||||
124
vendor/github.com/coreos/etcd/etcdserver/membership/member.go
generated
vendored
Normal file
124
vendor/github.com/coreos/etcd/etcdserver/membership/member.go
generated
vendored
Normal file
@@ -0,0 +1,124 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 membership
|
||||
|
||||
import (
|
||||
"crypto/sha1"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"sort"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
)
|
||||
|
||||
var (
|
||||
plog = capnslog.NewPackageLogger("github.com/coreos/etcd/etcdserver", "membership")
|
||||
)
|
||||
|
||||
// RaftAttributes represents the raft related attributes of an etcd member.
|
||||
type RaftAttributes struct {
|
||||
// PeerURLs is the list of peers in the raft cluster.
|
||||
// TODO(philips): ensure these are URLs
|
||||
PeerURLs []string `json:"peerURLs"`
|
||||
}
|
||||
|
||||
// Attributes represents all the non-raft related attributes of an etcd member.
|
||||
type Attributes struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
ClientURLs []string `json:"clientURLs,omitempty"`
|
||||
}
|
||||
|
||||
type Member struct {
|
||||
ID types.ID `json:"id"`
|
||||
RaftAttributes
|
||||
Attributes
|
||||
}
|
||||
|
||||
// NewMember creates a Member without an ID and generates one based on the
|
||||
// cluster name, peer URLs, and time. This is used for bootstrapping/adding new member.
|
||||
func NewMember(name string, peerURLs types.URLs, clusterName string, now *time.Time) *Member {
|
||||
m := &Member{
|
||||
RaftAttributes: RaftAttributes{PeerURLs: peerURLs.StringSlice()},
|
||||
Attributes: Attributes{Name: name},
|
||||
}
|
||||
|
||||
var b []byte
|
||||
sort.Strings(m.PeerURLs)
|
||||
for _, p := range m.PeerURLs {
|
||||
b = append(b, []byte(p)...)
|
||||
}
|
||||
|
||||
b = append(b, []byte(clusterName)...)
|
||||
if now != nil {
|
||||
b = append(b, []byte(fmt.Sprintf("%d", now.Unix()))...)
|
||||
}
|
||||
|
||||
hash := sha1.Sum(b)
|
||||
m.ID = types.ID(binary.BigEndian.Uint64(hash[:8]))
|
||||
return m
|
||||
}
|
||||
|
||||
// PickPeerURL chooses a random address from a given Member's PeerURLs.
|
||||
// It will panic if there is no PeerURLs available in Member.
|
||||
func (m *Member) PickPeerURL() string {
|
||||
if len(m.PeerURLs) == 0 {
|
||||
plog.Panicf("member should always have some peer url")
|
||||
}
|
||||
return m.PeerURLs[rand.Intn(len(m.PeerURLs))]
|
||||
}
|
||||
|
||||
func (m *Member) Clone() *Member {
|
||||
if m == nil {
|
||||
return nil
|
||||
}
|
||||
mm := &Member{
|
||||
ID: m.ID,
|
||||
Attributes: Attributes{
|
||||
Name: m.Name,
|
||||
},
|
||||
}
|
||||
if m.PeerURLs != nil {
|
||||
mm.PeerURLs = make([]string, len(m.PeerURLs))
|
||||
copy(mm.PeerURLs, m.PeerURLs)
|
||||
}
|
||||
if m.ClientURLs != nil {
|
||||
mm.ClientURLs = make([]string, len(m.ClientURLs))
|
||||
copy(mm.ClientURLs, m.ClientURLs)
|
||||
}
|
||||
return mm
|
||||
}
|
||||
|
||||
func (m *Member) IsStarted() bool {
|
||||
return len(m.Name) != 0
|
||||
}
|
||||
|
||||
// MembersByID implements sort by ID interface
|
||||
type MembersByID []*Member
|
||||
|
||||
func (ms MembersByID) Len() int { return len(ms) }
|
||||
func (ms MembersByID) Less(i, j int) bool { return ms[i].ID < ms[j].ID }
|
||||
func (ms MembersByID) Swap(i, j int) { ms[i], ms[j] = ms[j], ms[i] }
|
||||
|
||||
// MembersByPeerURLs implements sort by peer urls interface
|
||||
type MembersByPeerURLs []*Member
|
||||
|
||||
func (ms MembersByPeerURLs) Len() int { return len(ms) }
|
||||
func (ms MembersByPeerURLs) Less(i, j int) bool {
|
||||
return ms[i].PeerURLs[0] < ms[j].PeerURLs[0]
|
||||
}
|
||||
func (ms MembersByPeerURLs) Swap(i, j int) { ms[i], ms[j] = ms[j], ms[i] }
|
||||
149
vendor/github.com/coreos/etcd/etcdserver/membership/store.go
generated
vendored
Normal file
149
vendor/github.com/coreos/etcd/etcdserver/membership/store.go
generated
vendored
Normal file
@@ -0,0 +1,149 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 membership
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"path"
|
||||
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/etcd/storage/backend"
|
||||
"github.com/coreos/etcd/store"
|
||||
)
|
||||
|
||||
const (
|
||||
// TODO: make this private after moving all membership storage logic
|
||||
// from etcdserver pkg
|
||||
AttributesSuffix = "attributes"
|
||||
raftAttributesSuffix = "raftAttributes"
|
||||
|
||||
// the prefix for stroing membership related information in store provided by store pkg.
|
||||
storePrefix = "/0"
|
||||
)
|
||||
|
||||
var (
|
||||
membersBucketName = []byte("members")
|
||||
membersRemovedBuckedName = []byte("members_removed")
|
||||
|
||||
StoreMembersPrefix = path.Join(storePrefix, "members")
|
||||
storeRemovedMembersPrefix = path.Join(storePrefix, "removed_members")
|
||||
)
|
||||
|
||||
func mustSaveMemberToBackend(be backend.Backend, m *Member) {
|
||||
mkey := backendMemberKey(m.ID)
|
||||
mvalue, err := json.Marshal(m.RaftAttributes)
|
||||
if err != nil {
|
||||
plog.Panicf("marshal raftAttributes should never fail: %v", err)
|
||||
}
|
||||
|
||||
tx := be.BatchTx()
|
||||
tx.Lock()
|
||||
tx.UnsafePut(membersBucketName, mkey, mvalue)
|
||||
tx.Unlock()
|
||||
}
|
||||
|
||||
func mustDeleteMemberFromBackend(be backend.Backend, id types.ID) {
|
||||
mkey := backendMemberKey(id)
|
||||
|
||||
tx := be.BatchTx()
|
||||
tx.Lock()
|
||||
tx.UnsafeDelete(membersBucketName, mkey)
|
||||
tx.UnsafePut(membersRemovedBuckedName, mkey, []byte("removed"))
|
||||
tx.Unlock()
|
||||
}
|
||||
|
||||
func mustSaveMemberToStore(s store.Store, m *Member) {
|
||||
b, err := json.Marshal(m.RaftAttributes)
|
||||
if err != nil {
|
||||
plog.Panicf("marshal raftAttributes should never fail: %v", err)
|
||||
}
|
||||
p := path.Join(MemberStoreKey(m.ID), raftAttributesSuffix)
|
||||
if _, err := s.Create(p, false, string(b), false, store.TTLOptionSet{ExpireTime: store.Permanent}); err != nil {
|
||||
plog.Panicf("create raftAttributes should never fail: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func mustDeleteMemberFromStore(s store.Store, id types.ID) {
|
||||
if _, err := s.Delete(MemberStoreKey(id), true, true); err != nil {
|
||||
plog.Panicf("delete member should never fail: %v", err)
|
||||
}
|
||||
if _, err := s.Create(RemovedMemberStoreKey(id), false, "", false, store.TTLOptionSet{ExpireTime: store.Permanent}); err != nil {
|
||||
plog.Panicf("create removedMember should never fail: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func mustUpdateMemberInStore(s store.Store, m *Member) {
|
||||
b, err := json.Marshal(m.RaftAttributes)
|
||||
if err != nil {
|
||||
plog.Panicf("marshal raftAttributes should never fail: %v", err)
|
||||
}
|
||||
p := path.Join(MemberStoreKey(m.ID), raftAttributesSuffix)
|
||||
if _, err := s.Update(p, string(b), store.TTLOptionSet{ExpireTime: store.Permanent}); err != nil {
|
||||
plog.Panicf("update raftAttributes should never fail: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// nodeToMember builds member from a key value node.
|
||||
// the child nodes of the given node MUST be sorted by key.
|
||||
func nodeToMember(n *store.NodeExtern) (*Member, error) {
|
||||
m := &Member{ID: MustParseMemberIDFromKey(n.Key)}
|
||||
attrs := make(map[string][]byte)
|
||||
raftAttrKey := path.Join(n.Key, raftAttributesSuffix)
|
||||
attrKey := path.Join(n.Key, AttributesSuffix)
|
||||
for _, nn := range n.Nodes {
|
||||
if nn.Key != raftAttrKey && nn.Key != attrKey {
|
||||
return nil, fmt.Errorf("unknown key %q", nn.Key)
|
||||
}
|
||||
attrs[nn.Key] = []byte(*nn.Value)
|
||||
}
|
||||
if data := attrs[raftAttrKey]; data != nil {
|
||||
if err := json.Unmarshal(data, &m.RaftAttributes); err != nil {
|
||||
return nil, fmt.Errorf("unmarshal raftAttributes error: %v", err)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("raftAttributes key doesn't exist")
|
||||
}
|
||||
if data := attrs[attrKey]; data != nil {
|
||||
if err := json.Unmarshal(data, &m.Attributes); err != nil {
|
||||
return m, fmt.Errorf("unmarshal attributes error: %v", err)
|
||||
}
|
||||
}
|
||||
return m, nil
|
||||
}
|
||||
|
||||
func backendMemberKey(id types.ID) []byte {
|
||||
return []byte(path.Join(id.String(), raftAttributesSuffix))
|
||||
}
|
||||
|
||||
func MemberStoreKey(id types.ID) string {
|
||||
return path.Join(StoreMembersPrefix, id.String())
|
||||
}
|
||||
|
||||
func MemberAttributesStorePath(id types.ID) string {
|
||||
return path.Join(MemberStoreKey(id), AttributesSuffix)
|
||||
}
|
||||
|
||||
func MustParseMemberIDFromKey(key string) types.ID {
|
||||
id, err := types.IDFromString(path.Base(key))
|
||||
if err != nil {
|
||||
plog.Panicf("unexpected parse member id error: %v", err)
|
||||
}
|
||||
return id
|
||||
}
|
||||
|
||||
func RemovedMemberStoreKey(id types.ID) string {
|
||||
return path.Join(storeRemovedMembersPrefix, id.String())
|
||||
}
|
||||
87
vendor/github.com/coreos/etcd/etcdserver/metrics.go
generated
vendored
Normal file
87
vendor/github.com/coreos/etcd/etcdserver/metrics.go
generated
vendored
Normal file
@@ -0,0 +1,87 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 etcdserver
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/pkg/runtime"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var (
|
||||
// TODO: with label in v3?
|
||||
proposeDurations = prometheus.NewHistogram(prometheus.HistogramOpts{
|
||||
Namespace: "etcd",
|
||||
Subsystem: "server",
|
||||
Name: "proposal_durations_seconds",
|
||||
Help: "The latency distributions of committing proposal.",
|
||||
Buckets: prometheus.ExponentialBuckets(0.001, 2, 14),
|
||||
})
|
||||
proposePending = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: "etcd",
|
||||
Subsystem: "server",
|
||||
Name: "pending_proposal_total",
|
||||
Help: "The total number of pending proposals.",
|
||||
})
|
||||
// This is number of proposal failed in client's view.
|
||||
// The proposal might be later got committed in raft.
|
||||
proposeFailed = prometheus.NewCounter(prometheus.CounterOpts{
|
||||
Namespace: "etcd",
|
||||
Subsystem: "server",
|
||||
Name: "proposal_failed_total",
|
||||
Help: "The total number of failed proposals.",
|
||||
})
|
||||
|
||||
fileDescriptorUsed = prometheus.NewGauge(prometheus.GaugeOpts{
|
||||
Namespace: "etcd",
|
||||
Subsystem: "server",
|
||||
Name: "file_descriptors_used_total",
|
||||
Help: "The total number of file descriptors used.",
|
||||
})
|
||||
)
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(proposeDurations)
|
||||
prometheus.MustRegister(proposePending)
|
||||
prometheus.MustRegister(proposeFailed)
|
||||
prometheus.MustRegister(fileDescriptorUsed)
|
||||
}
|
||||
|
||||
func monitorFileDescriptor(done <-chan struct{}) {
|
||||
ticker := time.NewTicker(5 * time.Second)
|
||||
defer ticker.Stop()
|
||||
for {
|
||||
used, err := runtime.FDUsage()
|
||||
if err != nil {
|
||||
plog.Errorf("cannot monitor file descriptor usage (%v)", err)
|
||||
return
|
||||
}
|
||||
fileDescriptorUsed.Set(float64(used))
|
||||
limit, err := runtime.FDLimit()
|
||||
if err != nil {
|
||||
plog.Errorf("cannot monitor file descriptor usage (%v)", err)
|
||||
return
|
||||
}
|
||||
if used >= limit/5*4 {
|
||||
plog.Warningf("80%% of the file descriptor limit is used [used = %d, limit = %d]", used, limit)
|
||||
}
|
||||
select {
|
||||
case <-ticker.C:
|
||||
case <-done:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
114
vendor/github.com/coreos/etcd/etcdserver/quota.go
generated
vendored
Normal file
114
vendor/github.com/coreos/etcd/etcdserver/quota.go
generated
vendored
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 etcdserver
|
||||
|
||||
import (
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/storage/backend"
|
||||
)
|
||||
|
||||
// Quota represents an arbitrary quota against arbitrary requests. Each request
|
||||
// costs some charge; if there is not enough remaining charge, then there are
|
||||
// too few resources available within the quota to apply the request.
|
||||
type Quota interface {
|
||||
// Available judges whether the given request fits within the quota.
|
||||
Available(req interface{}) bool
|
||||
// Cost computes the charge against the quota for a given request.
|
||||
Cost(req interface{}) int
|
||||
// Remaining is the amount of charge left for the quota.
|
||||
Remaining() int64
|
||||
}
|
||||
|
||||
type passthroughQuota struct{}
|
||||
|
||||
func (*passthroughQuota) Available(interface{}) bool { return true }
|
||||
func (*passthroughQuota) Cost(interface{}) int { return 0 }
|
||||
func (*passthroughQuota) Remaining() int64 { return 1 }
|
||||
|
||||
type backendQuota struct {
|
||||
s *EtcdServer
|
||||
maxBackendBytes int64
|
||||
}
|
||||
|
||||
const (
|
||||
// leaseOverhead is an estimate for the cost of storing a lease
|
||||
leaseOverhead = 64
|
||||
// kvOverhead is an estimate for the cost of storing a key's metadata
|
||||
kvOverhead = 256
|
||||
)
|
||||
|
||||
func NewBackendQuota(s *EtcdServer) Quota {
|
||||
if s.cfg.QuotaBackendBytes < 0 {
|
||||
// disable quotas if negative
|
||||
plog.Warningf("disabling backend quota")
|
||||
return &passthroughQuota{}
|
||||
}
|
||||
if s.cfg.QuotaBackendBytes == 0 {
|
||||
// use default size if no quota size given
|
||||
return &backendQuota{s, backend.DefaultQuotaBytes}
|
||||
}
|
||||
if s.cfg.QuotaBackendBytes > backend.MaxQuotaBytes {
|
||||
plog.Warningf("backend quota %v exceeds maximum quota %v; using maximum", s.cfg.QuotaBackendBytes, backend.MaxQuotaBytes)
|
||||
return &backendQuota{s, backend.MaxQuotaBytes}
|
||||
}
|
||||
return &backendQuota{s, s.cfg.QuotaBackendBytes}
|
||||
}
|
||||
|
||||
func (b *backendQuota) Available(v interface{}) bool {
|
||||
// TODO: maybe optimize backend.Size()
|
||||
return b.s.Backend().Size()+int64(b.Cost(v)) < b.maxBackendBytes
|
||||
}
|
||||
|
||||
func (b *backendQuota) Cost(v interface{}) int {
|
||||
switch r := v.(type) {
|
||||
case *pb.PutRequest:
|
||||
return costPut(r)
|
||||
case *pb.TxnRequest:
|
||||
return costTxn(r)
|
||||
case *pb.LeaseGrantRequest:
|
||||
return leaseOverhead
|
||||
default:
|
||||
panic("unexpected cost")
|
||||
}
|
||||
}
|
||||
|
||||
func costPut(r *pb.PutRequest) int { return kvOverhead + len(r.Key) + len(r.Value) }
|
||||
|
||||
func costTxnReq(u *pb.RequestUnion) int {
|
||||
r := u.GetRequestPut()
|
||||
if r == nil {
|
||||
return 0
|
||||
}
|
||||
return costPut(r)
|
||||
}
|
||||
|
||||
func costTxn(r *pb.TxnRequest) int {
|
||||
sizeSuccess := 0
|
||||
for _, u := range r.Success {
|
||||
sizeSuccess += costTxnReq(u)
|
||||
}
|
||||
sizeFailure := 0
|
||||
for _, u := range r.Failure {
|
||||
sizeFailure += costTxnReq(u)
|
||||
}
|
||||
if sizeFailure > sizeSuccess {
|
||||
return sizeFailure
|
||||
}
|
||||
return sizeSuccess
|
||||
}
|
||||
|
||||
func (b *backendQuota) Remaining() int64 {
|
||||
return b.maxBackendBytes - b.s.Backend().Size()
|
||||
}
|
||||
499
vendor/github.com/coreos/etcd/etcdserver/raft.go
generated
vendored
Normal file
499
vendor/github.com/coreos/etcd/etcdserver/raft.go
generated
vendored
Normal file
@@ -0,0 +1,499 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 etcdserver
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"expvar"
|
||||
"os"
|
||||
"sort"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/etcdserver/membership"
|
||||
"github.com/coreos/etcd/pkg/contention"
|
||||
"github.com/coreos/etcd/pkg/pbutil"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/etcd/raft"
|
||||
"github.com/coreos/etcd/raft/raftpb"
|
||||
"github.com/coreos/etcd/rafthttp"
|
||||
"github.com/coreos/etcd/wal"
|
||||
"github.com/coreos/etcd/wal/walpb"
|
||||
"github.com/coreos/pkg/capnslog"
|
||||
)
|
||||
|
||||
const (
|
||||
// Number of entries for slow follower to catch-up after compacting
|
||||
// the raft storage entries.
|
||||
// We expect the follower has a millisecond level latency with the leader.
|
||||
// The max throughput is around 10K. Keep a 5K entries is enough for helping
|
||||
// follower to catch up.
|
||||
numberOfCatchUpEntries = 5000
|
||||
|
||||
// The max throughput of etcd will not exceed 100MB/s (100K * 1KB value).
|
||||
// Assuming the RTT is around 10ms, 1MB max size is large enough.
|
||||
maxSizePerMsg = 1 * 1024 * 1024
|
||||
// Never overflow the rafthttp buffer, which is 4096.
|
||||
// TODO: a better const?
|
||||
maxInflightMsgs = 4096 / 8
|
||||
)
|
||||
|
||||
var (
|
||||
// protects raftStatus
|
||||
raftStatusMu sync.Mutex
|
||||
// indirection for expvar func interface
|
||||
// expvar panics when publishing duplicate name
|
||||
// expvar does not support remove a registered name
|
||||
// so only register a func that calls raftStatus
|
||||
// and change raftStatus as we need.
|
||||
raftStatus func() raft.Status
|
||||
)
|
||||
|
||||
func init() {
|
||||
raft.SetLogger(capnslog.NewPackageLogger("github.com/coreos/etcd", "raft"))
|
||||
expvar.Publish("raft.status", expvar.Func(func() interface{} {
|
||||
raftStatusMu.Lock()
|
||||
defer raftStatusMu.Unlock()
|
||||
return raftStatus()
|
||||
}))
|
||||
}
|
||||
|
||||
type RaftTimer interface {
|
||||
Index() uint64
|
||||
Term() uint64
|
||||
}
|
||||
|
||||
// apply contains entries, snapshot to be applied. Once
|
||||
// an apply is consumed, the entries will be persisted to
|
||||
// to raft storage concurrently; the application must read
|
||||
// raftDone before assuming the raft messages are stable.
|
||||
type apply struct {
|
||||
entries []raftpb.Entry
|
||||
snapshot raftpb.Snapshot
|
||||
raftDone <-chan struct{} // rx {} after raft has persisted messages
|
||||
}
|
||||
|
||||
type raftNode struct {
|
||||
// Cache of the latest raft index and raft term the server has seen.
|
||||
// These three unit64 fields must be the first elements to keep 64-bit
|
||||
// alignment for atomic access to the fields.
|
||||
index uint64
|
||||
term uint64
|
||||
lead uint64
|
||||
|
||||
mu sync.Mutex
|
||||
// last lead elected time
|
||||
lt time.Time
|
||||
|
||||
raft.Node
|
||||
|
||||
// a chan to send out apply
|
||||
applyc chan apply
|
||||
|
||||
// TODO: remove the etcdserver related logic from raftNode
|
||||
// TODO: add a state machine interface to apply the commit entries
|
||||
// and do snapshot/recover
|
||||
s *EtcdServer
|
||||
|
||||
// utility
|
||||
ticker <-chan time.Time
|
||||
raftStorage *raft.MemoryStorage
|
||||
storage Storage
|
||||
// transport specifies the transport to send and receive msgs to members.
|
||||
// Sending messages MUST NOT block. It is okay to drop messages, since
|
||||
// clients should timeout and reissue their messages.
|
||||
// If transport is nil, server will panic.
|
||||
transport rafthttp.Transporter
|
||||
|
||||
td *contention.TimeoutDetector
|
||||
|
||||
stopped chan struct{}
|
||||
done chan struct{}
|
||||
}
|
||||
|
||||
// start prepares and starts raftNode in a new goroutine. It is no longer safe
|
||||
// to modify the fields after it has been started.
|
||||
// TODO: Ideally raftNode should get rid of the passed in server structure.
|
||||
func (r *raftNode) start(s *EtcdServer) {
|
||||
r.s = s
|
||||
r.applyc = make(chan apply)
|
||||
r.stopped = make(chan struct{})
|
||||
r.done = make(chan struct{})
|
||||
|
||||
heartbeat := 200 * time.Millisecond
|
||||
if s.cfg != nil {
|
||||
heartbeat = time.Duration(s.cfg.TickMs) * time.Millisecond
|
||||
}
|
||||
// set up contention detectors for raft heartbeat message.
|
||||
// expect to send a heartbeat within 2 heartbeat intervals.
|
||||
r.td = contention.NewTimeoutDetector(2 * heartbeat)
|
||||
|
||||
go func() {
|
||||
var syncC <-chan time.Time
|
||||
|
||||
defer r.onStop()
|
||||
islead := false
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-r.ticker:
|
||||
r.Tick()
|
||||
case rd := <-r.Ready():
|
||||
if rd.SoftState != nil {
|
||||
if lead := atomic.LoadUint64(&r.lead); rd.SoftState.Lead != raft.None && lead != rd.SoftState.Lead {
|
||||
r.mu.Lock()
|
||||
r.lt = time.Now()
|
||||
r.mu.Unlock()
|
||||
}
|
||||
atomic.StoreUint64(&r.lead, rd.SoftState.Lead)
|
||||
if rd.RaftState == raft.StateLeader {
|
||||
islead = true
|
||||
// TODO: raft should send server a notification through chan when
|
||||
// it promotes or demotes instead of modifying server directly.
|
||||
syncC = r.s.SyncTicker
|
||||
if r.s.lessor != nil {
|
||||
r.s.lessor.Promote(r.s.cfg.electionTimeout())
|
||||
}
|
||||
// TODO: remove the nil checking
|
||||
// current test utility does not provide the stats
|
||||
if r.s.stats != nil {
|
||||
r.s.stats.BecomeLeader()
|
||||
}
|
||||
if r.s.compactor != nil {
|
||||
r.s.compactor.Resume()
|
||||
}
|
||||
r.td.Reset()
|
||||
} else {
|
||||
islead = false
|
||||
if r.s.lessor != nil {
|
||||
r.s.lessor.Demote()
|
||||
}
|
||||
if r.s.compactor != nil {
|
||||
r.s.compactor.Pause()
|
||||
}
|
||||
syncC = nil
|
||||
}
|
||||
}
|
||||
|
||||
raftDone := make(chan struct{}, 1)
|
||||
ap := apply{
|
||||
entries: rd.CommittedEntries,
|
||||
snapshot: rd.Snapshot,
|
||||
raftDone: raftDone,
|
||||
}
|
||||
|
||||
select {
|
||||
case r.applyc <- ap:
|
||||
case <-r.stopped:
|
||||
return
|
||||
}
|
||||
|
||||
// the leader can write to its disk in parallel with replicating to the followers and them
|
||||
// writing to their disks.
|
||||
// For more details, check raft thesis 10.2.1
|
||||
if islead {
|
||||
r.s.send(rd.Messages)
|
||||
}
|
||||
|
||||
if !raft.IsEmptySnap(rd.Snapshot) {
|
||||
if err := r.storage.SaveSnap(rd.Snapshot); err != nil {
|
||||
plog.Fatalf("raft save snapshot error: %v", err)
|
||||
}
|
||||
r.raftStorage.ApplySnapshot(rd.Snapshot)
|
||||
plog.Infof("raft applied incoming snapshot at index %d", rd.Snapshot.Metadata.Index)
|
||||
}
|
||||
if err := r.storage.Save(rd.HardState, rd.Entries); err != nil {
|
||||
plog.Fatalf("raft save state and entries error: %v", err)
|
||||
}
|
||||
r.raftStorage.Append(rd.Entries)
|
||||
|
||||
if !islead {
|
||||
r.s.send(rd.Messages)
|
||||
}
|
||||
raftDone <- struct{}{}
|
||||
r.Advance()
|
||||
case <-syncC:
|
||||
r.s.sync(r.s.cfg.ReqTimeout())
|
||||
case <-r.stopped:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func (r *raftNode) apply() chan apply {
|
||||
return r.applyc
|
||||
}
|
||||
|
||||
func (r *raftNode) leadElectedTime() time.Time {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
return r.lt
|
||||
}
|
||||
|
||||
func (r *raftNode) stop() {
|
||||
r.stopped <- struct{}{}
|
||||
<-r.done
|
||||
}
|
||||
|
||||
func (r *raftNode) onStop() {
|
||||
r.Stop()
|
||||
r.transport.Stop()
|
||||
if err := r.storage.Close(); err != nil {
|
||||
plog.Panicf("raft close storage error: %v", err)
|
||||
}
|
||||
close(r.done)
|
||||
}
|
||||
|
||||
// for testing
|
||||
func (r *raftNode) pauseSending() {
|
||||
p := r.transport.(rafthttp.Pausable)
|
||||
p.Pause()
|
||||
}
|
||||
|
||||
func (r *raftNode) resumeSending() {
|
||||
p := r.transport.(rafthttp.Pausable)
|
||||
p.Resume()
|
||||
}
|
||||
|
||||
// advanceTicksForElection advances ticks to the node for fast election.
|
||||
// This reduces the time to wait for first leader election if bootstrapping the whole
|
||||
// cluster, while leaving at least 1 heartbeat for possible existing leader
|
||||
// to contact it.
|
||||
func advanceTicksForElection(n raft.Node, electionTicks int) {
|
||||
for i := 0; i < electionTicks-1; i++ {
|
||||
n.Tick()
|
||||
}
|
||||
}
|
||||
|
||||
func startNode(cfg *ServerConfig, cl *membership.RaftCluster, ids []types.ID) (id types.ID, n raft.Node, s *raft.MemoryStorage, w *wal.WAL) {
|
||||
var err error
|
||||
member := cl.MemberByName(cfg.Name)
|
||||
metadata := pbutil.MustMarshal(
|
||||
&pb.Metadata{
|
||||
NodeID: uint64(member.ID),
|
||||
ClusterID: uint64(cl.ID()),
|
||||
},
|
||||
)
|
||||
if err = os.MkdirAll(cfg.SnapDir(), privateDirMode); err != nil {
|
||||
plog.Fatalf("create snapshot directory error: %v", err)
|
||||
}
|
||||
if w, err = wal.Create(cfg.WALDir(), metadata); err != nil {
|
||||
plog.Fatalf("create wal error: %v", err)
|
||||
}
|
||||
peers := make([]raft.Peer, len(ids))
|
||||
for i, id := range ids {
|
||||
ctx, err := json.Marshal((*cl).Member(id))
|
||||
if err != nil {
|
||||
plog.Panicf("marshal member should never fail: %v", err)
|
||||
}
|
||||
peers[i] = raft.Peer{ID: uint64(id), Context: ctx}
|
||||
}
|
||||
id = member.ID
|
||||
plog.Infof("starting member %s in cluster %s", id, cl.ID())
|
||||
s = raft.NewMemoryStorage()
|
||||
c := &raft.Config{
|
||||
ID: uint64(id),
|
||||
ElectionTick: cfg.ElectionTicks,
|
||||
HeartbeatTick: 1,
|
||||
Storage: s,
|
||||
MaxSizePerMsg: maxSizePerMsg,
|
||||
MaxInflightMsgs: maxInflightMsgs,
|
||||
CheckQuorum: true,
|
||||
}
|
||||
|
||||
n = raft.StartNode(c, peers)
|
||||
raftStatusMu.Lock()
|
||||
raftStatus = n.Status
|
||||
raftStatusMu.Unlock()
|
||||
advanceTicksForElection(n, c.ElectionTick)
|
||||
return
|
||||
}
|
||||
|
||||
func restartNode(cfg *ServerConfig, snapshot *raftpb.Snapshot) (types.ID, *membership.RaftCluster, raft.Node, *raft.MemoryStorage, *wal.WAL) {
|
||||
var walsnap walpb.Snapshot
|
||||
if snapshot != nil {
|
||||
walsnap.Index, walsnap.Term = snapshot.Metadata.Index, snapshot.Metadata.Term
|
||||
}
|
||||
w, id, cid, st, ents := readWAL(cfg.WALDir(), walsnap)
|
||||
|
||||
plog.Infof("restarting member %s in cluster %s at commit index %d", id, cid, st.Commit)
|
||||
cl := membership.NewCluster("")
|
||||
cl.SetID(cid)
|
||||
s := raft.NewMemoryStorage()
|
||||
if snapshot != nil {
|
||||
s.ApplySnapshot(*snapshot)
|
||||
}
|
||||
s.SetHardState(st)
|
||||
s.Append(ents)
|
||||
c := &raft.Config{
|
||||
ID: uint64(id),
|
||||
ElectionTick: cfg.ElectionTicks,
|
||||
HeartbeatTick: 1,
|
||||
Storage: s,
|
||||
MaxSizePerMsg: maxSizePerMsg,
|
||||
MaxInflightMsgs: maxInflightMsgs,
|
||||
CheckQuorum: true,
|
||||
}
|
||||
|
||||
n := raft.RestartNode(c)
|
||||
raftStatusMu.Lock()
|
||||
raftStatus = n.Status
|
||||
raftStatusMu.Unlock()
|
||||
advanceTicksForElection(n, c.ElectionTick)
|
||||
return id, cl, n, s, w
|
||||
}
|
||||
|
||||
func restartAsStandaloneNode(cfg *ServerConfig, snapshot *raftpb.Snapshot) (types.ID, *membership.RaftCluster, raft.Node, *raft.MemoryStorage, *wal.WAL) {
|
||||
var walsnap walpb.Snapshot
|
||||
if snapshot != nil {
|
||||
walsnap.Index, walsnap.Term = snapshot.Metadata.Index, snapshot.Metadata.Term
|
||||
}
|
||||
w, id, cid, st, ents := readWAL(cfg.WALDir(), walsnap)
|
||||
|
||||
// discard the previously uncommitted entries
|
||||
for i, ent := range ents {
|
||||
if ent.Index > st.Commit {
|
||||
plog.Infof("discarding %d uncommitted WAL entries ", len(ents)-i)
|
||||
ents = ents[:i]
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// force append the configuration change entries
|
||||
toAppEnts := createConfigChangeEnts(getIDs(snapshot, ents), uint64(id), st.Term, st.Commit)
|
||||
ents = append(ents, toAppEnts...)
|
||||
|
||||
// force commit newly appended entries
|
||||
err := w.Save(raftpb.HardState{}, toAppEnts)
|
||||
if err != nil {
|
||||
plog.Fatalf("%v", err)
|
||||
}
|
||||
if len(ents) != 0 {
|
||||
st.Commit = ents[len(ents)-1].Index
|
||||
}
|
||||
|
||||
plog.Printf("forcing restart of member %s in cluster %s at commit index %d", id, cid, st.Commit)
|
||||
cl := membership.NewCluster("")
|
||||
cl.SetID(cid)
|
||||
s := raft.NewMemoryStorage()
|
||||
if snapshot != nil {
|
||||
s.ApplySnapshot(*snapshot)
|
||||
}
|
||||
s.SetHardState(st)
|
||||
s.Append(ents)
|
||||
c := &raft.Config{
|
||||
ID: uint64(id),
|
||||
ElectionTick: cfg.ElectionTicks,
|
||||
HeartbeatTick: 1,
|
||||
Storage: s,
|
||||
MaxSizePerMsg: maxSizePerMsg,
|
||||
MaxInflightMsgs: maxInflightMsgs,
|
||||
}
|
||||
n := raft.RestartNode(c)
|
||||
raftStatus = n.Status
|
||||
return id, cl, n, s, w
|
||||
}
|
||||
|
||||
// getIDs returns an ordered set of IDs included in the given snapshot and
|
||||
// the entries. The given snapshot/entries can contain two kinds of
|
||||
// ID-related entry:
|
||||
// - ConfChangeAddNode, in which case the contained ID will be added into the set.
|
||||
// - ConfChangeRemoveNode, in which case the contained ID will be removed from the set.
|
||||
func getIDs(snap *raftpb.Snapshot, ents []raftpb.Entry) []uint64 {
|
||||
ids := make(map[uint64]bool)
|
||||
if snap != nil {
|
||||
for _, id := range snap.Metadata.ConfState.Nodes {
|
||||
ids[id] = true
|
||||
}
|
||||
}
|
||||
for _, e := range ents {
|
||||
if e.Type != raftpb.EntryConfChange {
|
||||
continue
|
||||
}
|
||||
var cc raftpb.ConfChange
|
||||
pbutil.MustUnmarshal(&cc, e.Data)
|
||||
switch cc.Type {
|
||||
case raftpb.ConfChangeAddNode:
|
||||
ids[cc.NodeID] = true
|
||||
case raftpb.ConfChangeRemoveNode:
|
||||
delete(ids, cc.NodeID)
|
||||
case raftpb.ConfChangeUpdateNode:
|
||||
// do nothing
|
||||
default:
|
||||
plog.Panicf("ConfChange Type should be either ConfChangeAddNode or ConfChangeRemoveNode!")
|
||||
}
|
||||
}
|
||||
sids := make(types.Uint64Slice, 0)
|
||||
for id := range ids {
|
||||
sids = append(sids, id)
|
||||
}
|
||||
sort.Sort(sids)
|
||||
return []uint64(sids)
|
||||
}
|
||||
|
||||
// createConfigChangeEnts creates a series of Raft entries (i.e.
|
||||
// EntryConfChange) to remove the set of given IDs from the cluster. The ID
|
||||
// `self` is _not_ removed, even if present in the set.
|
||||
// If `self` is not inside the given ids, it creates a Raft entry to add a
|
||||
// default member with the given `self`.
|
||||
func createConfigChangeEnts(ids []uint64, self uint64, term, index uint64) []raftpb.Entry {
|
||||
ents := make([]raftpb.Entry, 0)
|
||||
next := index + 1
|
||||
found := false
|
||||
for _, id := range ids {
|
||||
if id == self {
|
||||
found = true
|
||||
continue
|
||||
}
|
||||
cc := &raftpb.ConfChange{
|
||||
Type: raftpb.ConfChangeRemoveNode,
|
||||
NodeID: id,
|
||||
}
|
||||
e := raftpb.Entry{
|
||||
Type: raftpb.EntryConfChange,
|
||||
Data: pbutil.MustMarshal(cc),
|
||||
Term: term,
|
||||
Index: next,
|
||||
}
|
||||
ents = append(ents, e)
|
||||
next++
|
||||
}
|
||||
if !found {
|
||||
m := membership.Member{
|
||||
ID: types.ID(self),
|
||||
RaftAttributes: membership.RaftAttributes{PeerURLs: []string{"http://localhost:7001", "http://localhost:2380"}},
|
||||
}
|
||||
ctx, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
plog.Panicf("marshal member should never fail: %v", err)
|
||||
}
|
||||
cc := &raftpb.ConfChange{
|
||||
Type: raftpb.ConfChangeAddNode,
|
||||
NodeID: self,
|
||||
Context: ctx,
|
||||
}
|
||||
e := raftpb.Entry{
|
||||
Type: raftpb.EntryConfChange,
|
||||
Data: pbutil.MustMarshal(cc),
|
||||
Term: term,
|
||||
Index: next,
|
||||
}
|
||||
ents = append(ents, e)
|
||||
}
|
||||
return ents
|
||||
}
|
||||
1365
vendor/github.com/coreos/etcd/etcdserver/server.go
generated
vendored
Normal file
1365
vendor/github.com/coreos/etcd/etcdserver/server.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load Diff
71
vendor/github.com/coreos/etcd/etcdserver/snapshot_merge.go
generated
vendored
Normal file
71
vendor/github.com/coreos/etcd/etcdserver/snapshot_merge.go
generated
vendored
Normal file
@@ -0,0 +1,71 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 etcdserver
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
|
||||
"github.com/coreos/etcd/raft/raftpb"
|
||||
"github.com/coreos/etcd/snap"
|
||||
"github.com/coreos/etcd/storage/backend"
|
||||
)
|
||||
|
||||
// createMergedSnapshotMessage creates a snapshot message that contains: raft status (term, conf),
|
||||
// a snapshot of v2 store inside raft.Snapshot as []byte, a snapshot of v3 KV in the top level message
|
||||
// as ReadCloser.
|
||||
func (s *EtcdServer) createMergedSnapshotMessage(m raftpb.Message, snapi uint64, confState raftpb.ConfState) snap.Message {
|
||||
snapt, err := s.r.raftStorage.Term(snapi)
|
||||
if err != nil {
|
||||
log.Panicf("get term should never fail: %v", err)
|
||||
}
|
||||
|
||||
// get a snapshot of v2 store as []byte
|
||||
clone := s.store.Clone()
|
||||
d, err := clone.SaveNoCopy()
|
||||
if err != nil {
|
||||
plog.Panicf("store save should never fail: %v", err)
|
||||
}
|
||||
|
||||
// get a snapshot of v3 KV as readCloser
|
||||
rc := newSnapshotReaderCloser(s.be.Snapshot())
|
||||
|
||||
// put the []byte snapshot of store into raft snapshot and return the merged snapshot with
|
||||
// KV readCloser snapshot.
|
||||
snapshot := raftpb.Snapshot{
|
||||
Metadata: raftpb.SnapshotMetadata{
|
||||
Index: snapi,
|
||||
Term: snapt,
|
||||
ConfState: confState,
|
||||
},
|
||||
Data: d,
|
||||
}
|
||||
m.Snapshot = snapshot
|
||||
|
||||
return *snap.NewMessage(m, rc)
|
||||
}
|
||||
|
||||
func newSnapshotReaderCloser(snapshot backend.Snapshot) io.ReadCloser {
|
||||
pr, pw := io.Pipe()
|
||||
go func() {
|
||||
n, err := snapshot.WriteTo(pw)
|
||||
if err == nil {
|
||||
plog.Infof("wrote database snapshot out [total bytes: %d]", n)
|
||||
}
|
||||
pw.CloseWithError(err)
|
||||
snapshot.Close()
|
||||
}()
|
||||
return pr
|
||||
}
|
||||
123
vendor/github.com/coreos/etcd/etcdserver/stats/leader.go
generated
vendored
Normal file
123
vendor/github.com/coreos/etcd/etcdserver/stats/leader.go
generated
vendored
Normal file
@@ -0,0 +1,123 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 stats
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// LeaderStats is used by the leader in an etcd cluster, and encapsulates
|
||||
// statistics about communication with its followers
|
||||
type LeaderStats struct {
|
||||
// Leader is the ID of the leader in the etcd cluster.
|
||||
// TODO(jonboulle): clarify that these are IDs, not names
|
||||
Leader string `json:"leader"`
|
||||
Followers map[string]*FollowerStats `json:"followers"`
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// NewLeaderStats generates a new LeaderStats with the given id as leader
|
||||
func NewLeaderStats(id string) *LeaderStats {
|
||||
return &LeaderStats{
|
||||
Leader: id,
|
||||
Followers: make(map[string]*FollowerStats),
|
||||
}
|
||||
}
|
||||
|
||||
func (ls *LeaderStats) JSON() []byte {
|
||||
ls.Lock()
|
||||
stats := *ls
|
||||
ls.Unlock()
|
||||
b, err := json.Marshal(stats)
|
||||
// TODO(jonboulle): appropriate error handling?
|
||||
if err != nil {
|
||||
plog.Errorf("error marshalling leader stats (%v)", err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (ls *LeaderStats) Follower(name string) *FollowerStats {
|
||||
ls.Lock()
|
||||
defer ls.Unlock()
|
||||
fs, ok := ls.Followers[name]
|
||||
if !ok {
|
||||
fs = &FollowerStats{}
|
||||
fs.Latency.Minimum = 1 << 63
|
||||
ls.Followers[name] = fs
|
||||
}
|
||||
return fs
|
||||
}
|
||||
|
||||
// FollowerStats encapsulates various statistics about a follower in an etcd cluster
|
||||
type FollowerStats struct {
|
||||
Latency LatencyStats `json:"latency"`
|
||||
Counts CountsStats `json:"counts"`
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
// LatencyStats encapsulates latency statistics.
|
||||
type LatencyStats struct {
|
||||
Current float64 `json:"current"`
|
||||
Average float64 `json:"average"`
|
||||
averageSquare float64
|
||||
StandardDeviation float64 `json:"standardDeviation"`
|
||||
Minimum float64 `json:"minimum"`
|
||||
Maximum float64 `json:"maximum"`
|
||||
}
|
||||
|
||||
// CountsStats encapsulates raft statistics.
|
||||
type CountsStats struct {
|
||||
Fail uint64 `json:"fail"`
|
||||
Success uint64 `json:"success"`
|
||||
}
|
||||
|
||||
// Succ updates the FollowerStats with a successful send
|
||||
func (fs *FollowerStats) Succ(d time.Duration) {
|
||||
fs.Lock()
|
||||
defer fs.Unlock()
|
||||
|
||||
total := float64(fs.Counts.Success) * fs.Latency.Average
|
||||
totalSquare := float64(fs.Counts.Success) * fs.Latency.averageSquare
|
||||
|
||||
fs.Counts.Success++
|
||||
|
||||
fs.Latency.Current = float64(d) / (1000000.0)
|
||||
|
||||
if fs.Latency.Current > fs.Latency.Maximum {
|
||||
fs.Latency.Maximum = fs.Latency.Current
|
||||
}
|
||||
|
||||
if fs.Latency.Current < fs.Latency.Minimum {
|
||||
fs.Latency.Minimum = fs.Latency.Current
|
||||
}
|
||||
|
||||
fs.Latency.Average = (total + fs.Latency.Current) / float64(fs.Counts.Success)
|
||||
fs.Latency.averageSquare = (totalSquare + fs.Latency.Current*fs.Latency.Current) / float64(fs.Counts.Success)
|
||||
|
||||
// sdv = sqrt(avg(x^2) - avg(x)^2)
|
||||
fs.Latency.StandardDeviation = math.Sqrt(fs.Latency.averageSquare - fs.Latency.Average*fs.Latency.Average)
|
||||
}
|
||||
|
||||
// Fail updates the FollowerStats with an unsuccessful send
|
||||
func (fs *FollowerStats) Fail() {
|
||||
fs.Lock()
|
||||
defer fs.Unlock()
|
||||
fs.Counts.Fail++
|
||||
}
|
||||
110
vendor/github.com/coreos/etcd/etcdserver/stats/queue.go
generated
vendored
Normal file
110
vendor/github.com/coreos/etcd/etcdserver/stats/queue.go
generated
vendored
Normal file
@@ -0,0 +1,110 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 stats
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
queueCapacity = 200
|
||||
)
|
||||
|
||||
// RequestStats represent the stats for a request.
|
||||
// It encapsulates the sending time and the size of the request.
|
||||
type RequestStats struct {
|
||||
SendingTime time.Time
|
||||
Size int
|
||||
}
|
||||
|
||||
type statsQueue struct {
|
||||
items [queueCapacity]*RequestStats
|
||||
size int
|
||||
front int
|
||||
back int
|
||||
totalReqSize int
|
||||
rwl sync.RWMutex
|
||||
}
|
||||
|
||||
func (q *statsQueue) Len() int {
|
||||
return q.size
|
||||
}
|
||||
|
||||
func (q *statsQueue) ReqSize() int {
|
||||
return q.totalReqSize
|
||||
}
|
||||
|
||||
// FrontAndBack gets the front and back elements in the queue
|
||||
// We must grab front and back together with the protection of the lock
|
||||
func (q *statsQueue) frontAndBack() (*RequestStats, *RequestStats) {
|
||||
q.rwl.RLock()
|
||||
defer q.rwl.RUnlock()
|
||||
if q.size != 0 {
|
||||
return q.items[q.front], q.items[q.back]
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Insert function insert a RequestStats into the queue and update the records
|
||||
func (q *statsQueue) Insert(p *RequestStats) {
|
||||
q.rwl.Lock()
|
||||
defer q.rwl.Unlock()
|
||||
|
||||
q.back = (q.back + 1) % queueCapacity
|
||||
|
||||
if q.size == queueCapacity { //dequeue
|
||||
q.totalReqSize -= q.items[q.front].Size
|
||||
q.front = (q.back + 1) % queueCapacity
|
||||
} else {
|
||||
q.size++
|
||||
}
|
||||
|
||||
q.items[q.back] = p
|
||||
q.totalReqSize += q.items[q.back].Size
|
||||
|
||||
}
|
||||
|
||||
// Rate function returns the package rate and byte rate
|
||||
func (q *statsQueue) Rate() (float64, float64) {
|
||||
front, back := q.frontAndBack()
|
||||
|
||||
if front == nil || back == nil {
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
if time.Now().Sub(back.SendingTime) > time.Second {
|
||||
q.Clear()
|
||||
return 0, 0
|
||||
}
|
||||
|
||||
sampleDuration := back.SendingTime.Sub(front.SendingTime)
|
||||
|
||||
pr := float64(q.Len()) / float64(sampleDuration) * float64(time.Second)
|
||||
|
||||
br := float64(q.ReqSize()) / float64(sampleDuration) * float64(time.Second)
|
||||
|
||||
return pr, br
|
||||
}
|
||||
|
||||
// Clear function clear up the statsQueue
|
||||
func (q *statsQueue) Clear() {
|
||||
q.rwl.Lock()
|
||||
defer q.rwl.Unlock()
|
||||
q.back = -1
|
||||
q.front = 0
|
||||
q.size = 0
|
||||
q.totalReqSize = 0
|
||||
}
|
||||
150
vendor/github.com/coreos/etcd/etcdserver/stats/server.go
generated
vendored
Normal file
150
vendor/github.com/coreos/etcd/etcdserver/stats/server.go
generated
vendored
Normal file
@@ -0,0 +1,150 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 stats
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/raft"
|
||||
)
|
||||
|
||||
// ServerStats encapsulates various statistics about an EtcdServer and its
|
||||
// communication with other members of the cluster
|
||||
type ServerStats struct {
|
||||
Name string `json:"name"`
|
||||
// ID is the raft ID of the node.
|
||||
// TODO(jonboulle): use ID instead of name?
|
||||
ID string `json:"id"`
|
||||
State raft.StateType `json:"state"`
|
||||
StartTime time.Time `json:"startTime"`
|
||||
|
||||
LeaderInfo struct {
|
||||
Name string `json:"leader"`
|
||||
Uptime string `json:"uptime"`
|
||||
StartTime time.Time `json:"startTime"`
|
||||
} `json:"leaderInfo"`
|
||||
|
||||
RecvAppendRequestCnt uint64 `json:"recvAppendRequestCnt,"`
|
||||
RecvingPkgRate float64 `json:"recvPkgRate,omitempty"`
|
||||
RecvingBandwidthRate float64 `json:"recvBandwidthRate,omitempty"`
|
||||
|
||||
SendAppendRequestCnt uint64 `json:"sendAppendRequestCnt"`
|
||||
SendingPkgRate float64 `json:"sendPkgRate,omitempty"`
|
||||
SendingBandwidthRate float64 `json:"sendBandwidthRate,omitempty"`
|
||||
|
||||
sendRateQueue *statsQueue
|
||||
recvRateQueue *statsQueue
|
||||
|
||||
sync.Mutex
|
||||
}
|
||||
|
||||
func (ss *ServerStats) JSON() []byte {
|
||||
ss.Lock()
|
||||
stats := *ss
|
||||
ss.Unlock()
|
||||
stats.LeaderInfo.Uptime = time.Now().Sub(stats.LeaderInfo.StartTime).String()
|
||||
stats.SendingPkgRate, stats.SendingBandwidthRate = stats.SendRates()
|
||||
stats.RecvingPkgRate, stats.RecvingBandwidthRate = stats.RecvRates()
|
||||
b, err := json.Marshal(stats)
|
||||
// TODO(jonboulle): appropriate error handling?
|
||||
if err != nil {
|
||||
log.Printf("stats: error marshalling server stats: %v", err)
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// Initialize clears the statistics of ServerStats and resets its start time
|
||||
func (ss *ServerStats) Initialize() {
|
||||
if ss == nil {
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
ss.StartTime = now
|
||||
ss.LeaderInfo.StartTime = now
|
||||
ss.sendRateQueue = &statsQueue{
|
||||
back: -1,
|
||||
}
|
||||
ss.recvRateQueue = &statsQueue{
|
||||
back: -1,
|
||||
}
|
||||
}
|
||||
|
||||
// RecvRates calculates and returns the rate of received append requests
|
||||
func (ss *ServerStats) RecvRates() (float64, float64) {
|
||||
return ss.recvRateQueue.Rate()
|
||||
}
|
||||
|
||||
// SendRates calculates and returns the rate of sent append requests
|
||||
func (ss *ServerStats) SendRates() (float64, float64) {
|
||||
return ss.sendRateQueue.Rate()
|
||||
}
|
||||
|
||||
// RecvAppendReq updates the ServerStats in response to an AppendRequest
|
||||
// from the given leader being received
|
||||
func (ss *ServerStats) RecvAppendReq(leader string, reqSize int) {
|
||||
ss.Lock()
|
||||
defer ss.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
ss.State = raft.StateFollower
|
||||
if leader != ss.LeaderInfo.Name {
|
||||
ss.LeaderInfo.Name = leader
|
||||
ss.LeaderInfo.StartTime = now
|
||||
}
|
||||
|
||||
ss.recvRateQueue.Insert(
|
||||
&RequestStats{
|
||||
SendingTime: now,
|
||||
Size: reqSize,
|
||||
},
|
||||
)
|
||||
ss.RecvAppendRequestCnt++
|
||||
}
|
||||
|
||||
// SendAppendReq updates the ServerStats in response to an AppendRequest
|
||||
// being sent by this server
|
||||
func (ss *ServerStats) SendAppendReq(reqSize int) {
|
||||
ss.Lock()
|
||||
defer ss.Unlock()
|
||||
|
||||
now := time.Now()
|
||||
|
||||
if ss.State != raft.StateLeader {
|
||||
ss.State = raft.StateLeader
|
||||
ss.LeaderInfo.Name = ss.ID
|
||||
ss.LeaderInfo.StartTime = now
|
||||
}
|
||||
|
||||
ss.sendRateQueue.Insert(
|
||||
&RequestStats{
|
||||
SendingTime: now,
|
||||
Size: reqSize,
|
||||
},
|
||||
)
|
||||
|
||||
ss.SendAppendRequestCnt++
|
||||
}
|
||||
|
||||
func (ss *ServerStats) BecomeLeader() {
|
||||
if ss.State != raft.StateLeader {
|
||||
ss.State = raft.StateLeader
|
||||
ss.LeaderInfo.Name = ss.ID
|
||||
ss.LeaderInfo.StartTime = time.Now()
|
||||
}
|
||||
}
|
||||
32
vendor/github.com/coreos/etcd/etcdserver/stats/stats.go
generated
vendored
Normal file
32
vendor/github.com/coreos/etcd/etcdserver/stats/stats.go
generated
vendored
Normal file
@@ -0,0 +1,32 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 stats defines a standard interface for etcd cluster statistics.
|
||||
package stats
|
||||
|
||||
import "github.com/coreos/pkg/capnslog"
|
||||
|
||||
var (
|
||||
plog = capnslog.NewPackageLogger("github.com/coreos/etcd/etcdserver", "stats")
|
||||
)
|
||||
|
||||
type Stats interface {
|
||||
// SelfStats returns the struct representing statistics of this server
|
||||
SelfStats() []byte
|
||||
// LeaderStats returns the statistics of all followers in the cluster
|
||||
// if this server is leader. Otherwise, nil is returned.
|
||||
LeaderStats() []byte
|
||||
// StoreStats returns statistics of the store backing this EtcdServer
|
||||
StoreStats() []byte
|
||||
}
|
||||
146
vendor/github.com/coreos/etcd/etcdserver/storage.go
generated
vendored
Normal file
146
vendor/github.com/coreos/etcd/etcdserver/storage.go
generated
vendored
Normal file
@@ -0,0 +1,146 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 etcdserver
|
||||
|
||||
import (
|
||||
"io"
|
||||
"os"
|
||||
"path"
|
||||
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/pkg/pbutil"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/etcd/raft/raftpb"
|
||||
"github.com/coreos/etcd/snap"
|
||||
"github.com/coreos/etcd/version"
|
||||
"github.com/coreos/etcd/wal"
|
||||
"github.com/coreos/etcd/wal/walpb"
|
||||
)
|
||||
|
||||
type Storage interface {
|
||||
// Save function saves ents and state to the underlying stable storage.
|
||||
// Save MUST block until st and ents are on stable storage.
|
||||
Save(st raftpb.HardState, ents []raftpb.Entry) error
|
||||
// SaveSnap function saves snapshot to the underlying stable storage.
|
||||
SaveSnap(snap raftpb.Snapshot) error
|
||||
// DBFilePath returns the file path of database snapshot saved with given
|
||||
// id.
|
||||
DBFilePath(id uint64) (string, error)
|
||||
// Close closes the Storage and performs finalization.
|
||||
Close() error
|
||||
}
|
||||
|
||||
type storage struct {
|
||||
*wal.WAL
|
||||
*snap.Snapshotter
|
||||
}
|
||||
|
||||
func NewStorage(w *wal.WAL, s *snap.Snapshotter) Storage {
|
||||
return &storage{w, s}
|
||||
}
|
||||
|
||||
// SaveSnap saves the snapshot to disk and release the locked
|
||||
// wal files since they will not be used.
|
||||
func (st *storage) SaveSnap(snap raftpb.Snapshot) error {
|
||||
walsnap := walpb.Snapshot{
|
||||
Index: snap.Metadata.Index,
|
||||
Term: snap.Metadata.Term,
|
||||
}
|
||||
err := st.WAL.SaveSnapshot(walsnap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = st.Snapshotter.SaveSnap(snap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = st.WAL.ReleaseLockTo(snap.Metadata.Index)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readWAL(waldir string, snap walpb.Snapshot) (w *wal.WAL, id, cid types.ID, st raftpb.HardState, ents []raftpb.Entry) {
|
||||
var (
|
||||
err error
|
||||
wmetadata []byte
|
||||
)
|
||||
|
||||
repaired := false
|
||||
for {
|
||||
if w, err = wal.Open(waldir, snap); err != nil {
|
||||
plog.Fatalf("open wal error: %v", err)
|
||||
}
|
||||
if wmetadata, st, ents, err = w.ReadAll(); err != nil {
|
||||
w.Close()
|
||||
// we can only repair ErrUnexpectedEOF and we never repair twice.
|
||||
if repaired || err != io.ErrUnexpectedEOF {
|
||||
plog.Fatalf("read wal error (%v) and cannot be repaired", err)
|
||||
}
|
||||
if !wal.Repair(waldir) {
|
||||
plog.Fatalf("WAL error (%v) cannot be repaired", err)
|
||||
} else {
|
||||
plog.Infof("repaired WAL error (%v)", err)
|
||||
repaired = true
|
||||
}
|
||||
continue
|
||||
}
|
||||
break
|
||||
}
|
||||
var metadata pb.Metadata
|
||||
pbutil.MustUnmarshal(&metadata, wmetadata)
|
||||
id = types.ID(metadata.NodeID)
|
||||
cid = types.ID(metadata.ClusterID)
|
||||
return
|
||||
}
|
||||
|
||||
// upgradeDataDir converts an older version of the etcdServer data to the newest version.
|
||||
// It must ensure that, after upgrading, the most recent version is present.
|
||||
func upgradeDataDir(baseDataDir string, name string, ver version.DataDirVersion) error {
|
||||
switch ver {
|
||||
case version.DataDir2_0:
|
||||
err := makeMemberDir(baseDataDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
fallthrough
|
||||
case version.DataDir2_0_1:
|
||||
fallthrough
|
||||
default:
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func makeMemberDir(dir string) error {
|
||||
membdir := path.Join(dir, "member")
|
||||
_, err := os.Stat(membdir)
|
||||
switch {
|
||||
case err == nil:
|
||||
return nil
|
||||
case !os.IsNotExist(err):
|
||||
return err
|
||||
}
|
||||
if err := os.MkdirAll(membdir, 0700); err != nil {
|
||||
return err
|
||||
}
|
||||
names := []string{"snap", "wal"}
|
||||
for _, name := range names {
|
||||
if err := os.Rename(path.Join(dir, name), path.Join(membdir, name)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
42
vendor/github.com/coreos/etcd/etcdserver/util.go
generated
vendored
Normal file
42
vendor/github.com/coreos/etcd/etcdserver/util.go
generated
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 etcdserver
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/coreos/etcd/etcdserver/membership"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/etcd/rafthttp"
|
||||
)
|
||||
|
||||
// isConnectedToQuorumSince checks whether the local member is connected to the
|
||||
// quorum of the cluster since the given time.
|
||||
func isConnectedToQuorumSince(transport rafthttp.Transporter, since time.Time, self types.ID, members []*membership.Member) bool {
|
||||
var connectedNum int
|
||||
for _, m := range members {
|
||||
if m.ID == self || isConnectedSince(transport, since, m.ID) {
|
||||
connectedNum++
|
||||
}
|
||||
}
|
||||
return connectedNum >= (len(members)+1)/2
|
||||
}
|
||||
|
||||
// isConnectedSince checks whether the local member is connected to the
|
||||
// remote member since the given time.
|
||||
func isConnectedSince(transport rafthttp.Transporter, since time.Time, remote types.ID) bool {
|
||||
t := transport.ActiveSince(remote)
|
||||
return !t.IsZero() && t.Before(since)
|
||||
}
|
||||
273
vendor/github.com/coreos/etcd/etcdserver/v3demo_server.go
generated
vendored
Normal file
273
vendor/github.com/coreos/etcd/etcdserver/v3demo_server.go
generated
vendored
Normal file
@@ -0,0 +1,273 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// 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 etcdserver
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/lease"
|
||||
"github.com/coreos/etcd/lease/leasehttp"
|
||||
dstorage "github.com/coreos/etcd/storage"
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
const (
|
||||
// the max request size that raft accepts.
|
||||
// TODO: make this a flag? But we probably do not want to
|
||||
// accept large request which might block raft stream. User
|
||||
// specify a large value might end up with shooting in the foot.
|
||||
maxRequestBytes = 1.5 * 1024 * 1024
|
||||
)
|
||||
|
||||
type RaftKV interface {
|
||||
Range(ctx context.Context, r *pb.RangeRequest) (*pb.RangeResponse, error)
|
||||
Put(ctx context.Context, r *pb.PutRequest) (*pb.PutResponse, error)
|
||||
DeleteRange(ctx context.Context, r *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error)
|
||||
Txn(ctx context.Context, r *pb.TxnRequest) (*pb.TxnResponse, error)
|
||||
Compact(ctx context.Context, r *pb.CompactionRequest) (*pb.CompactionResponse, error)
|
||||
}
|
||||
|
||||
type Lessor interface {
|
||||
// LeaseGrant sends LeaseGrant request to raft and apply it after committed.
|
||||
LeaseGrant(ctx context.Context, r *pb.LeaseGrantRequest) (*pb.LeaseGrantResponse, error)
|
||||
// LeaseRevoke sends LeaseRevoke request to raft and apply it after committed.
|
||||
LeaseRevoke(ctx context.Context, r *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error)
|
||||
|
||||
// LeaseRenew renews the lease with given ID. The renewed TTL is returned. Or an error
|
||||
// is returned.
|
||||
LeaseRenew(id lease.LeaseID) (int64, error)
|
||||
}
|
||||
|
||||
type Authenticator interface {
|
||||
AuthEnable(ctx context.Context, r *pb.AuthEnableRequest) (*pb.AuthEnableResponse, error)
|
||||
UserAdd(ctx context.Context, r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error)
|
||||
UserDelete(ctx context.Context, r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error)
|
||||
UserChangePassword(ctx context.Context, r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error)
|
||||
RoleAdd(ctx context.Context, r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error)
|
||||
}
|
||||
|
||||
func (s *EtcdServer) Range(ctx context.Context, r *pb.RangeRequest) (*pb.RangeResponse, error) {
|
||||
if r.Serializable {
|
||||
return s.applyV3.Range(noTxn, r)
|
||||
}
|
||||
|
||||
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{Range: r})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.resp.(*pb.RangeResponse), result.err
|
||||
}
|
||||
|
||||
func (s *EtcdServer) Put(ctx context.Context, r *pb.PutRequest) (*pb.PutResponse, error) {
|
||||
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{Put: r})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.resp.(*pb.PutResponse), result.err
|
||||
}
|
||||
|
||||
func (s *EtcdServer) DeleteRange(ctx context.Context, r *pb.DeleteRangeRequest) (*pb.DeleteRangeResponse, error) {
|
||||
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{DeleteRange: r})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.resp.(*pb.DeleteRangeResponse), result.err
|
||||
}
|
||||
|
||||
func (s *EtcdServer) Txn(ctx context.Context, r *pb.TxnRequest) (*pb.TxnResponse, error) {
|
||||
if isTxnSerializable(r) {
|
||||
return s.applyV3.Txn(r)
|
||||
}
|
||||
|
||||
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{Txn: r})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.resp.(*pb.TxnResponse), result.err
|
||||
}
|
||||
|
||||
func isTxnSerializable(r *pb.TxnRequest) bool {
|
||||
for _, u := range r.Success {
|
||||
if r := u.GetRequestRange(); r == nil || !r.Serializable {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for _, u := range r.Failure {
|
||||
if r := u.GetRequestRange(); r == nil || !r.Serializable {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (s *EtcdServer) Compact(ctx context.Context, r *pb.CompactionRequest) (*pb.CompactionResponse, error) {
|
||||
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{Compaction: r})
|
||||
if r.Physical && result.physc != nil {
|
||||
<-result.physc
|
||||
// The compaction is done deleting keys; the hash is now settled
|
||||
// but the data is not necessarily committed. If there's a crash,
|
||||
// the hash may revert to a hash prior to compaction completing
|
||||
// if the compaction resumes. Force the finished compaction to
|
||||
// commit so it won't resume following a crash.
|
||||
s.be.ForceCommit()
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
resp := result.resp.(*pb.CompactionResponse)
|
||||
if resp == nil {
|
||||
resp = &pb.CompactionResponse{}
|
||||
}
|
||||
if resp.Header == nil {
|
||||
resp.Header = &pb.ResponseHeader{}
|
||||
}
|
||||
resp.Header.Revision = s.kv.Rev()
|
||||
return resp, result.err
|
||||
}
|
||||
|
||||
func (s *EtcdServer) LeaseGrant(ctx context.Context, r *pb.LeaseGrantRequest) (*pb.LeaseGrantResponse, error) {
|
||||
// no id given? choose one
|
||||
for r.ID == int64(lease.NoLease) {
|
||||
// only use positive int64 id's
|
||||
r.ID = int64(s.reqIDGen.Next() & ((1 << 63) - 1))
|
||||
}
|
||||
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{LeaseGrant: r})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.resp.(*pb.LeaseGrantResponse), result.err
|
||||
}
|
||||
|
||||
func (s *EtcdServer) LeaseRevoke(ctx context.Context, r *pb.LeaseRevokeRequest) (*pb.LeaseRevokeResponse, error) {
|
||||
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{LeaseRevoke: r})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.resp.(*pb.LeaseRevokeResponse), result.err
|
||||
}
|
||||
|
||||
func (s *EtcdServer) LeaseRenew(id lease.LeaseID) (int64, error) {
|
||||
ttl, err := s.lessor.Renew(id)
|
||||
if err == nil {
|
||||
return ttl, nil
|
||||
}
|
||||
if err != lease.ErrNotPrimary {
|
||||
return -1, err
|
||||
}
|
||||
|
||||
// renewals don't go through raft; forward to leader manually
|
||||
leader := s.cluster.Member(s.Leader())
|
||||
for i := 0; i < 5 && leader == nil; i++ {
|
||||
// wait an election
|
||||
dur := time.Duration(s.cfg.ElectionTicks) * time.Duration(s.cfg.TickMs) * time.Millisecond
|
||||
select {
|
||||
case <-time.After(dur):
|
||||
leader = s.cluster.Member(s.Leader())
|
||||
case <-s.done:
|
||||
return -1, ErrStopped
|
||||
}
|
||||
}
|
||||
if leader == nil || len(leader.PeerURLs) == 0 {
|
||||
return -1, ErrNoLeader
|
||||
}
|
||||
|
||||
for _, url := range leader.PeerURLs {
|
||||
lurl := url + "/leases"
|
||||
ttl, err = leasehttp.RenewHTTP(id, lurl, s.peerRt, s.cfg.peerDialTimeout())
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
return ttl, err
|
||||
}
|
||||
|
||||
func (s *EtcdServer) Alarm(ctx context.Context, r *pb.AlarmRequest) (*pb.AlarmResponse, error) {
|
||||
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{Alarm: r})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.resp.(*pb.AlarmResponse), result.err
|
||||
}
|
||||
|
||||
func (s *EtcdServer) AuthEnable(ctx context.Context, r *pb.AuthEnableRequest) (*pb.AuthEnableResponse, error) {
|
||||
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthEnable: r})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.resp.(*pb.AuthEnableResponse), result.err
|
||||
}
|
||||
|
||||
func (s *EtcdServer) UserAdd(ctx context.Context, r *pb.AuthUserAddRequest) (*pb.AuthUserAddResponse, error) {
|
||||
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthUserAdd: r})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.resp.(*pb.AuthUserAddResponse), result.err
|
||||
}
|
||||
|
||||
func (s *EtcdServer) UserDelete(ctx context.Context, r *pb.AuthUserDeleteRequest) (*pb.AuthUserDeleteResponse, error) {
|
||||
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthUserDelete: r})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.resp.(*pb.AuthUserDeleteResponse), result.err
|
||||
}
|
||||
|
||||
func (s *EtcdServer) UserChangePassword(ctx context.Context, r *pb.AuthUserChangePasswordRequest) (*pb.AuthUserChangePasswordResponse, error) {
|
||||
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthUserChangePassword: r})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.resp.(*pb.AuthUserChangePasswordResponse), result.err
|
||||
}
|
||||
|
||||
func (s *EtcdServer) RoleAdd(ctx context.Context, r *pb.AuthRoleAddRequest) (*pb.AuthRoleAddResponse, error) {
|
||||
result, err := s.processInternalRaftRequest(ctx, pb.InternalRaftRequest{AuthRoleAdd: r})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return result.resp.(*pb.AuthRoleAddResponse), result.err
|
||||
}
|
||||
|
||||
func (s *EtcdServer) processInternalRaftRequest(ctx context.Context, r pb.InternalRaftRequest) (*applyResult, error) {
|
||||
r.ID = s.reqIDGen.Next()
|
||||
|
||||
data, err := r.Marshal()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(data) > maxRequestBytes {
|
||||
return nil, ErrRequestTooLarge
|
||||
}
|
||||
|
||||
ch := s.w.Register(r.ID)
|
||||
|
||||
s.r.Propose(ctx, data)
|
||||
|
||||
select {
|
||||
case x := <-ch:
|
||||
return x.(*applyResult), nil
|
||||
case <-ctx.Done():
|
||||
s.w.Trigger(r.ID, nil) // GC wait
|
||||
return nil, ctx.Err()
|
||||
case <-s.done:
|
||||
return nil, ErrStopped
|
||||
}
|
||||
}
|
||||
|
||||
// Watchable returns a watchable interface attached to the etcdserver.
|
||||
func (s *EtcdServer) Watchable() dstorage.Watchable { return s.KV() }
|
||||
772
vendor/github.com/coreos/etcd/integration/cluster.go
generated
vendored
Normal file
772
vendor/github.com/coreos/etcd/integration/cluster.go
generated
vendored
Normal file
@@ -0,0 +1,772 @@
|
||||
// Copyright 2016 CoreOS, Inc.
|
||||
//
|
||||
// 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 (
|
||||
"crypto/tls"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/context"
|
||||
"google.golang.org/grpc"
|
||||
|
||||
"github.com/coreos/etcd/client"
|
||||
"github.com/coreos/etcd/clientv3"
|
||||
"github.com/coreos/etcd/etcdserver"
|
||||
"github.com/coreos/etcd/etcdserver/api/v2http"
|
||||
"github.com/coreos/etcd/etcdserver/api/v3rpc"
|
||||
pb "github.com/coreos/etcd/etcdserver/etcdserverpb"
|
||||
"github.com/coreos/etcd/pkg/testutil"
|
||||
"github.com/coreos/etcd/pkg/transport"
|
||||
"github.com/coreos/etcd/pkg/types"
|
||||
"github.com/coreos/etcd/rafthttp"
|
||||
)
|
||||
|
||||
const (
|
||||
tickDuration = 10 * time.Millisecond
|
||||
clusterName = "etcd"
|
||||
requestTimeout = 20 * time.Second
|
||||
)
|
||||
|
||||
var (
|
||||
electionTicks = 10
|
||||
|
||||
// integration test uses well-known ports to listen for each running member,
|
||||
// which ensures restarted member could listen on specific port again.
|
||||
nextListenPort int64 = 20000
|
||||
|
||||
testTLSInfo = transport.TLSInfo{
|
||||
KeyFile: "./fixtures/server.key.insecure",
|
||||
CertFile: "./fixtures/server.crt",
|
||||
TrustedCAFile: "./fixtures/ca.crt",
|
||||
ClientCertAuth: true,
|
||||
}
|
||||
)
|
||||
|
||||
type ClusterConfig struct {
|
||||
Size int
|
||||
PeerTLS *transport.TLSInfo
|
||||
ClientTLS *transport.TLSInfo
|
||||
DiscoveryURL string
|
||||
UseGRPC bool
|
||||
}
|
||||
|
||||
type cluster struct {
|
||||
cfg *ClusterConfig
|
||||
Members []*member
|
||||
}
|
||||
|
||||
func (c *cluster) fillClusterForMembers() error {
|
||||
if c.cfg.DiscoveryURL != "" {
|
||||
// cluster will be discovered
|
||||
return nil
|
||||
}
|
||||
|
||||
addrs := make([]string, 0)
|
||||
for _, m := range c.Members {
|
||||
scheme := "http"
|
||||
if m.PeerTLSInfo != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
for _, l := range m.PeerListeners {
|
||||
addrs = append(addrs, fmt.Sprintf("%s=%s://%s", m.Name, scheme, l.Addr().String()))
|
||||
}
|
||||
}
|
||||
clusterStr := strings.Join(addrs, ",")
|
||||
var err error
|
||||
for _, m := range c.Members {
|
||||
m.InitialPeerURLsMap, err = types.NewURLsMap(clusterStr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func newCluster(t *testing.T, cfg *ClusterConfig) *cluster {
|
||||
c := &cluster{cfg: cfg}
|
||||
ms := make([]*member, cfg.Size)
|
||||
for i := 0; i < cfg.Size; i++ {
|
||||
ms[i] = c.mustNewMember(t)
|
||||
}
|
||||
c.Members = ms
|
||||
if err := c.fillClusterForMembers(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return c
|
||||
}
|
||||
|
||||
// NewCluster returns an unlaunched cluster of the given size which has been
|
||||
// set to use static bootstrap.
|
||||
func NewCluster(t *testing.T, size int) *cluster {
|
||||
return newCluster(t, &ClusterConfig{Size: size})
|
||||
}
|
||||
|
||||
// NewClusterByConfig returns an unlaunched cluster defined by a cluster configuration
|
||||
func NewClusterByConfig(t *testing.T, cfg *ClusterConfig) *cluster {
|
||||
return newCluster(t, cfg)
|
||||
}
|
||||
|
||||
func (c *cluster) Launch(t *testing.T) {
|
||||
errc := make(chan error)
|
||||
for _, m := range c.Members {
|
||||
// Members are launched in separate goroutines because if they boot
|
||||
// using discovery url, they have to wait for others to register to continue.
|
||||
go func(m *member) {
|
||||
errc <- m.Launch()
|
||||
}(m)
|
||||
}
|
||||
for range c.Members {
|
||||
if err := <-errc; err != nil {
|
||||
t.Fatalf("error setting up member: %v", err)
|
||||
}
|
||||
}
|
||||
// wait cluster to be stable to receive future client requests
|
||||
c.waitMembersMatch(t, c.HTTPMembers())
|
||||
c.waitVersion()
|
||||
}
|
||||
|
||||
func (c *cluster) URL(i int) string {
|
||||
return c.Members[i].ClientURLs[0].String()
|
||||
}
|
||||
|
||||
// URLs returns a list of all active client URLs in the cluster
|
||||
func (c *cluster) URLs() []string {
|
||||
urls := make([]string, 0)
|
||||
for _, m := range c.Members {
|
||||
select {
|
||||
case <-m.s.StopNotify():
|
||||
continue
|
||||
default:
|
||||
}
|
||||
for _, u := range m.ClientURLs {
|
||||
urls = append(urls, u.String())
|
||||
}
|
||||
}
|
||||
return urls
|
||||
}
|
||||
|
||||
// HTTPMembers returns a list of all active members as client.Members
|
||||
func (c *cluster) HTTPMembers() []client.Member {
|
||||
ms := []client.Member{}
|
||||
for _, m := range c.Members {
|
||||
pScheme, cScheme := "http", "http"
|
||||
if m.PeerTLSInfo != nil {
|
||||
pScheme = "https"
|
||||
}
|
||||
if m.ClientTLSInfo != nil {
|
||||
cScheme = "https"
|
||||
}
|
||||
cm := client.Member{Name: m.Name}
|
||||
for _, ln := range m.PeerListeners {
|
||||
cm.PeerURLs = append(cm.PeerURLs, pScheme+"://"+ln.Addr().String())
|
||||
}
|
||||
for _, ln := range m.ClientListeners {
|
||||
cm.ClientURLs = append(cm.ClientURLs, cScheme+"://"+ln.Addr().String())
|
||||
}
|
||||
ms = append(ms, cm)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
|
||||
func (c *cluster) mustNewMember(t *testing.T) *member {
|
||||
name := c.name(rand.Int())
|
||||
m := mustNewMember(t, name, c.cfg.PeerTLS, c.cfg.ClientTLS)
|
||||
m.DiscoveryURL = c.cfg.DiscoveryURL
|
||||
if c.cfg.UseGRPC {
|
||||
if err := m.listenGRPC(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (c *cluster) addMember(t *testing.T) {
|
||||
m := c.mustNewMember(t)
|
||||
|
||||
scheme := "http"
|
||||
if c.cfg.PeerTLS != nil {
|
||||
scheme = "https"
|
||||
}
|
||||
|
||||
// send add request to the cluster
|
||||
var err error
|
||||
for i := 0; i < len(c.Members); i++ {
|
||||
clientURL := c.URL(i)
|
||||
peerURL := scheme + "://" + m.PeerListeners[0].Addr().String()
|
||||
if err = c.addMemberByURL(t, clientURL, peerURL); err == nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatalf("add member failed on all members error: %v", err)
|
||||
}
|
||||
|
||||
m.InitialPeerURLsMap = types.URLsMap{}
|
||||
for _, mm := range c.Members {
|
||||
m.InitialPeerURLsMap[mm.Name] = mm.PeerURLs
|
||||
}
|
||||
m.InitialPeerURLsMap[m.Name] = m.PeerURLs
|
||||
m.NewCluster = false
|
||||
if err := m.Launch(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
c.Members = append(c.Members, m)
|
||||
// wait cluster to be stable to receive future client requests
|
||||
c.waitMembersMatch(t, c.HTTPMembers())
|
||||
}
|
||||
|
||||
func (c *cluster) addMemberByURL(t *testing.T, clientURL, peerURL string) error {
|
||||
cc := mustNewHTTPClient(t, []string{clientURL}, c.cfg.ClientTLS)
|
||||
ma := client.NewMembersAPI(cc)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
|
||||
if _, err := ma.Add(ctx, peerURL); err != nil {
|
||||
return err
|
||||
}
|
||||
cancel()
|
||||
|
||||
// wait for the add node entry applied in the cluster
|
||||
members := append(c.HTTPMembers(), client.Member{PeerURLs: []string{peerURL}, ClientURLs: []string{}})
|
||||
c.waitMembersMatch(t, members)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *cluster) AddMember(t *testing.T) {
|
||||
c.addMember(t)
|
||||
}
|
||||
|
||||
func (c *cluster) RemoveMember(t *testing.T, id uint64) {
|
||||
// send remove request to the cluster
|
||||
cc := mustNewHTTPClient(t, c.URLs(), c.cfg.ClientTLS)
|
||||
ma := client.NewMembersAPI(cc)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
|
||||
if err := ma.Remove(ctx, types.ID(id).String()); err != nil {
|
||||
t.Fatalf("unexpected remove error %v", err)
|
||||
}
|
||||
cancel()
|
||||
newMembers := make([]*member, 0)
|
||||
for _, m := range c.Members {
|
||||
if uint64(m.s.ID()) != id {
|
||||
newMembers = append(newMembers, m)
|
||||
} else {
|
||||
select {
|
||||
case <-m.s.StopNotify():
|
||||
m.Terminate(t)
|
||||
// 1s stop delay + election timeout + 1s disk and network delay + connection write timeout
|
||||
// TODO: remove connection write timeout by selecting on http response closeNotifier
|
||||
// blocking on https://github.com/golang/go/issues/9524
|
||||
case <-time.After(time.Second + time.Duration(electionTicks)*tickDuration + time.Second + rafthttp.ConnWriteTimeout):
|
||||
t.Fatalf("failed to remove member %s in time", m.s.ID())
|
||||
}
|
||||
}
|
||||
}
|
||||
c.Members = newMembers
|
||||
c.waitMembersMatch(t, c.HTTPMembers())
|
||||
}
|
||||
|
||||
func (c *cluster) Terminate(t *testing.T) {
|
||||
for _, m := range c.Members {
|
||||
m.Terminate(t)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cluster) waitMembersMatch(t *testing.T, membs []client.Member) {
|
||||
for _, u := range c.URLs() {
|
||||
cc := mustNewHTTPClient(t, []string{u}, c.cfg.ClientTLS)
|
||||
ma := client.NewMembersAPI(cc)
|
||||
for {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
|
||||
ms, err := ma.List(ctx)
|
||||
cancel()
|
||||
if err == nil && isMembersEqual(ms, membs) {
|
||||
break
|
||||
}
|
||||
time.Sleep(tickDuration)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c *cluster) waitLeader(t *testing.T, membs []*member) int {
|
||||
possibleLead := make(map[uint64]bool)
|
||||
var lead uint64
|
||||
for _, m := range membs {
|
||||
possibleLead[uint64(m.s.ID())] = true
|
||||
}
|
||||
|
||||
for lead == 0 || !possibleLead[lead] {
|
||||
lead = 0
|
||||
for _, m := range membs {
|
||||
select {
|
||||
case <-m.s.StopNotify():
|
||||
continue
|
||||
default:
|
||||
}
|
||||
if lead != 0 && lead != m.s.Lead() {
|
||||
lead = 0
|
||||
break
|
||||
}
|
||||
lead = m.s.Lead()
|
||||
}
|
||||
time.Sleep(10 * tickDuration)
|
||||
}
|
||||
|
||||
for i, m := range membs {
|
||||
if uint64(m.s.ID()) == lead {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
return -1
|
||||
}
|
||||
|
||||
func (c *cluster) waitVersion() {
|
||||
for _, m := range c.Members {
|
||||
for {
|
||||
if m.s.ClusterVersion() != nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(tickDuration)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *cluster) name(i int) string {
|
||||
return fmt.Sprint("node", i)
|
||||
}
|
||||
|
||||
// isMembersEqual checks whether two members equal except ID field.
|
||||
// The given wmembs should always set ID field to empty string.
|
||||
func isMembersEqual(membs []client.Member, wmembs []client.Member) bool {
|
||||
sort.Sort(SortableMemberSliceByPeerURLs(membs))
|
||||
sort.Sort(SortableMemberSliceByPeerURLs(wmembs))
|
||||
for i := range membs {
|
||||
membs[i].ID = ""
|
||||
}
|
||||
return reflect.DeepEqual(membs, wmembs)
|
||||
}
|
||||
|
||||
func newLocalListener(t *testing.T) net.Listener {
|
||||
port := atomic.AddInt64(&nextListenPort, 1)
|
||||
l, err := net.Listen("tcp", "127.0.0.1:"+strconv.FormatInt(port, 10))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
func newListenerWithAddr(t *testing.T, addr string) net.Listener {
|
||||
var err error
|
||||
var l net.Listener
|
||||
// TODO: we want to reuse a previous closed port immediately.
|
||||
// a better way is to set SO_REUSExx instead of doing retry.
|
||||
for i := 0; i < 5; i++ {
|
||||
l, err = net.Listen("tcp", addr)
|
||||
if err == nil {
|
||||
break
|
||||
}
|
||||
time.Sleep(500 * time.Millisecond)
|
||||
}
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return l
|
||||
}
|
||||
|
||||
type member struct {
|
||||
etcdserver.ServerConfig
|
||||
PeerListeners, ClientListeners []net.Listener
|
||||
grpcListener net.Listener
|
||||
// PeerTLSInfo enables peer TLS when set
|
||||
PeerTLSInfo *transport.TLSInfo
|
||||
// ClientTLSInfo enables client TLS when set
|
||||
ClientTLSInfo *transport.TLSInfo
|
||||
|
||||
raftHandler *testutil.PauseableHandler
|
||||
s *etcdserver.EtcdServer
|
||||
hss []*httptest.Server
|
||||
|
||||
grpcServer *grpc.Server
|
||||
grpcAddr string
|
||||
}
|
||||
|
||||
// mustNewMember return an inited member with the given name. If peerTLS is
|
||||
// set, it will use https scheme to communicate between peers.
|
||||
func mustNewMember(t *testing.T, name string, peerTLS *transport.TLSInfo, clientTLS *transport.TLSInfo) *member {
|
||||
var err error
|
||||
m := &member{}
|
||||
|
||||
peerScheme, clientScheme := "http", "http"
|
||||
if peerTLS != nil {
|
||||
peerScheme = "https"
|
||||
}
|
||||
if clientTLS != nil {
|
||||
clientScheme = "https"
|
||||
}
|
||||
|
||||
pln := newLocalListener(t)
|
||||
m.PeerListeners = []net.Listener{pln}
|
||||
m.PeerURLs, err = types.NewURLs([]string{peerScheme + "://" + pln.Addr().String()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m.PeerTLSInfo = peerTLS
|
||||
|
||||
cln := newLocalListener(t)
|
||||
m.ClientListeners = []net.Listener{cln}
|
||||
m.ClientURLs, err = types.NewURLs([]string{clientScheme + "://" + cln.Addr().String()})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m.ClientTLSInfo = clientTLS
|
||||
|
||||
m.Name = name
|
||||
|
||||
m.DataDir, err = ioutil.TempDir(os.TempDir(), "etcd")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
clusterStr := fmt.Sprintf("%s=%s://%s", name, peerScheme, pln.Addr().String())
|
||||
m.InitialPeerURLsMap, err = types.NewURLsMap(clusterStr)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
m.InitialClusterToken = clusterName
|
||||
m.NewCluster = true
|
||||
m.BootstrapTimeout = 10 * time.Millisecond
|
||||
if m.PeerTLSInfo != nil {
|
||||
m.ServerConfig.PeerTLSInfo = *m.PeerTLSInfo
|
||||
}
|
||||
m.ElectionTicks = electionTicks
|
||||
m.TickMs = uint(tickDuration / time.Millisecond)
|
||||
return m
|
||||
}
|
||||
|
||||
// listenGRPC starts a grpc server over a unix domain socket on the member
|
||||
func (m *member) listenGRPC() error {
|
||||
// prefix with localhost so cert has right domain
|
||||
m.grpcAddr = "localhost:" + m.Name + ".sock"
|
||||
if err := os.RemoveAll(m.grpcAddr); err != nil {
|
||||
return err
|
||||
}
|
||||
l, err := net.Listen("unix", m.grpcAddr)
|
||||
if err != nil {
|
||||
return fmt.Errorf("listen failed on grpc socket %s (%v)", m.grpcAddr, err)
|
||||
}
|
||||
m.grpcAddr = "unix://" + m.grpcAddr
|
||||
m.grpcListener = l
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewClientV3 creates a new grpc client connection to the member
|
||||
func NewClientV3(m *member) (*clientv3.Client, error) {
|
||||
if m.grpcAddr == "" {
|
||||
return nil, fmt.Errorf("member not configured for grpc")
|
||||
}
|
||||
|
||||
cfg := clientv3.Config{
|
||||
Endpoints: []string{m.grpcAddr},
|
||||
DialTimeout: 5 * time.Second,
|
||||
}
|
||||
|
||||
if m.ClientTLSInfo != nil {
|
||||
tls, err := m.ClientTLSInfo.ClientConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cfg.TLS = tls
|
||||
}
|
||||
return clientv3.New(cfg)
|
||||
}
|
||||
|
||||
// Clone returns a member with the same server configuration. The returned
|
||||
// member will not set PeerListeners and ClientListeners.
|
||||
func (m *member) Clone(t *testing.T) *member {
|
||||
mm := &member{}
|
||||
mm.ServerConfig = m.ServerConfig
|
||||
|
||||
var err error
|
||||
clientURLStrs := m.ClientURLs.StringSlice()
|
||||
mm.ClientURLs, err = types.NewURLs(clientURLStrs)
|
||||
if err != nil {
|
||||
// this should never fail
|
||||
panic(err)
|
||||
}
|
||||
peerURLStrs := m.PeerURLs.StringSlice()
|
||||
mm.PeerURLs, err = types.NewURLs(peerURLStrs)
|
||||
if err != nil {
|
||||
// this should never fail
|
||||
panic(err)
|
||||
}
|
||||
clusterStr := m.InitialPeerURLsMap.String()
|
||||
mm.InitialPeerURLsMap, err = types.NewURLsMap(clusterStr)
|
||||
if err != nil {
|
||||
// this should never fail
|
||||
panic(err)
|
||||
}
|
||||
mm.InitialClusterToken = m.InitialClusterToken
|
||||
mm.ElectionTicks = m.ElectionTicks
|
||||
mm.PeerTLSInfo = m.PeerTLSInfo
|
||||
mm.ClientTLSInfo = m.ClientTLSInfo
|
||||
return mm
|
||||
}
|
||||
|
||||
// Launch starts a member based on ServerConfig, PeerListeners
|
||||
// and ClientListeners.
|
||||
func (m *member) Launch() error {
|
||||
var err error
|
||||
if m.s, err = etcdserver.NewServer(&m.ServerConfig); err != nil {
|
||||
return fmt.Errorf("failed to initialize the etcd server: %v", err)
|
||||
}
|
||||
m.s.SyncTicker = time.Tick(500 * time.Millisecond)
|
||||
m.s.Start()
|
||||
|
||||
m.raftHandler = &testutil.PauseableHandler{Next: v2http.NewPeerHandler(m.s)}
|
||||
|
||||
for _, ln := range m.PeerListeners {
|
||||
hs := &httptest.Server{
|
||||
Listener: ln,
|
||||
Config: &http.Server{Handler: m.raftHandler},
|
||||
}
|
||||
if m.PeerTLSInfo == nil {
|
||||
hs.Start()
|
||||
} else {
|
||||
hs.TLS, err = m.PeerTLSInfo.ServerConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hs.StartTLS()
|
||||
}
|
||||
m.hss = append(m.hss, hs)
|
||||
}
|
||||
for _, ln := range m.ClientListeners {
|
||||
hs := &httptest.Server{
|
||||
Listener: ln,
|
||||
Config: &http.Server{Handler: v2http.NewClientHandler(m.s, m.ServerConfig.ReqTimeout())},
|
||||
}
|
||||
if m.ClientTLSInfo == nil {
|
||||
hs.Start()
|
||||
} else {
|
||||
hs.TLS, err = m.ClientTLSInfo.ServerConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
hs.StartTLS()
|
||||
}
|
||||
m.hss = append(m.hss, hs)
|
||||
}
|
||||
if m.grpcListener != nil {
|
||||
var (
|
||||
tlscfg *tls.Config
|
||||
)
|
||||
if m.ClientTLSInfo != nil && !m.ClientTLSInfo.Empty() {
|
||||
tlscfg, err = m.ClientTLSInfo.ServerConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
m.grpcServer = v3rpc.Server(m.s, tlscfg)
|
||||
go m.grpcServer.Serve(m.grpcListener)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *member) WaitOK(t *testing.T) {
|
||||
cc := mustNewHTTPClient(t, []string{m.URL()}, m.ClientTLSInfo)
|
||||
kapi := client.NewKeysAPI(cc)
|
||||
for {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), requestTimeout)
|
||||
_, err := kapi.Get(ctx, "/", nil)
|
||||
if err != nil {
|
||||
time.Sleep(tickDuration)
|
||||
continue
|
||||
}
|
||||
cancel()
|
||||
break
|
||||
}
|
||||
for m.s.Leader() == 0 {
|
||||
time.Sleep(tickDuration)
|
||||
}
|
||||
}
|
||||
|
||||
func (m *member) URL() string { return m.ClientURLs[0].String() }
|
||||
|
||||
func (m *member) Pause() {
|
||||
m.raftHandler.Pause()
|
||||
m.s.PauseSending()
|
||||
}
|
||||
|
||||
func (m *member) Resume() {
|
||||
m.raftHandler.Resume()
|
||||
m.s.ResumeSending()
|
||||
}
|
||||
|
||||
// Close stops the member's etcdserver and closes its connections
|
||||
func (m *member) Close() {
|
||||
if m.grpcServer != nil {
|
||||
m.grpcServer.Stop()
|
||||
m.grpcServer = nil
|
||||
}
|
||||
m.s.Stop()
|
||||
for _, hs := range m.hss {
|
||||
hs.CloseClientConnections()
|
||||
hs.Close()
|
||||
}
|
||||
}
|
||||
|
||||
// Stop stops the member, but the data dir of the member is preserved.
|
||||
func (m *member) Stop(t *testing.T) {
|
||||
m.Close()
|
||||
m.hss = nil
|
||||
}
|
||||
|
||||
// StopNotify unblocks when a member stop completes
|
||||
func (m *member) StopNotify() <-chan struct{} {
|
||||
return m.s.StopNotify()
|
||||
}
|
||||
|
||||
// Restart starts the member using the preserved data dir.
|
||||
func (m *member) Restart(t *testing.T) error {
|
||||
newPeerListeners := make([]net.Listener, 0)
|
||||
for _, ln := range m.PeerListeners {
|
||||
newPeerListeners = append(newPeerListeners, newListenerWithAddr(t, ln.Addr().String()))
|
||||
}
|
||||
m.PeerListeners = newPeerListeners
|
||||
newClientListeners := make([]net.Listener, 0)
|
||||
for _, ln := range m.ClientListeners {
|
||||
newClientListeners = append(newClientListeners, newListenerWithAddr(t, ln.Addr().String()))
|
||||
}
|
||||
m.ClientListeners = newClientListeners
|
||||
|
||||
if m.grpcListener != nil {
|
||||
if err := m.listenGRPC(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
return m.Launch()
|
||||
}
|
||||
|
||||
// Terminate stops the member and removes the data dir.
|
||||
func (m *member) Terminate(t *testing.T) {
|
||||
m.Close()
|
||||
if err := os.RemoveAll(m.ServerConfig.DataDir); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
|
||||
func mustNewHTTPClient(t *testing.T, eps []string, tls *transport.TLSInfo) client.Client {
|
||||
cfgtls := transport.TLSInfo{}
|
||||
if tls != nil {
|
||||
cfgtls = *tls
|
||||
}
|
||||
cfg := client.Config{Transport: mustNewTransport(t, cfgtls), Endpoints: eps}
|
||||
c, err := client.New(cfg)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func mustNewTransport(t *testing.T, tlsInfo transport.TLSInfo) *http.Transport {
|
||||
// tick in integration test is short, so 1s dial timeout could play well.
|
||||
tr, err := transport.NewTimeoutTransport(tlsInfo, time.Second, rafthttp.ConnReadTimeout, rafthttp.ConnWriteTimeout)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
return tr
|
||||
}
|
||||
|
||||
type SortableMemberSliceByPeerURLs []client.Member
|
||||
|
||||
func (p SortableMemberSliceByPeerURLs) Len() int { return len(p) }
|
||||
func (p SortableMemberSliceByPeerURLs) Less(i, j int) bool {
|
||||
return p[i].PeerURLs[0] < p[j].PeerURLs[0]
|
||||
}
|
||||
func (p SortableMemberSliceByPeerURLs) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
|
||||
|
||||
type ClusterV3 struct {
|
||||
*cluster
|
||||
clients []*clientv3.Client
|
||||
}
|
||||
|
||||
// NewClusterV3 returns a launched cluster with a grpc client connection
|
||||
// for each cluster member.
|
||||
func NewClusterV3(t *testing.T, cfg *ClusterConfig) *ClusterV3 {
|
||||
cfg.UseGRPC = true
|
||||
clus := &ClusterV3{cluster: NewClusterByConfig(t, cfg)}
|
||||
for _, m := range clus.Members {
|
||||
client, err := NewClientV3(m)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
clus.clients = append(clus.clients, client)
|
||||
}
|
||||
clus.Launch(t)
|
||||
return clus
|
||||
}
|
||||
|
||||
func (c *ClusterV3) Terminate(t *testing.T) {
|
||||
for _, client := range c.clients {
|
||||
if err := client.Close(); err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
c.cluster.Terminate(t)
|
||||
}
|
||||
|
||||
func (c *ClusterV3) RandClient() *clientv3.Client {
|
||||
return c.clients[rand.Intn(len(c.clients))]
|
||||
}
|
||||
|
||||
func (c *ClusterV3) Client(i int) *clientv3.Client {
|
||||
return c.clients[i]
|
||||
}
|
||||
|
||||
type grpcAPI struct {
|
||||
// Cluster is the cluster API for the client's connection.
|
||||
Cluster pb.ClusterClient
|
||||
// KV is the keyvalue API for the client's connection.
|
||||
KV pb.KVClient
|
||||
// Lease is the lease API for the client's connection.
|
||||
Lease pb.LeaseClient
|
||||
// Watch is the watch API for the client's connection.
|
||||
Watch pb.WatchClient
|
||||
// Maintenance is the maintenance API for the client's connection.
|
||||
Maintenance pb.MaintenanceClient
|
||||
}
|
||||
|
||||
func toGRPC(c *clientv3.Client) grpcAPI {
|
||||
return grpcAPI{
|
||||
pb.NewClusterClient(c.ActiveConnection()),
|
||||
pb.NewKVClient(c.ActiveConnection()),
|
||||
pb.NewLeaseClient(c.ActiveConnection()),
|
||||
pb.NewWatchClient(c.ActiveConnection()),
|
||||
pb.NewMaintenanceClient(c.ActiveConnection()),
|
||||
}
|
||||
}
|
||||
25
vendor/github.com/coreos/etcd/integration/doc.go
generated
vendored
Normal file
25
vendor/github.com/coreos/etcd/integration/doc.go
generated
vendored
Normal file
@@ -0,0 +1,25 @@
|
||||
// Copyright 2015 CoreOS, Inc.
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
/*
|
||||
Package integration implements tests built upon embedded etcd, and focus on
|
||||
etcd correctness.
|
||||
|
||||
Features/goals of the integration tests:
|
||||
1. test the whole code base except command-line parsing.
|
||||
2. check internal data, including raft, store and etc.
|
||||
3. based on goroutines, which is faster than process.
|
||||
4. mainly tests user behavior and user-facing API.
|
||||
*/
|
||||
package integration
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user