github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/acceptance/localcluster/localcluster.go (about)

     1  // Copyright 2015 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package localcluster
    12  
    13  import (
    14  	"bytes"
    15  	"context"
    16  	gosql "database/sql"
    17  	"net"
    18  	"os/exec"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/cockroachdb/cockroach/pkg/acceptance/cluster"
    23  	"github.com/cockroachdb/errors"
    24  )
    25  
    26  // LocalCluster implements cluster.Cluster.
    27  type LocalCluster struct {
    28  	*Cluster
    29  }
    30  
    31  var _ cluster.Cluster = &LocalCluster{}
    32  
    33  // Port implements cluster.Cluster.
    34  func (b *LocalCluster) Port(ctx context.Context, i int) string {
    35  	return b.RPCPort(i)
    36  }
    37  
    38  // NumNodes implements cluster.Cluster.
    39  func (b *LocalCluster) NumNodes() int {
    40  	return len(b.Nodes)
    41  }
    42  
    43  // NewDB implements the Cluster interface.
    44  func (b *LocalCluster) NewDB(ctx context.Context, i int) (*gosql.DB, error) {
    45  	return gosql.Open("postgres", b.PGUrl(ctx, i))
    46  }
    47  
    48  // PGUrl implements cluster.Cluster.
    49  func (b *LocalCluster) PGUrl(ctx context.Context, i int) string {
    50  	return b.Nodes[i].PGUrl()
    51  }
    52  
    53  // InternalIP implements cluster.Cluster.
    54  func (b *LocalCluster) InternalIP(ctx context.Context, i int) net.IP {
    55  	ips, err := net.LookupIP(b.IPAddr(i))
    56  	if err != nil {
    57  		panic(err)
    58  	}
    59  	return ips[0]
    60  }
    61  
    62  // Assert implements cluster.Cluster.
    63  func (b *LocalCluster) Assert(ctx context.Context, t testing.TB) {
    64  	// TODO(tschottdorf): actually implement this.
    65  }
    66  
    67  // AssertAndStop implements cluster.Cluster.
    68  func (b *LocalCluster) AssertAndStop(ctx context.Context, t testing.TB) {
    69  	b.Assert(ctx, t)
    70  	b.Close()
    71  }
    72  
    73  // ExecCLI implements cluster.Cluster.
    74  func (b *LocalCluster) ExecCLI(ctx context.Context, i int, cmd []string) (string, string, error) {
    75  	cmd = append([]string{b.Cfg.Binary}, cmd...)
    76  	cmd = append(cmd, "--insecure", "--host", ":"+b.Port(ctx, i))
    77  	c := exec.CommandContext(ctx, cmd[0], cmd[1:]...)
    78  	var o, e bytes.Buffer
    79  	c.Stdout, c.Stderr = &o, &e
    80  	err := c.Run()
    81  	if err != nil {
    82  		err = errors.Wrapf(err, "cmd: %v\nstderr:\n %s\nstdout:\n %s", cmd, o.String(), e.String())
    83  	}
    84  	return o.String(), e.String(), err
    85  }
    86  
    87  // Kill implements cluster.Cluster.
    88  func (b *LocalCluster) Kill(ctx context.Context, i int) error {
    89  	b.Nodes[i].Kill()
    90  	return nil
    91  }
    92  
    93  // RestartAsync restarts the node. The returned channel receives an error or,
    94  // once the node is successfully connected to the cluster and serving, nil.
    95  func (b *LocalCluster) RestartAsync(ctx context.Context, i int) <-chan error {
    96  	b.Nodes[i].Kill()
    97  	joins := b.joins()
    98  	ch := b.Nodes[i].StartAsync(ctx, joins...)
    99  	if len(joins) == 0 && len(b.Nodes) > 1 {
   100  		// This blocking loop in is counter-intuitive but is essential in allowing
   101  		// restarts of whole clusters. Roughly the following happens:
   102  		//
   103  		// 1. The whole cluster gets killed.
   104  		// 2. A node restarts.
   105  		// 3. It will *block* here until it has written down the file which contains
   106  		//    enough information to link other nodes.
   107  		// 4. When restarting other nodes, and `.joins()` is passed in, these nodes
   108  		//    can connect (at least) to the first node.
   109  		// 5. the cluster can become healthy after restart.
   110  		//
   111  		// If we didn't block here, we'd start all nodes up with join addresses that
   112  		// don't make any sense, and the cluster would likely not become connected.
   113  		//
   114  		// An additional difficulty is that older versions (pre 1.1) don't write
   115  		// this file. That's why we let *every* node do this (you could try to make
   116  		// only the first one wait, but if that one is 1.0, bad luck).
   117  		// Short-circuiting the wait in the case that the listening URL file is
   118  		// written makes restarts work with 1.0 servers for the most part.
   119  		for {
   120  			if gossipAddr := b.Nodes[i].AdvertiseAddr(); gossipAddr != "" {
   121  				return ch
   122  			}
   123  			time.Sleep(10 * time.Millisecond)
   124  		}
   125  	}
   126  	return ch
   127  }
   128  
   129  // Restart implements cluster.Cluster.
   130  func (b *LocalCluster) Restart(ctx context.Context, i int) error {
   131  	return <-b.RestartAsync(ctx, i)
   132  }
   133  
   134  // URL implements cluster.Cluster.
   135  func (b *LocalCluster) URL(ctx context.Context, i int) string {
   136  	rest := b.Nodes[i].HTTPAddr()
   137  	if rest == "" {
   138  		return ""
   139  	}
   140  	return "http://" + rest
   141  }
   142  
   143  // Addr implements cluster.Cluster.
   144  func (b *LocalCluster) Addr(ctx context.Context, i int, port string) string {
   145  	return net.JoinHostPort(b.Nodes[i].AdvertiseAddr(), port)
   146  }
   147  
   148  // Hostname implements cluster.Cluster.
   149  func (b *LocalCluster) Hostname(i int) string {
   150  	return b.IPAddr(i)
   151  }