github.com/instana/go-sensor@v1.62.2-0.20240520081010-4919868049e1/fargate_agent_test.go (about)

     1  // (c) Copyright IBM Corp. 2021
     2  // (c) Copyright Instana Inc. 2020
     3  
     4  //go:build fargate && integration
     5  // +build fargate,integration
     6  
     7  package instana_test
     8  
     9  import (
    10  	"context"
    11  	"encoding/json"
    12  	"log"
    13  	"net/http"
    14  	"net/http/httptest"
    15  	"os"
    16  	"testing"
    17  	"time"
    18  
    19  	instana "github.com/instana/go-sensor"
    20  	"github.com/stretchr/testify/assert"
    21  	"github.com/stretchr/testify/require"
    22  )
    23  
    24  var agent *serverlessAgent
    25  
    26  func TestMain(m *testing.M) {
    27  	teardownEnv := setupAWSFargateEnv()
    28  	defer teardownEnv()
    29  
    30  	teardownSrv := setupMetadataServer()
    31  	defer teardownSrv()
    32  
    33  	defer restoreEnvVarFunc("INSTANA_AGENT_KEY")
    34  	os.Setenv("INSTANA_AGENT_KEY", "testkey1")
    35  
    36  	defer restoreEnvVarFunc("INSTANA_ZONE")
    37  	os.Setenv("INSTANA_ZONE", "testzone")
    38  
    39  	defer restoreEnvVarFunc("INSTANA_TAGS")
    40  	os.Setenv("INSTANA_TAGS", "key1=value1,key2")
    41  
    42  	defer restoreEnvVarFunc("INSTANA_SECRETS")
    43  	os.Setenv("INSTANA_SECRETS", "contains-ignore-case:key,password,secret,classified")
    44  
    45  	defer restoreEnvVarFunc("CLASSIFIED_DATA")
    46  	os.Setenv("CLASSIFIED_DATA", "classified")
    47  
    48  	var err error
    49  	agent, err = setupServerlessAgent()
    50  	if err != nil {
    51  		log.Fatalf("failed to initialize serverless agent: %s", err)
    52  	}
    53  
    54  	instana.InitSensor(instana.DefaultOptions())
    55  
    56  	os.Exit(m.Run())
    57  }
    58  
    59  func TestFargateAgent_SendMetrics(t *testing.T) {
    60  	defer agent.Reset()
    61  
    62  	require.Eventually(t, func() bool { return len(agent.Bundles) > 0 }, 2*time.Second, 500*time.Millisecond)
    63  
    64  	collected := agent.Bundles[0]
    65  
    66  	assert.Equal(t, "arn:aws:ecs:us-east-2:012345678910:task/9781c248-0edd-4cdb-9a93-f63cb662a5d3::nginx-curl", collected.Header.Get("X-Instana-Host"))
    67  	assert.Equal(t, "testkey1", collected.Header.Get("X-Instana-Key"))
    68  	assert.NotEmpty(t, collected.Header.Get("X-Instana-Time"))
    69  
    70  	var payload struct {
    71  		Metrics struct {
    72  			Plugins []struct {
    73  				Name     string                 `json:"name"`
    74  				EntityID string                 `json:"entityId"`
    75  				Data     map[string]interface{} `json:"data"`
    76  			} `json:"plugins"`
    77  		} `json:"metrics"`
    78  	}
    79  	require.NoError(t, json.Unmarshal(collected.Body, &payload))
    80  
    81  	pluginData := make(map[string][]serverlessAgentPluginPayload)
    82  	for _, plugin := range payload.Metrics.Plugins {
    83  		pluginData[plugin.Name] = append(pluginData[plugin.Name], serverlessAgentPluginPayload{plugin.EntityID, plugin.Data})
    84  	}
    85  
    86  	t.Run("AWS ECS Task plugin payload", func(t *testing.T) {
    87  		require.Len(t, pluginData["com.instana.plugin.aws.ecs.task"], 1)
    88  		d := pluginData["com.instana.plugin.aws.ecs.task"][0]
    89  
    90  		assert.NotEmpty(t, d.EntityID)
    91  		assert.Equal(t, d.Data["taskArn"], d.EntityID)
    92  
    93  		assert.Equal(t, "testzone", d.Data["instanaZone"])
    94  		assert.Equal(t, map[string]interface{}{"key1": "value1", "key2": nil}, d.Data["tags"])
    95  		assert.Equal(t, "default", d.Data["clusterArn"])
    96  		assert.Equal(t, "nginx", d.Data["taskDefinition"])
    97  		assert.Equal(t, "5", d.Data["taskDefinitionVersion"])
    98  	})
    99  
   100  	t.Run("AWS ECS Container plugin payload", func(t *testing.T) {
   101  		require.Len(t, pluginData["com.instana.plugin.aws.ecs.container"], 2)
   102  
   103  		containers := make(map[string]serverlessAgentPluginPayload)
   104  		for _, container := range pluginData["com.instana.plugin.aws.ecs.container"] {
   105  			containers[container.EntityID] = container
   106  		}
   107  
   108  		t.Run("instrumented", func(t *testing.T) {
   109  			d := containers["arn:aws:ecs:us-east-2:012345678910:task/9781c248-0edd-4cdb-9a93-f63cb662a5d3::nginx-curl"]
   110  			require.NotEmpty(t, d)
   111  
   112  			assert.NotEmpty(t, d.EntityID)
   113  
   114  			require.IsType(t, d.Data["taskArn"], "")
   115  			require.IsType(t, d.Data["containerName"], "")
   116  			assert.Equal(t, d.Data["taskArn"].(string)+"::"+d.Data["containerName"].(string), d.EntityID)
   117  
   118  			if assert.NotEmpty(t, d.Data["taskArn"]) {
   119  				require.NotEmpty(t, pluginData["com.instana.plugin.aws.ecs.task"])
   120  				assert.Equal(t, pluginData["com.instana.plugin.aws.ecs.task"][0].EntityID, d.Data["taskArn"])
   121  			}
   122  
   123  			assert.Equal(t, true, d.Data["instrumented"])
   124  			assert.Equal(t, "go", d.Data["runtime"])
   125  			assert.Equal(t, "43481a6ce4842eec8fe72fc28500c6b52edcc0917f105b83379f88cac1ff3946", d.Data["dockerId"])
   126  		})
   127  
   128  		t.Run("non-instrumented", func(t *testing.T) {
   129  			d := containers["arn:aws:ecs:us-east-2:012345678910:task/9781c248-0edd-4cdb-9a93-f63cb662a5d3::~internal~ecs~pause"]
   130  			require.NotEmpty(t, d)
   131  
   132  			assert.NotEmpty(t, d.EntityID)
   133  
   134  			require.IsType(t, d.Data["taskArn"], "")
   135  			require.IsType(t, d.Data["containerName"], "")
   136  			assert.Equal(t, d.Data["taskArn"].(string)+"::"+d.Data["containerName"].(string), d.EntityID)
   137  
   138  			if assert.NotEmpty(t, d.Data["taskArn"]) {
   139  				require.NotEmpty(t, pluginData["com.instana.plugin.aws.ecs.task"])
   140  				assert.Equal(t, pluginData["com.instana.plugin.aws.ecs.task"][0].EntityID, d.Data["taskArn"])
   141  			}
   142  
   143  			assert.Nil(t, d.Data["instrumented"])
   144  			assert.Empty(t, d.Data["runtime"])
   145  			assert.Equal(t, "731a0d6a3b4210e2448339bc7015aaa79bfe4fa256384f4102db86ef94cbbc4c", d.Data["dockerId"])
   146  		})
   147  	})
   148  
   149  	t.Run("Docker plugin payload", func(t *testing.T) {
   150  		require.Len(t, pluginData["com.instana.plugin.docker"], 2)
   151  
   152  		containers := make(map[string]serverlessAgentPluginPayload)
   153  		for _, container := range pluginData["com.instana.plugin.docker"] {
   154  			containers[container.EntityID] = container
   155  		}
   156  
   157  		t.Run("instrumented", func(t *testing.T) {
   158  			d := containers["arn:aws:ecs:us-east-2:012345678910:task/9781c248-0edd-4cdb-9a93-f63cb662a5d3::nginx-curl"]
   159  			require.NotEmpty(t, d)
   160  
   161  			assert.NotEmpty(t, d.EntityID)
   162  			assert.Equal(t, "43481a6ce4842eec8fe72fc28500c6b52edcc0917f105b83379f88cac1ff3946", d.Data["Id"])
   163  		})
   164  
   165  		t.Run("non-instrumented", func(t *testing.T) {
   166  			d := containers["arn:aws:ecs:us-east-2:012345678910:task/9781c248-0edd-4cdb-9a93-f63cb662a5d3::~internal~ecs~pause"]
   167  			require.NotEmpty(t, d)
   168  
   169  			assert.NotEmpty(t, d.EntityID)
   170  			assert.Equal(t, "731a0d6a3b4210e2448339bc7015aaa79bfe4fa256384f4102db86ef94cbbc4c", d.Data["Id"])
   171  		})
   172  	})
   173  
   174  	t.Run("Process plugin payload", func(t *testing.T) {
   175  		require.Len(t, pluginData["com.instana.plugin.process"], 1)
   176  		d := pluginData["com.instana.plugin.process"][0]
   177  
   178  		assert.NotEmpty(t, d.EntityID)
   179  
   180  		assert.Equal(t, "docker", d.Data["containerType"])
   181  		assert.Equal(t, "43481a6ce4842eec8fe72fc28500c6b52edcc0917f105b83379f88cac1ff3946", d.Data["container"])
   182  		if assert.IsType(t, map[string]interface{}{}, d.Data["env"]) {
   183  			env := d.Data["env"].(map[string]interface{})
   184  
   185  			assert.Equal(t, os.Getenv("INSTANA_ZONE"), env["INSTANA_ZONE"])
   186  			assert.Equal(t, os.Getenv("INSTANA_TAGS"), env["INSTANA_TAGS"])
   187  			assert.Equal(t, os.Getenv("INSTANA_AGENT_KEY"), env["INSTANA_AGENT_KEY"])
   188  
   189  			assert.Equal(t, "<redacted>", env["INSTANA_SECRETS"])
   190  			assert.Equal(t, "<redacted>", env["CLASSIFIED_DATA"])
   191  		}
   192  	})
   193  
   194  	t.Run("Go process plugin payload", func(t *testing.T) {
   195  		require.Len(t, pluginData["com.instana.plugin.golang"], 1)
   196  		d := pluginData["com.instana.plugin.golang"][0]
   197  
   198  		assert.NotEmpty(t, d.EntityID)
   199  
   200  		require.NotEmpty(t, pluginData["com.instana.plugin.process"])
   201  		assert.Equal(t, pluginData["com.instana.plugin.process"][0].EntityID, d.EntityID)
   202  
   203  		assert.NotEmpty(t, d.Data["metrics"])
   204  	})
   205  }
   206  
   207  func TestFargateAgent_SendSpans(t *testing.T) {
   208  	defer agent.Reset()
   209  
   210  	sensor := instana.NewSensor("testing")
   211  
   212  	sp := sensor.Tracer().StartSpan("entry")
   213  	sp.SetTag("value", "42")
   214  	sp.Finish()
   215  
   216  	require.Eventually(t, func() bool {
   217  		if len(agent.Bundles) == 0 {
   218  			return false
   219  		}
   220  
   221  		for _, bundle := range agent.Bundles {
   222  			var payload struct {
   223  				Spans []json.RawMessage `json:"spans"`
   224  			}
   225  
   226  			json.Unmarshal(bundle.Body, &payload)
   227  			if len(payload.Spans) > 0 {
   228  				return true
   229  			}
   230  		}
   231  
   232  		return false
   233  	}, 4*time.Second, 500*time.Millisecond)
   234  
   235  	var spans []map[string]json.RawMessage
   236  	for _, bundle := range agent.Bundles {
   237  		var payload struct {
   238  			Spans []map[string]json.RawMessage `json:"spans"`
   239  		}
   240  
   241  		require.NoError(t, json.Unmarshal(bundle.Body, &payload), "%s", string(bundle.Body))
   242  		spans = append(spans, payload.Spans...)
   243  	}
   244  
   245  	require.Len(t, spans, 1)
   246  	assert.JSONEq(t, `{"hl": true, "cp": "aws", "e": "arn:aws:ecs:us-east-2:012345678910:task/9781c248-0edd-4cdb-9a93-f63cb662a5d3::nginx-curl"}`, string(spans[0]["f"]))
   247  }
   248  
   249  func TestFargateAgent_FlushSpans(t *testing.T) {
   250  	defer agent.Reset()
   251  
   252  	tracer := instana.NewTracer()
   253  	sensor := instana.NewSensorWithTracer(tracer)
   254  
   255  	sp := sensor.Tracer().StartSpan("entry")
   256  	sp.SetTag("value", "42")
   257  	sp.Finish()
   258  
   259  	ctx, cancel := context.WithTimeout(context.Background(), time.Second)
   260  	defer cancel()
   261  
   262  	require.NoError(t, tracer.Flush(ctx))
   263  }
   264  
   265  func setupAWSFargateEnv() func() {
   266  	teardown := restoreEnvVarFunc("AWS_EXECUTION_ENV")
   267  	os.Setenv("AWS_EXECUTION_ENV", "AWS_ECS_FARGATE")
   268  
   269  	return teardown
   270  }
   271  
   272  func setupMetadataServer() func() {
   273  	mux := http.NewServeMux()
   274  	mux.HandleFunc("/", func(w http.ResponseWriter, req *http.Request) {
   275  		http.ServeFile(w, req, "aws/testdata/container_metadata.json")
   276  	})
   277  	mux.HandleFunc("/task", func(w http.ResponseWriter, req *http.Request) {
   278  		http.ServeFile(w, req, "aws/testdata/task_metadata.json")
   279  	})
   280  	mux.HandleFunc("/task/stats", func(w http.ResponseWriter, req *http.Request) {
   281  		http.ServeFile(w, req, "aws/testdata/task_stats.json")
   282  	})
   283  
   284  	srv := httptest.NewServer(mux)
   285  
   286  	teardown := restoreEnvVarFunc("ECS_CONTAINER_METADATA_URI")
   287  	os.Setenv("ECS_CONTAINER_METADATA_URI", srv.URL)
   288  
   289  	return func() {
   290  		teardown()
   291  		srv.Close()
   292  	}
   293  }