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 }