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 }