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  }