Просмотр исходного кода

first PR to support download pricing, node pricing without local disk adjust

Signed-off-by: Alan Rodrigues <alanr5691@yahoo.com>
Alan Rodrigues 3 лет назад
Родитель
Сommit
f4089200bc

+ 1 - 0
Dockerfile

@@ -37,5 +37,6 @@ ADD ./configs/default.json /models/default.json
 ADD ./configs/azure.json /models/azure.json
 ADD ./configs/aws.json /models/aws.json
 ADD ./configs/gcp.json /models/gcp.json
+ADD ./configs/aliyun.json /models/aliyun.json
 USER 1001
 ENTRYPOINT ["/go/bin/app"]

+ 12 - 0
configs/aliyun.json

@@ -0,0 +1,12 @@
+{
+    "provider": "Aliyun",
+    "description": "Aliyun Json have to figure out what the contents need to look like. Defaulting it to AWS json for now",
+    "aliyunServiceKeyName": "ABC",
+    "aliyunServiceKeySecret": "XYZ",
+    "CPU": "0.031611",
+    "spotCPU": "0.006655",
+    "RAM": "0.004237",
+    "GPU": "0.95",
+    "spotRAM": "0.000892",
+    "storage": "0.00005479452"
+}

+ 3 - 1
go.mod

@@ -63,6 +63,7 @@ require (
 	github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
 	github.com/Azure/go-autorest/logger v0.2.1 // indirect
 	github.com/Azure/go-autorest/tracing v0.6.0 // indirect
+	github.com/aliyun/alibaba-cloud-sdk-go v1.62.3 // indirect
 	github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.2.0 // indirect
 	github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.10.0 // indirect
 	github.com/aws/aws-sdk-go-v2/internal/configsources v1.1.4 // indirect
@@ -108,6 +109,7 @@ require (
 	github.com/mitchellh/mapstructure v1.4.1 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect
+	github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b // indirect
 	github.com/pelletier/go-toml v1.9.3 // indirect
 	github.com/prometheus/procfs v0.7.3 // indirect
 	github.com/rs/xid v1.3.0 // indirect
@@ -133,7 +135,7 @@ require (
 	google.golang.org/grpc v1.38.0 // indirect
 	google.golang.org/protobuf v1.26.0 // indirect
 	gopkg.in/inf.v0 v0.9.1 // indirect
-	gopkg.in/ini.v1 v1.62.0 // indirect
+	gopkg.in/ini.v1 v1.67.0 // indirect
 	k8s.io/klog/v2 v2.4.0 // indirect
 	k8s.io/utils v0.0.0-20201110183641-67b214c5f920 // indirect
 	sigs.k8s.io/structured-merge-diff/v4 v4.0.2 // indirect

+ 12 - 0
go.sum

@@ -93,6 +93,8 @@ github.com/alecthomas/template v0.0.0-20190718012654-fb15b899a751/go.mod h1:LOuy
 github.com/alecthomas/units v0.0.0-20151022065526-2efee857e7cf/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190717042225-c3de453c63f4/go.mod h1:ybxpYRFXyAe+OPACYpWeL0wqObRcbAqCMya13uyzqw0=
 github.com/alecthomas/units v0.0.0-20190924025748-f65c72e2690d/go.mod h1:rBZYJk541a8SKzHPHnH3zbiI+7dagKZ0cgpgrD7Fyho=
+github.com/aliyun/alibaba-cloud-sdk-go v1.62.3 h1:kWY5c/9JOhSYBogi3mtNG7G9TxXS0CddtQ6RKOI3mvY=
+github.com/aliyun/alibaba-cloud-sdk-go v1.62.3/go.mod h1:Api2AkmMgGaSUAhmk76oaFObkoeCPc/bKAqcyplPODs=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/armon/circbuf v0.0.0-20150827004946-bbbad097214e/go.mod h1:3U/XgcO3hCbHZ8TKRvWD2dDTCfh9M9ya+I9JpbB7O8o=
 github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8=
@@ -241,6 +243,7 @@ github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7a
 github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
 github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q=
 github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q=
+github.com/goji/httpauth v0.0.0-20160601135302-2da839ab0f4d/go.mod h1:nnjvkQ9ptGaCkuDUx6wNykzzlUixGxvkme+H/lnzb+A=
 github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
 github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg=
 github.com/golang-jwt/jwt/v4 v4.4.1 h1:pC5DB52sCeK48Wlb9oPcdhnjkz1TKt1D/P7WKJ0kUcQ=
@@ -374,6 +377,7 @@ github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGw
 github.com/jmespath/go-jmespath/internal/testify v1.5.1/go.mod h1:L3OGu8Wl2/fWfCI6z80xFu9LTZmf1ZRjMHUOPmWr69U=
 github.com/jpillora/backoff v1.0.0 h1:uvFg412JmmHBHw7iwprIxkPMI+sGQ4kzOWsMeHnm2EA=
 github.com/jpillora/backoff v1.0.0/go.mod h1:J/6gKK9jxlEcS3zixgDgUAsiuZ7yrSoa/FX5e0EB2j4=
+github.com/json-iterator/go v1.1.5/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
 github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
 github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4=
@@ -488,6 +492,8 @@ github.com/onsi/ginkgo v1.11.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+
 github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA=
 github.com/onsi/gomega v1.7.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY=
 github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY=
+github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b h1:FfH+VrHHk6Lxt9HdVS0PXzSXFyS2NbZKXv33FYPol0A=
+github.com/opentracing/opentracing-go v1.2.1-0.20220228012449-10b1cf09e00b/go.mod h1:AC62GU6hc0BrNm+9RK9VSiwa/EUe1bkIeFORAMcHvJU=
 github.com/pascaldekloe/goe v0.0.0-20180627143212-57f6aae5913c/go.mod h1:lzWF7FIEvWOWxwDKqyGYQf6ZUaNfKdP144TG7ZOy1lc=
 github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc=
 github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ=
@@ -588,6 +594,8 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/subosito/gotenv v1.2.0 h1:Slr1R9HxAlEKefgq5jn9U+DnETlIUa6HfgEzj0g5d7s=
 github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
+github.com/uber/jaeger-client-go v2.30.0+incompatible/go.mod h1:WVhlPFC8FDjOFMMWRy2pZqQJSXxYSwNYOkTr/Z6d3Kk=
+github.com/uber/jaeger-lib v2.4.1+incompatible/go.mod h1:ComeNDZlWwrWnDv8aPp0Ba6+uUTzImX/AauajbLI56U=
 github.com/ugorji/go v1.1.4/go.mod h1:uQMGLiO92mf5W77hV/PUCpI3pbzQx3CRekS0kk+RGrc=
 github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw=
 github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0=
@@ -625,6 +633,7 @@ go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk=
 go.opencensus.io v0.23.0 h1:gqCw0LfLxScz8irSi8exQc7fyQ0fKQU/qnC/X8+V/1M=
 go.opencensus.io v0.23.0/go.mod h1:XItmlyltB5F7CS4xOC1DcqMoFqwtC6OG2xF7mCv7P7E=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
+go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
 go.uber.org/zap v1.17.0/go.mod h1:MXVU+bhUf/A7Xi2HNOnopQOrmycQ5Ih87HtOu4q5SSo=
 golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
@@ -1031,6 +1040,9 @@ gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/ini.v1 v1.57.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/ini.v1 v1.62.0 h1:duBzk771uxoUuOlyRLkHsygud9+5lrlGjdFBb4mSKDU=
 gopkg.in/ini.v1 v1.62.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.66.2/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
+gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
+gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
 gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce/go.mod h1:yeKp02qBN3iKW1OzL3MGk2IdtZzaj7SFntXj72NppTA=
 gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw=
 gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

+ 824 - 0
pkg/cloud/aliyunprovider.go

@@ -0,0 +1,824 @@
+package cloud
+
+import (
+	"fmt"
+	"io"
+	"io/ioutil"
+	"strings"
+	"sync"
+	"time"
+
+	"github.com/aliyun/alibaba-cloud-sdk-go/sdk"
+	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
+	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers"
+	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
+	"github.com/opencost/opencost/pkg/clustercache"
+	"github.com/opencost/opencost/pkg/env"
+	"github.com/opencost/opencost/pkg/kubecost"
+	"github.com/opencost/opencost/pkg/log"
+	"github.com/opencost/opencost/pkg/util/fileutil"
+	"github.com/opencost/opencost/pkg/util/json"
+	"github.com/opencost/opencost/pkg/util/stringutil"
+	"golang.org/x/exp/slices"
+	v1 "k8s.io/api/core/v1"
+)
+
+const (
+	ALIYUN_ECS_PRODUCT_CODE                   = "ecs"
+	ALIYUN_ECS_VERSION                        = "2014-05-26"
+	ALIYUN_ECS_DOMAIN                         = "ecs.aliyuncs.com"
+	ALIYUN_DESCRIBE_PRICE_API_ACTION          = "DescribePrice"
+	ALIYUN_INSTANCE_RESOURCE_TYPE             = "instance"
+	ALIYUN_DISK_RESOURCE_TYPE                 = "disk"
+	ALIYUN_PAY_AS_YOU_GO_BILLING              = "Pay-As-You-Go"
+	ALIYUN_SUBSCRIPTION_BILLING               = "Subscription"
+	ALIYUN_PREEMPTIBLE_BILLING                = "Preemptible"
+	ALIYUN_OPTIMIZE_KEYWORD                   = "optimize"
+	ALIYUN_NON_OPTIMIZE_KEYWORD               = "nonoptimize"
+	ALIYUN_HOUR_PRICE_UNIT                    = "Hour"
+	ALIYUN_MONTH_PRICE_UNIT                   = "Month"
+	ALIYUN_YEAR_PRICE_UNIT                    = "Year"
+	ALIYUN_UNKNOWN_INSTANCE_FAMILY_TYPE       = "unknown"
+	ALIYUN_NOT_SUPPORTED_INSTANCE_FAMILY_TYPE = "unsupported"
+	ALIYUN_ENHANCED_GENERAL_PURPOSE_TYPE      = "g6e"
+	ALIYUN_SYSTEMDISK_CLOUD_ESSD_CATEGORY     = "cloud_essd"
+)
+
+// Why predefined and dependency on code? Can be converted to API call - https://www.alibabacloud.com/help/en/elastic-compute-service/latest/regions-describeregions
+var aliyunRegions = []string{
+	"cn-qingdao",
+	"cn-beijing",
+	"cn-zhangjiakou",
+	"cn-huhehaote",
+	"cn-wulanchabu",
+	"cn-hangzhou",
+	"cn-shanghai",
+	"cn-nanjing",
+	"cn-fuzhou",
+	"cn-shenzhen",
+	"cn-guangzhou",
+	"cn-chengdu",
+	"cn-hongkong",
+	"ap-southeast-1",
+	"ap-southeast-2",
+	"ap-southeast-3",
+	"ap-southeast-5",
+	"ap-southeast-6",
+	"ap-southeast-7",
+	"ap-south-1",
+	"ap-northeast-1",
+	"ap-northeast-2",
+	"us-west-1",
+	"us-east-1",
+	"eu-central-1",
+	"me-east-1",
+}
+
+// To-Do: Convert to API call - https://www.alibabacloud.com/help/en/elastic-compute-service/latest/describeinstancetypefamilies
+// Also first pass only completely tested pricing API for General pupose instances families.
+var aliyunInstanceFamilies = []string{
+	"g6e",
+	"g6",
+	"g5",
+	"sn2",
+	"sn2ne",
+}
+
+// AliyunAccessKey holds Aliyun credentials parsing from the service-key.json file.
+type AliyunAccessKey struct {
+	AccessKeyID     string `json:"aliyun_access_key_id"`
+	SecretAccessKey string `json:"aliyun_secret_access_key"`
+}
+
+// TO-DO: Slim Version of k8s disk assigned to a node, To be used if price adjustment need to happen with local disk information passed to describePrice.
+type SlimK8sDisk struct {
+	DiskType         string
+	RegionID         string
+	DiskCategory     string
+	PerformanceLevel string
+	PriceUnit        string
+	SizeInGiB        int32
+	ProviderID       string
+}
+
+// Slim version of a k8s v1.node just to pass along the object of this struct instead of constant getting the labels from within v1.Node & unit testing.
+type SlimK8sNode struct {
+	InstanceType       string
+	RegionID           string
+	PriceUnit          string
+	MemorySizeInKiB    string // TO-DO : Possible to convert to float?
+	IsIoOptimized      bool
+	OSType             string
+	ProviderID         string
+	InstanceTypeFamily string // Bug in DescribePrice, doesn't default to enhanced type correct and you get an error in DescribePrice to get around need the family of the InstanceType.
+}
+
+func NewSlimK8sNode(instanceType, regionID, priceUnit, memorySizeInKiB, osType, providerID, instanceTypeFamily string, isIOOptimized bool) *SlimK8sNode {
+	return &SlimK8sNode{
+		InstanceType:       instanceType,
+		RegionID:           regionID,
+		PriceUnit:          priceUnit,
+		MemorySizeInKiB:    memorySizeInKiB,
+		IsIoOptimized:      isIOOptimized,
+		OSType:             osType,
+		ProviderID:         providerID,
+		InstanceTypeFamily: instanceTypeFamily,
+	}
+}
+
+// AliyunNodeAttributes represents metadata about the product used to map to a node.
+// Basic Attributes needed atleast to get the key, Some attributes from k8s Node response
+// be populated directly into *Node object.
+type AliyunNodeAttributes struct {
+	InstanceType    string `json:"instanceType"`
+	MemorySizeInKiB string `json:"memorySizeInKiB"`
+	IsIoOptimized   bool   `json:"isIoOptimized"`
+	OSType          string `json:"osType"`
+}
+
+func NewAliyunNodeAttributes(node *SlimK8sNode) *AliyunNodeAttributes {
+	return &AliyunNodeAttributes{
+		InstanceType:    node.InstanceType,
+		MemorySizeInKiB: node.MemorySizeInKiB,
+		IsIoOptimized:   node.IsIoOptimized,
+		OSType:          node.OSType,
+	}
+}
+
+// AliyunDiskAttributes represents metadata about the product used to map to a PV.
+// Basic Attributes needed atleast to get the keys, Some attributes from k8s Node response
+// be populated directly into *PV object.
+type AliyunPVAttributes struct {
+	DiskType         int32  `json:"diskType"`
+	DiskCategory     string `json:"diskCategory"`
+	PerformanceLevel string `json:"performanceLevel"`
+}
+
+// Stage 1 support will be Pay-As-You-Go with HourlyPrice equal to TradePrice with PriceUnit as Hour
+// TO-DO: Subscription and Premptible support, need to find how to distinguish node into these categories]
+// TO-DO: Open question Subscription would be either Monthly or Yearly, Firstly Data retrieval/population
+// TO-DO:  need to be tested from describe price API, but how would you calculate hourly price, is it PRICE_YEARLY/HOURS_IN_THE_YEAR?
+type AliyunPricingDetails struct {
+	HourlyPrice  float32 `json:"hourlyPrice"`  // Represents hourly price for the given Aliyun Product
+	PriceUnit    string  `json:"priceUnit"`    // Represents the unit in which Alibaba Product is billed can be Hour, Month or Year based on the billingMethod
+	TradePrice   float32 `json:"tradePrice"`   // Original Price used to acquire the Alibaba Product
+	CurrencyCode string  `json:"currencyCode"` // Represents the currency unit of the
+}
+
+func NewAliyunPricingDetails(hourlyPrice float32, priceUnit string, tradePrice float32, currencyCode string) *AliyunPricingDetails {
+	return &AliyunPricingDetails{
+		HourlyPrice:  hourlyPrice,
+		PriceUnit:    priceUnit,
+		TradePrice:   tradePrice,
+		CurrencyCode: currencyCode,
+	}
+}
+
+// AliyunPricingTerms can have three types of supported billing method Pay-As-You-Go, Subscription and Premptible
+type AliyunPricingTerms struct {
+	BillingMethod  string                `json:"billingMethod"`
+	PricingDetails *AliyunPricingDetails `json:"pricingDetails"`
+}
+
+func NewAliyunPricingTerms(billingMethod string, pricingDetails *AliyunPricingDetails) *AliyunPricingTerms {
+	return &AliyunPricingTerms{
+		BillingMethod:  billingMethod,
+		PricingDetails: pricingDetails,
+	}
+}
+
+// Alibaba Pricing struct carry the Attributes and pricing information for Node or PV
+type AliyunPricing struct {
+	NodeAttributes *AliyunNodeAttributes
+	PVAttributes   *AliyunPVAttributes
+	PricingTerms   *AliyunPricingTerms
+	Node           *Node
+	PV             *PV
+}
+
+// Aliyun's Provider struct
+type Aliyun struct {
+	Pricing                 map[string]*AliyunPricing // Data to store Aliyun(Alibaba cloud) pricing struct
+	DownloadPricingDataLock sync.RWMutex              // Lock Needed to provide thread safe
+	Clientset               clustercache.ClusterCache
+	Config                  *ProviderConfig
+	serviceAccountChecks    *ServiceAccountChecks
+	clusterAccountId        string
+	clusterRegion           string
+	loadedAccessKey         bool                             // Check if Aliyun is authenticated
+	accessKey               *credentials.AccessKeyCredential // Aliyun Access key used specifically in signer interface used to sign API calls
+	clients                 map[string]*sdk.Client           // Map of regionID to sdk.client to call API for that region
+	*CustomProvider
+}
+
+// GetAliyunAccessKey return the Access Key used to interact with the Alibaba cloud, if not set it
+// set it first by looking at env variables else load it from secret files.
+// <IMPORTANT>Ask in PR what is the exact purpose of so many functions to set the key in AWS providers, am i missing something here!!!!!
+func (aliyun *Aliyun) GetAliyunAccessKey() (*credentials.AccessKeyCredential, error) {
+	if aliyun.loadedAccessKey {
+		return aliyun.accessKey, nil
+	}
+
+	config, err := aliyun.GetConfig()
+	if err != nil {
+		return nil, fmt.Errorf("Error getting the default config for aliyun provider: %s", err.Error())
+	}
+
+	//Look for service key values in env if not present in config via helm chart once changes are done
+	if config.AliyunServiceKeyName == "" {
+		config.AliyunServiceKeyName = env.GetAliyunAccessKeyID()
+	}
+	if config.AliyunServiceKeySecret == "" {
+		config.AliyunServiceKeySecret = env.GetAliyunAccessKeySecret()
+	}
+
+	if config.AliyunServiceKeyName == "" && config.AliyunServiceKeySecret == "" {
+		log.Debugf("missing service key values for Aliyun cloud integration attempting to use service account integration")
+		err := aliyun.loadAliyunAuthSecretAndSetEnv(true)
+		if err != nil {
+			return nil, fmt.Errorf("unable to set the aliyun key/secret from config file")
+		}
+		// set custom pricing keys too
+		config.AliyunServiceKeyName = env.GetAliyunAccessKeyID()
+		config.AliyunServiceKeySecret = env.GetAliyunAccessKeySecret()
+	}
+
+	if config.AliyunServiceKeyName == "" && config.AliyunServiceKeySecret == "" {
+		return nil, fmt.Errorf("failed to get the access key for the current alibaba account")
+	}
+
+	aliyun.accessKey = &credentials.AccessKeyCredential{AccessKeyId: env.GetAliyunAccessKeyID(), AccessKeySecret: env.GetAliyunAccessKeySecret()}
+	aliyun.loadedAccessKey = true
+
+	return aliyun.accessKey, nil
+}
+
+func (aliyun *Aliyun) DownloadPricingData() error {
+	aliyun.DownloadPricingDataLock.Lock()
+	defer aliyun.DownloadPricingDataLock.Unlock()
+
+	var aak *credentials.AccessKeyCredential
+	var err error
+
+	if !aliyun.loadedAccessKey {
+		aak, err = aliyun.GetAliyunAccessKey()
+		if err != nil {
+			log.Errorf("Unable to get the access key information")
+			return fmt.Errorf("unable to get the access key information")
+		}
+	} else {
+		aak = aliyun.accessKey
+	}
+
+	c, err := aliyun.Config.GetCustomPricingData()
+	if err != nil {
+		log.Errorf("Error downloading default pricing data: %s", err.Error())
+		return fmt.Errorf("error downloading default pricing data: %s", err.Error())
+	}
+
+	// // Get the nodes in Aliyun provider, Once alibaba cloud is setup
+	// // and populate data from the node object to resemble the data in hardcodeNodes
+	nodeList := aliyun.Clientset.GetAllNodes()
+
+	var client *sdk.Client
+	var signer *signers.AccessKeySigner
+	var ok bool
+	var pricingObj *AliyunPricing
+	var lookupKey string
+	aliyun.clients = make(map[string]*sdk.Client)
+	aliyun.Pricing = make(map[string]*AliyunPricing)
+
+	// TO-DO: Add disk price adjustment by parsing the local disk information and putting it as a param in describe Price function.
+	for _, node := range nodeList {
+		slimK8sNode := generateSlimK8sNodeFromV1Node(node)
+		lookupKey, err = determineKeyForPricing(slimK8sNode)
+		if _, ok := aliyun.Pricing[lookupKey]; ok {
+			log.Infof("Pricing information for node with same features %s already exists hence skipping", lookupKey)
+			continue
+		}
+		// TO-DO: Check if Node pricing already available , skip it if available
+		if client, ok = aliyun.clients[slimK8sNode.RegionID]; !ok {
+			client, err = sdk.NewClientWithAccessKey(slimK8sNode.RegionID, aak.AccessKeyId, aak.AccessKeySecret)
+			if err != nil {
+				return fmt.Errorf("access key provided does not have access to location %s", slimK8sNode.RegionID)
+			}
+			aliyun.clients[slimK8sNode.RegionID] = client
+		}
+		signer = signers.NewAccessKeySigner(aak)
+		pricingObj, err = processDescribePriceAndCreateAliyunPricing(client, slimK8sNode, signer, c)
+
+		if err != nil {
+			return err
+		}
+		aliyun.Pricing[lookupKey] = pricingObj
+	}
+
+	// TO-DO: PV pricing
+	// //get pvList ultimately from aliyun cloud provider and resemble data from the pvtype to
+	// // Hardcodedk8sNodeDiskStruct
+	// pvList := aliyun.Clientset.GetAllPersistentVolumes()
+
+	// pvList := []*Hardcodedk8sNodeDiskStruct{}
+	// pvList = append(pvList, &Hardcodedk8sNodeDiskStruct{
+	// 	DiskType:         "data",
+	// 	DiskCategory:     "cloud",
+	// 	PerformanceLevel: "",
+	// 	RegionID:         "cn-hangzhou",
+	// 	PriceUnit:        "Hour",
+	// 	SizeInGiB:        60,
+	// 	ProviderID:       "Ali-XXX-pv-01",
+	// }, &Hardcodedk8sNodeDiskStruct{
+	// 	DiskType:         "data",
+	// 	DiskCategory:     "cloud",
+	// 	PerformanceLevel: "P1",
+	// 	RegionID:         "cn-hangzhou",
+	// 	PriceUnit:        "Hour",
+	// 	SizeInGiB:        40,
+	// 	ProviderID:       "Ali-XXX-pv-01",
+	// })
+
+	// for _, pv := range pvList {
+	// 	if client, ok = aliyun.clients[pv.RegionID]; !ok {
+	// 		client, err = sdk.NewClientWithAccessKey(pv.RegionID, aak.AccessKeyId, aak.AccessKeySecret)
+	// 		if err != nil {
+	// 			return fmt.Errorf("access key provided does not have access to location %s", pv.RegionID)
+	// 		}
+	// 		aliyun.clients[pv.RegionID] = client
+	// 	}
+	// 	signer = signers.NewAccessKeySigner(aak)
+	// 	pricingObj, err = processDescribePriceAndCreateAliyunPricing(client, pv, signer)
+	// 	lookupKey, err = determineKeyForPricing(pv)
+	// 	if err != nil {
+	// 		return err
+	// 	}
+	// 	aliyun.Pricing[lookupKey] = pricingObj
+	// }
+	// log.Infof("Length of pricing is %d", len(aliyun.Pricing))
+	// log.Infof("random value is %v", aliyun.Pricing[lookupKey])
+	return nil
+}
+
+// AllNodePricing returns all the billing data for nodes and pvs
+func (aliyun *Aliyun) AllNodePricing() (interface{}, error) {
+	aliyun.DownloadPricingDataLock.RLock()
+	defer aliyun.DownloadPricingDataLock.RUnlock()
+	return aliyun.Pricing, nil
+}
+
+// NodePricing gives a specific node for the key
+func (aliyun *Aliyun) NodePricing(key Key) (*Node, error) {
+	aliyun.DownloadPricingDataLock.RLock()
+	defer aliyun.DownloadPricingDataLock.RUnlock()
+
+	// Get node features for the key
+	keyFeature := key.Features()
+
+	pricing, ok := aliyun.Pricing[keyFeature]
+	if !ok {
+		log.Infof("Node pricing information not found for node with feature: %s", keyFeature)
+		return &Node{}, nil
+	}
+
+	log.Infof("returing the node price for the node with feature: %s", keyFeature)
+	return pricing.Node, nil
+}
+
+// PVPricing gives a specific PV price for the PVkey
+func (aliyun *Aliyun) PVPricing(pvk PVKey) (*PV, error) {
+	aliyun.DownloadPricingDataLock.RLock()
+	defer aliyun.DownloadPricingDataLock.RUnlock()
+
+	keyFeature := pvk.Features()
+
+	pricing, ok := aliyun.Pricing[keyFeature]
+
+	if !ok {
+		log.Infof("Persistent Volume pricing not found for PV with feature: %s", keyFeature)
+		return &PV{}, nil
+	}
+
+	log.Infof("returing the PV price for the node with feature: %s", keyFeature)
+	return pricing.PV, nil
+}
+
+// Stubbed NetworkPricing for Aliyun. Will look at this in Next PR
+func (aliyun *Aliyun) NetworkPricing() (*Network, error) {
+	return &Network{
+		ZoneNetworkEgressCost:     0.0,
+		RegionNetworkEgressCost:   0.0,
+		InternetNetworkEgressCost: 0.0,
+	}, nil
+}
+
+// Stubbed LoadBalancerPricing for Aliyun. Will look at this in Next PR
+func (aliyun *Aliyun) LoadBalancerPricing() (*LoadBalancer, error) {
+	return &LoadBalancer{
+		Cost: 0.0,
+	}, nil
+}
+
+func (aliyun *Aliyun) GetConfig() (*CustomPricing, error) {
+	c, err := aliyun.Config.GetCustomPricingData()
+	if err != nil {
+		return nil, err
+	}
+	if c.Discount == "" {
+		c.Discount = "0%"
+	}
+	if c.NegotiatedDiscount == "" {
+		c.NegotiatedDiscount = "0%"
+	}
+	if c.ShareTenancyCosts == "" {
+		c.ShareTenancyCosts = defaultShareTenancyCost
+	}
+
+	return c, nil
+}
+
+// Load once and cache the result (even on failure). This is an install time secret, so
+// we don't expect the secret to change. If it does, however, we can force reload using
+// the input parameter.
+func (aliyun *Aliyun) loadAliyunAuthSecretAndSetEnv(force bool) error {
+	if !force && aliyun.loadedAccessKey {
+		return nil
+	}
+
+	exists, err := fileutil.FileExists(authSecretPath)
+	if !exists || err != nil {
+		return fmt.Errorf("Failed to locate service account file: %s", authSecretPath)
+	}
+
+	result, err := ioutil.ReadFile(authSecretPath)
+	if err != nil {
+		return err
+	}
+
+	var ak *AliyunAccessKey
+	err = json.Unmarshal(result, &ak)
+	if err != nil {
+		return err
+	}
+
+	err = env.Set(env.AliyunAccessKeyIDEnvVar, ak.AccessKeyID)
+	if err != nil {
+		return err
+	}
+	err = env.Set(env.AliyunAccessKeySecretEnvVar, ak.SecretAccessKey)
+	if err != nil {
+		return err
+	}
+	aliyun.loadedAccessKey = true
+	aliyun.accessKey = &credentials.AccessKeyCredential{
+		AccessKeyId:     ak.AccessKeyID,
+		AccessKeySecret: ak.SecretAccessKey,
+	}
+	return nil
+}
+
+// Regions returns a current supported list of Alibaba regions
+func (aliyun *Aliyun) Regions() []string {
+	return aliyunRegions
+}
+
+// ClusterInfo returns information about ALiyun cluster, as provided by metadata. TO-DO: Look at this function closely at next PR iteration
+func (aliyun *Aliyun) ClusterInfo() (map[string]string, error) {
+
+	c, err := aliyun.GetConfig()
+	if err != nil {
+		log.Errorf("Error opening config: %s", err.Error())
+	}
+
+	var clusterName string
+	if c.ClusterName != "" {
+		clusterName = c.ClusterName
+	}
+
+	// Use a default name if none has been set until this point
+	if clusterName == "" {
+		clusterName = "Aliyun Cluster #1"
+	}
+
+	m := make(map[string]string)
+	m["name"] = clusterName
+	m["provider"] = kubecost.AliyunProvider
+	m["project"] = aliyun.clusterAccountId
+	m["region"] = aliyun.clusterRegion
+	m["id"] = env.GetClusterID()
+	return m, nil
+}
+
+// Will look at this in Next PR if needed
+func (aliyun *Aliyun) GetAddresses() ([]byte, error) {
+	return nil, nil
+}
+
+// Will look at this in Next PR if needed
+func (aliyun *Aliyun) GetDisks() ([]byte, error) {
+	return nil, nil
+}
+
+// Will look at this in Next PR if needed
+func (aliyun *Aliyun) UpdateConfig(r io.Reader, updateType string) (*CustomPricing, error) {
+	return nil, nil
+}
+
+// Will look at this in Next PR if needed
+func (aliyun *Aliyun) UpdateConfigFromConfigMap(cm map[string]string) (*CustomPricing, error) {
+	return nil, nil
+}
+
+// Will look at this in Next PR if needed
+func (aliyun *Aliyun) GetManagementPlatform() (string, error) {
+	return "", nil
+}
+
+// Will look at this in Next PR if needed
+func (aliyun *Aliyun) GetLocalStorageQuery(window, offset time.Duration, rate bool, used bool) string {
+	return ""
+}
+
+// Will look at this in Next PR if needed
+func (aliyun *Aliyun) ApplyReservedInstancePricing(nodes map[string]*Node) {
+
+}
+
+// Will look at this in Next PR if needed
+func (aliyun *Aliyun) ServiceAccountStatus() *ServiceAccountStatus {
+	return &ServiceAccountStatus{}
+}
+
+// Will look at this in Next PR if needed
+func (aliyun *Aliyun) PricingSourceStatus() map[string]*PricingSource {
+	return map[string]*PricingSource{}
+}
+
+// Will look at this in Next PR if needed
+func (aliyun *Aliyun) ClusterManagementPricing() (string, float64, error) {
+	return "", 0.0, nil
+}
+
+// Will look at this in Next PR if needed
+func (aliyun *Aliyun) CombinedDiscountForNode(string, bool, float64, float64) float64 {
+	return 0.0
+}
+
+type AliyunNodeKey struct {
+	ProviderID       string
+	RegionID         string
+	InstanceType     string
+	OSType           string
+	OptimizedKeyword string //If IsIoOptimized key will have optimize if not unoptimized the key for the node
+}
+
+func NewAliyunNodeKey(node *SlimK8sNode, optimizedKeyword string) *AliyunNodeKey {
+	return &AliyunNodeKey{
+		ProviderID:       node.ProviderID,
+		RegionID:         node.RegionID,
+		InstanceType:     node.InstanceType,
+		OSType:           node.OSType,
+		OptimizedKeyword: optimizedKeyword,
+	}
+}
+
+func (aliyunNodeKey *AliyunNodeKey) ID() string {
+	return aliyunNodeKey.ProviderID
+}
+
+func (aliyunNodeKey *AliyunNodeKey) Features() string {
+	keyLookup := stringutil.DeleteEmptyStringsFromArray([]string{aliyunNodeKey.RegionID, aliyunNodeKey.InstanceType, aliyunNodeKey.OSType, aliyunNodeKey.OptimizedKeyword})
+	return strings.Join(keyLookup, "::")
+}
+
+func (aliyunNodeKey *AliyunNodeKey) GPUType() string {
+	return ""
+}
+
+// Get's the key for the k8s node input
+func (aliyun *Aliyun) GetKey(mapValue map[string]string, node *v1.Node) Key {
+	//Mostly parse the Node object and get the ProviderID, region, InstanceType, OSType and OptimizedKeyword(In if block)
+	// Currently just hardcoding a Node but eventually need to Node object
+	slimK8sNode := generateSlimK8sNodeFromV1Node(node)
+
+	optimizedKeyword := ""
+	if slimK8sNode.IsIoOptimized {
+		optimizedKeyword = ALIYUN_OPTIMIZE_KEYWORD
+	} else {
+		optimizedKeyword = ALIYUN_NON_OPTIMIZE_KEYWORD
+	}
+	return NewAliyunNodeKey(slimK8sNode, optimizedKeyword)
+}
+
+type AliyunPVKey struct {
+	ProviderID       string
+	RegionID         string
+	DiskType         string
+	DiskCategory     string
+	PerformaceLevel  string
+	StorageClassName string
+}
+
+func (aliyunPVKey *AliyunPVKey) Features() string {
+	keyLookup := stringutil.DeleteEmptyStringsFromArray([]string{aliyunPVKey.RegionID, aliyunPVKey.DiskType, aliyunPVKey.DiskCategory, aliyunPVKey.PerformaceLevel})
+	return strings.Join(keyLookup, "::")
+}
+
+func (aliyunPVKey *AliyunPVKey) ID() string {
+	return aliyunPVKey.ProviderID
+}
+
+// Get storage class information for PV.
+func (aliyunPVKey *AliyunPVKey) GetStorageClass() string {
+	return aliyunPVKey.StorageClassName
+}
+
+// Helper functions for aliyunprovider.go
+
+// createDescribePriceACSRequest creates the HTTP GET request for the required resources' Price information,
+// When supporting subscription and Premptible resources this HTTP call needs to be modified with PriceUnit information
+// When supporting different new type of instances like Compute Optimized, Memory Optimized etc make sure you add the instance type
+// in unit test and check if it works or not to create the ack request and processDescribePriceAndCreateAliyunPricing function
+// else more paramters need to be pulled from kubernetes node response or gather infromation from elsewhere and function modified.
+// TO-DO: Add disk adjustments to the node , Test it out!
+func createDescribePriceACSRequest(i interface{}) (*requests.CommonRequest, error) {
+	request := requests.NewCommonRequest()
+	request.Method = requests.GET
+	request.Product = ALIYUN_ECS_PRODUCT_CODE
+	request.Domain = ALIYUN_ECS_DOMAIN
+	request.Version = ALIYUN_ECS_VERSION
+	request.Scheme = requests.HTTPS
+	request.ApiName = ALIYUN_DESCRIBE_PRICE_API_ACTION
+	switch i.(type) {
+	case *SlimK8sNode:
+		node := i.(*SlimK8sNode)
+		request.QueryParams["RegionId"] = node.RegionID
+		request.QueryParams["ResourceType"] = ALIYUN_INSTANCE_RESOURCE_TYPE
+		request.QueryParams["InstanceType"] = node.InstanceType
+		request.QueryParams["PriceUnit"] = node.PriceUnit
+		// For Enhanced General Purpose Type g6e SystemDisk.Category param doesn't default right,
+		// need it to be specifically assigned to "cloud_ssd" otherwise there's errors
+		if node.InstanceTypeFamily == ALIYUN_ENHANCED_GENERAL_PURPOSE_TYPE {
+			request.QueryParams["SystemDisk.Category"] = ALIYUN_SYSTEMDISK_CLOUD_ESSD_CATEGORY
+		}
+		request.TransToAcsRequest()
+		return request, nil
+	case *SlimK8sDisk:
+		disk := i.(*SlimK8sDisk)
+		request.QueryParams["RegionId"] = disk.RegionID
+		request.QueryParams["ResourceType"] = ALIYUN_DISK_RESOURCE_TYPE
+		request.QueryParams["DataDisk.1.Category"] = disk.DiskCategory
+		request.QueryParams["DataDisk.1.Size"] = fmt.Sprintf("%d", disk.SizeInGiB)
+		request.QueryParams["PriceUnit"] = disk.PriceUnit
+		request.TransToAcsRequest()
+		return request, nil
+	default:
+		return nil, fmt.Errorf("unsupported ECS type for DescribePrice at this time")
+	}
+}
+
+// determineKeyForPricing generate a unique key from SlimK8sNode object that is construct from v1.Node object.
+// This function is
+func determineKeyForPricing(i interface{}) (string, error) {
+	switch i.(type) {
+	case *SlimK8sNode:
+		node := i.(*SlimK8sNode)
+		if node.IsIoOptimized {
+			keyLookup := stringutil.DeleteEmptyStringsFromArray([]string{node.RegionID, node.InstanceType, node.OSType, ALIYUN_OPTIMIZE_KEYWORD})
+			return strings.Join(keyLookup, "::"), nil
+		} else {
+			keyLookup := stringutil.DeleteEmptyStringsFromArray([]string{node.RegionID, node.InstanceType, node.OSType, ALIYUN_NON_OPTIMIZE_KEYWORD})
+			return strings.Join(keyLookup, "::"), nil
+		}
+	case *SlimK8sDisk:
+		disk := i.(*SlimK8sDisk)
+		keyLookup := stringutil.DeleteEmptyStringsFromArray([]string{disk.RegionID, disk.DiskCategory, disk.DiskType, disk.PerformanceLevel})
+		return strings.Join(keyLookup, "::"), nil
+	default:
+		return "", fmt.Errorf("unsupported ECS pricing component at this time")
+	}
+}
+
+// Below structs represents the structs to unmarshal json response of Aliyun's API DescribePrice
+type Price struct {
+	OriginalPrice             float32 `json:"OriginalPrice"`
+	ReservedInstanceHourPrice float32 `json:"ReservedInstanceHourPrice"`
+	DiscountPrice             float32 `json:"DiscountPrice"`
+	Currency                  string  `json:"Currency"`
+	TradePrice                float32 `json:"TradePrice"`
+}
+
+type PriceInfo struct {
+	Price Price `json:"Price"`
+}
+type DescribePriceResponse struct {
+	RequestId string    `json:"RequestId"`
+	PriceInfo PriceInfo `json:"PriceInfo"`
+}
+
+// processDescribePriceAndCreateAliyunPricing processes the DescribePrice API and generates the pricing information for alibaba node resource.
+func processDescribePriceAndCreateAliyunPricing(client *sdk.Client, i interface{}, signer *signers.AccessKeySigner, custom *CustomPricing) (pricing *AliyunPricing, err error) {
+	pricing = &AliyunPricing{}
+	var response DescribePriceResponse
+	log.Infof("type is %v", i)
+	switch i.(type) {
+	case *SlimK8sNode:
+		node := i.(*SlimK8sNode)
+		req, err := createDescribePriceACSRequest(node)
+		log.Debugf("Request is : %v", req)
+		if err != nil {
+			return nil, err
+		}
+		resp, err := client.ProcessCommonRequestWithSigner(req, signer)
+		log.Infof("value is %s", resp.GetHttpContentString())
+		pricing.NodeAttributes = NewAliyunNodeAttributes(node)
+		if err != nil || resp.GetHttpStatus() != 200 {
+			// Can be defaulted to some value here?
+			return nil, fmt.Errorf("unable to fetch information for node with InstanceType: %v", node.InstanceType)
+		} else {
+			// This is where population of Pricing happens
+			err = json.Unmarshal(resp.GetHttpContentBytes(), &response)
+			if err != nil {
+				return nil, fmt.Errorf("unable to unmarshall json response to custom struct, possible change in json response of DescribePrice")
+			}
+			// TO-DO : Ask in PR How to get the defaults is it equal to AWS/GCP defaults? And what needs to be returned
+			pricing.Node = &Node{
+				Cost:         fmt.Sprintf("%f", response.PriceInfo.Price.TradePrice),
+				BaseCPUPrice: custom.CPU,
+				BaseRAMPrice: custom.RAM,
+				BaseGPUPrice: custom.GPU,
+			}
+			// TO-DO : Currently with Pay-As-You-go Offering TradePrice = HourlyPrice , When support happens to other type HourlyPrice Need to be determined.
+			pricing.PricingTerms = NewAliyunPricingTerms(ALIYUN_PAY_AS_YOU_GO_BILLING, NewAliyunPricingDetails(response.PriceInfo.Price.TradePrice, ALIYUN_HOUR_PRICE_UNIT, response.PriceInfo.Price.TradePrice, response.PriceInfo.Price.Currency))
+		}
+	case *SlimK8sDisk:
+		disk := i.(*SlimK8sDisk)
+		req, err := createDescribePriceACSRequest(disk)
+		if err != nil {
+			return nil, err
+		}
+		resp, err := client.ProcessCommonRequestWithSigner(req, signer)
+		if err != nil {
+			return nil, fmt.Errorf("unable to fetch information for disk with DiskType: %v", disk.DiskType)
+		} else {
+			// This is where population of Pricing happens
+			err = json.Unmarshal(resp.GetHttpContentBytes(), &response)
+			if err != nil {
+				return nil, fmt.Errorf("unable to unmarshall json response to custom struct, possible change in json response of DescribePrice")
+			}
+			pricing.PVAttributes = &AliyunPVAttributes{}
+			pricing.PV = &PV{
+				Cost: fmt.Sprintf("%f", response.PriceInfo.Price.TradePrice),
+			}
+
+		}
+	default:
+		return nil, fmt.Errorf("unsupported ECS pricing component at this time")
+	}
+
+	return pricing, nil
+}
+
+// This function is to get the InstanceFamily from the InstanceType , convention followed in
+// instance type is ecs.[FamilyName].[DifferentSize], it gets the familyName , if it is unable to get it
+// it lists the instance family name as Unknown.
+// TO-DO: might need predefined list of instance types.
+func getInstanceFamilyFromType(instanceType string) string {
+	splitinstanceType := strings.Split(instanceType, ".")
+	if len(splitinstanceType) != 3 {
+		log.Warnf("unable to find the family of the instance type %s, returning it's family type unknown", instanceType)
+		return ALIYUN_UNKNOWN_INSTANCE_FAMILY_TYPE
+	}
+	if !slices.Contains(aliyunInstanceFamilies, splitinstanceType[1]) {
+		log.Warnf("currently the instance family type %s is not valid or not tested completely for pricing API", instanceType)
+		return ALIYUN_NOT_SUPPORTED_INSTANCE_FAMILY_TYPE
+	}
+	return splitinstanceType[1]
+}
+
+func generateSlimK8sNodeFromV1Node(node *v1.Node) *SlimK8sNode {
+	var regionID, osType, instanceType, providerID, priceUnit, instanceFamily string
+	var memorySizeInKiB string // TO-DO: try to convert it into float
+	var ok, IsIoOptimized bool
+	if regionID, ok = node.Labels["topology.kubernetes.io/region"]; !ok {
+		// HIGHLY UNLIKELY THAT THIS LABEL WONT BE THERE.
+		log.Debugf("No RegionID label for the node: %s", node.Name)
+	}
+	if osType, ok = node.Labels["beta.kubernetes.io/os"]; !ok {
+		// HIGHLY UNLIKELY THAT THIS LABEL WONT BE THERE.
+		log.Debugf("OS type undetected for the node: %s", node.Name)
+	}
+	if instanceType, ok = node.Labels["node.kubernetes.io/instance-type"]; !ok {
+		// HIGHLY UNLIKELY THAT THIS LABEL WONT BE THERE.
+		log.Debugf("Instance Type undetected for the node: %s", node.Name)
+	}
+
+	instanceFamily = getInstanceFamilyFromType(instanceType)
+	memorySizeInKiB = fmt.Sprintf("%v", node.Status.Capacity.Memory())
+	providerID = node.Spec.ProviderID // Aliyun provider doesnt follow convention of prefix with cloud provider name
+
+	// Looking at current Instance offering , all of the Instances seem to be I/O optimized - https://www.alibabacloud.com/help/en/elastic-compute-service/latest/instance-family
+	// Basic price Json has it as part of the key so defaulting to true.
+	IsIoOptimized = true
+	priceUnit = ALIYUN_HOUR_PRICE_UNIT
+
+	return NewSlimK8sNode(instanceType, regionID, priceUnit, memorySizeInKiB, osType, providerID, instanceFamily, IsIoOptimized)
+}

+ 285 - 0
pkg/cloud/aliyunprovider_test.go

@@ -0,0 +1,285 @@
+package cloud
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/aliyun/alibaba-cloud-sdk-go/sdk"
+	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
+	"github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers"
+	"github.com/opencost/opencost/pkg/log"
+	v1 "k8s.io/api/core/v1"
+	resource "k8s.io/apimachinery/pkg/api/resource"
+)
+
+func TestCreateDescribePriceACSRequest(t *testing.T) {
+	node := &SlimK8sNode{
+		InstanceType:       "ecs.g6.large",
+		RegionID:           "cn-hangzhou",
+		PriceUnit:          "Hour",
+		MemorySizeInKiB:    "16KiB",
+		IsIoOptimized:      true,
+		OSType:             "Linux",
+		ProviderID:         "Ali-XXX-node-01",
+		InstanceTypeFamily: "g6",
+	}
+	_, err := createDescribePriceACSRequest(node)
+	if err != nil {
+		t.Errorf("Error converting to Alibaba cloud request")
+	}
+}
+
+func TestProcessDescribePriceAndCreateAliyunPricing(t *testing.T) {
+	// Skipping this test case since it exposes secret but a good test case to verify when
+	// supporting a new family of instances, steps to perform are
+	// STEP 1: Comment the t.Skip() line and then replace XXX_KEY_ID with the aliyun key id of your account and XXX_SECRET_ID with aliyun secret of your account.
+	// STEP 2: Once you verify describePrice is working and no change needed in processDescribePriceAndCreateAliyunPricing, you can go ahead and revert the step 1 changes.
+
+	// This test case was use to test all general puprose instances
+
+	t.Skip()
+
+	client, err := sdk.NewClientWithAccessKey("cn-hangzhou", "XXX_KEY_ID", "XXX_SECRET_ID")
+	if err != nil {
+		t.Errorf("Error connecting to the Alibaba cloud")
+	}
+	aak := credentials.NewAccessKeyCredential("XXX_KEY_ID", "XXX_SECRET_ID")
+	signer := signers.NewAccessKeySigner(aak)
+
+	cases := []struct {
+		name          string
+		testNode      *SlimK8sNode
+		expectedError error
+	}{
+		{
+			name: "test Enhanced General Purpose Type g6e instance family",
+			testNode: &SlimK8sNode{
+				InstanceType:       "ecs.g6e.xlarge",
+				RegionID:           "cn-hangzhou",
+				PriceUnit:          "Hour",
+				MemorySizeInKiB:    "16777216KiB",
+				IsIoOptimized:      true,
+				OSType:             "Linux",
+				ProviderID:         "cn-hangzhou.i-test-01",
+				InstanceTypeFamily: "g6e",
+			},
+			expectedError: nil,
+		},
+		{
+			name: "test General Purpose Type g6 instance family",
+			testNode: &SlimK8sNode{
+				InstanceType:       "ecs.g6.3xlarge",
+				RegionID:           "cn-hangzhou",
+				PriceUnit:          "Hour",
+				MemorySizeInKiB:    "50331648KiB",
+				IsIoOptimized:      true,
+				OSType:             "Linux",
+				ProviderID:         "cn-hangzhou.i-test-02",
+				InstanceTypeFamily: "g6",
+			},
+			expectedError: nil,
+		},
+		{
+			name: "test General Purpose Type g5 instance family",
+			testNode: &SlimK8sNode{
+				InstanceType:       "ecs.g5.2xlarge",
+				RegionID:           "cn-hangzhou",
+				PriceUnit:          "Hour",
+				MemorySizeInKiB:    "33554432KiB",
+				IsIoOptimized:      true,
+				OSType:             "Linux",
+				ProviderID:         "cn-hangzhou.i-test-03",
+				InstanceTypeFamily: "g5",
+			},
+			expectedError: nil,
+		},
+		{
+			name: "test General Purpose Type sn2 instance family",
+			testNode: &SlimK8sNode{
+				InstanceType:       "ecs.sn2.large",
+				RegionID:           "cn-hangzhou",
+				PriceUnit:          "Hour",
+				MemorySizeInKiB:    "16777216KiB",
+				IsIoOptimized:      true,
+				OSType:             "Linux",
+				ProviderID:         "cn-hangzhou.i-test-04",
+				InstanceTypeFamily: "sn2",
+			},
+			expectedError: nil,
+		},
+		{
+			name: "test General Purpose Type with Enhanced Network Performance sn2ne instance family",
+			testNode: &SlimK8sNode{
+				InstanceType:       "ecs.sn2ne.2xlarge",
+				RegionID:           "cn-hangzhou",
+				PriceUnit:          "Hour",
+				MemorySizeInKiB:    "33554432KiB",
+				IsIoOptimized:      true,
+				OSType:             "Linux",
+				ProviderID:         "cn-hangzhou.i-test-05",
+				InstanceTypeFamily: "sn2ne",
+			},
+			expectedError: nil,
+		},
+	}
+	custom := &CustomPricing{}
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			pricingObj, err := processDescribePriceAndCreateAliyunPricing(client, c.testNode, signer, custom)
+			if err != nil && c.expectedError == nil {
+				t.Fatalf("Case name %s: got an error %s", c.name, err)
+			}
+			if pricingObj == nil {
+				t.Fatalf("Case name %s: got a nil pricing object", c.name)
+			}
+			t.Logf("Pricing Information gathered for instanceType %s is %v", c.name, pricingObj.PricingTerms.PricingDetails.TradePrice)
+		})
+	}
+}
+
+func TestGetInstanceFamilyFromType(t *testing.T) {
+	cases := []struct {
+		name                   string
+		instanceType           string
+		expectedInstanceFamily string
+	}{
+		{
+			name:                   "test if ecs.[instance-family].[different-type] work",
+			instanceType:           "ecs.sn2ne.2xlarge",
+			expectedInstanceFamily: "sn2ne",
+		},
+		{
+			name:                   "test if random word gives you ALIYUN_UNKNOWN_INSTANCE_FAMILY_TYPE value ",
+			instanceType:           "random.value",
+			expectedInstanceFamily: ALIYUN_UNKNOWN_INSTANCE_FAMILY_TYPE,
+		},
+		{
+			name:                   "test if random instance family gives you ALIYUN_NOT_SUPPORTED_INSTANCE_FAMILY_TYPE value ",
+			instanceType:           "ecs.g7e.2xlarge",
+			expectedInstanceFamily: ALIYUN_NOT_SUPPORTED_INSTANCE_FAMILY_TYPE,
+		},
+	}
+
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			returnValue := getInstanceFamilyFromType(c.instanceType)
+			if returnValue != c.expectedInstanceFamily {
+				t.Fatalf("Case name %s: expected instance family of type %s but got %s", c.name, c.expectedInstanceFamily, returnValue)
+			}
+		})
+	}
+}
+
+func TestDetermineKeyForPricing(t *testing.T) {
+	type randomK8sStruct struct {
+		name string
+	}
+	cases := []struct {
+		name          string
+		testVar       interface{}
+		expectedKey   string
+		expectedError error
+	}{
+		{
+			name: "test when all RegionID, InstanceType, OSType & ALIYUN_OPTIMIZE_KEYWORD words are used to key",
+			testVar: &SlimK8sNode{
+				InstanceType:       "ecs.sn2.large",
+				RegionID:           "cn-hangzhou",
+				PriceUnit:          "Hour",
+				MemorySizeInKiB:    "16777216KiB",
+				IsIoOptimized:      true,
+				OSType:             "linux",
+				ProviderID:         "cn-hangzhou.i-test-04",
+				InstanceTypeFamily: "sn2",
+			},
+			expectedKey:   "cn-hangzhou::ecs.sn2.large::linux::optimize",
+			expectedError: nil,
+		},
+		{
+			name: "test missing InstanceType to create key",
+			testVar: &SlimK8sNode{
+				RegionID:        "cn-hangzhou",
+				PriceUnit:       "Hour",
+				MemorySizeInKiB: "16777216KiB",
+				IsIoOptimized:   true,
+				OSType:          "linux",
+				ProviderID:      "cn-hangzhou.i-test-04",
+			},
+			expectedKey:   "cn-hangzhou::linux::optimize",
+			expectedError: nil,
+		},
+		{
+			name: "test random k8s struct should return unsupported error",
+			testVar: &randomK8sStruct{
+				name: "test struct",
+			},
+			expectedKey:   "",
+			expectedError: fmt.Errorf("unsupported ECS type for DescribePrice at this time"),
+		},
+	}
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			returnString, returnErr := determineKeyForPricing(c.testVar)
+			if c.expectedError == nil && returnErr != nil {
+				t.Fatalf("Case name %s: expected error was nil but recieved error %v", c.name, returnErr)
+			}
+			if returnString != c.expectedKey {
+				t.Fatalf("Case name %s: determineKeyForPricing recieved %s but expected %s", c.name, returnString, c.expectedKey)
+			}
+		})
+	}
+}
+
+func TestGenerateSlimK8sNodeFromV1Node(t *testing.T) {
+	testv1Node := &v1.Node{}
+	testv1Node.Labels = make(map[string]string)
+	testv1Node.Labels["topology.kubernetes.io/region"] = "us-east-1"
+	testv1Node.Labels["beta.kubernetes.io/os"] = "linux"
+	testv1Node.Labels["node.kubernetes.io/instance-type"] = "ecs.sn2ne.2xlarge"
+	testv1Node.Status.Capacity = v1.ResourceList{
+		v1.ResourceMemory: *resource.NewQuantity(16, resource.BinarySI),
+	}
+	cases := []struct {
+		name             string
+		testNode         *v1.Node
+		expectedSlimNode *SlimK8sNode
+	}{
+		{
+			name:     "test a generic *v1.Node to *SlimK8sNode Conversion",
+			testNode: testv1Node,
+			expectedSlimNode: &SlimK8sNode{
+				InstanceType:       "ecs.sn2ne.2xlarge",
+				RegionID:           "us-east-1",
+				PriceUnit:          "Hour",
+				MemorySizeInKiB:    "16",
+				IsIoOptimized:      true,
+				OSType:             "linux",
+				InstanceTypeFamily: "sn2ne",
+			},
+		},
+	}
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			returnSlimK8sNode := generateSlimK8sNodeFromV1Node(c.testNode)
+			log.Infof("value is %v", returnSlimK8sNode)
+			if returnSlimK8sNode.InstanceType != c.expectedSlimNode.InstanceType {
+				t.Fatalf("unexpected conversion in function generateSlimK8sNodeFromV1Node expected InstanceType: %s , recieved Instance Type: %s", c.expectedSlimNode.InstanceType, returnSlimK8sNode.InstanceType)
+			}
+			if returnSlimK8sNode.RegionID != c.expectedSlimNode.RegionID {
+				t.Fatalf("unexpected conversion in function generateSlimK8sNodeFromV1Node expected RegionID: %s , recieved RegionID Type: %s", c.expectedSlimNode.RegionID, returnSlimK8sNode.RegionID)
+			}
+			if returnSlimK8sNode.PriceUnit != c.expectedSlimNode.PriceUnit {
+				t.Fatalf("unexpected conversion in function generateSlimK8sNodeFromV1Node expected PriceUnit: %s , recieved PriceUnit Type: %s", c.expectedSlimNode.PriceUnit, returnSlimK8sNode.PriceUnit)
+			}
+			if returnSlimK8sNode.MemorySizeInKiB != c.expectedSlimNode.MemorySizeInKiB {
+				t.Fatalf("unexpected conversion in function generateSlimK8sNodeFromV1Node expected MemorySizeInKiB: %s , recieved MemorySizeInKiB Type: %s", c.expectedSlimNode.MemorySizeInKiB, returnSlimK8sNode.MemorySizeInKiB)
+			}
+			if returnSlimK8sNode.OSType != c.expectedSlimNode.OSType {
+				t.Fatalf("unexpected conversion in function generateSlimK8sNodeFromV1Node expected OSType: %s , recieved OSType Type: %s", c.expectedSlimNode.OSType, returnSlimK8sNode.OSType)
+			}
+			if returnSlimK8sNode.InstanceTypeFamily != c.expectedSlimNode.InstanceTypeFamily {
+				t.Fatalf("unexpected conversion in function generateSlimK8sNodeFromV1Node expected InstanceTypeFamily: %s , recieved InstanceTypeFamily Type: %s", c.expectedSlimNode.InstanceTypeFamily, returnSlimK8sNode.InstanceTypeFamily)
+			}
+		})
+	}
+}

