github.com/instana/go-sensor@v1.62.2-0.20240520081010-4919868049e1/fsm_test.go (about) 1 // (c) Copyright IBM Corp. 2022 2 3 package instana 4 5 import ( 6 "context" 7 "io" 8 "net/http" 9 "net/http/httptest" 10 "net/url" 11 "os" 12 "strconv" 13 "testing" 14 "time" 15 16 f "github.com/looplab/fsm" 17 "github.com/stretchr/testify/assert" 18 ) 19 20 func getTestServer(fn func(w http.ResponseWriter, r *http.Request)) *httptest.Server { 21 handler := http.NewServeMux() 22 handler.HandleFunc("/", fn) 23 return httptest.NewServer(handler) 24 } 25 26 // Case: Current state is ANNOUNCED, trying to be READY 27 func Test_fsmS_testAgent(t *testing.T) { 28 // Forces the mocked agent to fail with HTTP 400 in the first call to lead fsm to retry once 29 var serverGaveErrorOnFirstCall bool 30 31 server := getTestServer(func(w http.ResponseWriter, r *http.Request) { 32 if serverGaveErrorOnFirstCall { 33 w.WriteHeader(http.StatusOK) 34 } else { 35 serverGaveErrorOnFirstCall = true 36 w.WriteHeader(http.StatusBadRequest) 37 } 38 }) 39 40 defer server.Close() 41 42 surl := server.URL 43 u, err := url.Parse(surl) 44 45 assert.NoError(t, err) 46 47 res := make(chan bool, 1) 48 49 r := &fsmS{ 50 agentComm: newAgentCommunicator(u.Hostname(), u.Port(), &fromS{EntityID: "12345"}, defaultLogger), 51 fsm: f.NewFSM( 52 "announced", 53 f.Events{ 54 {Name: eTest, Src: []string{"announced"}, Dst: "ready"}, 55 }, 56 f.Callbacks{ 57 "ready": func(_ context.Context, event *f.Event) { 58 res <- true 59 }, 60 }), 61 retriesLeft: maximumRetries, 62 expDelayFunc: func(retryNumber int) time.Duration { 63 return 0 64 }, 65 logger: defaultLogger, 66 } 67 68 r.testAgent(context.Background(), &f.Event{}) 69 70 assert.True(t, <-res) 71 // after a successful request, retriesLeft is reset to maximumRetries 72 assert.Equal(t, maximumRetries, r.retriesLeft) 73 } 74 75 func Test_fsmS_testAgent_Error(t *testing.T) { 76 // Forces the mocked agent to fail with HTTP 400 to lead fsm to retry 77 server := getTestServer(func(w http.ResponseWriter, r *http.Request) { 78 w.WriteHeader(http.StatusBadRequest) 79 }) 80 81 defer server.Close() 82 83 surl := server.URL 84 u, err := url.Parse(surl) 85 86 assert.NoError(t, err) 87 88 res := make(chan bool, 1) 89 90 r := &fsmS{ 91 agentComm: newAgentCommunicator(u.Hostname(), u.Port(), &fromS{}, defaultLogger), 92 fsm: f.NewFSM( 93 "announced", 94 f.Events{ 95 {Name: eInit, Src: []string{"announced"}, Dst: "init"}}, 96 f.Callbacks{ 97 "init": func(_ context.Context, event *f.Event) { 98 res <- true 99 }, 100 }), 101 retriesLeft: maximumRetries, 102 expDelayFunc: func(retryNumber int) time.Duration { 103 return 0 104 }, 105 logger: defaultLogger, 106 } 107 108 r.testAgent(context.Background(), &f.Event{}) 109 110 assert.True(t, <-res) 111 assert.Equal(t, 0, r.retriesLeft) 112 } 113 114 func Test_fsmS_announceSensor(t *testing.T) { 115 // initializes the global sensor as it is needed when the announcement is successful 116 InitSensor(DefaultOptions()) 117 defer ShutdownSensor() 118 119 // Forces the mocked agent to fail with HTTP 400 in the first call to lead fsm to retry once 120 var serverGaveErrorOnFirstCall bool 121 122 server := getTestServer(func(w http.ResponseWriter, r *http.Request) { 123 if serverGaveErrorOnFirstCall { 124 pid := strconv.FormatInt(int64(os.Getpid()), 10) 125 io.WriteString(w, `{"pid":`+pid+`}`) 126 } else { 127 serverGaveErrorOnFirstCall = true 128 w.WriteHeader(http.StatusBadRequest) 129 } 130 }) 131 defer server.Close() 132 133 surl := server.URL 134 u, err := url.Parse(surl) 135 136 assert.NoError(t, err) 137 138 res := make(chan bool, 1) 139 140 r := &fsmS{ 141 agentComm: newAgentCommunicator(u.Hostname(), u.Port(), &fromS{}, defaultLogger), 142 fsm: f.NewFSM( 143 "unannounced", 144 f.Events{ 145 {Name: eAnnounce, Src: []string{"unannounced"}, Dst: "announced"}}, 146 f.Callbacks{ 147 "announced": func(_ context.Context, event *f.Event) { 148 res <- true 149 }, 150 }), 151 retriesLeft: maximumRetries, 152 expDelayFunc: func(retryNumber int) time.Duration { 153 return 0 154 }, 155 logger: defaultLogger, 156 } 157 158 r.announceSensor(context.Background(), &f.Event{}) 159 160 assert.True(t, <-res) 161 assert.Equal(t, maximumRetries, r.retriesLeft) 162 } 163 164 func Test_fsmS_announceSensor_Error(t *testing.T) { 165 server := getTestServer(func(w http.ResponseWriter, r *http.Request) { 166 w.WriteHeader(http.StatusBadRequest) 167 }) 168 defer server.Close() 169 170 surl := server.URL 171 u, err := url.Parse(surl) 172 173 assert.NoError(t, err) 174 175 res := make(chan bool, 1) 176 177 r := &fsmS{ 178 agentComm: newAgentCommunicator(u.Hostname(), u.Port(), &fromS{}, defaultLogger), 179 fsm: f.NewFSM( 180 "unannounced", 181 f.Events{ 182 {Name: eInit, Src: []string{"unannounced"}, Dst: "init"}}, 183 f.Callbacks{ 184 "init": func(_ context.Context, event *f.Event) { 185 res <- true 186 }, 187 }), 188 retriesLeft: maximumRetries, 189 expDelayFunc: func(retryNumber int) time.Duration { 190 return 0 191 }, 192 logger: defaultLogger, 193 } 194 195 r.announceSensor(context.Background(), &f.Event{}) 196 197 assert.True(t, <-res) 198 assert.Equal(t, 0, r.retriesLeft) 199 } 200 201 func Test_fsmS_lookupAgentHost(t *testing.T) { 202 // Forces the mocked agent to fail with HTTP 400 in the first call to lead fsm to retry once 203 var serverGaveErrorOnFirstCall bool 204 205 server := getTestServer(func(w http.ResponseWriter, r *http.Request) { 206 if serverGaveErrorOnFirstCall { 207 w.Header().Add("Server", agentHeader) 208 w.WriteHeader(http.StatusOK) 209 } else { 210 serverGaveErrorOnFirstCall = true 211 w.WriteHeader(http.StatusBadRequest) 212 } 213 }) 214 defer server.Close() 215 216 surl := server.URL 217 u, err := url.Parse(surl) 218 219 assert.NoError(t, err) 220 221 res := make(chan bool, 1) 222 223 r := &fsmS{ 224 agentComm: newAgentCommunicator(u.Hostname(), u.Port(), &fromS{}, defaultLogger), 225 lookupAgentHostRetryPeriod: 0, 226 fsm: f.NewFSM( 227 "init", 228 f.Events{ 229 {Name: eLookup, Src: []string{"init"}, Dst: "unannounced"}}, 230 f.Callbacks{ 231 "enter_unannounced": func(_ context.Context, event *f.Event) { 232 res <- true 233 }, 234 }), 235 retriesLeft: maximumRetries, 236 expDelayFunc: func(retryNumber int) time.Duration { 237 return 0 238 }, 239 logger: defaultLogger, 240 } 241 242 r.lookupAgentHost(context.Background(), &f.Event{}) 243 244 assert.True(t, <-res) 245 assert.Equal(t, maximumRetries, r.retriesLeft) 246 } 247 248 // Case: 249 // 1. Connection between the application and the agent is established. 250 // 2. Connection with Agent is lost. 251 // 3. Former Agent host is no longer available. 252 // 4. A valid Agent hostname is available in the INSTANA_AGENT_HOST env var. 253 // 5. A connection with the Agent is reestablished via env var. 254 func Test_fsmS_agentConnectionReestablished(t *testing.T) { 255 agentResponseJSON := `{ 256 "pid": 37808, 257 "agentUuid": "88:66:5a:ff:fe:05:a5:f0", 258 "extraHeaders": ["expected-value"], 259 "secrets": { 260 "matcher": "contains-ignore-case", 261 "list": ["key","pass","secret"] 262 } 263 }` 264 265 sensor = &sensorS{ 266 options: DefaultOptions(), 267 } 268 defer func() { 269 sensor = nil 270 }() 271 272 server := getTestServer(func(w http.ResponseWriter, r *http.Request) { 273 p := r.URL.Path 274 275 // announce phase (enter_unannounced) 276 if p == "/com.instana.plugin.golang.discovery" { 277 io.WriteString(w, agentResponseJSON) 278 return 279 } 280 281 w.WriteHeader(http.StatusOK) 282 }) 283 defer server.Close() 284 285 surl := server.URL 286 u, err := url.Parse(surl) 287 288 os.Setenv("INSTANA_AGENT_HOST", u.Hostname()) 289 defer func() { 290 os.Unsetenv("INSTANA_AGENT_HOST") 291 }() 292 293 assert.NoError(t, err) 294 295 res := make(chan bool) 296 297 r := &fsmS{ 298 agentComm: newAgentCommunicator(u.Hostname(), u.Port(), &fromS{EntityID: "12345"}, defaultLogger), 299 lookupAgentHostRetryPeriod: 0, 300 retriesLeft: maximumRetries, 301 expDelayFunc: func(retryNumber int) time.Duration { 302 return 0 303 }, 304 logger: defaultLogger, 305 } 306 307 r.fsm = f.NewFSM( 308 "none", 309 f.Events{ 310 {Name: eInit, Src: []string{"none", "unannounced", "announced", "ready"}, Dst: "init"}, 311 {Name: eLookup, Src: []string{"init"}, Dst: "unannounced"}, 312 {Name: eAnnounce, Src: []string{"unannounced"}, Dst: "announced"}, 313 {Name: eTest, Src: []string{"announced"}, Dst: "ready"}}, 314 f.Callbacks{ 315 "init": func(ctx context.Context, e *f.Event) { 316 r.lookupAgentHost(ctx, e) 317 }, 318 "enter_unannounced": func(ctx context.Context, e *f.Event) { 319 r.announceSensor(ctx, e) 320 }, 321 "enter_announced": func(ctx context.Context, e *f.Event) { 322 r.testAgent(ctx, e) 323 }, 324 "ready": func(ctx context.Context, e *f.Event) { 325 r.ready(ctx, e) 326 res <- true 327 }, 328 }) 329 330 // We fail the test if the channel does not resolve after 5 seconds 331 go func() { 332 time.AfterFunc(time.Second*5, func() { 333 res <- false 334 }) 335 }() 336 337 r.fsm.Event(context.Background(), eInit) 338 339 assert.True(t, <-res) 340 341 // Simulate Agent connection lost 342 r.agentComm.host = "invalid_host" 343 r.reset() 344 345 assert.True(t, <-res) 346 assert.Equal(t, os.Getenv("INSTANA_AGENT_HOST"), r.agentComm.host, "Configured host to be updated with env var value") 347 }