Skip to content

Commit

Permalink
merge
Browse files Browse the repository at this point in the history
  • Loading branch information
yeszhanov95 committed Jul 28, 2022
1 parent ee8ecb3 commit ad9b566
Show file tree
Hide file tree
Showing 8 changed files with 875 additions and 5 deletions.
16 changes: 16 additions & 0 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import (
"github.com/mysteriumnetwork/discovery/location"
"github.com/mysteriumnetwork/discovery/price"
"github.com/mysteriumnetwork/discovery/price/pricing"
"github.com/mysteriumnetwork/discovery/price/pricingbyservice"
"github.com/mysteriumnetwork/discovery/proposal"
"github.com/mysteriumnetwork/discovery/quality"
"github.com/mysteriumnetwork/discovery/quality/oracleapi"
Expand Down Expand Up @@ -81,21 +82,36 @@ func main() {
locationProvider := location.NewLocationProvider(cfg.LocationAddress.String(), cfg.LocationUser, cfg.LocationPass)

v3 := r.Group("/api/v3")
v4 := r.Group("/api/v4")

proposal.NewAPI(proposalService, proposalRepo, locationProvider).RegisterRoutes(v3)
proposal.NewAPI(proposalService, proposalRepo, locationProvider).RegisterRoutes(v4)

health.NewAPI(rdb).RegisterRoutes(v3)
health.NewAPI(rdb).RegisterRoutes(v4)

cfger := pricing.NewConfigProviderDB(rdb)
_, err = cfger.Get()
if err != nil {
log.Fatal().Err(err).Msg("Failed to load cfg")
}
cfgerByService := pricingbyservice.NewConfigProviderDB(rdb)
_, err = cfger.Get()
if err != nil {
log.Fatal().Err(err).Msg("Failed to load cfg by service")
}

getter, err := pricing.NewPriceGetter(rdb)
if err != nil {
log.Fatal().Err(err).Msg("failed to initialize price getter")
}
getterByService, err := pricingbyservice.NewPriceGetter(rdb)
if err != nil {
log.Fatal().Err(err).Msg("failed to initialize price getter by service")
}

price.NewAPI(getter, cfger, cfg.UniverseJWTSecret).RegisterRoutes(v3)
price.NewAPIByService(getterByService, cfgerByService, cfg.UniverseJWTSecret).RegisterRoutes(v4)

brokerListener := listener.New(cfg.BrokerURL.String(), proposalRepo)

Expand Down
2 changes: 1 addition & 1 deletion metrics/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ var CurrentPriceByCountry = prometheus.NewGaugeVec(
Name: "discovery_current_price",
Help: "Current pricing by country",
},
[]string{"country_code", "node_type", "price_type"},
[]string{"country_code", "node_type", "service_type", "price_type"},
)

func InitialiseMonitoring() {
Expand Down
83 changes: 83 additions & 0 deletions price/api_by_service.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
package price

import (
"net/http"

"github.com/gin-gonic/gin"
"github.com/mysteriumnetwork/discovery/gorest"
"github.com/mysteriumnetwork/discovery/price/pricingbyservice"
"github.com/rs/zerolog/log"
)

type APIByService struct {
pricer *pricingbyservice.PriceGetter
jwtSecret string
cfger pricingbyservice.ConfigProvider
}

func NewAPIByService(pricer *pricingbyservice.PriceGetter, cfger pricingbyservice.ConfigProvider, jwtSecret string) *APIByService {
return &APIByService{
pricer: pricer,
cfger: cfger,
jwtSecret: jwtSecret,
}
}

// LatestPrices returns latest prices
// @Summary Latest Prices
// @Description Latest Prices
// @Product json
// @Success 200 {array} pricing.LatestPrices
// @Router /prices [get]
// @Tags prices
func (a *APIByService) LatestPrices(c *gin.Context) {
c.JSON(200, a.pricer.GetPrices())
}

// GetConfig returns the base pricing config
// @Summary Price config
// @Description price config
// @Product json
// @Success 200 {array} pricing.Config
// @Router /prices/config [get]
// @Tags prices
func (a *APIByService) GetConfig(c *gin.Context) {
cfg, err := a.cfger.Get()
if err != nil {
log.Err(err).Msg("Failed to get config")
c.JSON(http.StatusInternalServerError, gorest.NewErrResponse(err.Error()))
return
}
c.JSON(http.StatusOK, cfg)
}

// UpdateConfig updates the pricing config
// @Summary update price config
// @Description update price config
// @Product json
// @Success 202
// @Param config body pricing.Config true "config object"
// @Router /prices/config [post]
// @Tags prices
func (a *APIByService) UpdateConfig(c *gin.Context) {
var cfg pricingbyservice.Config
if err := c.BindJSON(&cfg); err != nil {
c.JSON(http.StatusBadRequest, gorest.NewErrResponse(err.Error()))
return
}

err := a.cfger.Update(cfg)
if err != nil {
log.Err(err).Msg("Failed to update config")
c.JSON(http.StatusBadRequest, gorest.NewErrResponse(err.Error()))
return
}

c.Data(http.StatusAccepted, gin.MIMEJSON, nil)
}

func (a *APIByService) RegisterRoutes(r gin.IRoutes) {
r.GET("/prices/config", JWTAuthorized(a.jwtSecret), a.GetConfig)
r.POST("/prices/config", JWTAuthorized(a.jwtSecret), a.UpdateConfig)
r.GET("/prices", a.LatestPrices)
}
12 changes: 8 additions & 4 deletions price/pricing/price_updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,11 @@ type PriceUpdater struct {
priceAPI FiatPriceAPI
priceLifetime time.Duration
mystBound Bound
<<<<<<< HEAD
db redis.UniversalClient
=======
db *redis.Client
>>>>>>> add support for different pricing

lock sync.Mutex
lp LatestPrices
Expand Down Expand Up @@ -130,10 +134,10 @@ func (p *PriceUpdater) submitMetrics() {
}

func (p *PriceUpdater) submitPriceMetric(country string, price *PriceByType) {
metrics.CurrentPriceByCountry.WithLabelValues(country, "other", "per_gib").Set(price.Other.PricePerGiBHumanReadable)
metrics.CurrentPriceByCountry.WithLabelValues(country, "other", "per_hour").Set(price.Other.PricePerHourHumanReadable)
metrics.CurrentPriceByCountry.WithLabelValues(country, "residential", "per_gib").Set(price.Residential.PricePerGiBHumanReadable)
metrics.CurrentPriceByCountry.WithLabelValues(country, "residential", "per_hour").Set(price.Residential.PricePerHourHumanReadable)
metrics.CurrentPriceByCountry.WithLabelValues(country, "other", "wireguard", "per_gib").Set(price.Other.PricePerGiBHumanReadable)
metrics.CurrentPriceByCountry.WithLabelValues(country, "other", "wireguard", "per_hour").Set(price.Other.PricePerHourHumanReadable)
metrics.CurrentPriceByCountry.WithLabelValues(country, "residential", "wireguard", "per_gib").Set(price.Residential.PricePerGiBHumanReadable)
metrics.CurrentPriceByCountry.WithLabelValues(country, "residential", "wireguard", "per_hour").Set(price.Residential.PricePerHourHumanReadable)
}

func (p *PriceUpdater) Stop() {
Expand Down
214 changes: 214 additions & 0 deletions price/pricingbyservice/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,214 @@
package pricingbyservice

import (
"context"
"encoding/json"
"errors"
"fmt"
"sync"
"time"

"github.com/go-redis/redis/v8"
"github.com/mysteriumnetwork/discovery/price/pricing"
"github.com/rs/zerolog/log"
)

const PricingConfigRedisKey = "DISCOVERY_PRICE_BASE_CONFIG_BY_SERVICE"

type ConfigProvider interface {
Get() (Config, error)
Update(Config) error
}

type ConfigProviderDB struct {
db *redis.Client
lock sync.Mutex
}

func NewConfigProviderDB(redis *redis.Client) *ConfigProviderDB {
return &ConfigProviderDB{
db: redis,
}
}

func (cpd *ConfigProviderDB) Get() (Config, error) {
cfg, err := cpd.fetchConfig()
if err != nil {
log.Err(err).Msg("could not fetch config")
return Config{}, errors.New("internal error")
}

return cfg, nil
}

func (cpd *ConfigProviderDB) Update(in Config) error {
cpd.lock.Lock()
defer cpd.lock.Unlock()

err := in.Validate()
if err != nil {
return err
}

cfgJSON, err := json.Marshal(in)
if err != nil {
return err
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()

err = cpd.db.Set(ctx, PricingConfigRedisKey, string(cfgJSON), 0).Err()
if err != nil {
return err
}

return nil
}

func (cpd *ConfigProviderDB) fetchConfig() (Config, error) {
ctx, cancel := context.WithTimeout(context.Background(), time.Second*5)
defer cancel()
val, err := cpd.db.Get(ctx, PricingConfigRedisKey).Result()
if err != nil {
if errors.Is(err, redis.Nil) {
err = cpd.db.Set(ctx, PricingConfigRedisKey, defaultPriceConfig, 0).Err()
if err != nil {
return Config{}, err
}
val = defaultPriceConfig
} else {
return Config{}, err
}
}

res := Config{}
return res, json.Unmarshal([]byte(val), &res)
}

type Config struct {
BasePrices PriceByTypeUSD `json:"base_prices"`
CountryModifiers map[pricing.ISO3166CountryCode]Modifier `json:"country_modifiers"`
}

func (c Config) Validate() error {
err := c.BasePrices.Validate()
if err != nil {
return fmt.Errorf("base price invalid: %w", err)
}

for k, v := range c.CountryModifiers {
err := k.Validate()
if err != nil {
return err
}

err = v.Validate()
if err != nil {
return fmt.Errorf("country %v contains invalid pricing: %w", k, err)
}
}

return nil
}

type PriceByTypeUSD struct {
Residential *PriceByServiceTypeUSD `json:"residential"`
Other *PriceByServiceTypeUSD `json:"other"`
}

func (p PriceByTypeUSD) Validate() error {
if p.Residential == nil || p.Other == nil {
return errors.New("residential and other pricing should not be nil")
}

err := p.Residential.Validate()
if err != nil {
return err
}

return p.Other.Validate()
}

type PriceByServiceTypeUSD struct {
Wireguard *PriceUSD `json:"wireguard"`
Scraping *PriceUSD `json:"scraping"`
DataTransfer *PriceUSD `json:"data_transfer"`
}

func (p PriceByServiceTypeUSD) Validate() error {
if p.Wireguard == nil || p.Scraping == nil || p.DataTransfer == nil {
return errors.New("wireguard, scraping and data_transfer pricing should not be nil")
}

if err := p.Wireguard.Validate(); err != nil {
return err
}
if err := p.Scraping.Validate(); err != nil {
return err
}
return p.DataTransfer.Validate()
}

type PriceUSD struct {
PricePerHour float64 `json:"price_per_hour_usd"`
PricePerGiB float64 `json:"price_per_gib_usd"`
}

func (p PriceUSD) Validate() error {
if p.PricePerGiB < 0 || p.PricePerHour < 0 {
return errors.New("prices should be non negative")
}

return nil
}

type Modifier struct {
Residential float64 `json:"residential"`
Other float64 `json:"other"`
}

func (m Modifier) Validate() error {
if m.Residential < 0 || m.Other < 0 {
return errors.New("modifiers should be non negative")
}
return nil
}

var defaultPriceConfig = `{
"base_prices": {
"residential": {
"wireguard": {
"price_per_hour_usd": 0.00036,
"price_per_gib_usd": 0.06
},
"scraping": {
"price_per_hour_usd": 0.00036,
"price_per_gib_usd": 0.06
},
"data_transfer": {
"price_per_hour_usd": 0.00036,
"price_per_gib_usd": 0.06
}
},
"other": {
"wireguard": {
"price_per_hour_usd": 0.00036,
"price_per_gib_usd": 0.06
},
"scraping": {
"price_per_hour_usd": 0.00036,
"price_per_gib_usd": 0.06
},
"data_transfer": {
"price_per_hour_usd": 0.00036,
"price_per_gib_usd": 0.06
}
}
},
"country_modifiers": {
"US": {
"residential": 1.5,
"other": 1.2
}
}
}`
Loading

0 comments on commit ad9b566

Please sign in to comment.