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  }