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 }