Skip to content

Commit 997b2c8

Browse files
authored
Merge pull request #233 from cloudability/Region-URL-Support
Add CLOUDABILITY_UPLOAD_REGION Env Variable
2 parents 8b117f3 + 7e20430 commit 997b2c8

File tree

9 files changed

+79
-162
lines changed

9 files changed

+79
-162
lines changed

README.md

+2-1
Original file line numberDiff line numberDiff line change
@@ -58,9 +58,10 @@ Cloudability Metrics Agent currently does not support Rancher or On Prem cluster
5858
| CLOUDABILITY_LOG_LEVEL | Optional: Log level to run the agent at (INFO,WARN,DEBUG,TRACE). Default: `INFO` |
5959
| CLOUDABILITY_SCRATCH_DIR | Optional: Temporary directory that metrics will be written to. If set, must assure that the directory exists and that the user agent UID 1000 has read/write access to the folder. Default: `/tmp` |
6060
| CLOUDABILITY_NUMBER_OF_CONCURRENT_NODE_POLLERS | Optional: Number of goroutines that are created to poll node metrics in parallel. Default: `100` |
61-
| CLOUDABILITY_INFORMER_RESYNC_INTERVAL | Optional: Period of time (in hours) that the informers will fully resync the list of running resources. Default: 24 hours. Can be set to 0 to never resync |
61+
| CLOUDABILITY_INFORMER_RESYNC_INTERVAL | Optional: Period of time (in hours) that the informers will fully resync the list of running resources. Default: 24 hours. Can be set to 0 to never resync |
6262
| CLOUDABILITY_PARSE_METRIC_DATA | Optional: When true, core files will be parsed and non-relevant data will be removed prior to upload. Default: `false` |
6363
| CLOUDABILITY_HTTPS_CLIENT_TIMEOUT | Optional: Amount (in seconds) of time the http client has before timing out requests. Might need to be increased to clusters with large payloads. Default: `60` |
64+
| CLOUDABILITY_UPLOAD_REGION | Optional: The region the metrics-agent will upload data to. Default `us-west-2`. Supported values: `us-west-2` |
6465

