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

Merge branch 'develop' into AjayTripathy-configurable-pricing-ep

Signed-off-by: Ajay Tripathy <ajay@kubecost.com>
Ajay Tripathy 3 лет назад
Родитель
Сommit
6cb14955fa

+ 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/alibaba.json /models/alibaba.json
 USER 1001
 ENTRYPOINT ["/go/bin/app"]

+ 12 - 0
configs/alibaba.json

@@ -0,0 +1,12 @@
+{
+    "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",
+    "CPU": "0.031611",
+    "spotCPU": "0.006655",
+    "RAM": "0.004237",
+    "GPU": "0.95",
+    "spotRAM": "0.000892",
+    "storage": "0.00005479452"
+}

+ 4 - 2
go.mod

@@ -47,6 +47,7 @@ require (
 	golang.org/x/exp v0.0.0-20220609121020-a51bd0440498
 	golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c
 	golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
+	golang.org/x/text v0.4.0
 	google.golang.org/api v0.44.0
 	gopkg.in/yaml.v2 v2.4.0
 	k8s.io/api v0.20.4
@@ -63,6 +64,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 +110,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
@@ -125,7 +128,6 @@ require (
 	golang.org/x/net v0.1.0 // indirect
 	golang.org/x/sys v0.1.0 // indirect
 	golang.org/x/term v0.1.0 // indirect
-	golang.org/x/text v0.4.0 // indirect
 	golang.org/x/time v0.0.0-20200630173020-3af7569d3a1e // indirect
 	golang.org/x/tools v0.1.12 // indirect
 	google.golang.org/appengine v1.6.7 // 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=

+ 1050 - 0
pkg/cloud/aliyunprovider.go

@@ -0,0 +1,1050 @@
+package cloud
+
+import (
+	"fmt"
+	"io"
+	"io/ioutil"
+	"regexp"
+	"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 (
+	ALIBABA_ECS_PRODUCT_CODE                   = "ecs"
+	ALIBABA_ECS_VERSION                        = "2014-05-26"
+	ALIBABA_ECS_DOMAIN                         = "ecs.aliyuncs.com"
+	ALIBABA_DESCRIBE_PRICE_API_ACTION          = "DescribePrice"
+	ALIBABA_DESCRIBE_DISK_API_ACTION           = "DescribeDisks"
+	ALIBABA_INSTANCE_RESOURCE_TYPE             = "instance"
+	ALIBABA_DISK_RESOURCE_TYPE                 = "disk"
+	ALIBABA_PAY_AS_YOU_GO_BILLING              = "Pay-As-You-Go"
+	ALIBABA_SUBSCRIPTION_BILLING               = "Subscription"
+	ALIBABA_PREEMPTIBLE_BILLING                = "Preemptible"
+	ALIBABA_OPTIMIZE_KEYWORD                   = "optimize"
+	ALIBABA_NON_OPTIMIZE_KEYWORD               = "nonoptimize"
+	ALIBABA_HOUR_PRICE_UNIT                    = "Hour"
+	ALIBABA_MONTH_PRICE_UNIT                   = "Month"
+	ALIBABA_YEAR_PRICE_UNIT                    = "Year"
+	ALIBABA_UNKNOWN_INSTANCE_FAMILY_TYPE       = "unknown"
+	ALIBABA_NOT_SUPPORTED_INSTANCE_FAMILY_TYPE = "unsupported"
+	ALIBABA_ENHANCED_GENERAL_PURPOSE_TYPE      = "g6e"
+	ALIBABA_DISK_CLOUD_ESSD_CATEGORY           = "cloud_essd"
+	ALIBABA_DISK_CLOUD_CATEGORY                = "cloud"
+	ALIBABA_DATA_DISK_CATEGORY                 = "data"
+	ALIBABA_SYSTEM_DISK_CATEGORY               = "system"
+	ALIBABA_DATA_DISK_PREFIX                   = "DataDisk"
+	ALIBABA_PV_CLOUD_DISK_TYPE                 = "CloudDisk"
+	ALIBABA_PV_NAS_TYPE                        = "NAS"
+	ALIBABA_PV_OSS_TYPE                        = "OSS"
+	ALIBABA_DEFAULT_DATADISK_SIZE              = "2000"
+	ALIBABA_DISK_TOPOLOGY_REGION_LABEL         = "topology.diskplugin.csi.alibabacloud.com/region"
+	ALIBABA_DISK_TOPOLOGY_ZONE_LABEL           = "topology.diskplugin.csi.alibabacloud.com/zone"
+)
+
+var (
+	// Regular expression to get the numerical value of PV suffix with GiB from *v1.PersistentVolume.
+	sizeRegEx = regexp.MustCompile("(.*?)Gi")
+)
+
+// 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 alibabaRegions = []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 alibabaInstanceFamilies = []string{
+	"g6e",
+	"g6",
+	"g5",
+	"sn2",
+	"sn2ne",
+}
+
+// AlibabaAccessKey holds Alibaba credentials parsing from the service-key.json file.
+type AlibabaAccessKey struct {
+	AccessKeyID     string `json:"alibaba_access_key_id"`
+	SecretAccessKey string `json:"alibaba_secret_access_key"`
+}
+
+// Slim Version of k8s disk assigned to a node or PV, To be used if price adjustment need to happen with local disk information passed to describePrice.
+type SlimK8sDisk struct {
+	DiskType         string
+	RegionID         string
+	PriceUnit        string
+	SizeInGiB        string
+	DiskCategory     string
+	PerformanceLevel string
+	ProviderID       string
+	StorageClass     string
+}
+
+func NewSlimK8sDisk(diskType, regionID, priceUnit, diskCategory, performanceLevel, providerID, storageClass, sizeInGiB string) *SlimK8sDisk {
+	return &SlimK8sDisk{
+		DiskType:         diskType,
+		RegionID:         regionID,
+		PriceUnit:        priceUnit,
+		SizeInGiB:        sizeInGiB,
+		DiskCategory:     diskCategory,
+		PerformanceLevel: performanceLevel,
+		ProviderID:       providerID,
+		StorageClass:     storageClass,
+	}
+}
+
+// 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,
+	}
+}
+
+// AlibabaNodeAttributes represents metadata about the product pricing information 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 AlibabaNodeAttributes struct {
+	// InstanceType represents the type of instance.
+	InstanceType string `json:"instanceType"`
+	// MemorySizeInKiB represents the size of memory of instance.
+	MemorySizeInKiB string `json:"memorySizeInKiB"`
+	// IsIoOptimized represents the if instance is I/O optimized.
+	IsIoOptimized bool `json:"isIoOptimized"`
+	// OSType represents the OS installed in the Instance.
+	OSType string `json:"osType"`
+}
+
+func NewAlibabaNodeAttributes(node *SlimK8sNode) *AlibabaNodeAttributes {
+	return &AlibabaNodeAttributes{
+		InstanceType:    node.InstanceType,
+		MemorySizeInKiB: node.MemorySizeInKiB,
+		IsIoOptimized:   node.IsIoOptimized,
+		OSType:          node.OSType,
+	}
+}
+
+// AlibabaPVAttributes represents metadata about the product pricing information used to map to a PV.
+// Basic Attributes needed atleast to get the keys.Some attributes from k8s PV response
+// be populated directly into *PV object.
+type AlibabaPVAttributes struct {
+	// PVType can be Cloud Disk, NetWork Attached Storage(NAS) or Object Storage Service (OSS).
+	// Represents the way the PV was attached
+	PVType string `json:"pvType"`
+	// PVSubType represent the sub category of PVType. This is Data in case of Cloud Disk.
+	PVSubType string `json:"pvSubType"`
+	// Example for PVCategory with cloudDisk PVType are cloud, cloud_efficiency, cloud_ssd,
+	// ephemeral_ssd and cloud_essd. If not present returns empty.
+	PVCategory string `json:"pvCategory"`
+	// Example for PerformanceLevel with cloudDisk PVType are PL0,PL1,PL2 &PL3. If not present returns empty.
+	PVPerformanceLevel string `json:"performanceLevel"`
+	// The Size of the PV in terms of GiB
+	SizeInGiB string `json:"sizeInGiB"`
+}
+
+// TO-Do: next iteration of Alibaba provider support NetWork Attached Storage(NAS) and Object Storage Service (OSS type PVs).
+// Currently defaulting to cloudDisk with provision to add work in future.
+func NewAlibabaPVAttributes(disk *SlimK8sDisk) *AlibabaPVAttributes {
+	return &AlibabaPVAttributes{
+		PVType:             ALIBABA_PV_CLOUD_DISK_TYPE,
+		PVSubType:          disk.DiskType,
+		PVCategory:         disk.DiskCategory,
+		PVPerformanceLevel: disk.PerformanceLevel,
+		SizeInGiB:          disk.SizeInGiB,
+	}
+}
+
+// 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 AlibabaPricingDetails struct {
+	// Represents hourly price for the given Alibaba cloud Product.
+	HourlyPrice float32 `json:"hourlyPrice"`
+	// Represents the unit in which Alibaba Product is billed can be Hour, Month or Year based on the billingMethod.
+	PriceUnit string `json:"priceUnit"`
+	// Original Price paid to acquire the Alibaba Product.
+	TradePrice float32 `json:"tradePrice"`
+	// Represents the currency unit of the price for billing Alibaba Product.
+	CurrencyCode string `json:"currencyCode"`
+}
+
+func NewAlibabaPricingDetails(hourlyPrice float32, priceUnit string, tradePrice float32, currencyCode string) *AlibabaPricingDetails {
+	return &AlibabaPricingDetails{
+		HourlyPrice:  hourlyPrice,
+		PriceUnit:    priceUnit,
+		TradePrice:   tradePrice,
+		CurrencyCode: currencyCode,
+	}
+}
+
+// AlibabaPricingTerms can have three types of supported billing method Pay-As-You-Go, Subscription and Premptible
+type AlibabaPricingTerms struct {
+	BillingMethod  string                 `json:"billingMethod"`
+	PricingDetails *AlibabaPricingDetails `json:"pricingDetails"`
+}
+
+func NewAlibabaPricingTerms(billingMethod string, pricingDetails *AlibabaPricingDetails) *AlibabaPricingTerms {
+	return &AlibabaPricingTerms{
+		BillingMethod:  billingMethod,
+		PricingDetails: pricingDetails,
+	}
+}
+
+// Alibaba Pricing struct carry the Attributes and pricing information for Node or PV
+type AlibabaPricing struct {
+	NodeAttributes *AlibabaNodeAttributes
+	PVAttributes   *AlibabaPVAttributes
+	PricingTerms   *AlibabaPricingTerms
+	Node           *Node
+	PV             *PV
+}
+
+// Alibaba cloud's Provider struct
+type Alibaba struct {
+	// Data to store Alibaba cloud's pricing struct, key in the map represents exact match to
+	// node.features() or pv.features for easy lookup
+	Pricing map[string]*AlibabaPricing
+	// Lock Needed to provide thread safe
+	DownloadPricingDataLock sync.RWMutex
+	Clientset               clustercache.ClusterCache
+	Config                  *ProviderConfig
+	*CustomProvider
+
+	// TO-DO: These needs to be decided if either exported or unexported.
+	serviceAccountChecks *ServiceAccountChecks
+	clusterAccountId     string
+	clusterRegion        string
+
+	// The following fields are unexported because of avoiding any leak of secrets of these keys.
+	// Alibaba Access key used specifically in signer interface used to sign API calls
+	accessKey *credentials.AccessKeyCredential
+	// Map of regionID to sdk.client to call API for that region
+	clients map[string]*sdk.Client
+}
+
+// GetAlibabaAccessKey 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 (alibaba *Alibaba) GetAlibabaAccessKey() (*credentials.AccessKeyCredential, error) {
+	if alibaba.accessKeyisLoaded() {
+		return alibaba.accessKey, nil
+	}
+
+	config, err := alibaba.GetConfig()
+	if err != nil {
+		return nil, fmt.Errorf("error getting the default config for Alibaba Cloud provider: %w", err)
+	}
+
+	//Look for service key values in env if not present in config via helm chart once changes are done
+	if config.AlibabaServiceKeyName == "" {
+		config.AlibabaServiceKeyName = env.GetAlibabaAccessKeyID()
+	}
+	if config.AlibabaServiceKeySecret == "" {
+		config.AlibabaServiceKeySecret = env.GetAlibabaAccessKeySecret()
+	}
+
+	if config.AlibabaServiceKeyName == "" && config.AlibabaServiceKeySecret == "" {
+		log.Debugf("missing service key values for Alibaba cloud integration attempting to use service account integration")
+		err := alibaba.loadAlibabaAuthSecretAndSetEnv(true)
+		if err != nil {
+			return nil, fmt.Errorf("unable to set the Alibaba Cloud key/secret from config file %w", err)
+		}
+		// set custom pricing keys too
+		config.AlibabaServiceKeyName = env.GetAlibabaAccessKeyID()
+		config.AlibabaServiceKeySecret = env.GetAlibabaAccessKeySecret()
+	}
+
+	if config.AlibabaServiceKeyName == "" && config.AlibabaServiceKeySecret == "" {
+		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()}
+
+	return alibaba.accessKey, nil
+}
+
+// DownloadPricingData satisfies the provider interface and downloads the price for node and PVs.
+func (alibaba *Alibaba) DownloadPricingData() error {
+	alibaba.DownloadPricingDataLock.Lock()
+	defer alibaba.DownloadPricingDataLock.Unlock()
+
+	var aak *credentials.AccessKeyCredential
+	var err error
+
+	if !alibaba.accessKeyisLoaded() {
+		aak, err = alibaba.GetAlibabaAccessKey()
+		if err != nil {
+			return fmt.Errorf("unable to get the access key information: %w", err)
+		}
+	} else {
+		aak = alibaba.accessKey
+	}
+
+	c, err := alibaba.Config.GetCustomPricingData()
+	if err != nil {
+		return fmt.Errorf("error downloading default pricing data: %w", err)
+	}
+
+	// Get all the nodes from Alibaba cluster.
+	nodeList := alibaba.Clientset.GetAllNodes()
+
+	var client *sdk.Client
+	var signer *signers.AccessKeySigner
+	var ok bool
+	var lookupKey string
+	alibaba.clients = make(map[string]*sdk.Client)
+	alibaba.Pricing = make(map[string]*AlibabaPricing)
+
+	// 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 {
+		pricingObj := &AlibabaPricing{}
+		slimK8sNode := generateSlimK8sNodeFromV1Node(node)
+		lookupKey, err = determineKeyForPricing(slimK8sNode)
+		if _, ok := alibaba.Pricing[lookupKey]; ok {
+			log.Debugf("Pricing information for node with same features %s already exists hence skipping", lookupKey)
+			continue
+		}
+
+		if client, ok = alibaba.clients[slimK8sNode.RegionID]; !ok {
+			client, err = sdk.NewClientWithAccessKey(slimK8sNode.RegionID, aak.AccessKeyId, aak.AccessKeySecret)
+			if err != nil {
+				return fmt.Errorf("unable to initiate alibaba cloud sdk client for region %s : %w", slimK8sNode.RegionID, err)
+			}
+			alibaba.clients[slimK8sNode.RegionID] = client
+		}
+		signer = signers.NewAccessKeySigner(aak)
+		pricingObj, err = processDescribePriceAndCreateAlibabaPricing(client, slimK8sNode, signer, c)
+
+		if err != nil {
+			return fmt.Errorf("failed to create pricing information for node with type %s with error: %w", slimK8sNode.InstanceType, err)
+		}
+		alibaba.Pricing[lookupKey] = pricingObj
+	}
+
+	// set the first occurance of region from the node
+	if alibaba.clusterRegion == "" {
+		for _, node := range nodeList {
+			if regionID, ok := node.Labels["topology.kubernetes.io/region"]; ok {
+				alibaba.clusterRegion = regionID
+				break
+			}
+		}
+	}
+
+	// PV pricing for only Cloud Disk for now.
+	// TO-DO: Support both NAS(Network Attached storage) and OSS(Object Storage Service) type PVs
+
+	pvList := alibaba.Clientset.GetAllPersistentVolumes()
+
+	for _, pv := range pvList {
+		pvRegion := determinePVRegion(pv)
+		if pvRegion == "" {
+			pvRegion = alibaba.clusterRegion
+		}
+		pricingObj := &AlibabaPricing{}
+		slimK8sDisk := generateSlimK8sDiskFromV1PV(pv, pvRegion)
+		lookupKey, err = determineKeyForPricing(slimK8sDisk)
+		if _, ok := alibaba.Pricing[lookupKey]; ok {
+			log.Debugf("Pricing information for pv with same features %s already exists hence skipping", lookupKey)
+			continue
+		}
+		if client, ok = alibaba.clients[slimK8sDisk.RegionID]; !ok {
+			client, err = sdk.NewClientWithAccessKey(slimK8sDisk.RegionID, aak.AccessKeyId, aak.AccessKeySecret)
+			if err != nil {
+				return fmt.Errorf("unable to initiate alibaba cloud sdk client for region %s : %w", slimK8sDisk.RegionID, err)
+			}
+			alibaba.clients[slimK8sDisk.RegionID] = client
+		}
+		signer = signers.NewAccessKeySigner(aak)
+		pricingObj, err = processDescribePriceAndCreateAlibabaPricing(client, slimK8sDisk, signer, c)
+		if err != nil {
+			return fmt.Errorf("failed to create pricing information for pv with category %s with error: %w", slimK8sDisk.DiskCategory, err)
+		}
+		alibaba.Pricing[lookupKey] = pricingObj
+	}
+
+	return nil
+}
+
+// AllNodePricing returns all the pricing data for all nodes and pvs
+func (alibaba *Alibaba) AllNodePricing() (interface{}, error) {
+	alibaba.DownloadPricingDataLock.RLock()
+	defer alibaba.DownloadPricingDataLock.RUnlock()
+	return alibaba.Pricing, nil
+}
+
+// NodePricing gives pricing information of a specific node given by the key
+func (alibaba *Alibaba) NodePricing(key Key) (*Node, error) {
+	alibaba.DownloadPricingDataLock.RLock()
+	defer alibaba.DownloadPricingDataLock.RUnlock()
+
+	// Get node features for the key
+	keyFeature := key.Features()
+
+	pricing, ok := alibaba.Pricing[keyFeature]
+	if !ok {
+		log.Warnf("Node pricing information not found for node with feature: %s", keyFeature)
+		return &Node{}, nil
+	}
+
+	log.Debugf("returning the node price for the node with feature: %s", keyFeature)
+	returnNode := pricing.Node
+
+	return returnNode, nil
+}
+
+// PVPricing gives a pricing information of a specific PV given by PVkey
+func (alibaba *Alibaba) PVPricing(pvk PVKey) (*PV, error) {
+	alibaba.DownloadPricingDataLock.RLock()
+	defer alibaba.DownloadPricingDataLock.RUnlock()
+
+	keyFeature := pvk.Features()
+
+	pricing, ok := alibaba.Pricing[keyFeature]
+
+	if !ok {
+		log.Warnf("Persistent Volume pricing not found for PV with feature: %s", keyFeature)
+		return &PV{}, nil
+	}
+
+	log.Debugf("returning the PV price for the node with feature: %s", keyFeature)
+	return pricing.PV, nil
+}
+
+// Stubbed NetworkPricing for Alibaba Cloud. Will look at this in Next PR
+func (alibaba *Alibaba) NetworkPricing() (*Network, error) {
+	return &Network{
+		ZoneNetworkEgressCost:     0.0,
+		RegionNetworkEgressCost:   0.0,
+		InternetNetworkEgressCost: 0.0,
+	}, nil
+}
+
+// Stubbed LoadBalancerPricing for Alibaba Cloud. Will look at this in Next PR
+func (alibaba *Alibaba) LoadBalancerPricing() (*LoadBalancer, error) {
+	return &LoadBalancer{
+		Cost: 0.0,
+	}, nil
+}
+
+func (alibaba *Alibaba) GetConfig() (*CustomPricing, error) {
+	c, err := alibaba.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 (alibaba *Alibaba) loadAlibabaAuthSecretAndSetEnv(force bool) error {
+	if !force && alibaba.accessKeyisLoaded() {
+		return nil
+	}
+
+	exists, err := fileutil.FileExists(authSecretPath)
+	if !exists || err != nil {
+		return fmt.Errorf("failed to locate service account file: %s with err: %w", authSecretPath, err)
+	}
+
+	result, err := ioutil.ReadFile(authSecretPath)
+	if err != nil {
+		return fmt.Errorf("failed to read service account file: %s with err: %w", authSecretPath, err)
+	}
+
+	var ak *AlibabaAccessKey
+	err = json.Unmarshal(result, &ak)
+	if err != nil {
+		return fmt.Errorf("failed to unmarshall access key id and access key secret with err: %w", err)
+	}
+
+	err = env.Set(env.AlibabaAccessKeyIDEnvVar, ak.AccessKeyID)
+	if err != nil {
+		return fmt.Errorf("failed to set environment variable: %s with err: %w", env.AlibabaAccessKeyIDEnvVar, err)
+	}
+	err = env.Set(env.AlibabaAccessKeySecretEnvVar, ak.SecretAccessKey)
+	if err != nil {
+		return fmt.Errorf("failed to set environment variable: %s with err: %w", env.AlibabaAccessKeySecretEnvVar, err)
+	}
+
+	alibaba.accessKey = &credentials.AccessKeyCredential{
+		AccessKeyId:     ak.AccessKeyID,
+		AccessKeySecret: ak.SecretAccessKey,
+	}
+	return nil
+}
+
+// Regions returns a current supported list of Alibaba regions
+func (alibaba *Alibaba) Regions() []string {
+	return alibabaRegions
+}
+
+// ClusterInfo returns information about Alibaba Cloud cluster, as provided by metadata. TO-DO: Look at this function closely at next PR iteration
+func (alibaba *Alibaba) ClusterInfo() (map[string]string, error) {
+
+	c, err := alibaba.GetConfig()
+	if err != nil {
+		return nil, fmt.Errorf("failed to getConfig with err: %w", err)
+	}
+
+	var clusterName string
+	if c.ClusterName != "" {
+		clusterName = c.ClusterName
+	}
+
+	// Set it to environment clusterID if not set at this point
+	if clusterName == "" {
+		clusterName = env.GetClusterID()
+	}
+
+	m := make(map[string]string)
+	m["name"] = clusterName
+	m["provider"] = kubecost.AlibabaProvider
+	m["project"] = alibaba.clusterAccountId
+	m["region"] = alibaba.clusterRegion
+	m["id"] = env.GetClusterID()
+	return m, nil
+}
+
+// Will look at this in Next PR if needed
+func (alibaba *Alibaba) GetAddresses() ([]byte, error) {
+	return nil, nil
+}
+
+// Will look at this in Next PR if needed
+func (alibaba *Alibaba) GetDisks() ([]byte, error) {
+	return nil, nil
+}
+
+// Will look at this in Next PR if needed
+func (alibaba *Alibaba) UpdateConfig(r io.Reader, updateType string) (*CustomPricing, error) {
+	return nil, nil
+}
+
+// Will look at this in Next PR if needed
+func (alibaba *Alibaba) UpdateConfigFromConfigMap(cm map[string]string) (*CustomPricing, error) {
+	return nil, nil
+}
+
+// Will look at this in Next PR if needed
+func (alibaba *Alibaba) GetManagementPlatform() (string, error) {
+	return "", nil
+}
+
+// Will look at this in Next PR if needed
+func (alibaba *Alibaba) GetLocalStorageQuery(window, offset time.Duration, rate bool, used bool) string {
+	return ""
+}
+
+// Will look at this in Next PR if needed
+func (alibaba *Alibaba) ApplyReservedInstancePricing(nodes map[string]*Node) {
+
+}
+
+// Will look at this in Next PR if needed
+func (alibaba *Alibaba) ServiceAccountStatus() *ServiceAccountStatus {
+	return &ServiceAccountStatus{}
+}
+
+// Will look at this in Next PR if needed
+func (alibaba *Alibaba) PricingSourceStatus() map[string]*PricingSource {
+	return map[string]*PricingSource{}
+}
+
+// Will look at this in Next PR if needed
+func (alibaba *Alibaba) ClusterManagementPricing() (string, float64, error) {
+	return "", 0.0, nil
+}
+
+// Will look at this in Next PR if needed
+func (alibaba *Alibaba) CombinedDiscountForNode(string, bool, float64, float64) float64 {
+	return 0.0
+}
+
+func (alibaba *Alibaba) accessKeyisLoaded() bool {
+	return alibaba.accessKey != nil
+}
+
+type AlibabaNodeKey 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 NewAlibabaNodeKey(node *SlimK8sNode, optimizedKeyword string) *AlibabaNodeKey {
+	return &AlibabaNodeKey{
+		ProviderID:       node.ProviderID,
+		RegionID:         node.RegionID,
+		InstanceType:     node.InstanceType,
+		OSType:           node.OSType,
+		OptimizedKeyword: optimizedKeyword,
+	}
+}
+
+func (alibabaNodeKey *AlibabaNodeKey) ID() string {
+	return alibabaNodeKey.ProviderID
+}
+
+func (alibabaNodeKey *AlibabaNodeKey) Features() string {
+	keyLookup := stringutil.DeleteEmptyStringsFromArray([]string{alibabaNodeKey.RegionID, alibabaNodeKey.InstanceType, alibabaNodeKey.OSType, alibabaNodeKey.OptimizedKeyword})
+	return strings.Join(keyLookup, "::")
+}
+
+func (alibabaNodeKey *AlibabaNodeKey) GPUType() string {
+	return ""
+}
+
+func (alibabaNodeKey *AlibabaNodeKey) GPUCount() int {
+	return 0
+}
+
+// Get's the key for the k8s node input
+func (alibaba *Alibaba) 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 = ALIBABA_OPTIMIZE_KEYWORD
+	} else {
+		optimizedKeyword = ALIBABA_NON_OPTIMIZE_KEYWORD
+	}
+	return NewAlibabaNodeKey(slimK8sNode, optimizedKeyword)
+}
+
+type AlibabaPVKey struct {
+	ProviderID        string
+	RegionID          string
+	PVType            string
+	PVSubType         string
+	PVCategory        string
+	PVPerformaceLevel string
+	StorageClassName  string
+	SizeInGiB         string
+}
+
+func (alibaba *Alibaba) GetPVKey(pv *v1.PersistentVolume, parameters map[string]string, defaultRegion string) PVKey {
+	regionID := defaultRegion
+	// If default Region is not passed default it to cluster region ID.
+	if defaultRegion == "" {
+		regionID = alibaba.clusterRegion
+	}
+	slimK8sDisk := generateSlimK8sDiskFromV1PV(pv, defaultRegion)
+	return &AlibabaPVKey{
+		ProviderID:        slimK8sDisk.ProviderID,
+		RegionID:          regionID,
+		PVType:            ALIBABA_PV_CLOUD_DISK_TYPE,
+		PVSubType:         slimK8sDisk.DiskType,
+		PVCategory:        slimK8sDisk.DiskCategory,
+		PVPerformaceLevel: slimK8sDisk.PerformanceLevel,
+		StorageClassName:  pv.Spec.StorageClassName,
+		SizeInGiB:         slimK8sDisk.SizeInGiB,
+	}
+}
+
+func (alibabaPVKey *AlibabaPVKey) Features() string {
+	keyLookup := stringutil.DeleteEmptyStringsFromArray([]string{alibabaPVKey.RegionID, alibabaPVKey.PVSubType, alibabaPVKey.PVCategory, alibabaPVKey.PVPerformaceLevel, alibabaPVKey.SizeInGiB})
+	return strings.Join(keyLookup, "::")
+}
+
+func (alibabaPVKey *AlibabaPVKey) ID() string {
+	return alibabaPVKey.ProviderID
+}
+
+// Get storage class information for PV.
+func (alibabaPVKey *AlibabaPVKey) GetStorageClass() string {
+	return alibabaPVKey.StorageClassName
+}
+
+// Helper functions for alibabaprovider.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 processDescribePriceAndCreateAlibabaPricing 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 = ALIBABA_ECS_PRODUCT_CODE
+	request.Domain = ALIBABA_ECS_DOMAIN
+	request.Version = ALIBABA_ECS_VERSION
+	request.Scheme = requests.HTTPS
+	request.ApiName = ALIBABA_DESCRIBE_PRICE_API_ACTION
+	switch i.(type) {
+	case *SlimK8sNode:
+		node := i.(*SlimK8sNode)
+		request.QueryParams["RegionId"] = node.RegionID
+		request.QueryParams["ResourceType"] = ALIBABA_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 == ALIBABA_ENHANCED_GENERAL_PURPOSE_TYPE {
+			request.QueryParams["SystemDisk.Category"] = ALIBABA_DISK_CLOUD_ESSD_CATEGORY
+		}
+		request.TransToAcsRequest()
+		return request, nil
+	case *SlimK8sDisk:
+		disk := i.(*SlimK8sDisk)
+		request.QueryParams["RegionId"] = disk.RegionID
+		request.QueryParams["PriceUnit"] = disk.PriceUnit
+		request.QueryParams["ResourceType"] = ALIBABA_DISK_RESOURCE_TYPE
+		request.QueryParams[fmt.Sprintf("%s.%d.Size", ALIBABA_DATA_DISK_PREFIX, 1)] = disk.SizeInGiB
+		request.QueryParams[fmt.Sprintf("%s.%d.Category", ALIBABA_DATA_DISK_PREFIX, 1)] = disk.DiskCategory
+		// Performance level defaults to PL1 if not present in volume attribute.
+		if disk.PerformanceLevel != "" {
+			request.QueryParams[fmt.Sprintf("%s.%d.PerformanceLevel", ALIBABA_DATA_DISK_PREFIX, 1)] = disk.PerformanceLevel
+		}
+		request.TransToAcsRequest()
+		return request, nil
+	default:
+		return nil, fmt.Errorf("unsupported ECS type (%T) for DescribePrice at this time", i)
+	}
+}
+
+// determineKeyForPricing generate a unique key from SlimK8sNode object that is constructed from v1.Node object and
+// SlimK8sDisk that is constructed from v1.PersistentVolume.
+func determineKeyForPricing(i interface{}) (string, error) {
+	if i == nil {
+		return "", fmt.Errorf("nil component passed to determine key")
+	}
+	switch i.(type) {
+	case *SlimK8sNode:
+		node := i.(*SlimK8sNode)
+		if node.IsIoOptimized {
+			keyLookup := stringutil.DeleteEmptyStringsFromArray([]string{node.RegionID, node.InstanceType, node.OSType, ALIBABA_OPTIMIZE_KEYWORD})
+			return strings.Join(keyLookup, "::"), nil
+		} else {
+			keyLookup := stringutil.DeleteEmptyStringsFromArray([]string{node.RegionID, node.InstanceType, node.OSType, ALIBABA_NON_OPTIMIZE_KEYWORD})
+			return strings.Join(keyLookup, "::"), nil
+		}
+	case *SlimK8sDisk:
+		disk := i.(*SlimK8sDisk)
+		keyLookup := stringutil.DeleteEmptyStringsFromArray([]string{disk.RegionID, disk.DiskType, disk.DiskCategory, disk.PerformanceLevel, disk.SizeInGiB})
+		return strings.Join(keyLookup, "::"), nil
+	default:
+		return "", fmt.Errorf("unsupported ECS type (%T) at this time", i)
+	}
+}
+
+// Below structs are used to unmarshal json response of Alibaba cloud'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"`
+}
+
+// processDescribePriceAndCreateAlibabaPricing processes the DescribePrice API and generates the pricing information for alibaba node resource and alibaba pv resource that's backed by cloud disk.
+func processDescribePriceAndCreateAlibabaPricing(client *sdk.Client, i interface{}, signer *signers.AccessKeySigner, custom *CustomPricing) (pricing *AlibabaPricing, err error) {
+	pricing = &AlibabaPricing{}
+	var response DescribePriceResponse
+
+	if i == nil {
+		return nil, fmt.Errorf("nil component passed to process the pricing information")
+	}
+	switch i.(type) {
+	case *SlimK8sNode:
+		node := i.(*SlimK8sNode)
+		req, err := createDescribePriceACSRequest(node)
+		if err != nil {
+			return nil, err
+		}
+		resp, err := client.ProcessCommonRequestWithSigner(req, signer)
+		pricing.NodeAttributes = NewAlibabaNodeAttributes(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 with err: %w", err)
+			}
+			// 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 = NewAlibabaPricingTerms(ALIBABA_PAY_AS_YOU_GO_BILLING, NewAlibabaPricingDetails(response.PriceInfo.Price.TradePrice, ALIBABA_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 || resp.GetHttpStatus() != 200 {
+			return nil, fmt.Errorf("unable to fetch information for disk with DiskType: %v with err: %w", disk.DiskCategory, err)
+		} 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 with err: %w", err)
+			}
+			pricing.PVAttributes = NewAlibabaPVAttributes(disk)
+			pricing.PV = &PV{
+				Cost: fmt.Sprintf("%f", response.PriceInfo.Price.TradePrice),
+			}
+			// TO-DO : Disk has support for Hour and Month but pricing API is failing for month for disk(Research why?) and same challenge as node pricing no prepaid/postpaid distinction in v1.PersistentVolume object have to look at APIs for th information.
+			pricing.PricingTerms = NewAlibabaPricingTerms(ALIBABA_PAY_AS_YOU_GO_BILLING, NewAlibabaPricingDetails(response.PriceInfo.Price.TradePrice, ALIBABA_HOUR_PRICE_UNIT, response.PriceInfo.Price.TradePrice, response.PriceInfo.Price.Currency))
+		}
+	default:
+		return nil, fmt.Errorf("unsupported ECS Pricing component of type (%T) at this time", i)
+	}
+
+	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 ALIBABA_UNKNOWN_INSTANCE_FAMILY_TYPE
+	}
+	if !slices.Contains(alibabaInstanceFamilies, splitinstanceType[1]) {
+		log.Warnf("currently the instance family type %s is not valid or not tested completely for pricing API", instanceType)
+		return ALIBABA_NOT_SUPPORTED_INSTANCE_FAMILY_TYPE
+	}
+	return splitinstanceType[1]
+}
+
+// generateSlimK8sNodeFromV1Node generates SlimK8sNode struct from v1.Node to fetch pricing information.
+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("%s", node.Status.Capacity.Memory())
+	providerID = node.Spec.ProviderID // Alibaba Cloud 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 = ALIBABA_HOUR_PRICE_UNIT
+
+	return NewSlimK8sNode(instanceType, regionID, priceUnit, memorySizeInKiB, osType, providerID, instanceFamily, IsIoOptimized)
+}
+
+// getNumericalValueFromResourceQuantity returns the numericalValue of the resourceQuantity
+// An example is: 20Gi returns to 20. If any error occurs it returns the default value used in describePrice API which is 2000.
+func getNumericalValueFromResourceQuantity(quantity string) (value string) {
+	// defaulting when any panic or empty string occurs.
+	defer func() {
+		log.Debugf("unable to determine the size of the PV so defaulting the size to %s", ALIBABA_DEFAULT_DATADISK_SIZE)
+		if err := recover(); err != nil {
+			value = ALIBABA_DEFAULT_DATADISK_SIZE
+		}
+		if value == "" {
+			value = ALIBABA_DEFAULT_DATADISK_SIZE
+		}
+	}()
+	res := sizeRegEx.FindAllStringSubmatch(quantity, 1)
+	value = res[0][1]
+	return
+}
+
+// generateSlimK8sDiskFromV1PV function generates SlimK8sDisk from v1.PersistentVolume and DescribeDisk API(If required) of alibaba
+// to generate slim disk type that can be used to fetch pricing information.
+func generateSlimK8sDiskFromV1PV(pv *v1.PersistentVolume, regionID string) *SlimK8sDisk {
+
+	// All PVs are data disks while local disk are categorized as system disk
+	diskType := ALIBABA_DATA_DISK_CATEGORY
+
+	//TO-DO: Disk supports month and hour prices , defaulting to hour
+	priceUnit := ALIBABA_HOUR_PRICE_UNIT
+
+	sizeQuantity := fmt.Sprintf("%s", pv.Spec.Capacity.Storage())
+
+	// res := sizeRegEx.FindAllStringSubmatch(sizeQuantity, 1)
+
+	sizeInGiB := getNumericalValueFromResourceQuantity(sizeQuantity)
+
+	providerID := ""
+	if pv.Spec.CSI != nil {
+		providerID = pv.Spec.CSI.VolumeHandle
+	} else {
+		providerID = pv.Name // Looks like pv name is same as providerID in Alibaba k8s cluster
+	}
+
+	// Performance level being empty string gets defaulted in describePrice to PL1.
+	performanceLevel := ""
+	diskCategory := ""
+	if pv.Spec.CSI != nil {
+		if val, ok := pv.Spec.CSI.VolumeAttributes["performanceLevel"]; ok {
+			performanceLevel = val
+		}
+		if val, ok := pv.Spec.CSI.VolumeAttributes["type"]; ok {
+			diskCategory = val
+		}
+	}
+
+	// Highly unlikely that label pv.Spec.CSI.VolumeAttributes["type"] doesn't exist but if occured default to cloud (most basic disk type)
+	if diskCategory == "" {
+		diskCategory = ALIBABA_DISK_CLOUD_CATEGORY
+	}
+
+	return NewSlimK8sDisk(diskType, regionID, priceUnit, diskCategory, performanceLevel, providerID, pv.Spec.StorageClassName, sizeInGiB)
+}
+
+// determinePVRegion determines associated region for a particular PV based on the following priority, which can be changed and any other path to determine region can be added!
+// if topology.diskplugin.csi.alibabacloud.com/region label/annotation is passed during PV creation return that as the PV region.
+// if topology.diskplugin.csi.alibabacloud.com/zone label/annotation is passed during PV creation determine the region based on this pv label.
+// if neither of the above label/annotation is present check node affinity for the zone affinity and determine the region based on this zone.
+// if nether of the above yields a region , return empty string to default it to cluster region.
+func determinePVRegion(pv *v1.PersistentVolume) string {
+	// if "topology.diskplugin.csi.alibabacloud.com/region" is present as a label or annotation return that as the PV region
+	if val, ok := pv.Labels[ALIBABA_DISK_TOPOLOGY_REGION_LABEL]; ok {
+		log.Debugf("determinePVRegion returned a region value of: %s through label: %s for PV name: %s", val, ALIBABA_DISK_TOPOLOGY_REGION_LABEL, pv.Name)
+		return val
+	}
+	if val, ok := pv.Annotations[ALIBABA_DISK_TOPOLOGY_REGION_LABEL]; ok {
+		log.Debugf("determinePVRegion returned a region value of: %s through annotation: %s for PV name: %s", val, ALIBABA_DISK_TOPOLOGY_REGION_LABEL, pv.Name)
+		return val
+	}
+
+	// if "topology.diskplugin.csi.alibabacloud.com/zone" is present as a label or annotation set it as the PV zone before looking at node affinity to determine the region PV belongs too
+	var pvZone string
+
+	if val, ok := pv.Labels[ALIBABA_DISK_TOPOLOGY_ZONE_LABEL]; ok {
+		log.Debugf("determinePVRegion will set zone value to: %s through label: %s for PV name: %s", val, ALIBABA_DISK_TOPOLOGY_ZONE_LABEL, pv.Name)
+		pvZone = val
+	}
+
+	if pvZone == "" {
+		if val, ok := pv.Annotations[ALIBABA_DISK_TOPOLOGY_ZONE_LABEL]; ok {
+			log.Debugf("determinePVRegion will set zone value to: %s through annotation: %s for PV name: %s", val, ALIBABA_DISK_TOPOLOGY_ZONE_LABEL, pv.Name)
+			pvZone = val
+		}
+	}
+
+	if pvZone == "" {
+		// zone and regionID labels are optional in Alibaba PV creation, while UI creation put's a zone associated with PV assign the region of
+		// pv based on this information if available. If pv is provision via yaml and the block is missing default it to clusterRegion.
+		if pv.Spec.NodeAffinity != nil {
+			nodeAffinity := pv.Spec.NodeAffinity
+			if nodeAffinity.Required != nil && nodeAffinity.Required.NodeSelectorTerms != nil {
+				for _, nodeSelectorTerm := range nodeAffinity.Required.NodeSelectorTerms {
+					matchExpression := nodeSelectorTerm.MatchExpressions
+					for _, nodeSelectorRequirement := range matchExpression {
+						if nodeSelectorRequirement.Key == ALIBABA_DISK_TOPOLOGY_ZONE_LABEL {
+							log.Debugf("determinePVRegion will set zone value to: %s through node affinity label: %s for PV name: %s", nodeSelectorRequirement.Values[0], ALIBABA_DISK_TOPOLOGY_ZONE_LABEL, pv.Name)
+							pvZone = nodeSelectorRequirement.Values[0]
+						}
+					}
+				}
+			}
+		}
+	}
+
+	for _, region := range alibabaRegions {
+		if strings.Contains(pvZone, region) {
+			log.Debugf("determinePVRegion determined region of %s through zone affiliation of the PV %s\n", region, pvZone)
+			return region
+		}
+	}
+	return ""
+}

