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  }