-
Notifications
You must be signed in to change notification settings - Fork 57
Closed
gophercloud/gophercloud
#3188Description
Issue Description
When using the Gophercloud utils API client to send batch metrics data to Gnocchi, the client erroneously returns an EOF error, even though the API successfully processes the request and returns a 202 Accepted response.
Expected result
After sending the POST request to /v1/batch/resources/metrics/measures?create_metrics=true
, a successful response (202 Accepted) from Gnocchi should be interpreted as a successful operation by the Gophercloud utils client.
Actual result
error=EOF
Code
The issue occurs in the following part of our codebase:
package run
import (
"context"
"encoding/json"
"os"
"io"
"time"
"fmt"
"net/http"
"net/http/httputil"
"github.com/<omitted>/<omitted>/utils"
"github.com/<omitted>/<omitted>/utils/logger"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/utils/gnocchi"
"github.com/gophercloud/utils/gnocchi/metric/v1/measures"
log "github.com/sirupsen/logrus"
)
type loggingRoundTripper struct {
inner http.RoundTripper
logger io.Writer
}
func init() {
http.DefaultTransport = wrapRoundTripper(http.DefaultTransport, os.Stdout)
}
func wrapRoundTripper(in http.RoundTripper, log io.Writer) http.RoundTripper {
return &loggingRoundTripper{inner: in, logger: log}
}
func (d *loggingRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
d.dumpRequest(req)
res, err := d.inner.RoundTrip(req)
d.dumpResponse(res)
return res, err
}
func (d *loggingRoundTripper) dumpRequest(r *http.Request) {
dump, err := httputil.DumpRequestOut(r, true)
if err != nil {
fmt.Fprintf(d.logger, "\n\tERROR dumping: %v\n", err)
}
d.dump("REQUEST", dump)
}
func (d *loggingRoundTripper) dumpResponse(r *http.Response) {
dump, err := httputil.DumpResponse(r, true)
if err != nil {
fmt.Fprintf(d.logger, "\n\tERROR dumping: %v\n", err)
}
d.dump("RESPONSE", dump)
}
func (d *loggingRoundTripper) dump(label string, dump []byte) {
fmt.Fprintf(d.logger, "\n------------------------------------------------------------\n")
defer fmt.Fprintf(d.logger, "\n------------------------------------------------------------\n")
fmt.Fprintf(d.logger, "%s:\n--\n%s", label, string(dump))
}
func sendToGnocchi(done <-chan bool, status chan<- bool, ctx context.Context) {
logger := logger.FromContext(ctx)
logger.Info("sendToGnocchi")
// Auth options
opts := gophercloud.AuthOptions{
IdentityEndpoint: os.Getenv("OS_AUTH_URL"),
Username: os.Getenv("OS_USERNAME"),
Password: os.Getenv("OS_PASSWORD"),
TenantName: os.Getenv("OS_PROJECT_NAME"),
DomainName: os.Getenv("OS_PROJECT_DOMAIN_NAME"),
}
// Authenticated provider
provider, err := openstack.AuthenticatedClient(opts)
if err != nil {
logger.WithError(err).Info("Failed to authenticate with openstack")
return
}
// Gnocchi client
gnocchiClient, err := gnocchi.NewGnocchiV1(provider, gophercloud.EndpointOpts{
Region: os.Getenv("OS_REGION_NAME"),
})
if err != nil {
logger.WithError(err).Info("Failed to create gnocchiClient")
return
}
metricIDRx := "network-traffic-received"
metricIDTx := "network-traffic-sent"
// Periodically send data
ticker := time.NewTicker(10 * time.Second)
defer ticker.Stop()
for range ticker.C {
select {
case <-ticker.C:
logger.Info("sendToGnocchi tick")
instanceTrafficData, err := getBytesData()
if err != nil {
logger.WithError(err).Error("Error getting bytes data")
continue
}
currentTime := time.Now().UTC()
for instanceID, trafficData := range instanceTrafficData {
var batchMetricsOpts []measures.BatchResourcesMetricsOpts
batchMetricsOpts = append(batchMetricsOpts, measures.BatchResourcesMetricsOpts{
ResourceID: instanceID,
ResourcesMetrics: []measures.ResourcesMetricsOpts{
{
MetricName: metricIDRx,
Measures: []measures.MeasureOpts{
{
Timestamp: ¤tTime,
Value: float64(trafficData.Received),
},
},
},
{
MetricName: metricIDTx,
Measures: []measures.MeasureOpts{
{
Timestamp: ¤tTime,
Value: float64(trafficData.Sent),
},
},
},
},
})
// Construct json body of request
jsonBody, err := json.Marshal(measures.BatchCreateResourcesMetricsOpts{
CreateMetrics: true, // Set to true or false based on your need
BatchResourcesMetrics: batchMetricsOpts,
})
if err != nil {
log.WithError(err).Error("Error marshalling json body")
} else {
log.Info("Sending data to Gnocchi: ", string(jsonBody))
}
// Batch create resources metrics
measureErr := measures.BatchCreateResourcesMetrics(gnocchiClient, measures.BatchCreateResourcesMetricsOpts{
CreateMetrics: true, // Set to true or false based on your need
BatchResourcesMetrics: batchMetricsOpts,
}).ExtractErr()
if measureErr != nil {
log.WithError(measureErr).Error("Error sending batch metrics data to Gnocchi.")
} else {
log.Info("Successfully sent batch metrics data to Gnocchi")
}
}
case <-done:
log.Info("Stopping sendToGnocchi goroutine")
status <- true
return
}
}
}
func getBytesData() (map[string]TrafficData, error) {
instanceTrafficData := make(map[string]TrafficData)
metrics, err := utils.TrafficGatherer.Gather()
if err != nil {
return nil, err
}
for _, family := range metrics {
for _, m := range family.GetMetric() {
labels := make(map[string]string)
for _, label := range m.GetLabel() {
labels[label.GetName()] = label.GetValue()
}
instanceID := labels["instance_id"]
trafficData := instanceTrafficData[instanceID] // Get the existing traffic data for the instance
if family.GetName() == "vm_tx_bytes" {
trafficData.Sent += int64(m.GetCounter().GetValue())
} else if family.GetName() == "vm_rx_bytes" {
trafficData.Received += int64(m.GetCounter().GetValue())
}
instanceTrafficData[instanceID] = trafficData // Update the map with the modified traffic data
}
}
return instanceTrafficData, nil
}
type TrafficData struct {
Received int64
Sent int64
}
Complete log output, with network request logging tools
Gophercloud version: v1.7.0 and github.com/gophercloud/utils v0.0.0-20231010081019-80377eca5d56
Go version: go version go1.21.5 linux/amd64
Gnocchi version: 4.3.7.dev2
------------------------------------------------------------
REQUEST:
--
POST /v1/batch/resources/metrics/measures?create_metrics=true HTTP/1.1
Host: <omitted>:8041
User-Agent: gophercloud/v1.7.0
Content-Length: 234
Accept: application/json, */*
Content-Type: application/json
X-Auth-Token: <omitted>
Accept-Encoding: gzip
{"<omitted instance ID>":{"network-traffic-received":{"measures":[{"timestamp":"2024-01-09T15:22:52.17807","value":0}]},"network-traffic-sent":{"measures":[{"timestamp":"2024-01-09T15:22:52.17807","value":153196032}]}}}
------------------------------------------------------------
------------------------------------------------------------
RESPONSE:
--
HTTP/1.1 202 Accepted
Connection: close
Content-Length: 0
Content-Type: application/json
------------------------------------------------------------
ERRO[0080] Error sending batch metrics data to Gnocchi. error=EOF
Metadata
Metadata
Assignees
Labels
No labels