Skip to content

Commit 40b601e

Browse files
[AWS ELB] Use correct region when fetching elb price locally (#915)
Co-authored-by: Stephan Rayner <stephan.rayner@grafana.com>
1 parent f64b1ba commit 40b601e

4 files changed

Lines changed: 81 additions & 9 deletions

File tree

pkg/aws/aws.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,8 +208,18 @@ func newWithDependencies(ctx context.Context, config *Config, awsClient client.C
208208
}
209209
collectors = append(collectors, collector)
210210
case serviceELB:
211+
// pricing API for ELB client needs to use always the same region
212+
// as the pricing data is only available in us-east-1
213+
elbPricingConfig, err := createAWSConfig(ctx, "us-east-1", config.Profile, config.RoleARN)
214+
if err != nil {
215+
return nil, err
216+
}
217+
awsELBPricingClient := client.NewAWSClient(client.Config{
218+
PricingService: awsPricing.NewFromConfig(elbPricingConfig),
219+
})
211220
collector := elb.New(&elb.Config{
212221
Regions: regions,
222+
PricingClient: awsELBPricingClient,
213223
RegionClients: regionClients,
214224
ScrapeInterval: config.ScrapeInterval,
215225
Logger: logger,

pkg/aws/elb/elb.go

Lines changed: 14 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ import (
1919
)
2020

2121
const (
22-
subsystem = "aws_elb"
22+
serviceName = "elb"
23+
subsystem = "aws_" + serviceName
2324
)
2425

2526
var (
@@ -43,6 +44,7 @@ type Collector struct {
4344
regions []ec2Types.Region
4445
ScrapeInterval time.Duration
4546
pricingMap *ELBPricingMap
47+
pricingClient client.Client
4648
awsRegionClientMap map[string]client.Client
4749
NextScrape time.Time
4850
logger *slog.Logger
@@ -52,9 +54,12 @@ type Collector struct {
5254
type Config struct {
5355
ScrapeInterval time.Duration
5456
Regions []ec2Types.Region
55-
RegionClients map[string]client.Client
56-
Logger *slog.Logger
57-
AccountID string
57+
// PricingClient must be a client configured for us-east-1: the AWS Pricing API
58+
// is only available in us-east-1 and ap-south-1.
59+
PricingClient client.Client
60+
RegionClients map[string]client.Client
61+
Logger *slog.Logger
62+
AccountID string
5863
}
5964

6065
type LoadBalancerInfo struct {
@@ -83,12 +88,14 @@ type elbProduct struct {
8388
}
8489

8590
func New(config *Config) *Collector {
91+
logger := config.Logger.With("logger", serviceName)
8692
return &Collector{
8793
regions: config.Regions,
8894
ScrapeInterval: config.ScrapeInterval,
95+
pricingClient: config.PricingClient,
8996
awsRegionClientMap: config.RegionClients,
90-
logger: config.Logger,
91-
pricingMap: NewELBPricingMap(config.Logger),
97+
logger: logger,
98+
pricingMap: NewELBPricingMap(logger),
9299
accountID: config.AccountID,
93100
}
94101
}
@@ -107,7 +114,7 @@ func (c *Collector) Collect(ctx context.Context, ch chan<- prometheus.Metric) er
107114
c.logger.Info("Starting ELB collection")
108115

109116
if c.shouldScrape() {
110-
if err := c.pricingMap.refresh(ctx, c.awsRegionClientMap, c.regions); err != nil {
117+
if err := c.pricingMap.refresh(ctx, c.pricingClient, c.regions); err != nil {
111118
c.logger.Error("Failed to refresh pricing", "error", err)
112119
return err
113120
}

pkg/aws/elb/elb_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package elb
22

33
import (
4+
"errors"
45
"log/slog"
56
"testing"
67
"time"
@@ -24,6 +25,7 @@ func TestNew(t *testing.T) {
2425
Regions: []ec2Types.Region{
2526
{RegionName: stringPtr("us-east-1")},
2627
},
28+
PricingClient: mockClient,
2729
RegionClients: map[string]client.Client{
2830
"us-east-1": mockClient,
2931
},
@@ -36,6 +38,7 @@ func TestNew(t *testing.T) {
3638
assert.NotNil(t, collector)
3739
assert.Equal(t, config.ScrapeInterval, collector.ScrapeInterval)
3840
assert.Equal(t, config.Regions, collector.regions)
41+
assert.Equal(t, mockClient, collector.pricingClient)
3942
assert.Equal(t, mockClient, collector.awsRegionClientMap["us-east-1"])
4043
assert.NotNil(t, collector.pricingMap)
4144
}
@@ -130,6 +133,58 @@ func TestCollectRegionLoadBalancers(t *testing.T) {
130133

131134
}
132135

136+
func TestFetchRegionPricing(t *testing.T) {
137+
ctrl := gomock.NewController(t)
138+
mockClient := mock_client.NewMockClient(ctrl)
139+
140+
albProduct := `{"Product":{"Attributes":{"usageType":"USE1-LoadBalancerUsage","operation":"LoadBalancing:Application"}},"Terms":{"OnDemand":{"t1":{"PriceDimensions":{"d1":{"pricePerUnit":{"USD":"0.0225"}}}}}}}`
141+
nlbProduct := `{"Product":{"Attributes":{"usageType":"USE1-LCUUsage","operation":"LoadBalancing:Network"}},"Terms":{"OnDemand":{"t1":{"PriceDimensions":{"d1":{"pricePerUnit":{"USD":"0.006"}}}}}}}`
142+
mockClient.EXPECT().ListELBPrices(gomock.Any(), "us-east-1").Return([]string{albProduct, nlbProduct}, nil)
143+
144+
pm := NewELBPricingMap(slog.Default())
145+
pricing, err := pm.FetchRegionPricing(mockClient, t.Context(), "us-east-1")
146+
147+
assert.NoError(t, err)
148+
assert.Equal(t, 0.0225, pricing.ALBHourlyRate[LoadBalancerUsage])
149+
assert.Equal(t, 0.006, pricing.NLBHourlyRate[LCUUsage])
150+
}
151+
152+
func TestFetchRegionPricingError(t *testing.T) {
153+
ctrl := gomock.NewController(t)
154+
mockClient := mock_client.NewMockClient(ctrl)
155+
mockClient.EXPECT().ListELBPrices(gomock.Any(), "us-east-1").Return(nil, errors.New("api error"))
156+
157+
pm := NewELBPricingMap(slog.Default())
158+
pricing, err := pm.FetchRegionPricing(mockClient, t.Context(), "us-east-1")
159+
160+
assert.Error(t, err)
161+
assert.Nil(t, pricing)
162+
}
163+
164+
func TestRefresh(t *testing.T) {
165+
ctrl := gomock.NewController(t)
166+
mockClient := mock_client.NewMockClient(ctrl)
167+
168+
albProduct := `{"Product":{"Attributes":{"usageType":"USE1-LoadBalancerUsage","operation":"LoadBalancing:Application"}},"Terms":{"OnDemand":{"t1":{"PriceDimensions":{"d1":{"pricePerUnit":{"USD":"0.0225"}}}}}}}`
169+
mockClient.EXPECT().ListELBPrices(gomock.Any(), "us-east-1").Return([]string{albProduct}, nil)
170+
mockClient.EXPECT().ListELBPrices(gomock.Any(), "us-west-2").Return([]string{albProduct}, nil)
171+
172+
pm := NewELBPricingMap(slog.Default())
173+
regions := []ec2Types.Region{
174+
{RegionName: stringPtr("us-east-1")},
175+
{RegionName: stringPtr("us-west-2")},
176+
}
177+
178+
err := pm.refresh(t.Context(), mockClient, regions)
179+
assert.NoError(t, err)
180+
181+
for _, region := range []string{"us-east-1", "us-west-2"} {
182+
pricing, err := pm.GetRegionPricing(region)
183+
assert.NoError(t, err)
184+
assert.Equal(t, 0.0225, pricing.ALBHourlyRate[LoadBalancerUsage])
185+
}
186+
}
187+
133188
func stringPtr(s string) *string {
134189
return &s
135190
}

pkg/aws/elb/pricing_map.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ func (pm *ELBPricingMap) GetRegionPricing(region string) (*RegionPricing, error)
6464
return pricing, nil
6565
}
6666

67-
func (pm *ELBPricingMap) refresh(ctx context.Context, client map[string]client.Client, regions []ec2Types.Region) error {
67+
func (pm *ELBPricingMap) refresh(ctx context.Context, pricingClient client.Client, regions []ec2Types.Region) error {
6868
pm.logger.Info("Refreshing ELB pricing data")
6969

7070
eg := errgroup.Group{}
@@ -73,7 +73,7 @@ func (pm *ELBPricingMap) refresh(ctx context.Context, client map[string]client.C
7373
for _, region := range regions {
7474
regionName := *region.RegionName
7575
eg.Go(func() error {
76-
pricing, err := pm.FetchRegionPricing(client[regionName], ctx, regionName)
76+
pricing, err := pm.FetchRegionPricing(pricingClient, ctx, regionName)
7777
if err != nil {
7878
return fmt.Errorf("failed to fetch pricing for region %s: %w", regionName, err)
7979
}

0 commit comments

Comments
 (0)