+ 593 - 0
pkg/cloud/aliyunprovider_test.go

@@ -0,0 +1,593 @@
+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"
+	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",
+	}
+
+	disk := &SlimK8sDisk{
+		DiskType:         "data",
+		RegionID:         "cn-hangzhou",
+		PriceUnit:        "Hour",
+		SizeInGiB:        "20",
+		DiskCategory:     "diskCategory",
+		PerformanceLevel: "cloud_essd",
+		ProviderID:       "d-Ali-XXX-01",
+		StorageClass:     "testStorageClass",
+	}
+
+	cases := []struct {
+		name          string
+		testStruct    interface{}
+		expectedError error
+	}{
+		{
+			name:          "test CreateDescribePriceACSRequest with SlimK8sNode struct Object",
+			testStruct:    node,
+			expectedError: nil,
+		},
+		{
+			name:          "test CreateDescribePriceACSRequest with SlimK8sDisk struct Object",
+			testStruct:    disk,
+			expectedError: nil,
+		},
+	}
+
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			_, err := createDescribePriceACSRequest(c.testStruct)
+			if err != nil && c.expectedError == nil {
+				t.Fatalf("Case name %s: Error converting to Alibaba cloud request", c.name)
+			}
+		})
+	}
+}
+
+func TestProcessDescribePriceAndCreateAlibabaPricing(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 alibaba key id of your account and XXX_SECRET_ID with alibaba cloud secret of your account.
+	// STEP 2: Once you verify describePrice is working and no change needed in processDescribePriceAndCreateAlibabaPricing, 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
+		teststruct    interface{}
+		expectedError error
+	}{
+		{
+			name: "test Enhanced General Purpose Type g6e instance family",
+			teststruct: &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",
+			teststruct: &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",
+			teststruct: &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",
+			teststruct: &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",
+			teststruct: &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,
+		},
+		{
+			name:          "test for a nil information",
+			teststruct:    nil,
+			expectedError: fmt.Errorf("unsupported ECS pricing component at this time"),
+		},
+		{
+			name: "test Cloud Disk with Category cloud representing basic disk",
+			teststruct: &SlimK8sDisk{
+				DiskType:     "data",
+				RegionID:     "cn-hangzhou",
+				PriceUnit:    "Hour",
+				SizeInGiB:    "20",
+				DiskCategory: "cloud",
+				ProviderID:   "d-Ali-cloud-XXX-01",
+				StorageClass: "temp",
+			},
+			expectedError: nil,
+		},
+		{
+			name: "test Cloud Disk with Category cloud_efficiency representing ultra disk",
+			teststruct: &SlimK8sDisk{
+				DiskType:     "data",
+				RegionID:     "cn-hangzhou",
+				PriceUnit:    "Hour",
+				SizeInGiB:    "40",
+				DiskCategory: "cloud_efficiency",
+				ProviderID:   "d-Ali-cloud-XXX-02",
+				StorageClass: "temp",
+			},
+			expectedError: nil,
+		},
+		{
+			name: "test Cloud Disk with Category cloud_ssd representing standard SSD",
+			teststruct: &SlimK8sDisk{
+				DiskType:     "data",
+				RegionID:     "cn-hangzhou",
+				PriceUnit:    "Hour",
+				SizeInGiB:    "40",
+				DiskCategory: "cloud_efficiency",
+				ProviderID:   "d-Ali-cloud-XXX-02",
+				StorageClass: "temp",
+			},
+			expectedError: nil,
+		},
+		{
+			name: "test Cloud Disk with Category cloud_essd representing Enhanced SSD with PL2 performance level",
+			teststruct: &SlimK8sDisk{
+				DiskType:         "data",
+				RegionID:         "cn-hangzhou",
+				PriceUnit:        "Hour",
+				SizeInGiB:        "80",
+				DiskCategory:     "cloud_ssd",
+				PerformanceLevel: "PL2",
+				ProviderID:       "d-Ali-cloud-XXX-04",
+				StorageClass:     "temp",
+			},
+			expectedError: nil,
+		},
+	}
+	custom := &CustomPricing{}
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			pricingObj, err := processDescribePriceAndCreateAlibabaPricing(client, c.teststruct, signer, custom)
+			if err != nil && c.expectedError == nil {
+				t.Fatalf("Case name %s: got an error %s", c.name, err)
+			}
+			if c.teststruct != nil {
+				if pricingObj == nil {
+					t.Fatalf("Case name %s: got a nil pricing object", c.name)
+				}
+				t.Logf("Case name %s: Pricing Information gathered for instanceType 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 ALIBABA_UNKNOWN_INSTANCE_FAMILY_TYPE value ",
+			instanceType:           "random.value",
+			expectedInstanceFamily: ALIBABA_UNKNOWN_INSTANCE_FAMILY_TYPE,
+		},
+		{
+			name:                   "test if random instance family gives you ALIBABA_NOT_SUPPORTED_INSTANCE_FAMILY_TYPE value ",
+			instanceType:           "ecs.g7e.2xlarge",
+			expectedInstanceFamily: ALIBABA_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 & ALIBABA_OPTIMIZE_KEYWORD words are used in Node 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 Node 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 randomK8sStruct for DescribePrice at this time"),
+		},
+		{
+			name:          "test for nil check",
+			testVar:       nil,
+			expectedKey:   "",
+			expectedError: fmt.Errorf("unsupported ECS type randomK8sStruct for DescribePrice at this time"),
+		},
+		{
+			name: "test when all RegionID, InstanceType, OSType & ALIBABA_OPTIMIZE_KEYWORD words are used to key",
+			testVar: &SlimK8sDisk{
+				DiskType:     "data",
+				RegionID:     "cn-hangzhou",
+				PriceUnit:    "Hour",
+				SizeInGiB:    "40",
+				DiskCategory: "cloud_efficiency",
+				ProviderID:   "d-Ali-cloud-XXX-02",
+				StorageClass: "temp",
+			},
+			expectedKey:   "cn-hangzhou::data::cloud_efficiency::40",
+			expectedError: nil,
+		},
+		{
+			name: "test missing InstanceType to create key",
+			testVar: &SlimK8sDisk{
+				DiskType:         "data",
+				RegionID:         "cn-hangzhou",
+				PriceUnit:        "Hour",
+				SizeInGiB:        "80",
+				DiskCategory:     "cloud_ssd",
+				PerformanceLevel: "PL2",
+				ProviderID:       "d-Ali-cloud-XXX-04",
+				StorageClass:     "temp",
+			},
+			expectedKey:   "cn-hangzhou::data::cloud_ssd::PL2::80",
+			expectedError: nil,
+		},
+	}
+	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:          ALIBABA_HOUR_PRICE_UNIT,
+				MemorySizeInKiB:    "16",
+				IsIoOptimized:      true,
+				OSType:             "linux",
+				InstanceTypeFamily: "sn2ne",
+			},
+		},
+	}
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			returnSlimK8sNode := generateSlimK8sNodeFromV1Node(c.testNode)
+			if returnSlimK8sNode.InstanceType != c.expectedSlimNode.InstanceType {
+				t.Fatalf("unexpected conversion in function generateSlimK8sNodeFromV1Node expected InstanceType: %s , recieved InstanceType: %s", c.expectedSlimNode.InstanceType, returnSlimK8sNode.InstanceType)
+			}
+			if returnSlimK8sNode.RegionID != c.expectedSlimNode.RegionID {
+				t.Fatalf("unexpected conversion in function generateSlimK8sNodeFromV1Node expected RegionID: %s , recieved RegionID: %s", c.expectedSlimNode.RegionID, returnSlimK8sNode.RegionID)
+			}
+			if returnSlimK8sNode.PriceUnit != c.expectedSlimNode.PriceUnit {
+				t.Fatalf("unexpected conversion in function generateSlimK8sNodeFromV1Node expected PriceUnit: %s , recieved PriceUnit: %s", c.expectedSlimNode.PriceUnit, returnSlimK8sNode.PriceUnit)
+			}
+			if returnSlimK8sNode.MemorySizeInKiB != c.expectedSlimNode.MemorySizeInKiB {
+				t.Fatalf("unexpected conversion in function generateSlimK8sNodeFromV1Node expected MemorySizeInKiB: %s , recieved MemorySizeInKiB: %s", c.expectedSlimNode.MemorySizeInKiB, returnSlimK8sNode.MemorySizeInKiB)
+			}
+			if returnSlimK8sNode.OSType != c.expectedSlimNode.OSType {
+				t.Fatalf("unexpected conversion in function generateSlimK8sNodeFromV1Node expected OSType: %s , recieved OSType: %s", c.expectedSlimNode.OSType, returnSlimK8sNode.OSType)
+			}
+			if returnSlimK8sNode.InstanceTypeFamily != c.expectedSlimNode.InstanceTypeFamily {
+				t.Fatalf("unexpected conversion in function generateSlimK8sNodeFromV1Node expected InstanceTypeFamily: %s , recieved InstanceTypeFamily: %s", c.expectedSlimNode.InstanceTypeFamily, returnSlimK8sNode.InstanceTypeFamily)
+			}
+		})
+	}
+}
+
+func TestGenerateSlimK8sDiskFromV1PV(t *testing.T) {
+	testv1PV := &v1.PersistentVolume{}
+	testv1PV.Spec.Capacity = v1.ResourceList{
+		v1.ResourceStorage: *resource.NewQuantity(16*1024*1024*1024, resource.BinarySI),
+	}
+	testv1PV.Spec.CSI = &v1.CSIPersistentVolumeSource{}
+	testv1PV.Spec.CSI.VolumeHandle = "testPV"
+	testv1PV.Spec.CSI.VolumeAttributes = map[string]string{
+		"performanceLevel": "PL2",
+		"type":             "cloud_essd",
+	}
+	testv1PV.Spec.CSI.VolumeHandle = "testPV"
+	testv1PV.Spec.StorageClassName = "testStorageClass"
+	cases := []struct {
+		name             string
+		testPV           *v1.PersistentVolume
+		expectedSlimDisk *SlimK8sDisk
+		inpRegionID      string
+	}{
+		{
+			name:   "test a generic *v1.Node to *SlimK8sNode Conversion",
+			testPV: testv1PV,
+			expectedSlimDisk: &SlimK8sDisk{
+				DiskType:         ALIBABA_DATA_DISK_CATEGORY,
+				RegionID:         "us-east-1",
+				PriceUnit:        ALIBABA_HOUR_PRICE_UNIT,
+				SizeInGiB:        "16",
+				DiskCategory:     "cloud_essd",
+				PerformanceLevel: "PL2",
+				ProviderID:       "testPV",
+				StorageClass:     "testStorageClass",
+			},
+			inpRegionID: "us-east-1",
+		},
+	}
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			returnSlimK8sDisk := generateSlimK8sDiskFromV1PV(c.testPV, c.inpRegionID)
+			if returnSlimK8sDisk.DiskType != c.expectedSlimDisk.DiskType {
+				t.Fatalf("unexpected conversion in function generateSlimK8sDiskFromV1PV expected DiskType: %s , recieved DiskType: %s", c.expectedSlimDisk.DiskType, returnSlimK8sDisk.DiskType)
+			}
+			if returnSlimK8sDisk.RegionID != c.expectedSlimDisk.RegionID {
+				t.Fatalf("unexpected conversion in function generateSlimK8sDiskFromV1PV expected RegionID: %s , recieved RegionID Type: %s", c.expectedSlimDisk.RegionID, returnSlimK8sDisk.RegionID)
+			}
+			if returnSlimK8sDisk.PriceUnit != c.expectedSlimDisk.PriceUnit {
+				t.Fatalf("unexpected conversion in function generateSlimK8sDiskFromV1PV expected PriceUnit: %s , recieved PriceUnit Type: %s", c.expectedSlimDisk.PriceUnit, returnSlimK8sDisk.PriceUnit)
+			}
+			if returnSlimK8sDisk.SizeInGiB != c.expectedSlimDisk.SizeInGiB {
+				t.Fatalf("unexpected conversion in function generateSlimK8sDiskFromV1PV expected SizeInGiB: %s , recieved SizeInGiB Type: %s", c.expectedSlimDisk.SizeInGiB, returnSlimK8sDisk.SizeInGiB)
+			}
+			if returnSlimK8sDisk.DiskCategory != c.expectedSlimDisk.DiskCategory {
+				t.Fatalf("unexpected conversion in function generateSlimK8sDiskFromV1PV expected DiskCategory: %s , recieved DiskCategory Type: %s", c.expectedSlimDisk.DiskCategory, returnSlimK8sDisk.DiskCategory)
+			}
+			if returnSlimK8sDisk.PerformanceLevel != c.expectedSlimDisk.PerformanceLevel {
+				t.Fatalf("unexpected conversion in function generateSlimK8sDiskFromV1PV expected PerformanceLevel: %s , recieved PerformanceLevel Type: %s", c.expectedSlimDisk.PerformanceLevel, returnSlimK8sDisk.PerformanceLevel)
+			}
+			if returnSlimK8sDisk.ProviderID != c.expectedSlimDisk.ProviderID {
+				t.Fatalf("unexpected conversion in function generateSlimK8sDiskFromV1PV expected ProviderID: %s , recieved ProviderID Type: %s", c.expectedSlimDisk.ProviderID, returnSlimK8sDisk.ProviderID)
+			}
+			if returnSlimK8sDisk.StorageClass != c.expectedSlimDisk.StorageClass {
+				t.Fatalf("unexpected conversion in function generateSlimK8sDiskFromV1PV expected StorageClass: %s , recieved StorageClass Type: %s", c.expectedSlimDisk.StorageClass, returnSlimK8sDisk.StorageClass)
+			}
+		})
+	}
+}
+
+func TestGetNumericalValueFromResourceQuantity(t *testing.T) {
+	cases := []struct {
+		name                 string
+		inputResourceQuanity string
+		expectedValue        string
+	}{
+		{
+			name:                 "positive scenario: when inputResourceQuantity is 10Gi",
+			inputResourceQuanity: "10Gi",
+			expectedValue:        "10",
+		},
+		{
+			name:                 "negative scenario: when inputResourceQuantity is Gi",
+			inputResourceQuanity: "Gi",
+			expectedValue:        ALIBABA_DEFAULT_DATADISK_SIZE,
+		},
+		{
+			name:                 "negative scenario: when inputResourceQuantity is 10",
+			inputResourceQuanity: "10",
+			expectedValue:        ALIBABA_DEFAULT_DATADISK_SIZE,
+		},
+		{
+			name:                 "negative scenario: when inputResourceQuantity is empty string",
+			inputResourceQuanity: "",
+			expectedValue:        ALIBABA_DEFAULT_DATADISK_SIZE,
+		},
+	}
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			returnValue := getNumericalValueFromResourceQuantity(c.inputResourceQuanity)
+			if c.expectedValue != returnValue {
+				t.Fatalf("Case name %s: getNumericalValueFromResourceQuantity recieved %s but expected %s", c.name, returnValue, c.expectedValue)
+			}
+		})
+	}
+}
+
+func TestDeterminePVRegion(t *testing.T) {
+	genericNodeAffinityTestStruct := v1.NodeSelectorTerm{
+		MatchExpressions: []v1.NodeSelectorRequirement{
+			{
+				Key:      "topology.diskplugin.csi.alibabacloud.com/zone",
+				Operator: v1.NodeSelectorOpIn,
+				Values:   []string{"us-east-1a"},
+			},
+		},
+		MatchFields: []v1.NodeSelectorRequirement{},
+	}
+
+	// testPV1 contains the Label with region information as well as node affinity in spec
+	testPV1 := &v1.PersistentVolume{}
+	testPV1.Name = "testPV1"
+	testPV1.Labels = make(map[string]string)
+	testPV1.Labels[ALIBABA_DISK_TOPOLOGY_REGION_LABEL] = "us-east-1"
+	testPV1.Spec.NodeAffinity = &v1.VolumeNodeAffinity{
+		Required: &v1.NodeSelector{
+			NodeSelectorTerms: []v1.NodeSelectorTerm{genericNodeAffinityTestStruct},
+		},
+	}
+
+	// testPV2 contains the only zone label
+	testPV2 := &v1.PersistentVolume{}
+	testPV2.Name = "testPV2"
+	testPV2.Labels = make(map[string]string)
+	testPV2.Labels[ALIBABA_DISK_TOPOLOGY_ZONE_LABEL] = "us-east-1a"
+
+	// testPV3 contains only node affinity in spec
+	testPV3 := &v1.PersistentVolume{}
+	testPV3.Name = "testPV3"
+	testPV3.Spec.NodeAffinity = &v1.VolumeNodeAffinity{
+		Required: &v1.NodeSelector{
+			NodeSelectorTerms: []v1.NodeSelectorTerm{genericNodeAffinityTestStruct},
+		},
+	}
+
+	// testPV4 contains no label/annotation or any node affinity
+	testPV4 := &v1.PersistentVolume{}
+	testPV4.Name = "testPV4"
+
+	cases := []struct {
+		name           string
+		inputPV        *v1.PersistentVolume
+		expectedRegion string
+	}{
+		{
+			name:           "When Region label topology.diskplugin.csi.alibabacloud.com/region is present along with node affinity details",
+			inputPV:        testPV1,
+			expectedRegion: "us-east-1",
+		},
+		{
+			name:           "When zone label topology.diskplugin.csi.alibabacloud.com/zone is present function has to determine region",
+			inputPV:        testPV2,
+			expectedRegion: "us-east-1",
+		},
+		{
+			name:           "When only node affinity detail is present function has to determine the region",
+			inputPV:        testPV3,
+			expectedRegion: "us-east-1",
+		},
+		{
+			name:           "When no region/zone information is present function returns empty to default to cluster region",
+			inputPV:        testPV4,
+			expectedRegion: "",
+		},
+	}
+	for _, c := range cases {
+		t.Run(c.name, func(t *testing.T) {
+			returnRegion := determinePVRegion(c.inputPV)
+			if c.expectedRegion != returnRegion {
+				t.Fatalf("Case name %s: determinePVRegion recieved region :%s but expected region: %s", c.name, returnRegion, c.expectedRegion)
+			}
+		})
+	}
+
+}

