Add price provider

This commit is contained in:
Jan Gräfen 2021-03-05 20:28:17 +01:00
parent 6056d7ba39
commit 816a6d02fc
10 changed files with 122 additions and 54 deletions

View file

@ -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),
}

View file

@ -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)

View file

@ -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 {

82
fetcher/prices.go Normal file
View file

@ -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
}

View file

@ -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 {

View file

@ -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()
}

View file

@ -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)

View file

@ -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)

View file

@ -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)

24
main.go
View file

@ -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(),