vitess.io/vitess@v0.16.2/go/vt/topo/consultopo/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 consultopo 18 19 import ( 20 "path" 21 22 "context" 23 24 "github.com/hashicorp/consul/api" 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 need to do a Put with version=0 and get the version 34 // back. KV.CAS does not return that information. However, a 35 // CAS in a transaction will return the node's data, so we use that. 36 ops := api.KVTxnOps{ 37 &api.KVTxnOp{ 38 Verb: api.KVCAS, 39 Key: nodePath, 40 Value: contents, 41 Index: 0, 42 }, 43 } 44 ok, resp, _, err := s.kv.Txn(ops, nil) 45 if err != nil { 46 // Communication error. 47 return nil, err 48 } 49 if !ok { 50 // Transaction was rolled back, means the node exists. 51 return nil, topo.NewError(topo.NodeExists, nodePath) 52 } 53 return ConsulVersion(resp.Results[0].ModifyIndex), nil 54 } 55 56 // Update is part of the topo.Conn interface. 57 func (s *Server) Update(ctx context.Context, filePath string, contents []byte, version topo.Version) (topo.Version, error) { 58 nodePath := path.Join(s.root, filePath) 59 60 // Again, we need to get the final version back. 61 // So we have to use a transaction, as Put doesn't return the version. 62 ops := api.KVTxnOps{ 63 &api.KVTxnOp{ 64 Verb: api.KVSet, 65 Key: nodePath, 66 Value: contents, 67 }, 68 } 69 if version != nil { 70 ops[0].Verb = api.KVCAS 71 ops[0].Index = uint64(version.(ConsulVersion)) 72 } 73 ok, resp, _, err := s.kv.Txn(ops, nil) 74 if err != nil { 75 // Communication error. 76 return nil, err 77 } 78 if !ok { 79 // Transaction was rolled back, means the node has a 80 // bad version. 81 return nil, topo.NewError(topo.BadVersion, nodePath) 82 } 83 return ConsulVersion(resp.Results[0].ModifyIndex), nil 84 } 85 86 // Get is part of the topo.Conn interface. 87 func (s *Server) Get(ctx context.Context, filePath string) ([]byte, topo.Version, error) { 88 nodePath := path.Join(s.root, filePath) 89 90 pair, _, err := s.kv.Get(nodePath, nil) 91 if err != nil { 92 return nil, nil, err 93 } 94 if pair == nil { 95 return nil, nil, topo.NewError(topo.NoNode, nodePath) 96 } 97 98 return pair.Value, ConsulVersion(pair.ModifyIndex), nil 99 } 100 101 // List is part of the topo.Conn interface. 102 func (s *Server) List(ctx context.Context, filePathPrefix string) ([]topo.KVInfo, error) { 103 nodePathPrefix := path.Join(s.root, filePathPrefix) 104 105 pairs, _, err := s.kv.List(nodePathPrefix, nil) 106 if err != nil { 107 return []topo.KVInfo{}, err 108 } 109 if len(pairs) == 0 { 110 return []topo.KVInfo{}, topo.NewError(topo.NoNode, nodePathPrefix) 111 } 112 results := make([]topo.KVInfo, len(pairs)) 113 for n := range pairs { 114 results[n].Key = []byte(pairs[n].Key) 115 results[n].Value = pairs[n].Value 116 results[n].Version = ConsulVersion(pairs[n].ModifyIndex) 117 } 118 119 return results, nil 120 } 121 122 // Delete is part of the topo.Conn interface. 123 func (s *Server) Delete(ctx context.Context, filePath string, version topo.Version) error { 124 nodePath := path.Join(s.root, filePath) 125 126 // We need to differentiate if the node existed or not. 127 // So we cannot use a regular Delete, which returns success 128 // whether or not the node originally existed. 129 // Let's do a 'Get' and then a 'Delete' in a transaction: 130 // - If the node doesn't exists, the Get will fail and abort. 131 // - If the node exists, the Get will work, and the Delete will 132 // then execute (and may or may not work for other reasons). 133 ops := api.KVTxnOps{ 134 &api.KVTxnOp{ 135 Verb: api.KVGet, 136 Key: nodePath, 137 }, 138 &api.KVTxnOp{ 139 Verb: api.KVDelete, 140 Key: nodePath, 141 }, 142 } 143 if version != nil { 144 // if we have a version, the delete we use specifies it. 145 ops[1].Verb = api.KVDeleteCAS 146 ops[1].Index = uint64(version.(ConsulVersion)) 147 } 148 ok, resp, _, err := s.kv.Txn(ops, nil) 149 if err != nil { 150 // Communication error. 151 return err 152 } 153 if !ok { 154 // Transaction was rolled back, means the Get failed, 155 // or the Delete had the wrong version. See which one it was. 156 switch resp.Errors[0].OpIndex { 157 case 0: 158 // Get failed (operation 0), the node didn't exist. 159 return topo.NewError(topo.NoNode, nodePath) 160 case 1: 161 // DeleteCAS failed (operation 1), means bad version. 162 return topo.NewError(topo.BadVersion, nodePath) 163 default: 164 // very unexpected. 165 return ErrBadResponse 166 } 167 } 168 return nil 169 }