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

     1  // (c) Copyright IBM Corp. 2022
     2  
     3  package instana
     4  
     5  import (
     6  	"bytes"
     7  	"context"
     8  	"encoding/json"
     9  	"io"
    10  	"io/ioutil"
    11  	"net/http"
    12  )
    13  
    14  // agentCommunicator is a collection of data and actions to be executed against the agent.
    15  type agentCommunicator struct {
    16  	// host is the agent host. It can be updated via default gateway or a new client announcement.
    17  	host string
    18  
    19  	// port id the agent port.
    20  	port string
    21  
    22  	// from is the agent information sent with each span in the "from" (span.f) section. it's format is as follows:
    23  	// {e: "entityId", h: "hostAgentId", hl: trueIfServerlessPlatform, cp: "The cloud provider for a hostless span"}
    24  	// Only span.f.e is mandatory.
    25  	from *fromS
    26  
    27  	// client is an HTTP client
    28  	client httpClient
    29  
    30  	// l is the Instana logger
    31  	l LeveledLogger
    32  }
    33  
    34  // buildURL builds an Agent URL based on the sufix for the different Agent services.
    35  func (a *agentCommunicator) buildURL(sufix string) string {
    36  	url := "http://" + a.host + ":" + a.port + sufix
    37  
    38  	if sufix[len(sufix)-1:] == "." && a.from.EntityID != "" {
    39  		url += a.from.EntityID
    40  	}
    41  
    42  	return url
    43  }
    44  
    45  // checkForSuccessResponse checks for a successful GET operation with the agent host
    46  func (a *agentCommunicator) checkForSuccessResponse() bool {
    47  	url := a.buildURL("/")
    48  
    49  	req, err := http.NewRequest(http.MethodGet, url, nil)
    50  	if err != nil {
    51  		a.l.Debug("Error creating request while attempting to retrieve the 'Server' response: ", err.Error())
    52  		return false
    53  	}
    54  
    55  	resp, err := a.client.Do(req)
    56  	if err != nil || resp == nil {
    57  		a.l.Debug("No response from the agent while attempting to retrieve the 'Server' response: ", err.Error())
    58  		return false
    59  	}
    60  
    61  	defer func() {
    62  		io.CopyN(ioutil.Discard, resp.Body, 256<<10)
    63  		resp.Body.Close()
    64  	}()
    65  
    66  	if resp.StatusCode < 200 || resp.StatusCode > 299 {
    67  		a.l.Debug("Unexpected response from the agent host server. Status code: ", resp.StatusCode)
    68  		return false
    69  	}
    70  
    71  	a.l.Debug("Expected response from Agent! Status code: ", resp.StatusCode)
    72  
    73  	return true
    74  }
    75  
    76  // agentResponse attempts to retrieve the agent response containing its configuration
    77  func (a *agentCommunicator) agentResponse(d *discoveryS) *agentResponse {
    78  	jsonData, _ := json.Marshal(d)
    79  
    80  	var resp agentResponse
    81  
    82  	u := a.buildURL(agentDiscoveryURL)
    83  
    84  	req, err := http.NewRequest(http.MethodPut, u, bytes.NewBuffer(jsonData))
    85  
    86  	if err != nil {
    87  		a.l.Debug("Error creating request to the agent while attempting to get the response: ", err.Error())
    88  		return nil
    89  	}
    90  
    91  	res, err := a.client.Do(req)
    92  
    93  	if res == nil {
    94  		a.l.Debug("No response from the agent while attempting to get the response: ", err.Error())
    95  		return nil
    96  	}
    97  
    98  	defer func() {
    99  		io.CopyN(ioutil.Discard, res.Body, 256<<10)
   100  		res.Body.Close()
   101  	}()
   102  
   103  	badResponse := res.StatusCode < 200 || res.StatusCode >= 300
   104  
   105  	if err != nil || badResponse {
   106  		a.l.Debug("Error requesting response data from the agent: ", err, "; Bad response: ", badResponse)
   107  		return nil
   108  	}
   109  
   110  	respBytes, err := ioutil.ReadAll(res.Body)
   111  
   112  	if err != nil {
   113  		a.l.Debug("Error reading res.Body while attempting to get response data from the agent: ", err.Error())
   114  		return nil
   115  	}
   116  
   117  	err = json.Unmarshal(respBytes, &resp)
   118  
   119  	if err != nil {
   120  		a.l.Debug("Error unmarshaling body while attempting to get response data from the agent: ", err.Error())
   121  		return nil
   122  	}
   123  
   124  	return &resp
   125  }
   126  
   127  // pingAgent send a HEAD request to the agent and returns true if it receives a response from it
   128  func (a *agentCommunicator) pingAgent() bool {
   129  	u := a.buildURL(agentDataURL)
   130  	req, err := http.NewRequest(http.MethodHead, u, nil)
   131  
   132  	if err != nil {
   133  		a.l.Debug("Error preparing request while attempting to ping the agent: ", err.Error())
   134  		return false
   135  	}
   136  
   137  	resp, err := a.client.Do(req)
   138  
   139  	if err != nil || resp == nil {
   140  		a.l.Debug("Error pinging the agent: ", err.Error(), ", response: ", resp)
   141  		return false
   142  	}
   143  
   144  	defer func() {
   145  		io.CopyN(ioutil.Discard, resp.Body, 256<<10)
   146  		resp.Body.Close()
   147  	}()
   148  
   149  	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
   150  		a.l.Debug("Agent ping failed, response: ", resp.StatusCode, " with message ", resp.Status, "; URL: ", u)
   151  		return false
   152  	}
   153  
   154  	a.l.Debug("Agent ping ok!")
   155  
   156  	return true
   157  }
   158  
   159  // sendDataToAgent makes a POST to the agent sending some data as payload. eg: spans, events or metrics
   160  func (a *agentCommunicator) sendDataToAgent(suffix string, data interface{}) error {
   161  	url := a.buildURL(suffix)
   162  	ctx, cancel := context.WithTimeout(context.Background(), clientTimeout)
   163  	defer cancel()
   164  
   165  	var r *bytes.Buffer
   166  
   167  	if data != nil {
   168  		b, err := json.Marshal(data)
   169  
   170  		if err != nil {
   171  			a.l.Debug("Sending data to agent marshaling failed: ", err.Error())
   172  			return err
   173  		}
   174  
   175  		r = bytes.NewBuffer(b)
   176  
   177  		if r.Len() > maxContentLength {
   178  			return payloadTooLargeErr
   179  		}
   180  	}
   181  
   182  	req, err := http.NewRequest(http.MethodPost, url, r)
   183  
   184  	if err != nil {
   185  		a.l.Debug("Sending data to agent request creation failed: ", err.Error())
   186  		return err
   187  	}
   188  
   189  	req = req.WithContext(ctx)
   190  
   191  	req.Header.Set("Content-Type", "application/json")
   192  
   193  	resp, err := a.client.Do(req)
   194  
   195  	if resp == nil {
   196  		a.l.Debug("Sending data to agent: response nil for URL ", url)
   197  	}
   198  
   199  	if resp != nil {
   200  		respCode := resp.StatusCode
   201  		if respCode < 200 || respCode >= 300 {
   202  			a.l.Debug("Sending data to agent: response code: ", resp.StatusCode, "-", resp.Status, "; ", url)
   203  		}
   204  
   205  		io.CopyN(ioutil.Discard, resp.Body, 256<<10)
   206  		resp.Body.Close()
   207  	}
   208  
   209  	if err != nil {
   210  		a.l.Debug("Sending data to agent request failed: ", err.Error())
   211  	}
   212  
   213  	return err
   214  }
   215  
   216  func newAgentCommunicator(host, port string, from *fromS, logger LeveledLogger) *agentCommunicator {
   217  	return &agentCommunicator{
   218  		host: host,
   219  		port: port,
   220  		from: from,
   221  		client: &http.Client{
   222  			Timeout: announceTimeout,
   223  		},
   224  		l: logger,
   225  	}
   226  }