vitess.io/vitess@v0.16.2/go/vt/topo/etcd2topo/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 etcd2topo implements topo.Server with etcd as the backend.
    19  
    20  We expect the following behavior from the etcd client library:
    21  
    22    - Get and Delete return ErrorCodeKeyNotFound if the node doesn't exist.
    23    - Create returns ErrorCodeNodeExist if the node already exists.
    24    - Intermediate directories are always created automatically if necessary.
    25    - Set returns ErrorCodeKeyNotFound if the node doesn't exist already.
    26    - It returns ErrorCodeTestFailed if the provided version index doesn't match.
    27  
    28  We follow these conventions within this package:
    29  
    30    - Call convertError(err) on any errors returned from the etcd client library.
    31      Functions defined in this package can be assumed to have already converted
    32      errors as necessary.
    33  */
    34  package etcd2topo
    35  
    36  import (
    37  	"crypto/tls"
    38  	"crypto/x509"
    39  	"strings"
    40  	"time"
    41  
    42  	"github.com/spf13/pflag"
    43  	"go.etcd.io/etcd/client/pkg/v3/tlsutil"
    44  	"google.golang.org/grpc"
    45  
    46  	clientv3 "go.etcd.io/etcd/client/v3"
    47  
    48  	"vitess.io/vitess/go/vt/servenv"
    49  	"vitess.io/vitess/go/vt/topo"
    50  )
    51  
    52  var (
    53  	clientCertPath string
    54  	clientKeyPath  string
    55  	serverCaPath   string
    56  )
    57  
    58  // Factory is the consul topo.Factory implementation.
    59  type Factory struct{}
    60  
    61  // HasGlobalReadOnlyCell is part of the topo.Factory interface.
    62  func (f Factory) HasGlobalReadOnlyCell(serverAddr, root string) bool {
    63  	return false
    64  }
    65  
    66  // Create is part of the topo.Factory interface.
    67  func (f Factory) Create(cell, serverAddr, root string) (topo.Conn, error) {
    68  	return NewServer(serverAddr, root)
    69  }
    70  
    71  // Server is the implementation of topo.Server for etcd.
    72  type Server struct {
    73  	// cli is the v3 client.
    74  	cli *clientv3.Client
    75  
    76  	// root is the root path for this client.
    77  	root string
    78  
    79  	running chan struct{}
    80  }
    81  
    82  func init() {
    83  	for _, cmd := range topo.FlagBinaries {
    84  		servenv.OnParseFor(cmd, registerEtcd2TopoFlags)
    85  	}
    86  	topo.RegisterFactory("etcd2", Factory{})
    87  }
    88  
    89  func registerEtcd2TopoFlags(fs *pflag.FlagSet) {
    90  	fs.StringVar(&clientCertPath, "topo_etcd_tls_cert", clientCertPath, "path to the client cert to use to connect to the etcd topo server, requires topo_etcd_tls_key, enables TLS")
    91  	fs.StringVar(&clientKeyPath, "topo_etcd_tls_key", clientKeyPath, "path to the client key to use to connect to the etcd topo server, enables TLS")
    92  	fs.StringVar(&serverCaPath, "topo_etcd_tls_ca", serverCaPath, "path to the ca to use to validate the server cert when connecting to the etcd topo server")
    93  }
    94  
    95  // Close implements topo.Server.Close.
    96  // It will nil out the global and cells fields, so any attempt to
    97  // re-use this server will panic.
    98  func (s *Server) Close() {
    99  	close(s.running)
   100  	s.cli.Close()
   101  	s.cli = nil
   102  }
   103  
   104  func newTLSConfig(certPath, keyPath, caPath string) (*tls.Config, error) {
   105  	var tlscfg *tls.Config
   106  	// If TLS is enabled, attach TLS config info.
   107  	if certPath != "" && keyPath != "" {
   108  		var (
   109  			cert *tls.Certificate
   110  			cp   *x509.CertPool
   111  			err  error
   112  		)
   113  
   114  		cert, err = tlsutil.NewCert(certPath, keyPath, nil)
   115  		if err != nil {
   116  			return nil, err
   117  		}
   118  
   119  		if caPath != "" {
   120  			cp, err = tlsutil.NewCertPool([]string{caPath})
   121  			if err != nil {
   122  				return nil, err
   123  			}
   124  		}
   125  
   126  		tlscfg = &tls.Config{
   127  			MinVersion:         tls.VersionTLS12,
   128  			RootCAs:            cp,
   129  			InsecureSkipVerify: false,
   130  		}
   131  		if cert != nil {
   132  			tlscfg.Certificates = []tls.Certificate{*cert}
   133  		}
   134  	}
   135  	return tlscfg, nil
   136  }
   137  
   138  // NewServerWithOpts creates a new server with the provided TLS options
   139  func NewServerWithOpts(serverAddr, root, certPath, keyPath, caPath string) (*Server, error) {
   140  	// TODO: Rename this to NewServer and change NewServer to a name that signifies it uses the process-wide TLS settings.
   141  	config := clientv3.Config{
   142  		Endpoints:   strings.Split(serverAddr, ","),
   143  		DialTimeout: 5 * time.Second,
   144  		DialOptions: []grpc.DialOption{grpc.WithBlock()},
   145  	}
   146  
   147  	tlscfg, err := newTLSConfig(certPath, keyPath, caPath)
   148  	if err != nil {
   149  		return nil, err
   150  	}
   151  
   152  	config.TLS = tlscfg
   153  
   154  	cli, err := clientv3.New(config)
   155  	if err != nil {
   156  		return nil, err
   157  	}
   158  
   159  	return &Server{
   160  		cli:     cli,
   161  		root:    root,
   162  		running: make(chan struct{}),
   163  	}, nil
   164  }
   165  
   166  // NewServer returns a new etcdtopo.Server.
   167  func NewServer(serverAddr, root string) (*Server, error) {
   168  	// TODO: Rename this to a name to signifies this function uses the process-wide TLS settings.
   169  
   170  	return NewServerWithOpts(serverAddr, root, clientCertPath, clientKeyPath, serverCaPath)
   171  }