6566
```sh
6667

charts/metrics-agent/Chart.yaml

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ type: application
1414

1515
# This is the chart version. This version number should be incremented each time you make changes
1616
# to the chart and its templates, including the app version.
17-
version: 2.11.15
17+
version: 2.11.16
1818

1919
# This is the version number of the application being deployed. This version number should be
2020
# incremented each time you make changes to the application.
21-
appVersion: 2.11.15
21+
appVersion: 2.11.16

charts/metrics-agent/values.yaml

+1-1
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pollInterval: 180
2424

2525
image:
2626
name: cloudability/metrics-agent
27-
tag: 2.11.15
27+
tag: 2.11.16
2828
pullPolicy: Always
2929

3030
imagePullSecrets: []

client/client.go

+20-52
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,8 @@ import (
3030

3131
//nolint gosec
3232

33-
const defaultBaseURL = "https://metrics-collector.cloudability.com"
33+
const DefaultBaseURL string = "https://metrics-collector.cloudability.com/metricsample"
34+
const EUBaseURL string = "https://metrics-collector-eu.cloudability.com/metricsample"
3435
const defaultTimeout = 1 * time.Minute
3536
const defaultMaxRetries = 5
3637

@@ -56,6 +57,7 @@ type Configuration struct {
5657
ProxyAuth string
5758
ProxyInsecure bool
5859
Verbose bool
60+
Region string
5961
}
6062

6163
// NewHTTPMetricClient will configure a new instance of a Cloudability client.
@@ -74,9 +76,9 @@ func NewHTTPMetricClient(cfg Configuration) (MetricClient, error) {
7476
}
7577
if len(strings.TrimSpace(cfg.BaseURL)) == 0 {
7678
if cfg.Verbose {
77-
log.Infof("Using default baseURL of %v", defaultBaseURL)
79+
log.Infof("Using default baseURL of %v", DefaultBaseURL)
7880
}
79-
cfg.BaseURL = defaultBaseURL
81+
cfg.BaseURL = GetUploadURLByRegion(cfg.Region)
8082
}
8183
if cfg.MaxRetries <= 0 {
8284
if cfg.Verbose {
@@ -131,7 +133,6 @@ func NewHTTPMetricClient(cfg Configuration) (MetricClient, error) {
131133

132134
// MetricClient represents a interface to send a cloudability measurement or metrics sample to an endpoint.
133135
type MetricClient interface {
134-
SendMeasurement(measurements []measurement.Measurement) error
135136
SendMetricSample(*os.File, string, string) error
136137
GetUploadURL(*os.File, string, string, string, int) (string, string, error)
137138
}
@@ -150,56 +151,9 @@ type MetricSampleResponse struct {
150151
Location string `json:"location"`
151152
}
152153

153-
func (c httpMetricClient) SendMeasurement(measurements []measurement.Measurement) error {
154-
155-
measurementURL := c.baseURL + "/metrics"
156-
157-
b, err := toJSONLines(measurements)
158-
if err != nil {
159-
return err
160-
}
161-
162-
req, err := http.NewRequest(http.MethodPost, measurementURL, bytes.NewBuffer(b))
163-
if err != nil {
164-
return err
165-
}
166-
167-
req.Header.Set("Content-Type", "application/json")
168-
req.Header.Set(authHeader, c.token)
169-
req.Header.Set(apiKeyHeader, c.token)
170-
req.Header.Set(userAgentHeader, c.userAgent)
171-
172-
if c.verbose {
173-
requestDump, err := httputil.DumpRequest(req, true)
174-
if err != nil {
175-
log.Errorln(err)
176-
}
177-
log.Infoln(string(requestDump))
178-
}
179-
180-
resp, err := c.httpClient.Do(req)
181-
if err != nil {
182-
return err
183-
}
184-
185-
if resp.StatusCode != http.StatusOK {
186-
return fmt.Errorf("Request received %v response", resp.StatusCode)
187-
}
188-
189-
if c.verbose {
190-
responseDump, err := httputil.DumpResponse(resp, true)
191-
if err != nil {
192-
log.Errorln(err)
193-
}
194-
log.Infoln(string(responseDump))
195-
}
196-
197-
return nil
198-
}
199-
200154
// SendMetricSample uploads a file at a given path to the metrics endpoint.
201155
func (c httpMetricClient) SendMetricSample(metricSampleFile *os.File, agentVersion string, UID string) (rerr error) {
202-
metricSampleURL := c.baseURL + "/metricsample"
156+
metricSampleURL := c.baseURL
203157

204158
resp, err := c.retryWithBackoff(metricSampleURL, metricSampleFile, agentVersion, UID)
205159
if err != nil {
@@ -452,3 +406,17 @@ func GetB64MD5Hash(name string) (b64Hash string, rerr error) {
452406

453407
return base64.StdEncoding.EncodeToString(h.Sum(nil)), err
454408
}
409+
410+
// GetUploadURLByRegion returns the correct base url depending on the env variable CLOUDABILITY_UPLOAD_REGION.
411+
// If value is not supported, default to us-west-2 (original) URL
412+
func GetUploadURLByRegion(region string) string {
413+
switch region {
414+
case "eu-central-1":
415+
return EUBaseURL
416+
case "us-west-2":
417+
return DefaultBaseURL
418+
default:
419+
log.Warnf("Region %s is not supported. Defaulting to us-west-2 region.", region)
420+
return DefaultBaseURL
421+
}
422+
}

client/client_test.go

+31-94
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import (
55
"bytes"
66
"compress/gzip"
77
"encoding/json"
8-
"io"
98
"math/rand"
109
"net"
1110
"net/http"
@@ -18,9 +17,10 @@ import (
1817
"github.com/cloudability/metrics-agent/client"
1918
"github.com/cloudability/metrics-agent/measurement"
2019
"github.com/cloudability/metrics-agent/test"
21-
"github.com/cloudability/metrics-agent/version"
2220
)
2321

22+
const metricsSuffix string = "/metricsample"
23+
2424
// nolint: dupl
2525
func TestClientCreation(t *testing.T) {
2626
t.Parallel()
@@ -31,102 +31,14 @@ func TestClientCreation(t *testing.T) {
3131
Token: test.SecureRandomAlphaString(20),
3232
BaseURL: "https://cloudability.com",
3333
MaxRetries: 2,
34+
Region: "us-west-2",
3435
})
3536
if c == nil || err != nil {
3637
t.Error("Expected client to successfully create")
3738
}
3839
})
3940
}
4041

41-
func TestSendMeasurement(t *testing.T) {
42-
t.Parallel()
43-
44-
token := test.SecureRandomAlphaString(20)
45-
46-
tags := make(map[string]string)
47-
tags["host"] = "macbookpro.Local.abc123"
48-
tags["cat"] = "dog"
49-
50-
measure := measurement.Measurement{
51-
Name: "ametric",
52-
Value: 1243.00,
53-
Timestamp: time.Now().Unix(),
54-
Tags: tags,
55-
}
56-
m := []measurement.Measurement{measure}
57-
jsonBytes, _ := client.ToJSONLines(m)
58-
59-
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
60-
if r.Method != http.MethodPost {
61-
t.Error("Expected to be a POST")
62-
}
63-
if !strings.Contains(r.UserAgent(), version.VERSION) {
64-
t.Error("Expected version to be present in user agent")
65-
}
66-
if r.Header.Get(client.AuthHeader) != token {
67-
t.Error("Expected token to be set on request")
68-
}
69-
if r.Header.Get(client.APIKeyHeader) != token {
70-
t.Error("Expected token to be set on request")
71-
}
72-
bodyBytes, _ := io.ReadAll(r.Body)
73-
if !bytes.Equal(jsonBytes, bodyBytes) {
74-
t.Errorf("Did not receive expect json. Got %v, expected %v", string(bodyBytes), string(jsonBytes))
75-
}
76-
w.WriteHeader(200)
77-
}))
78-
defer ts.Close()
79-
80-
c, err := client.NewHTTPMetricClient(client.Configuration{
81-
Timeout: 10 * time.Second,
82-
Token: token,
83-
BaseURL: ts.URL,
84-
MaxRetries: 2,
85-
})
86-
if err != nil {
87-
t.Error(err)
88-
}
89-
90-
err = c.SendMeasurement(m)
91-
if err != nil {
92-
t.Error(err)
93-
}
94-
}
95-
96-
func TestSendMeasurement_ErrorState(t *testing.T) {
97-
t.Parallel()
98-
99-
t.Run("404 Not Found", func(t *testing.T) {
100-
t.Parallel()
101-
102-
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
103-
w.WriteHeader(404)
104-
}))
105-
//nolint errcheck
106-
defer ts.Close()
107-
108-
c, err := client.NewHTTPMetricClient(client.Configuration{
109-
Timeout: 10 * time.Second,
110-
Token: test.SecureRandomAlphaString(20),
111-
BaseURL: ts.URL,
112-
MaxRetries: 2,
113-
})
114-
if err != nil {
115-
t.Error(err)
116-
}
117-
m := []measurement.Measurement{{}}
118-
119-
err = c.SendMeasurement(m)
120-
if err == nil {
121-
t.Errorf("Expected to receive an error")
122-
}
123-
if !strings.Contains(err.Error(), "404") {
124-
t.Logf("Error message: %v", err.Error())
125-
t.Errorf("Expected 404 status code in response")
126-
}
127-
})
128-
}
129-
13042
func TestToJSONLines(t *testing.T) {
13143
t.Parallel()
13244

@@ -256,7 +168,8 @@ func TestSendMetricSample(t *testing.T) {
256168
Timeout: 10 * time.Second,
257169
Token: token,
258170
MaxRetries: 2,
259-
BaseURL: ts.URL,
171+
BaseURL: ts.URL + metricsSuffix,
172+
Region: "us-west-2",
260173
})
261174
if err != nil {
262175
t.Error(err)
@@ -298,6 +211,7 @@ func TestSendMetricSample_ErrorState(t *testing.T) {
298211
Token: test.SecureRandomAlphaString(20),
299212
BaseURL: ts.URL,
300213
MaxRetries: 2,
214+
Region: "us-west-2",
301215
})
302216
if err != nil {
303217
t.Error(err)
@@ -344,8 +258,9 @@ func TestSendMetricSample_ErrorState(t *testing.T) {
344258
c, err := client.NewHTTPMetricClient(client.Configuration{
345259
Timeout: 10 * time.Second,
346260
Token: test.SecureRandomAlphaString(20),
347-
BaseURL: ts.URL,
261+
BaseURL: ts.URL + metricsSuffix,
348262
MaxRetries: 2,
263+
Region: "us-west-2",
349264
})
350265
if err != nil {
351266
t.Error(err)
@@ -399,6 +314,7 @@ func TestSendMetricSample_ErrorState(t *testing.T) {
399314
Token: test.SecureRandomAlphaString(20),
400315
MaxRetries: 2,
401316
BaseURL: ts.URL,
317+
Region: "us-west-2",
402318
})
403319
if err != nil {
404320
t.Error(err)
@@ -449,6 +365,7 @@ func TestSendMetricSample_ErrorState(t *testing.T) {
449365
Token: test.SecureRandomAlphaString(20),
450366
MaxRetries: 2,
451367
BaseURL: ts.URL,
368+
Region: "us-west-2",
452369
})
453370
if err != nil {
454371
t.Error(err)
@@ -500,8 +417,9 @@ func TestSendMetricSample_ErrorState(t *testing.T) {
500417
c, err := client.NewHTTPMetricClient(client.Configuration{
501418
Timeout: 10 * time.Second,
502419
Token: test.SecureRandomAlphaString(20),
503-
BaseURL: ts.URL,
420+
BaseURL: ts.URL + metricsSuffix,
504421
MaxRetries: 2,
422+
Region: "us-west-2",
505423
})
506424
if err != nil {
507425
t.Error(err)
@@ -584,6 +502,7 @@ func TestGetUploadURL(t *testing.T) {
584502
Token: token,
585503
MaxRetries: 2,
586504
BaseURL: ts.URL,
505+
Region: "us-west-2",
587506
})
588507
if err != nil {
589508
t.Error(err)
@@ -614,3 +533,21 @@ func Test_getB64MD5Hash(t *testing.T) {
614533
}
615534

616535
}
536+
537+
func Test_getUploadURLByRegion(t *testing.T) {
538+
// us-west-2 url generation
539+
uploadURL := client.GetUploadURLByRegion("us-west-2")
540+
if uploadURL != client.DefaultBaseURL {
541+
t.Error("US URL was not generated correctly")
542+
}
543+
// eu-central-1 url generation
544+
uploadURL = client.GetUploadURLByRegion("eu-central-1")
545+
if uploadURL != client.EUBaseURL {
546+
t.Error("EU URL was not generated correctly")
547+
}
548+
// unsupported region should default to us-west-2 url generation
549+
uploadURL = client.GetUploadURLByRegion("my-unsupported-region-1")
550+
if uploadURL != client.DefaultBaseURL {
551+
t.Error("Unsupported region default to US URL was not generated correctly")
552+
}
553+
}

cmd/kubernetesCmd.go

+8
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,12 @@ func init() {
132132
60,
133133
"Amount (in seconds) of time the https client has before timing out requests. Default 60",
134134
)
135+
kubernetesCmd.PersistentFlags().StringVar(
136+
&config.UploadRegion,
137+
"upload_region",
138+
"us-west-2",
139+
"The region the metrics-agent will upload data to. Default us-west-2",
140+
)
135141

136142
//nolint gas
137143
_ = viper.BindPFlag("api_key", kubernetesCmd.PersistentFlags().Lookup("api_key"))
@@ -156,6 +162,7 @@ func init() {
156162
kubernetesCmd.PersistentFlags().Lookup("number_of_concurrent_node_pollers"))
157163
_ = viper.BindPFlag("parse_metric_data", kubernetesCmd.PersistentFlags().Lookup("parse_metric_data"))
158164
_ = viper.BindPFlag("https_client_timeout", kubernetesCmd.PersistentFlags().Lookup("https_client_timeout"))
165+
_ = viper.BindPFlag("upload_region", kubernetesCmd.PersistentFlags().Lookup("upload_region"))
159166

160167
viper.SetEnvPrefix("cloudability")
161168
viper.AutomaticEnv()
@@ -180,6 +187,7 @@ func init() {
180187
InformerResyncInterval: viper.GetInt("informer_resync_interval"),
181188
ParseMetricData: viper.GetBool("parse_metric_data"),
182189
HTTPSTimeout: viper.GetInt("https_client_timeout"),
190+
UploadRegion: viper.GetString("upload_region"),
183191
}
184192

185193
}

0 commit comments

Comments
 (0)