vitess.io/vitess@v0.16.2/go/vt/topo/consultopo/server.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  /*
    18  Package consultopo implements topo.Server with consul as the backend.
    19  */
    20  package consultopo
    21  
    22  import (
    23  	"encoding/json"
    24  	"fmt"
    25  	"os"
    26  	"strings"
    27  	"sync"
    28  	"time"
    29  
    30  	"github.com/hashicorp/consul/api"
    31  	"github.com/spf13/pflag"
    32  
    33  	"vitess.io/vitess/go/vt/log"
    34  	"vitess.io/vitess/go/vt/servenv"
    35  	"vitess.io/vitess/go/vt/topo"
    36  	"vitess.io/vitess/go/vt/vterrors"
    37  )
    38  
    39  var (
    40  	consulAuthClientStaticFile string
    41  	// serfHealth is the default check from consul
    42  	consulLockSessionChecks = "serfHealth"
    43  	consulLockSessionTTL    string
    44  	consulLockDelay         = 15 * time.Second
    45  )
    46  
    47  func init() {
    48  	servenv.RegisterFlagsForTopoBinaries(registerServerFlags)
    49  }
    50  
    51  func registerServerFlags(fs *pflag.FlagSet) {
    52  	fs.StringVar(&consulAuthClientStaticFile, "consul_auth_static_file", consulAuthClientStaticFile, "JSON File to read the topos/tokens from.")
    53  	fs.StringVar(&consulLockSessionChecks, "topo_consul_lock_session_checks", consulLockSessionChecks, "List of checks for consul session.")
    54  	fs.StringVar(&consulLockSessionTTL, "topo_consul_lock_session_ttl", consulLockSessionTTL, "TTL for consul session.")
    55  	fs.DurationVar(&consulLockDelay, "topo_consul_lock_delay", consulLockDelay, "LockDelay for consul session.")
    56  }
    57  
    58  // ClientAuthCred credential to use for consul clusters
    59  type ClientAuthCred struct {
    60  	// ACLToken when provided, the client will use this token when making requests to the Consul server.
    61  	ACLToken string `json:"acl_token,omitempty"`
    62  }
    63  
    64  // Factory is the consul topo.Factory implementation.
    65  type Factory struct{}
    66  
    67  // HasGlobalReadOnlyCell is part of the topo.Factory interface.
    68  func (f Factory) HasGlobalReadOnlyCell(serverAddr, root string) bool {
    69  	return false
    70  }
    71  
    72  // Create is part of the topo.Factory interface.
    73  func (f Factory) Create(cell, serverAddr, root string) (topo.Conn, error) {
    74  	return NewServer(cell, serverAddr, root)
    75  }
    76  
    77  func getClientCreds() (creds map[string]*ClientAuthCred, err error) {
    78  	creds = make(map[string]*ClientAuthCred)
    79  
    80  	if consulAuthClientStaticFile == "" {
    81  		// Not configured, nothing to do.
    82  		log.Infof("Consul client auth is not set up. consul_auth_static_file was not provided")
    83  		return nil, nil
    84  	}
    85  
    86  	data, err := os.ReadFile(consulAuthClientStaticFile)
    87  	if err != nil {
    88  		err = vterrors.Wrapf(err, "Failed to read consul_auth_static_file file")
    89  		return creds, err
    90  	}
    91  
    92  	if err := json.Unmarshal(data, &creds); err != nil {
    93  		err = vterrors.Wrapf(err, fmt.Sprintf("Error parsing consul_auth_static_file")) //nolint
    94  		return creds, err
    95  	}
    96  	return creds, nil
    97  }
    98  
    99  // Server is the implementation of topo.Server for consul.
   100  type Server struct {
   101  	// client is the consul api client.
   102  	client *api.Client
   103  	kv     *api.KV
   104  
   105  	// root is the root path for this client.
   106  	root string
   107  
   108  	// mu protects the following fields.
   109  	mu sync.Mutex
   110  	// locks is a map of *lockInstance structures.
   111  	// The key is the filepath of the Lock file.
   112  	locks map[string]*lockInstance
   113  
   114  	lockChecks []string
   115  	lockTTL    string
   116  	lockDelay  time.Duration
   117  }
   118  
   119  // lockInstance keeps track of one lock held by this client.
   120  type lockInstance struct {
   121  	// lock has the api.Lock structure.
   122  	lock *api.Lock
   123  
   124  	// done is closed when the lock is release by this process.
   125  	done chan struct{}
   126  }
   127  
   128  // NewServer returns a new consultopo.Server.
   129  func NewServer(cell, serverAddr, root string) (*Server, error) {
   130  	creds, err := getClientCreds()
   131  	if err != nil {
   132  		return nil, err
   133  	}
   134  	cfg := api.DefaultConfig()
   135  	cfg.Address = serverAddr
   136  	if creds != nil {
   137  		if creds[cell] != nil {
   138  			cfg.Token = creds[cell].ACLToken
   139  		} else {
   140  			log.Warningf("Client auth not configured for cell: %v", cell)
   141  		}
   142  	}
   143  
   144  	client, err := api.NewClient(cfg)
   145  	if err != nil {
   146  		return nil, err
   147  	}
   148  
   149  	return &Server{
   150  		client:     client,
   151  		kv:         client.KV(),
   152  		root:       root,
   153  		locks:      make(map[string]*lockInstance),
   154  		lockChecks: parseConsulLockSessionChecks(consulLockSessionChecks),
   155  		lockTTL:    consulLockSessionTTL,
   156  		lockDelay:  consulLockDelay,
   157  	}, nil
   158  }
   159  
   160  func parseConsulLockSessionChecks(s string) []string {
   161  	var res []string
   162  	if len(s) == 0 {
   163  		return res
   164  	}
   165  	return strings.Split(consulLockSessionChecks, ",")
   166  }
   167  
   168  // Close implements topo.Server.Close.
   169  // It will nil out the global and cells fields, so any attempt to
   170  // re-use this server will panic.
   171  func (s *Server) Close() {
   172  	s.client = nil
   173  	s.kv = nil
   174  	s.mu.Lock()
   175  	defer s.mu.Unlock()
   176  	s.locks = nil
   177  }
   178  
   179  func init() {
   180  	topo.RegisterFactory("consul", Factory{})
   181  }