Răsfoiți Sursa

network costs and load balancer costs in alibaba provider (#1881)

Signed-off-by: Alan Rodrigues <alanr5691@yahoo.com>
Alan Rodrigues 3 ani în urmă
părinte
comite
5e49311eb0
4 a modificat fișierele cu 99 adăugiri și 12 ștergeri
  1. 8 4
      configs/alibaba.json
  2. 48 8
      pkg/cloud/aliyunprovider.go
  3. 1 0
      pkg/cloud/provider.go
  4. 42 0
      pkg/cloud/providerconfig.go

+ 8 - 4
configs/alibaba.json

@@ -1,12 +1,16 @@
 {
     "provider": "Alibaba",
-    "description": "Default prices used to compute allocation between RAM and CPU. Alibaba Cloud pricing API data still used for total node cost.",
-    "alibabaServiceKeyName": "ABC",
-    "alibabaServiceKeySecret": "XYZ",
+    "description": "Default prices used to compute allocation between RAM and CPU. Alibaba Cloud pricing API is used for total node and PV cost.",
+    "alibabaServiceKeyName": "",
+    "alibabaServiceKeySecret": "",
     "CPU": "0.031611",
     "spotCPU": "0.006655",
     "RAM": "0.004237",
     "GPU": "0.95",
     "spotRAM": "0.000892",
-    "storage": "0.00005479452"
+    "storage": "0.00005479452",
+    "zoneNetworkEgress": "0.02",
+    "regionNetworkEgress": "0.08",
+    "internetNetworkEgress": "0.123",
+    "defaultLBPrice": "0.007"
 }

+ 48 - 8
pkg/cloud/aliyunprovider.go

@@ -6,6 +6,7 @@ import (
 	"io"
 	"os"
 	"regexp"
+	"strconv"
 	"strings"
 	"sync"
 	"time"
@@ -366,7 +367,10 @@ func (alibaba *Alibaba) GetAlibabaAccessKey() (*credentials.AccessKeyCredential,
 		return nil, fmt.Errorf("failed to get the access key for the current alibaba account")
 	}
 
-	alibaba.accessKey = &credentials.AccessKeyCredential{AccessKeyId: env.GetAlibabaAccessKeyID(), AccessKeySecret: env.GetAlibabaAccessKeySecret()}
+	// At this point either user is using the alibaba key and secret from secret passed in helm config if not he will use the secret that is passed in custom pricing
+	// There's no check at this time for if the custom pricing key and secret is valid and that's on the user else there will be errors recorded.
+	// Key and secret passed in config will supersede key and secret passed while installing Closed source helm chart.
+	alibaba.accessKey = &credentials.AccessKeyCredential{AccessKeyId: config.AlibabaServiceKeyName, AccessKeySecret: config.AlibabaServiceKeySecret}
 
 	return alibaba.accessKey, nil
 }
@@ -544,19 +548,46 @@ func (alibaba *Alibaba) PVPricing(pvk PVKey) (*PV, error) {
 	return pricing.PV, nil
 }
 
-// Stubbed NetworkPricing for Alibaba Cloud. Will look at this in Next PR
+// Inter zone and Inter region network cost are defaulted based on https://www.alibabacloud.com/help/en/cloud-data-transmission/latest/cross-region-data-transfers
+// Internet cost is default based on https://www.alibabacloud.com/help/en/elastic-compute-service/latest/public-bandwidth to $0.123
 func (alibaba *Alibaba) NetworkPricing() (*Network, error) {
+	cpricing, err := alibaba.Config.GetCustomPricingData()
+	if err != nil {
+		return nil, err
+	}
+	znec, err := strconv.ParseFloat(cpricing.ZoneNetworkEgress, 64)
+	if err != nil {
+		return nil, err
+	}
+	rnec, err := strconv.ParseFloat(cpricing.RegionNetworkEgress, 64)
+	if err != nil {
+		return nil, err
+	}
+	inec, err := strconv.ParseFloat(cpricing.InternetNetworkEgress, 64)
+	if err != nil {
+		return nil, err
+	}
+
 	return &Network{
-		ZoneNetworkEgressCost:     0.0,
-		RegionNetworkEgressCost:   0.0,
-		InternetNetworkEgressCost: 0.0,
+		ZoneNetworkEgressCost:     znec,
+		RegionNetworkEgressCost:   rnec,
+		InternetNetworkEgressCost: inec,
 	}, nil
 }
 
-// Stubbed LoadBalancerPricing for Alibaba Cloud. Will look at this in Next PR
+// Alibaba loadbalancer has three different types https://www.alibabacloud.com/product/server-load-balancer,
+// defaulted price to classic load balancer https://www.alibabacloud.com/help/en/server-load-balancer/latest/pay-as-you-go.
 func (alibaba *Alibaba) LoadBalancerPricing() (*LoadBalancer, error) {
+	cpricing, err := alibaba.Config.GetCustomPricingData()
+	if err != nil {
+		return nil, err
+	}
+	lbPricing, err := strconv.ParseFloat(cpricing.DefaultLBPrice, 64)
+	if err != nil {
+		return nil, err
+	}
 	return &LoadBalancer{
-		Cost: 0.0,
+		Cost: lbPricing,
 	}, nil
 }
 
@@ -747,7 +778,16 @@ func (alibaba *Alibaba) CombinedDiscountForNode(string, bool, float64, float64)
 }
 
 func (alibaba *Alibaba) accessKeyisLoaded() bool {
-	return alibaba.accessKey != nil
+	if alibaba.accessKey == nil {
+		return false
+	}
+	if alibaba.accessKey.AccessKeyId == "" {
+		return false
+	}
+	if alibaba.accessKey.AccessKeySecret == "" {
+		return false
+	}
+	return true
 }
 
 type AlibabaNodeKey struct {

+ 1 - 0
pkg/cloud/provider.go

@@ -234,6 +234,7 @@ type CustomPricing struct {
 	KubecostToken                string `json:"kubecostToken"`
 	GoogleAnalyticsTag           string `json:"googleAnalyticsTag"`
 	ExcludeProviderID            string `json:"excludeProviderID"`
+	DefaultLBPrice               string `json:"defaultLBPrice"`
 }
 
 // GetSharedOverheadCostPerMonth parses and returns a float64 representation

+ 42 - 0
pkg/cloud/providerconfig.go

@@ -2,6 +2,8 @@ package cloud
 
 import (
 	"fmt"
+	"io/ioutil"
+	"os"
 	gopath "path"
 	"reflect"
 	"strconv"
@@ -14,6 +16,8 @@ import (
 	"github.com/opencost/opencost/pkg/util/json"
 )
 
+const closedSourceConfigMount = "models/"
+
 var sanitizePolicy = bluemonday.UGCPolicy()
 
 // ProviderConfig is a utility class that provides a thread-safe configuration storage/cache for all Provider
@@ -91,6 +95,15 @@ func (pc *ProviderConfig) loadConfig(writeIfNotExists bool) (*CustomPricing, err
 	if !exists {
 		log.Infof("Could not find Custom Pricing file at path '%s'", pc.configFile.Path())
 		pc.customPricing = DefaultPricing()
+		// If config file is not present use the contents from mount models/ as pricing data
+		// in closed source rather than from from  DefaultPricing as first source of truth.
+		// since most images will already have a mount, to avail this facility user needs to delete the
+		// config file manually from configpath else default pricing still holds good.
+		fileName := filenameInConfigPath(pc.configFile.Path())
+		defaultPricing, err := ReturnPricingFromConfigs(fileName)
+		if err == nil {
+			pc.customPricing = defaultPricing
+		}
 
 		// Only write the file if flag enabled
 		if writeIfNotExists {
@@ -273,3 +286,32 @@ func configPathFor(filename string) string {
 	path := env.GetConfigPathWithDefault("/models/")
 	return gopath.Join(path, filename)
 }
+
+// Gives the config file name in a full qualified file name
+func filenameInConfigPath(fqfn string) string {
+	_, fileName := gopath.Split(fqfn)
+	return fileName
+}
+
+// ReturnPricingFromConfigs is a safe function to return pricing from configs of opensource to the closed source
+// before defaulting it with the above function DefaultPricing
+func ReturnPricingFromConfigs(filename string) (*CustomPricing, error) {
+	if _, err := os.Stat(closedSourceConfigMount); os.IsNotExist(err) {
+		return &CustomPricing{}, fmt.Errorf("ReturnPricingFromConfigs: %s likely running in provider config in opencost itself with err: %v", closedSourceConfigMount, err)
+	}
+	providerConfigFile := gopath.Join(closedSourceConfigMount, filename)
+	if _, err := os.Stat(providerConfigFile); err != nil {
+		return &CustomPricing{}, fmt.Errorf("ReturnPricingFromConfigs: unable to find file %s with err: %v", providerConfigFile, err)
+	}
+	configFile, err := ioutil.ReadFile(providerConfigFile)
+	if err != nil {
+		return &CustomPricing{}, fmt.Errorf("ReturnPricingFromConfigs: unable to open file %s with err: %v", providerConfigFile, err)
+	}
+
+	defaultPricing := &CustomPricing{}
+	err = json.Unmarshal(configFile, defaultPricing)
+	if err != nil {
+		return &CustomPricing{}, fmt.Errorf("ReturnPricingFromConfigs: unable to open file %s with err: %v", providerConfigFile, err)
+	}
+	return defaultPricing, nil
+}