From 816a6d02fccbfeefe5b7edec947707bf5f409a24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20Gr=C3=A4fen?= Date: Fri, 5 Mar 2021 20:28:17 +0100 Subject: [PATCH] Add price provider --- fetcher/fetcher.go | 4 +- fetcher/floatingip.go | 10 ++--- fetcher/loadbalancer.go | 4 +- fetcher/prices.go | 82 +++++++++++++++++++++++++++++++++++++++ fetcher/server.go | 4 +- fetcher/server_backups.go | 18 ++++----- fetcher/server_traffic.go | 10 ++--- fetcher/snapshot.go | 10 ++--- fetcher/volume.go | 10 ++--- main.go | 24 +++++++----- 10 files changed, 122 insertions(+), 54 deletions(-) create mode 100644 fetcher/prices.go diff --git a/fetcher/fetcher.go b/fetcher/fetcher.go index 34eefda..2380d6f 100644 --- a/fetcher/fetcher.go +++ b/fetcher/fetcher.go @@ -23,6 +23,7 @@ type Fetcher interface { } type baseFetcher struct { + pricing *PriceProvider hourly *prometheus.GaugeVec monthly *prometheus.GaugeVec } @@ -35,7 +36,7 @@ func (fetcher baseFetcher) GetMonthly() prometheus.Collector { return fetcher.monthly } -func newBase(resource string, additionalLabels ...string) *baseFetcher { +func newBase(pricing *PriceProvider, resource string, additionalLabels ...string) *baseFetcher { labels := []string{"name"} labels = append(labels, additionalLabels...) @@ -53,6 +54,7 @@ func newBase(resource string, additionalLabels ...string) *baseFetcher { } return &baseFetcher{ + pricing: pricing, hourly: prometheus.NewGaugeVec(hourlyGaugeOpts, labels), monthly: prometheus.NewGaugeVec(monthlyGaugeOpts, labels), } diff --git a/fetcher/floatingip.go b/fetcher/floatingip.go index 9dd7b8c..4f2112c 100644 --- a/fetcher/floatingip.go +++ b/fetcher/floatingip.go @@ -4,15 +4,11 @@ import ( "github.com/hetznercloud/hcloud-go/hcloud" ) -const ( - floatingIPPrice = float64(1.19) -) - var _ Fetcher = &floatingIP{} // NewFloatingIP creates a new fetcher that will collect pricing information on floating IPs. -func NewFloatingIP() Fetcher { - return &floatingIP{newBase("floatingip", "location")} +func NewFloatingIP(pricing *PriceProvider) Fetcher { + return &floatingIP{newBase(pricing, "floatingip", "location")} } type floatingIP struct { @@ -28,7 +24,7 @@ func (floatingIP floatingIP) Run(client *hcloud.Client) error { for _, f := range floatingIPs { location := f.HomeLocation - monthlyPrice := floatingIPPrice + monthlyPrice := floatingIP.pricing.FloatingIP() hourlyPrice := pricingPerHour(monthlyPrice) floatingIP.hourly.WithLabelValues(f.Name, location.Name).Set(hourlyPrice) diff --git a/fetcher/loadbalancer.go b/fetcher/loadbalancer.go index 50cc27b..1b4b8de 100644 --- a/fetcher/loadbalancer.go +++ b/fetcher/loadbalancer.go @@ -9,8 +9,8 @@ import ( var _ Fetcher = &loadBalancer{} // NewLoadbalancer creates a new fetcher that will collect pricing information on load balancers. -func NewLoadbalancer() Fetcher { - return &loadBalancer{newBase("loadbalancer", "location", "type")} +func NewLoadbalancer(pricing *PriceProvider) Fetcher { + return &loadBalancer{newBase(pricing, "loadbalancer", "location", "type")} } type loadBalancer struct { diff --git a/fetcher/prices.go b/fetcher/prices.go new file mode 100644 index 0000000..0462ad8 --- /dev/null +++ b/fetcher/prices.go @@ -0,0 +1,82 @@ +package fetcher + +import ( + "context" + "strconv" + "sync" + + "github.com/hetznercloud/hcloud-go/hcloud" +) + +// PriceProvider provides easy access to current HCloud prices +type PriceProvider struct { + Client *hcloud.Client + pricing *hcloud.Pricing + pricingLock sync.RWMutex +} + +// FloatingIP returns the current price for a floating IP per month. +func (provider *PriceProvider) FloatingIP() float64 { + provider.pricingLock.RLock() + defer provider.pricingLock.RUnlock() + + return parsePrice(provider.getPricing().FloatingIP.Monthly.Gross) +} + +// Image returns the current price for an image per GB per month. +func (provider *PriceProvider) Image() float64 { + provider.pricingLock.RLock() + defer provider.pricingLock.RUnlock() + + return parsePrice(provider.getPricing().Image.PerGBMonth.Gross) +} + +// Traffic returns the current price for a TB of extra traffic per month. +func (provider *PriceProvider) Traffic() float64 { + provider.pricingLock.RLock() + defer provider.pricingLock.RUnlock() + + return parsePrice(provider.getPricing().Traffic.PerTB.Gross) +} + +// ServerBackup returns the percentage of base price increase for server backups per month. +func (provider *PriceProvider) ServerBackup() float64 { + provider.pricingLock.RLock() + defer provider.pricingLock.RUnlock() + + return parsePrice(provider.getPricing().ServerBackup.Percentage) +} + +// Volume returns the current price for a volume per GB per month. +func (provider *PriceProvider) Volume() float64 { + return 0.0476 +} + +// Sync forces the provider to re-fetch prices from the HCloud API. +func (provider *PriceProvider) Sync() { + provider.pricingLock.Lock() + defer provider.pricingLock.Unlock() + + provider.pricing = nil +} + +func (provider *PriceProvider) getPricing() *hcloud.Pricing { + if provider.pricing == nil { + pricing, _, err := provider.Client.Pricing.Get(context.Background()) + if err != nil { + panic(err) + } + + provider.pricing = &pricing + } + + return provider.pricing +} + +func parsePrice(rawPrice string) float64 { + if price, err := strconv.ParseFloat(rawPrice, 32); err == nil { + return price + } + + return 0 +} diff --git a/fetcher/server.go b/fetcher/server.go index 6706d19..2e76338 100644 --- a/fetcher/server.go +++ b/fetcher/server.go @@ -9,8 +9,8 @@ import ( var _ Fetcher = &server{} // NewServer creates a new fetcher that will collect pricing information on servers. -func NewServer() Fetcher { - return &server{newBase("server", "location")} +func NewServer(pricing *PriceProvider) Fetcher { + return &server{newBase(pricing, "server", "location")} } type server struct { diff --git a/fetcher/server_backups.go b/fetcher/server_backups.go index 03e582e..04d104e 100644 --- a/fetcher/server_backups.go +++ b/fetcher/server_backups.go @@ -6,15 +6,11 @@ import ( "github.com/hetznercloud/hcloud-go/hcloud" ) -const ( - backupPriceMultiplier = 0.2 -) - var _ Fetcher = &server{} // NewServerBackup creates a new fetcher that will collect pricing information on server backups. -func NewServerBackup() Fetcher { - return &serverBackup{newBase("server_backup", "location", "type")} +func NewServerBackup(pricing *PriceProvider) Fetcher { + return &serverBackup{newBase(pricing, "server_backup", "location", "type")} } type serverBackup struct { @@ -36,8 +32,8 @@ func (serverBackup serverBackup) Run(client *hcloud.Client) error { return err } - hourlyPrice := toBackupPrice(serverPrice.Hourly.Gross) - monthlyPrice := toBackupPrice(serverPrice.Monthly.Gross) + hourlyPrice := serverBackup.toBackupPrice(serverPrice.Hourly.Gross) + monthlyPrice := serverBackup.toBackupPrice(serverPrice.Monthly.Gross) serverBackup.hourly.WithLabelValues(s.Name, location.Name, s.ServerType.Name).Set(hourlyPrice) serverBackup.monthly.WithLabelValues(s.Name, location.Name, s.ServerType.Name).Set(monthlyPrice) @@ -47,11 +43,11 @@ func (serverBackup serverBackup) Run(client *hcloud.Client) error { return nil } -func toBackupPrice(rawServerPrice string) float64 { - serverPrice, err := strconv.Atoi(rawServerPrice) +func (serverBackup serverBackup) toBackupPrice(rawServerPrice string) float64 { + serverPrice, err := strconv.ParseFloat(rawServerPrice, 32) if err != nil { return 0 } - return float64(serverPrice) * backupPriceMultiplier + return serverPrice * serverBackup.pricing.ServerBackup() } diff --git a/fetcher/server_traffic.go b/fetcher/server_traffic.go index 2600f3b..1ef003a 100644 --- a/fetcher/server_traffic.go +++ b/fetcher/server_traffic.go @@ -6,15 +6,11 @@ import ( "github.com/hetznercloud/hcloud-go/hcloud" ) -const ( - trafficPrice = 1.19 -) - var _ Fetcher = &serverTraffic{} // NewServerTraffic creates a new fetcher that will collect pricing information on server traffic. -func NewServerTraffic() Fetcher { - return &serverTraffic{newBase("server_traffic", "location", "type")} +func NewServerTraffic(pricing *PriceProvider) Fetcher { + return &serverTraffic{newBase(pricing, "server_traffic", "location", "type")} } type serverTraffic struct { @@ -37,7 +33,7 @@ func (serverTraffic serverTraffic) Run(client *hcloud.Client) error { break } - monthlyPrice := math.Ceil(float64(additionalTraffic)/sizeTB) * trafficPrice + monthlyPrice := math.Ceil(float64(additionalTraffic)/sizeTB) * serverTraffic.pricing.Traffic() hourlyPrice := pricingPerHour(monthlyPrice) serverTraffic.hourly.WithLabelValues(s.Name, location.Name, s.ServerType.Name).Set(hourlyPrice) diff --git a/fetcher/snapshot.go b/fetcher/snapshot.go index 5e09e87..edddcb9 100644 --- a/fetcher/snapshot.go +++ b/fetcher/snapshot.go @@ -6,15 +6,11 @@ import ( "github.com/hetznercloud/hcloud-go/hcloud" ) -const ( - imagePrice = 0.0119 -) - var _ Fetcher = &snapshot{} // NewSnapshot creates a new fetcher that will collect pricing information on server snapshots. -func NewSnapshot() Fetcher { - return &snapshot{newBase("snapshot")} +func NewSnapshot(pricing *PriceProvider) Fetcher { + return &snapshot{newBase(pricing, "snapshot")} } type snapshot struct { @@ -29,7 +25,7 @@ func (snapshot snapshot) Run(client *hcloud.Client) error { for _, i := range images { if i.Type == "snapshot" { - monthlyPrice := math.Ceil(float64(i.ImageSize)/sizeGB) * imagePrice + monthlyPrice := math.Ceil(float64(i.ImageSize)/sizeGB) * snapshot.pricing.Image() hourlyPrice := pricingPerHour(monthlyPrice) snapshot.hourly.WithLabelValues(i.Name).Set(hourlyPrice) diff --git a/fetcher/volume.go b/fetcher/volume.go index bf217dc..4c6311f 100644 --- a/fetcher/volume.go +++ b/fetcher/volume.go @@ -7,15 +7,11 @@ import ( "github.com/hetznercloud/hcloud-go/hcloud" ) -const ( - volumePrice = float64(0.0476) -) - var _ Fetcher = &volume{} // NewVolume creates a new fetcher that will collect pricing information on volumes. -func NewVolume() Fetcher { - return &server{newBase("volume", "location", "bytes")} +func NewVolume(pricing *PriceProvider) Fetcher { + return &server{newBase(pricing, "volume", "location", "bytes")} } type volume struct { @@ -29,7 +25,7 @@ func (volume volume) Run(client *hcloud.Client) error { } for _, v := range volumes { - monthlyPrice := math.Ceil(float64(v.Size/sizeGB)) * volumePrice + monthlyPrice := math.Ceil(float64(v.Size/sizeGB)) * volume.pricing.Volume() hourlyPrice := pricingPerHour(monthlyPrice) volume.hourly.WithLabelValues(v.Name, v.Location.Name, strconv.Itoa(v.Size)).Set(hourlyPrice) diff --git a/main.go b/main.go index 7bca801..c285bf3 100644 --- a/main.go +++ b/main.go @@ -19,16 +19,6 @@ const ( defaultFetchInterval = 1 * time.Minute ) -var ( - floatingIP = fetcher.NewFloatingIP() - loadBalancer = fetcher.NewLoadbalancer() - server = fetcher.NewServer() - serverBackup = fetcher.NewServerBackup() - serverTraffic = fetcher.NewServerTraffic() - snapshot = fetcher.NewSnapshot() - volume = fetcher.NewVolume() -) - func toScheduler(client *hcloud.Client, f func(*hcloud.Client) error) func() { return func() { if err := f(client); err != nil { @@ -59,6 +49,18 @@ func main() { } client := hcloud.NewClient(hcloud.WithToken(hcloudAPIToken)) + priceRepository := &fetcher.PriceProvider{Client: client} + + var ( + floatingIP = fetcher.NewFloatingIP(priceRepository) + loadBalancer = fetcher.NewLoadbalancer(priceRepository) + server = fetcher.NewServer(priceRepository) + serverBackup = fetcher.NewServerBackup(priceRepository) + serverTraffic = fetcher.NewServerTraffic(priceRepository) + snapshot = fetcher.NewSnapshot(priceRepository) + volume = fetcher.NewVolume(priceRepository) + ) + scheduler.RunTaskAtInterval(toScheduler(client, floatingIP.Run), fetchInterval, 0) scheduler.RunTaskAtInterval(toScheduler(client, loadBalancer.Run), fetchInterval, 0) scheduler.RunTaskAtInterval(toScheduler(client, server.Run), fetchInterval, 0) @@ -67,6 +69,8 @@ func main() { scheduler.RunTaskAtInterval(toScheduler(client, snapshot.Run), fetchInterval, 0) scheduler.RunTaskAtInterval(toScheduler(client, volume.Run), fetchInterval, 0) + scheduler.RunTaskAtInterval(priceRepository.Sync, 10*fetchInterval, 10*fetchInterval) + registry := prometheus.NewRegistry() registry.MustRegister( floatingIP.GetHourly(),