func ExampleKV_delete() { cli, err := clientv3.New(clientv3.Config{ Endpoints: endpoints, DialTimeout: dialTimeout, }) if err != nil { log.Fatal(err) } defer cli.Close() ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) defer cancel() // count keys about to be deleted gresp, err := cli.Get(ctx, "key", clientv3.WithPrefix()) if err != nil { log.Fatal(err) } // delete the keys dresp, err := cli.Delete(ctx, "key", clientv3.WithPrefix()) if err != nil { log.Fatal(err) } fmt.Println("Deleted all keys:", int64(len(gresp.Kvs)) == dresp.Deleted) // Output: // Deleted all keys: true }
func TestWatch(t *testing.T) { sync := newSync(t) sync.etcdClient.Delete(context.Background(), "/", etcd.WithPrefix()) path := "/path/to/watch" responseChan := make(chan *gohan_sync.Event) stopChan := make(chan bool) sync.etcdClient.Put(context.Background(), path+"/existing", `{"existing": true}`) go func() { err := sync.Watch(path, responseChan, stopChan) if err != nil { t.Errorf("failed to watch") } }() resp := <-responseChan if resp.Action != "get" || resp.Key != path+"/existing" || resp.Data["existing"].(bool) != true { t.Errorf("mismatch response: %+v", resp) } sync.etcdClient.Put(context.Background(), path+"/new", `{"existing": false}`) resp = <-responseChan if resp.Action != "set" || resp.Key != path+"/new" || resp.Data["existing"].(bool) != false { t.Errorf("mismatch response: %+v", resp) } sync.etcdClient.Delete(context.Background(), path+"/existing") resp = <-responseChan if resp.Action != "delete" || resp.Key != path+"/existing" || len(resp.Data) != 0 { t.Errorf("mismatch response: %+v", resp) } }
func (e *etcdv3Registry) ListServices() ([]*registry.Service, error) { var services []*registry.Service nameSet := make(map[string]struct{}) ctx, cancel := context.WithTimeout(context.Background(), e.options.Timeout) defer cancel() rsp, err := e.client.Get(ctx, prefix, clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByKey, clientv3.SortDescend)) if err != nil { return nil, err } if len(rsp.Kvs) == 0 { return []*registry.Service{}, nil } for _, n := range rsp.Kvs { if sn := decode(n.Value); sn != nil { nameSet[sn.Name] = struct{}{} } } for k := range nameSet { service := ®istry.Service{} service.Name = k services = append(services, service) } return services, nil }
// Enter waits for "count" processes to enter the barrier then returns func (b *DoubleBarrier) Enter() error { ek, err := NewUniqueEphemeralKey(b.client, b.key+"/waiters") if err != nil { return err } b.myKey = ek resp, err := b.client.Get(b.ctx, b.key+"/waiters", clientv3.WithPrefix()) if err != nil { return err } if len(resp.Kvs) > b.count { return ErrTooManyClients } if len(resp.Kvs) == b.count { // unblock waiters _, err = b.client.Put(b.ctx, b.key+"/ready", "") return err } _, err = WaitEvents( b.client, b.key+"/ready", ek.Revision(), []storagepb.Event_EventType{storagepb.PUT}) return err }
// Leave waits for "count" processes to leave the barrier then returns func (b *DoubleBarrier) Leave() error { resp, err := b.client.Get(b.ctx, b.key+"/waiters", clientv3.WithPrefix()) if err != nil { return err } if len(resp.Kvs) == 0 { return nil } lowest, highest := resp.Kvs[0], resp.Kvs[0] for _, k := range resp.Kvs { if k.ModRevision < lowest.ModRevision { lowest = k } if k.ModRevision > highest.ModRevision { highest = k } } isLowest := string(lowest.Key) == b.myKey.Key() if len(resp.Kvs) == 1 { // this is the only node in the barrier; finish up if _, err = b.client.Delete(b.ctx, b.key+"/ready"); err != nil { return err } return b.myKey.Delete() } // this ensures that if a process fails, the ephemeral lease will be // revoked, its barrier key is removed, and the barrier can resume // lowest process in node => wait on highest process if isLowest { _, err = WaitEvents( b.client, string(highest.Key), highest.ModRevision, []storagepb.Event_EventType{storagepb.DELETE}) if err != nil { return err } return b.Leave() } // delete self and wait on lowest process if err := b.myKey.Delete(); err != nil { return err } key := string(lowest.Key) _, err = WaitEvents( b.client, key, lowest.ModRevision, []storagepb.Event_EventType{storagepb.DELETE}) if err != nil { return err } return b.Leave() }
func WaitPrefixEvents(c *clientv3.Client, prefix string, rev int64, evs []mvccpb.Event_EventType) (*clientv3.Event, error) { wc := c.Watch(context.Background(), prefix, clientv3.WithPrefix(), clientv3.WithRev(rev)) if wc == nil { return nil, ErrNoWatcher } return waitEvents(wc, evs), nil }
// startWatching does: // - get current objects if initialRev=0; set initialRev to current rev // - watch on given key and send events to process. func (wc *watchChan) startWatching(watchClosedCh chan struct{}) { if wc.initialRev == 0 { if err := wc.sync(); err != nil { glog.Errorf("failed to sync with latest state: %v", err) wc.sendError(err) return } } opts := []clientv3.OpOption{clientv3.WithRev(wc.initialRev + 1), clientv3.WithPrevKV()} if wc.recursive { opts = append(opts, clientv3.WithPrefix()) } wch := wc.watcher.client.Watch(wc.ctx, wc.key, opts...) for wres := range wch { if wres.Err() != nil { err := wres.Err() // If there is an error on server (e.g. compaction), the channel will return it before closed. glog.Errorf("watch chan error: %v", err) wc.sendError(err) return } for _, e := range wres.Events { wc.sendEvent(parseEvent(e)) } } // When we come to this point, it's only possible that client side ends the watch. // e.g. cancel the context, close the client. // If this watch chan is broken and context isn't cancelled, other goroutines will still hang. // We should notify the main thread that this goroutine has exited. close(watchClosedCh) }
// watchCommandFunc executes the "watch" command. func watchCommandFunc(cmd *cobra.Command, args []string) { if watchInteractive { watchInteractiveFunc(cmd, args) return } if len(args) < 1 || len(args) > 2 { ExitWithError(ExitBadArgs, fmt.Errorf("watch in non-interactive mode requires one or two arguments as key or prefix, with range end")) } opts := []clientv3.OpOption{clientv3.WithRev(watchRev)} key := args[0] if len(args) == 2 { if watchPrefix { ExitWithError(ExitBadArgs, fmt.Errorf("`range_end` and `--prefix` cannot be set at the same time, choose one")) } opts = append(opts, clientv3.WithRange(args[1])) } if watchPrefix { opts = append(opts, clientv3.WithPrefix()) } c := mustClientFromCmd(cmd) wc := c.Watch(context.TODO(), key, opts...) printWatchCh(wc) err := c.Close() if err == nil { ExitWithError(ExitInterrupted, fmt.Errorf("watch is canceled by the server")) } ExitWithError(ExitBadConnection, err) }
func getDelOp(cmd *cobra.Command, args []string) (string, []clientv3.OpOption) { if len(args) == 0 || len(args) > 2 { ExitWithError(ExitBadArgs, fmt.Errorf("del command needs one argument as key and an optional argument as range_end.")) } if delPrefix && delFromKey { ExitWithError(ExitBadArgs, fmt.Errorf("`--prefix` and `--from-key` cannot be set at the same time, choose one.")) } opts := []clientv3.OpOption{} key := args[0] if len(args) > 1 { if delPrefix || delFromKey { ExitWithError(ExitBadArgs, fmt.Errorf("too many arguments, only accept one argument when `--prefix` or `--from-key` is set.")) } opts = append(opts, clientv3.WithRange(args[1])) } if delPrefix { opts = append(opts, clientv3.WithPrefix()) } if delPrevKV { opts = append(opts, clientv3.WithPrevKV()) } if delFromKey { if len(key) == 0 { key = "\x00" } opts = append(opts, clientv3.WithFromKey()) } return key, opts }
func (n *ng) GetSnapshot() (*engine.Snapshot, error) { response, err := n.client.Get(n.context, n.etcdKey, etcd.WithPrefix(), etcd.WithSort(etcd.SortByKey, etcd.SortAscend)) if err != nil { return nil, err } s := &engine.Snapshot{Index: uint64(response.Header.Revision)} s.FrontendSpecs, err = n.parseFrontends(filterByPrefix(response.Kvs, n.etcdKey+"/frontends")) if err != nil { return nil, err } s.BackendSpecs, err = n.parseBackends(filterByPrefix(response.Kvs, n.etcdKey+"/backends")) if err != nil { return nil, err } s.Hosts, err = n.parseHosts(filterByPrefix(response.Kvs, n.etcdKey+"/hosts")) if err != nil { return nil, err } s.Listeners, err = n.parseListeners(filterByPrefix(response.Kvs, n.etcdKey+"/listeners")) if err != nil { return nil, err } return s, nil }
func ExampleKV_getSortedPrefix() { cli, err := clientv3.New(clientv3.Config{ Endpoints: endpoints, DialTimeout: dialTimeout, }) if err != nil { log.Fatal(err) } defer cli.Close() kvc := clientv3.NewKV(cli) for i := range make([]int, 3) { ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) _, err = kvc.Put(ctx, fmt.Sprintf("key_%d", i), "value") cancel() if err != nil { log.Fatal(err) } } ctx, cancel := context.WithTimeout(context.Background(), requestTimeout) resp, err := kvc.Get(ctx, "key", clientv3.WithPrefix(), clientv3.WithSort(clientv3.SortByKey, clientv3.SortDescend)) cancel() if err != nil { log.Fatal(err) } for _, ev := range resp.Kvs { fmt.Printf("%s : %s\n", ev.Key, ev.Value) } // key_2 : value // key_1 : value // key_0 : value }
func (e *Election) observe(ctx context.Context, ch chan<- v3.GetResponse) { client := e.session.Client() defer close(ch) for { resp, err := client.Get(ctx, e.keyPrefix, v3.WithFirstCreate()...) if err != nil { return } var kv *mvccpb.KeyValue cctx, cancel := context.WithCancel(ctx) if len(resp.Kvs) == 0 { // wait for first key put on prefix opts := []v3.OpOption{v3.WithRev(resp.Header.Revision), v3.WithPrefix()} wch := client.Watch(cctx, e.keyPrefix, opts...) for kv == nil { wr, ok := <-wch if !ok || wr.Err() != nil { cancel() return } // only accept PUTs; a DELETE will make observe() spin for _, ev := range wr.Events { if ev.Type == mvccpb.PUT { kv = ev.Kv break } } } } else { kv = resp.Kvs[0] } wch := client.Watch(cctx, string(kv.Key), v3.WithRev(kv.ModRevision)) keyDeleted := false for !keyDeleted { wr, ok := <-wch if !ok { return } for _, ev := range wr.Events { if ev.Type == mvccpb.DELETE { keyDeleted = true break } resp.Header = &wr.Header resp.Kvs = []*mvccpb.KeyValue{ev.Kv} select { case ch <- *resp: case <-cctx.Done(): return } } } cancel() } }
// watchCommandFunc executes the "watch" command. func watchCommandFunc(cmd *cobra.Command, args []string) { if watchInteractive { watchInteractiveFunc(cmd, args) return } if len(args) != 1 { ExitWithError(ExitBadArgs, fmt.Errorf("watch in non-interactive mode requires an argument as key or prefix")) } c := mustClientFromCmd(cmd) w := clientv3.NewWatcher(c) opts := []clientv3.OpOption{clientv3.WithRev(watchRev)} if watchPrefix { opts = append(opts, clientv3.WithPrefix()) } wc := w.Watch(context.TODO(), args[0], opts...) printWatchCh(wc) err := w.Close() if err == nil { ExitWithError(ExitInterrupted, fmt.Errorf("watch is canceled by the server")) } ExitWithError(ExitBadConnection, err) }
func TestNonEmptyUpdate(t *testing.T) { sync := newSync(t) sync.etcdClient.Delete(context.Background(), "/", etcd.WithPrefix()) path := "/path/to/somewhere" data := "blabla" err := sync.Update(path, data) if err != nil { t.Errorf("unexpected error") } node, err := sync.Fetch(path) if err != nil { t.Errorf("unexpected error") } if node.Key != path || node.Value != data || len(node.Children) != 0 { t.Errorf("unexpected node: %+v", node) } err = sync.Delete(path) if err != nil { t.Errorf("unexpected error") } node, err = sync.Fetch(path) if err == nil { t.Errorf("unexpected non error") } }
// Lock locks the mutex with a cancellable context. If the context is cancelled // while trying to acquire the lock, the mutex tries to clean its stale lock entry. func (m *Mutex) Lock(ctx context.Context) error { s, serr := NewSession(m.client) if serr != nil { return serr } m.myKey = fmt.Sprintf("%s/%x", m.pfx, s.Lease()) cmp := v3.Compare(v3.CreateRevision(m.myKey), "=", 0) // put self in lock waiters via myKey; oldest waiter holds lock put := v3.OpPut(m.myKey, "", v3.WithLease(s.Lease())) // reuse key in case this session already holds the lock get := v3.OpGet(m.myKey) resp, err := m.client.Txn(ctx).If(cmp).Then(put).Else(get).Commit() if err != nil { return err } m.myRev = resp.Header.Revision if !resp.Succeeded { m.myRev = resp.Responses[0].GetResponseRange().Kvs[0].CreateRevision } // wait for deletion revisions prior to myKey err = waitDeletes(ctx, m.client, m.pfx, v3.WithPrefix(), v3.WithRev(m.myRev-1)) // release lock key if cancelled select { case <-ctx.Done(): m.Unlock() default: } return err }
func TestWatchDeleteEventObjectHaveLatestRV(t *testing.T) { ctx, store, cluster := testSetup(t) defer cluster.Terminate(t) key, storedObj := testPropogateStore(ctx, t, store, &api.Pod{ObjectMeta: api.ObjectMeta{Name: "foo"}}) w, err := store.Watch(ctx, key, storedObj.ResourceVersion, storage.Everything) if err != nil { t.Fatalf("Watch failed: %v", err) } etcdW := cluster.RandClient().Watch(ctx, "/", clientv3.WithPrefix()) if err := store.Delete(ctx, key, &api.Pod{}, &storage.Preconditions{}); err != nil { t.Fatalf("Delete failed: %v", err) } e := <-w.ResultChan() watchedDeleteObj := e.Object.(*api.Pod) var wres clientv3.WatchResponse wres = <-etcdW watchedDeleteRev, err := storage.ParseWatchResourceVersion(watchedDeleteObj.ResourceVersion) if err != nil { t.Fatalf("ParseWatchResourceVersion failed: %v", err) } if int64(watchedDeleteRev) != wres.Events[0].Kv.ModRevision { t.Errorf("Object from delete event have version: %v, should be the same as etcd delete's mod rev: %d", watchedDeleteRev, wres.Events[0].Kv.ModRevision) } }
// startWatching does: // - get current objects if initialRev=0; set initialRev to current rev // - watch on given key and send events to process. func (wc *watchChan) startWatching() { if wc.initialRev == 0 { if err := wc.sync(); err != nil { glog.Errorf("failed to sync with latest state: %v", err) wc.sendError(err) return } } opts := []clientv3.OpOption{clientv3.WithRev(wc.initialRev + 1)} if wc.recursive { opts = append(opts, clientv3.WithPrefix()) } wch := wc.watcher.client.Watch(wc.ctx, wc.key, opts...) for wres := range wch { if wres.Err() != nil { err := wres.Err() // If there is an error on server (e.g. compaction), the channel will return it before closed. glog.Errorf("watch chan error: %v", err) wc.sendError(err) return } for _, e := range wres.Events { wc.sendEvent(parseEvent(e)) } } }
// List implements storage.Interface.List. func (s *store) List(ctx context.Context, key, resourceVersion string, pred storage.SelectionPredicate, listObj runtime.Object) error { listPtr, err := meta.GetItemsPtr(listObj) if err != nil { return err } key = keyWithPrefix(s.pathPrefix, key) // We need to make sure the key ended with "/" so that we only get children "directories". // e.g. if we have key "/a", "/a/b", "/ab", getting keys with prefix "/a" will return all three, // while with prefix "/a/" will return only "/a/b" which is the correct answer. if !strings.HasSuffix(key, "/") { key += "/" } getResp, err := s.client.KV.Get(ctx, key, clientv3.WithPrefix()) if err != nil { return err } elems := make([]*elemForDecode, len(getResp.Kvs)) for i, kv := range getResp.Kvs { elems[i] = &elemForDecode{ data: kv.Value, rev: uint64(kv.ModRevision), } } if err := decodeList(elems, storage.SimpleFilter(pred), listPtr, s.codec, s.versioner); err != nil { return err } // update version with cluster level revision return s.versioner.UpdateList(listObj, uint64(getResp.Header.Revision)) }
func deletePrefix(ctx context.Context, client *clientv3.Client, key string) error { for ctx.Err() == nil { if _, err := client.Delete(ctx, key, clientv3.WithPrefix()); err == nil { return nil } } return ctx.Err() }
func (s *syncer) SyncBase(ctx context.Context) (<-chan clientv3.GetResponse, chan error) { respchan := make(chan clientv3.GetResponse, 1024) errchan := make(chan error, 1) kapi := clientv3.NewKV(s.c) // if rev is not specified, we will choose the most recent revision. if s.rev == 0 { resp, err := kapi.Get(ctx, "foo") if err != nil { errchan <- err close(respchan) close(errchan) return respchan, errchan } s.rev = resp.Header.Revision } go func() { defer close(respchan) defer close(errchan) var key string opts := []clientv3.OpOption{clientv3.WithLimit(batchLimit), clientv3.WithRev(s.rev)} if len(s.prefix) == 0 { // If len(s.prefix) == 0, we will sync the entire key-value space. // We then range from the smallest key (0x00) to the end. opts = append(opts, clientv3.WithFromKey()) key = "\x00" } else { // If len(s.prefix) != 0, we will sync key-value space with given prefix. // We then range from the prefix to the next prefix if exists. Or we will // range from the prefix to the end if the next prefix does not exists. opts = append(opts, clientv3.WithPrefix()) key = s.prefix } for { resp, err := kapi.Get(ctx, key, opts...) if err != nil { errchan <- err return } respchan <- (clientv3.GetResponse)(*resp) if !resp.More { return } // move to next key key = string(append(resp.Kvs[len(resp.Kvs)-1].Key, 0)) } }() return respchan, errchan }
func watchInteractiveFunc(cmd *cobra.Command, args []string) { c := mustClientFromCmd(cmd) reader := bufio.NewReader(os.Stdin) for { l, err := reader.ReadString('\n') if err != nil { ExitWithError(ExitInvalidInput, fmt.Errorf("Error reading watch request line: %v", err)) } l = strings.TrimSuffix(l, "\n") args := argify(l) if len(args) < 2 { fmt.Fprintf(os.Stderr, "Invalid command %s (command type or key is not provided)\n", l) continue } if args[0] != "watch" { fmt.Fprintf(os.Stderr, "Invalid command %s (only support watch)\n", l) continue } flagset := NewWatchCommand().Flags() err = flagset.Parse(args[1:]) if err != nil { fmt.Fprintf(os.Stderr, "Invalid command %s (%v)\n", l, err) continue } moreargs := flagset.Args() if len(moreargs) < 1 || len(moreargs) > 2 { fmt.Fprintf(os.Stderr, "Invalid command %s (Too few or many arguments)\n", l) continue } var key string _, err = fmt.Sscanf(moreargs[0], "%q", &key) if err != nil { key = moreargs[0] } opts := []clientv3.OpOption{clientv3.WithRev(watchRev)} if len(moreargs) == 2 { if watchPrefix { fmt.Fprintf(os.Stderr, "`range_end` and `--prefix` cannot be set at the same time, choose one\n") continue } opts = append(opts, clientv3.WithRange(moreargs[1])) } if watchPrefix { opts = append(opts, clientv3.WithPrefix()) } ch := c.Watch(context.TODO(), key, opts...) go printWatchCh(ch) } }
func testWatchWithProgressNotify(t *testing.T, watchOnPut bool) { defer testutil.AfterTest(t) // accelerate report interval so test terminates quickly oldpi := v3rpc.GetProgressReportInterval() // using atomics to avoid race warnings v3rpc.SetProgressReportInterval(3 * time.Second) pi := 3 * time.Second defer func() { v3rpc.SetProgressReportInterval(oldpi) }() clus := integration.NewClusterV3(t, &integration.ClusterConfig{Size: 3}) defer clus.Terminate(t) wc := clientv3.NewWatcher(clus.RandClient()) defer wc.Close() opts := []clientv3.OpOption{clientv3.WithProgressNotify()} if watchOnPut { opts = append(opts, clientv3.WithPrefix()) } rch := wc.Watch(context.Background(), "foo", opts...) select { case resp := <-rch: // wait for notification if len(resp.Events) != 0 { t.Fatalf("resp.Events expected none, got %+v", resp.Events) } case <-time.After(2 * pi): t.Fatalf("watch response expected in %v, but timed out", pi) } kvc := clientv3.NewKV(clus.RandClient()) if _, err := kvc.Put(context.TODO(), "foox", "bar"); err != nil { t.Fatal(err) } select { case resp := <-rch: if resp.Header.Revision != 2 { t.Fatalf("resp.Header.Revision expected 2, got %d", resp.Header.Revision) } if watchOnPut { // wait for put if watch on the put key ev := []*clientv3.Event{{Type: clientv3.EventTypePut, Kv: &mvccpb.KeyValue{Key: []byte("foox"), Value: []byte("bar"), CreateRevision: 2, ModRevision: 2, Version: 1}}} if !reflect.DeepEqual(ev, resp.Events) { t.Fatalf("expected %+v, got %+v", ev, resp.Events) } } else if len(resp.Events) != 0 { // wait for notification otherwise t.Fatalf("expected no events, but got %+v", resp.Events) } case <-time.After(2 * pi): t.Fatalf("watch response expected in %v, but timed out", pi) } }
func prefixToKeyOption(prefix string) (key string, option clientv3.OpOption) { if prefix == "" { key = "\x00" option = clientv3.WithFromKey() } else { key = prefix option = clientv3.WithPrefix() } return key, option }
//Fetch data from sync func (s *Sync) Fetch(key string) (*sync.Node, error) { node, err := s.etcdClient.Get(s.withTimeout(), key, etcd.WithSort(etcd.SortByKey, etcd.SortAscend)) if err != nil { return nil, err } dir, err := s.etcdClient.Get(s.withTimeout(), key+"/", etcd.WithPrefix(), etcd.WithSort(etcd.SortByKey, etcd.SortAscend)) if err != nil { return nil, err } return s.recursiveFetch(key, node.Kvs, dir.Kvs) }
func wipeEtcdMetadata(cfg torus.Config) error { client, err := etcdv3.New(etcdv3.Config{Endpoints: []string{cfg.MetadataAddress}, TLS: cfg.TLS}) if err != nil { return err } defer client.Close() _, err = client.Delete(context.Background(), MkKey(), etcdv3.WithPrefix()) if err != nil { return err } return nil }
// snapshotToStdout streams a snapshot over stdout func snapshotToStdout(c *clientv3.Client) { // must explicitly fetch first revision since no retry on stdout wr := <-c.Watch(context.TODO(), "", clientv3.WithPrefix(), clientv3.WithRev(1)) if wr.Err() == nil { wr.CompactRevision = 1 } if rev := snapshot(os.Stdout, c, wr.CompactRevision+1); rev != 0 { err := fmt.Errorf("snapshot interrupted by compaction %v", rev) ExitWithError(ExitInterrupted, err) } os.Stdout.Sync() }
func (gw *gRPCWatcher) firstNext() ([]*naming.Update, error) { // Use serialized request so resolution still works if the target etcd // server is partitioned away from the quorum. resp, err := gw.c.Get(gw.ctx, gw.target, etcd.WithPrefix(), etcd.WithSerializable()) if gw.err = err; err != nil { return nil, err } updates := make([]*naming.Update, 0, len(resp.Kvs)) for _, kv := range resp.Kvs { var jupdate naming.Update if err := json.Unmarshal(kv.Value, &jupdate); err != nil { continue } updates = append(updates, &jupdate) } opts := []etcd.OpOption{etcd.WithRev(resp.Header.Revision + 1), etcd.WithPrefix(), etcd.WithPrevKV()} gw.wch = gw.c.Watch(gw.ctx, gw.target, opts...) return updates, nil }
func (n *ng) DeleteBackend(bk engine.BackendKey) error { if bk.Id == "" { return &engine.InvalidFormatError{Message: "backend id can not be empty"} } fs, err := n.backendUsedBy(bk) if err != nil { return err } if len(fs) != 0 { return fmt.Errorf("can not delete backend '%v', it is in use by %s", bk, fs) } _, err = n.client.Delete(n.context, n.path("backends", bk.Id), etcd.WithPrefix()) return convertErr(err) }
// snapshotToStdout streams a snapshot over stdout func snapshotToStdout(c *clientv3.Client) { // must explicitly fetch first revision since no retry on stdout wapi := clientv3.NewWatcher(c) defer wapi.Close() wr := <-wapi.Watch(context.TODO(), "", clientv3.WithPrefix(), clientv3.WithRev(1)) if len(wr.Events) > 0 { wr.CompactRevision = 1 } if rev := snapshot(os.Stdout, c, wr.CompactRevision+1); rev != 0 { err := fmt.Errorf("snapshot interrupted by compaction %v", rev) ExitWithError(ExitInterrupted, err) } os.Stdout.Sync() }
// sync tries to retrieve existing data and send them to process. // The revision to watch will be set to the revision in response. // All events sent will have isCreated=true func (wc *watchChan) sync() error { opts := []clientv3.OpOption{} if wc.recursive { opts = append(opts, clientv3.WithPrefix()) } getResp, err := wc.watcher.client.Get(wc.ctx, wc.key, opts...) if err != nil { return err } wc.initialRev = getResp.Header.Revision for _, kv := range getResp.Kvs { wc.sendEvent(parseKV(kv)) } return nil }