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