mirror of https://github.com/dexidp/dex.git
58 changed files with 28531 additions and 6 deletions
@ -0,0 +1,92 @@ |
|||||||
|
package etcd |
||||||
|
|
||||||
|
import ( |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/coreos/dex/storage" |
||||||
|
"github.com/coreos/etcd/clientv3" |
||||||
|
"github.com/coreos/etcd/clientv3/namespace" |
||||||
|
"github.com/coreos/etcd/pkg/transport" |
||||||
|
"github.com/sirupsen/logrus" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
defaultDialTimeout = 2 * time.Second |
||||||
|
) |
||||||
|
|
||||||
|
// SSL represents SSL options for etcd databases.
|
||||||
|
type SSL struct { |
||||||
|
ServerName string `json:"serverName" yaml:"serverName"` |
||||||
|
CAFile string `json:"caFile" yaml:"caFile"` |
||||||
|
KeyFile string `json:"keyFile" yaml:"keyFile"` |
||||||
|
CertFile string `json:"certFile" yaml:"certFile"` |
||||||
|
} |
||||||
|
|
||||||
|
// Etcd options for connecting to etcd databases.
|
||||||
|
// If you are using a shared etcd cluster for storage, it might be useful to
|
||||||
|
// configure an etcd namespace either via Namespace field or using `etcd grpc-proxy
|
||||||
|
// --namespace=<prefix>`
|
||||||
|
type Etcd struct { |
||||||
|
Endpoints []string `json:"endpoints" yaml:"endpoints"` |
||||||
|
Namespace string `json:"namespace" yaml:"namespace"` |
||||||
|
Username string `json:"username" yaml:"username"` |
||||||
|
Password string `json:"password" yaml:"password"` |
||||||
|
SSL SSL `json:"ssl" yaml:"ssl"` |
||||||
|
} |
||||||
|
|
||||||
|
// Open creates a new storage implementation backed by Etcd
|
||||||
|
func (p *Etcd) Open(logger logrus.FieldLogger) (storage.Storage, error) { |
||||||
|
return p.open(logger) |
||||||
|
} |
||||||
|
|
||||||
|
func (p *Etcd) open(logger logrus.FieldLogger) (*conn, error) { |
||||||
|
cfg := clientv3.Config{ |
||||||
|
Endpoints: p.Endpoints, |
||||||
|
DialTimeout: defaultDialTimeout * time.Second, |
||||||
|
Username: p.Username, |
||||||
|
Password: p.Password, |
||||||
|
} |
||||||
|
|
||||||
|
var cfgtls *transport.TLSInfo |
||||||
|
tlsinfo := transport.TLSInfo{} |
||||||
|
if p.SSL.CertFile != "" { |
||||||
|
tlsinfo.CertFile = p.SSL.CertFile |
||||||
|
cfgtls = &tlsinfo |
||||||
|
} |
||||||
|
|
||||||
|
if p.SSL.KeyFile != "" { |
||||||
|
tlsinfo.KeyFile = p.SSL.KeyFile |
||||||
|
cfgtls = &tlsinfo |
||||||
|
} |
||||||
|
|
||||||
|
if p.SSL.CAFile != "" { |
||||||
|
tlsinfo.CAFile = p.SSL.CAFile |
||||||
|
cfgtls = &tlsinfo |
||||||
|
} |
||||||
|
|
||||||
|
if p.SSL.ServerName != "" { |
||||||
|
tlsinfo.ServerName = p.SSL.ServerName |
||||||
|
cfgtls = &tlsinfo |
||||||
|
} |
||||||
|
|
||||||
|
if cfgtls != nil { |
||||||
|
clientTLS, err := cfgtls.ClientConfig() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
cfg.TLS = clientTLS |
||||||
|
} |
||||||
|
|
||||||
|
db, err := clientv3.New(cfg) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
if len(p.Namespace) > 0 { |
||||||
|
db.KV = namespace.NewKV(db.KV, p.Namespace) |
||||||
|
} |
||||||
|
c := &conn{ |
||||||
|
db: db, |
||||||
|
logger: logger, |
||||||
|
} |
||||||
|
return c, nil |
||||||
|
} |
||||||
@ -0,0 +1,532 @@ |
|||||||
|
package etcd |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"encoding/json" |
||||||
|
"fmt" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/coreos/dex/storage" |
||||||
|
"github.com/coreos/etcd/clientv3" |
||||||
|
"github.com/sirupsen/logrus" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
clientPrefix = "client/" |
||||||
|
authCodePrefix = "auth_code/" |
||||||
|
refreshTokenPrefix = "refresh_token/" |
||||||
|
authRequestPrefix = "auth_req/" |
||||||
|
passwordPrefix = "password/" |
||||||
|
offlineSessionPrefix = "offline_session/" |
||||||
|
connectorPrefix = "connector/" |
||||||
|
keysName = "openid-connect-keys" |
||||||
|
|
||||||
|
// defaultStorageTimeout will be applied to all storage's operations.
|
||||||
|
defaultStorageTimeout = 5 * time.Second |
||||||
|
) |
||||||
|
|
||||||
|
type conn struct { |
||||||
|
db *clientv3.Client |
||||||
|
logger logrus.FieldLogger |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) Close() error { |
||||||
|
return c.db.Close() |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) GarbageCollect(now time.Time) (result storage.GCResult, err error) { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
authRequests, err := c.listAuthRequests(ctx) |
||||||
|
if err != nil { |
||||||
|
return result, err |
||||||
|
} |
||||||
|
|
||||||
|
var delErr error |
||||||
|
for _, authRequest := range authRequests { |
||||||
|
if now.After(authRequest.Expiry) { |
||||||
|
if err := c.deleteKey(ctx, keyID(authRequestPrefix, authRequest.ID)); err != nil { |
||||||
|
c.logger.Errorf("failed to delete auth request: %v", err) |
||||||
|
delErr = fmt.Errorf("failed to delete auth request: %v", err) |
||||||
|
} |
||||||
|
result.AuthRequests++ |
||||||
|
} |
||||||
|
} |
||||||
|
if delErr != nil { |
||||||
|
return result, delErr |
||||||
|
} |
||||||
|
|
||||||
|
authCodes, err := c.listAuthCodes(ctx) |
||||||
|
if err != nil { |
||||||
|
return result, err |
||||||
|
} |
||||||
|
|
||||||
|
for _, authCode := range authCodes { |
||||||
|
if now.After(authCode.Expiry) { |
||||||
|
if err := c.deleteKey(ctx, keyID(authCodePrefix, authCode.ID)); err != nil { |
||||||
|
c.logger.Errorf("failed to delete auth code %v", err) |
||||||
|
delErr = fmt.Errorf("failed to delete auth code: %v", err) |
||||||
|
} |
||||||
|
result.AuthCodes++ |
||||||
|
} |
||||||
|
} |
||||||
|
return result, delErr |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) CreateAuthRequest(a storage.AuthRequest) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
return c.txnCreate(ctx, keyID(authRequestPrefix, a.ID), fromStorageAuthRequest(a)) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) GetAuthRequest(id string) (a storage.AuthRequest, err error) { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
var req AuthRequest |
||||||
|
if err = c.getKey(ctx, keyID(authRequestPrefix, id), &req); err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
return toStorageAuthRequest(req), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) UpdateAuthRequest(id string, updater func(a storage.AuthRequest) (storage.AuthRequest, error)) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
return c.txnUpdate(ctx, keyID(authRequestPrefix, id), func(currentValue []byte) ([]byte, error) { |
||||||
|
var current AuthRequest |
||||||
|
if len(currentValue) > 0 { |
||||||
|
if err := json.Unmarshal(currentValue, ¤t); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} |
||||||
|
updated, err := updater(toStorageAuthRequest(current)) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return json.Marshal(fromStorageAuthRequest(updated)) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) DeleteAuthRequest(id string) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
return c.deleteKey(ctx, keyID(authRequestPrefix, id)) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) CreateAuthCode(a storage.AuthCode) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
return c.txnCreate(ctx, keyID(authCodePrefix, a.ID), fromStorageAuthCode(a)) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) GetAuthCode(id string) (a storage.AuthCode, err error) { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
err = c.getKey(ctx, keyID(authCodePrefix, id), &a) |
||||||
|
return a, err |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) DeleteAuthCode(id string) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
return c.deleteKey(ctx, keyID(authCodePrefix, id)) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) CreateRefresh(r storage.RefreshToken) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
return c.txnCreate(ctx, keyID(refreshTokenPrefix, r.ID), fromStorageRefreshToken(r)) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) GetRefresh(id string) (r storage.RefreshToken, err error) { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
var token RefreshToken |
||||||
|
if err = c.getKey(ctx, keyID(refreshTokenPrefix, id), &token); err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
return toStorageRefreshToken(token), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) UpdateRefreshToken(id string, updater func(old storage.RefreshToken) (storage.RefreshToken, error)) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
return c.txnUpdate(ctx, keyID(refreshTokenPrefix, id), func(currentValue []byte) ([]byte, error) { |
||||||
|
var current RefreshToken |
||||||
|
if len(currentValue) > 0 { |
||||||
|
if err := json.Unmarshal([]byte(currentValue), ¤t); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} |
||||||
|
updated, err := updater(toStorageRefreshToken(current)) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return json.Marshal(fromStorageRefreshToken(updated)) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) DeleteRefresh(id string) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
return c.deleteKey(ctx, keyID(refreshTokenPrefix, id)) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) ListRefreshTokens() (tokens []storage.RefreshToken, err error) { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
res, err := c.db.Get(ctx, refreshTokenPrefix, clientv3.WithPrefix()) |
||||||
|
if err != nil { |
||||||
|
return tokens, err |
||||||
|
} |
||||||
|
for _, v := range res.Kvs { |
||||||
|
var token RefreshToken |
||||||
|
if err = json.Unmarshal(v.Value, &token); err != nil { |
||||||
|
return tokens, err |
||||||
|
} |
||||||
|
tokens = append(tokens, toStorageRefreshToken(token)) |
||||||
|
} |
||||||
|
return tokens, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) CreateClient(cli storage.Client) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
return c.txnCreate(ctx, keyID(clientPrefix, cli.ID), cli) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) GetClient(id string) (cli storage.Client, err error) { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
err = c.getKey(ctx, keyID(clientPrefix, id), &cli) |
||||||
|
return cli, err |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) UpdateClient(id string, updater func(old storage.Client) (storage.Client, error)) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
return c.txnUpdate(ctx, keyID(clientPrefix, id), func(currentValue []byte) ([]byte, error) { |
||||||
|
var current storage.Client |
||||||
|
if len(currentValue) > 0 { |
||||||
|
if err := json.Unmarshal(currentValue, ¤t); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} |
||||||
|
updated, err := updater(current) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return json.Marshal(updated) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) DeleteClient(id string) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
return c.deleteKey(ctx, keyID(clientPrefix, id)) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) ListClients() (clients []storage.Client, err error) { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
res, err := c.db.Get(ctx, clientPrefix, clientv3.WithPrefix()) |
||||||
|
if err != nil { |
||||||
|
return clients, err |
||||||
|
} |
||||||
|
for _, v := range res.Kvs { |
||||||
|
var cli storage.Client |
||||||
|
if err = json.Unmarshal(v.Value, &cli); err != nil { |
||||||
|
return clients, err |
||||||
|
} |
||||||
|
clients = append(clients, cli) |
||||||
|
} |
||||||
|
return clients, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) CreatePassword(p storage.Password) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
return c.txnCreate(ctx, passwordPrefix+strings.ToLower(p.Email), p) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) GetPassword(email string) (p storage.Password, err error) { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
err = c.getKey(ctx, keyEmail(passwordPrefix, email), &p) |
||||||
|
return p, err |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) UpdatePassword(email string, updater func(p storage.Password) (storage.Password, error)) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
return c.txnUpdate(ctx, keyEmail(passwordPrefix, email), func(currentValue []byte) ([]byte, error) { |
||||||
|
var current storage.Password |
||||||
|
if len(currentValue) > 0 { |
||||||
|
if err := json.Unmarshal(currentValue, ¤t); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} |
||||||
|
updated, err := updater(current) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return json.Marshal(updated) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) DeletePassword(email string) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
return c.deleteKey(ctx, keyEmail(passwordPrefix, email)) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) ListPasswords() (passwords []storage.Password, err error) { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
res, err := c.db.Get(ctx, passwordPrefix, clientv3.WithPrefix()) |
||||||
|
if err != nil { |
||||||
|
return passwords, err |
||||||
|
} |
||||||
|
for _, v := range res.Kvs { |
||||||
|
var p storage.Password |
||||||
|
if err = json.Unmarshal(v.Value, &p); err != nil { |
||||||
|
return passwords, err |
||||||
|
} |
||||||
|
passwords = append(passwords, p) |
||||||
|
} |
||||||
|
return passwords, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) CreateOfflineSessions(s storage.OfflineSessions) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
return c.txnCreate(ctx, keySession(offlineSessionPrefix, s.UserID, s.ConnID), fromStorageOfflineSessions(s)) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) UpdateOfflineSessions(userID string, connID string, updater func(s storage.OfflineSessions) (storage.OfflineSessions, error)) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
return c.txnUpdate(ctx, keySession(offlineSessionPrefix, userID, connID), func(currentValue []byte) ([]byte, error) { |
||||||
|
var current OfflineSessions |
||||||
|
if len(currentValue) > 0 { |
||||||
|
if err := json.Unmarshal(currentValue, ¤t); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} |
||||||
|
updated, err := updater(toStorageOfflineSessions(current)) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return json.Marshal(fromStorageOfflineSessions(updated)) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) GetOfflineSessions(userID string, connID string) (s storage.OfflineSessions, err error) { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
var os OfflineSessions |
||||||
|
if err = c.getKey(ctx, keySession(offlineSessionPrefix, userID, connID), &os); err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
return toStorageOfflineSessions(os), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) DeleteOfflineSessions(userID string, connID string) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
return c.deleteKey(ctx, keySession(offlineSessionPrefix, userID, connID)) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) CreateConnector(connector storage.Connector) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
return c.txnCreate(ctx, keyID(connectorPrefix, connector.ID), connector) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) GetConnector(id string) (conn storage.Connector, err error) { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
err = c.getKey(ctx, keyID(connectorPrefix, id), &conn) |
||||||
|
return conn, err |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) UpdateConnector(id string, updater func(s storage.Connector) (storage.Connector, error)) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
return c.txnUpdate(ctx, keyID(connectorPrefix, id), func(currentValue []byte) ([]byte, error) { |
||||||
|
var current storage.Connector |
||||||
|
if len(currentValue) > 0 { |
||||||
|
if err := json.Unmarshal(currentValue, ¤t); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} |
||||||
|
updated, err := updater(current) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return json.Marshal(updated) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) DeleteConnector(id string) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
return c.deleteKey(ctx, keyID(connectorPrefix, id)) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) ListConnectors() (connectors []storage.Connector, err error) { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
res, err := c.db.Get(ctx, connectorPrefix, clientv3.WithPrefix()) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
for _, v := range res.Kvs { |
||||||
|
var c storage.Connector |
||||||
|
if err = json.Unmarshal(v.Value, &c); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
connectors = append(connectors, c) |
||||||
|
} |
||||||
|
return connectors, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) GetKeys() (keys storage.Keys, err error) { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
res, err := c.db.Get(ctx, keysName) |
||||||
|
if err != nil { |
||||||
|
return keys, err |
||||||
|
} |
||||||
|
if res.Count > 0 && len(res.Kvs) > 0 { |
||||||
|
err = json.Unmarshal(res.Kvs[0].Value, &keys) |
||||||
|
} |
||||||
|
return keys, err |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) UpdateKeys(updater func(old storage.Keys) (storage.Keys, error)) error { |
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), defaultStorageTimeout) |
||||||
|
defer cancel() |
||||||
|
return c.txnUpdate(ctx, keysName, func(currentValue []byte) ([]byte, error) { |
||||||
|
var current storage.Keys |
||||||
|
if len(currentValue) > 0 { |
||||||
|
if err := json.Unmarshal(currentValue, ¤t); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} |
||||||
|
updated, err := updater(current) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return json.Marshal(updated) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) deleteKey(ctx context.Context, key string) error { |
||||||
|
res, err := c.db.Delete(ctx, key) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if res.Deleted == 0 { |
||||||
|
return storage.ErrNotFound |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) getKey(ctx context.Context, key string, value interface{}) error { |
||||||
|
r, err := c.db.Get(ctx, key) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if r.Count == 0 { |
||||||
|
return storage.ErrNotFound |
||||||
|
} |
||||||
|
return json.Unmarshal(r.Kvs[0].Value, value) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) listAuthRequests(ctx context.Context) (reqs []AuthRequest, err error) { |
||||||
|
res, err := c.db.Get(ctx, authRequestPrefix, clientv3.WithPrefix()) |
||||||
|
if err != nil { |
||||||
|
return reqs, err |
||||||
|
} |
||||||
|
for _, v := range res.Kvs { |
||||||
|
var r AuthRequest |
||||||
|
if err = json.Unmarshal(v.Value, &r); err != nil { |
||||||
|
return reqs, err |
||||||
|
} |
||||||
|
reqs = append(reqs, r) |
||||||
|
} |
||||||
|
return reqs, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) listAuthCodes(ctx context.Context) (codes []AuthCode, err error) { |
||||||
|
res, err := c.db.Get(ctx, authCodePrefix, clientv3.WithPrefix()) |
||||||
|
if err != nil { |
||||||
|
return codes, err |
||||||
|
} |
||||||
|
for _, v := range res.Kvs { |
||||||
|
var c AuthCode |
||||||
|
if err = json.Unmarshal(v.Value, &c); err != nil { |
||||||
|
return codes, err |
||||||
|
} |
||||||
|
codes = append(codes, c) |
||||||
|
} |
||||||
|
return codes, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) txnCreate(ctx context.Context, key string, value interface{}) error { |
||||||
|
b, err := json.Marshal(value) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
txn := c.db.Txn(ctx) |
||||||
|
res, err := txn. |
||||||
|
If(clientv3.Compare(clientv3.CreateRevision(key), "=", 0)). |
||||||
|
Then(clientv3.OpPut(key, string(b))). |
||||||
|
Commit() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if !res.Succeeded { |
||||||
|
return storage.ErrAlreadyExists |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *conn) txnUpdate(ctx context.Context, key string, update func(current []byte) ([]byte, error)) error { |
||||||
|
getResp, err := c.db.Get(ctx, key) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
var currentValue []byte |
||||||
|
var modRev int64 |
||||||
|
if len(getResp.Kvs) > 0 { |
||||||
|
currentValue = getResp.Kvs[0].Value |
||||||
|
modRev = getResp.Kvs[0].ModRevision |
||||||
|
} |
||||||
|
|
||||||
|
updatedValue, err := update(currentValue) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
txn := c.db.Txn(ctx) |
||||||
|
updateResp, err := txn. |
||||||
|
If(clientv3.Compare(clientv3.ModRevision(key), "=", modRev)). |
||||||
|
Then(clientv3.OpPut(key, string(updatedValue))). |
||||||
|
Commit() |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if !updateResp.Succeeded { |
||||||
|
return fmt.Errorf("failed to update key=%q: concurrent conflicting update happened", key) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func keyID(prefix, id string) string { return prefix + id } |
||||||
|
func keyEmail(prefix, email string) string { return prefix + strings.ToLower(email) } |
||||||
|
func keySession(prefix, userID, connID string) string { |
||||||
|
return prefix + strings.ToLower(userID+"|"+connID) |
||||||
|
} |
||||||
@ -0,0 +1,94 @@ |
|||||||
|
package etcd |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"fmt" |
||||||
|
"os" |
||||||
|
"runtime" |
||||||
|
"strings" |
||||||
|
"testing" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/coreos/dex/storage" |
||||||
|
"github.com/coreos/dex/storage/conformance" |
||||||
|
"github.com/coreos/etcd/clientv3" |
||||||
|
"github.com/sirupsen/logrus" |
||||||
|
) |
||||||
|
|
||||||
|
func withTimeout(t time.Duration, f func()) { |
||||||
|
c := make(chan struct{}) |
||||||
|
defer close(c) |
||||||
|
|
||||||
|
go func() { |
||||||
|
select { |
||||||
|
case <-c: |
||||||
|
case <-time.After(t): |
||||||
|
// Dump a stack trace of the program. Useful for debugging deadlocks.
|
||||||
|
buf := make([]byte, 2<<20) |
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", buf[:runtime.Stack(buf, true)]) |
||||||
|
panic("test took too long") |
||||||
|
} |
||||||
|
}() |
||||||
|
|
||||||
|
f() |
||||||
|
} |
||||||
|
|
||||||
|
func cleanDB(c *conn) error { |
||||||
|
ctx := context.TODO() |
||||||
|
for _, prefix := range []string{ |
||||||
|
clientPrefix, |
||||||
|
authCodePrefix, |
||||||
|
refreshTokenPrefix, |
||||||
|
authRequestPrefix, |
||||||
|
passwordPrefix, |
||||||
|
offlineSessionPrefix, |
||||||
|
connectorPrefix, |
||||||
|
} { |
||||||
|
_, err := c.db.Delete(ctx, prefix, clientv3.WithPrefix()) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
var logger = &logrus.Logger{ |
||||||
|
Out: os.Stderr, |
||||||
|
Formatter: &logrus.TextFormatter{DisableColors: true}, |
||||||
|
Level: logrus.DebugLevel, |
||||||
|
} |
||||||
|
|
||||||
|
func TestEtcd(t *testing.T) { |
||||||
|
testEtcdEnv := "DEX_ETCD_ENDPOINTS" |
||||||
|
endpointsStr := os.Getenv(testEtcdEnv) |
||||||
|
if endpointsStr == "" { |
||||||
|
t.Skipf("test environment variable %q not set, skipping", testEtcdEnv) |
||||||
|
return |
||||||
|
} |
||||||
|
endpoints := strings.Split(endpointsStr, ",") |
||||||
|
|
||||||
|
newStorage := func() storage.Storage { |
||||||
|
s := &Etcd{ |
||||||
|
Endpoints: endpoints, |
||||||
|
} |
||||||
|
conn, err := s.open(logger) |
||||||
|
if err != nil { |
||||||
|
fmt.Fprintln(os.Stdout, err) |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
|
||||||
|
if err := cleanDB(conn); err != nil { |
||||||
|
fmt.Fprintln(os.Stdout, err) |
||||||
|
t.Fatal(err) |
||||||
|
} |
||||||
|
return conn |
||||||
|
} |
||||||
|
|
||||||
|
withTimeout(time.Second*10, func() { |
||||||
|
conformance.RunTests(t, newStorage) |
||||||
|
}) |
||||||
|
|
||||||
|
withTimeout(time.Minute*1, func() { |
||||||
|
conformance.RunTransactionTests(t, newStorage) |
||||||
|
}) |
||||||
|
} |
||||||
@ -0,0 +1,109 @@ |
|||||||
|
#!/bin/bash |
||||||
|
|
||||||
|
if [ "$EUID" -ne 0 ] |
||||||
|
then echo "Please run as root" |
||||||
|
exit |
||||||
|
fi |
||||||
|
|
||||||
|
function usage { |
||||||
|
cat << EOF >> /dev/stderr |
||||||
|
Usage: sudo ./standup.sh [create|destroy] [etcd] |
||||||
|
|
||||||
|
This is a script for standing up test databases. It uses systemd to daemonize |
||||||
|
rkt containers running on a local loopback IP. |
||||||
|
|
||||||
|
The general workflow is to create a daemonized container, use the output to set |
||||||
|
the test environment variables, run the tests, then destroy the container. |
||||||
|
|
||||||
|
sudo ./standup.sh create etcd |
||||||
|
# Copy environment variables and run tests. |
||||||
|
go test -v -i # always install test dependencies |
||||||
|
go test -v |
||||||
|
sudo ./standup.sh destroy etcd |
||||||
|
|
||||||
|
EOF |
||||||
|
exit 2 |
||||||
|
} |
||||||
|
|
||||||
|
function main { |
||||||
|
if [ "$#" -ne 2 ]; then |
||||||
|
usage |
||||||
|
exit 2 |
||||||
|
fi |
||||||
|
|
||||||
|
case "$1" in |
||||||
|
"create") |
||||||
|
case "$2" in |
||||||
|
"etcd") |
||||||
|
create_etcd;; |
||||||
|
*) |
||||||
|
usage |
||||||
|
exit 2 |
||||||
|
;; |
||||||
|
esac |
||||||
|
;; |
||||||
|
"destroy") |
||||||
|
case "$2" in |
||||||
|
"etcd") |
||||||
|
destroy_etcd;; |
||||||
|
*) |
||||||
|
usage |
||||||
|
exit 2 |
||||||
|
;; |
||||||
|
esac |
||||||
|
;; |
||||||
|
*) |
||||||
|
usage |
||||||
|
exit 2 |
||||||
|
;; |
||||||
|
esac |
||||||
|
} |
||||||
|
|
||||||
|
function wait_for_file { |
||||||
|
while [ ! -f $1 ]; do |
||||||
|
sleep 1 |
||||||
|
done |
||||||
|
} |
||||||
|
|
||||||
|
function wait_for_container { |
||||||
|
while [ -z "$( rkt list --full | grep $1 | grep running )" ]; do |
||||||
|
sleep 1 |
||||||
|
done |
||||||
|
} |
||||||
|
|
||||||
|
function create_etcd { |
||||||
|
UUID_FILE=/tmp/dex-etcd-uuid |
||||||
|
if [ -f $UUID_FILE ]; then |
||||||
|
echo "etcd database already exists, try ./standup.sh destroy etcd" |
||||||
|
exit 2 |
||||||
|
fi |
||||||
|
|
||||||
|
echo "Starting etcd . To view progress run:" |
||||||
|
echo "" |
||||||
|
echo " journalctl -fu dex-etcd" |
||||||
|
echo "" |
||||||
|
UNIFIED_CGROUP_HIERARCHY=no \ |
||||||
|
systemd-run --unit=dex-etcd \ |
||||||
|
rkt run --uuid-file-save=$UUID_FILE --insecure-options=image \ |
||||||
|
--net=host \ |
||||||
|
docker://quay.io/coreos/etcd:v3.2.9 |
||||||
|
|
||||||
|
wait_for_file $UUID_FILE |
||||||
|
|
||||||
|
UUID=$( cat $UUID_FILE ) |
||||||
|
wait_for_container $UUID |
||||||
|
echo "To run tests export the following environment variables:" |
||||||
|
echo "" |
||||||
|
echo " export DEX_ETCD_ENDPOINTS=http://localhost:2379" |
||||||
|
echo "" |
||||||
|
} |
||||||
|
|
||||||
|
function destroy_etcd { |
||||||
|
UUID_FILE=/tmp/dex-etcd-uuid |
||||||
|
systemctl stop dex-etcd |
||||||
|
rkt rm --uuid-file=$UUID_FILE |
||||||
|
rm $UUID_FILE |
||||||
|
} |
||||||
|
|
||||||
|
|
||||||
|
main $@ |
||||||
@ -0,0 +1,229 @@ |
|||||||
|
package etcd |
||||||
|
|
||||||
|
import ( |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/coreos/dex/storage" |
||||||
|
jose "gopkg.in/square/go-jose.v2" |
||||||
|
) |
||||||
|
|
||||||
|
// AuthCode is a mirrored struct from storage with JSON struct tags
|
||||||
|
type AuthCode struct { |
||||||
|
ID string `json:"ID"` |
||||||
|
ClientID string `json:"clientID"` |
||||||
|
RedirectURI string `json:"redirectURI"` |
||||||
|
Nonce string `json:"nonce,omitempty"` |
||||||
|
Scopes []string `json:"scopes,omitempty"` |
||||||
|
|
||||||
|
ConnectorID string `json:"connectorID,omitempty"` |
||||||
|
ConnectorData []byte `json:"connectorData,omitempty"` |
||||||
|
Claims Claims `json:"claims,omitempty"` |
||||||
|
|
||||||
|
Expiry time.Time `json:"expiry"` |
||||||
|
} |
||||||
|
|
||||||
|
func fromStorageAuthCode(a storage.AuthCode) AuthCode { |
||||||
|
return AuthCode{ |
||||||
|
ID: a.ID, |
||||||
|
ClientID: a.ClientID, |
||||||
|
RedirectURI: a.RedirectURI, |
||||||
|
ConnectorID: a.ConnectorID, |
||||||
|
ConnectorData: a.ConnectorData, |
||||||
|
Nonce: a.Nonce, |
||||||
|
Scopes: a.Scopes, |
||||||
|
Claims: fromStorageClaims(a.Claims), |
||||||
|
Expiry: a.Expiry, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// AuthRequest is a mirrored struct from storage with JSON struct tags
|
||||||
|
type AuthRequest struct { |
||||||
|
ID string `json:"id"` |
||||||
|
ClientID string `json:"client_id"` |
||||||
|
|
||||||
|
ResponseTypes []string `json:"response_types"` |
||||||
|
Scopes []string `json:"scopes"` |
||||||
|
RedirectURI string `json:"redirect_uri"` |
||||||
|
Nonce string `json:"nonce"` |
||||||
|
State string `json:"state"` |
||||||
|
|
||||||
|
ForceApprovalPrompt bool `json:"force_approval_prompt"` |
||||||
|
|
||||||
|
Expiry time.Time `json:"expiry"` |
||||||
|
|
||||||
|
LoggedIn bool `json:"logged_in"` |
||||||
|
|
||||||
|
Claims Claims `json:"claims"` |
||||||
|
|
||||||
|
ConnectorID string `json:"connector_id"` |
||||||
|
ConnectorData []byte `json:"connector_data"` |
||||||
|
} |
||||||
|
|
||||||
|
func fromStorageAuthRequest(a storage.AuthRequest) AuthRequest { |
||||||
|
return AuthRequest{ |
||||||
|
ID: a.ID, |
||||||
|
ClientID: a.ClientID, |
||||||
|
ResponseTypes: a.ResponseTypes, |
||||||
|
Scopes: a.Scopes, |
||||||
|
RedirectURI: a.RedirectURI, |
||||||
|
Nonce: a.Nonce, |
||||||
|
State: a.State, |
||||||
|
ForceApprovalPrompt: a.ForceApprovalPrompt, |
||||||
|
Expiry: a.Expiry, |
||||||
|
LoggedIn: a.LoggedIn, |
||||||
|
Claims: fromStorageClaims(a.Claims), |
||||||
|
ConnectorID: a.ConnectorID, |
||||||
|
ConnectorData: a.ConnectorData, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func toStorageAuthRequest(a AuthRequest) storage.AuthRequest { |
||||||
|
return storage.AuthRequest{ |
||||||
|
ID: a.ID, |
||||||
|
ClientID: a.ClientID, |
||||||
|
ResponseTypes: a.ResponseTypes, |
||||||
|
Scopes: a.Scopes, |
||||||
|
RedirectURI: a.RedirectURI, |
||||||
|
Nonce: a.Nonce, |
||||||
|
State: a.State, |
||||||
|
ForceApprovalPrompt: a.ForceApprovalPrompt, |
||||||
|
LoggedIn: a.LoggedIn, |
||||||
|
ConnectorID: a.ConnectorID, |
||||||
|
ConnectorData: a.ConnectorData, |
||||||
|
Expiry: a.Expiry, |
||||||
|
Claims: toStorageClaims(a.Claims), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// RefreshToken is a mirrored struct from storage with JSON struct tags
|
||||||
|
type RefreshToken struct { |
||||||
|
ID string `json:"id"` |
||||||
|
|
||||||
|
Token string `json:"token"` |
||||||
|
|
||||||
|
CreatedAt time.Time `json:"created_at"` |
||||||
|
LastUsed time.Time `json:"last_used"` |
||||||
|
|
||||||
|
ClientID string `json:"client_id"` |
||||||
|
|
||||||
|
ConnectorID string `json:"connector_id"` |
||||||
|
ConnectorData []byte `json:"connector_data"` |
||||||
|
Claims Claims `json:"claims"` |
||||||
|
|
||||||
|
Scopes []string `json:"scopes"` |
||||||
|
|
||||||
|
Nonce string `json:"nonce"` |
||||||
|
} |
||||||
|
|
||||||
|
func toStorageRefreshToken(r RefreshToken) storage.RefreshToken { |
||||||
|
return storage.RefreshToken{ |
||||||
|
ID: r.ID, |
||||||
|
Token: r.Token, |
||||||
|
CreatedAt: r.CreatedAt, |
||||||
|
LastUsed: r.LastUsed, |
||||||
|
ClientID: r.ClientID, |
||||||
|
ConnectorID: r.ConnectorID, |
||||||
|
ConnectorData: r.ConnectorData, |
||||||
|
Scopes: r.Scopes, |
||||||
|
Nonce: r.Nonce, |
||||||
|
Claims: toStorageClaims(r.Claims), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func fromStorageRefreshToken(r storage.RefreshToken) RefreshToken { |
||||||
|
return RefreshToken{ |
||||||
|
ID: r.ID, |
||||||
|
Token: r.Token, |
||||||
|
CreatedAt: r.CreatedAt, |
||||||
|
LastUsed: r.LastUsed, |
||||||
|
ClientID: r.ClientID, |
||||||
|
ConnectorID: r.ConnectorID, |
||||||
|
ConnectorData: r.ConnectorData, |
||||||
|
Scopes: r.Scopes, |
||||||
|
Nonce: r.Nonce, |
||||||
|
Claims: fromStorageClaims(r.Claims), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Claims is a mirrored struct from storage with JSON struct tags.
|
||||||
|
type Claims struct { |
||||||
|
UserID string `json:"userID"` |
||||||
|
Username string `json:"username"` |
||||||
|
Email string `json:"email"` |
||||||
|
EmailVerified bool `json:"emailVerified"` |
||||||
|
Groups []string `json:"groups,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
func fromStorageClaims(i storage.Claims) Claims { |
||||||
|
return Claims{ |
||||||
|
UserID: i.UserID, |
||||||
|
Username: i.Username, |
||||||
|
Email: i.Email, |
||||||
|
EmailVerified: i.EmailVerified, |
||||||
|
Groups: i.Groups, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func toStorageClaims(i Claims) storage.Claims { |
||||||
|
return storage.Claims{ |
||||||
|
UserID: i.UserID, |
||||||
|
Username: i.Username, |
||||||
|
Email: i.Email, |
||||||
|
EmailVerified: i.EmailVerified, |
||||||
|
Groups: i.Groups, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// Keys is a mirrored struct from storage with JSON struct tags
|
||||||
|
type Keys struct { |
||||||
|
SigningKey *jose.JSONWebKey `json:"signing_key,omitempty"` |
||||||
|
SigningKeyPub *jose.JSONWebKey `json:"signing_key_pub,omitempty"` |
||||||
|
VerificationKeys []storage.VerificationKey `json:"verification_keys"` |
||||||
|
NextRotation time.Time `json:"next_rotation"` |
||||||
|
} |
||||||
|
|
||||||
|
func fromStorageKeys(keys storage.Keys) Keys { |
||||||
|
return Keys{ |
||||||
|
SigningKey: keys.SigningKey, |
||||||
|
SigningKeyPub: keys.SigningKeyPub, |
||||||
|
VerificationKeys: keys.VerificationKeys, |
||||||
|
NextRotation: keys.NextRotation, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func toStorageKeys(keys Keys) storage.Keys { |
||||||
|
return storage.Keys{ |
||||||
|
SigningKey: keys.SigningKey, |
||||||
|
SigningKeyPub: keys.SigningKeyPub, |
||||||
|
VerificationKeys: keys.VerificationKeys, |
||||||
|
NextRotation: keys.NextRotation, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// OfflineSessions is a mirrored struct from storage with JSON struct tags
|
||||||
|
type OfflineSessions struct { |
||||||
|
UserID string `json:"user_id,omitempty"` |
||||||
|
ConnID string `json:"conn_id,omitempty"` |
||||||
|
Refresh map[string]*storage.RefreshTokenRef `json:"refresh,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
func fromStorageOfflineSessions(o storage.OfflineSessions) OfflineSessions { |
||||||
|
return OfflineSessions{ |
||||||
|
UserID: o.UserID, |
||||||
|
ConnID: o.ConnID, |
||||||
|
Refresh: o.Refresh, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func toStorageOfflineSessions(o OfflineSessions) storage.OfflineSessions { |
||||||
|
s := storage.OfflineSessions{ |
||||||
|
UserID: o.UserID, |
||||||
|
ConnID: o.ConnID, |
||||||
|
Refresh: o.Refresh, |
||||||
|
} |
||||||
|
if s.Refresh == nil { |
||||||
|
// Server code assumes this will be non-nil.
|
||||||
|
s.Refresh = make(map[string]*storage.RefreshTokenRef) |
||||||
|
} |
||||||
|
return s |
||||||
|
} |
||||||
@ -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. |
||||||
@ -0,0 +1,5 @@ |
|||||||
|
CoreOS Project |
||||||
|
Copyright 2014 CoreOS, Inc |
||||||
|
|
||||||
|
This product includes software developed at CoreOS, Inc. |
||||||
|
(http://www.coreos.com/). |
||||||
@ -0,0 +1,824 @@ |
|||||||
|
// 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 |
||||||
|
Permission |
||||||
|
Role |
||||||
|
*/ |
||||||
|
package authpb |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
|
||||||
|
proto "github.com/golang/protobuf/proto" |
||||||
|
|
||||||
|
math "math" |
||||||
|
|
||||||
|
io "io" |
||||||
|
) |
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var _ = proto.Marshal |
||||||
|
var _ = fmt.Errorf |
||||||
|
var _ = math.Inf |
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the proto package it is being compiled against.
|
||||||
|
// A compilation error at this line likely means your copy of the
|
||||||
|
// proto package needs to be updated.
|
||||||
|
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||||
|
|
||||||
|
type Permission_Type int32 |
||||||
|
|
||||||
|
const ( |
||||||
|
READ Permission_Type = 0 |
||||||
|
WRITE Permission_Type = 1 |
||||||
|
READWRITE Permission_Type = 2 |
||||||
|
) |
||||||
|
|
||||||
|
var Permission_Type_name = map[int32]string{ |
||||||
|
0: "READ", |
||||||
|
1: "WRITE", |
||||||
|
2: "READWRITE", |
||||||
|
} |
||||||
|
var Permission_Type_value = map[string]int32{ |
||||||
|
"READ": 0, |
||||||
|
"WRITE": 1, |
||||||
|
"READWRITE": 2, |
||||||
|
} |
||||||
|
|
||||||
|
func (x Permission_Type) String() string { |
||||||
|
return proto.EnumName(Permission_Type_name, int32(x)) |
||||||
|
} |
||||||
|
func (Permission_Type) EnumDescriptor() ([]byte, []int) { return fileDescriptorAuth, []int{1, 0} } |
||||||
|
|
||||||
|
// 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"` |
||||||
|
Roles []string `protobuf:"bytes,3,rep,name=roles" json:"roles,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
func (m *User) Reset() { *m = User{} } |
||||||
|
func (m *User) String() string { return proto.CompactTextString(m) } |
||||||
|
func (*User) ProtoMessage() {} |
||||||
|
func (*User) Descriptor() ([]byte, []int) { return fileDescriptorAuth, []int{0} } |
||||||
|
|
||||||
|
// Permission is a single entity
|
||||||
|
type Permission struct { |
||||||
|
PermType Permission_Type `protobuf:"varint,1,opt,name=permType,proto3,enum=authpb.Permission_Type" json:"permType,omitempty"` |
||||||
|
Key []byte `protobuf:"bytes,2,opt,name=key,proto3" json:"key,omitempty"` |
||||||
|
RangeEnd []byte `protobuf:"bytes,3,opt,name=range_end,json=rangeEnd,proto3" json:"range_end,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
func (m *Permission) Reset() { *m = Permission{} } |
||||||
|
func (m *Permission) String() string { return proto.CompactTextString(m) } |
||||||
|
func (*Permission) ProtoMessage() {} |
||||||
|
func (*Permission) Descriptor() ([]byte, []int) { return fileDescriptorAuth, []int{1} } |
||||||
|
|
||||||
|
// Role is a single entry in the bucket authRoles
|
||||||
|
type Role struct { |
||||||
|
Name []byte `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"` |
||||||
|
KeyPermission []*Permission `protobuf:"bytes,2,rep,name=keyPermission" json:"keyPermission,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
func (m *Role) Reset() { *m = Role{} } |
||||||
|
func (m *Role) String() string { return proto.CompactTextString(m) } |
||||||
|
func (*Role) ProtoMessage() {} |
||||||
|
func (*Role) Descriptor() ([]byte, []int) { return fileDescriptorAuth, []int{2} } |
||||||
|
|
||||||
|
func init() { |
||||||
|
proto.RegisterType((*User)(nil), "authpb.User") |
||||||
|
proto.RegisterType((*Permission)(nil), "authpb.Permission") |
||||||
|
proto.RegisterType((*Role)(nil), "authpb.Role") |
||||||
|
proto.RegisterEnum("authpb.Permission_Type", Permission_Type_name, Permission_Type_value) |
||||||
|
} |
||||||
|
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 len(m.Name) > 0 { |
||||||
|
dAtA[i] = 0xa |
||||||
|
i++ |
||||||
|
i = encodeVarintAuth(dAtA, i, uint64(len(m.Name))) |
||||||
|
i += copy(dAtA[i:], m.Name) |
||||||
|
} |
||||||
|
if len(m.Password) > 0 { |
||||||
|
dAtA[i] = 0x12 |
||||||
|
i++ |
||||||
|
i = encodeVarintAuth(dAtA, i, uint64(len(m.Password))) |
||||||
|
i += copy(dAtA[i:], m.Password) |
||||||
|
} |
||||||
|
if len(m.Roles) > 0 { |
||||||
|
for _, s := range m.Roles { |
||||||
|
dAtA[i] = 0x1a |
||||||
|
i++ |
||||||
|
l = len(s) |
||||||
|
for l >= 1<<7 { |
||||||
|
dAtA[i] = uint8(uint64(l)&0x7f | 0x80) |
||||||
|
l >>= 7 |
||||||
|
i++ |
||||||
|
} |
||||||
|
dAtA[i] = uint8(l) |
||||||
|
i++ |
||||||
|
i += copy(dAtA[i:], s) |
||||||
|
} |
||||||
|
} |
||||||
|
return i, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (m *Permission) 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 *Permission) MarshalTo(dAtA []byte) (int, error) { |
||||||
|
var i int |
||||||
|
_ = i |
||||||
|
var l int |
||||||
|
_ = l |
||||||
|
if m.PermType != 0 { |
||||||
|
dAtA[i] = 0x8 |
||||||
|
i++ |
||||||
|
i = encodeVarintAuth(dAtA, i, uint64(m.PermType)) |
||||||
|
} |
||||||
|
if len(m.Key) > 0 { |
||||||
|
dAtA[i] = 0x12 |
||||||
|
i++ |
||||||
|
i = encodeVarintAuth(dAtA, i, uint64(len(m.Key))) |
||||||
|
i += copy(dAtA[i:], m.Key) |
||||||
|
} |
||||||
|
if len(m.RangeEnd) > 0 { |
||||||
|
dAtA[i] = 0x1a |
||||||
|
i++ |
||||||
|
i = encodeVarintAuth(dAtA, i, uint64(len(m.RangeEnd))) |
||||||
|
i += copy(dAtA[i:], m.RangeEnd) |
||||||
|
} |
||||||
|
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 len(m.Name) > 0 { |
||||||
|
dAtA[i] = 0xa |
||||||
|
i++ |
||||||
|
i = encodeVarintAuth(dAtA, i, uint64(len(m.Name))) |
||||||
|
i += copy(dAtA[i:], m.Name) |
||||||
|
} |
||||||
|
if len(m.KeyPermission) > 0 { |
||||||
|
for _, msg := range m.KeyPermission { |
||||||
|
dAtA[i] = 0x12 |
||||||
|
i++ |
||||||
|
i = encodeVarintAuth(dAtA, i, uint64(msg.Size())) |
||||||
|
n, err := msg.MarshalTo(dAtA[i:]) |
||||||
|
if err != nil { |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
i += n |
||||||
|
} |
||||||
|
} |
||||||
|
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 |
||||||
|
l = len(m.Name) |
||||||
|
if l > 0 { |
||||||
|
n += 1 + l + sovAuth(uint64(l)) |
||||||
|
} |
||||||
|
l = len(m.Password) |
||||||
|
if l > 0 { |
||||||
|
n += 1 + l + sovAuth(uint64(l)) |
||||||
|
} |
||||||
|
if len(m.Roles) > 0 { |
||||||
|
for _, s := range m.Roles { |
||||||
|
l = len(s) |
||||||
|
n += 1 + l + sovAuth(uint64(l)) |
||||||
|
} |
||||||
|
} |
||||||
|
return n |
||||||
|
} |
||||||
|
|
||||||
|
func (m *Permission) Size() (n int) { |
||||||
|
var l int |
||||||
|
_ = l |
||||||
|
if m.PermType != 0 { |
||||||
|
n += 1 + sovAuth(uint64(m.PermType)) |
||||||
|
} |
||||||
|
l = len(m.Key) |
||||||
|
if l > 0 { |
||||||
|
n += 1 + l + sovAuth(uint64(l)) |
||||||
|
} |
||||||
|
l = len(m.RangeEnd) |
||||||
|
if l > 0 { |
||||||
|
n += 1 + l + sovAuth(uint64(l)) |
||||||
|
} |
||||||
|
return n |
||||||
|
} |
||||||
|
|
||||||
|
func (m *Role) Size() (n int) { |
||||||
|
var l int |
||||||
|
_ = l |
||||||
|
l = len(m.Name) |
||||||
|
if l > 0 { |
||||||
|
n += 1 + l + sovAuth(uint64(l)) |
||||||
|
} |
||||||
|
if len(m.KeyPermission) > 0 { |
||||||
|
for _, e := range m.KeyPermission { |
||||||
|
l = e.Size() |
||||||
|
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 != 2 { |
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field Roles", wireType) |
||||||
|
} |
||||||
|
var stringLen uint64 |
||||||
|
for shift := uint(0); ; shift += 7 { |
||||||
|
if shift >= 64 { |
||||||
|
return ErrIntOverflowAuth |
||||||
|
} |
||||||
|
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 ErrInvalidLengthAuth |
||||||
|
} |
||||||
|
postIndex := iNdEx + intStringLen |
||||||
|
if postIndex > l { |
||||||
|
return io.ErrUnexpectedEOF |
||||||
|
} |
||||||
|
m.Roles = append(m.Roles, string(dAtA[iNdEx:postIndex])) |
||||||
|
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 (m *Permission) 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: Permission: wiretype end group for non-group") |
||||||
|
} |
||||||
|
if fieldNum <= 0 { |
||||||
|
return fmt.Errorf("proto: Permission: illegal tag %d (wire type %d)", fieldNum, wire) |
||||||
|
} |
||||||
|
switch fieldNum { |
||||||
|
case 1: |
||||||
|
if wireType != 0 { |
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field PermType", wireType) |
||||||
|
} |
||||||
|
m.PermType = 0 |
||||||
|
for shift := uint(0); ; shift += 7 { |
||||||
|
if shift >= 64 { |
||||||
|
return ErrIntOverflowAuth |
||||||
|
} |
||||||
|
if iNdEx >= l { |
||||||
|
return io.ErrUnexpectedEOF |
||||||
|
} |
||||||
|
b := dAtA[iNdEx] |
||||||
|
iNdEx++ |
||||||
|
m.PermType |= (Permission_Type(b) & 0x7F) << shift |
||||||
|
if b < 0x80 { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
case 2: |
||||||
|
if wireType != 2 { |
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field Key", 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.Key = append(m.Key[:0], dAtA[iNdEx:postIndex]...) |
||||||
|
if m.Key == nil { |
||||||
|
m.Key = []byte{} |
||||||
|
} |
||||||
|
iNdEx = postIndex |
||||||
|
case 3: |
||||||
|
if wireType != 2 { |
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field RangeEnd", 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.RangeEnd = append(m.RangeEnd[:0], dAtA[iNdEx:postIndex]...) |
||||||
|
if m.RangeEnd == nil { |
||||||
|
m.RangeEnd = []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 (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 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 KeyPermission", wireType) |
||||||
|
} |
||||||
|
var msglen int |
||||||
|
for shift := uint(0); ; shift += 7 { |
||||||
|
if shift >= 64 { |
||||||
|
return ErrIntOverflowAuth |
||||||
|
} |
||||||
|
if iNdEx >= l { |
||||||
|
return io.ErrUnexpectedEOF |
||||||
|
} |
||||||
|
b := dAtA[iNdEx] |
||||||
|
iNdEx++ |
||||||
|
msglen |= (int(b) & 0x7F) << shift |
||||||
|
if b < 0x80 { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
if msglen < 0 { |
||||||
|
return ErrInvalidLengthAuth |
||||||
|
} |
||||||
|
postIndex := iNdEx + msglen |
||||||
|
if postIndex > l { |
||||||
|
return io.ErrUnexpectedEOF |
||||||
|
} |
||||||
|
m.KeyPermission = append(m.KeyPermission, &Permission{}) |
||||||
|
if err := m.KeyPermission[len(m.KeyPermission)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
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") |
||||||
|
) |
||||||
|
|
||||||
|
func init() { proto.RegisterFile("auth.proto", fileDescriptorAuth) } |
||||||
|
|
||||||
|
var fileDescriptorAuth = []byte{ |
||||||
|
// 288 bytes of a gzipped FileDescriptorProto
|
||||||
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x90, 0xc1, 0x4a, 0xc3, 0x30, |
||||||
|
0x1c, 0xc6, 0x9b, 0xb6, 0x1b, 0xed, 0x5f, 0x27, 0x25, 0x0c, 0x0c, 0x13, 0x42, 0xe9, 0xa9, 0x78, |
||||||
|
0xa8, 0xb0, 0x5d, 0xbc, 0x2a, 0xf6, 0x20, 0x78, 0x90, 0x50, 0xf1, 0x28, 0x1d, 0x0d, 0x75, 0x6c, |
||||||
|
0x6d, 0x4a, 0x32, 0x91, 0xbe, 0x89, 0x07, 0x1f, 0x68, 0xc7, 0x3d, 0x82, 0xab, 0x2f, 0x22, 0x4d, |
||||||
|
0x64, 0x43, 0xdc, 0xed, 0xfb, 0xbe, 0xff, 0x97, 0xe4, 0x97, 0x3f, 0x40, 0xfe, 0xb6, 0x7e, 0x4d, |
||||||
|
0x1a, 0x29, 0xd6, 0x02, 0x0f, 0x7b, 0xdd, 0xcc, 0x27, 0xe3, 0x52, 0x94, 0x42, 0x47, 0x57, 0xbd, |
||||||
|
0x32, 0xd3, 0xe8, 0x01, 0xdc, 0x27, 0xc5, 0x25, 0xc6, 0xe0, 0xd6, 0x79, 0xc5, 0x09, 0x0a, 0x51, |
||||||
|
0x7c, 0xca, 0xb4, 0xc6, 0x13, 0xf0, 0x9a, 0x5c, 0xa9, 0x77, 0x21, 0x0b, 0x62, 0xeb, 0x7c, 0xef, |
||||||
|
0xf1, 0x18, 0x06, 0x52, 0xac, 0xb8, 0x22, 0x4e, 0xe8, 0xc4, 0x3e, 0x33, 0x26, 0xfa, 0x44, 0x00, |
||||||
|
0x8f, 0x5c, 0x56, 0x0b, 0xa5, 0x16, 0xa2, 0xc6, 0x33, 0xf0, 0x1a, 0x2e, 0xab, 0xac, 0x6d, 0xcc, |
||||||
|
0xc5, 0x67, 0xd3, 0xf3, 0xc4, 0xd0, 0x24, 0x87, 0x56, 0xd2, 0x8f, 0xd9, 0xbe, 0x88, 0x03, 0x70, |
||||||
|
0x96, 0xbc, 0xfd, 0x7d, 0xb0, 0x97, 0xf8, 0x02, 0x7c, 0x99, 0xd7, 0x25, 0x7f, 0xe1, 0x75, 0x41, |
||||||
|
0x1c, 0x03, 0xa2, 0x83, 0xb4, 0x2e, 0xa2, 0x4b, 0x70, 0xf5, 0x31, 0x0f, 0x5c, 0x96, 0xde, 0xdc, |
||||||
|
0x05, 0x16, 0xf6, 0x61, 0xf0, 0xcc, 0xee, 0xb3, 0x34, 0x40, 0x78, 0x04, 0x7e, 0x1f, 0x1a, 0x6b, |
||||||
|
0x47, 0x19, 0xb8, 0x4c, 0xac, 0xf8, 0xd1, 0xcf, 0x5e, 0xc3, 0x68, 0xc9, 0xdb, 0x03, 0x16, 0xb1, |
||||||
|
0x43, 0x27, 0x3e, 0x99, 0xe2, 0xff, 0xc0, 0xec, 0x6f, 0xf1, 0x96, 0x6c, 0x76, 0xd4, 0xda, 0xee, |
||||||
|
0xa8, 0xb5, 0xe9, 0x28, 0xda, 0x76, 0x14, 0x7d, 0x75, 0x14, 0x7d, 0x7c, 0x53, 0x6b, 0x3e, 0xd4, |
||||||
|
0x3b, 0x9e, 0xfd, 0x04, 0x00, 0x00, 0xff, 0xff, 0xcc, 0x76, 0x8d, 0x4f, 0x8f, 0x01, 0x00, 0x00, |
||||||
|
} |
||||||
@ -0,0 +1,222 @@ |
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package clientv3 |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"strings" |
||||||
|
|
||||||
|
"github.com/coreos/etcd/auth/authpb" |
||||||
|
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" |
||||||
|
"golang.org/x/net/context" |
||||||
|
"google.golang.org/grpc" |
||||||
|
) |
||||||
|
|
||||||
|
type ( |
||||||
|
AuthEnableResponse pb.AuthEnableResponse |
||||||
|
AuthDisableResponse pb.AuthDisableResponse |
||||||
|
AuthenticateResponse pb.AuthenticateResponse |
||||||
|
AuthUserAddResponse pb.AuthUserAddResponse |
||||||
|
AuthUserDeleteResponse pb.AuthUserDeleteResponse |
||||||
|
AuthUserChangePasswordResponse pb.AuthUserChangePasswordResponse |
||||||
|
AuthUserGrantRoleResponse pb.AuthUserGrantRoleResponse |
||||||
|
AuthUserGetResponse pb.AuthUserGetResponse |
||||||
|
AuthUserRevokeRoleResponse pb.AuthUserRevokeRoleResponse |
||||||
|
AuthRoleAddResponse pb.AuthRoleAddResponse |
||||||
|
AuthRoleGrantPermissionResponse pb.AuthRoleGrantPermissionResponse |
||||||
|
AuthRoleGetResponse pb.AuthRoleGetResponse |
||||||
|
AuthRoleRevokePermissionResponse pb.AuthRoleRevokePermissionResponse |
||||||
|
AuthRoleDeleteResponse pb.AuthRoleDeleteResponse |
||||||
|
AuthUserListResponse pb.AuthUserListResponse |
||||||
|
AuthRoleListResponse pb.AuthRoleListResponse |
||||||
|
|
||||||
|
PermissionType authpb.Permission_Type |
||||||
|
Permission authpb.Permission |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
PermRead = authpb.READ |
||||||
|
PermWrite = authpb.WRITE |
||||||
|
PermReadWrite = authpb.READWRITE |
||||||
|
) |
||||||
|
|
||||||
|
type Auth interface { |
||||||
|
// AuthEnable enables auth of an etcd cluster.
|
||||||
|
AuthEnable(ctx context.Context) (*AuthEnableResponse, error) |
||||||
|
|
||||||
|
// AuthDisable disables auth of an etcd cluster.
|
||||||
|
AuthDisable(ctx context.Context) (*AuthDisableResponse, 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) |
||||||
|
|
||||||
|
// UserGrantRole grants a role to a user.
|
||||||
|
UserGrantRole(ctx context.Context, user string, role string) (*AuthUserGrantRoleResponse, error) |
||||||
|
|
||||||
|
// UserGet gets a detailed information of a user.
|
||||||
|
UserGet(ctx context.Context, name string) (*AuthUserGetResponse, error) |
||||||
|
|
||||||
|
// UserList gets a list of all users.
|
||||||
|
UserList(ctx context.Context) (*AuthUserListResponse, error) |
||||||
|
|
||||||
|
// UserRevokeRole revokes a role of a user.
|
||||||
|
UserRevokeRole(ctx context.Context, name string, role string) (*AuthUserRevokeRoleResponse, error) |
||||||
|
|
||||||
|
// RoleAdd adds a new role to an etcd cluster.
|
||||||
|
RoleAdd(ctx context.Context, name string) (*AuthRoleAddResponse, error) |
||||||
|
|
||||||
|
// RoleGrantPermission grants a permission to a role.
|
||||||
|
RoleGrantPermission(ctx context.Context, name string, key, rangeEnd string, permType PermissionType) (*AuthRoleGrantPermissionResponse, error) |
||||||
|
|
||||||
|
// RoleGet gets a detailed information of a role.
|
||||||
|
RoleGet(ctx context.Context, role string) (*AuthRoleGetResponse, error) |
||||||
|
|
||||||
|
// RoleList gets a list of all roles.
|
||||||
|
RoleList(ctx context.Context) (*AuthRoleListResponse, error) |
||||||
|
|
||||||
|
// RoleRevokePermission revokes a permission from a role.
|
||||||
|
RoleRevokePermission(ctx context.Context, role string, key, rangeEnd string) (*AuthRoleRevokePermissionResponse, error) |
||||||
|
|
||||||
|
// RoleDelete deletes a role.
|
||||||
|
RoleDelete(ctx context.Context, role string) (*AuthRoleDeleteResponse, error) |
||||||
|
} |
||||||
|
|
||||||
|
type auth struct { |
||||||
|
remote pb.AuthClient |
||||||
|
} |
||||||
|
|
||||||
|
func NewAuth(c *Client) Auth { |
||||||
|
return &auth{remote: pb.NewAuthClient(c.ActiveConnection())} |
||||||
|
} |
||||||
|
|
||||||
|
func (auth *auth) AuthEnable(ctx context.Context) (*AuthEnableResponse, error) { |
||||||
|
resp, err := auth.remote.AuthEnable(ctx, &pb.AuthEnableRequest{}, grpc.FailFast(false)) |
||||||
|
return (*AuthEnableResponse)(resp), toErr(ctx, err) |
||||||
|
} |
||||||
|
|
||||||
|
func (auth *auth) AuthDisable(ctx context.Context) (*AuthDisableResponse, error) { |
||||||
|
resp, err := auth.remote.AuthDisable(ctx, &pb.AuthDisableRequest{}, grpc.FailFast(false)) |
||||||
|
return (*AuthDisableResponse)(resp), toErr(ctx, 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), toErr(ctx, 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), toErr(ctx, 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), toErr(ctx, err) |
||||||
|
} |
||||||
|
|
||||||
|
func (auth *auth) UserGrantRole(ctx context.Context, user string, role string) (*AuthUserGrantRoleResponse, error) { |
||||||
|
resp, err := auth.remote.UserGrantRole(ctx, &pb.AuthUserGrantRoleRequest{User: user, Role: role}) |
||||||
|
return (*AuthUserGrantRoleResponse)(resp), toErr(ctx, err) |
||||||
|
} |
||||||
|
|
||||||
|
func (auth *auth) UserGet(ctx context.Context, name string) (*AuthUserGetResponse, error) { |
||||||
|
resp, err := auth.remote.UserGet(ctx, &pb.AuthUserGetRequest{Name: name}, grpc.FailFast(false)) |
||||||
|
return (*AuthUserGetResponse)(resp), toErr(ctx, err) |
||||||
|
} |
||||||
|
|
||||||
|
func (auth *auth) UserList(ctx context.Context) (*AuthUserListResponse, error) { |
||||||
|
resp, err := auth.remote.UserList(ctx, &pb.AuthUserListRequest{}, grpc.FailFast(false)) |
||||||
|
return (*AuthUserListResponse)(resp), toErr(ctx, err) |
||||||
|
} |
||||||
|
|
||||||
|
func (auth *auth) UserRevokeRole(ctx context.Context, name string, role string) (*AuthUserRevokeRoleResponse, error) { |
||||||
|
resp, err := auth.remote.UserRevokeRole(ctx, &pb.AuthUserRevokeRoleRequest{Name: name, Role: role}) |
||||||
|
return (*AuthUserRevokeRoleResponse)(resp), toErr(ctx, 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), toErr(ctx, err) |
||||||
|
} |
||||||
|
|
||||||
|
func (auth *auth) RoleGrantPermission(ctx context.Context, name string, key, rangeEnd string, permType PermissionType) (*AuthRoleGrantPermissionResponse, error) { |
||||||
|
perm := &authpb.Permission{ |
||||||
|
Key: []byte(key), |
||||||
|
RangeEnd: []byte(rangeEnd), |
||||||
|
PermType: authpb.Permission_Type(permType), |
||||||
|
} |
||||||
|
resp, err := auth.remote.RoleGrantPermission(ctx, &pb.AuthRoleGrantPermissionRequest{Name: name, Perm: perm}) |
||||||
|
return (*AuthRoleGrantPermissionResponse)(resp), toErr(ctx, err) |
||||||
|
} |
||||||
|
|
||||||
|
func (auth *auth) RoleGet(ctx context.Context, role string) (*AuthRoleGetResponse, error) { |
||||||
|
resp, err := auth.remote.RoleGet(ctx, &pb.AuthRoleGetRequest{Role: role}, grpc.FailFast(false)) |
||||||
|
return (*AuthRoleGetResponse)(resp), toErr(ctx, err) |
||||||
|
} |
||||||
|
|
||||||
|
func (auth *auth) RoleList(ctx context.Context) (*AuthRoleListResponse, error) { |
||||||
|
resp, err := auth.remote.RoleList(ctx, &pb.AuthRoleListRequest{}, grpc.FailFast(false)) |
||||||
|
return (*AuthRoleListResponse)(resp), toErr(ctx, err) |
||||||
|
} |
||||||
|
|
||||||
|
func (auth *auth) RoleRevokePermission(ctx context.Context, role string, key, rangeEnd string) (*AuthRoleRevokePermissionResponse, error) { |
||||||
|
resp, err := auth.remote.RoleRevokePermission(ctx, &pb.AuthRoleRevokePermissionRequest{Role: role, Key: key, RangeEnd: rangeEnd}) |
||||||
|
return (*AuthRoleRevokePermissionResponse)(resp), toErr(ctx, err) |
||||||
|
} |
||||||
|
|
||||||
|
func (auth *auth) RoleDelete(ctx context.Context, role string) (*AuthRoleDeleteResponse, error) { |
||||||
|
resp, err := auth.remote.RoleDelete(ctx, &pb.AuthRoleDeleteRequest{Role: role}) |
||||||
|
return (*AuthRoleDeleteResponse)(resp), toErr(ctx, err) |
||||||
|
} |
||||||
|
|
||||||
|
func StrToPermissionType(s string) (PermissionType, error) { |
||||||
|
val, ok := authpb.Permission_Type_value[strings.ToUpper(s)] |
||||||
|
if ok { |
||||||
|
return PermissionType(val), nil |
||||||
|
} |
||||||
|
return PermissionType(-1), fmt.Errorf("invalid permission type: %s", s) |
||||||
|
} |
||||||
|
|
||||||
|
type authenticator struct { |
||||||
|
conn *grpc.ClientConn // conn in-use
|
||||||
|
remote pb.AuthClient |
||||||
|
} |
||||||
|
|
||||||
|
func (auth *authenticator) authenticate(ctx context.Context, name string, password string) (*AuthenticateResponse, error) { |
||||||
|
resp, err := auth.remote.Authenticate(ctx, &pb.AuthenticateRequest{Name: name, Password: password}, grpc.FailFast(false)) |
||||||
|
return (*AuthenticateResponse)(resp), toErr(ctx, err) |
||||||
|
} |
||||||
|
|
||||||
|
func (auth *authenticator) close() { |
||||||
|
auth.conn.Close() |
||||||
|
} |
||||||
|
|
||||||
|
func newAuthenticator(endpoint string, opts []grpc.DialOption) (*authenticator, error) { |
||||||
|
conn, err := grpc.Dial(endpoint, opts...) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
return &authenticator{ |
||||||
|
conn: conn, |
||||||
|
remote: pb.NewAuthClient(conn), |
||||||
|
}, nil |
||||||
|
} |
||||||
@ -0,0 +1,356 @@ |
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package clientv3 |
||||||
|
|
||||||
|
import ( |
||||||
|
"net/url" |
||||||
|
"strings" |
||||||
|
"sync" |
||||||
|
|
||||||
|
"golang.org/x/net/context" |
||||||
|
"google.golang.org/grpc" |
||||||
|
"google.golang.org/grpc/codes" |
||||||
|
) |
||||||
|
|
||||||
|
// ErrNoAddrAvilable is returned by Get() when the balancer does not have
|
||||||
|
// any active connection to endpoints at the time.
|
||||||
|
// This error is returned only when opts.BlockingWait is true.
|
||||||
|
var ErrNoAddrAvilable = grpc.Errorf(codes.Unavailable, "there is no address available") |
||||||
|
|
||||||
|
// simpleBalancer does the bare minimum to expose multiple eps
|
||||||
|
// to the grpc reconnection code path
|
||||||
|
type simpleBalancer struct { |
||||||
|
// addrs are the client's endpoints for grpc
|
||||||
|
addrs []grpc.Address |
||||||
|
// notifyCh notifies grpc of the set of addresses for connecting
|
||||||
|
notifyCh chan []grpc.Address |
||||||
|
|
||||||
|
// readyc closes once the first connection is up
|
||||||
|
readyc chan struct{} |
||||||
|
readyOnce sync.Once |
||||||
|
|
||||||
|
// mu protects upEps, pinAddr, and connectingAddr
|
||||||
|
mu sync.RWMutex |
||||||
|
|
||||||
|
// upc closes when upEps transitions from empty to non-zero or the balancer closes.
|
||||||
|
upc chan struct{} |
||||||
|
|
||||||
|
// downc closes when grpc calls down() on pinAddr
|
||||||
|
downc chan struct{} |
||||||
|
|
||||||
|
// stopc is closed to signal updateNotifyLoop should stop.
|
||||||
|
stopc chan struct{} |
||||||
|
|
||||||
|
// donec closes when all goroutines are exited
|
||||||
|
donec chan struct{} |
||||||
|
|
||||||
|
// updateAddrsC notifies updateNotifyLoop to update addrs.
|
||||||
|
updateAddrsC chan struct{} |
||||||
|
|
||||||
|
// grpc issues TLS cert checks using the string passed into dial so
|
||||||
|
// that string must be the host. To recover the full scheme://host URL,
|
||||||
|
// have a map from hosts to the original endpoint.
|
||||||
|
host2ep map[string]string |
||||||
|
|
||||||
|
// pinAddr is the currently pinned address; set to the empty string on
|
||||||
|
// intialization and shutdown.
|
||||||
|
pinAddr string |
||||||
|
|
||||||
|
closed bool |
||||||
|
} |
||||||
|
|
||||||
|
func newSimpleBalancer(eps []string) *simpleBalancer { |
||||||
|
notifyCh := make(chan []grpc.Address, 1) |
||||||
|
addrs := make([]grpc.Address, len(eps)) |
||||||
|
for i := range eps { |
||||||
|
addrs[i].Addr = getHost(eps[i]) |
||||||
|
} |
||||||
|
sb := &simpleBalancer{ |
||||||
|
addrs: addrs, |
||||||
|
notifyCh: notifyCh, |
||||||
|
readyc: make(chan struct{}), |
||||||
|
upc: make(chan struct{}), |
||||||
|
stopc: make(chan struct{}), |
||||||
|
downc: make(chan struct{}), |
||||||
|
donec: make(chan struct{}), |
||||||
|
updateAddrsC: make(chan struct{}, 1), |
||||||
|
host2ep: getHost2ep(eps), |
||||||
|
} |
||||||
|
close(sb.downc) |
||||||
|
go sb.updateNotifyLoop() |
||||||
|
return sb |
||||||
|
} |
||||||
|
|
||||||
|
func (b *simpleBalancer) Start(target string, config grpc.BalancerConfig) error { return nil } |
||||||
|
|
||||||
|
func (b *simpleBalancer) ConnectNotify() <-chan struct{} { |
||||||
|
b.mu.Lock() |
||||||
|
defer b.mu.Unlock() |
||||||
|
return b.upc |
||||||
|
} |
||||||
|
|
||||||
|
func (b *simpleBalancer) getEndpoint(host string) string { |
||||||
|
b.mu.Lock() |
||||||
|
defer b.mu.Unlock() |
||||||
|
return b.host2ep[host] |
||||||
|
} |
||||||
|
|
||||||
|
func getHost2ep(eps []string) map[string]string { |
||||||
|
hm := make(map[string]string, len(eps)) |
||||||
|
for i := range eps { |
||||||
|
_, host, _ := parseEndpoint(eps[i]) |
||||||
|
hm[host] = eps[i] |
||||||
|
} |
||||||
|
return hm |
||||||
|
} |
||||||
|
|
||||||
|
func (b *simpleBalancer) updateAddrs(eps []string) { |
||||||
|
np := getHost2ep(eps) |
||||||
|
|
||||||
|
b.mu.Lock() |
||||||
|
|
||||||
|
match := len(np) == len(b.host2ep) |
||||||
|
for k, v := range np { |
||||||
|
if b.host2ep[k] != v { |
||||||
|
match = false |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
if match { |
||||||
|
// same endpoints, so no need to update address
|
||||||
|
b.mu.Unlock() |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
b.host2ep = np |
||||||
|
|
||||||
|
addrs := make([]grpc.Address, 0, len(eps)) |
||||||
|
for i := range eps { |
||||||
|
addrs = append(addrs, grpc.Address{Addr: getHost(eps[i])}) |
||||||
|
} |
||||||
|
b.addrs = addrs |
||||||
|
|
||||||
|
// updating notifyCh can trigger new connections,
|
||||||
|
// only update addrs if all connections are down
|
||||||
|
// or addrs does not include pinAddr.
|
||||||
|
update := !hasAddr(addrs, b.pinAddr) |
||||||
|
b.mu.Unlock() |
||||||
|
|
||||||
|
if update { |
||||||
|
select { |
||||||
|
case b.updateAddrsC <- struct{}{}: |
||||||
|
case <-b.stopc: |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func hasAddr(addrs []grpc.Address, targetAddr string) bool { |
||||||
|
for _, addr := range addrs { |
||||||
|
if targetAddr == addr.Addr { |
||||||
|
return true |
||||||
|
} |
||||||
|
} |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func (b *simpleBalancer) updateNotifyLoop() { |
||||||
|
defer close(b.donec) |
||||||
|
|
||||||
|
for { |
||||||
|
b.mu.RLock() |
||||||
|
upc, downc, addr := b.upc, b.downc, b.pinAddr |
||||||
|
b.mu.RUnlock() |
||||||
|
// downc or upc should be closed
|
||||||
|
select { |
||||||
|
case <-downc: |
||||||
|
downc = nil |
||||||
|
default: |
||||||
|
} |
||||||
|
select { |
||||||
|
case <-upc: |
||||||
|
upc = nil |
||||||
|
default: |
||||||
|
} |
||||||
|
switch { |
||||||
|
case downc == nil && upc == nil: |
||||||
|
// stale
|
||||||
|
select { |
||||||
|
case <-b.stopc: |
||||||
|
return |
||||||
|
default: |
||||||
|
} |
||||||
|
case downc == nil: |
||||||
|
b.notifyAddrs() |
||||||
|
select { |
||||||
|
case <-upc: |
||||||
|
case <-b.updateAddrsC: |
||||||
|
b.notifyAddrs() |
||||||
|
case <-b.stopc: |
||||||
|
return |
||||||
|
} |
||||||
|
case upc == nil: |
||||||
|
select { |
||||||
|
// close connections that are not the pinned address
|
||||||
|
case b.notifyCh <- []grpc.Address{{Addr: addr}}: |
||||||
|
case <-downc: |
||||||
|
case <-b.stopc: |
||||||
|
return |
||||||
|
} |
||||||
|
select { |
||||||
|
case <-downc: |
||||||
|
case <-b.updateAddrsC: |
||||||
|
case <-b.stopc: |
||||||
|
return |
||||||
|
} |
||||||
|
b.notifyAddrs() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (b *simpleBalancer) notifyAddrs() { |
||||||
|
b.mu.RLock() |
||||||
|
addrs := b.addrs |
||||||
|
b.mu.RUnlock() |
||||||
|
select { |
||||||
|
case b.notifyCh <- addrs: |
||||||
|
case <-b.stopc: |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (b *simpleBalancer) Up(addr grpc.Address) func(error) { |
||||||
|
b.mu.Lock() |
||||||
|
defer b.mu.Unlock() |
||||||
|
|
||||||
|
// gRPC might call Up after it called Close. We add this check
|
||||||
|
// to "fix" it up at application layer. Or our simplerBalancer
|
||||||
|
// might panic since b.upc is closed.
|
||||||
|
if b.closed { |
||||||
|
return func(err error) {} |
||||||
|
} |
||||||
|
// gRPC might call Up on a stale address.
|
||||||
|
// Prevent updating pinAddr with a stale address.
|
||||||
|
if !hasAddr(b.addrs, addr.Addr) { |
||||||
|
return func(err error) {} |
||||||
|
} |
||||||
|
if b.pinAddr != "" { |
||||||
|
return func(err error) {} |
||||||
|
} |
||||||
|
// notify waiting Get()s and pin first connected address
|
||||||
|
close(b.upc) |
||||||
|
b.downc = make(chan struct{}) |
||||||
|
b.pinAddr = addr.Addr |
||||||
|
// notify client that a connection is up
|
||||||
|
b.readyOnce.Do(func() { close(b.readyc) }) |
||||||
|
return func(err error) { |
||||||
|
b.mu.Lock() |
||||||
|
b.upc = make(chan struct{}) |
||||||
|
close(b.downc) |
||||||
|
b.pinAddr = "" |
||||||
|
b.mu.Unlock() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (b *simpleBalancer) Get(ctx context.Context, opts grpc.BalancerGetOptions) (grpc.Address, func(), error) { |
||||||
|
var ( |
||||||
|
addr string |
||||||
|
closed bool |
||||||
|
) |
||||||
|
|
||||||
|
// If opts.BlockingWait is false (for fail-fast RPCs), it should return
|
||||||
|
// an address it has notified via Notify immediately instead of blocking.
|
||||||
|
if !opts.BlockingWait { |
||||||
|
b.mu.RLock() |
||||||
|
closed = b.closed |
||||||
|
addr = b.pinAddr |
||||||
|
b.mu.RUnlock() |
||||||
|
if closed { |
||||||
|
return grpc.Address{Addr: ""}, nil, grpc.ErrClientConnClosing |
||||||
|
} |
||||||
|
if addr == "" { |
||||||
|
return grpc.Address{Addr: ""}, nil, ErrNoAddrAvilable |
||||||
|
} |
||||||
|
return grpc.Address{Addr: addr}, func() {}, nil |
||||||
|
} |
||||||
|
|
||||||
|
for { |
||||||
|
b.mu.RLock() |
||||||
|
ch := b.upc |
||||||
|
b.mu.RUnlock() |
||||||
|
select { |
||||||
|
case <-ch: |
||||||
|
case <-b.donec: |
||||||
|
return grpc.Address{Addr: ""}, nil, grpc.ErrClientConnClosing |
||||||
|
case <-ctx.Done(): |
||||||
|
return grpc.Address{Addr: ""}, nil, ctx.Err() |
||||||
|
} |
||||||
|
b.mu.RLock() |
||||||
|
closed = b.closed |
||||||
|
addr = b.pinAddr |
||||||
|
b.mu.RUnlock() |
||||||
|
// Close() which sets b.closed = true can be called before Get(), Get() must exit if balancer is closed.
|
||||||
|
if closed { |
||||||
|
return grpc.Address{Addr: ""}, nil, grpc.ErrClientConnClosing |
||||||
|
} |
||||||
|
if addr != "" { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
return grpc.Address{Addr: addr}, func() {}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (b *simpleBalancer) Notify() <-chan []grpc.Address { return b.notifyCh } |
||||||
|
|
||||||
|
func (b *simpleBalancer) Close() error { |
||||||
|
b.mu.Lock() |
||||||
|
// In case gRPC calls close twice. TODO: remove the checking
|
||||||
|
// when we are sure that gRPC wont call close twice.
|
||||||
|
if b.closed { |
||||||
|
b.mu.Unlock() |
||||||
|
<-b.donec |
||||||
|
return nil |
||||||
|
} |
||||||
|
b.closed = true |
||||||
|
close(b.stopc) |
||||||
|
b.pinAddr = "" |
||||||
|
|
||||||
|
// In the case of following scenario:
|
||||||
|
// 1. upc is not closed; no pinned address
|
||||||
|
// 2. client issues an rpc, calling invoke(), which calls Get(), enters for loop, blocks
|
||||||
|
// 3. clientconn.Close() calls balancer.Close(); closed = true
|
||||||
|
// 4. for loop in Get() never exits since ctx is the context passed in by the client and may not be canceled
|
||||||
|
// we must close upc so Get() exits from blocking on upc
|
||||||
|
select { |
||||||
|
case <-b.upc: |
||||||
|
default: |
||||||
|
// terminate all waiting Get()s
|
||||||
|
close(b.upc) |
||||||
|
} |
||||||
|
|
||||||
|
b.mu.Unlock() |
||||||
|
|
||||||
|
// wait for updateNotifyLoop to finish
|
||||||
|
<-b.donec |
||||||
|
close(b.notifyCh) |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func getHost(ep string) string { |
||||||
|
url, uerr := url.Parse(ep) |
||||||
|
if uerr != nil || !strings.Contains(ep, "://") { |
||||||
|
return ep |
||||||
|
} |
||||||
|
return url.Host |
||||||
|
} |
||||||
@ -0,0 +1,515 @@ |
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package clientv3 |
||||||
|
|
||||||
|
import ( |
||||||
|
"crypto/tls" |
||||||
|
"errors" |
||||||
|
"fmt" |
||||||
|
"net" |
||||||
|
"net/url" |
||||||
|
"strconv" |
||||||
|
"strings" |
||||||
|
"sync" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" |
||||||
|
|
||||||
|
"golang.org/x/net/context" |
||||||
|
"google.golang.org/grpc" |
||||||
|
"google.golang.org/grpc/codes" |
||||||
|
"google.golang.org/grpc/credentials" |
||||||
|
"google.golang.org/grpc/metadata" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
ErrNoAvailableEndpoints = errors.New("etcdclient: no available endpoints") |
||||||
|
ErrOldCluster = errors.New("etcdclient: old cluster version") |
||||||
|
) |
||||||
|
|
||||||
|
// Client provides and manages an etcd v3 client session.
|
||||||
|
type Client struct { |
||||||
|
Cluster |
||||||
|
KV |
||||||
|
Lease |
||||||
|
Watcher |
||||||
|
Auth |
||||||
|
Maintenance |
||||||
|
|
||||||
|
conn *grpc.ClientConn |
||||||
|
dialerrc chan error |
||||||
|
|
||||||
|
cfg Config |
||||||
|
creds *credentials.TransportCredentials |
||||||
|
balancer *simpleBalancer |
||||||
|
retryWrapper retryRpcFunc |
||||||
|
retryAuthWrapper retryRpcFunc |
||||||
|
|
||||||
|
ctx context.Context |
||||||
|
cancel context.CancelFunc |
||||||
|
|
||||||
|
// Username is a username for authentication
|
||||||
|
Username string |
||||||
|
// Password is a password for authentication
|
||||||
|
Password string |
||||||
|
// tokenCred is an instance of WithPerRPCCredentials()'s argument
|
||||||
|
tokenCred *authTokenCredential |
||||||
|
} |
||||||
|
|
||||||
|
// New creates a new etcdv3 client from a given configuration.
|
||||||
|
func New(cfg Config) (*Client, error) { |
||||||
|
if len(cfg.Endpoints) == 0 { |
||||||
|
return nil, ErrNoAvailableEndpoints |
||||||
|
} |
||||||
|
|
||||||
|
return newClient(&cfg) |
||||||
|
} |
||||||
|
|
||||||
|
// NewCtxClient creates a client with a context but no underlying grpc
|
||||||
|
// connection. This is useful for embedded cases that override the
|
||||||
|
// service interface implementations and do not need connection management.
|
||||||
|
func NewCtxClient(ctx context.Context) *Client { |
||||||
|
cctx, cancel := context.WithCancel(ctx) |
||||||
|
return &Client{ctx: cctx, cancel: cancel} |
||||||
|
} |
||||||
|
|
||||||
|
// NewFromURL creates a new etcdv3 client from a URL.
|
||||||
|
func NewFromURL(url string) (*Client, error) { |
||||||
|
return New(Config{Endpoints: []string{url}}) |
||||||
|
} |
||||||
|
|
||||||
|
// Close shuts down the client's etcd connections.
|
||||||
|
func (c *Client) Close() error { |
||||||
|
c.cancel() |
||||||
|
c.Watcher.Close() |
||||||
|
c.Lease.Close() |
||||||
|
if c.conn != nil { |
||||||
|
return toErr(c.ctx, c.conn.Close()) |
||||||
|
} |
||||||
|
return c.ctx.Err() |
||||||
|
} |
||||||
|
|
||||||
|
// 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() (eps []string) { |
||||||
|
// copy the slice; protect original endpoints from being changed
|
||||||
|
eps = make([]string, len(c.cfg.Endpoints)) |
||||||
|
copy(eps, c.cfg.Endpoints) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// SetEndpoints updates client's endpoints.
|
||||||
|
func (c *Client) SetEndpoints(eps ...string) { |
||||||
|
c.cfg.Endpoints = eps |
||||||
|
c.balancer.updateAddrs(eps) |
||||||
|
} |
||||||
|
|
||||||
|
// Sync synchronizes client's endpoints with the known endpoints from the etcd membership.
|
||||||
|
func (c *Client) Sync(ctx context.Context) error { |
||||||
|
mresp, err := c.MemberList(ctx) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
var eps []string |
||||||
|
for _, m := range mresp.Members { |
||||||
|
eps = append(eps, m.ClientURLs...) |
||||||
|
} |
||||||
|
c.SetEndpoints(eps...) |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *Client) autoSync() { |
||||||
|
if c.cfg.AutoSyncInterval == time.Duration(0) { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
for { |
||||||
|
select { |
||||||
|
case <-c.ctx.Done(): |
||||||
|
return |
||||||
|
case <-time.After(c.cfg.AutoSyncInterval): |
||||||
|
ctx, _ := context.WithTimeout(c.ctx, 5*time.Second) |
||||||
|
if err := c.Sync(ctx); err != nil && err != c.ctx.Err() { |
||||||
|
logger.Println("Auto sync endpoints failed:", err) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
type authTokenCredential struct { |
||||||
|
token string |
||||||
|
tokenMu *sync.RWMutex |
||||||
|
} |
||||||
|
|
||||||
|
func (cred authTokenCredential) RequireTransportSecurity() bool { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
func (cred authTokenCredential) GetRequestMetadata(ctx context.Context, s ...string) (map[string]string, error) { |
||||||
|
cred.tokenMu.RLock() |
||||||
|
defer cred.tokenMu.RUnlock() |
||||||
|
return map[string]string{ |
||||||
|
"token": cred.token, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func parseEndpoint(endpoint string) (proto string, host string, scheme string) { |
||||||
|
proto = "tcp" |
||||||
|
host = endpoint |
||||||
|
url, uerr := url.Parse(endpoint) |
||||||
|
if uerr != nil || !strings.Contains(endpoint, "://") { |
||||||
|
return |
||||||
|
} |
||||||
|
scheme = url.Scheme |
||||||
|
|
||||||
|
// strip scheme:// prefix since grpc dials by host
|
||||||
|
host = url.Host |
||||||
|
switch url.Scheme { |
||||||
|
case "http", "https": |
||||||
|
case "unix", "unixs": |
||||||
|
proto = "unix" |
||||||
|
host = url.Host + url.Path |
||||||
|
default: |
||||||
|
proto, host = "", "" |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
func (c *Client) processCreds(scheme string) (creds *credentials.TransportCredentials) { |
||||||
|
creds = c.creds |
||||||
|
switch scheme { |
||||||
|
case "unix": |
||||||
|
case "http": |
||||||
|
creds = nil |
||||||
|
case "https", "unixs": |
||||||
|
if creds != nil { |
||||||
|
break |
||||||
|
} |
||||||
|
tlsconfig := &tls.Config{} |
||||||
|
emptyCreds := credentials.NewTLS(tlsconfig) |
||||||
|
creds = &emptyCreds |
||||||
|
default: |
||||||
|
creds = nil |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// dialSetupOpts gives the dial opts prior to any authentication
|
||||||
|
func (c *Client) dialSetupOpts(endpoint string, dopts ...grpc.DialOption) (opts []grpc.DialOption) { |
||||||
|
if c.cfg.DialTimeout > 0 { |
||||||
|
opts = []grpc.DialOption{grpc.WithTimeout(c.cfg.DialTimeout)} |
||||||
|
} |
||||||
|
opts = append(opts, dopts...) |
||||||
|
|
||||||
|
f := func(host string, t time.Duration) (net.Conn, error) { |
||||||
|
proto, host, _ := parseEndpoint(c.balancer.getEndpoint(host)) |
||||||
|
if host == "" && endpoint != "" { |
||||||
|
// dialing an endpoint not in the balancer; use
|
||||||
|
// endpoint passed into dial
|
||||||
|
proto, host, _ = parseEndpoint(endpoint) |
||||||
|
} |
||||||
|
if proto == "" { |
||||||
|
return nil, fmt.Errorf("unknown scheme for %q", host) |
||||||
|
} |
||||||
|
select { |
||||||
|
case <-c.ctx.Done(): |
||||||
|
return nil, c.ctx.Err() |
||||||
|
default: |
||||||
|
} |
||||||
|
dialer := &net.Dialer{Timeout: t} |
||||||
|
conn, err := dialer.DialContext(c.ctx, proto, host) |
||||||
|
if err != nil { |
||||||
|
select { |
||||||
|
case c.dialerrc <- err: |
||||||
|
default: |
||||||
|
} |
||||||
|
} |
||||||
|
return conn, err |
||||||
|
} |
||||||
|
opts = append(opts, grpc.WithDialer(f)) |
||||||
|
|
||||||
|
creds := c.creds |
||||||
|
if _, _, scheme := parseEndpoint(endpoint); len(scheme) != 0 { |
||||||
|
creds = c.processCreds(scheme) |
||||||
|
} |
||||||
|
if creds != nil { |
||||||
|
opts = append(opts, grpc.WithTransportCredentials(*creds)) |
||||||
|
} else { |
||||||
|
opts = append(opts, grpc.WithInsecure()) |
||||||
|
} |
||||||
|
|
||||||
|
return opts |
||||||
|
} |
||||||
|
|
||||||
|
// Dial connects to a single endpoint using the client's config.
|
||||||
|
func (c *Client) Dial(endpoint string) (*grpc.ClientConn, error) { |
||||||
|
return c.dial(endpoint) |
||||||
|
} |
||||||
|
|
||||||
|
func (c *Client) getToken(ctx context.Context) error { |
||||||
|
var err error // return last error in a case of fail
|
||||||
|
var auth *authenticator |
||||||
|
|
||||||
|
for i := 0; i < len(c.cfg.Endpoints); i++ { |
||||||
|
endpoint := c.cfg.Endpoints[i] |
||||||
|
host := getHost(endpoint) |
||||||
|
// use dial options without dopts to avoid reusing the client balancer
|
||||||
|
auth, err = newAuthenticator(host, c.dialSetupOpts(endpoint)) |
||||||
|
if err != nil { |
||||||
|
continue |
||||||
|
} |
||||||
|
defer auth.close() |
||||||
|
|
||||||
|
var resp *AuthenticateResponse |
||||||
|
resp, err = auth.authenticate(ctx, c.Username, c.Password) |
||||||
|
if err != nil { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
c.tokenCred.tokenMu.Lock() |
||||||
|
c.tokenCred.token = resp.Token |
||||||
|
c.tokenCred.tokenMu.Unlock() |
||||||
|
|
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
func (c *Client) dial(endpoint string, dopts ...grpc.DialOption) (*grpc.ClientConn, error) { |
||||||
|
opts := c.dialSetupOpts(endpoint, dopts...) |
||||||
|
host := getHost(endpoint) |
||||||
|
if c.Username != "" && c.Password != "" { |
||||||
|
c.tokenCred = &authTokenCredential{ |
||||||
|
tokenMu: &sync.RWMutex{}, |
||||||
|
} |
||||||
|
|
||||||
|
ctx := c.ctx |
||||||
|
if c.cfg.DialTimeout > 0 { |
||||||
|
cctx, cancel := context.WithTimeout(ctx, c.cfg.DialTimeout) |
||||||
|
defer cancel() |
||||||
|
ctx = cctx |
||||||
|
} |
||||||
|
|
||||||
|
err := c.getToken(ctx) |
||||||
|
if err != nil { |
||||||
|
if toErr(ctx, err) != rpctypes.ErrAuthNotEnabled { |
||||||
|
if err == ctx.Err() && ctx.Err() != c.ctx.Err() { |
||||||
|
err = grpc.ErrClientConnTimeout |
||||||
|
} |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} else { |
||||||
|
opts = append(opts, grpc.WithPerRPCCredentials(c.tokenCred)) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
opts = append(opts, c.cfg.DialOptions...) |
||||||
|
|
||||||
|
conn, err := grpc.DialContext(c.ctx, host, opts...) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return conn, nil |
||||||
|
} |
||||||
|
|
||||||
|
// WithRequireLeader requires client requests to only succeed
|
||||||
|
// when the cluster has a leader.
|
||||||
|
func WithRequireLeader(ctx context.Context) context.Context { |
||||||
|
md := metadata.Pairs(rpctypes.MetadataRequireLeaderKey, rpctypes.MetadataHasLeader) |
||||||
|
return metadata.NewContext(ctx, md) |
||||||
|
} |
||||||
|
|
||||||
|
func newClient(cfg *Config) (*Client, error) { |
||||||
|
if cfg == nil { |
||||||
|
cfg = &Config{} |
||||||
|
} |
||||||
|
var creds *credentials.TransportCredentials |
||||||
|
if cfg.TLS != nil { |
||||||
|
c := credentials.NewTLS(cfg.TLS) |
||||||
|
creds = &c |
||||||
|
} |
||||||
|
|
||||||
|
// use a temporary skeleton client to bootstrap first connection
|
||||||
|
baseCtx := context.TODO() |
||||||
|
if cfg.Context != nil { |
||||||
|
baseCtx = cfg.Context |
||||||
|
} |
||||||
|
|
||||||
|
ctx, cancel := context.WithCancel(baseCtx) |
||||||
|
client := &Client{ |
||||||
|
conn: nil, |
||||||
|
dialerrc: make(chan error, 1), |
||||||
|
cfg: *cfg, |
||||||
|
creds: creds, |
||||||
|
ctx: ctx, |
||||||
|
cancel: cancel, |
||||||
|
} |
||||||
|
if cfg.Username != "" && cfg.Password != "" { |
||||||
|
client.Username = cfg.Username |
||||||
|
client.Password = cfg.Password |
||||||
|
} |
||||||
|
|
||||||
|
client.balancer = newSimpleBalancer(cfg.Endpoints) |
||||||
|
// use Endpoints[0] so that for https:// without any tls config given, then
|
||||||
|
// grpc will assume the ServerName is in the endpoint.
|
||||||
|
conn, err := client.dial(cfg.Endpoints[0], grpc.WithBalancer(client.balancer)) |
||||||
|
if err != nil { |
||||||
|
client.cancel() |
||||||
|
client.balancer.Close() |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
client.conn = conn |
||||||
|
client.retryWrapper = client.newRetryWrapper() |
||||||
|
client.retryAuthWrapper = client.newAuthRetryWrapper() |
||||||
|
|
||||||
|
// wait for a connection
|
||||||
|
if cfg.DialTimeout > 0 { |
||||||
|
hasConn := false |
||||||
|
waitc := time.After(cfg.DialTimeout) |
||||||
|
select { |
||||||
|
case <-client.balancer.readyc: |
||||||
|
hasConn = true |
||||||
|
case <-ctx.Done(): |
||||||
|
case <-waitc: |
||||||
|
} |
||||||
|
if !hasConn { |
||||||
|
err := grpc.ErrClientConnTimeout |
||||||
|
select { |
||||||
|
case err = <-client.dialerrc: |
||||||
|
default: |
||||||
|
} |
||||||
|
client.cancel() |
||||||
|
client.balancer.Close() |
||||||
|
conn.Close() |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
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.RejectOldCluster { |
||||||
|
if err := client.checkVersion(); err != nil { |
||||||
|
client.Close() |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
go client.autoSync() |
||||||
|
return client, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *Client) checkVersion() (err error) { |
||||||
|
var wg sync.WaitGroup |
||||||
|
errc := make(chan error, len(c.cfg.Endpoints)) |
||||||
|
ctx, cancel := context.WithCancel(c.ctx) |
||||||
|
if c.cfg.DialTimeout > 0 { |
||||||
|
ctx, _ = context.WithTimeout(ctx, c.cfg.DialTimeout) |
||||||
|
} |
||||||
|
wg.Add(len(c.cfg.Endpoints)) |
||||||
|
for _, ep := range c.cfg.Endpoints { |
||||||
|
// if cluster is current, any endpoint gives a recent version
|
||||||
|
go func(e string) { |
||||||
|
defer wg.Done() |
||||||
|
resp, rerr := c.Status(ctx, e) |
||||||
|
if rerr != nil { |
||||||
|
errc <- rerr |
||||||
|
return |
||||||
|
} |
||||||
|
vs := strings.Split(resp.Version, ".") |
||||||
|
maj, min := 0, 0 |
||||||
|
if len(vs) >= 2 { |
||||||
|
maj, rerr = strconv.Atoi(vs[0]) |
||||||
|
min, rerr = strconv.Atoi(vs[1]) |
||||||
|
} |
||||||
|
if maj < 3 || (maj == 3 && min < 2) { |
||||||
|
rerr = ErrOldCluster |
||||||
|
} |
||||||
|
errc <- rerr |
||||||
|
}(ep) |
||||||
|
} |
||||||
|
// wait for success
|
||||||
|
for i := 0; i < len(c.cfg.Endpoints); i++ { |
||||||
|
if err = <-errc; err == nil { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
cancel() |
||||||
|
wg.Wait() |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// ActiveConnection returns the current in-use connection
|
||||||
|
func (c *Client) ActiveConnection() *grpc.ClientConn { return c.conn } |
||||||
|
|
||||||
|
// isHaltErr returns true if the given error and context indicate no forward
|
||||||
|
// progress can be made, even after reconnecting.
|
||||||
|
func isHaltErr(ctx context.Context, err error) bool { |
||||||
|
if ctx != nil && ctx.Err() != nil { |
||||||
|
return true |
||||||
|
} |
||||||
|
if err == nil { |
||||||
|
return false |
||||||
|
} |
||||||
|
code := grpc.Code(err) |
||||||
|
// Unavailable codes mean the system will be right back.
|
||||||
|
// (e.g., can't connect, lost leader)
|
||||||
|
// Treat Internal codes as if something failed, leaving the
|
||||||
|
// system in an inconsistent state, but retrying could make progress.
|
||||||
|
// (e.g., failed in middle of send, corrupted frame)
|
||||||
|
// TODO: are permanent Internal errors possible from grpc?
|
||||||
|
return code != codes.Unavailable && code != codes.Internal |
||||||
|
} |
||||||
|
|
||||||
|
func toErr(ctx context.Context, err error) error { |
||||||
|
if err == nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
err = rpctypes.Error(err) |
||||||
|
if _, ok := err.(rpctypes.EtcdError); ok { |
||||||
|
return err |
||||||
|
} |
||||||
|
code := grpc.Code(err) |
||||||
|
switch code { |
||||||
|
case codes.DeadlineExceeded: |
||||||
|
fallthrough |
||||||
|
case codes.Canceled: |
||||||
|
if ctx.Err() != nil { |
||||||
|
err = ctx.Err() |
||||||
|
} |
||||||
|
case codes.Unavailable: |
||||||
|
err = ErrNoAvailableEndpoints |
||||||
|
case codes.FailedPrecondition: |
||||||
|
err = grpc.ErrClientConnClosing |
||||||
|
} |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
func canceledByCaller(stopCtx context.Context, err error) bool { |
||||||
|
if stopCtx.Err() == nil || err == nil { |
||||||
|
return false |
||||||
|
} |
||||||
|
|
||||||
|
return err == context.Canceled || err == context.DeadlineExceeded |
||||||
|
} |
||||||
@ -0,0 +1,100 @@ |
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package clientv3 |
||||||
|
|
||||||
|
import ( |
||||||
|
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" |
||||||
|
"golang.org/x/net/context" |
||||||
|
"google.golang.org/grpc" |
||||||
|
) |
||||||
|
|
||||||
|
type ( |
||||||
|
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) |
||||||
|
|
||||||
|
// 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 { |
||||||
|
remote pb.ClusterClient |
||||||
|
} |
||||||
|
|
||||||
|
func NewCluster(c *Client) Cluster { |
||||||
|
return &cluster{remote: RetryClusterClient(c)} |
||||||
|
} |
||||||
|
|
||||||
|
func NewClusterFromClusterClient(remote pb.ClusterClient) Cluster { |
||||||
|
return &cluster{remote: remote} |
||||||
|
} |
||||||
|
|
||||||
|
func (c *cluster) MemberAdd(ctx context.Context, peerAddrs []string) (*MemberAddResponse, error) { |
||||||
|
r := &pb.MemberAddRequest{PeerURLs: peerAddrs} |
||||||
|
resp, err := c.remote.MemberAdd(ctx, r) |
||||||
|
if err != nil { |
||||||
|
return nil, toErr(ctx, err) |
||||||
|
} |
||||||
|
return (*MemberAddResponse)(resp), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (c *cluster) MemberRemove(ctx context.Context, id uint64) (*MemberRemoveResponse, error) { |
||||||
|
r := &pb.MemberRemoveRequest{ID: id} |
||||||
|
resp, err := c.remote.MemberRemove(ctx, r) |
||||||
|
if err != nil { |
||||||
|
return nil, toErr(ctx, err) |
||||||
|
} |
||||||
|
return (*MemberRemoveResponse)(resp), nil |
||||||
|
} |
||||||
|
|
||||||
|
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.remote.MemberUpdate(ctx, r, grpc.FailFast(false)) |
||||||
|
if err == nil { |
||||||
|
return (*MemberUpdateResponse)(resp), nil |
||||||
|
} |
||||||
|
if isHaltErr(ctx, err) { |
||||||
|
return nil, toErr(ctx, err) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (c *cluster) MemberList(ctx context.Context) (*MemberListResponse, error) { |
||||||
|
// it is safe to retry on list.
|
||||||
|
for { |
||||||
|
resp, err := c.remote.MemberList(ctx, &pb.MemberListRequest{}, grpc.FailFast(false)) |
||||||
|
if err == nil { |
||||||
|
return (*MemberListResponse)(resp), nil |
||||||
|
} |
||||||
|
if isHaltErr(ctx, err) { |
||||||
|
return nil, toErr(ctx, err) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,53 @@ |
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package clientv3 |
||||||
|
|
||||||
|
import ( |
||||||
|
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" |
||||||
|
) |
||||||
|
|
||||||
|
// CompactOp represents a compact operation.
|
||||||
|
type CompactOp struct { |
||||||
|
revision int64 |
||||||
|
physical bool |
||||||
|
} |
||||||
|
|
||||||
|
// CompactOption configures compact operation.
|
||||||
|
type CompactOption func(*CompactOp) |
||||||
|
|
||||||
|
func (op *CompactOp) applyCompactOpts(opts []CompactOption) { |
||||||
|
for _, opt := range opts { |
||||||
|
opt(op) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// OpCompact wraps slice CompactOption to create a CompactOp.
|
||||||
|
func OpCompact(rev int64, opts ...CompactOption) CompactOp { |
||||||
|
ret := CompactOp{revision: rev} |
||||||
|
ret.applyCompactOpts(opts) |
||||||
|
return ret |
||||||
|
} |
||||||
|
|
||||||
|
func (op CompactOp) toRequest() *pb.CompactionRequest { |
||||||
|
return &pb.CompactionRequest{Revision: op.revision, Physical: op.physical} |
||||||
|
} |
||||||
|
|
||||||
|
// WithCompactPhysical makes compact RPC call wait until
|
||||||
|
// the compaction is physically applied to the local database
|
||||||
|
// such that compacted entries are totally removed from the
|
||||||
|
// backend database.
|
||||||
|
func WithCompactPhysical() CompactOption { |
||||||
|
return func(op *CompactOp) { op.physical = true } |
||||||
|
} |
||||||
@ -0,0 +1,110 @@ |
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package clientv3 |
||||||
|
|
||||||
|
import ( |
||||||
|
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" |
||||||
|
) |
||||||
|
|
||||||
|
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_NOT_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} |
||||||
|
} |
||||||
|
|
||||||
|
// KeyBytes returns the byte slice holding with the comparison key.
|
||||||
|
func (cmp *Cmp) KeyBytes() []byte { return cmp.Key } |
||||||
|
|
||||||
|
// WithKeyBytes sets the byte slice for the comparison key.
|
||||||
|
func (cmp *Cmp) WithKeyBytes(key []byte) { cmp.Key = key } |
||||||
|
|
||||||
|
// ValueBytes returns the byte slice holding the comparison value, if any.
|
||||||
|
func (cmp *Cmp) ValueBytes() []byte { |
||||||
|
if tu, ok := cmp.TargetUnion.(*pb.Compare_Value); ok { |
||||||
|
return tu.Value |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// WithValueBytes sets the byte slice for the comparison's value.
|
||||||
|
func (cmp *Cmp) WithValueBytes(v []byte) { cmp.TargetUnion.(*pb.Compare_Value).Value = v } |
||||||
|
|
||||||
|
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") |
||||||
|
} |
||||||
@ -0,0 +1,54 @@ |
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package clientv3 |
||||||
|
|
||||||
|
import ( |
||||||
|
"crypto/tls" |
||||||
|
"time" |
||||||
|
|
||||||
|
"golang.org/x/net/context" |
||||||
|
"google.golang.org/grpc" |
||||||
|
) |
||||||
|
|
||||||
|
type Config struct { |
||||||
|
// Endpoints is a list of URLs.
|
||||||
|
Endpoints []string `json:"endpoints"` |
||||||
|
|
||||||
|
// AutoSyncInterval is the interval to update endpoints with its latest members.
|
||||||
|
// 0 disables auto-sync. By default auto-sync is disabled.
|
||||||
|
AutoSyncInterval time.Duration `json:"auto-sync-interval"` |
||||||
|
|
||||||
|
// DialTimeout is the timeout for failing to establish a connection.
|
||||||
|
DialTimeout time.Duration `json:"dial-timeout"` |
||||||
|
|
||||||
|
// TLS holds the client secure credentials, if any.
|
||||||
|
TLS *tls.Config |
||||||
|
|
||||||
|
// Username is a username for authentication.
|
||||||
|
Username string `json:"username"` |
||||||
|
|
||||||
|
// Password is a password for authentication.
|
||||||
|
Password string `json:"password"` |
||||||
|
|
||||||
|
// RejectOldCluster when set will refuse to create a client against an outdated cluster.
|
||||||
|
RejectOldCluster bool `json:"reject-old-cluster"` |
||||||
|
|
||||||
|
// DialOptions is a list of dial options for the grpc client (e.g., for interceptors).
|
||||||
|
DialOptions []grpc.DialOption |
||||||
|
|
||||||
|
// Context is the default client context; it can be used to cancel grpc dial out and
|
||||||
|
// other operations that do not have an explicit context.
|
||||||
|
Context context.Context |
||||||
|
} |
||||||
@ -0,0 +1,64 @@ |
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package clientv3 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/rpctypes/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 |
||||||
@ -0,0 +1,162 @@ |
|||||||
|
// Copyright 2015 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package clientv3 |
||||||
|
|
||||||
|
import ( |
||||||
|
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" |
||||||
|
"golang.org/x/net/context" |
||||||
|
"google.golang.org/grpc" |
||||||
|
) |
||||||
|
|
||||||
|
type ( |
||||||
|
CompactResponse pb.CompactionResponse |
||||||
|
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, opts ...CompactOption) (*CompactResponse, error) |
||||||
|
|
||||||
|
// 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 |
||||||
|
} |
||||||
|
|
||||||
|
func (op OpResponse) Put() *PutResponse { return op.put } |
||||||
|
func (op OpResponse) Get() *GetResponse { return op.get } |
||||||
|
func (op OpResponse) Del() *DeleteResponse { return op.del } |
||||||
|
|
||||||
|
type kv struct { |
||||||
|
remote pb.KVClient |
||||||
|
} |
||||||
|
|
||||||
|
func NewKV(c *Client) KV { |
||||||
|
return &kv{remote: RetryKVClient(c)} |
||||||
|
} |
||||||
|
|
||||||
|
func NewKVFromKVClient(remote pb.KVClient) KV { |
||||||
|
return &kv{remote: remote} |
||||||
|
} |
||||||
|
|
||||||
|
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, toErr(ctx, 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, toErr(ctx, 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, toErr(ctx, err) |
||||||
|
} |
||||||
|
|
||||||
|
func (kv *kv) Compact(ctx context.Context, rev int64, opts ...CompactOption) (*CompactResponse, error) { |
||||||
|
resp, err := kv.remote.Compact(ctx, OpCompact(rev, opts...).toRequest()) |
||||||
|
if err != nil { |
||||||
|
return nil, toErr(ctx, err) |
||||||
|
} |
||||||
|
return (*CompactResponse)(resp), 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 { |
||||||
|
resp, err := kv.do(ctx, op) |
||||||
|
if err == nil { |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
if isHaltErr(ctx, err) { |
||||||
|
return resp, toErr(ctx, err) |
||||||
|
} |
||||||
|
// do not retry on modifications
|
||||||
|
if op.isWrite() { |
||||||
|
return resp, toErr(ctx, err) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (kv *kv) do(ctx context.Context, op Op) (OpResponse, error) { |
||||||
|
var err error |
||||||
|
switch op.t { |
||||||
|
// TODO: handle other ops
|
||||||
|
case tRange: |
||||||
|
var resp *pb.RangeResponse |
||||||
|
resp, err = kv.remote.Range(ctx, op.toRangeRequest(), grpc.FailFast(false)) |
||||||
|
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), PrevKv: op.prevKV, IgnoreValue: op.ignoreValue, IgnoreLease: op.ignoreLease} |
||||||
|
resp, err = kv.remote.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, PrevKv: op.prevKV} |
||||||
|
resp, err = kv.remote.DeleteRange(ctx, r) |
||||||
|
if err == nil { |
||||||
|
return OpResponse{del: (*DeleteResponse)(resp)}, nil |
||||||
|
} |
||||||
|
default: |
||||||
|
panic("Unknown op") |
||||||
|
} |
||||||
|
return OpResponse{}, err |
||||||
|
} |
||||||
@ -0,0 +1,547 @@ |
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package clientv3 |
||||||
|
|
||||||
|
import ( |
||||||
|
"sync" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" |
||||||
|
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" |
||||||
|
"golang.org/x/net/context" |
||||||
|
"google.golang.org/grpc" |
||||||
|
"google.golang.org/grpc/metadata" |
||||||
|
) |
||||||
|
|
||||||
|
type ( |
||||||
|
LeaseRevokeResponse pb.LeaseRevokeResponse |
||||||
|
LeaseID int64 |
||||||
|
) |
||||||
|
|
||||||
|
// LeaseGrantResponse is used to convert the protobuf grant response.
|
||||||
|
type LeaseGrantResponse struct { |
||||||
|
*pb.ResponseHeader |
||||||
|
ID LeaseID |
||||||
|
TTL int64 |
||||||
|
Error string |
||||||
|
} |
||||||
|
|
||||||
|
// LeaseKeepAliveResponse is used to convert the protobuf keepalive response.
|
||||||
|
type LeaseKeepAliveResponse struct { |
||||||
|
*pb.ResponseHeader |
||||||
|
ID LeaseID |
||||||
|
TTL int64 |
||||||
|
} |
||||||
|
|
||||||
|
// LeaseTimeToLiveResponse is used to convert the protobuf lease timetolive response.
|
||||||
|
type LeaseTimeToLiveResponse struct { |
||||||
|
*pb.ResponseHeader |
||||||
|
ID LeaseID `json:"id"` |
||||||
|
|
||||||
|
// TTL is the remaining TTL in seconds for the lease; the lease will expire in under TTL+1 seconds.
|
||||||
|
TTL int64 `json:"ttl"` |
||||||
|
|
||||||
|
// GrantedTTL is the initial granted time in seconds upon lease creation/renewal.
|
||||||
|
GrantedTTL int64 `json:"granted-ttl"` |
||||||
|
|
||||||
|
// Keys is the list of keys attached to this lease.
|
||||||
|
Keys [][]byte `json:"keys"` |
||||||
|
} |
||||||
|
|
||||||
|
const ( |
||||||
|
// defaultTTL is the assumed lease TTL used for the first keepalive
|
||||||
|
// deadline before the actual TTL is known to the client.
|
||||||
|
defaultTTL = 5 * time.Second |
||||||
|
// a small buffer to store unsent lease responses.
|
||||||
|
leaseResponseChSize = 16 |
||||||
|
// NoLease is a lease ID for the absence of a lease.
|
||||||
|
NoLease LeaseID = 0 |
||||||
|
|
||||||
|
// retryConnWait is how long to wait before retrying request due to an error
|
||||||
|
retryConnWait = 500 * time.Millisecond |
||||||
|
) |
||||||
|
|
||||||
|
// ErrKeepAliveHalted is returned if client keep alive loop halts with an unexpected error.
|
||||||
|
//
|
||||||
|
// This usually means that automatic lease renewal via KeepAlive is broken, but KeepAliveOnce will still work as expected.
|
||||||
|
type ErrKeepAliveHalted struct { |
||||||
|
Reason error |
||||||
|
} |
||||||
|
|
||||||
|
func (e ErrKeepAliveHalted) Error() string { |
||||||
|
s := "etcdclient: leases keep alive halted" |
||||||
|
if e.Reason != nil { |
||||||
|
s += ": " + e.Reason.Error() |
||||||
|
} |
||||||
|
return s |
||||||
|
} |
||||||
|
|
||||||
|
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) |
||||||
|
|
||||||
|
// TimeToLive retrieves the lease information of the given lease ID.
|
||||||
|
TimeToLive(ctx context.Context, id LeaseID, opts ...LeaseOption) (*LeaseTimeToLiveResponse, 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 { |
||||||
|
mu sync.Mutex // guards all fields
|
||||||
|
|
||||||
|
// donec is closed and loopErr is set when recvKeepAliveLoop stops
|
||||||
|
donec chan struct{} |
||||||
|
loopErr error |
||||||
|
|
||||||
|
remote pb.LeaseClient |
||||||
|
|
||||||
|
stream pb.Lease_LeaseKeepAliveClient |
||||||
|
streamCancel context.CancelFunc |
||||||
|
|
||||||
|
stopCtx context.Context |
||||||
|
stopCancel context.CancelFunc |
||||||
|
|
||||||
|
keepAlives map[LeaseID]*keepAlive |
||||||
|
|
||||||
|
// firstKeepAliveTimeout is the timeout for the first keepalive request
|
||||||
|
// before the actual TTL is known to the lease client
|
||||||
|
firstKeepAliveTimeout time.Duration |
||||||
|
|
||||||
|
// firstKeepAliveOnce ensures stream starts after first KeepAlive call.
|
||||||
|
firstKeepAliveOnce sync.Once |
||||||
|
} |
||||||
|
|
||||||
|
// keepAlive multiplexes a keepalive for a lease over multiple channels
|
||||||
|
type keepAlive struct { |
||||||
|
chs []chan<- *LeaseKeepAliveResponse |
||||||
|
ctxs []context.Context |
||||||
|
// deadline is the time the keep alive channels close if no response
|
||||||
|
deadline time.Time |
||||||
|
// nextKeepAlive is when to send the next keep alive message
|
||||||
|
nextKeepAlive time.Time |
||||||
|
// donec is closed on lease revoke, expiration, or cancel.
|
||||||
|
donec chan struct{} |
||||||
|
} |
||||||
|
|
||||||
|
func NewLease(c *Client) Lease { |
||||||
|
return NewLeaseFromLeaseClient(RetryLeaseClient(c), c.cfg.DialTimeout+time.Second) |
||||||
|
} |
||||||
|
|
||||||
|
func NewLeaseFromLeaseClient(remote pb.LeaseClient, keepAliveTimeout time.Duration) Lease { |
||||||
|
l := &lessor{ |
||||||
|
donec: make(chan struct{}), |
||||||
|
keepAlives: make(map[LeaseID]*keepAlive), |
||||||
|
remote: remote, |
||||||
|
firstKeepAliveTimeout: keepAliveTimeout, |
||||||
|
} |
||||||
|
if l.firstKeepAliveTimeout == time.Second { |
||||||
|
l.firstKeepAliveTimeout = defaultTTL |
||||||
|
} |
||||||
|
reqLeaderCtx := WithRequireLeader(context.Background()) |
||||||
|
l.stopCtx, l.stopCancel = context.WithCancel(reqLeaderCtx) |
||||||
|
return l |
||||||
|
} |
||||||
|
|
||||||
|
func (l *lessor) Grant(ctx context.Context, ttl int64) (*LeaseGrantResponse, error) { |
||||||
|
for { |
||||||
|
r := &pb.LeaseGrantRequest{TTL: ttl} |
||||||
|
resp, err := l.remote.LeaseGrant(ctx, r) |
||||||
|
if err == nil { |
||||||
|
gresp := &LeaseGrantResponse{ |
||||||
|
ResponseHeader: resp.GetHeader(), |
||||||
|
ID: LeaseID(resp.ID), |
||||||
|
TTL: resp.TTL, |
||||||
|
Error: resp.Error, |
||||||
|
} |
||||||
|
return gresp, nil |
||||||
|
} |
||||||
|
if isHaltErr(ctx, err) { |
||||||
|
return nil, toErr(ctx, err) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (l *lessor) Revoke(ctx context.Context, id LeaseID) (*LeaseRevokeResponse, error) { |
||||||
|
for { |
||||||
|
r := &pb.LeaseRevokeRequest{ID: int64(id)} |
||||||
|
resp, err := l.remote.LeaseRevoke(ctx, r) |
||||||
|
|
||||||
|
if err == nil { |
||||||
|
return (*LeaseRevokeResponse)(resp), nil |
||||||
|
} |
||||||
|
if isHaltErr(ctx, err) { |
||||||
|
return nil, toErr(ctx, err) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (l *lessor) TimeToLive(ctx context.Context, id LeaseID, opts ...LeaseOption) (*LeaseTimeToLiveResponse, error) { |
||||||
|
for { |
||||||
|
r := toLeaseTimeToLiveRequest(id, opts...) |
||||||
|
resp, err := l.remote.LeaseTimeToLive(ctx, r, grpc.FailFast(false)) |
||||||
|
if err == nil { |
||||||
|
gresp := &LeaseTimeToLiveResponse{ |
||||||
|
ResponseHeader: resp.GetHeader(), |
||||||
|
ID: LeaseID(resp.ID), |
||||||
|
TTL: resp.TTL, |
||||||
|
GrantedTTL: resp.GrantedTTL, |
||||||
|
Keys: resp.Keys, |
||||||
|
} |
||||||
|
return gresp, nil |
||||||
|
} |
||||||
|
if isHaltErr(ctx, err) { |
||||||
|
return nil, toErr(ctx, err) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (l *lessor) KeepAlive(ctx context.Context, id LeaseID) (<-chan *LeaseKeepAliveResponse, error) { |
||||||
|
ch := make(chan *LeaseKeepAliveResponse, leaseResponseChSize) |
||||||
|
|
||||||
|
l.mu.Lock() |
||||||
|
// ensure that recvKeepAliveLoop is still running
|
||||||
|
select { |
||||||
|
case <-l.donec: |
||||||
|
err := l.loopErr |
||||||
|
l.mu.Unlock() |
||||||
|
close(ch) |
||||||
|
return ch, ErrKeepAliveHalted{Reason: err} |
||||||
|
default: |
||||||
|
} |
||||||
|
ka, ok := l.keepAlives[id] |
||||||
|
if !ok { |
||||||
|
// create fresh keep alive
|
||||||
|
ka = &keepAlive{ |
||||||
|
chs: []chan<- *LeaseKeepAliveResponse{ch}, |
||||||
|
ctxs: []context.Context{ctx}, |
||||||
|
deadline: time.Now().Add(l.firstKeepAliveTimeout), |
||||||
|
nextKeepAlive: 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) |
||||||
|
l.firstKeepAliveOnce.Do(func() { |
||||||
|
go l.recvKeepAliveLoop() |
||||||
|
go l.deadlineLoop() |
||||||
|
}) |
||||||
|
|
||||||
|
return ch, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (l *lessor) KeepAliveOnce(ctx context.Context, id LeaseID) (*LeaseKeepAliveResponse, error) { |
||||||
|
for { |
||||||
|
resp, err := l.keepAliveOnce(ctx, id) |
||||||
|
if err == nil { |
||||||
|
if resp.TTL <= 0 { |
||||||
|
err = rpctypes.ErrLeaseNotFound |
||||||
|
} |
||||||
|
return resp, err |
||||||
|
} |
||||||
|
if isHaltErr(ctx, err) { |
||||||
|
return nil, toErr(ctx, err) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (l *lessor) Close() error { |
||||||
|
l.stopCancel() |
||||||
|
// close for synchronous teardown if stream goroutines never launched
|
||||||
|
l.firstKeepAliveOnce.Do(func() { close(l.donec) }) |
||||||
|
<-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) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// closeRequireLeader scans all keep alives for ctxs that have require leader
|
||||||
|
// and closes the associated channels.
|
||||||
|
func (l *lessor) closeRequireLeader() { |
||||||
|
l.mu.Lock() |
||||||
|
defer l.mu.Unlock() |
||||||
|
for _, ka := range l.keepAlives { |
||||||
|
reqIdxs := 0 |
||||||
|
// find all required leader channels, close, mark as nil
|
||||||
|
for i, ctx := range ka.ctxs { |
||||||
|
md, ok := metadata.FromContext(ctx) |
||||||
|
if !ok { |
||||||
|
continue |
||||||
|
} |
||||||
|
ks := md[rpctypes.MetadataRequireLeaderKey] |
||||||
|
if len(ks) < 1 || ks[0] != rpctypes.MetadataHasLeader { |
||||||
|
continue |
||||||
|
} |
||||||
|
close(ka.chs[i]) |
||||||
|
ka.chs[i] = nil |
||||||
|
reqIdxs++ |
||||||
|
} |
||||||
|
if reqIdxs == 0 { |
||||||
|
continue |
||||||
|
} |
||||||
|
// remove all channels that required a leader from keepalive
|
||||||
|
newChs := make([]chan<- *LeaseKeepAliveResponse, len(ka.chs)-reqIdxs) |
||||||
|
newCtxs := make([]context.Context, len(newChs)) |
||||||
|
newIdx := 0 |
||||||
|
for i := range ka.chs { |
||||||
|
if ka.chs[i] == nil { |
||||||
|
continue |
||||||
|
} |
||||||
|
newChs[newIdx], newCtxs[newIdx] = ka.chs[i], ka.ctxs[newIdx] |
||||||
|
newIdx++ |
||||||
|
} |
||||||
|
ka.chs, ka.ctxs = newChs, newCtxs |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (l *lessor) keepAliveOnce(ctx context.Context, id LeaseID) (*LeaseKeepAliveResponse, error) { |
||||||
|
cctx, cancel := context.WithCancel(ctx) |
||||||
|
defer cancel() |
||||||
|
|
||||||
|
stream, err := l.remote.LeaseKeepAlive(cctx, grpc.FailFast(false)) |
||||||
|
if err != nil { |
||||||
|
return nil, toErr(ctx, err) |
||||||
|
} |
||||||
|
|
||||||
|
err = stream.Send(&pb.LeaseKeepAliveRequest{ID: int64(id)}) |
||||||
|
if err != nil { |
||||||
|
return nil, toErr(ctx, err) |
||||||
|
} |
||||||
|
|
||||||
|
resp, rerr := stream.Recv() |
||||||
|
if rerr != nil { |
||||||
|
return nil, toErr(ctx, rerr) |
||||||
|
} |
||||||
|
|
||||||
|
karesp := &LeaseKeepAliveResponse{ |
||||||
|
ResponseHeader: resp.GetHeader(), |
||||||
|
ID: LeaseID(resp.ID), |
||||||
|
TTL: resp.TTL, |
||||||
|
} |
||||||
|
return karesp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (l *lessor) recvKeepAliveLoop() (gerr error) { |
||||||
|
defer func() { |
||||||
|
l.mu.Lock() |
||||||
|
close(l.donec) |
||||||
|
l.loopErr = gerr |
||||||
|
for _, ka := range l.keepAlives { |
||||||
|
ka.Close() |
||||||
|
} |
||||||
|
l.keepAlives = make(map[LeaseID]*keepAlive) |
||||||
|
l.mu.Unlock() |
||||||
|
}() |
||||||
|
|
||||||
|
for { |
||||||
|
stream, err := l.resetRecv() |
||||||
|
if err != nil { |
||||||
|
if canceledByCaller(l.stopCtx, err) { |
||||||
|
return err |
||||||
|
} |
||||||
|
} else { |
||||||
|
for { |
||||||
|
resp, err := stream.Recv() |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
if canceledByCaller(l.stopCtx, err) { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
if toErr(l.stopCtx, err) == rpctypes.ErrNoLeader { |
||||||
|
l.closeRequireLeader() |
||||||
|
} |
||||||
|
break |
||||||
|
} |
||||||
|
|
||||||
|
l.recvKeepAlive(resp) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
select { |
||||||
|
case <-time.After(retryConnWait): |
||||||
|
continue |
||||||
|
case <-l.stopCtx.Done(): |
||||||
|
return l.stopCtx.Err() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// resetRecv opens a new lease stream and starts sending LeaseKeepAliveRequests
|
||||||
|
func (l *lessor) resetRecv() (pb.Lease_LeaseKeepAliveClient, error) { |
||||||
|
sctx, cancel := context.WithCancel(l.stopCtx) |
||||||
|
stream, err := l.remote.LeaseKeepAlive(sctx, grpc.FailFast(false)) |
||||||
|
if err != nil { |
||||||
|
cancel() |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
l.mu.Lock() |
||||||
|
defer l.mu.Unlock() |
||||||
|
if l.stream != nil && l.streamCancel != nil { |
||||||
|
l.streamCancel() |
||||||
|
} |
||||||
|
|
||||||
|
l.streamCancel = cancel |
||||||
|
l.stream = stream |
||||||
|
|
||||||
|
go l.sendKeepAliveLoop(stream) |
||||||
|
return stream, nil |
||||||
|
} |
||||||
|
|
||||||
|
// recvKeepAlive updates a lease based on its LeaseKeepAliveResponse
|
||||||
|
func (l *lessor) recvKeepAlive(resp *pb.LeaseKeepAliveResponse) { |
||||||
|
karesp := &LeaseKeepAliveResponse{ |
||||||
|
ResponseHeader: resp.GetHeader(), |
||||||
|
ID: LeaseID(resp.ID), |
||||||
|
TTL: resp.TTL, |
||||||
|
} |
||||||
|
|
||||||
|
l.mu.Lock() |
||||||
|
defer l.mu.Unlock() |
||||||
|
|
||||||
|
ka, ok := l.keepAlives[karesp.ID] |
||||||
|
if !ok { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if karesp.TTL <= 0 { |
||||||
|
// lease expired; close all keep alive channels
|
||||||
|
delete(l.keepAlives, karesp.ID) |
||||||
|
ka.Close() |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// send update to all channels
|
||||||
|
nextKeepAlive := time.Now().Add((time.Duration(karesp.TTL) * time.Second) / 3.0) |
||||||
|
ka.deadline = time.Now().Add(time.Duration(karesp.TTL) * time.Second) |
||||||
|
for _, ch := range ka.chs { |
||||||
|
select { |
||||||
|
case ch <- karesp: |
||||||
|
ka.nextKeepAlive = nextKeepAlive |
||||||
|
default: |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// deadlineLoop reaps any keep alive channels that have not received a response
|
||||||
|
// within the lease TTL
|
||||||
|
func (l *lessor) deadlineLoop() { |
||||||
|
for { |
||||||
|
select { |
||||||
|
case <-time.After(time.Second): |
||||||
|
case <-l.donec: |
||||||
|
return |
||||||
|
} |
||||||
|
now := time.Now() |
||||||
|
l.mu.Lock() |
||||||
|
for id, ka := range l.keepAlives { |
||||||
|
if ka.deadline.Before(now) { |
||||||
|
// waited too long for response; lease may be expired
|
||||||
|
ka.Close() |
||||||
|
delete(l.keepAlives, id) |
||||||
|
} |
||||||
|
} |
||||||
|
l.mu.Unlock() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// sendKeepAliveLoop sends LeaseKeepAliveRequests for the lifetime of a lease stream
|
||||||
|
func (l *lessor) sendKeepAliveLoop(stream pb.Lease_LeaseKeepAliveClient) { |
||||||
|
for { |
||||||
|
var tosend []LeaseID |
||||||
|
|
||||||
|
now := time.Now() |
||||||
|
l.mu.Lock() |
||||||
|
for id, ka := range l.keepAlives { |
||||||
|
if ka.nextKeepAlive.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 |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
select { |
||||||
|
case <-time.After(500 * time.Millisecond): |
||||||
|
case <-stream.Context().Done(): |
||||||
|
return |
||||||
|
case <-l.donec: |
||||||
|
return |
||||||
|
case <-l.stopCtx.Done(): |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (ka *keepAlive) Close() { |
||||||
|
close(ka.donec) |
||||||
|
for _, ch := range ka.chs { |
||||||
|
close(ch) |
||||||
|
} |
||||||
|
} |
||||||
@ -0,0 +1,82 @@ |
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package clientv3 |
||||||
|
|
||||||
|
import ( |
||||||
|
"io/ioutil" |
||||||
|
"log" |
||||||
|
"sync" |
||||||
|
|
||||||
|
"google.golang.org/grpc/grpclog" |
||||||
|
) |
||||||
|
|
||||||
|
// Logger is the logger used by client library.
|
||||||
|
// It implements grpclog.Logger interface.
|
||||||
|
type Logger grpclog.Logger |
||||||
|
|
||||||
|
var ( |
||||||
|
logger settableLogger |
||||||
|
) |
||||||
|
|
||||||
|
type settableLogger struct { |
||||||
|
l grpclog.Logger |
||||||
|
mu sync.RWMutex |
||||||
|
} |
||||||
|
|
||||||
|
func init() { |
||||||
|
// disable client side logs by default
|
||||||
|
logger.mu.Lock() |
||||||
|
logger.l = log.New(ioutil.Discard, "", 0) |
||||||
|
|
||||||
|
// logger has to override the grpclog at initialization so that
|
||||||
|
// any changes to the grpclog go through logger with locking
|
||||||
|
// instead of through SetLogger
|
||||||
|
//
|
||||||
|
// now updates only happen through settableLogger.set
|
||||||
|
grpclog.SetLogger(&logger) |
||||||
|
logger.mu.Unlock() |
||||||
|
} |
||||||
|
|
||||||
|
// SetLogger sets client-side Logger. By default, logs are disabled.
|
||||||
|
func SetLogger(l Logger) { |
||||||
|
logger.set(l) |
||||||
|
} |
||||||
|
|
||||||
|
// GetLogger returns the current logger.
|
||||||
|
func GetLogger() Logger { |
||||||
|
return logger.get() |
||||||
|
} |
||||||
|
|
||||||
|
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...) } |
||||||
@ -0,0 +1,182 @@ |
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package clientv3 |
||||||
|
|
||||||
|
import ( |
||||||
|
"io" |
||||||
|
|
||||||
|
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 endpoint.
|
||||||
|
Status(ctx context.Context, endpoint string) (*StatusResponse, error) |
||||||
|
|
||||||
|
// Snapshot provides a reader for a snapshot of a backend.
|
||||||
|
Snapshot(ctx context.Context) (io.ReadCloser, error) |
||||||
|
} |
||||||
|
|
||||||
|
type maintenance struct { |
||||||
|
dial func(endpoint string) (pb.MaintenanceClient, func(), error) |
||||||
|
remote pb.MaintenanceClient |
||||||
|
} |
||||||
|
|
||||||
|
func NewMaintenance(c *Client) Maintenance { |
||||||
|
return &maintenance{ |
||||||
|
dial: func(endpoint string) (pb.MaintenanceClient, func(), error) { |
||||||
|
conn, err := c.dial(endpoint) |
||||||
|
if err != nil { |
||||||
|
return nil, nil, err |
||||||
|
} |
||||||
|
cancel := func() { conn.Close() } |
||||||
|
return pb.NewMaintenanceClient(conn), cancel, nil |
||||||
|
}, |
||||||
|
remote: pb.NewMaintenanceClient(c.conn), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func NewMaintenanceFromMaintenanceClient(remote pb.MaintenanceClient) Maintenance { |
||||||
|
return &maintenance{ |
||||||
|
dial: func(string) (pb.MaintenanceClient, func(), error) { |
||||||
|
return remote, func() {}, nil |
||||||
|
}, |
||||||
|
remote: remote, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
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.remote.Alarm(ctx, req, grpc.FailFast(false)) |
||||||
|
if err == nil { |
||||||
|
return (*AlarmResponse)(resp), nil |
||||||
|
} |
||||||
|
if isHaltErr(ctx, err) { |
||||||
|
return nil, toErr(ctx, 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, toErr(ctx, err) |
||||||
|
} |
||||||
|
ret := AlarmResponse{} |
||||||
|
for _, am := range ar.Alarms { |
||||||
|
dresp, derr := m.AlarmDisarm(ctx, (*AlarmMember)(am)) |
||||||
|
if derr != nil { |
||||||
|
return nil, toErr(ctx, derr) |
||||||
|
} |
||||||
|
ret.Alarms = append(ret.Alarms, dresp.Alarms...) |
||||||
|
} |
||||||
|
return &ret, nil |
||||||
|
} |
||||||
|
|
||||||
|
resp, err := m.remote.Alarm(ctx, req, grpc.FailFast(false)) |
||||||
|
if err == nil { |
||||||
|
return (*AlarmResponse)(resp), nil |
||||||
|
} |
||||||
|
return nil, toErr(ctx, err) |
||||||
|
} |
||||||
|
|
||||||
|
func (m *maintenance) Defragment(ctx context.Context, endpoint string) (*DefragmentResponse, error) { |
||||||
|
remote, cancel, err := m.dial(endpoint) |
||||||
|
if err != nil { |
||||||
|
return nil, toErr(ctx, err) |
||||||
|
} |
||||||
|
defer cancel() |
||||||
|
resp, err := remote.Defragment(ctx, &pb.DefragmentRequest{}, grpc.FailFast(false)) |
||||||
|
if err != nil { |
||||||
|
return nil, toErr(ctx, err) |
||||||
|
} |
||||||
|
return (*DefragmentResponse)(resp), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (m *maintenance) Status(ctx context.Context, endpoint string) (*StatusResponse, error) { |
||||||
|
remote, cancel, err := m.dial(endpoint) |
||||||
|
if err != nil { |
||||||
|
return nil, toErr(ctx, err) |
||||||
|
} |
||||||
|
defer cancel() |
||||||
|
resp, err := remote.Status(ctx, &pb.StatusRequest{}, grpc.FailFast(false)) |
||||||
|
if err != nil { |
||||||
|
return nil, toErr(ctx, err) |
||||||
|
} |
||||||
|
return (*StatusResponse)(resp), nil |
||||||
|
} |
||||||
|
|
||||||
|
func (m *maintenance) Snapshot(ctx context.Context) (io.ReadCloser, error) { |
||||||
|
ss, err := m.remote.Snapshot(ctx, &pb.SnapshotRequest{}, grpc.FailFast(false)) |
||||||
|
if err != nil { |
||||||
|
return nil, toErr(ctx, err) |
||||||
|
} |
||||||
|
|
||||||
|
pr, pw := io.Pipe() |
||||||
|
go func() { |
||||||
|
for { |
||||||
|
resp, err := ss.Recv() |
||||||
|
if err != nil { |
||||||
|
pw.CloseWithError(err) |
||||||
|
return |
||||||
|
} |
||||||
|
if resp == nil && err == nil { |
||||||
|
break |
||||||
|
} |
||||||
|
if _, werr := pw.Write(resp.Blob); werr != nil { |
||||||
|
pw.CloseWithError(werr) |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
pw.Close() |
||||||
|
}() |
||||||
|
return pr, nil |
||||||
|
} |
||||||
@ -0,0 +1,43 @@ |
|||||||
|
// Copyright 2017 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package namespace is a clientv3 wrapper that translates all keys to begin
|
||||||
|
// with a given prefix.
|
||||||
|
//
|
||||||
|
// First, create a client:
|
||||||
|
//
|
||||||
|
// cli, err := clientv3.New(clientv3.Config{Endpoints: []string{"localhost:2379"}})
|
||||||
|
// if err != nil {
|
||||||
|
// // handle error!
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// Next, override the client interfaces:
|
||||||
|
//
|
||||||
|
// unprefixedKV := cli.KV
|
||||||
|
// cli.KV = namespace.NewKV(cli.KV, "my-prefix/")
|
||||||
|
// cli.Watcher = namespace.NewWatcher(cli.Watcher, "my-prefix/")
|
||||||
|
// cli.Lease = namespace.NewLease(cli.Lease, "my-prefix/")
|
||||||
|
//
|
||||||
|
// Now calls using 'cli' will namespace / prefix all keys with "my-prefix/":
|
||||||
|
//
|
||||||
|
// cli.Put(context.TODO(), "abc", "123")
|
||||||
|
// resp, _ := unprefixedKV.Get(context.TODO(), "my-prefix/abc")
|
||||||
|
// fmt.Printf("%s\n", resp.Kvs[0].Value)
|
||||||
|
// // Output: 123
|
||||||
|
// unprefixedKV.Put(context.TODO(), "my-prefix/abc", "456")
|
||||||
|
// resp, _ = cli.Get("abc")
|
||||||
|
// fmt.Printf("%s\n", resp.Kvs[0].Value)
|
||||||
|
// // Output: 456
|
||||||
|
//
|
||||||
|
package namespace |
||||||
@ -0,0 +1,189 @@ |
|||||||
|
// Copyright 2017 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package namespace |
||||||
|
|
||||||
|
import ( |
||||||
|
"golang.org/x/net/context" |
||||||
|
|
||||||
|
"github.com/coreos/etcd/clientv3" |
||||||
|
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" |
||||||
|
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" |
||||||
|
) |
||||||
|
|
||||||
|
type kvPrefix struct { |
||||||
|
clientv3.KV |
||||||
|
pfx string |
||||||
|
} |
||||||
|
|
||||||
|
// NewKV wraps a KV instance so that all requests
|
||||||
|
// are prefixed with a given string.
|
||||||
|
func NewKV(kv clientv3.KV, prefix string) clientv3.KV { |
||||||
|
return &kvPrefix{kv, prefix} |
||||||
|
} |
||||||
|
|
||||||
|
func (kv *kvPrefix) Put(ctx context.Context, key, val string, opts ...clientv3.OpOption) (*clientv3.PutResponse, error) { |
||||||
|
if len(key) == 0 { |
||||||
|
return nil, rpctypes.ErrEmptyKey |
||||||
|
} |
||||||
|
op := kv.prefixOp(clientv3.OpPut(key, val, opts...)) |
||||||
|
r, err := kv.KV.Do(ctx, op) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
put := r.Put() |
||||||
|
kv.unprefixPutResponse(put) |
||||||
|
return put, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (kv *kvPrefix) Get(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.GetResponse, error) { |
||||||
|
if len(key) == 0 { |
||||||
|
return nil, rpctypes.ErrEmptyKey |
||||||
|
} |
||||||
|
r, err := kv.KV.Do(ctx, kv.prefixOp(clientv3.OpGet(key, opts...))) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
get := r.Get() |
||||||
|
kv.unprefixGetResponse(get) |
||||||
|
return get, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (kv *kvPrefix) Delete(ctx context.Context, key string, opts ...clientv3.OpOption) (*clientv3.DeleteResponse, error) { |
||||||
|
if len(key) == 0 { |
||||||
|
return nil, rpctypes.ErrEmptyKey |
||||||
|
} |
||||||
|
r, err := kv.KV.Do(ctx, kv.prefixOp(clientv3.OpDelete(key, opts...))) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
del := r.Del() |
||||||
|
kv.unprefixDeleteResponse(del) |
||||||
|
return del, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (kv *kvPrefix) Do(ctx context.Context, op clientv3.Op) (clientv3.OpResponse, error) { |
||||||
|
if len(op.KeyBytes()) == 0 { |
||||||
|
return clientv3.OpResponse{}, rpctypes.ErrEmptyKey |
||||||
|
} |
||||||
|
r, err := kv.KV.Do(ctx, kv.prefixOp(op)) |
||||||
|
if err != nil { |
||||||
|
return r, err |
||||||
|
} |
||||||
|
switch { |
||||||
|
case r.Get() != nil: |
||||||
|
kv.unprefixGetResponse(r.Get()) |
||||||
|
case r.Put() != nil: |
||||||
|
kv.unprefixPutResponse(r.Put()) |
||||||
|
case r.Del() != nil: |
||||||
|
kv.unprefixDeleteResponse(r.Del()) |
||||||
|
} |
||||||
|
return r, nil |
||||||
|
} |
||||||
|
|
||||||
|
type txnPrefix struct { |
||||||
|
clientv3.Txn |
||||||
|
kv *kvPrefix |
||||||
|
} |
||||||
|
|
||||||
|
func (kv *kvPrefix) Txn(ctx context.Context) clientv3.Txn { |
||||||
|
return &txnPrefix{kv.KV.Txn(ctx), kv} |
||||||
|
} |
||||||
|
|
||||||
|
func (txn *txnPrefix) If(cs ...clientv3.Cmp) clientv3.Txn { |
||||||
|
newCmps := make([]clientv3.Cmp, len(cs)) |
||||||
|
for i := range cs { |
||||||
|
newCmps[i] = cs[i] |
||||||
|
pfxKey, _ := txn.kv.prefixInterval(cs[i].KeyBytes(), nil) |
||||||
|
newCmps[i].WithKeyBytes(pfxKey) |
||||||
|
} |
||||||
|
txn.Txn = txn.Txn.If(newCmps...) |
||||||
|
return txn |
||||||
|
} |
||||||
|
|
||||||
|
func (txn *txnPrefix) Then(ops ...clientv3.Op) clientv3.Txn { |
||||||
|
newOps := make([]clientv3.Op, len(ops)) |
||||||
|
for i := range ops { |
||||||
|
newOps[i] = txn.kv.prefixOp(ops[i]) |
||||||
|
} |
||||||
|
txn.Txn = txn.Txn.Then(newOps...) |
||||||
|
return txn |
||||||
|
} |
||||||
|
|
||||||
|
func (txn *txnPrefix) Else(ops ...clientv3.Op) clientv3.Txn { |
||||||
|
newOps := make([]clientv3.Op, len(ops)) |
||||||
|
for i := range ops { |
||||||
|
newOps[i] = txn.kv.prefixOp(ops[i]) |
||||||
|
} |
||||||
|
txn.Txn = txn.Txn.Else(newOps...) |
||||||
|
return txn |
||||||
|
} |
||||||
|
|
||||||
|
func (txn *txnPrefix) Commit() (*clientv3.TxnResponse, error) { |
||||||
|
resp, err := txn.Txn.Commit() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
txn.kv.unprefixTxnResponse(resp) |
||||||
|
return resp, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (kv *kvPrefix) prefixOp(op clientv3.Op) clientv3.Op { |
||||||
|
begin, end := kv.prefixInterval(op.KeyBytes(), op.RangeBytes()) |
||||||
|
op.WithKeyBytes(begin) |
||||||
|
op.WithRangeBytes(end) |
||||||
|
return op |
||||||
|
} |
||||||
|
|
||||||
|
func (kv *kvPrefix) unprefixGetResponse(resp *clientv3.GetResponse) { |
||||||
|
for i := range resp.Kvs { |
||||||
|
resp.Kvs[i].Key = resp.Kvs[i].Key[len(kv.pfx):] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (kv *kvPrefix) unprefixPutResponse(resp *clientv3.PutResponse) { |
||||||
|
if resp.PrevKv != nil { |
||||||
|
resp.PrevKv.Key = resp.PrevKv.Key[len(kv.pfx):] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (kv *kvPrefix) unprefixDeleteResponse(resp *clientv3.DeleteResponse) { |
||||||
|
for i := range resp.PrevKvs { |
||||||
|
resp.PrevKvs[i].Key = resp.PrevKvs[i].Key[len(kv.pfx):] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (kv *kvPrefix) unprefixTxnResponse(resp *clientv3.TxnResponse) { |
||||||
|
for _, r := range resp.Responses { |
||||||
|
switch tv := r.Response.(type) { |
||||||
|
case *pb.ResponseOp_ResponseRange: |
||||||
|
if tv.ResponseRange != nil { |
||||||
|
kv.unprefixGetResponse((*clientv3.GetResponse)(tv.ResponseRange)) |
||||||
|
} |
||||||
|
case *pb.ResponseOp_ResponsePut: |
||||||
|
if tv.ResponsePut != nil { |
||||||
|
kv.unprefixPutResponse((*clientv3.PutResponse)(tv.ResponsePut)) |
||||||
|
} |
||||||
|
case *pb.ResponseOp_ResponseDeleteRange: |
||||||
|
if tv.ResponseDeleteRange != nil { |
||||||
|
kv.unprefixDeleteResponse((*clientv3.DeleteResponse)(tv.ResponseDeleteRange)) |
||||||
|
} |
||||||
|
default: |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (p *kvPrefix) prefixInterval(key, end []byte) (pfxKey []byte, pfxEnd []byte) { |
||||||
|
return prefixInterval(p.pfx, key, end) |
||||||
|
} |
||||||
@ -0,0 +1,58 @@ |
|||||||
|
// Copyright 2017 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package namespace |
||||||
|
|
||||||
|
import ( |
||||||
|
"bytes" |
||||||
|
|
||||||
|
"golang.org/x/net/context" |
||||||
|
|
||||||
|
"github.com/coreos/etcd/clientv3" |
||||||
|
) |
||||||
|
|
||||||
|
type leasePrefix struct { |
||||||
|
clientv3.Lease |
||||||
|
pfx []byte |
||||||
|
} |
||||||
|
|
||||||
|
// NewLease wraps a Lease interface to filter for only keys with a prefix
|
||||||
|
// and remove that prefix when fetching attached keys through TimeToLive.
|
||||||
|
func NewLease(l clientv3.Lease, prefix string) clientv3.Lease { |
||||||
|
return &leasePrefix{l, []byte(prefix)} |
||||||
|
} |
||||||
|
|
||||||
|
func (l *leasePrefix) TimeToLive(ctx context.Context, id clientv3.LeaseID, opts ...clientv3.LeaseOption) (*clientv3.LeaseTimeToLiveResponse, error) { |
||||||
|
resp, err := l.Lease.TimeToLive(ctx, id, opts...) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
if len(resp.Keys) > 0 { |
||||||
|
var outKeys [][]byte |
||||||
|
for i := range resp.Keys { |
||||||
|
if len(resp.Keys[i]) < len(l.pfx) { |
||||||
|
// too short
|
||||||
|
continue |
||||||
|
} |
||||||
|
if !bytes.Equal(resp.Keys[i][:len(l.pfx)], l.pfx) { |
||||||
|
// doesn't match prefix
|
||||||
|
continue |
||||||
|
} |
||||||
|
// strip prefix
|
||||||
|
outKeys = append(outKeys, resp.Keys[i][len(l.pfx):]) |
||||||
|
} |
||||||
|
resp.Keys = outKeys |
||||||
|
} |
||||||
|
return resp, nil |
||||||
|
} |
||||||
@ -0,0 +1,42 @@ |
|||||||
|
// Copyright 2017 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package namespace |
||||||
|
|
||||||
|
func prefixInterval(pfx string, key, end []byte) (pfxKey []byte, pfxEnd []byte) { |
||||||
|
pfxKey = make([]byte, len(pfx)+len(key)) |
||||||
|
copy(pfxKey[copy(pfxKey, pfx):], key) |
||||||
|
|
||||||
|
if len(end) == 1 && end[0] == 0 { |
||||||
|
// the edge of the keyspace
|
||||||
|
pfxEnd = make([]byte, len(pfx)) |
||||||
|
copy(pfxEnd, pfx) |
||||||
|
ok := false |
||||||
|
for i := len(pfxEnd) - 1; i >= 0; i-- { |
||||||
|
if pfxEnd[i]++; pfxEnd[i] != 0 { |
||||||
|
ok = true |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
if !ok { |
||||||
|
// 0xff..ff => 0x00
|
||||||
|
pfxEnd = []byte{0} |
||||||
|
} |
||||||
|
} else if len(end) >= 1 { |
||||||
|
pfxEnd = make([]byte, len(pfx)+len(end)) |
||||||
|
copy(pfxEnd[copy(pfxEnd, pfx):], end) |
||||||
|
} |
||||||
|
|
||||||
|
return pfxKey, pfxEnd |
||||||
|
} |
||||||
@ -0,0 +1,84 @@ |
|||||||
|
// Copyright 2017 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package namespace |
||||||
|
|
||||||
|
import ( |
||||||
|
"sync" |
||||||
|
|
||||||
|
"golang.org/x/net/context" |
||||||
|
|
||||||
|
"github.com/coreos/etcd/clientv3" |
||||||
|
) |
||||||
|
|
||||||
|
type watcherPrefix struct { |
||||||
|
clientv3.Watcher |
||||||
|
pfx string |
||||||
|
|
||||||
|
wg sync.WaitGroup |
||||||
|
stopc chan struct{} |
||||||
|
stopOnce sync.Once |
||||||
|
} |
||||||
|
|
||||||
|
// NewWatcher wraps a Watcher instance so that all Watch requests
|
||||||
|
// are prefixed with a given string and all Watch responses have
|
||||||
|
// the prefix removed.
|
||||||
|
func NewWatcher(w clientv3.Watcher, prefix string) clientv3.Watcher { |
||||||
|
return &watcherPrefix{Watcher: w, pfx: prefix, stopc: make(chan struct{})} |
||||||
|
} |
||||||
|
|
||||||
|
func (w *watcherPrefix) Watch(ctx context.Context, key string, opts ...clientv3.OpOption) clientv3.WatchChan { |
||||||
|
// since OpOption is opaque, determine range for prefixing through an OpGet
|
||||||
|
op := clientv3.OpGet(key, opts...) |
||||||
|
end := op.RangeBytes() |
||||||
|
pfxBegin, pfxEnd := prefixInterval(w.pfx, []byte(key), end) |
||||||
|
if pfxEnd != nil { |
||||||
|
opts = append(opts, clientv3.WithRange(string(pfxEnd))) |
||||||
|
} |
||||||
|
|
||||||
|
wch := w.Watcher.Watch(ctx, string(pfxBegin), opts...) |
||||||
|
|
||||||
|
// translate watch events from prefixed to unprefixed
|
||||||
|
pfxWch := make(chan clientv3.WatchResponse) |
||||||
|
w.wg.Add(1) |
||||||
|
go func() { |
||||||
|
defer func() { |
||||||
|
close(pfxWch) |
||||||
|
w.wg.Done() |
||||||
|
}() |
||||||
|
for wr := range wch { |
||||||
|
for i := range wr.Events { |
||||||
|
wr.Events[i].Kv.Key = wr.Events[i].Kv.Key[len(w.pfx):] |
||||||
|
if wr.Events[i].PrevKv != nil { |
||||||
|
wr.Events[i].PrevKv.Key = wr.Events[i].Kv.Key |
||||||
|
} |
||||||
|
} |
||||||
|
select { |
||||||
|
case pfxWch <- wr: |
||||||
|
case <-ctx.Done(): |
||||||
|
return |
||||||
|
case <-w.stopc: |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
}() |
||||||
|
return pfxWch |
||||||
|
} |
||||||
|
|
||||||
|
func (w *watcherPrefix) Close() error { |
||||||
|
err := w.Watcher.Close() |
||||||
|
w.stopOnce.Do(func() { close(w.stopc) }) |
||||||
|
w.wg.Wait() |
||||||
|
return err |
||||||
|
} |
||||||
@ -0,0 +1,437 @@ |
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package clientv3 |
||||||
|
|
||||||
|
import pb "github.com/coreos/etcd/etcdserver/etcdserverpb" |
||||||
|
|
||||||
|
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 |
||||||
|
keysOnly bool |
||||||
|
countOnly bool |
||||||
|
minModRev int64 |
||||||
|
maxModRev int64 |
||||||
|
minCreateRev int64 |
||||||
|
maxCreateRev int64 |
||||||
|
|
||||||
|
// for range, watch
|
||||||
|
rev int64 |
||||||
|
|
||||||
|
// for watch, put, delete
|
||||||
|
prevKV bool |
||||||
|
|
||||||
|
// for put
|
||||||
|
ignoreValue bool |
||||||
|
ignoreLease bool |
||||||
|
|
||||||
|
// progressNotify is for progress updates.
|
||||||
|
progressNotify bool |
||||||
|
// createdNotify is for created event
|
||||||
|
createdNotify bool |
||||||
|
// filters for watchers
|
||||||
|
filterPut bool |
||||||
|
filterDelete bool |
||||||
|
|
||||||
|
// for put
|
||||||
|
val []byte |
||||||
|
leaseID LeaseID |
||||||
|
} |
||||||
|
|
||||||
|
// accesors / mutators
|
||||||
|
|
||||||
|
// KeyBytes returns the byte slice holding the Op's key.
|
||||||
|
func (op Op) KeyBytes() []byte { return op.key } |
||||||
|
|
||||||
|
// WithKeyBytes sets the byte slice for the Op's key.
|
||||||
|
func (op *Op) WithKeyBytes(key []byte) { op.key = key } |
||||||
|
|
||||||
|
// RangeBytes returns the byte slice holding with the Op's range end, if any.
|
||||||
|
func (op Op) RangeBytes() []byte { return op.end } |
||||||
|
|
||||||
|
// WithRangeBytes sets the byte slice for the Op's range end.
|
||||||
|
func (op *Op) WithRangeBytes(end []byte) { op.end = end } |
||||||
|
|
||||||
|
// ValueBytes returns the byte slice holding the Op's value, if any.
|
||||||
|
func (op Op) ValueBytes() []byte { return op.val } |
||||||
|
|
||||||
|
// WithValueBytes sets the byte slice for the Op's value.
|
||||||
|
func (op *Op) WithValueBytes(v []byte) { op.val = v } |
||||||
|
|
||||||
|
func (op Op) toRangeRequest() *pb.RangeRequest { |
||||||
|
if op.t != tRange { |
||||||
|
panic("op.t != tRange") |
||||||
|
} |
||||||
|
r := &pb.RangeRequest{ |
||||||
|
Key: op.key, |
||||||
|
RangeEnd: op.end, |
||||||
|
Limit: op.limit, |
||||||
|
Revision: op.rev, |
||||||
|
Serializable: op.serializable, |
||||||
|
KeysOnly: op.keysOnly, |
||||||
|
CountOnly: op.countOnly, |
||||||
|
MinModRevision: op.minModRev, |
||||||
|
MaxModRevision: op.maxModRev, |
||||||
|
MinCreateRevision: op.minCreateRev, |
||||||
|
MaxCreateRevision: op.maxCreateRev, |
||||||
|
} |
||||||
|
if op.sort != nil { |
||||||
|
r.SortOrder = pb.RangeRequest_SortOrder(op.sort.Order) |
||||||
|
r.SortTarget = pb.RangeRequest_SortTarget(op.sort.Target) |
||||||
|
} |
||||||
|
return r |
||||||
|
} |
||||||
|
|
||||||
|
func (op Op) toRequestOp() *pb.RequestOp { |
||||||
|
switch op.t { |
||||||
|
case tRange: |
||||||
|
return &pb.RequestOp{Request: &pb.RequestOp_RequestRange{RequestRange: op.toRangeRequest()}} |
||||||
|
case tPut: |
||||||
|
r := &pb.PutRequest{Key: op.key, Value: op.val, Lease: int64(op.leaseID), PrevKv: op.prevKV, IgnoreValue: op.ignoreValue, IgnoreLease: op.ignoreLease} |
||||||
|
return &pb.RequestOp{Request: &pb.RequestOp_RequestPut{RequestPut: r}} |
||||||
|
case tDeleteRange: |
||||||
|
r := &pb.DeleteRangeRequest{Key: op.key, RangeEnd: op.end, PrevKv: op.prevKV} |
||||||
|
return &pb.RequestOp{Request: &pb.RequestOp_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") |
||||||
|
case ret.countOnly: |
||||||
|
panic("unexpected countOnly in delete") |
||||||
|
case ret.minModRev != 0, ret.maxModRev != 0: |
||||||
|
panic("unexpected mod revision filter in delete") |
||||||
|
case ret.minCreateRev != 0, ret.maxCreateRev != 0: |
||||||
|
panic("unexpected create revision filter in delete") |
||||||
|
case ret.filterDelete, ret.filterPut: |
||||||
|
panic("unexpected filter in delete") |
||||||
|
case ret.createdNotify: |
||||||
|
panic("unexpected createdNotify 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 put") |
||||||
|
case ret.countOnly: |
||||||
|
panic("unexpected countOnly in put") |
||||||
|
case ret.minModRev != 0, ret.maxModRev != 0: |
||||||
|
panic("unexpected mod revision filter in put") |
||||||
|
case ret.minCreateRev != 0, ret.maxCreateRev != 0: |
||||||
|
panic("unexpected create revision filter in put") |
||||||
|
case ret.filterDelete, ret.filterPut: |
||||||
|
panic("unexpected filter in put") |
||||||
|
case ret.createdNotify: |
||||||
|
panic("unexpected createdNotify in put") |
||||||
|
} |
||||||
|
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") |
||||||
|
case ret.countOnly: |
||||||
|
panic("unexpected countOnly in watch") |
||||||
|
case ret.minModRev != 0, ret.maxModRev != 0: |
||||||
|
panic("unexpected mod revision filter in watch") |
||||||
|
case ret.minCreateRev != 0, ret.maxCreateRev != 0: |
||||||
|
panic("unexpected create revision filter 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.
|
||||||
|
// If WithLimit is given a 0 limit, it is treated as no limit.
|
||||||
|
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) { |
||||||
|
if target == SortByKey && order == SortAscend { |
||||||
|
// If order != SortNone, server fetches the entire key-space,
|
||||||
|
// and then applies the sort and limit, if provided.
|
||||||
|
// Since current mvcc.Range implementation returns results
|
||||||
|
// sorted by keys in lexicographically ascending order,
|
||||||
|
// client should ignore SortOrder if the target is SortByKey.
|
||||||
|
order = SortNone |
||||||
|
} |
||||||
|
op.sort = &SortOption{target, order} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// GetPrefixRangeEnd gets the range end of the prefix.
|
||||||
|
// 'Get(foo, WithPrefix())' is equal to 'Get(foo, WithRange(GetPrefixRangeEnd(foo))'.
|
||||||
|
func GetPrefixRangeEnd(prefix string) string { |
||||||
|
return string(getPrefix([]byte(prefix))) |
||||||
|
} |
||||||
|
|
||||||
|
func getPrefix(key []byte) []byte { |
||||||
|
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) { |
||||||
|
if len(op.key) == 0 { |
||||||
|
op.key, op.end = []byte{0}, []byte{0} |
||||||
|
return |
||||||
|
} |
||||||
|
op.end = getPrefix(op.key) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// WithRange specifies the range of 'Get', 'Delete', 'Watch' requests.
|
||||||
|
// For example, 'Get' requests with 'WithRange(end)' returns
|
||||||
|
// the keys in the range [key, end).
|
||||||
|
// endKey must be lexicographically greater than start key.
|
||||||
|
func WithRange(endKey string) OpOption { |
||||||
|
return func(op *Op) { op.end = []byte(endKey) } |
||||||
|
} |
||||||
|
|
||||||
|
// WithFromKey specifies the range of 'Get', 'Delete', 'Watch' requests
|
||||||
|
// to be equal or greater than the 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 } |
||||||
|
} |
||||||
|
|
||||||
|
// WithKeysOnly makes the 'Get' request return only the keys and the corresponding
|
||||||
|
// values will be omitted.
|
||||||
|
func WithKeysOnly() OpOption { |
||||||
|
return func(op *Op) { op.keysOnly = true } |
||||||
|
} |
||||||
|
|
||||||
|
// WithCountOnly makes the 'Get' request return only the count of keys.
|
||||||
|
func WithCountOnly() OpOption { |
||||||
|
return func(op *Op) { op.countOnly = true } |
||||||
|
} |
||||||
|
|
||||||
|
// WithMinModRev filters out keys for Get with modification revisions less than the given revision.
|
||||||
|
func WithMinModRev(rev int64) OpOption { return func(op *Op) { op.minModRev = rev } } |
||||||
|
|
||||||
|
// WithMaxModRev filters out keys for Get with modification revisions greater than the given revision.
|
||||||
|
func WithMaxModRev(rev int64) OpOption { return func(op *Op) { op.maxModRev = rev } } |
||||||
|
|
||||||
|
// WithMinCreateRev filters out keys for Get with creation revisions less than the given revision.
|
||||||
|
func WithMinCreateRev(rev int64) OpOption { return func(op *Op) { op.minCreateRev = rev } } |
||||||
|
|
||||||
|
// WithMaxCreateRev filters out keys for Get with creation revisions greater than the given revision.
|
||||||
|
func WithMaxCreateRev(rev int64) OpOption { return func(op *Op) { op.maxCreateRev = rev } } |
||||||
|
|
||||||
|
// 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
|
||||||
|
// every 10 minutes when there is no incoming events.
|
||||||
|
// Progress updates have zero events in WatchResponse.
|
||||||
|
func WithProgressNotify() OpOption { |
||||||
|
return func(op *Op) { |
||||||
|
op.progressNotify = true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// WithCreatedNotify makes watch server sends the created event.
|
||||||
|
func WithCreatedNotify() OpOption { |
||||||
|
return func(op *Op) { |
||||||
|
op.createdNotify = true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// WithFilterPut discards PUT events from the watcher.
|
||||||
|
func WithFilterPut() OpOption { |
||||||
|
return func(op *Op) { op.filterPut = true } |
||||||
|
} |
||||||
|
|
||||||
|
// WithFilterDelete discards DELETE events from the watcher.
|
||||||
|
func WithFilterDelete() OpOption { |
||||||
|
return func(op *Op) { op.filterDelete = true } |
||||||
|
} |
||||||
|
|
||||||
|
// WithPrevKV gets the previous key-value pair before the event happens. If the previous KV is already compacted,
|
||||||
|
// nothing will be returned.
|
||||||
|
func WithPrevKV() OpOption { |
||||||
|
return func(op *Op) { |
||||||
|
op.prevKV = true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// WithIgnoreValue updates the key using its current value.
|
||||||
|
// Empty value should be passed when ignore_value is set.
|
||||||
|
// Returns an error if the key does not exist.
|
||||||
|
func WithIgnoreValue() OpOption { |
||||||
|
return func(op *Op) { |
||||||
|
op.ignoreValue = true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// WithIgnoreLease updates the key using its current lease.
|
||||||
|
// Empty lease should be passed when ignore_lease is set.
|
||||||
|
// Returns an error if the key does not exist.
|
||||||
|
func WithIgnoreLease() OpOption { |
||||||
|
return func(op *Op) { |
||||||
|
op.ignoreLease = true |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// LeaseOp represents an Operation that lease can execute.
|
||||||
|
type LeaseOp struct { |
||||||
|
id LeaseID |
||||||
|
|
||||||
|
// for TimeToLive
|
||||||
|
attachedKeys bool |
||||||
|
} |
||||||
|
|
||||||
|
// LeaseOption configures lease operations.
|
||||||
|
type LeaseOption func(*LeaseOp) |
||||||
|
|
||||||
|
func (op *LeaseOp) applyOpts(opts []LeaseOption) { |
||||||
|
for _, opt := range opts { |
||||||
|
opt(op) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// WithAttachedKeys requests lease timetolive API to return
|
||||||
|
// attached keys of given lease ID.
|
||||||
|
func WithAttachedKeys() LeaseOption { |
||||||
|
return func(op *LeaseOp) { op.attachedKeys = true } |
||||||
|
} |
||||||
|
|
||||||
|
func toLeaseTimeToLiveRequest(id LeaseID, opts ...LeaseOption) *pb.LeaseTimeToLiveRequest { |
||||||
|
ret := &LeaseOp{id: id} |
||||||
|
ret.applyOpts(opts) |
||||||
|
return &pb.LeaseTimeToLiveRequest{ID: int64(id), Keys: ret.attachedKeys} |
||||||
|
} |
||||||
@ -0,0 +1,293 @@ |
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package clientv3 |
||||||
|
|
||||||
|
import ( |
||||||
|
"github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" |
||||||
|
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" |
||||||
|
"golang.org/x/net/context" |
||||||
|
"google.golang.org/grpc" |
||||||
|
"google.golang.org/grpc/codes" |
||||||
|
) |
||||||
|
|
||||||
|
type rpcFunc func(ctx context.Context) error |
||||||
|
type retryRpcFunc func(context.Context, rpcFunc) error |
||||||
|
|
||||||
|
func (c *Client) newRetryWrapper() retryRpcFunc { |
||||||
|
return func(rpcCtx context.Context, f rpcFunc) error { |
||||||
|
for { |
||||||
|
err := f(rpcCtx) |
||||||
|
if err == nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
eErr := rpctypes.Error(err) |
||||||
|
// always stop retry on etcd errors
|
||||||
|
if _, ok := eErr.(rpctypes.EtcdError); ok { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
// only retry if unavailable
|
||||||
|
if grpc.Code(err) != codes.Unavailable { |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
select { |
||||||
|
case <-c.balancer.ConnectNotify(): |
||||||
|
case <-rpcCtx.Done(): |
||||||
|
return rpcCtx.Err() |
||||||
|
case <-c.ctx.Done(): |
||||||
|
return c.ctx.Err() |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (c *Client) newAuthRetryWrapper() retryRpcFunc { |
||||||
|
return func(rpcCtx context.Context, f rpcFunc) error { |
||||||
|
for { |
||||||
|
err := f(rpcCtx) |
||||||
|
if err == nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// always stop retry on etcd errors other than invalid auth token
|
||||||
|
if rpctypes.Error(err) == rpctypes.ErrInvalidAuthToken { |
||||||
|
gterr := c.getToken(rpcCtx) |
||||||
|
if gterr != nil { |
||||||
|
return err // return the original error for simplicity
|
||||||
|
} |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
return err |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// RetryKVClient implements a KVClient that uses the client's FailFast retry policy.
|
||||||
|
func RetryKVClient(c *Client) pb.KVClient { |
||||||
|
retryWrite := &retryWriteKVClient{pb.NewKVClient(c.conn), c.retryWrapper} |
||||||
|
return &retryKVClient{&retryWriteKVClient{retryWrite, c.retryAuthWrapper}} |
||||||
|
} |
||||||
|
|
||||||
|
type retryKVClient struct { |
||||||
|
*retryWriteKVClient |
||||||
|
} |
||||||
|
|
||||||
|
func (rkv *retryKVClient) Range(ctx context.Context, in *pb.RangeRequest, opts ...grpc.CallOption) (resp *pb.RangeResponse, err error) { |
||||||
|
err = rkv.retryf(ctx, func(rctx context.Context) error { |
||||||
|
resp, err = rkv.retryWriteKVClient.Range(rctx, in, opts...) |
||||||
|
return err |
||||||
|
}) |
||||||
|
return resp, err |
||||||
|
} |
||||||
|
|
||||||
|
type retryWriteKVClient struct { |
||||||
|
pb.KVClient |
||||||
|
retryf retryRpcFunc |
||||||
|
} |
||||||
|
|
||||||
|
func (rkv *retryWriteKVClient) Put(ctx context.Context, in *pb.PutRequest, opts ...grpc.CallOption) (resp *pb.PutResponse, err error) { |
||||||
|
err = rkv.retryf(ctx, func(rctx context.Context) error { |
||||||
|
resp, err = rkv.KVClient.Put(rctx, in, opts...) |
||||||
|
return err |
||||||
|
}) |
||||||
|
return resp, err |
||||||
|
} |
||||||
|
|
||||||
|
func (rkv *retryWriteKVClient) DeleteRange(ctx context.Context, in *pb.DeleteRangeRequest, opts ...grpc.CallOption) (resp *pb.DeleteRangeResponse, err error) { |
||||||
|
err = rkv.retryf(ctx, func(rctx context.Context) error { |
||||||
|
resp, err = rkv.KVClient.DeleteRange(rctx, in, opts...) |
||||||
|
return err |
||||||
|
}) |
||||||
|
return resp, err |
||||||
|
} |
||||||
|
|
||||||
|
func (rkv *retryWriteKVClient) Txn(ctx context.Context, in *pb.TxnRequest, opts ...grpc.CallOption) (resp *pb.TxnResponse, err error) { |
||||||
|
err = rkv.retryf(ctx, func(rctx context.Context) error { |
||||||
|
resp, err = rkv.KVClient.Txn(rctx, in, opts...) |
||||||
|
return err |
||||||
|
}) |
||||||
|
return resp, err |
||||||
|
} |
||||||
|
|
||||||
|
func (rkv *retryWriteKVClient) Compact(ctx context.Context, in *pb.CompactionRequest, opts ...grpc.CallOption) (resp *pb.CompactionResponse, err error) { |
||||||
|
err = rkv.retryf(ctx, func(rctx context.Context) error { |
||||||
|
resp, err = rkv.KVClient.Compact(rctx, in, opts...) |
||||||
|
return err |
||||||
|
}) |
||||||
|
return resp, err |
||||||
|
} |
||||||
|
|
||||||
|
type retryLeaseClient struct { |
||||||
|
pb.LeaseClient |
||||||
|
retryf retryRpcFunc |
||||||
|
} |
||||||
|
|
||||||
|
// RetryLeaseClient implements a LeaseClient that uses the client's FailFast retry policy.
|
||||||
|
func RetryLeaseClient(c *Client) pb.LeaseClient { |
||||||
|
retry := &retryLeaseClient{pb.NewLeaseClient(c.conn), c.retryWrapper} |
||||||
|
return &retryLeaseClient{retry, c.retryAuthWrapper} |
||||||
|
} |
||||||
|
|
||||||
|
func (rlc *retryLeaseClient) LeaseGrant(ctx context.Context, in *pb.LeaseGrantRequest, opts ...grpc.CallOption) (resp *pb.LeaseGrantResponse, err error) { |
||||||
|
err = rlc.retryf(ctx, func(rctx context.Context) error { |
||||||
|
resp, err = rlc.LeaseClient.LeaseGrant(rctx, in, opts...) |
||||||
|
return err |
||||||
|
}) |
||||||
|
return resp, err |
||||||
|
|
||||||
|
} |
||||||
|
|
||||||
|
func (rlc *retryLeaseClient) LeaseRevoke(ctx context.Context, in *pb.LeaseRevokeRequest, opts ...grpc.CallOption) (resp *pb.LeaseRevokeResponse, err error) { |
||||||
|
err = rlc.retryf(ctx, func(rctx context.Context) error { |
||||||
|
resp, err = rlc.LeaseClient.LeaseRevoke(rctx, in, opts...) |
||||||
|
return err |
||||||
|
}) |
||||||
|
return resp, err |
||||||
|
} |
||||||
|
|
||||||
|
type retryClusterClient struct { |
||||||
|
pb.ClusterClient |
||||||
|
retryf retryRpcFunc |
||||||
|
} |
||||||
|
|
||||||
|
// RetryClusterClient implements a ClusterClient that uses the client's FailFast retry policy.
|
||||||
|
func RetryClusterClient(c *Client) pb.ClusterClient { |
||||||
|
return &retryClusterClient{pb.NewClusterClient(c.conn), c.retryWrapper} |
||||||
|
} |
||||||
|
|
||||||
|
func (rcc *retryClusterClient) MemberAdd(ctx context.Context, in *pb.MemberAddRequest, opts ...grpc.CallOption) (resp *pb.MemberAddResponse, err error) { |
||||||
|
err = rcc.retryf(ctx, func(rctx context.Context) error { |
||||||
|
resp, err = rcc.ClusterClient.MemberAdd(rctx, in, opts...) |
||||||
|
return err |
||||||
|
}) |
||||||
|
return resp, err |
||||||
|
} |
||||||
|
|
||||||
|
func (rcc *retryClusterClient) MemberRemove(ctx context.Context, in *pb.MemberRemoveRequest, opts ...grpc.CallOption) (resp *pb.MemberRemoveResponse, err error) { |
||||||
|
err = rcc.retryf(ctx, func(rctx context.Context) error { |
||||||
|
resp, err = rcc.ClusterClient.MemberRemove(rctx, in, opts...) |
||||||
|
return err |
||||||
|
}) |
||||||
|
return resp, err |
||||||
|
} |
||||||
|
|
||||||
|
func (rcc *retryClusterClient) MemberUpdate(ctx context.Context, in *pb.MemberUpdateRequest, opts ...grpc.CallOption) (resp *pb.MemberUpdateResponse, err error) { |
||||||
|
err = rcc.retryf(ctx, func(rctx context.Context) error { |
||||||
|
resp, err = rcc.ClusterClient.MemberUpdate(rctx, in, opts...) |
||||||
|
return err |
||||||
|
}) |
||||||
|
return resp, err |
||||||
|
} |
||||||
|
|
||||||
|
type retryAuthClient struct { |
||||||
|
pb.AuthClient |
||||||
|
retryf retryRpcFunc |
||||||
|
} |
||||||
|
|
||||||
|
// RetryAuthClient implements a AuthClient that uses the client's FailFast retry policy.
|
||||||
|
func RetryAuthClient(c *Client) pb.AuthClient { |
||||||
|
return &retryAuthClient{pb.NewAuthClient(c.conn), c.retryWrapper} |
||||||
|
} |
||||||
|
|
||||||
|
func (rac *retryAuthClient) AuthEnable(ctx context.Context, in *pb.AuthEnableRequest, opts ...grpc.CallOption) (resp *pb.AuthEnableResponse, err error) { |
||||||
|
err = rac.retryf(ctx, func(rctx context.Context) error { |
||||||
|
resp, err = rac.AuthClient.AuthEnable(rctx, in, opts...) |
||||||
|
return err |
||||||
|
}) |
||||||
|
return resp, err |
||||||
|
} |
||||||
|
|
||||||
|
func (rac *retryAuthClient) AuthDisable(ctx context.Context, in *pb.AuthDisableRequest, opts ...grpc.CallOption) (resp *pb.AuthDisableResponse, err error) { |
||||||
|
err = rac.retryf(ctx, func(rctx context.Context) error { |
||||||
|
resp, err = rac.AuthClient.AuthDisable(rctx, in, opts...) |
||||||
|
return err |
||||||
|
}) |
||||||
|
return resp, err |
||||||
|
} |
||||||
|
|
||||||
|
func (rac *retryAuthClient) UserAdd(ctx context.Context, in *pb.AuthUserAddRequest, opts ...grpc.CallOption) (resp *pb.AuthUserAddResponse, err error) { |
||||||
|
err = rac.retryf(ctx, func(rctx context.Context) error { |
||||||
|
resp, err = rac.AuthClient.UserAdd(rctx, in, opts...) |
||||||
|
return err |
||||||
|
}) |
||||||
|
return resp, err |
||||||
|
} |
||||||
|
|
||||||
|
func (rac *retryAuthClient) UserDelete(ctx context.Context, in *pb.AuthUserDeleteRequest, opts ...grpc.CallOption) (resp *pb.AuthUserDeleteResponse, err error) { |
||||||
|
err = rac.retryf(ctx, func(rctx context.Context) error { |
||||||
|
resp, err = rac.AuthClient.UserDelete(rctx, in, opts...) |
||||||
|
return err |
||||||
|
}) |
||||||
|
return resp, err |
||||||
|
} |
||||||
|
|
||||||
|
func (rac *retryAuthClient) UserChangePassword(ctx context.Context, in *pb.AuthUserChangePasswordRequest, opts ...grpc.CallOption) (resp *pb.AuthUserChangePasswordResponse, err error) { |
||||||
|
err = rac.retryf(ctx, func(rctx context.Context) error { |
||||||
|
resp, err = rac.AuthClient.UserChangePassword(rctx, in, opts...) |
||||||
|
return err |
||||||
|
}) |
||||||
|
return resp, err |
||||||
|
} |
||||||
|
|
||||||
|
func (rac *retryAuthClient) UserGrantRole(ctx context.Context, in *pb.AuthUserGrantRoleRequest, opts ...grpc.CallOption) (resp *pb.AuthUserGrantRoleResponse, err error) { |
||||||
|
err = rac.retryf(ctx, func(rctx context.Context) error { |
||||||
|
resp, err = rac.AuthClient.UserGrantRole(rctx, in, opts...) |
||||||
|
return err |
||||||
|
}) |
||||||
|
return resp, err |
||||||
|
} |
||||||
|
|
||||||
|
func (rac *retryAuthClient) UserRevokeRole(ctx context.Context, in *pb.AuthUserRevokeRoleRequest, opts ...grpc.CallOption) (resp *pb.AuthUserRevokeRoleResponse, err error) { |
||||||
|
err = rac.retryf(ctx, func(rctx context.Context) error { |
||||||
|
resp, err = rac.AuthClient.UserRevokeRole(rctx, in, opts...) |
||||||
|
return err |
||||||
|
}) |
||||||
|
return resp, err |
||||||
|
} |
||||||
|
|
||||||
|
func (rac *retryAuthClient) RoleAdd(ctx context.Context, in *pb.AuthRoleAddRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleAddResponse, err error) { |
||||||
|
err = rac.retryf(ctx, func(rctx context.Context) error { |
||||||
|
resp, err = rac.AuthClient.RoleAdd(rctx, in, opts...) |
||||||
|
return err |
||||||
|
}) |
||||||
|
return resp, err |
||||||
|
} |
||||||
|
|
||||||
|
func (rac *retryAuthClient) RoleDelete(ctx context.Context, in *pb.AuthRoleDeleteRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleDeleteResponse, err error) { |
||||||
|
err = rac.retryf(ctx, func(rctx context.Context) error { |
||||||
|
resp, err = rac.AuthClient.RoleDelete(rctx, in, opts...) |
||||||
|
return err |
||||||
|
}) |
||||||
|
return resp, err |
||||||
|
} |
||||||
|
|
||||||
|
func (rac *retryAuthClient) RoleGrantPermission(ctx context.Context, in *pb.AuthRoleGrantPermissionRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleGrantPermissionResponse, err error) { |
||||||
|
err = rac.retryf(ctx, func(rctx context.Context) error { |
||||||
|
resp, err = rac.AuthClient.RoleGrantPermission(rctx, in, opts...) |
||||||
|
return err |
||||||
|
}) |
||||||
|
return resp, err |
||||||
|
} |
||||||
|
|
||||||
|
func (rac *retryAuthClient) RoleRevokePermission(ctx context.Context, in *pb.AuthRoleRevokePermissionRequest, opts ...grpc.CallOption) (resp *pb.AuthRoleRevokePermissionResponse, err error) { |
||||||
|
err = rac.retryf(ctx, func(rctx context.Context) error { |
||||||
|
resp, err = rac.AuthClient.RoleRevokePermission(rctx, in, opts...) |
||||||
|
return err |
||||||
|
}) |
||||||
|
return resp, err |
||||||
|
} |
||||||
@ -0,0 +1,37 @@ |
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package clientv3 |
||||||
|
|
||||||
|
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 |
||||||
|
} |
||||||
@ -0,0 +1,164 @@ |
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package clientv3 |
||||||
|
|
||||||
|
import ( |
||||||
|
"sync" |
||||||
|
|
||||||
|
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" |
||||||
|
"golang.org/x/net/context" |
||||||
|
"google.golang.org/grpc" |
||||||
|
) |
||||||
|
|
||||||
|
// Txn is the interface that wraps mini-transactions.
|
||||||
|
//
|
||||||
|
// 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) |
||||||
|
} |
||||||
|
|
||||||
|
type txn struct { |
||||||
|
kv *kv |
||||||
|
ctx context.Context |
||||||
|
|
||||||
|
mu sync.Mutex |
||||||
|
cif bool |
||||||
|
cthen bool |
||||||
|
celse bool |
||||||
|
|
||||||
|
isWrite bool |
||||||
|
|
||||||
|
cmps []*pb.Compare |
||||||
|
|
||||||
|
sus []*pb.RequestOp |
||||||
|
fas []*pb.RequestOp |
||||||
|
} |
||||||
|
|
||||||
|
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.toRequestOp()) |
||||||
|
} |
||||||
|
|
||||||
|
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.toRequestOp()) |
||||||
|
} |
||||||
|
|
||||||
|
return txn |
||||||
|
} |
||||||
|
|
||||||
|
func (txn *txn) Commit() (*TxnResponse, error) { |
||||||
|
txn.mu.Lock() |
||||||
|
defer txn.mu.Unlock() |
||||||
|
for { |
||||||
|
resp, err := txn.commit() |
||||||
|
if err == nil { |
||||||
|
return resp, err |
||||||
|
} |
||||||
|
if isHaltErr(txn.ctx, err) { |
||||||
|
return nil, toErr(txn.ctx, err) |
||||||
|
} |
||||||
|
if txn.isWrite { |
||||||
|
return nil, toErr(txn.ctx, err) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func (txn *txn) commit() (*TxnResponse, error) { |
||||||
|
r := &pb.TxnRequest{Compare: txn.cmps, Success: txn.sus, Failure: txn.fas} |
||||||
|
|
||||||
|
var opts []grpc.CallOption |
||||||
|
if !txn.isWrite { |
||||||
|
opts = []grpc.CallOption{grpc.FailFast(false)} |
||||||
|
} |
||||||
|
resp, err := txn.kv.remote.Txn(txn.ctx, r, opts...) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return (*TxnResponse)(resp), nil |
||||||
|
} |
||||||
@ -0,0 +1,797 @@ |
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package clientv3 |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"sync" |
||||||
|
"time" |
||||||
|
|
||||||
|
v3rpc "github.com/coreos/etcd/etcdserver/api/v3rpc/rpctypes" |
||||||
|
pb "github.com/coreos/etcd/etcdserver/etcdserverpb" |
||||||
|
mvccpb "github.com/coreos/etcd/mvcc/mvccpb" |
||||||
|
"golang.org/x/net/context" |
||||||
|
"google.golang.org/grpc" |
||||||
|
"google.golang.org/grpc/codes" |
||||||
|
) |
||||||
|
|
||||||
|
const ( |
||||||
|
EventTypeDelete = mvccpb.DELETE |
||||||
|
EventTypePut = mvccpb.PUT |
||||||
|
|
||||||
|
closeSendErrTimeout = 250 * time.Millisecond |
||||||
|
) |
||||||
|
|
||||||
|
type Event mvccpb.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 'WithPrefix'.
|
||||||
|
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 |
||||||
|
|
||||||
|
// Created is used to indicate the creation of the watcher.
|
||||||
|
Created bool |
||||||
|
|
||||||
|
closeErr error |
||||||
|
|
||||||
|
// cancelReason is a reason of canceling watch
|
||||||
|
cancelReason string |
||||||
|
} |
||||||
|
|
||||||
|
// 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 { |
||||||
|
switch { |
||||||
|
case wr.closeErr != nil: |
||||||
|
return v3rpc.Error(wr.closeErr) |
||||||
|
case wr.CompactRevision != 0: |
||||||
|
return v3rpc.ErrCompacted |
||||||
|
case wr.Canceled: |
||||||
|
if len(wr.cancelReason) != 0 { |
||||||
|
return v3rpc.Error(grpc.Errorf(codes.FailedPrecondition, "%s", wr.cancelReason)) |
||||||
|
} |
||||||
|
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 && !wr.Created && wr.CompactRevision == 0 && wr.Header.Revision != 0 |
||||||
|
} |
||||||
|
|
||||||
|
// watcher implements the Watcher interface
|
||||||
|
type watcher struct { |
||||||
|
remote pb.WatchClient |
||||||
|
|
||||||
|
// mu protects the grpc streams map
|
||||||
|
mu sync.RWMutex |
||||||
|
|
||||||
|
// streams holds all the active grpc streams keyed by ctx value.
|
||||||
|
streams map[string]*watchGrpcStream |
||||||
|
} |
||||||
|
|
||||||
|
// watchGrpcStream tracks all watch resources attached to a single grpc stream.
|
||||||
|
type watchGrpcStream struct { |
||||||
|
owner *watcher |
||||||
|
remote pb.WatchClient |
||||||
|
|
||||||
|
// ctx controls internal remote.Watch requests
|
||||||
|
ctx context.Context |
||||||
|
// ctxKey is the key used when looking up this stream's context
|
||||||
|
ctxKey string |
||||||
|
cancel context.CancelFunc |
||||||
|
|
||||||
|
// substreams holds all active watchers on this grpc stream
|
||||||
|
substreams map[int64]*watcherStream |
||||||
|
// resuming holds all resuming watchers on this grpc stream
|
||||||
|
resuming []*watcherStream |
||||||
|
|
||||||
|
// 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 |
||||||
|
// donec closes to broadcast shutdown
|
||||||
|
donec chan struct{} |
||||||
|
// errc transmits errors from grpc Recv to the watch stream reconn logic
|
||||||
|
errc chan error |
||||||
|
// closingc gets the watcherStream of closing watchers
|
||||||
|
closingc chan *watcherStream |
||||||
|
// wg is Done when all substream goroutines have exited
|
||||||
|
wg sync.WaitGroup |
||||||
|
|
||||||
|
// resumec closes to signal that all substreams should begin resuming
|
||||||
|
resumec chan struct{} |
||||||
|
// closeErr is the error that closed the watch stream
|
||||||
|
closeErr error |
||||||
|
} |
||||||
|
|
||||||
|
// watchRequest is issued by the subscriber to start a new watcher
|
||||||
|
type watchRequest struct { |
||||||
|
ctx context.Context |
||||||
|
key string |
||||||
|
end string |
||||||
|
rev int64 |
||||||
|
// send created notification event if this field is true
|
||||||
|
createdNotify bool |
||||||
|
// progressNotify is for progress updates
|
||||||
|
progressNotify bool |
||||||
|
// filters is the list of events to filter out
|
||||||
|
filters []pb.WatchCreateRequest_FilterType |
||||||
|
// get the previous key-value pair before the event happens
|
||||||
|
prevKV bool |
||||||
|
// retc receives a chan WatchResponse once the watcher is established
|
||||||
|
retc chan chan WatchResponse |
||||||
|
} |
||||||
|
|
||||||
|
// watcherStream represents a registered watcher
|
||||||
|
type watcherStream struct { |
||||||
|
// initReq is the request that initiated this request
|
||||||
|
initReq watchRequest |
||||||
|
|
||||||
|
// outc publishes watch responses to subscriber
|
||||||
|
outc chan WatchResponse |
||||||
|
// recvc buffers watch responses before publishing
|
||||||
|
recvc chan *WatchResponse |
||||||
|
// donec closes when the watcherStream goroutine stops.
|
||||||
|
donec chan struct{} |
||||||
|
// closing is set to true when stream should be scheduled to shutdown.
|
||||||
|
closing bool |
||||||
|
// id is the registered watch id on the grpc stream
|
||||||
|
id int64 |
||||||
|
|
||||||
|
// buf holds all events received from etcd but not yet consumed by the client
|
||||||
|
buf []*WatchResponse |
||||||
|
} |
||||||
|
|
||||||
|
func NewWatcher(c *Client) Watcher { |
||||||
|
return NewWatchFromWatchClient(pb.NewWatchClient(c.conn)) |
||||||
|
} |
||||||
|
|
||||||
|
func NewWatchFromWatchClient(wc pb.WatchClient) Watcher { |
||||||
|
return &watcher{ |
||||||
|
remote: wc, |
||||||
|
streams: make(map[string]*watchGrpcStream), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// never closes
|
||||||
|
var valCtxCh = make(chan struct{}) |
||||||
|
var zeroTime = time.Unix(0, 0) |
||||||
|
|
||||||
|
// ctx with only the values; never Done
|
||||||
|
type valCtx struct{ context.Context } |
||||||
|
|
||||||
|
func (vc *valCtx) Deadline() (time.Time, bool) { return zeroTime, false } |
||||||
|
func (vc *valCtx) Done() <-chan struct{} { return valCtxCh } |
||||||
|
func (vc *valCtx) Err() error { return nil } |
||||||
|
|
||||||
|
func (w *watcher) newWatcherGrpcStream(inctx context.Context) *watchGrpcStream { |
||||||
|
ctx, cancel := context.WithCancel(&valCtx{inctx}) |
||||||
|
wgs := &watchGrpcStream{ |
||||||
|
owner: w, |
||||||
|
remote: w.remote, |
||||||
|
ctx: ctx, |
||||||
|
ctxKey: fmt.Sprintf("%v", inctx), |
||||||
|
cancel: cancel, |
||||||
|
substreams: make(map[int64]*watcherStream), |
||||||
|
|
||||||
|
respc: make(chan *pb.WatchResponse), |
||||||
|
reqc: make(chan *watchRequest), |
||||||
|
donec: make(chan struct{}), |
||||||
|
errc: make(chan error, 1), |
||||||
|
closingc: make(chan *watcherStream), |
||||||
|
resumec: make(chan struct{}), |
||||||
|
} |
||||||
|
go wgs.run() |
||||||
|
return wgs |
||||||
|
} |
||||||
|
|
||||||
|
// 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...) |
||||||
|
|
||||||
|
var filters []pb.WatchCreateRequest_FilterType |
||||||
|
if ow.filterPut { |
||||||
|
filters = append(filters, pb.WatchCreateRequest_NOPUT) |
||||||
|
} |
||||||
|
if ow.filterDelete { |
||||||
|
filters = append(filters, pb.WatchCreateRequest_NODELETE) |
||||||
|
} |
||||||
|
|
||||||
|
wr := &watchRequest{ |
||||||
|
ctx: ctx, |
||||||
|
createdNotify: ow.createdNotify, |
||||||
|
key: string(ow.key), |
||||||
|
end: string(ow.end), |
||||||
|
rev: ow.rev, |
||||||
|
progressNotify: ow.progressNotify, |
||||||
|
filters: filters, |
||||||
|
prevKV: ow.prevKV, |
||||||
|
retc: make(chan chan WatchResponse, 1), |
||||||
|
} |
||||||
|
|
||||||
|
ok := false |
||||||
|
ctxKey := fmt.Sprintf("%v", ctx) |
||||||
|
|
||||||
|
// find or allocate appropriate grpc watch stream
|
||||||
|
w.mu.Lock() |
||||||
|
if w.streams == nil { |
||||||
|
// closed
|
||||||
|
w.mu.Unlock() |
||||||
|
ch := make(chan WatchResponse) |
||||||
|
close(ch) |
||||||
|
return ch |
||||||
|
} |
||||||
|
wgs := w.streams[ctxKey] |
||||||
|
if wgs == nil { |
||||||
|
wgs = w.newWatcherGrpcStream(ctx) |
||||||
|
w.streams[ctxKey] = wgs |
||||||
|
} |
||||||
|
donec := wgs.donec |
||||||
|
reqc := wgs.reqc |
||||||
|
w.mu.Unlock() |
||||||
|
|
||||||
|
// couldn't create channel; return closed channel
|
||||||
|
closeCh := make(chan WatchResponse, 1) |
||||||
|
|
||||||
|
// submit request
|
||||||
|
select { |
||||||
|
case reqc <- wr: |
||||||
|
ok = true |
||||||
|
case <-wr.ctx.Done(): |
||||||
|
case <-donec: |
||||||
|
if wgs.closeErr != nil { |
||||||
|
closeCh <- WatchResponse{closeErr: wgs.closeErr} |
||||||
|
break |
||||||
|
} |
||||||
|
// retry; may have dropped stream from no ctxs
|
||||||
|
return w.Watch(ctx, key, opts...) |
||||||
|
} |
||||||
|
|
||||||
|
// receive channel
|
||||||
|
if ok { |
||||||
|
select { |
||||||
|
case ret := <-wr.retc: |
||||||
|
return ret |
||||||
|
case <-ctx.Done(): |
||||||
|
case <-donec: |
||||||
|
if wgs.closeErr != nil { |
||||||
|
closeCh <- WatchResponse{closeErr: wgs.closeErr} |
||||||
|
break |
||||||
|
} |
||||||
|
// retry; may have dropped stream from no ctxs
|
||||||
|
return w.Watch(ctx, key, opts...) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
close(closeCh) |
||||||
|
return closeCh |
||||||
|
} |
||||||
|
|
||||||
|
func (w *watcher) Close() (err error) { |
||||||
|
w.mu.Lock() |
||||||
|
streams := w.streams |
||||||
|
w.streams = nil |
||||||
|
w.mu.Unlock() |
||||||
|
for _, wgs := range streams { |
||||||
|
if werr := wgs.Close(); werr != nil { |
||||||
|
err = werr |
||||||
|
} |
||||||
|
} |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
func (w *watchGrpcStream) Close() (err error) { |
||||||
|
w.cancel() |
||||||
|
<-w.donec |
||||||
|
select { |
||||||
|
case err = <-w.errc: |
||||||
|
default: |
||||||
|
} |
||||||
|
return toErr(w.ctx, err) |
||||||
|
} |
||||||
|
|
||||||
|
func (w *watcher) closeStream(wgs *watchGrpcStream) { |
||||||
|
w.mu.Lock() |
||||||
|
close(wgs.donec) |
||||||
|
wgs.cancel() |
||||||
|
if w.streams != nil { |
||||||
|
delete(w.streams, wgs.ctxKey) |
||||||
|
} |
||||||
|
w.mu.Unlock() |
||||||
|
} |
||||||
|
|
||||||
|
func (w *watchGrpcStream) addSubstream(resp *pb.WatchResponse, ws *watcherStream) { |
||||||
|
if resp.WatchId == -1 { |
||||||
|
// failed; no channel
|
||||||
|
close(ws.recvc) |
||||||
|
return |
||||||
|
} |
||||||
|
ws.id = resp.WatchId |
||||||
|
w.substreams[ws.id] = ws |
||||||
|
} |
||||||
|
|
||||||
|
func (w *watchGrpcStream) sendCloseSubstream(ws *watcherStream, resp *WatchResponse) { |
||||||
|
select { |
||||||
|
case ws.outc <- *resp: |
||||||
|
case <-ws.initReq.ctx.Done(): |
||||||
|
case <-time.After(closeSendErrTimeout): |
||||||
|
} |
||||||
|
close(ws.outc) |
||||||
|
} |
||||||
|
|
||||||
|
func (w *watchGrpcStream) closeSubstream(ws *watcherStream) { |
||||||
|
// send channel response in case stream was never established
|
||||||
|
select { |
||||||
|
case ws.initReq.retc <- ws.outc: |
||||||
|
default: |
||||||
|
} |
||||||
|
// close subscriber's channel
|
||||||
|
if closeErr := w.closeErr; closeErr != nil && ws.initReq.ctx.Err() == nil { |
||||||
|
go w.sendCloseSubstream(ws, &WatchResponse{closeErr: w.closeErr}) |
||||||
|
} else if ws.outc != nil { |
||||||
|
close(ws.outc) |
||||||
|
} |
||||||
|
if ws.id != -1 { |
||||||
|
delete(w.substreams, ws.id) |
||||||
|
return |
||||||
|
} |
||||||
|
for i := range w.resuming { |
||||||
|
if w.resuming[i] == ws { |
||||||
|
w.resuming[i] = nil |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// run is the root of the goroutines for managing a watcher client
|
||||||
|
func (w *watchGrpcStream) run() { |
||||||
|
var wc pb.Watch_WatchClient |
||||||
|
var closeErr error |
||||||
|
|
||||||
|
// substreams marked to close but goroutine still running; needed for
|
||||||
|
// avoiding double-closing recvc on grpc stream teardown
|
||||||
|
closing := make(map[*watcherStream]struct{}) |
||||||
|
|
||||||
|
defer func() { |
||||||
|
w.closeErr = closeErr |
||||||
|
// shutdown substreams and resuming substreams
|
||||||
|
for _, ws := range w.substreams { |
||||||
|
if _, ok := closing[ws]; !ok { |
||||||
|
close(ws.recvc) |
||||||
|
closing[ws] = struct{}{} |
||||||
|
} |
||||||
|
} |
||||||
|
for _, ws := range w.resuming { |
||||||
|
if _, ok := closing[ws]; ws != nil && !ok { |
||||||
|
close(ws.recvc) |
||||||
|
closing[ws] = struct{}{} |
||||||
|
} |
||||||
|
} |
||||||
|
w.joinSubstreams() |
||||||
|
for range closing { |
||||||
|
w.closeSubstream(<-w.closingc) |
||||||
|
} |
||||||
|
w.wg.Wait() |
||||||
|
w.owner.closeStream(w) |
||||||
|
}() |
||||||
|
|
||||||
|
// start a stream with the etcd grpc server
|
||||||
|
if wc, closeErr = w.newWatchClient(); closeErr != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
cancelSet := make(map[int64]struct{}) |
||||||
|
|
||||||
|
for { |
||||||
|
select { |
||||||
|
// Watch() requested
|
||||||
|
case wreq := <-w.reqc: |
||||||
|
outc := make(chan WatchResponse, 1) |
||||||
|
ws := &watcherStream{ |
||||||
|
initReq: *wreq, |
||||||
|
id: -1, |
||||||
|
outc: outc, |
||||||
|
// unbufffered so resumes won't cause repeat events
|
||||||
|
recvc: make(chan *WatchResponse), |
||||||
|
} |
||||||
|
|
||||||
|
ws.donec = make(chan struct{}) |
||||||
|
w.wg.Add(1) |
||||||
|
go w.serveSubstream(ws, w.resumec) |
||||||
|
|
||||||
|
// queue up for watcher creation/resume
|
||||||
|
w.resuming = append(w.resuming, ws) |
||||||
|
if len(w.resuming) == 1 { |
||||||
|
// head of resume queue, can register a new watcher
|
||||||
|
wc.Send(ws.initReq.toPB()) |
||||||
|
} |
||||||
|
// New events from the watch client
|
||||||
|
case pbresp := <-w.respc: |
||||||
|
switch { |
||||||
|
case pbresp.Created: |
||||||
|
// response to head of queue creation
|
||||||
|
if ws := w.resuming[0]; ws != nil { |
||||||
|
w.addSubstream(pbresp, ws) |
||||||
|
w.dispatchEvent(pbresp) |
||||||
|
w.resuming[0] = nil |
||||||
|
} |
||||||
|
if ws := w.nextResume(); ws != nil { |
||||||
|
wc.Send(ws.initReq.toPB()) |
||||||
|
} |
||||||
|
case pbresp.Canceled: |
||||||
|
delete(cancelSet, pbresp.WatchId) |
||||||
|
if ws, ok := w.substreams[pbresp.WatchId]; ok { |
||||||
|
// signal to stream goroutine to update closingc
|
||||||
|
close(ws.recvc) |
||||||
|
closing[ws] = struct{}{} |
||||||
|
} |
||||||
|
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
|
||||||
|
case err := <-w.errc: |
||||||
|
if isHaltErr(w.ctx, err) || toErr(w.ctx, err) == v3rpc.ErrNoLeader { |
||||||
|
closeErr = err |
||||||
|
return |
||||||
|
} |
||||||
|
if wc, closeErr = w.newWatchClient(); closeErr != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
if ws := w.nextResume(); ws != nil { |
||||||
|
wc.Send(ws.initReq.toPB()) |
||||||
|
} |
||||||
|
cancelSet = make(map[int64]struct{}) |
||||||
|
case <-w.ctx.Done(): |
||||||
|
return |
||||||
|
case ws := <-w.closingc: |
||||||
|
w.closeSubstream(ws) |
||||||
|
delete(closing, ws) |
||||||
|
if len(w.substreams)+len(w.resuming) == 0 { |
||||||
|
// no more watchers on this stream, shutdown
|
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// nextResume chooses the next resuming to register with the grpc stream. Abandoned
|
||||||
|
// streams are marked as nil in the queue since the head must wait for its inflight registration.
|
||||||
|
func (w *watchGrpcStream) nextResume() *watcherStream { |
||||||
|
for len(w.resuming) != 0 { |
||||||
|
if w.resuming[0] != nil { |
||||||
|
return w.resuming[0] |
||||||
|
} |
||||||
|
w.resuming = w.resuming[1:len(w.resuming)] |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
// dispatchEvent sends a WatchResponse to the appropriate watcher stream
|
||||||
|
func (w *watchGrpcStream) dispatchEvent(pbresp *pb.WatchResponse) bool { |
||||||
|
events := make([]*Event, len(pbresp.Events)) |
||||||
|
for i, ev := range pbresp.Events { |
||||||
|
events[i] = (*Event)(ev) |
||||||
|
} |
||||||
|
wr := &WatchResponse{ |
||||||
|
Header: *pbresp.Header, |
||||||
|
Events: events, |
||||||
|
CompactRevision: pbresp.CompactRevision, |
||||||
|
Created: pbresp.Created, |
||||||
|
Canceled: pbresp.Canceled, |
||||||
|
cancelReason: pbresp.CancelReason, |
||||||
|
} |
||||||
|
ws, ok := w.substreams[pbresp.WatchId] |
||||||
|
if !ok { |
||||||
|
return false |
||||||
|
} |
||||||
|
select { |
||||||
|
case ws.recvc <- wr: |
||||||
|
case <-ws.donec: |
||||||
|
return false |
||||||
|
} |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
// serveWatchClient forwards messages from the grpc stream to run()
|
||||||
|
func (w *watchGrpcStream) 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 |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// serveSubstream forwards watch responses from run() to the subscriber
|
||||||
|
func (w *watchGrpcStream) serveSubstream(ws *watcherStream, resumec chan struct{}) { |
||||||
|
if ws.closing { |
||||||
|
panic("created substream goroutine but substream is closing") |
||||||
|
} |
||||||
|
|
||||||
|
// nextRev is the minimum expected next revision
|
||||||
|
nextRev := ws.initReq.rev |
||||||
|
resuming := false |
||||||
|
defer func() { |
||||||
|
if !resuming { |
||||||
|
ws.closing = true |
||||||
|
} |
||||||
|
close(ws.donec) |
||||||
|
if !resuming { |
||||||
|
w.closingc <- ws |
||||||
|
} |
||||||
|
w.wg.Done() |
||||||
|
}() |
||||||
|
|
||||||
|
emptyWr := &WatchResponse{} |
||||||
|
for { |
||||||
|
curWr := emptyWr |
||||||
|
outc := ws.outc |
||||||
|
|
||||||
|
if len(ws.buf) > 0 { |
||||||
|
curWr = ws.buf[0] |
||||||
|
} else { |
||||||
|
outc = nil |
||||||
|
} |
||||||
|
select { |
||||||
|
case outc <- *curWr: |
||||||
|
if ws.buf[0].Err() != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
ws.buf[0] = nil |
||||||
|
ws.buf = ws.buf[1:] |
||||||
|
case wr, ok := <-ws.recvc: |
||||||
|
if !ok { |
||||||
|
// shutdown from closeSubstream
|
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
if wr.Created { |
||||||
|
if ws.initReq.retc != nil { |
||||||
|
ws.initReq.retc <- ws.outc |
||||||
|
// to prevent next write from taking the slot in buffered channel
|
||||||
|
// and posting duplicate create events
|
||||||
|
ws.initReq.retc = nil |
||||||
|
|
||||||
|
// send first creation event only if requested
|
||||||
|
if ws.initReq.createdNotify { |
||||||
|
ws.outc <- *wr |
||||||
|
} |
||||||
|
// once the watch channel is returned, a current revision
|
||||||
|
// watch must resume at the store revision. This is necessary
|
||||||
|
// for the following case to work as expected:
|
||||||
|
// wch := m1.Watch("a")
|
||||||
|
// m2.Put("a", "b")
|
||||||
|
// <-wch
|
||||||
|
// If the revision is only bound on the first observed event,
|
||||||
|
// if wch is disconnected before the Put is issued, then reconnects
|
||||||
|
// after it is committed, it'll miss the Put.
|
||||||
|
if ws.initReq.rev == 0 { |
||||||
|
nextRev = wr.Header.Revision |
||||||
|
} |
||||||
|
} |
||||||
|
} else { |
||||||
|
// current progress of watch; <= store revision
|
||||||
|
nextRev = wr.Header.Revision |
||||||
|
} |
||||||
|
|
||||||
|
if len(wr.Events) > 0 { |
||||||
|
nextRev = wr.Events[len(wr.Events)-1].Kv.ModRevision + 1 |
||||||
|
} |
||||||
|
ws.initReq.rev = nextRev |
||||||
|
|
||||||
|
// created event is already sent above,
|
||||||
|
// watcher should not post duplicate events
|
||||||
|
if wr.Created { |
||||||
|
continue |
||||||
|
} |
||||||
|
|
||||||
|
// TODO pause channel if buffer gets too large
|
||||||
|
ws.buf = append(ws.buf, wr) |
||||||
|
case <-w.ctx.Done(): |
||||||
|
return |
||||||
|
case <-ws.initReq.ctx.Done(): |
||||||
|
return |
||||||
|
case <-resumec: |
||||||
|
resuming = true |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
// lazily send cancel message if events on missing id
|
||||||
|
} |
||||||
|
|
||||||
|
func (w *watchGrpcStream) newWatchClient() (pb.Watch_WatchClient, error) { |
||||||
|
// mark all substreams as resuming
|
||||||
|
close(w.resumec) |
||||||
|
w.resumec = make(chan struct{}) |
||||||
|
w.joinSubstreams() |
||||||
|
for _, ws := range w.substreams { |
||||||
|
ws.id = -1 |
||||||
|
w.resuming = append(w.resuming, ws) |
||||||
|
} |
||||||
|
// strip out nils, if any
|
||||||
|
var resuming []*watcherStream |
||||||
|
for _, ws := range w.resuming { |
||||||
|
if ws != nil { |
||||||
|
resuming = append(resuming, ws) |
||||||
|
} |
||||||
|
} |
||||||
|
w.resuming = resuming |
||||||
|
w.substreams = make(map[int64]*watcherStream) |
||||||
|
|
||||||
|
// connect to grpc stream while accepting watcher cancelation
|
||||||
|
stopc := make(chan struct{}) |
||||||
|
donec := w.waitCancelSubstreams(stopc) |
||||||
|
wc, err := w.openWatchClient() |
||||||
|
close(stopc) |
||||||
|
<-donec |
||||||
|
|
||||||
|
// serve all non-closing streams, even if there's a client error
|
||||||
|
// so that the teardown path can shutdown the streams as expected.
|
||||||
|
for _, ws := range w.resuming { |
||||||
|
if ws.closing { |
||||||
|
continue |
||||||
|
} |
||||||
|
ws.donec = make(chan struct{}) |
||||||
|
w.wg.Add(1) |
||||||
|
go w.serveSubstream(ws, w.resumec) |
||||||
|
} |
||||||
|
|
||||||
|
if err != nil { |
||||||
|
return nil, v3rpc.Error(err) |
||||||
|
} |
||||||
|
|
||||||
|
// receive data from new grpc stream
|
||||||
|
go w.serveWatchClient(wc) |
||||||
|
return wc, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (w *watchGrpcStream) waitCancelSubstreams(stopc <-chan struct{}) <-chan struct{} { |
||||||
|
var wg sync.WaitGroup |
||||||
|
wg.Add(len(w.resuming)) |
||||||
|
donec := make(chan struct{}) |
||||||
|
for i := range w.resuming { |
||||||
|
go func(ws *watcherStream) { |
||||||
|
defer wg.Done() |
||||||
|
if ws.closing { |
||||||
|
if ws.initReq.ctx.Err() != nil && ws.outc != nil { |
||||||
|
close(ws.outc) |
||||||
|
ws.outc = nil |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
select { |
||||||
|
case <-ws.initReq.ctx.Done(): |
||||||
|
// closed ws will be removed from resuming
|
||||||
|
ws.closing = true |
||||||
|
close(ws.outc) |
||||||
|
ws.outc = nil |
||||||
|
w.wg.Add(1) |
||||||
|
go func() { |
||||||
|
defer w.wg.Done() |
||||||
|
w.closingc <- ws |
||||||
|
}() |
||||||
|
case <-stopc: |
||||||
|
} |
||||||
|
}(w.resuming[i]) |
||||||
|
} |
||||||
|
go func() { |
||||||
|
defer close(donec) |
||||||
|
wg.Wait() |
||||||
|
}() |
||||||
|
return donec |
||||||
|
} |
||||||
|
|
||||||
|
// joinSubstream waits for all substream goroutines to complete
|
||||||
|
func (w *watchGrpcStream) joinSubstreams() { |
||||||
|
for _, ws := range w.substreams { |
||||||
|
<-ws.donec |
||||||
|
} |
||||||
|
for _, ws := range w.resuming { |
||||||
|
if ws != nil { |
||||||
|
<-ws.donec |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// openWatchClient retries opening a watchclient until retryConnection fails
|
||||||
|
func (w *watchGrpcStream) openWatchClient() (ws pb.Watch_WatchClient, err error) { |
||||||
|
for { |
||||||
|
select { |
||||||
|
case <-w.ctx.Done(): |
||||||
|
if err == nil { |
||||||
|
return nil, w.ctx.Err() |
||||||
|
} |
||||||
|
return nil, err |
||||||
|
default: |
||||||
|
} |
||||||
|
if ws, err = w.remote.Watch(w.ctx, grpc.FailFast(false)); ws != nil && err == nil { |
||||||
|
break |
||||||
|
} |
||||||
|
if isHaltErr(w.ctx, err) { |
||||||
|
return nil, v3rpc.Error(err) |
||||||
|
} |
||||||
|
} |
||||||
|
return ws, 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, |
||||||
|
Filters: wr.filters, |
||||||
|
PrevKv: wr.prevKV, |
||||||
|
} |
||||||
|
cr := &pb.WatchRequest_CreateRequest{CreateRequest: req} |
||||||
|
return &pb.WatchRequest{RequestUnion: cr} |
||||||
|
} |
||||||
@ -0,0 +1,16 @@ |
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package rpctypes has types and values shared by the etcd server and client for v3 RPC interaction.
|
||||||
|
package rpctypes |
||||||
@ -0,0 +1,190 @@ |
|||||||
|
// Copyright 2015 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package rpctypes |
||||||
|
|
||||||
|
import ( |
||||||
|
"google.golang.org/grpc" |
||||||
|
"google.golang.org/grpc/codes" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
// server-side error
|
||||||
|
ErrGRPCEmptyKey = grpc.Errorf(codes.InvalidArgument, "etcdserver: key is not provided") |
||||||
|
ErrGRPCKeyNotFound = grpc.Errorf(codes.InvalidArgument, "etcdserver: key not found") |
||||||
|
ErrGRPCValueProvided = grpc.Errorf(codes.InvalidArgument, "etcdserver: value is provided") |
||||||
|
ErrGRPCLeaseProvided = grpc.Errorf(codes.InvalidArgument, "etcdserver: lease is provided") |
||||||
|
ErrGRPCTooManyOps = grpc.Errorf(codes.InvalidArgument, "etcdserver: too many operations in txn request") |
||||||
|
ErrGRPCDuplicateKey = grpc.Errorf(codes.InvalidArgument, "etcdserver: duplicate key given in txn request") |
||||||
|
ErrGRPCCompacted = grpc.Errorf(codes.OutOfRange, "etcdserver: mvcc: required revision has been compacted") |
||||||
|
ErrGRPCFutureRev = grpc.Errorf(codes.OutOfRange, "etcdserver: mvcc: required revision is a future revision") |
||||||
|
ErrGRPCNoSpace = grpc.Errorf(codes.ResourceExhausted, "etcdserver: mvcc: database space exceeded") |
||||||
|
|
||||||
|
ErrGRPCLeaseNotFound = grpc.Errorf(codes.NotFound, "etcdserver: requested lease not found") |
||||||
|
ErrGRPCLeaseExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: lease already exists") |
||||||
|
|
||||||
|
ErrGRPCMemberExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: member ID already exist") |
||||||
|
ErrGRPCPeerURLExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: Peer URLs already exists") |
||||||
|
ErrGRPCMemberNotEnoughStarted = grpc.Errorf(codes.FailedPrecondition, "etcdserver: re-configuration failed due to not enough started members") |
||||||
|
ErrGRPCMemberBadURLs = grpc.Errorf(codes.InvalidArgument, "etcdserver: given member URLs are invalid") |
||||||
|
ErrGRPCMemberNotFound = grpc.Errorf(codes.NotFound, "etcdserver: member not found") |
||||||
|
|
||||||
|
ErrGRPCRequestTooLarge = grpc.Errorf(codes.InvalidArgument, "etcdserver: request is too large") |
||||||
|
ErrGRPCRequestTooManyRequests = grpc.Errorf(codes.ResourceExhausted, "etcdserver: too many requests") |
||||||
|
|
||||||
|
ErrGRPCRootUserNotExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: root user does not exist") |
||||||
|
ErrGRPCRootRoleNotExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: root user does not have root role") |
||||||
|
ErrGRPCUserAlreadyExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: user name already exists") |
||||||
|
ErrGRPCUserEmpty = grpc.Errorf(codes.InvalidArgument, "etcdserver: user name is empty") |
||||||
|
ErrGRPCUserNotFound = grpc.Errorf(codes.FailedPrecondition, "etcdserver: user name not found") |
||||||
|
ErrGRPCRoleAlreadyExist = grpc.Errorf(codes.FailedPrecondition, "etcdserver: role name already exists") |
||||||
|
ErrGRPCRoleNotFound = grpc.Errorf(codes.FailedPrecondition, "etcdserver: role name not found") |
||||||
|
ErrGRPCAuthFailed = grpc.Errorf(codes.InvalidArgument, "etcdserver: authentication failed, invalid user ID or password") |
||||||
|
ErrGRPCPermissionDenied = grpc.Errorf(codes.PermissionDenied, "etcdserver: permission denied") |
||||||
|
ErrGRPCRoleNotGranted = grpc.Errorf(codes.FailedPrecondition, "etcdserver: role is not granted to the user") |
||||||
|
ErrGRPCPermissionNotGranted = grpc.Errorf(codes.FailedPrecondition, "etcdserver: permission is not granted to the role") |
||||||
|
ErrGRPCAuthNotEnabled = grpc.Errorf(codes.FailedPrecondition, "etcdserver: authentication is not enabled") |
||||||
|
ErrGRPCInvalidAuthToken = grpc.Errorf(codes.Unauthenticated, "etcdserver: invalid auth token") |
||||||
|
ErrGRPCInvalidAuthMgmt = grpc.Errorf(codes.InvalidArgument, "etcdserver: invalid auth management") |
||||||
|
|
||||||
|
ErrGRPCNoLeader = grpc.Errorf(codes.Unavailable, "etcdserver: no leader") |
||||||
|
ErrGRPCNotCapable = grpc.Errorf(codes.Unavailable, "etcdserver: not capable") |
||||||
|
ErrGRPCStopped = grpc.Errorf(codes.Unavailable, "etcdserver: server stopped") |
||||||
|
ErrGRPCTimeout = grpc.Errorf(codes.Unavailable, "etcdserver: request timed out") |
||||||
|
ErrGRPCTimeoutDueToLeaderFail = grpc.Errorf(codes.Unavailable, "etcdserver: request timed out, possibly due to previous leader failure") |
||||||
|
ErrGRPCTimeoutDueToConnectionLost = grpc.Errorf(codes.Unavailable, "etcdserver: request timed out, possibly due to connection lost") |
||||||
|
ErrGRPCUnhealthy = grpc.Errorf(codes.Unavailable, "etcdserver: unhealthy cluster") |
||||||
|
|
||||||
|
errStringToError = map[string]error{ |
||||||
|
grpc.ErrorDesc(ErrGRPCEmptyKey): ErrGRPCEmptyKey, |
||||||
|
grpc.ErrorDesc(ErrGRPCKeyNotFound): ErrGRPCKeyNotFound, |
||||||
|
grpc.ErrorDesc(ErrGRPCValueProvided): ErrGRPCValueProvided, |
||||||
|
grpc.ErrorDesc(ErrGRPCLeaseProvided): ErrGRPCLeaseProvided, |
||||||
|
|
||||||
|
grpc.ErrorDesc(ErrGRPCTooManyOps): ErrGRPCTooManyOps, |
||||||
|
grpc.ErrorDesc(ErrGRPCDuplicateKey): ErrGRPCDuplicateKey, |
||||||
|
grpc.ErrorDesc(ErrGRPCCompacted): ErrGRPCCompacted, |
||||||
|
grpc.ErrorDesc(ErrGRPCFutureRev): ErrGRPCFutureRev, |
||||||
|
grpc.ErrorDesc(ErrGRPCNoSpace): ErrGRPCNoSpace, |
||||||
|
|
||||||
|
grpc.ErrorDesc(ErrGRPCLeaseNotFound): ErrGRPCLeaseNotFound, |
||||||
|
grpc.ErrorDesc(ErrGRPCLeaseExist): ErrGRPCLeaseExist, |
||||||
|
|
||||||
|
grpc.ErrorDesc(ErrGRPCMemberExist): ErrGRPCMemberExist, |
||||||
|
grpc.ErrorDesc(ErrGRPCPeerURLExist): ErrGRPCPeerURLExist, |
||||||
|
grpc.ErrorDesc(ErrGRPCMemberNotEnoughStarted): ErrGRPCMemberNotEnoughStarted, |
||||||
|
grpc.ErrorDesc(ErrGRPCMemberBadURLs): ErrGRPCMemberBadURLs, |
||||||
|
grpc.ErrorDesc(ErrGRPCMemberNotFound): ErrGRPCMemberNotFound, |
||||||
|
|
||||||
|
grpc.ErrorDesc(ErrGRPCRequestTooLarge): ErrGRPCRequestTooLarge, |
||||||
|
grpc.ErrorDesc(ErrGRPCRequestTooManyRequests): ErrGRPCRequestTooManyRequests, |
||||||
|
|
||||||
|
grpc.ErrorDesc(ErrGRPCRootUserNotExist): ErrGRPCRootUserNotExist, |
||||||
|
grpc.ErrorDesc(ErrGRPCRootRoleNotExist): ErrGRPCRootRoleNotExist, |
||||||
|
grpc.ErrorDesc(ErrGRPCUserAlreadyExist): ErrGRPCUserAlreadyExist, |
||||||
|
grpc.ErrorDesc(ErrGRPCUserEmpty): ErrGRPCUserEmpty, |
||||||
|
grpc.ErrorDesc(ErrGRPCUserNotFound): ErrGRPCUserNotFound, |
||||||
|
grpc.ErrorDesc(ErrGRPCRoleAlreadyExist): ErrGRPCRoleAlreadyExist, |
||||||
|
grpc.ErrorDesc(ErrGRPCRoleNotFound): ErrGRPCRoleNotFound, |
||||||
|
grpc.ErrorDesc(ErrGRPCAuthFailed): ErrGRPCAuthFailed, |
||||||
|
grpc.ErrorDesc(ErrGRPCPermissionDenied): ErrGRPCPermissionDenied, |
||||||
|
grpc.ErrorDesc(ErrGRPCRoleNotGranted): ErrGRPCRoleNotGranted, |
||||||
|
grpc.ErrorDesc(ErrGRPCPermissionNotGranted): ErrGRPCPermissionNotGranted, |
||||||
|
grpc.ErrorDesc(ErrGRPCAuthNotEnabled): ErrGRPCAuthNotEnabled, |
||||||
|
grpc.ErrorDesc(ErrGRPCInvalidAuthToken): ErrGRPCInvalidAuthToken, |
||||||
|
grpc.ErrorDesc(ErrGRPCInvalidAuthMgmt): ErrGRPCInvalidAuthMgmt, |
||||||
|
|
||||||
|
grpc.ErrorDesc(ErrGRPCNoLeader): ErrGRPCNoLeader, |
||||||
|
grpc.ErrorDesc(ErrGRPCNotCapable): ErrGRPCNotCapable, |
||||||
|
grpc.ErrorDesc(ErrGRPCStopped): ErrGRPCStopped, |
||||||
|
grpc.ErrorDesc(ErrGRPCTimeout): ErrGRPCTimeout, |
||||||
|
grpc.ErrorDesc(ErrGRPCTimeoutDueToLeaderFail): ErrGRPCTimeoutDueToLeaderFail, |
||||||
|
grpc.ErrorDesc(ErrGRPCTimeoutDueToConnectionLost): ErrGRPCTimeoutDueToConnectionLost, |
||||||
|
grpc.ErrorDesc(ErrGRPCUnhealthy): ErrGRPCUnhealthy, |
||||||
|
} |
||||||
|
|
||||||
|
// client-side error
|
||||||
|
ErrEmptyKey = Error(ErrGRPCEmptyKey) |
||||||
|
ErrKeyNotFound = Error(ErrGRPCKeyNotFound) |
||||||
|
ErrValueProvided = Error(ErrGRPCValueProvided) |
||||||
|
ErrLeaseProvided = Error(ErrGRPCLeaseProvided) |
||||||
|
ErrTooManyOps = Error(ErrGRPCTooManyOps) |
||||||
|
ErrDuplicateKey = Error(ErrGRPCDuplicateKey) |
||||||
|
ErrCompacted = Error(ErrGRPCCompacted) |
||||||
|
ErrFutureRev = Error(ErrGRPCFutureRev) |
||||||
|
ErrNoSpace = Error(ErrGRPCNoSpace) |
||||||
|
|
||||||
|
ErrLeaseNotFound = Error(ErrGRPCLeaseNotFound) |
||||||
|
ErrLeaseExist = Error(ErrGRPCLeaseExist) |
||||||
|
|
||||||
|
ErrMemberExist = Error(ErrGRPCMemberExist) |
||||||
|
ErrPeerURLExist = Error(ErrGRPCPeerURLExist) |
||||||
|
ErrMemberNotEnoughStarted = Error(ErrGRPCMemberNotEnoughStarted) |
||||||
|
ErrMemberBadURLs = Error(ErrGRPCMemberBadURLs) |
||||||
|
ErrMemberNotFound = Error(ErrGRPCMemberNotFound) |
||||||
|
|
||||||
|
ErrRequestTooLarge = Error(ErrGRPCRequestTooLarge) |
||||||
|
ErrTooManyRequests = Error(ErrGRPCRequestTooManyRequests) |
||||||
|
|
||||||
|
ErrRootUserNotExist = Error(ErrGRPCRootUserNotExist) |
||||||
|
ErrRootRoleNotExist = Error(ErrGRPCRootRoleNotExist) |
||||||
|
ErrUserAlreadyExist = Error(ErrGRPCUserAlreadyExist) |
||||||
|
ErrUserEmpty = Error(ErrGRPCUserEmpty) |
||||||
|
ErrUserNotFound = Error(ErrGRPCUserNotFound) |
||||||
|
ErrRoleAlreadyExist = Error(ErrGRPCRoleAlreadyExist) |
||||||
|
ErrRoleNotFound = Error(ErrGRPCRoleNotFound) |
||||||
|
ErrAuthFailed = Error(ErrGRPCAuthFailed) |
||||||
|
ErrPermissionDenied = Error(ErrGRPCPermissionDenied) |
||||||
|
ErrRoleNotGranted = Error(ErrGRPCRoleNotGranted) |
||||||
|
ErrPermissionNotGranted = Error(ErrGRPCPermissionNotGranted) |
||||||
|
ErrAuthNotEnabled = Error(ErrGRPCAuthNotEnabled) |
||||||
|
ErrInvalidAuthToken = Error(ErrGRPCInvalidAuthToken) |
||||||
|
ErrInvalidAuthMgmt = Error(ErrGRPCInvalidAuthMgmt) |
||||||
|
|
||||||
|
ErrNoLeader = Error(ErrGRPCNoLeader) |
||||||
|
ErrNotCapable = Error(ErrGRPCNotCapable) |
||||||
|
ErrStopped = Error(ErrGRPCStopped) |
||||||
|
ErrTimeout = Error(ErrGRPCTimeout) |
||||||
|
ErrTimeoutDueToLeaderFail = Error(ErrGRPCTimeoutDueToLeaderFail) |
||||||
|
ErrTimeoutDueToConnectionLost = Error(ErrGRPCTimeoutDueToConnectionLost) |
||||||
|
ErrUnhealthy = Error(ErrGRPCUnhealthy) |
||||||
|
) |
||||||
|
|
||||||
|
// EtcdError defines gRPC server errors.
|
||||||
|
// (https://github.com/grpc/grpc-go/blob/master/rpc_util.go#L319-L323)
|
||||||
|
type EtcdError struct { |
||||||
|
code codes.Code |
||||||
|
desc string |
||||||
|
} |
||||||
|
|
||||||
|
// Code returns grpc/codes.Code.
|
||||||
|
// TODO: define clientv3/codes.Code.
|
||||||
|
func (e EtcdError) Code() codes.Code { |
||||||
|
return e.code |
||||||
|
} |
||||||
|
|
||||||
|
func (e EtcdError) Error() string { |
||||||
|
return e.desc |
||||||
|
} |
||||||
|
|
||||||
|
func Error(err error) error { |
||||||
|
if err == nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
verr, ok := errStringToError[grpc.ErrorDesc(err)] |
||||||
|
if !ok { // not gRPC error
|
||||||
|
return err |
||||||
|
} |
||||||
|
return EtcdError{code: grpc.Code(verr), desc: grpc.ErrorDesc(verr)} |
||||||
|
} |
||||||
@ -0,0 +1,20 @@ |
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package rpctypes |
||||||
|
|
||||||
|
var ( |
||||||
|
MetadataRequireLeaderKey = "hasleader" |
||||||
|
MetadataHasLeader = "true" |
||||||
|
) |
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,29 @@ |
|||||||
|
// Copyright 2015 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package main is a simple wrapper of the real etcd entrypoint package
|
||||||
|
// (located at github.com/coreos/etcd/etcdmain) to ensure that etcd is still
|
||||||
|
// "go getable"; e.g. `go get github.com/coreos/etcd` works as expected and
|
||||||
|
// builds a binary in $GOBIN/etcd
|
||||||
|
//
|
||||||
|
// This package should NOT be extended or modified in any way; to modify the
|
||||||
|
// etcd binary, work in the `github.com/coreos/etcd/etcdmain` package.
|
||||||
|
//
|
||||||
|
package main |
||||||
|
|
||||||
|
import "github.com/coreos/etcd/etcdmain" |
||||||
|
|
||||||
|
func main() { |
||||||
|
etcdmain.Main() |
||||||
|
} |
||||||
@ -0,0 +1,735 @@ |
|||||||
|
// Code generated by protoc-gen-gogo.
|
||||||
|
// source: kv.proto
|
||||||
|
// DO NOT EDIT!
|
||||||
|
|
||||||
|
/* |
||||||
|
Package mvccpb is a generated protocol buffer package. |
||||||
|
|
||||||
|
It is generated from these files: |
||||||
|
kv.proto |
||||||
|
|
||||||
|
It has these top-level messages: |
||||||
|
KeyValue |
||||||
|
Event |
||||||
|
*/ |
||||||
|
package mvccpb |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
|
||||||
|
proto "github.com/golang/protobuf/proto" |
||||||
|
|
||||||
|
math "math" |
||||||
|
|
||||||
|
io "io" |
||||||
|
) |
||||||
|
|
||||||
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
|
var _ = proto.Marshal |
||||||
|
var _ = fmt.Errorf |
||||||
|
var _ = math.Inf |
||||||
|
|
||||||
|
// This is a compile-time assertion to ensure that this generated file
|
||||||
|
// is compatible with the proto package it is being compiled against.
|
||||||
|
// A compilation error at this line likely means your copy of the
|
||||||
|
// proto package needs to be updated.
|
||||||
|
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
|
||||||
|
|
||||||
|
type Event_EventType int32 |
||||||
|
|
||||||
|
const ( |
||||||
|
PUT Event_EventType = 0 |
||||||
|
DELETE Event_EventType = 1 |
||||||
|
) |
||||||
|
|
||||||
|
var Event_EventType_name = map[int32]string{ |
||||||
|
0: "PUT", |
||||||
|
1: "DELETE", |
||||||
|
} |
||||||
|
var Event_EventType_value = map[string]int32{ |
||||||
|
"PUT": 0, |
||||||
|
"DELETE": 1, |
||||||
|
} |
||||||
|
|
||||||
|
func (x Event_EventType) String() string { |
||||||
|
return proto.EnumName(Event_EventType_name, int32(x)) |
||||||
|
} |
||||||
|
func (Event_EventType) EnumDescriptor() ([]byte, []int) { return fileDescriptorKv, []int{1, 0} } |
||||||
|
|
||||||
|
type KeyValue struct { |
||||||
|
// key is the key in bytes. An empty key is not allowed.
|
||||||
|
Key []byte `protobuf:"bytes,1,opt,name=key,proto3" json:"key,omitempty"` |
||||||
|
// create_revision is the revision of last creation on this key.
|
||||||
|
CreateRevision int64 `protobuf:"varint,2,opt,name=create_revision,json=createRevision,proto3" json:"create_revision,omitempty"` |
||||||
|
// mod_revision is the revision of last modification on this key.
|
||||||
|
ModRevision int64 `protobuf:"varint,3,opt,name=mod_revision,json=modRevision,proto3" json:"mod_revision,omitempty"` |
||||||
|
// version is the version of the key. A deletion resets
|
||||||
|
// the version to zero and any modification of the key
|
||||||
|
// increases its version.
|
||||||
|
Version int64 `protobuf:"varint,4,opt,name=version,proto3" json:"version,omitempty"` |
||||||
|
// value is the value held by the key, in bytes.
|
||||||
|
Value []byte `protobuf:"bytes,5,opt,name=value,proto3" json:"value,omitempty"` |
||||||
|
// lease is the ID of the lease that attached to key.
|
||||||
|
// When the attached lease expires, the key will be deleted.
|
||||||
|
// If lease is 0, then no lease is attached to the key.
|
||||||
|
Lease int64 `protobuf:"varint,6,opt,name=lease,proto3" json:"lease,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
func (m *KeyValue) Reset() { *m = KeyValue{} } |
||||||
|
func (m *KeyValue) String() string { return proto.CompactTextString(m) } |
||||||
|
func (*KeyValue) ProtoMessage() {} |
||||||
|
func (*KeyValue) Descriptor() ([]byte, []int) { return fileDescriptorKv, []int{0} } |
||||||
|
|
||||||
|
type Event struct { |
||||||
|
// type is the kind of event. If type is a PUT, it indicates
|
||||||
|
// new data has been stored to the key. If type is a DELETE,
|
||||||
|
// it indicates the key was deleted.
|
||||||
|
Type Event_EventType `protobuf:"varint,1,opt,name=type,proto3,enum=mvccpb.Event_EventType" json:"type,omitempty"` |
||||||
|
// kv holds the KeyValue for the event.
|
||||||
|
// A PUT event contains current kv pair.
|
||||||
|
// A PUT event with kv.Version=1 indicates the creation of a key.
|
||||||
|
// A DELETE/EXPIRE event contains the deleted key with
|
||||||
|
// its modification revision set to the revision of deletion.
|
||||||
|
Kv *KeyValue `protobuf:"bytes,2,opt,name=kv" json:"kv,omitempty"` |
||||||
|
// prev_kv holds the key-value pair before the event happens.
|
||||||
|
PrevKv *KeyValue `protobuf:"bytes,3,opt,name=prev_kv,json=prevKv" json:"prev_kv,omitempty"` |
||||||
|
} |
||||||
|
|
||||||
|
func (m *Event) Reset() { *m = Event{} } |
||||||
|
func (m *Event) String() string { return proto.CompactTextString(m) } |
||||||
|
func (*Event) ProtoMessage() {} |
||||||
|
func (*Event) Descriptor() ([]byte, []int) { return fileDescriptorKv, []int{1} } |
||||||
|
|
||||||
|
func init() { |
||||||
|
proto.RegisterType((*KeyValue)(nil), "mvccpb.KeyValue") |
||||||
|
proto.RegisterType((*Event)(nil), "mvccpb.Event") |
||||||
|
proto.RegisterEnum("mvccpb.Event_EventType", Event_EventType_name, Event_EventType_value) |
||||||
|
} |
||||||
|
func (m *KeyValue) 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 *KeyValue) MarshalTo(dAtA []byte) (int, error) { |
||||||
|
var i int |
||||||
|
_ = i |
||||||
|
var l int |
||||||
|
_ = l |
||||||
|
if len(m.Key) > 0 { |
||||||
|
dAtA[i] = 0xa |
||||||
|
i++ |
||||||
|
i = encodeVarintKv(dAtA, i, uint64(len(m.Key))) |
||||||
|
i += copy(dAtA[i:], m.Key) |
||||||
|
} |
||||||
|
if m.CreateRevision != 0 { |
||||||
|
dAtA[i] = 0x10 |
||||||
|
i++ |
||||||
|
i = encodeVarintKv(dAtA, i, uint64(m.CreateRevision)) |
||||||
|
} |
||||||
|
if m.ModRevision != 0 { |
||||||
|
dAtA[i] = 0x18 |
||||||
|
i++ |
||||||
|
i = encodeVarintKv(dAtA, i, uint64(m.ModRevision)) |
||||||
|
} |
||||||
|
if m.Version != 0 { |
||||||
|
dAtA[i] = 0x20 |
||||||
|
i++ |
||||||
|
i = encodeVarintKv(dAtA, i, uint64(m.Version)) |
||||||
|
} |
||||||
|
if len(m.Value) > 0 { |
||||||
|
dAtA[i] = 0x2a |
||||||
|
i++ |
||||||
|
i = encodeVarintKv(dAtA, i, uint64(len(m.Value))) |
||||||
|
i += copy(dAtA[i:], m.Value) |
||||||
|
} |
||||||
|
if m.Lease != 0 { |
||||||
|
dAtA[i] = 0x30 |
||||||
|
i++ |
||||||
|
i = encodeVarintKv(dAtA, i, uint64(m.Lease)) |
||||||
|
} |
||||||
|
return i, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (m *Event) 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 *Event) MarshalTo(dAtA []byte) (int, error) { |
||||||
|
var i int |
||||||
|
_ = i |
||||||
|
var l int |
||||||
|
_ = l |
||||||
|
if m.Type != 0 { |
||||||
|
dAtA[i] = 0x8 |
||||||
|
i++ |
||||||
|
i = encodeVarintKv(dAtA, i, uint64(m.Type)) |
||||||
|
} |
||||||
|
if m.Kv != nil { |
||||||
|
dAtA[i] = 0x12 |
||||||
|
i++ |
||||||
|
i = encodeVarintKv(dAtA, i, uint64(m.Kv.Size())) |
||||||
|
n1, err := m.Kv.MarshalTo(dAtA[i:]) |
||||||
|
if err != nil { |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
i += n1 |
||||||
|
} |
||||||
|
if m.PrevKv != nil { |
||||||
|
dAtA[i] = 0x1a |
||||||
|
i++ |
||||||
|
i = encodeVarintKv(dAtA, i, uint64(m.PrevKv.Size())) |
||||||
|
n2, err := m.PrevKv.MarshalTo(dAtA[i:]) |
||||||
|
if err != nil { |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
i += n2 |
||||||
|
} |
||||||
|
return i, nil |
||||||
|
} |
||||||
|
|
||||||
|
func encodeFixed64Kv(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 encodeFixed32Kv(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 encodeVarintKv(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 *KeyValue) Size() (n int) { |
||||||
|
var l int |
||||||
|
_ = l |
||||||
|
l = len(m.Key) |
||||||
|
if l > 0 { |
||||||
|
n += 1 + l + sovKv(uint64(l)) |
||||||
|
} |
||||||
|
if m.CreateRevision != 0 { |
||||||
|
n += 1 + sovKv(uint64(m.CreateRevision)) |
||||||
|
} |
||||||
|
if m.ModRevision != 0 { |
||||||
|
n += 1 + sovKv(uint64(m.ModRevision)) |
||||||
|
} |
||||||
|
if m.Version != 0 { |
||||||
|
n += 1 + sovKv(uint64(m.Version)) |
||||||
|
} |
||||||
|
l = len(m.Value) |
||||||
|
if l > 0 { |
||||||
|
n += 1 + l + sovKv(uint64(l)) |
||||||
|
} |
||||||
|
if m.Lease != 0 { |
||||||
|
n += 1 + sovKv(uint64(m.Lease)) |
||||||
|
} |
||||||
|
return n |
||||||
|
} |
||||||
|
|
||||||
|
func (m *Event) Size() (n int) { |
||||||
|
var l int |
||||||
|
_ = l |
||||||
|
if m.Type != 0 { |
||||||
|
n += 1 + sovKv(uint64(m.Type)) |
||||||
|
} |
||||||
|
if m.Kv != nil { |
||||||
|
l = m.Kv.Size() |
||||||
|
n += 1 + l + sovKv(uint64(l)) |
||||||
|
} |
||||||
|
if m.PrevKv != nil { |
||||||
|
l = m.PrevKv.Size() |
||||||
|
n += 1 + l + sovKv(uint64(l)) |
||||||
|
} |
||||||
|
return n |
||||||
|
} |
||||||
|
|
||||||
|
func sovKv(x uint64) (n int) { |
||||||
|
for { |
||||||
|
n++ |
||||||
|
x >>= 7 |
||||||
|
if x == 0 { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
return n |
||||||
|
} |
||||||
|
func sozKv(x uint64) (n int) { |
||||||
|
return sovKv(uint64((x << 1) ^ uint64((int64(x) >> 63)))) |
||||||
|
} |
||||||
|
func (m *KeyValue) 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 ErrIntOverflowKv |
||||||
|
} |
||||||
|
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: KeyValue: wiretype end group for non-group") |
||||||
|
} |
||||||
|
if fieldNum <= 0 { |
||||||
|
return fmt.Errorf("proto: KeyValue: illegal tag %d (wire type %d)", fieldNum, wire) |
||||||
|
} |
||||||
|
switch fieldNum { |
||||||
|
case 1: |
||||||
|
if wireType != 2 { |
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field Key", wireType) |
||||||
|
} |
||||||
|
var byteLen int |
||||||
|
for shift := uint(0); ; shift += 7 { |
||||||
|
if shift >= 64 { |
||||||
|
return ErrIntOverflowKv |
||||||
|
} |
||||||
|
if iNdEx >= l { |
||||||
|
return io.ErrUnexpectedEOF |
||||||
|
} |
||||||
|
b := dAtA[iNdEx] |
||||||
|
iNdEx++ |
||||||
|
byteLen |= (int(b) & 0x7F) << shift |
||||||
|
if b < 0x80 { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
if byteLen < 0 { |
||||||
|
return ErrInvalidLengthKv |
||||||
|
} |
||||||
|
postIndex := iNdEx + byteLen |
||||||
|
if postIndex > l { |
||||||
|
return io.ErrUnexpectedEOF |
||||||
|
} |
||||||
|
m.Key = append(m.Key[:0], dAtA[iNdEx:postIndex]...) |
||||||
|
if m.Key == nil { |
||||||
|
m.Key = []byte{} |
||||||
|
} |
||||||
|
iNdEx = postIndex |
||||||
|
case 2: |
||||||
|
if wireType != 0 { |
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field CreateRevision", wireType) |
||||||
|
} |
||||||
|
m.CreateRevision = 0 |
||||||
|
for shift := uint(0); ; shift += 7 { |
||||||
|
if shift >= 64 { |
||||||
|
return ErrIntOverflowKv |
||||||
|
} |
||||||
|
if iNdEx >= l { |
||||||
|
return io.ErrUnexpectedEOF |
||||||
|
} |
||||||
|
b := dAtA[iNdEx] |
||||||
|
iNdEx++ |
||||||
|
m.CreateRevision |= (int64(b) & 0x7F) << shift |
||||||
|
if b < 0x80 { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
case 3: |
||||||
|
if wireType != 0 { |
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field ModRevision", wireType) |
||||||
|
} |
||||||
|
m.ModRevision = 0 |
||||||
|
for shift := uint(0); ; shift += 7 { |
||||||
|
if shift >= 64 { |
||||||
|
return ErrIntOverflowKv |
||||||
|
} |
||||||
|
if iNdEx >= l { |
||||||
|
return io.ErrUnexpectedEOF |
||||||
|
} |
||||||
|
b := dAtA[iNdEx] |
||||||
|
iNdEx++ |
||||||
|
m.ModRevision |= (int64(b) & 0x7F) << shift |
||||||
|
if b < 0x80 { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
case 4: |
||||||
|
if wireType != 0 { |
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field Version", wireType) |
||||||
|
} |
||||||
|
m.Version = 0 |
||||||
|
for shift := uint(0); ; shift += 7 { |
||||||
|
if shift >= 64 { |
||||||
|
return ErrIntOverflowKv |
||||||
|
} |
||||||
|
if iNdEx >= l { |
||||||
|
return io.ErrUnexpectedEOF |
||||||
|
} |
||||||
|
b := dAtA[iNdEx] |
||||||
|
iNdEx++ |
||||||
|
m.Version |= (int64(b) & 0x7F) << shift |
||||||
|
if b < 0x80 { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
case 5: |
||||||
|
if wireType != 2 { |
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field Value", wireType) |
||||||
|
} |
||||||
|
var byteLen int |
||||||
|
for shift := uint(0); ; shift += 7 { |
||||||
|
if shift >= 64 { |
||||||
|
return ErrIntOverflowKv |
||||||
|
} |
||||||
|
if iNdEx >= l { |
||||||
|
return io.ErrUnexpectedEOF |
||||||
|
} |
||||||
|
b := dAtA[iNdEx] |
||||||
|
iNdEx++ |
||||||
|
byteLen |= (int(b) & 0x7F) << shift |
||||||
|
if b < 0x80 { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
if byteLen < 0 { |
||||||
|
return ErrInvalidLengthKv |
||||||
|
} |
||||||
|
postIndex := iNdEx + byteLen |
||||||
|
if postIndex > l { |
||||||
|
return io.ErrUnexpectedEOF |
||||||
|
} |
||||||
|
m.Value = append(m.Value[:0], dAtA[iNdEx:postIndex]...) |
||||||
|
if m.Value == nil { |
||||||
|
m.Value = []byte{} |
||||||
|
} |
||||||
|
iNdEx = postIndex |
||||||
|
case 6: |
||||||
|
if wireType != 0 { |
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field Lease", wireType) |
||||||
|
} |
||||||
|
m.Lease = 0 |
||||||
|
for shift := uint(0); ; shift += 7 { |
||||||
|
if shift >= 64 { |
||||||
|
return ErrIntOverflowKv |
||||||
|
} |
||||||
|
if iNdEx >= l { |
||||||
|
return io.ErrUnexpectedEOF |
||||||
|
} |
||||||
|
b := dAtA[iNdEx] |
||||||
|
iNdEx++ |
||||||
|
m.Lease |= (int64(b) & 0x7F) << shift |
||||||
|
if b < 0x80 { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
default: |
||||||
|
iNdEx = preIndex |
||||||
|
skippy, err := skipKv(dAtA[iNdEx:]) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if skippy < 0 { |
||||||
|
return ErrInvalidLengthKv |
||||||
|
} |
||||||
|
if (iNdEx + skippy) > l { |
||||||
|
return io.ErrUnexpectedEOF |
||||||
|
} |
||||||
|
iNdEx += skippy |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if iNdEx > l { |
||||||
|
return io.ErrUnexpectedEOF |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
func (m *Event) 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 ErrIntOverflowKv |
||||||
|
} |
||||||
|
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: Event: wiretype end group for non-group") |
||||||
|
} |
||||||
|
if fieldNum <= 0 { |
||||||
|
return fmt.Errorf("proto: Event: illegal tag %d (wire type %d)", fieldNum, wire) |
||||||
|
} |
||||||
|
switch fieldNum { |
||||||
|
case 1: |
||||||
|
if wireType != 0 { |
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field Type", wireType) |
||||||
|
} |
||||||
|
m.Type = 0 |
||||||
|
for shift := uint(0); ; shift += 7 { |
||||||
|
if shift >= 64 { |
||||||
|
return ErrIntOverflowKv |
||||||
|
} |
||||||
|
if iNdEx >= l { |
||||||
|
return io.ErrUnexpectedEOF |
||||||
|
} |
||||||
|
b := dAtA[iNdEx] |
||||||
|
iNdEx++ |
||||||
|
m.Type |= (Event_EventType(b) & 0x7F) << shift |
||||||
|
if b < 0x80 { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
case 2: |
||||||
|
if wireType != 2 { |
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field Kv", wireType) |
||||||
|
} |
||||||
|
var msglen int |
||||||
|
for shift := uint(0); ; shift += 7 { |
||||||
|
if shift >= 64 { |
||||||
|
return ErrIntOverflowKv |
||||||
|
} |
||||||
|
if iNdEx >= l { |
||||||
|
return io.ErrUnexpectedEOF |
||||||
|
} |
||||||
|
b := dAtA[iNdEx] |
||||||
|
iNdEx++ |
||||||
|
msglen |= (int(b) & 0x7F) << shift |
||||||
|
if b < 0x80 { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
if msglen < 0 { |
||||||
|
return ErrInvalidLengthKv |
||||||
|
} |
||||||
|
postIndex := iNdEx + msglen |
||||||
|
if postIndex > l { |
||||||
|
return io.ErrUnexpectedEOF |
||||||
|
} |
||||||
|
if m.Kv == nil { |
||||||
|
m.Kv = &KeyValue{} |
||||||
|
} |
||||||
|
if err := m.Kv.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
iNdEx = postIndex |
||||||
|
case 3: |
||||||
|
if wireType != 2 { |
||||||
|
return fmt.Errorf("proto: wrong wireType = %d for field PrevKv", wireType) |
||||||
|
} |
||||||
|
var msglen int |
||||||
|
for shift := uint(0); ; shift += 7 { |
||||||
|
if shift >= 64 { |
||||||
|
return ErrIntOverflowKv |
||||||
|
} |
||||||
|
if iNdEx >= l { |
||||||
|
return io.ErrUnexpectedEOF |
||||||
|
} |
||||||
|
b := dAtA[iNdEx] |
||||||
|
iNdEx++ |
||||||
|
msglen |= (int(b) & 0x7F) << shift |
||||||
|
if b < 0x80 { |
||||||
|
break |
||||||
|
} |
||||||
|
} |
||||||
|
if msglen < 0 { |
||||||
|
return ErrInvalidLengthKv |
||||||
|
} |
||||||
|
postIndex := iNdEx + msglen |
||||||
|
if postIndex > l { |
||||||
|
return io.ErrUnexpectedEOF |
||||||
|
} |
||||||
|
if m.PrevKv == nil { |
||||||
|
m.PrevKv = &KeyValue{} |
||||||
|
} |
||||||
|
if err := m.PrevKv.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
iNdEx = postIndex |
||||||
|
default: |
||||||
|
iNdEx = preIndex |
||||||
|
skippy, err := skipKv(dAtA[iNdEx:]) |
||||||
|
if err != nil { |
||||||
|
return err |
||||||
|
} |
||||||
|
if skippy < 0 { |
||||||
|
return ErrInvalidLengthKv |
||||||
|
} |
||||||
|
if (iNdEx + skippy) > l { |
||||||
|
return io.ErrUnexpectedEOF |
||||||
|
} |
||||||
|
iNdEx += skippy |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if iNdEx > l { |
||||||
|
return io.ErrUnexpectedEOF |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
func skipKv(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, ErrIntOverflowKv |
||||||
|
} |
||||||
|
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, ErrIntOverflowKv |
||||||
|
} |
||||||
|
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, ErrIntOverflowKv |
||||||
|
} |
||||||
|
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, ErrInvalidLengthKv |
||||||
|
} |
||||||
|
return iNdEx, nil |
||||||
|
case 3: |
||||||
|
for { |
||||||
|
var innerWire uint64 |
||||||
|
var start int = iNdEx |
||||||
|
for shift := uint(0); ; shift += 7 { |
||||||
|
if shift >= 64 { |
||||||
|
return 0, ErrIntOverflowKv |
||||||
|
} |
||||||
|
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 := skipKv(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 ( |
||||||
|
ErrInvalidLengthKv = fmt.Errorf("proto: negative length found during unmarshaling") |
||||||
|
ErrIntOverflowKv = fmt.Errorf("proto: integer overflow") |
||||||
|
) |
||||||
|
|
||||||
|
func init() { proto.RegisterFile("kv.proto", fileDescriptorKv) } |
||||||
|
|
||||||
|
var fileDescriptorKv = []byte{ |
||||||
|
// 303 bytes of a gzipped FileDescriptorProto
|
||||||
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x90, 0x41, 0x4e, 0xc2, 0x40, |
||||||
|
0x14, 0x86, 0x3b, 0x14, 0x0a, 0x3e, 0x08, 0x36, 0x13, 0x12, 0x27, 0x2e, 0x26, 0x95, 0x8d, 0x18, |
||||||
|
0x13, 0x4c, 0xf0, 0x06, 0xc6, 0xae, 0x70, 0x61, 0x1a, 0x74, 0x4b, 0x4a, 0x79, 0x21, 0xa4, 0x94, |
||||||
|
0x69, 0x4a, 0x9d, 0xa4, 0x37, 0x71, 0xef, 0xde, 0x73, 0xb0, 0xe4, 0x08, 0x52, 0x2f, 0x62, 0xfa, |
||||||
|
0xc6, 0xe2, 0xc6, 0xcd, 0xe4, 0xfd, 0xff, 0xff, 0x65, 0xe6, 0x7f, 0x03, 0x9d, 0x58, 0x8f, 0xd3, |
||||||
|
0x4c, 0xe5, 0x8a, 0x3b, 0x89, 0x8e, 0xa2, 0x74, 0x71, 0x39, 0x58, 0xa9, 0x95, 0x22, 0xeb, 0xae, |
||||||
|
0x9a, 0x4c, 0x3a, 0xfc, 0x64, 0xd0, 0x99, 0x62, 0xf1, 0x1a, 0x6e, 0xde, 0x90, 0xbb, 0x60, 0xc7, |
||||||
|
0x58, 0x08, 0xe6, 0xb1, 0x51, 0x2f, 0xa8, 0x46, 0x7e, 0x0d, 0xe7, 0x51, 0x86, 0x61, 0x8e, 0xf3, |
||||||
|
0x0c, 0xf5, 0x7a, 0xb7, 0x56, 0x5b, 0xd1, 0xf0, 0xd8, 0xc8, 0x0e, 0xfa, 0xc6, 0x0e, 0x7e, 0x5d, |
||||||
|
0x7e, 0x05, 0xbd, 0x44, 0x2d, 0xff, 0x28, 0x9b, 0xa8, 0x6e, 0xa2, 0x96, 0x27, 0x44, 0x40, 0x5b, |
||||||
|
0x63, 0x46, 0x69, 0x93, 0xd2, 0x5a, 0xf2, 0x01, 0xb4, 0x74, 0x55, 0x40, 0xb4, 0xe8, 0x65, 0x23, |
||||||
|
0x2a, 0x77, 0x83, 0xe1, 0x0e, 0x85, 0x43, 0xb4, 0x11, 0xc3, 0x0f, 0x06, 0x2d, 0x5f, 0xe3, 0x36, |
||||||
|
0xe7, 0xb7, 0xd0, 0xcc, 0x8b, 0x14, 0xa9, 0x6e, 0x7f, 0x72, 0x31, 0x36, 0x7b, 0x8e, 0x29, 0x34, |
||||||
|
0xe7, 0xac, 0x48, 0x31, 0x20, 0x88, 0x7b, 0xd0, 0x88, 0x35, 0x75, 0xef, 0x4e, 0xdc, 0x1a, 0xad, |
||||||
|
0x17, 0x0f, 0x1a, 0xb1, 0xe6, 0x37, 0xd0, 0x4e, 0x33, 0xd4, 0xf3, 0x58, 0x53, 0xf9, 0xff, 0x30, |
||||||
|
0xa7, 0x02, 0xa6, 0x7a, 0xe8, 0xc1, 0xd9, 0xe9, 0x7e, 0xde, 0x06, 0xfb, 0xf9, 0x65, 0xe6, 0x5a, |
||||||
|
0x1c, 0xc0, 0x79, 0xf4, 0x9f, 0xfc, 0x99, 0xef, 0xb2, 0x07, 0xb1, 0x3f, 0x4a, 0xeb, 0x70, 0x94, |
||||||
|
0xd6, 0xbe, 0x94, 0xec, 0x50, 0x4a, 0xf6, 0x55, 0x4a, 0xf6, 0xfe, 0x2d, 0xad, 0x85, 0x43, 0xff, |
||||||
|
0x7e, 0xff, 0x13, 0x00, 0x00, 0xff, 0xff, 0xb5, 0x45, 0x92, 0x5d, 0xa1, 0x01, 0x00, 0x00, |
||||||
|
} |
||||||
@ -0,0 +1,16 @@ |
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package tlsutil provides utility functions for handling TLS.
|
||||||
|
package tlsutil |
||||||
@ -0,0 +1,72 @@ |
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package tlsutil |
||||||
|
|
||||||
|
import ( |
||||||
|
"crypto/tls" |
||||||
|
"crypto/x509" |
||||||
|
"encoding/pem" |
||||||
|
"io/ioutil" |
||||||
|
) |
||||||
|
|
||||||
|
// NewCertPool creates x509 certPool with provided CA files.
|
||||||
|
func NewCertPool(CAFiles []string) (*x509.CertPool, error) { |
||||||
|
certPool := x509.NewCertPool() |
||||||
|
|
||||||
|
for _, CAFile := range CAFiles { |
||||||
|
pemByte, err := ioutil.ReadFile(CAFile) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
for { |
||||||
|
var block *pem.Block |
||||||
|
block, pemByte = pem.Decode(pemByte) |
||||||
|
if block == nil { |
||||||
|
break |
||||||
|
} |
||||||
|
cert, err := x509.ParseCertificate(block.Bytes) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
certPool.AddCert(cert) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
return certPool, nil |
||||||
|
} |
||||||
|
|
||||||
|
// NewCert generates TLS cert by using the given cert,key and parse function.
|
||||||
|
func NewCert(certfile, keyfile string, parseFunc func([]byte, []byte) (tls.Certificate, error)) (*tls.Certificate, error) { |
||||||
|
cert, err := ioutil.ReadFile(certfile) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
key, err := ioutil.ReadFile(keyfile) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
if parseFunc == nil { |
||||||
|
parseFunc = tls.X509KeyPair |
||||||
|
} |
||||||
|
|
||||||
|
tlsCert, err := parseFunc(cert, key) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return &tlsCert, nil |
||||||
|
} |
||||||
@ -0,0 +1,17 @@ |
|||||||
|
// Copyright 2015 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package transport implements various HTTP transport utilities based on Go
|
||||||
|
// net package.
|
||||||
|
package transport |
||||||
@ -0,0 +1,94 @@ |
|||||||
|
// Copyright 2015 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package transport |
||||||
|
|
||||||
|
import ( |
||||||
|
"crypto/tls" |
||||||
|
"fmt" |
||||||
|
"net" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
type keepAliveConn interface { |
||||||
|
SetKeepAlive(bool) error |
||||||
|
SetKeepAlivePeriod(d time.Duration) error |
||||||
|
} |
||||||
|
|
||||||
|
// NewKeepAliveListener returns a listener that listens on the given address.
|
||||||
|
// Be careful when wrap around KeepAliveListener with another Listener if TLSInfo is not nil.
|
||||||
|
// Some pkgs (like go/http) might expect Listener to return TLSConn type to start TLS handshake.
|
||||||
|
// http://tldp.org/HOWTO/TCP-Keepalive-HOWTO/overview.html
|
||||||
|
func NewKeepAliveListener(l net.Listener, scheme string, tlscfg *tls.Config) (net.Listener, error) { |
||||||
|
if scheme == "https" { |
||||||
|
if tlscfg == nil { |
||||||
|
return nil, fmt.Errorf("cannot listen on TLS for given listener: KeyFile and CertFile are not presented") |
||||||
|
} |
||||||
|
return newTLSKeepaliveListener(l, tlscfg), nil |
||||||
|
} |
||||||
|
|
||||||
|
return &keepaliveListener{ |
||||||
|
Listener: l, |
||||||
|
}, nil |
||||||
|
} |
||||||
|
|
||||||
|
type keepaliveListener struct{ net.Listener } |
||||||
|
|
||||||
|
func (kln *keepaliveListener) Accept() (net.Conn, error) { |
||||||
|
c, err := kln.Listener.Accept() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
kac := c.(keepAliveConn) |
||||||
|
// detection time: tcp_keepalive_time + tcp_keepalive_probes + tcp_keepalive_intvl
|
||||||
|
// default on linux: 30 + 8 * 30
|
||||||
|
// default on osx: 30 + 8 * 75
|
||||||
|
kac.SetKeepAlive(true) |
||||||
|
kac.SetKeepAlivePeriod(30 * time.Second) |
||||||
|
return c, nil |
||||||
|
} |
||||||
|
|
||||||
|
// A tlsKeepaliveListener implements a network listener (net.Listener) for TLS connections.
|
||||||
|
type tlsKeepaliveListener struct { |
||||||
|
net.Listener |
||||||
|
config *tls.Config |
||||||
|
} |
||||||
|
|
||||||
|
// Accept waits for and returns the next incoming TLS connection.
|
||||||
|
// The returned connection c is a *tls.Conn.
|
||||||
|
func (l *tlsKeepaliveListener) Accept() (c net.Conn, err error) { |
||||||
|
c, err = l.Listener.Accept() |
||||||
|
if err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
kac := c.(keepAliveConn) |
||||||
|
// detection time: tcp_keepalive_time + tcp_keepalive_probes + tcp_keepalive_intvl
|
||||||
|
// default on linux: 30 + 8 * 30
|
||||||
|
// default on osx: 30 + 8 * 75
|
||||||
|
kac.SetKeepAlive(true) |
||||||
|
kac.SetKeepAlivePeriod(30 * time.Second) |
||||||
|
c = tls.Server(c, l.config) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
// NewListener creates a Listener which accepts connections from an inner
|
||||||
|
// Listener and wraps each connection with Server.
|
||||||
|
// The configuration config must be non-nil and must have
|
||||||
|
// at least one certificate.
|
||||||
|
func newTLSKeepaliveListener(inner net.Listener, config *tls.Config) net.Listener { |
||||||
|
l := &tlsKeepaliveListener{} |
||||||
|
l.Listener = inner |
||||||
|
l.config = config |
||||||
|
return l |
||||||
|
} |
||||||
@ -0,0 +1,80 @@ |
|||||||
|
// Copyright 2013 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
// Package transport provides network utility functions, complementing the more
|
||||||
|
// common ones in the net package.
|
||||||
|
package transport |
||||||
|
|
||||||
|
import ( |
||||||
|
"errors" |
||||||
|
"net" |
||||||
|
"sync" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
var ( |
||||||
|
ErrNotTCP = errors.New("only tcp connections have keepalive") |
||||||
|
) |
||||||
|
|
||||||
|
// LimitListener returns a Listener that accepts at most n simultaneous
|
||||||
|
// connections from the provided Listener.
|
||||||
|
func LimitListener(l net.Listener, n int) net.Listener { |
||||||
|
return &limitListener{l, make(chan struct{}, n)} |
||||||
|
} |
||||||
|
|
||||||
|
type limitListener struct { |
||||||
|
net.Listener |
||||||
|
sem chan struct{} |
||||||
|
} |
||||||
|
|
||||||
|
func (l *limitListener) acquire() { l.sem <- struct{}{} } |
||||||
|
func (l *limitListener) release() { <-l.sem } |
||||||
|
|
||||||
|
func (l *limitListener) Accept() (net.Conn, error) { |
||||||
|
l.acquire() |
||||||
|
c, err := l.Listener.Accept() |
||||||
|
if err != nil { |
||||||
|
l.release() |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return &limitListenerConn{Conn: c, release: l.release}, nil |
||||||
|
} |
||||||
|
|
||||||
|
type limitListenerConn struct { |
||||||
|
net.Conn |
||||||
|
releaseOnce sync.Once |
||||||
|
release func() |
||||||
|
} |
||||||
|
|
||||||
|
func (l *limitListenerConn) Close() error { |
||||||
|
err := l.Conn.Close() |
||||||
|
l.releaseOnce.Do(l.release) |
||||||
|
return err |
||||||
|
} |
||||||
|
|
||||||
|
func (l *limitListenerConn) SetKeepAlive(doKeepAlive bool) error { |
||||||
|
tcpc, ok := l.Conn.(*net.TCPConn) |
||||||
|
if !ok { |
||||||
|
return ErrNotTCP |
||||||
|
} |
||||||
|
return tcpc.SetKeepAlive(doKeepAlive) |
||||||
|
} |
||||||
|
|
||||||
|
func (l *limitListenerConn) SetKeepAlivePeriod(d time.Duration) error { |
||||||
|
tcpc, ok := l.Conn.(*net.TCPConn) |
||||||
|
if !ok { |
||||||
|
return ErrNotTCP |
||||||
|
} |
||||||
|
return tcpc.SetKeepAlivePeriod(d) |
||||||
|
} |
||||||
@ -0,0 +1,260 @@ |
|||||||
|
// Copyright 2015 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package transport |
||||||
|
|
||||||
|
import ( |
||||||
|
"crypto/ecdsa" |
||||||
|
"crypto/elliptic" |
||||||
|
"crypto/rand" |
||||||
|
"crypto/tls" |
||||||
|
"crypto/x509" |
||||||
|
"crypto/x509/pkix" |
||||||
|
"encoding/pem" |
||||||
|
"fmt" |
||||||
|
"math/big" |
||||||
|
"net" |
||||||
|
"os" |
||||||
|
"path/filepath" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
|
||||||
|
"github.com/coreos/etcd/pkg/tlsutil" |
||||||
|
) |
||||||
|
|
||||||
|
func NewListener(addr, scheme string, tlsinfo *TLSInfo) (l net.Listener, err error) { |
||||||
|
if l, err = newListener(addr, scheme); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return wrapTLS(addr, scheme, tlsinfo, l) |
||||||
|
} |
||||||
|
|
||||||
|
func newListener(addr string, scheme string) (net.Listener, error) { |
||||||
|
if scheme == "unix" || scheme == "unixs" { |
||||||
|
// unix sockets via unix://laddr
|
||||||
|
return NewUnixListener(addr) |
||||||
|
} |
||||||
|
return net.Listen("tcp", addr) |
||||||
|
} |
||||||
|
|
||||||
|
func wrapTLS(addr, scheme string, tlsinfo *TLSInfo, l net.Listener) (net.Listener, error) { |
||||||
|
if scheme != "https" && scheme != "unixs" { |
||||||
|
return l, nil |
||||||
|
} |
||||||
|
return newTLSListener(l, tlsinfo) |
||||||
|
} |
||||||
|
|
||||||
|
type TLSInfo struct { |
||||||
|
CertFile string |
||||||
|
KeyFile string |
||||||
|
CAFile string |
||||||
|
TrustedCAFile string |
||||||
|
ClientCertAuth bool |
||||||
|
|
||||||
|
// ServerName ensures the cert matches the given host in case of discovery / virtual hosting
|
||||||
|
ServerName string |
||||||
|
|
||||||
|
// HandshakeFailure is optionally called when a connection fails to handshake. The
|
||||||
|
// connection will be closed immediately afterwards.
|
||||||
|
HandshakeFailure func(*tls.Conn, error) |
||||||
|
|
||||||
|
selfCert bool |
||||||
|
|
||||||
|
// parseFunc exists to simplify testing. Typically, parseFunc
|
||||||
|
// should be left nil. In that case, tls.X509KeyPair will be used.
|
||||||
|
parseFunc func([]byte, []byte) (tls.Certificate, error) |
||||||
|
} |
||||||
|
|
||||||
|
func (info TLSInfo) String() string { |
||||||
|
return fmt.Sprintf("cert = %s, key = %s, ca = %s, trusted-ca = %s, client-cert-auth = %v", info.CertFile, info.KeyFile, info.CAFile, info.TrustedCAFile, info.ClientCertAuth) |
||||||
|
} |
||||||
|
|
||||||
|
func (info TLSInfo) Empty() bool { |
||||||
|
return info.CertFile == "" && info.KeyFile == "" |
||||||
|
} |
||||||
|
|
||||||
|
func SelfCert(dirpath string, hosts []string) (info TLSInfo, err error) { |
||||||
|
if err = os.MkdirAll(dirpath, 0700); err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
certPath := filepath.Join(dirpath, "cert.pem") |
||||||
|
keyPath := filepath.Join(dirpath, "key.pem") |
||||||
|
_, errcert := os.Stat(certPath) |
||||||
|
_, errkey := os.Stat(keyPath) |
||||||
|
if errcert == nil && errkey == nil { |
||||||
|
info.CertFile = certPath |
||||||
|
info.KeyFile = keyPath |
||||||
|
info.selfCert = true |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
serialNumberLimit := new(big.Int).Lsh(big.NewInt(1), 128) |
||||||
|
serialNumber, err := rand.Int(rand.Reader, serialNumberLimit) |
||||||
|
if err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
tmpl := x509.Certificate{ |
||||||
|
SerialNumber: serialNumber, |
||||||
|
Subject: pkix.Name{Organization: []string{"etcd"}}, |
||||||
|
NotBefore: time.Now(), |
||||||
|
NotAfter: time.Now().Add(365 * (24 * time.Hour)), |
||||||
|
|
||||||
|
KeyUsage: x509.KeyUsageKeyEncipherment | x509.KeyUsageDigitalSignature, |
||||||
|
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth}, |
||||||
|
BasicConstraintsValid: true, |
||||||
|
} |
||||||
|
|
||||||
|
for _, host := range hosts { |
||||||
|
h, _, _ := net.SplitHostPort(host) |
||||||
|
if ip := net.ParseIP(h); ip != nil { |
||||||
|
tmpl.IPAddresses = append(tmpl.IPAddresses, ip) |
||||||
|
} else { |
||||||
|
tmpl.DNSNames = append(tmpl.DNSNames, h) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
priv, err := ecdsa.GenerateKey(elliptic.P521(), rand.Reader) |
||||||
|
if err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
derBytes, err := x509.CreateCertificate(rand.Reader, &tmpl, &tmpl, &priv.PublicKey, priv) |
||||||
|
if err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
certOut, err := os.Create(certPath) |
||||||
|
if err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
pem.Encode(certOut, &pem.Block{Type: "CERTIFICATE", Bytes: derBytes}) |
||||||
|
certOut.Close() |
||||||
|
|
||||||
|
b, err := x509.MarshalECPrivateKey(priv) |
||||||
|
if err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
keyOut, err := os.OpenFile(keyPath, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0600) |
||||||
|
if err != nil { |
||||||
|
return |
||||||
|
} |
||||||
|
pem.Encode(keyOut, &pem.Block{Type: "EC PRIVATE KEY", Bytes: b}) |
||||||
|
keyOut.Close() |
||||||
|
|
||||||
|
return SelfCert(dirpath, hosts) |
||||||
|
} |
||||||
|
|
||||||
|
func (info TLSInfo) baseConfig() (*tls.Config, error) { |
||||||
|
if info.KeyFile == "" || info.CertFile == "" { |
||||||
|
return nil, fmt.Errorf("KeyFile and CertFile must both be present[key: %v, cert: %v]", info.KeyFile, info.CertFile) |
||||||
|
} |
||||||
|
|
||||||
|
tlsCert, err := tlsutil.NewCert(info.CertFile, info.KeyFile, info.parseFunc) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
cfg := &tls.Config{ |
||||||
|
Certificates: []tls.Certificate{*tlsCert}, |
||||||
|
MinVersion: tls.VersionTLS12, |
||||||
|
ServerName: info.ServerName, |
||||||
|
} |
||||||
|
// this only reloads certs when there's a client request
|
||||||
|
// TODO: support server-side refresh (e.g. inotify, SIGHUP), caching
|
||||||
|
cfg.GetCertificate = func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) { |
||||||
|
return tlsutil.NewCert(info.CertFile, info.KeyFile, info.parseFunc) |
||||||
|
} |
||||||
|
cfg.GetClientCertificate = func(unused *tls.CertificateRequestInfo) (*tls.Certificate, error) { |
||||||
|
return tlsutil.NewCert(info.CertFile, info.KeyFile, info.parseFunc) |
||||||
|
} |
||||||
|
return cfg, nil |
||||||
|
} |
||||||
|
|
||||||
|
// cafiles returns a list of CA file paths.
|
||||||
|
func (info TLSInfo) cafiles() []string { |
||||||
|
cs := make([]string, 0) |
||||||
|
if info.CAFile != "" { |
||||||
|
cs = append(cs, info.CAFile) |
||||||
|
} |
||||||
|
if info.TrustedCAFile != "" { |
||||||
|
cs = append(cs, info.TrustedCAFile) |
||||||
|
} |
||||||
|
return cs |
||||||
|
} |
||||||
|
|
||||||
|
// ServerConfig generates a tls.Config object for use by an HTTP server.
|
||||||
|
func (info TLSInfo) ServerConfig() (*tls.Config, error) { |
||||||
|
cfg, err := info.baseConfig() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
cfg.ClientAuth = tls.NoClientCert |
||||||
|
if info.CAFile != "" || info.ClientCertAuth { |
||||||
|
cfg.ClientAuth = tls.RequireAndVerifyClientCert |
||||||
|
} |
||||||
|
|
||||||
|
CAFiles := info.cafiles() |
||||||
|
if len(CAFiles) > 0 { |
||||||
|
cp, err := tlsutil.NewCertPool(CAFiles) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
cfg.ClientCAs = cp |
||||||
|
} |
||||||
|
|
||||||
|
// "h2" NextProtos is necessary for enabling HTTP2 for go's HTTP server
|
||||||
|
cfg.NextProtos = []string{"h2"} |
||||||
|
|
||||||
|
return cfg, nil |
||||||
|
} |
||||||
|
|
||||||
|
// ClientConfig generates a tls.Config object for use by an HTTP client.
|
||||||
|
func (info TLSInfo) ClientConfig() (*tls.Config, error) { |
||||||
|
var cfg *tls.Config |
||||||
|
var err error |
||||||
|
|
||||||
|
if !info.Empty() { |
||||||
|
cfg, err = info.baseConfig() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} else { |
||||||
|
cfg = &tls.Config{ServerName: info.ServerName} |
||||||
|
} |
||||||
|
|
||||||
|
CAFiles := info.cafiles() |
||||||
|
if len(CAFiles) > 0 { |
||||||
|
cfg.RootCAs, err = tlsutil.NewCertPool(CAFiles) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if info.selfCert { |
||||||
|
cfg.InsecureSkipVerify = true |
||||||
|
} |
||||||
|
return cfg, nil |
||||||
|
} |
||||||
|
|
||||||
|
// IsClosedConnError returns true if the error is from closing listener, cmux.
|
||||||
|
// copied from golang.org/x/net/http2/http2.go
|
||||||
|
func IsClosedConnError(err error) bool { |
||||||
|
// 'use of closed network connection' (Go <=1.8)
|
||||||
|
// 'use of closed file or network connection' (Go >1.8, internal/poll.ErrClosing)
|
||||||
|
// 'mux: listener closed' (cmux.ErrListenerClosed)
|
||||||
|
return err != nil && strings.Contains(err.Error(), "closed") |
||||||
|
} |
||||||
@ -0,0 +1,217 @@ |
|||||||
|
// Copyright 2017 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package transport |
||||||
|
|
||||||
|
import ( |
||||||
|
"context" |
||||||
|
"crypto/tls" |
||||||
|
"crypto/x509" |
||||||
|
"fmt" |
||||||
|
"net" |
||||||
|
"strings" |
||||||
|
"sync" |
||||||
|
) |
||||||
|
|
||||||
|
// tlsListener overrides a TLS listener so it will reject client
|
||||||
|
// certificates with insufficient SAN credentials.
|
||||||
|
type tlsListener struct { |
||||||
|
net.Listener |
||||||
|
connc chan net.Conn |
||||||
|
donec chan struct{} |
||||||
|
err error |
||||||
|
handshakeFailure func(*tls.Conn, error) |
||||||
|
} |
||||||
|
|
||||||
|
func newTLSListener(l net.Listener, tlsinfo *TLSInfo) (net.Listener, error) { |
||||||
|
if tlsinfo == nil || tlsinfo.Empty() { |
||||||
|
l.Close() |
||||||
|
return nil, fmt.Errorf("cannot listen on TLS for %s: KeyFile and CertFile are not presented", l.Addr().String()) |
||||||
|
} |
||||||
|
tlscfg, err := tlsinfo.ServerConfig() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
hf := tlsinfo.HandshakeFailure |
||||||
|
if hf == nil { |
||||||
|
hf = func(*tls.Conn, error) {} |
||||||
|
} |
||||||
|
tlsl := &tlsListener{ |
||||||
|
Listener: tls.NewListener(l, tlscfg), |
||||||
|
connc: make(chan net.Conn), |
||||||
|
donec: make(chan struct{}), |
||||||
|
handshakeFailure: hf, |
||||||
|
} |
||||||
|
go tlsl.acceptLoop() |
||||||
|
return tlsl, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (l *tlsListener) Accept() (net.Conn, error) { |
||||||
|
select { |
||||||
|
case conn := <-l.connc: |
||||||
|
return conn, nil |
||||||
|
case <-l.donec: |
||||||
|
return nil, l.err |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
// acceptLoop launches each TLS handshake in a separate goroutine
|
||||||
|
// to prevent a hanging TLS connection from blocking other connections.
|
||||||
|
func (l *tlsListener) acceptLoop() { |
||||||
|
var wg sync.WaitGroup |
||||||
|
var pendingMu sync.Mutex |
||||||
|
|
||||||
|
pending := make(map[net.Conn]struct{}) |
||||||
|
ctx, cancel := context.WithCancel(context.Background()) |
||||||
|
defer func() { |
||||||
|
cancel() |
||||||
|
pendingMu.Lock() |
||||||
|
for c := range pending { |
||||||
|
c.Close() |
||||||
|
} |
||||||
|
pendingMu.Unlock() |
||||||
|
wg.Wait() |
||||||
|
close(l.donec) |
||||||
|
}() |
||||||
|
|
||||||
|
for { |
||||||
|
conn, err := l.Listener.Accept() |
||||||
|
if err != nil { |
||||||
|
l.err = err |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
pendingMu.Lock() |
||||||
|
pending[conn] = struct{}{} |
||||||
|
pendingMu.Unlock() |
||||||
|
|
||||||
|
wg.Add(1) |
||||||
|
go func() { |
||||||
|
defer func() { |
||||||
|
if conn != nil { |
||||||
|
conn.Close() |
||||||
|
} |
||||||
|
wg.Done() |
||||||
|
}() |
||||||
|
|
||||||
|
tlsConn := conn.(*tls.Conn) |
||||||
|
herr := tlsConn.Handshake() |
||||||
|
pendingMu.Lock() |
||||||
|
delete(pending, conn) |
||||||
|
pendingMu.Unlock() |
||||||
|
if herr != nil { |
||||||
|
l.handshakeFailure(tlsConn, herr) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
st := tlsConn.ConnectionState() |
||||||
|
if len(st.PeerCertificates) > 0 { |
||||||
|
cert := st.PeerCertificates[0] |
||||||
|
addr := tlsConn.RemoteAddr().String() |
||||||
|
if cerr := checkCert(ctx, cert, addr); cerr != nil { |
||||||
|
l.handshakeFailure(tlsConn, cerr) |
||||||
|
return |
||||||
|
} |
||||||
|
} |
||||||
|
select { |
||||||
|
case l.connc <- tlsConn: |
||||||
|
conn = nil |
||||||
|
case <-ctx.Done(): |
||||||
|
} |
||||||
|
}() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
func checkCert(ctx context.Context, cert *x509.Certificate, remoteAddr string) error { |
||||||
|
h, _, herr := net.SplitHostPort(remoteAddr) |
||||||
|
if len(cert.IPAddresses) == 0 && len(cert.DNSNames) == 0 { |
||||||
|
return nil |
||||||
|
} |
||||||
|
if herr != nil { |
||||||
|
return herr |
||||||
|
} |
||||||
|
if len(cert.IPAddresses) > 0 { |
||||||
|
cerr := cert.VerifyHostname(h) |
||||||
|
if cerr == nil { |
||||||
|
return nil |
||||||
|
} |
||||||
|
if len(cert.DNSNames) == 0 { |
||||||
|
return cerr |
||||||
|
} |
||||||
|
} |
||||||
|
if len(cert.DNSNames) > 0 { |
||||||
|
ok, err := isHostInDNS(ctx, h, cert.DNSNames) |
||||||
|
if ok { |
||||||
|
return nil |
||||||
|
} |
||||||
|
errStr := "" |
||||||
|
if err != nil { |
||||||
|
errStr = " (" + err.Error() + ")" |
||||||
|
} |
||||||
|
return fmt.Errorf("tls: %q does not match any of DNSNames %q"+errStr, h, cert.DNSNames) |
||||||
|
} |
||||||
|
return nil |
||||||
|
} |
||||||
|
|
||||||
|
func isHostInDNS(ctx context.Context, host string, dnsNames []string) (ok bool, err error) { |
||||||
|
// reverse lookup
|
||||||
|
wildcards, names := []string{}, []string{} |
||||||
|
for _, dns := range dnsNames { |
||||||
|
if strings.HasPrefix(dns, "*.") { |
||||||
|
wildcards = append(wildcards, dns[1:]) |
||||||
|
} else { |
||||||
|
names = append(names, dns) |
||||||
|
} |
||||||
|
} |
||||||
|
lnames, lerr := net.DefaultResolver.LookupAddr(ctx, host) |
||||||
|
for _, name := range lnames { |
||||||
|
// strip trailing '.' from PTR record
|
||||||
|
if name[len(name)-1] == '.' { |
||||||
|
name = name[:len(name)-1] |
||||||
|
} |
||||||
|
for _, wc := range wildcards { |
||||||
|
if strings.HasSuffix(name, wc) { |
||||||
|
return true, nil |
||||||
|
} |
||||||
|
} |
||||||
|
for _, n := range names { |
||||||
|
if n == name { |
||||||
|
return true, nil |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
err = lerr |
||||||
|
|
||||||
|
// forward lookup
|
||||||
|
for _, dns := range names { |
||||||
|
addrs, lerr := net.DefaultResolver.LookupHost(ctx, dns) |
||||||
|
if lerr != nil { |
||||||
|
err = lerr |
||||||
|
continue |
||||||
|
} |
||||||
|
for _, addr := range addrs { |
||||||
|
if addr == host { |
||||||
|
return true, nil |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
return false, err |
||||||
|
} |
||||||
|
|
||||||
|
func (l *tlsListener) Close() error { |
||||||
|
err := l.Listener.Close() |
||||||
|
<-l.donec |
||||||
|
return err |
||||||
|
} |
||||||
@ -0,0 +1,44 @@ |
|||||||
|
// Copyright 2015 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package transport |
||||||
|
|
||||||
|
import ( |
||||||
|
"net" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
type timeoutConn struct { |
||||||
|
net.Conn |
||||||
|
wtimeoutd time.Duration |
||||||
|
rdtimeoutd time.Duration |
||||||
|
} |
||||||
|
|
||||||
|
func (c timeoutConn) Write(b []byte) (n int, err error) { |
||||||
|
if c.wtimeoutd > 0 { |
||||||
|
if err := c.SetWriteDeadline(time.Now().Add(c.wtimeoutd)); err != nil { |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
} |
||||||
|
return c.Conn.Write(b) |
||||||
|
} |
||||||
|
|
||||||
|
func (c timeoutConn) Read(b []byte) (n int, err error) { |
||||||
|
if c.rdtimeoutd > 0 { |
||||||
|
if err := c.SetReadDeadline(time.Now().Add(c.rdtimeoutd)); err != nil { |
||||||
|
return 0, err |
||||||
|
} |
||||||
|
} |
||||||
|
return c.Conn.Read(b) |
||||||
|
} |
||||||
@ -0,0 +1,36 @@ |
|||||||
|
// Copyright 2015 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package transport |
||||||
|
|
||||||
|
import ( |
||||||
|
"net" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
type rwTimeoutDialer struct { |
||||||
|
wtimeoutd time.Duration |
||||||
|
rdtimeoutd time.Duration |
||||||
|
net.Dialer |
||||||
|
} |
||||||
|
|
||||||
|
func (d *rwTimeoutDialer) Dial(network, address string) (net.Conn, error) { |
||||||
|
conn, err := d.Dialer.Dial(network, address) |
||||||
|
tconn := &timeoutConn{ |
||||||
|
rdtimeoutd: d.rdtimeoutd, |
||||||
|
wtimeoutd: d.wtimeoutd, |
||||||
|
Conn: conn, |
||||||
|
} |
||||||
|
return tconn, err |
||||||
|
} |
||||||
@ -0,0 +1,57 @@ |
|||||||
|
// Copyright 2015 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package transport |
||||||
|
|
||||||
|
import ( |
||||||
|
"net" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
// NewTimeoutListener returns a listener that listens on the given address.
|
||||||
|
// If read/write on the accepted connection blocks longer than its time limit,
|
||||||
|
// it will return timeout error.
|
||||||
|
func NewTimeoutListener(addr string, scheme string, tlsinfo *TLSInfo, rdtimeoutd, wtimeoutd time.Duration) (net.Listener, error) { |
||||||
|
ln, err := newListener(addr, scheme) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
ln = &rwTimeoutListener{ |
||||||
|
Listener: ln, |
||||||
|
rdtimeoutd: rdtimeoutd, |
||||||
|
wtimeoutd: wtimeoutd, |
||||||
|
} |
||||||
|
if ln, err = wrapTLS(addr, scheme, tlsinfo, ln); err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return ln, nil |
||||||
|
} |
||||||
|
|
||||||
|
type rwTimeoutListener struct { |
||||||
|
net.Listener |
||||||
|
wtimeoutd time.Duration |
||||||
|
rdtimeoutd time.Duration |
||||||
|
} |
||||||
|
|
||||||
|
func (rwln *rwTimeoutListener) Accept() (net.Conn, error) { |
||||||
|
c, err := rwln.Listener.Accept() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return timeoutConn{ |
||||||
|
Conn: c, |
||||||
|
wtimeoutd: rwln.wtimeoutd, |
||||||
|
rdtimeoutd: rwln.rdtimeoutd, |
||||||
|
}, nil |
||||||
|
} |
||||||
@ -0,0 +1,51 @@ |
|||||||
|
// Copyright 2015 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package transport |
||||||
|
|
||||||
|
import ( |
||||||
|
"net" |
||||||
|
"net/http" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
// NewTimeoutTransport returns a transport created using the given TLS info.
|
||||||
|
// If read/write on the created connection blocks longer than its time limit,
|
||||||
|
// it will return timeout error.
|
||||||
|
// If read/write timeout is set, transport will not be able to reuse connection.
|
||||||
|
func NewTimeoutTransport(info TLSInfo, dialtimeoutd, rdtimeoutd, wtimeoutd time.Duration) (*http.Transport, error) { |
||||||
|
tr, err := NewTransport(info, dialtimeoutd) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
if rdtimeoutd != 0 || wtimeoutd != 0 { |
||||||
|
// the timed out connection will timeout soon after it is idle.
|
||||||
|
// it should not be put back to http transport as an idle connection for future usage.
|
||||||
|
tr.MaxIdleConnsPerHost = -1 |
||||||
|
} else { |
||||||
|
// allow more idle connections between peers to avoid unnecessary port allocation.
|
||||||
|
tr.MaxIdleConnsPerHost = 1024 |
||||||
|
} |
||||||
|
|
||||||
|
tr.Dial = (&rwTimeoutDialer{ |
||||||
|
Dialer: net.Dialer{ |
||||||
|
Timeout: dialtimeoutd, |
||||||
|
KeepAlive: 30 * time.Second, |
||||||
|
}, |
||||||
|
rdtimeoutd: rdtimeoutd, |
||||||
|
wtimeoutd: wtimeoutd, |
||||||
|
}).Dial |
||||||
|
return tr, nil |
||||||
|
} |
||||||
@ -0,0 +1,49 @@ |
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package transport |
||||||
|
|
||||||
|
import ( |
||||||
|
"fmt" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
// ValidateSecureEndpoints scans the given endpoints against tls info, returning only those
|
||||||
|
// endpoints that could be validated as secure.
|
||||||
|
func ValidateSecureEndpoints(tlsInfo TLSInfo, eps []string) ([]string, error) { |
||||||
|
t, err := NewTransport(tlsInfo, 5*time.Second) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
var errs []string |
||||||
|
var endpoints []string |
||||||
|
for _, ep := range eps { |
||||||
|
if !strings.HasPrefix(ep, "https://") { |
||||||
|
errs = append(errs, fmt.Sprintf("%q is insecure", ep)) |
||||||
|
continue |
||||||
|
} |
||||||
|
conn, cerr := t.Dial("tcp", ep[len("https://"):]) |
||||||
|
if cerr != nil { |
||||||
|
errs = append(errs, fmt.Sprintf("%q failed to dial (%v)", ep, cerr)) |
||||||
|
continue |
||||||
|
} |
||||||
|
conn.Close() |
||||||
|
endpoints = append(endpoints, ep) |
||||||
|
} |
||||||
|
if len(errs) != 0 { |
||||||
|
err = fmt.Errorf("%s", strings.Join(errs, ",")) |
||||||
|
} |
||||||
|
return endpoints, err |
||||||
|
} |
||||||
@ -0,0 +1,71 @@ |
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package transport |
||||||
|
|
||||||
|
import ( |
||||||
|
"net" |
||||||
|
"net/http" |
||||||
|
"strings" |
||||||
|
"time" |
||||||
|
) |
||||||
|
|
||||||
|
type unixTransport struct{ *http.Transport } |
||||||
|
|
||||||
|
func NewTransport(info TLSInfo, dialtimeoutd time.Duration) (*http.Transport, error) { |
||||||
|
cfg, err := info.ClientConfig() |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
|
||||||
|
t := &http.Transport{ |
||||||
|
Proxy: http.ProxyFromEnvironment, |
||||||
|
Dial: (&net.Dialer{ |
||||||
|
Timeout: dialtimeoutd, |
||||||
|
// value taken from http.DefaultTransport
|
||||||
|
KeepAlive: 30 * time.Second, |
||||||
|
}).Dial, |
||||||
|
// value taken from http.DefaultTransport
|
||||||
|
TLSHandshakeTimeout: 10 * time.Second, |
||||||
|
TLSClientConfig: cfg, |
||||||
|
} |
||||||
|
|
||||||
|
dialer := (&net.Dialer{ |
||||||
|
Timeout: dialtimeoutd, |
||||||
|
KeepAlive: 30 * time.Second, |
||||||
|
}) |
||||||
|
dial := func(net, addr string) (net.Conn, error) { |
||||||
|
return dialer.Dial("unix", addr) |
||||||
|
} |
||||||
|
|
||||||
|
tu := &http.Transport{ |
||||||
|
Proxy: http.ProxyFromEnvironment, |
||||||
|
Dial: dial, |
||||||
|
TLSHandshakeTimeout: 10 * time.Second, |
||||||
|
TLSClientConfig: cfg, |
||||||
|
} |
||||||
|
ut := &unixTransport{tu} |
||||||
|
|
||||||
|
t.RegisterProtocol("unix", ut) |
||||||
|
t.RegisterProtocol("unixs", ut) |
||||||
|
|
||||||
|
return t, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (urt *unixTransport) RoundTrip(req *http.Request) (*http.Response, error) { |
||||||
|
url := *req.URL |
||||||
|
req.URL = &url |
||||||
|
req.URL.Scheme = strings.Replace(req.URL.Scheme, "unix", "http", 1) |
||||||
|
return urt.Transport.RoundTrip(req) |
||||||
|
} |
||||||
@ -0,0 +1,40 @@ |
|||||||
|
// Copyright 2016 The etcd Authors
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package transport |
||||||
|
|
||||||
|
import ( |
||||||
|
"net" |
||||||
|
"os" |
||||||
|
) |
||||||
|
|
||||||
|
type unixListener struct{ net.Listener } |
||||||
|
|
||||||
|
func NewUnixListener(addr string) (net.Listener, error) { |
||||||
|
if err := os.Remove(addr); err != nil && !os.IsNotExist(err) { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
l, err := net.Listen("unix", addr) |
||||||
|
if err != nil { |
||||||
|
return nil, err |
||||||
|
} |
||||||
|
return &unixListener{l}, nil |
||||||
|
} |
||||||
|
|
||||||
|
func (ul *unixListener) Close() error { |
||||||
|
if err := os.Remove(ul.Addr().String()); err != nil && !os.IsNotExist(err) { |
||||||
|
return err |
||||||
|
} |
||||||
|
return ul.Listener.Close() |
||||||
|
} |
||||||
Loading…
Reference in new issue