github.com/m3db/m3@v1.5.0/src/m3em/integration/harness.go (about) 1 // Copyright (c) 2017 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 // +build integration 22 23 package integration 24 25 import ( 26 "io" 27 "net" 28 "os" 29 "strings" 30 "sync/atomic" 31 "testing" 32 "time" 33 34 "github.com/m3db/m3/src/cluster/placement" 35 "github.com/m3db/m3/src/m3em/agent" 36 hb "github.com/m3db/m3/src/m3em/generated/proto/heartbeat" 37 "github.com/m3db/m3/src/m3em/generated/proto/m3em" 38 "github.com/m3db/m3/src/m3em/integration/resources" 39 "github.com/m3db/m3/src/m3em/node" 40 xgrpc "github.com/m3db/m3/src/m3em/x/grpc" 41 xerrors "github.com/m3db/m3/src/x/errors" 42 "github.com/m3db/m3/src/x/instrument" 43 xtest "github.com/m3db/m3/src/x/test" 44 45 "github.com/stretchr/testify/require" 46 "go.uber.org/zap" 47 "google.golang.org/grpc" 48 "google.golang.org/grpc/credentials" 49 ) 50 51 type closeFn func() error 52 53 type testHarness struct { 54 io.Closer 55 56 closers []closeFn 57 t *testing.T 58 scriptNum int 59 harnessDir string 60 iopts instrument.Options 61 logger *zap.Logger 62 agentListener net.Listener 63 agentOptions agent.Options 64 agentService agent.Agent 65 agentServer *grpc.Server 66 agentStopped int32 67 nodeOptions node.Options 68 nodeService node.ServiceNode 69 heartbeatListener net.Listener 70 heartbeatServer *grpc.Server 71 heartbeatStopped int32 72 } 73 74 func newTestHarnessWithHearbeat(t *testing.T) *testHarness { 75 return newTestHarnessWithHearbeatOptions(t, testHeartbeatOptions()) 76 } 77 78 func newTestHarness(t *testing.T) *testHarness { 79 return newTestHarnessWithHearbeatOptions(t, nil) 80 } 81 82 func newTestHarnessWithHearbeatOptions(t *testing.T, hbOpts node.HeartbeatOptions) *testHarness { 83 logger := xtest.NewLogger(t) 84 useTLS := strings.ToLower(os.Getenv("TEST_TLS_COMMUNICATION")) == "true" 85 if useTLS { 86 logger.Info("using TLS for RPC") 87 } else { 88 logger.Info("not using TLS for RPC") 89 } 90 91 th := &testHarness{ 92 t: t, 93 logger: logger, 94 iopts: instrument.NewOptions().SetLogger(logger), 95 } 96 97 th.harnessDir = newTempDir(t) 98 th.addCloser(func() error { 99 return os.RemoveAll(th.harnessDir) 100 }) 101 102 // create agent listener 103 agentListener, err := net.Listen("tcp", "127.0.0.1:0") 104 require.NoError(t, err) 105 th.agentListener = agentListener 106 th.addCloser(func() error { 107 return agentListener.Close() 108 }) 109 110 var serverCreds credentials.TransportCredentials 111 if useTLS { 112 serverCreds, err = resources.ServerTransportCredentials() 113 require.NoError(th.t, err) 114 } 115 116 // create agent (service|server) 117 th.agentOptions = agent.NewOptions(th.iopts). 118 SetWorkingDirectory(newSubDir(t, th.harnessDir, "agent-wd")). 119 SetExecGenFn(testExecGenFn) 120 service, err := agent.New(th.agentOptions) 121 require.NoError(t, err) 122 th.agentService = service 123 th.agentServer = xgrpc.NewServer(serverCreds) 124 m3em.RegisterOperatorServer(th.agentServer, service) 125 th.addCloser(func() error { 126 th.agentServer.GracefulStop() 127 return nil 128 }) 129 130 // if provided valid heartbeat options, create heartbeating resources 131 if hbOpts != nil { 132 heartbeatListener, err := net.Listen("tcp", "127.0.0.1:0") 133 require.NoError(t, err) 134 th.heartbeatListener = heartbeatListener 135 th.addCloser(func() error { 136 return heartbeatListener.Close() 137 }) 138 139 hbRouter := node.NewHeartbeatRouter(heartbeatListener.Addr().String()) 140 hbServer := xgrpc.NewServer(nil) 141 hb.RegisterHeartbeaterServer(hbServer, hbRouter) 142 th.heartbeatServer = hbServer 143 th.addCloser(func() error { 144 th.heartbeatServer.GracefulStop() 145 return nil 146 }) 147 hbOpts = hbOpts.SetEnabled(true).SetHeartbeatRouter(hbRouter) 148 149 } else { 150 hbOpts = node.NewHeartbeatOptions().SetEnabled(false) 151 } 152 153 // create options to communicate with agent 154 th.nodeOptions = th.newNodeOptions(hbOpts, useTLS) 155 svc := placement.NewInstance() 156 node, err := node.New(svc, th.nodeOptions) 157 require.NoError(t, err) 158 th.nodeService = node 159 th.addCloser(func() error { 160 return node.Close() 161 }) 162 163 return th 164 } 165 166 func (th *testHarness) addCloser(fn closeFn) { 167 th.closers = append(th.closers, fn) 168 } 169 170 func (th *testHarness) Start() { 171 serveGRPCServer := func(server *grpc.Server, listener net.Listener, state *int32) { 172 err := server.Serve(listener) 173 if closed := atomic.LoadInt32(state); closed == 0 { 174 require.NoError(th.t, err) 175 } 176 } 177 178 if th.nodeOptions.HeartbeatOptions().Enabled() { 179 go serveGRPCServer(th.heartbeatServer, th.heartbeatListener, &th.heartbeatStopped) 180 } 181 182 go serveGRPCServer(th.agentServer, th.agentListener, &th.agentStopped) 183 } 184 185 func (th *testHarness) StopHeartbeatServer() { 186 atomic.StoreInt32(&th.heartbeatStopped, 1) 187 th.heartbeatServer.Stop() 188 } 189 190 func (th *testHarness) Close() error { 191 atomic.StoreInt32(&th.agentStopped, 1) 192 atomic.StoreInt32(&th.heartbeatStopped, 1) 193 194 var multiErr xerrors.MultiError 195 for i := len(th.closers) - 1; i >= 0; i-- { 196 closer := th.closers[i] 197 multiErr = multiErr.Add(closer()) 198 } 199 return multiErr.FinalError() 200 } 201 202 func (th *testHarness) newTempFile(contents []byte) *os.File { 203 file := newTempFile(th.t, th.harnessDir, contents) 204 th.addCloser(func() error { 205 return os.Remove(file.Name()) 206 }) 207 return file 208 } 209 210 func (th *testHarness) newTestScript(program testProgram) string { 211 sn := th.scriptNum 212 th.scriptNum++ 213 file := newTestScript(th.t, th.harnessDir, sn, program) 214 th.addCloser(func() error { 215 return os.Remove(file) 216 }) 217 return file 218 } 219 220 func testHeartbeatOptions() node.HeartbeatOptions { 221 return node.NewHeartbeatOptions(). 222 SetEnabled(true). 223 SetTimeout(2 * time.Second). 224 SetCheckInterval(100 * time.Millisecond). 225 SetInterval(time.Second) 226 } 227 228 func (th *testHarness) newNodeOptions(hbOpts node.HeartbeatOptions, useTLS bool) node.Options { 229 return node.NewOptions(th.iopts). 230 SetHeartbeatOptions(hbOpts). 231 SetOperatorClientFn(th.testOperatorClientFn(useTLS)) 232 } 233 234 func (th *testHarness) testOperatorClientFn(useTLS bool) node.OperatorClientFn { 235 endpoint := th.agentListener.Addr().String() 236 dialOpt := grpc.WithInsecure() 237 if useTLS { 238 tc, err := resources.ClientTransportCredentials() 239 require.NoError(th.t, err) 240 dialOpt = grpc.WithTransportCredentials(tc) 241 } 242 return func() (*grpc.ClientConn, m3em.OperatorClient, error) { 243 conn, err := grpc.Dial(endpoint, dialOpt) 244 if err != nil { 245 return nil, nil, err 246 } 247 return conn, m3em.NewOperatorClient(conn), err 248 } 249 }