+ 16 - 0
pkg/cloud/provider.go

@@ -158,6 +158,8 @@ type CustomPricing struct {
 	GpuLabelValue                string `json:"gpuLabelValue,omitempty"`
 	ServiceKeyName               string `json:"awsServiceKeyName,omitempty"`
 	ServiceKeySecret             string `json:"awsServiceKeySecret,omitempty"`
+	AliyunServiceKeyName         string `json:"aliyunServiceKeyName,omitempty"`
+	AliyunServiceKeySecret       string `json:"aliyunServiceKeySecret,omitempty"`
 	SpotDataRegion               string `json:"awsSpotDataRegion,omitempty"`
 	SpotDataBucket               string `json:"awsSpotDataBucket,omitempty"`
 	SpotDataPrefix               string `json:"awsSpotDataPrefix,omitempty"`
@@ -482,6 +484,15 @@ func NewProvider(cache clustercache.ClusterCache, apiKey string, config *config.
 			clusterAccountId:     cp.accountID,
 			serviceAccountChecks: NewServiceAccountChecks(),
 		}, nil
+	case kubecost.AliyunProvider:
+		log.Info("Found ProviderID starting with \"aliyun\", using Aliyun(Alibaba Cloud) Provider")
+		return &Aliyun{
+			Clientset:            cache,
+			Config:               NewProviderConfig(config, cp.configFileName),
+			clusterRegion:        cp.region,
+			clusterAccountId:     cp.accountID,
+			serviceAccountChecks: NewServiceAccountChecks(),
+		}, nil
 	case kubecost.ScalewayProvider:
 		log.Info("Found ProviderID starting with \"scaleway\", using Scaleway Provider")
 		return &Scaleway{
@@ -530,6 +541,11 @@ func getClusterProperties(node *v1.Node) clusterProperties {
 	} else if strings.HasPrefix(providerID, "scaleway") { // the scaleway provider ID looks like scaleway://instance/<instance_id>
 		cp.provider = kubecost.ScalewayProvider
 		cp.configFileName = "scaleway.json"
+	} else if strings.Contains(node.Status.NodeInfo.KubeletVersion, "aliyun") { // provider ID is not prefix with any distinct keyword like other providers
+		// TO-DO: Check for Nil conditions
+		log.Infof("I assgined the cp provider and json")
+		cp.provider = kubecost.AliyunProvider
+		cp.configFileName = "aliyun.json"
 	}
 	if env.IsUseCSVProvider() {
 		cp.provider = kubecost.CSVProvider

+ 15 - 0
pkg/env/costmodelenv.go

@@ -16,6 +16,9 @@ const (
 	AWSAccessKeySecretEnvVar = "AWS_SECRET_ACCESS_KEY"
 	AWSClusterIDEnvVar       = "AWS_CLUSTER_ID"
 
+	AliyunAccessKeyIDEnvVar     = "ALIYUN_ACCESS_KEY_ID"
+	AliyunAccessKeySecretEnvVar = "ALIYUN_SECRET_ACCESS_KEY"
+
 	KubecostNamespaceEnvVar        = "KUBECOST_NAMESPACE"
 	PodNameEnvVar                  = "POD_NAME"
 	ClusterIDEnvVar                = "CLUSTER_ID"
@@ -205,6 +208,18 @@ func GetAWSClusterID() string {
 	return Get(AWSClusterIDEnvVar, "")
 }
 
+// GetAliyunAccessKeyID returns the environment variable value for AliyunAccessKeyIDEnvVar which represents
+// the Aliyun access key for authentication
+func GetAliyunAccessKeyID() string {
+	return Get(AliyunAccessKeyIDEnvVar, "")
+}
+
+// GetAliyunAccessKeySecret returns the environment variable value for AliyunAccessKeySecretEnvVar which represents
+// the Aliyun access key secret for authentication
+func GetAliyunAccessKeySecret() string {
+	return Get(AliyunAccessKeySecretEnvVar, "")
+}
+
 // GetKubecostNamespace returns the environment variable value for KubecostNamespaceEnvVar which
 // represents the namespace the cost model exists in.
 func GetKubecostNamespace() string {

+ 3 - 0
pkg/kubecost/assetprops.go

@@ -99,6 +99,9 @@ const GCPProvider = "GCP"
 // AzureProvider describes the provider Azure
 const AzureProvider = "Azure"
 
+// AliyunProvider describes the provider Azure
+const AliyunProvider = "Aliyun"
+
 // CSVProvider describes the provider a CSV
 const CSVProvider = "CSV"
 

+ 10 - 0
pkg/util/stringutil/stringutil.go

@@ -164,3 +164,13 @@ func StringSlicesEqual(left, right []string) bool {
 	}
 	return true
 }
+
+// DeleteEmptyStringsFromArray removes the empty strings from an array.
+func DeleteEmptyStringsFromArray(input []string) (output []string) {
+	for _, str := range input {
+		if str != "" {
+			output = append(output, str)
+		}
+	}
+	return
+}