+ 2 - 2
pkg/cloud/awsprovider.go

@@ -7,8 +7,8 @@ import (
 	"encoding/csv"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
+	"os"
 	"regexp"
 	"strconv"
 	"strings"
@@ -1422,7 +1422,7 @@ func (aws *AWS) loadAWSAuthSecret(force bool) (*AWSAccessKey, error) {
 		return nil, fmt.Errorf("Failed to locate service account file: %s", authSecretPath)
 	}
 
-	result, err := ioutil.ReadFile(authSecretPath)
+	result, err := os.ReadFile(authSecretPath)
 	if err != nil {
 		return nil, err
 	}

+ 4 - 4
pkg/cloud/azureprovider.go

@@ -4,9 +4,9 @@ import (
 	"context"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net/http"
 	"net/url"
+	"os"
 	"regexp"
 	"strconv"
 	"strings"
@@ -275,7 +275,7 @@ func getRetailPrice(region string, skuName string, currencyCode string, spot boo
 
 	pricingPayload := AzureRetailPricing{}
 
-	body, err := ioutil.ReadAll(resp.Body)
+	body, err := io.ReadAll(resp.Body)
 	if err != nil {
 		return "", fmt.Errorf("Error getting response: %v", err)
 	}
@@ -617,7 +617,7 @@ func (az *Azure) loadAzureAuthSecret(force bool) (*AzureServiceKey, error) {
 		return nil, fmt.Errorf("Failed to locate service account file: %s", authSecretPath)
 	}
 
-	result, err := ioutil.ReadFile(authSecretPath)
+	result, err := os.ReadFile(authSecretPath)
 	if err != nil {
 		return nil, err
 	}
@@ -646,7 +646,7 @@ func (az *Azure) loadAzureStorageConfig(force bool) (*AzureStorageConfig, error)
 		return nil, fmt.Errorf("Failed to locate azure storage config file: %s", storageConfigSecretPath)
 	}
 
-	result, err := ioutil.ReadFile(storageConfigSecretPath)
+	result, err := os.ReadFile(storageConfigSecretPath)
 	if err != nil {
 		return nil, err
 	}

+ 4 - 4
pkg/cloud/gcpprovider.go

@@ -4,9 +4,9 @@ import (
 	"context"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"math"
 	"net/http"
+	"os"
 	"regexp"
 	"strconv"
 	"strings"
@@ -209,13 +209,13 @@ func (*GCP) loadGCPAuthSecret() {
 		return
 	}
 
-	result, err := ioutil.ReadFile(authSecretPath)
+	result, err := os.ReadFile(authSecretPath)
 	if err != nil {
 		log.Warnf("Failed to load auth secret, or was not mounted: %s", err.Error())
 		return
 	}
 
-	err = ioutil.WriteFile(keyPath, result, 0644)
+	err = os.WriteFile(keyPath, result, 0644)
 	if err != nil {
 		log.Warnf("Failed to copy auth secret to %s: %s", keyPath, err.Error())
 	}
@@ -246,7 +246,7 @@ func (gcp *GCP) UpdateConfig(r io.Reader, updateType string) (*CustomPricing, er
 				path := env.GetConfigPathWithDefault("/models/")
 
 				keyPath := path + "key.json"
-				err = ioutil.WriteFile(keyPath, j, 0644)
+				err = os.WriteFile(keyPath, j, 0644)
 				if err != nil {
 					return err
 				}

+ 14 - 0
pkg/cloud/provider.go

@@ -161,6 +161,8 @@ type CustomPricing struct {
 	GpuLabelValue                string `json:"gpuLabelValue,omitempty"`
 	ServiceKeyName               string `json:"awsServiceKeyName,omitempty"`
 	ServiceKeySecret             string `json:"awsServiceKeySecret,omitempty"`
+	AlibabaServiceKeyName        string `json:"alibabaServiceKeyName,omitempty"`
+	AlibabaServiceKeySecret      string `json:"alibabaServiceKeySecret,omitempty"`
 	SpotDataRegion               string `json:"awsSpotDataRegion,omitempty"`
 	SpotDataBucket               string `json:"awsSpotDataBucket,omitempty"`
 	SpotDataPrefix               string `json:"awsSpotDataPrefix,omitempty"`
@@ -488,6 +490,15 @@ func NewProvider(cache clustercache.ClusterCache, apiKey string, config *config.
 			clusterAccountId:     cp.accountID,
 			serviceAccountChecks: NewServiceAccountChecks(),
 		}, nil
+	case kubecost.AlibabaProvider:
+		log.Info("Found ProviderID starting with \"alibaba\", using Alibaba Cloud Provider")
+		return &Alibaba{
+			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{
@@ -536,6 +547,9 @@ 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
+		cp.provider = kubecost.AlibabaProvider
+		cp.configFileName = "alibaba.json"
 	}
 	if env.IsUseCSVProvider() {
 		cp.provider = kubecost.CSVProvider

+ 2 - 2
pkg/config/configmanager.go

@@ -1,7 +1,7 @@
 package config
 
 import (
-	"io/ioutil"
+	"os"
 	"sync"
 
 	"github.com/opencost/opencost/pkg/log"
@@ -59,7 +59,7 @@ func NewConfigFileManager(opts *ConfigFileManagerOpts) *ConfigFileManager {
 
 	var configStore storage.Storage
 	if opts.IsBucketStorageEnabled() {
-		bucketConfig, err := ioutil.ReadFile(opts.BucketStoreConfig)
+		bucketConfig, err := os.ReadFile(opts.BucketStoreConfig)
 		if err != nil {
 			log.Warnf("Failed to initialize config bucket storage: %s", err)
 		} else {

+ 4 - 3
pkg/costmodel/router.go

@@ -4,8 +4,9 @@ import (
 	"context"
 	"encoding/base64"
 	"fmt"
-	"io/ioutil"
+	"io"
 	"net/http"
+	"os"
 	"path"
 	"reflect"
 	"regexp"
@@ -1251,7 +1252,7 @@ func logsFor(c kubernetes.Interface, namespace string, pod string, container str
 		return "", err
 	}
 
-	podLogs, err := ioutil.ReadAll(reader)
+	podLogs, err := io.ReadAll(reader)
 	if err != nil {
 		return "", err
 	}
@@ -1354,7 +1355,7 @@ func (a *Accesses) AddServiceKey(w http.ResponseWriter, r *http.Request, ps http
 
 	key := r.PostForm.Get("key")
 	k := []byte(key)
-	err := ioutil.WriteFile(path.Join(env.GetConfigPathWithDefault("/var/configs/"), "key.json"), k, 0644)
+	err := os.WriteFile(path.Join(env.GetConfigPathWithDefault("/var/configs/"), "key.json"), k, 0644)
 	if err != nil {
 		fmt.Fprintf(w, "Error writing service key: "+err.Error())
 	}

+ 15 - 0
pkg/env/costmodelenv.go

@@ -17,6 +17,9 @@ const (
 	AWSClusterIDEnvVar       = "AWS_CLUSTER_ID"
 	AWSPricingURL            = "AWS_PRICING_URL"
 
+	AlibabaAccessKeyIDEnvVar     = "ALIBABA_ACCESS_KEY_ID"
+	AlibabaAccessKeySecretEnvVar = "ALIBABA_SECRET_ACCESS_KEY"
+
 	KubecostNamespaceEnvVar        = "KUBECOST_NAMESPACE"
 	PodNameEnvVar                  = "POD_NAME"
 	ClusterIDEnvVar                = "CLUSTER_ID"
@@ -212,6 +215,18 @@ func GetAWSPricingURL() string {
 	return Get(AWSPricingURL, "")
 }
 
+// GetAlibabaAccessKeyID returns the environment variable value for AlibabaAccessKeyIDEnvVar which represents
+// the Alibaba access key for authentication
+func GetAlibabaAccessKeyID() string {
+	return Get(AlibabaAccessKeyIDEnvVar, "")
+}
+
+// GetAlibabaAccessKeySecret returns the environment variable value for AlibabaAccessKeySecretEnvVar which represents
+// the Alibaba access key secret for authentication
+func GetAlibabaAccessKeySecret() string {
+	return Get(AlibabaAccessKeySecretEnvVar, "")
+}
+
 // 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"
 
+// AlibabaProvider describes the provider for Alibaba Cloud
+const AlibabaProvider = "Alibaba"
+
 // CSVProvider describes the provider a CSV
 const CSVProvider = "CSV"
 

+ 2 - 3
pkg/metrics/metricsconfig.go

@@ -3,7 +3,6 @@ package metrics
 import (
 	"encoding/json"
 	"fmt"
-	"io/ioutil"
 	"os"
 	"path"
 	"sync"
@@ -37,7 +36,7 @@ func GetMetricsConfig() (*MetricsConfig, error) {
 	metricsConfigLock.Lock()
 	defer metricsConfigLock.Unlock()
 	mc := &MetricsConfig{}
-	body, err := ioutil.ReadFile(metricsFilePath)
+	body, err := os.ReadFile(metricsFilePath)
 	if os.IsNotExist(err) {
 
 		return mc, nil
@@ -63,7 +62,7 @@ func UpdateMetricsConfig(mc *MetricsConfig) (*MetricsConfig, error) {
 		return nil, fmt.Errorf("error encoding metrics config struct: %s", err)
 	}
 
-	err = ioutil.WriteFile(metricsFilePath, mcb, 0644)
+	err = os.WriteFile(metricsFilePath, mcb, 0644)
 	if err != nil {
 		return nil, fmt.Errorf("error writing to metrics config file: %s", err)
 	}

+ 3 - 3
pkg/services/clusters/clustermanager.go

@@ -3,7 +3,7 @@ package clusters
 import (
 	"encoding/base64"
 	"fmt"
-	"io/ioutil"
+	"os"
 	"strings"
 
 	"github.com/google/uuid"
@@ -97,7 +97,7 @@ func NewConfiguredClusterManager(storage ClusterStorage, config string) *Cluster
 		return clusterManager
 	}
 
-	data, err := ioutil.ReadFile(config)
+	data, err := os.ReadFile(config)
 	if err != nil {
 		return clusterManager
 	}
@@ -223,7 +223,7 @@ func fromSecret(secretName string) (string, error) {
 		return "", fmt.Errorf("Failed to locate secret: %s", file)
 	}
 
-	data, err := ioutil.ReadFile(file)
+	data, err := os.ReadFile(file)
 	if err != nil {
 		return "", fmt.Errorf("Failed to load secret: %s", file)
 	}

+ 2 - 2
pkg/services/clusters/clustersendpoints.go

@@ -2,7 +2,7 @@ package clusters
 
 import (
 	"errors"
-	"io/ioutil"
+	"io"
 	"net/http"
 
 	"github.com/julienschmidt/httprouter"
@@ -50,7 +50,7 @@ func (cme *ClusterManagerHTTPService) GetAllClusters(w http.ResponseWriter, r *h
 func (cme *ClusterManagerHTTPService) PutCluster(w http.ResponseWriter, r *http.Request, ps httprouter.Params) {
 	w.Header().Set("Content-Type", "application/json")
 
-	data, err := ioutil.ReadAll(r.Body)
+	data, err := io.ReadAll(r.Body)
 	if err != nil {
 		w.Write(wrapData(nil, err))
 		return

+ 1 - 2
pkg/storage/azurestorage.go

@@ -9,7 +9,6 @@ import (
 	"context"
 	"fmt"
 	"io"
-	"io/ioutil"
 	"net"
 	"net/http"
 	"net/url"
@@ -563,7 +562,7 @@ func (b *AzureStorage) getBlobReader(ctx context.Context, name string, offset, l
 		return nil, errors.Wrapf(err, "cannot download blob, address: %s", blobURL.BlobURL)
 	}
 
-	return ioutil.NopCloser(bytes.NewReader(destBuffer)), nil
+	return io.NopCloser(bytes.NewReader(destBuffer)), nil
 }
 
 func getAzureStorageCredentials(conf AzureConfig) (blob.Credential, error) {

+ 20 - 5
pkg/storage/filestorage.go

@@ -2,7 +2,6 @@ package storage
 
 import (
 	gofs "io/fs"
-	"io/ioutil"
 	"os"
 	gopath "path"
 	"path/filepath"
@@ -52,10 +51,18 @@ func (fs *FileStorage) List(path string) ([]*StorageInfo, error) {
 	p := gopath.Join(fs.baseDir, path)
 
 	// Read files in the backup path
-	files, err := ioutil.ReadDir(p)
+	entries, err := os.ReadDir(p)
 	if err != nil {
 		return nil, err
 	}
+	files := make([]gofs.FileInfo, 0, len(entries))
+	for _, entry := range entries {
+		info, err := entry.Info()
+		if err != nil {
+			return nil, err
+		}
+		files = append(files, info)
+	}
 
 	return FilesToStorageInfo(files), nil
 }
@@ -64,10 +71,18 @@ func (fs *FileStorage) ListDirectories(path string) ([]*StorageInfo, error) {
 	p := gopath.Join(fs.baseDir, path)
 
 	// Read files in the backup path
-	files, err := ioutil.ReadDir(p)
+	entries, err := os.ReadDir(p)
 	if err != nil {
 		return nil, err
 	}
+	files := make([]gofs.FileInfo, 0, len(entries))
+	for _, entry := range entries {
+		info, err := entry.Info()
+		if err != nil {
+			return nil, err
+		}
+		files = append(files, info)
+	}
 
 	return DirFilesToStorageInfo(files, path), nil
 }
@@ -77,7 +92,7 @@ func (fs *FileStorage) ListDirectories(path string) ([]*StorageInfo, error) {
 func (fs *FileStorage) Read(path string) ([]byte, error) {
 	f := gopath.Join(fs.baseDir, path)
 
-	b, err := ioutil.ReadFile(f)
+	b, err := os.ReadFile(f)
 	if err != nil {
 		if os.IsNotExist(err) {
 			return nil, DoesNotExistError
@@ -95,7 +110,7 @@ func (fs *FileStorage) Write(path string, data []byte) error {
 	if err != nil {
 		return errors.Wrap(err, "Failed to prepare path")
 	}
-	err = ioutil.WriteFile(f, data, os.ModePerm)
+	err = os.WriteFile(f, data, os.ModePerm)
 	if err != nil {
 		return errors.Wrap(err, "Failed to write file")
 	}

+ 4 - 3
pkg/storage/s3storage.go

@@ -7,9 +7,10 @@ package storage
 import (
 	"bytes"
 	"context"
-	"io/ioutil"
+	"io"
 	"net"
 	"net/http"
+	"os"
 	"strings"
 	"time"
 
@@ -264,7 +265,7 @@ func NewS3StorageWith(config S3Config) (*S3Storage, error) {
 			}
 
 		case SSEC:
-			key, err := ioutil.ReadFile(config.SSEConfig.EncryptionKey)
+			key, err := os.ReadFile(config.SSEConfig.EncryptionKey)
 			if err != nil {
 				return nil, err
 			}
@@ -582,7 +583,7 @@ func (s3 *S3Storage) getRange(ctx context.Context, name string, off, length int6
 		return nil, errors.Wrap(err, "Read from S3 failed")
 	}
 
-	return ioutil.ReadAll(r)
+	return io.ReadAll(r)
 }
 
 // awsAuth retrieves credentials from the aws-sdk-go.

+ 2 - 2
pkg/storage/tlsconfig.go

@@ -4,7 +4,7 @@ import (
 	"crypto/tls"
 	"crypto/x509"
 	"fmt"
-	"io/ioutil"
+	"os"
 )
 
 // NewTLSConfig creates a new tls.Config from the given TLSConfig.
@@ -43,7 +43,7 @@ func NewTLSConfig(cfg *TLSConfig) (*tls.Config, error) {
 
 // readCAFile reads the CA cert file from disk.
 func readCAFile(f string) ([]byte, error) {
-	data, err := ioutil.ReadFile(f)
+	data, err := os.ReadFile(f)
 	if err != nil {
 		return nil, fmt.Errorf("unable to load specified CA cert %s: %s", f, err)
 	}

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