vitess.io/vitess@v0.16.2/go/vt/topo/etcd2topo/file.go (about) 1 /* 2 Copyright 2019 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package etcd2topo 18 19 import ( 20 "path" 21 22 "context" 23 24 clientv3 "go.etcd.io/etcd/client/v3" 25 26 "vitess.io/vitess/go/vt/topo" 27 ) 28 29 // Create is part of the topo.Conn interface. 30 func (s *Server) Create(ctx context.Context, filePath string, contents []byte) (topo.Version, error) { 31 nodePath := path.Join(s.root, filePath) 32 33 // We have to do a transaction, comparing existing version with 0. 34 // This means: if the file doesn't exist, create it. 35 txnresp, err := s.cli.Txn(ctx). 36 If(clientv3.Compare(clientv3.Version(nodePath), "=", 0)). 37 Then(clientv3.OpPut(nodePath, string(contents))). 38 Commit() 39 if err != nil { 40 return nil, convertError(err, nodePath) 41 } 42 if !txnresp.Succeeded { 43 return nil, topo.NewError(topo.NodeExists, nodePath) 44 } 45 return EtcdVersion(txnresp.Header.Revision), nil 46 } 47 48 // Update is part of the topo.Conn interface. 49 func (s *Server) Update(ctx context.Context, filePath string, contents []byte, version topo.Version) (topo.Version, error) { 50 nodePath := path.Join(s.root, filePath) 51 52 if version != nil { 53 // We have to do a transaction. This means: if the 54 // current file revision is what we expect, save it. 55 txnresp, err := s.cli.Txn(ctx). 56 If(clientv3.Compare(clientv3.ModRevision(nodePath), "=", int64(version.(EtcdVersion)))). 57 Then(clientv3.OpPut(nodePath, string(contents))). 58 Commit() 59 if err != nil { 60 return nil, convertError(err, nodePath) 61 } 62 if !txnresp.Succeeded { 63 return nil, topo.NewError(topo.BadVersion, nodePath) 64 } 65 return EtcdVersion(txnresp.Header.Revision), nil 66 } 67 68 // No version specified. We can use a simple unconditional Put. 69 resp, err := s.cli.Put(ctx, nodePath, string(contents)) 70 if err != nil { 71 return nil, convertError(err, nodePath) 72 } 73 return EtcdVersion(resp.Header.Revision), nil 74 } 75 76 // Get is part of the topo.Conn interface. 77 func (s *Server) Get(ctx context.Context, filePath string) ([]byte, topo.Version, error) { 78 nodePath := path.Join(s.root, filePath) 79 80 resp, err := s.cli.Get(ctx, nodePath) 81 if err != nil { 82 return nil, nil, convertError(err, nodePath) 83 } 84 if len(resp.Kvs) != 1 { 85 return nil, nil, topo.NewError(topo.NoNode, nodePath) 86 } 87 88 return resp.Kvs[0].Value, EtcdVersion(resp.Kvs[0].ModRevision), nil 89 } 90 91 // List is part of the topo.Conn interface. 92 func (s *Server) List(ctx context.Context, filePathPrefix string) ([]topo.KVInfo, error) { 93 nodePathPrefix := path.Join(s.root, filePathPrefix) 94 95 resp, err := s.cli.Get(ctx, nodePathPrefix, clientv3.WithPrefix()) 96 if err != nil { 97 return []topo.KVInfo{}, err 98 } 99 pairs := resp.Kvs 100 if len(pairs) == 0 { 101 return []topo.KVInfo{}, topo.NewError(topo.NoNode, nodePathPrefix) 102 } 103 results := make([]topo.KVInfo, len(pairs)) 104 for n := range pairs { 105 results[n].Key = pairs[n].Key 106 results[n].Value = pairs[n].Value 107 results[n].Version = EtcdVersion(pairs[n].ModRevision) 108 } 109 110 return results, nil 111 } 112 113 // Delete is part of the topo.Conn interface. 114 func (s *Server) Delete(ctx context.Context, filePath string, version topo.Version) error { 115 nodePath := path.Join(s.root, filePath) 116 117 if version != nil { 118 // We have to do a transaction. This means: if the 119 // node revision is what we expect, delete it, 120 // otherwise get the file. If the transaction doesn't 121 // succeed, we also ask for the value of the 122 // node. That way we'll know if it failed because it 123 // didn't exist, or because the version was wrong. 124 txnresp, err := s.cli.Txn(ctx). 125 If(clientv3.Compare(clientv3.ModRevision(nodePath), "=", int64(version.(EtcdVersion)))). 126 Then(clientv3.OpDelete(nodePath)). 127 Else(clientv3.OpGet(nodePath)). 128 Commit() 129 if err != nil { 130 return convertError(err, nodePath) 131 } 132 if !txnresp.Succeeded { 133 if len(txnresp.Responses) > 0 { 134 if len(txnresp.Responses[0].GetResponseRange().Kvs) > 0 { 135 return topo.NewError(topo.BadVersion, nodePath) 136 } 137 } 138 return topo.NewError(topo.NoNode, nodePath) 139 } 140 return nil 141 } 142 143 // This is just a regular unconditional Delete here. 144 resp, err := s.cli.Delete(ctx, nodePath) 145 if err != nil { 146 return convertError(err, nodePath) 147 } 148 if resp.Deleted != 1 { 149 return topo.NewError(topo.NoNode, nodePath) 150 } 151 return nil 152 }