github.com/m3db/m3@v1.5.1-0.20231129193456-75a402aa583b/src/m3em/node/heartbeat_test.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 package node 22 23 import ( 24 "sync" 25 "testing" 26 "time" 27 28 hb "github.com/m3db/m3/src/m3em/generated/proto/heartbeat" 29 "github.com/m3db/m3/src/x/instrument" 30 31 "github.com/stretchr/testify/require" 32 context "golang.org/x/net/context" 33 ) 34 35 var ( 36 testEndpoint = "a.b.c.d:12345" 37 ) 38 39 func newTestHeartbeatOpts() HeartbeatOptions { 40 return NewHeartbeatOptions(). 41 SetCheckInterval(10 * time.Millisecond). 42 SetInterval(20 * time.Millisecond). 43 SetTimeout(100 * time.Millisecond) 44 } 45 46 func newTestListener(t *testing.T) *listener { 47 return &listener{ 48 onProcessTerminate: func(_ ServiceNode, desc string) { require.Fail(t, "onProcessTerminate invoked %s", desc) }, 49 onHeartbeatTimeout: func(_ ServiceNode, ts time.Time) { require.Fail(t, "onHeartbeatTimeout invoked %s", ts.String()) }, 50 onOverwrite: func(_ ServiceNode, desc string) { require.Fail(t, "onOverwrite invoked %s", desc) }, 51 } 52 } 53 54 func TestHeartbeaterSimple(t *testing.T) { 55 var ( 56 lg = newListenerGroup(nil) 57 opts = newTestHeartbeatOpts() 58 iopts = instrument.NewOptions() 59 hbServer = newHeartbeater(lg, opts, iopts) 60 ) 61 require.NoError(t, hbServer.start()) 62 63 hbServer.Heartbeat(context.Background(), 64 &hb.HeartbeatRequest{ 65 Code: hb.HeartbeatCode_HEALTHY, 66 ProcessRunning: false, 67 }) 68 time.Sleep(10 * time.Millisecond) // to yield to any pending go routines 69 hbServer.stop() 70 } 71 72 func TestHeartbeatingUnknownCode(t *testing.T) { 73 var ( 74 lg = newListenerGroup(nil) 75 opts = newTestHeartbeatOpts() 76 iopts = instrument.NewOptions() 77 hbServer = newHeartbeater(lg, opts, iopts) 78 ) 79 require.NoError(t, hbServer.start()) 80 81 _, err := hbServer.Heartbeat(context.Background(), 82 &hb.HeartbeatRequest{ 83 Code: hb.HeartbeatCode_UNKNOWN, 84 ProcessRunning: false, 85 }) 86 require.Error(t, err) 87 time.Sleep(10 * time.Millisecond) // to yield to any pending go routines 88 hbServer.stop() 89 } 90 91 func TestHeartbeatingProcessTermination(t *testing.T) { 92 var ( 93 lg = newListenerGroup(nil) 94 opts = newTestHeartbeatOpts() 95 iopts = instrument.NewOptions() 96 ) 97 98 var ( 99 lock sync.Mutex 100 processTerminated = false 101 lnr = newTestListener(t) 102 ) 103 lnr.onProcessTerminate = func(ServiceNode, string) { 104 lock.Lock() 105 processTerminated = true 106 lock.Unlock() 107 } 108 lg.add(lnr) 109 110 hbServer := newHeartbeater(lg, opts, iopts) 111 require.NoError(t, hbServer.start()) 112 _, err := hbServer.Heartbeat(context.Background(), 113 &hb.HeartbeatRequest{ 114 Code: hb.HeartbeatCode_PROCESS_TERMINATION, 115 }) 116 require.NoError(t, err) 117 time.Sleep(10 * time.Millisecond) // to yield to any pending go routines 118 hbServer.stop() 119 120 lock.Lock() 121 defer lock.Unlock() 122 require.True(t, processTerminated) 123 } 124 125 func TestHeartbeatingOverwrite(t *testing.T) { 126 var ( 127 lg = newListenerGroup(nil) 128 opts = newTestHeartbeatOpts() 129 iopts = instrument.NewOptions() 130 ) 131 132 var ( 133 lock sync.Mutex 134 overwritten = false 135 lnr = newTestListener(t) 136 ) 137 lnr.onOverwrite = func(ServiceNode, string) { 138 lock.Lock() 139 overwritten = true 140 lock.Unlock() 141 } 142 lg.add(lnr) 143 144 hbServer := newHeartbeater(lg, opts, iopts) 145 require.NoError(t, hbServer.start()) 146 _, err := hbServer.Heartbeat(context.Background(), 147 &hb.HeartbeatRequest{ 148 Code: hb.HeartbeatCode_OVERWRITTEN, 149 }) 150 require.NoError(t, err) 151 time.Sleep(10 * time.Millisecond) // to yield to any pending go routines 152 hbServer.stop() 153 154 lock.Lock() 155 defer lock.Unlock() 156 require.True(t, overwritten) 157 } 158 159 func TestHeartbeatingTimeout(t *testing.T) { 160 var ( 161 now = time.Now() 162 callCount = 0 163 nowFn = func() time.Time { 164 callCount++ 165 if callCount == 1 { 166 return now 167 } 168 return now.Add(time.Hour) 169 } 170 lg = newListenerGroup(nil) 171 opts = newTestHeartbeatOpts().SetNowFn(nowFn) 172 iopts = instrument.NewOptions() 173 ) 174 175 var ( 176 lock sync.Mutex 177 timedout = false 178 lnr = newTestListener(t) 179 ) 180 lnr.onHeartbeatTimeout = func(_ ServiceNode, ts time.Time) { 181 lock.Lock() 182 timedout = true 183 lock.Unlock() 184 } 185 lg.add(lnr) 186 187 hbServer := newHeartbeater(lg, opts, iopts) 188 require.NoError(t, hbServer.start()) 189 _, err := hbServer.Heartbeat(context.Background(), 190 &hb.HeartbeatRequest{ 191 Code: hb.HeartbeatCode_HEALTHY, 192 }) 193 require.NoError(t, err) 194 time.Sleep(100 * time.Millisecond) 195 hbServer.stop() 196 197 lock.Lock() 198 defer lock.Unlock() 199 require.True(t, timedout) 200 } 201 202 func TestHeartbeatRouterValidRoute(t *testing.T) { 203 var ( 204 lg = newListenerGroup(nil) 205 opts = newTestHeartbeatOpts() 206 iopts = instrument.NewOptions() 207 router = NewHeartbeatRouter(testEndpoint) 208 ) 209 require.Equal(t, testEndpoint, router.Endpoint()) 210 211 var ( 212 lock sync.Mutex 213 overwritten = false 214 lnr = newTestListener(t) 215 ) 216 lnr.onOverwrite = func(ServiceNode, string) { 217 lock.Lock() 218 overwritten = true 219 lock.Unlock() 220 } 221 lg.add(lnr) 222 hbServer := newHeartbeater(lg, opts, iopts) 223 require.NoError(t, hbServer.start()) 224 225 testUUID := "123456789" 226 require.NoError(t, router.Register(testUUID, hbServer)) 227 require.Error(t, router.Register(testUUID, nil)) 228 229 _, err := router.Heartbeat(context.Background(), 230 &hb.HeartbeatRequest{ 231 OperatorUuid: testUUID, 232 Code: hb.HeartbeatCode_OVERWRITTEN, 233 }) 234 require.NoError(t, err) 235 time.Sleep(10 * time.Millisecond) // to yield to any pending go routines 236 hbServer.stop() 237 238 lock.Lock() 239 defer lock.Unlock() 240 require.True(t, overwritten) 241 } 242 243 func TestHeartbeatRouterDeregister(t *testing.T) { 244 var ( 245 lg = newListenerGroup(nil) 246 opts = newTestHeartbeatOpts() 247 iopts = instrument.NewOptions() 248 router = NewHeartbeatRouter(testEndpoint) 249 ) 250 require.Equal(t, testEndpoint, router.Endpoint()) 251 hbServer := newHeartbeater(lg, opts, iopts) 252 testUUID := "123456789" 253 254 require.NoError(t, router.Register(testUUID, hbServer)) 255 _, err := router.Heartbeat(context.Background(), 256 &hb.HeartbeatRequest{ 257 OperatorUuid: string(testUUID), 258 Code: hb.HeartbeatCode_OVERWRITTEN, 259 }) 260 require.NoError(t, err) 261 262 require.NoError(t, router.Deregister(testUUID)) 263 _, err = router.Heartbeat(context.Background(), 264 &hb.HeartbeatRequest{ 265 OperatorUuid: string(testUUID), 266 Code: hb.HeartbeatCode_OVERWRITTEN, 267 }) 268 require.Error(t, err) 269 } 270 271 func TestHeartbeatRouterInvalidRoute(t *testing.T) { 272 var ( 273 router = NewHeartbeatRouter(testEndpoint) 274 testUUID = "123456789" 275 ) 276 _, err := router.Heartbeat(context.Background(), 277 &hb.HeartbeatRequest{ 278 OperatorUuid: testUUID, 279 Code: hb.HeartbeatCode_OVERWRITTEN, 280 }) 281 require.Error(t, err) 282 }