2
0

provider.go 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470
  1. package alibaba
  2. import (
  3. "errors"
  4. "fmt"
  5. "io"
  6. "math"
  7. "os"
  8. "regexp"
  9. "strconv"
  10. "strings"
  11. "sync"
  12. "github.com/aliyun/alibaba-cloud-sdk-go/sdk"
  13. "github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
  14. "github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers"
  15. "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests"
  16. "github.com/opencost/opencost/core/pkg/clustercache"
  17. coreenv "github.com/opencost/opencost/core/pkg/env"
  18. "github.com/opencost/opencost/core/pkg/log"
  19. "github.com/opencost/opencost/core/pkg/opencost"
  20. "github.com/opencost/opencost/core/pkg/util/fileutil"
  21. "github.com/opencost/opencost/core/pkg/util/json"
  22. "github.com/opencost/opencost/core/pkg/util/stringutil"
  23. "github.com/opencost/opencost/pkg/cloud/models"
  24. "github.com/opencost/opencost/pkg/cloud/utils"
  25. "github.com/opencost/opencost/pkg/env"
  26. "golang.org/x/exp/slices"
  27. )
  28. const (
  29. ALIBABA_ECS_PRODUCT_CODE = "ecs"
  30. ALIBABA_ECS_VERSION = "2014-05-26"
  31. ALIBABA_ECS_DOMAIN = "ecs.aliyuncs.com"
  32. ALIBABA_DESCRIBE_PRICE_API_ACTION = "DescribePrice"
  33. ALIBABA_DESCRIBE_DISK_API_ACTION = "DescribeDisks"
  34. ALIBABA_INSTANCE_RESOURCE_TYPE = "instance"
  35. ALIBABA_DISK_RESOURCE_TYPE = "disk"
  36. ALIBABA_PAY_AS_YOU_GO_BILLING = "Pay-As-You-Go"
  37. ALIBABA_SUBSCRIPTION_BILLING = "Subscription"
  38. ALIBABA_PREEMPTIBLE_BILLING = "Preemptible"
  39. ALIBABA_OPTIMIZE_KEYWORD = "optimize"
  40. ALIBABA_NON_OPTIMIZE_KEYWORD = "nonoptimize"
  41. ALIBABA_HOUR_PRICE_UNIT = "Hour"
  42. ALIBABA_MONTH_PRICE_UNIT = "Month"
  43. ALIBABA_YEAR_PRICE_UNIT = "Year"
  44. ALIBABA_UNKNOWN_INSTANCE_FAMILY_TYPE = "unknown"
  45. ALIBABA_NOT_SUPPORTED_INSTANCE_FAMILY_TYPE = "unsupported"
  46. ALIBABA_DISK_CLOUD_ESSD_CATEGORY = "cloud_essd"
  47. ALIBABA_DISK_CLOUD_CATEGORY = "cloud"
  48. ALIBABA_DATA_DISK_CATEGORY = "data"
  49. ALIBABA_SYSTEM_DISK_CATEGORY = "system"
  50. ALIBABA_DATA_DISK_PREFIX = "DataDisk"
  51. ALIBABA_PV_CLOUD_DISK_TYPE = "CloudDisk"
  52. ALIBABA_PV_NAS_TYPE = "NAS"
  53. ALIBABA_PV_OSS_TYPE = "OSS"
  54. ALIBABA_DEFAULT_DATADISK_SIZE = "2000"
  55. ALIBABA_DISK_TOPOLOGY_REGION_LABEL = "topology.diskplugin.csi.alibabacloud.com/region"
  56. ALIBABA_DISK_TOPOLOGY_ZONE_LABEL = "topology.diskplugin.csi.alibabacloud.com/zone"
  57. )
  58. var (
  59. // sizeRegEx parses a PV capacity string into a numeric part and an optional binary SI suffix (Ki, Mi, Gi, Ti).
  60. sizeRegEx = regexp.MustCompile(`^(\d+(?:\.\d+)?)(Ki|Mi|Gi|Ti)?$`)
  61. // generationRegEx extracts the numeric generation from an instance family name (e.g. 7 from "g7ne").
  62. generationRegEx = regexp.MustCompile(`(\d+)`)
  63. )
  64. // Variable to keep track of instance families that fail in DescribePrice API due improper defaulting of systemDisk if the information is not available
  65. var alibabaDefaultToCloudEssd = []string{"g6e", "r6e"}
  66. var alibabaRegions = []string{
  67. "cn-qingdao",
  68. "cn-beijing",
  69. "cn-zhangjiakou",
  70. "cn-huhehaote",
  71. "cn-wulanchabu",
  72. "cn-hangzhou",
  73. "cn-shanghai",
  74. "cn-nanjing",
  75. "cn-shenzhen",
  76. "cn-heyuan",
  77. "cn-guangzhou",
  78. "cn-fuzhou",
  79. "cn-wuhan-lr",
  80. "cn-chengdu",
  81. "cn-hongkong",
  82. "ap-northeast-1",
  83. "ap-northeast-2",
  84. "ap-southeast-1",
  85. "ap-southeast-2",
  86. "ap-southeast-3",
  87. "ap-southeast-6",
  88. "ap-southeast-5",
  89. "ap-south-1",
  90. "ap-southeast-7",
  91. "us-east-1",
  92. "us-west-1",
  93. "eu-west-1",
  94. "me-east-1",
  95. "me-central-1",
  96. "eu-central-1",
  97. }
  98. // AlibabaInfo contains configuration for Alibaba's CUR integration
  99. // Deprecated: v1.104 Use BOAConfiguration instead
  100. type AlibabaInfo struct {
  101. AlibabaClusterRegion string `json:"ClusterRegion"`
  102. AlibabaServiceKeyName string `json:"serviceKeyName"`
  103. AlibabaServiceKeySecret string `json:"serviceKeySecret"`
  104. AlibabaAccountID string `json:"accountID"`
  105. }
  106. // IsEmpty returns true if all fields in config are empty, false if not.
  107. func (ai *AlibabaInfo) IsEmpty() bool {
  108. return ai.AlibabaClusterRegion == "" &&
  109. ai.AlibabaServiceKeyName == "" &&
  110. ai.AlibabaServiceKeySecret == "" &&
  111. ai.AlibabaAccountID == ""
  112. }
  113. // AlibabaAccessKey holds Alibaba credentials parsing from the service-key.json file.
  114. // Deprecated: v1.104 Use AccessKey instead
  115. type AlibabaAccessKey struct {
  116. AccessKeyID string `json:"alibaba_access_key_id"`
  117. SecretAccessKey string `json:"alibaba_secret_access_key"`
  118. }
  119. // Slim Version of k8s disk assigned to a node or PV.
  120. type SlimK8sDisk struct {
  121. DiskType string
  122. RegionID string
  123. PriceUnit string
  124. SizeInGiB string
  125. DiskCategory string
  126. PerformanceLevel string
  127. ProviderID string
  128. StorageClass string
  129. }
  130. func NewSlimK8sDisk(diskType, regionID, priceUnit, diskCategory, performanceLevel, providerID, storageClass, sizeInGiB string) *SlimK8sDisk {
  131. return &SlimK8sDisk{
  132. DiskType: diskType,
  133. RegionID: regionID,
  134. PriceUnit: priceUnit,
  135. SizeInGiB: sizeInGiB,
  136. DiskCategory: diskCategory,
  137. PerformanceLevel: performanceLevel,
  138. ProviderID: providerID,
  139. StorageClass: storageClass,
  140. }
  141. }
  142. // 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.
  143. type SlimK8sNode struct {
  144. InstanceType string
  145. RegionID string
  146. PriceUnit string
  147. MemorySizeInKiB string // TO-DO : Possible to convert to float?
  148. IsIoOptimized bool
  149. OSType string
  150. ProviderID string
  151. SystemDisk *SlimK8sDisk
  152. InstanceTypeFamily string // Bug in DescribePrice, doesn't default to enhanced type correctly and you get an error in DescribePrice to get around need the family of the InstanceType.
  153. }
  154. func NewSlimK8sNode(instanceType, regionID, priceUnit, memorySizeInKiB, osType, providerID, instanceTypeFamily string, isIOOptimized bool, systemDiskInfo *SlimK8sDisk) *SlimK8sNode {
  155. return &SlimK8sNode{
  156. InstanceType: instanceType,
  157. RegionID: regionID,
  158. PriceUnit: priceUnit,
  159. MemorySizeInKiB: memorySizeInKiB,
  160. IsIoOptimized: isIOOptimized,
  161. OSType: osType,
  162. SystemDisk: systemDiskInfo,
  163. ProviderID: providerID,
  164. InstanceTypeFamily: instanceTypeFamily,
  165. }
  166. }
  167. // AlibabaNodeAttributes represents metadata about the Node in its pricing information.
  168. // Basic Attributes needed atleast to get the key, Some attributes from k8s Node response
  169. // be populated directly into *Node object.
  170. type AlibabaNodeAttributes struct {
  171. // InstanceType represents the type of instance.
  172. InstanceType string `json:"instanceType"`
  173. // MemorySizeInKiB represents the size of memory of instance.
  174. MemorySizeInKiB string `json:"memorySizeInKiB"`
  175. // IsIoOptimized represents the if instance is I/O optimized.
  176. IsIoOptimized bool `json:"isIoOptimized"`
  177. // OSType represents the OS installed in the Instance.
  178. OSType string `json:"osType"`
  179. // SystemDiskCategory represents the exact category of the system disk attached to the node.
  180. SystemDiskCategory string `json:"systemDiskCategory"`
  181. // SystemDiskSizeInGiB represents the size of the system disk attached to the node.
  182. SystemDiskSizeInGiB string `json:"systemDiskSizeInGiB"`
  183. // SystemDiskPerformanceLevel represents the performance level of the system disk attached to the node.
  184. SystemDiskPerformanceLevel string `json:"systemPerformanceLevel"`
  185. }
  186. func NewAlibabaNodeAttributes(node *SlimK8sNode) *AlibabaNodeAttributes {
  187. if node == nil {
  188. return nil
  189. }
  190. var diskCategory, sizeInGiB, performanceLevel string
  191. if node.SystemDisk != nil {
  192. diskCategory = node.SystemDisk.DiskCategory
  193. sizeInGiB = node.SystemDisk.SizeInGiB
  194. performanceLevel = node.SystemDisk.PerformanceLevel
  195. }
  196. return &AlibabaNodeAttributes{
  197. InstanceType: node.InstanceType,
  198. MemorySizeInKiB: node.MemorySizeInKiB,
  199. IsIoOptimized: node.IsIoOptimized,
  200. OSType: node.OSType,
  201. SystemDiskCategory: diskCategory,
  202. SystemDiskSizeInGiB: sizeInGiB,
  203. SystemDiskPerformanceLevel: performanceLevel,
  204. }
  205. }
  206. // AlibabaPVAttributes represents metadata the PV in its pricing information.
  207. // Basic Attributes needed atleast to get the keys. Some attributes from k8s PV response
  208. // be populated directly into *PV object.
  209. type AlibabaPVAttributes struct {
  210. // PVType can be Cloud Disk, NetWork Attached Storage(NAS) or Object Storage Service (OSS).
  211. // Represents the way the PV was attached
  212. PVType string `json:"pvType"`
  213. // PVSubType represent the sub category of PVType. This is Data in case of Cloud Disk.
  214. PVSubType string `json:"pvSubType"`
  215. // Example for PVCategory with cloudDisk PVType are cloud, cloud_efficiency, cloud_ssd,
  216. // ephemeral_ssd and cloud_essd. If not present returns empty.
  217. PVCategory string `json:"pvCategory"`
  218. // Example for PerformanceLevel with cloudDisk PVType are PL0,PL1,PL2 &PL3. If not present returns empty.
  219. PVPerformanceLevel string `json:"performanceLevel"`
  220. // The Size of the PV in terms of GiB
  221. SizeInGiB string `json:"sizeInGiB"`
  222. }
  223. // TO-Do: next iteration of Alibaba provider support NetWork Attached Storage(NAS) and Object Storage Service (OSS type PVs).
  224. // Currently defaulting to cloudDisk with provision to add work in future.
  225. func NewAlibabaPVAttributes(disk *SlimK8sDisk) *AlibabaPVAttributes {
  226. if disk == nil {
  227. return nil
  228. }
  229. return &AlibabaPVAttributes{
  230. PVType: ALIBABA_PV_CLOUD_DISK_TYPE,
  231. PVSubType: disk.DiskType,
  232. PVCategory: disk.DiskCategory,
  233. PVPerformanceLevel: disk.PerformanceLevel,
  234. SizeInGiB: disk.SizeInGiB,
  235. }
  236. }
  237. // Stage 1 support will be Pay-As-You-Go with HourlyPrice equal to TradePrice with PriceUnit as Hour
  238. // TO-DO: Subscription and Premptible support, Information can be gathered from describing instance for subscription type
  239. // and spotprice can be gather from DescribeSpotPriceHistory API.
  240. // TO-DO: how would you calculate hourly price for subscription type, is it PRICE_YEARLY/HOURS_IN_THE_YEAR|MONTH?
  241. type AlibabaPricingDetails struct {
  242. // Represents hourly price for the given Alibaba cloud Product.
  243. HourlyPrice float32 `json:"hourlyPrice"`
  244. // Represents the unit in which Alibaba Product is billed can be Hour, Month or Year based on the billingMethod.
  245. PriceUnit string `json:"priceUnit"`
  246. // Original Price paid to acquire the Alibaba Product.
  247. TradePrice float32 `json:"tradePrice"`
  248. // Represents the currency unit of the price for billing Alibaba Product.
  249. CurrencyCode string `json:"currencyCode"`
  250. }
  251. func NewAlibabaPricingDetails(hourlyPrice float32, priceUnit string, tradePrice float32, currencyCode string) *AlibabaPricingDetails {
  252. return &AlibabaPricingDetails{
  253. HourlyPrice: hourlyPrice,
  254. PriceUnit: priceUnit,
  255. TradePrice: tradePrice,
  256. CurrencyCode: currencyCode,
  257. }
  258. }
  259. // AlibabaPricingTerms can have three types of supported billing method Pay-As-You-Go, Subscription and Premptible
  260. type AlibabaPricingTerms struct {
  261. BillingMethod string `json:"billingMethod"`
  262. PricingDetails *AlibabaPricingDetails `json:"pricingDetails"`
  263. }
  264. func NewAlibabaPricingTerms(billingMethod string, pricingDetails *AlibabaPricingDetails) *AlibabaPricingTerms {
  265. return &AlibabaPricingTerms{
  266. BillingMethod: billingMethod,
  267. PricingDetails: pricingDetails,
  268. }
  269. }
  270. // Alibaba Pricing struct carry the Attributes and pricing information for Node or PV
  271. type AlibabaPricing struct {
  272. NodeAttributes *AlibabaNodeAttributes
  273. PVAttributes *AlibabaPVAttributes
  274. PricingTerms *AlibabaPricingTerms
  275. Node *models.Node
  276. PV *models.PV
  277. }
  278. // Alibaba cloud's Provider struct
  279. type Alibaba struct {
  280. // Data to store Alibaba cloud's pricing struct, key in the map represents exact match to
  281. // node.features() or pv.features for easy lookup
  282. Pricing map[string]*AlibabaPricing
  283. // Lock Needed to provide thread safe
  284. DownloadPricingDataLock sync.RWMutex
  285. Clientset clustercache.ClusterCache
  286. Config models.ProviderConfig
  287. ServiceAccountChecks *models.ServiceAccountChecks
  288. ClusterAccountId string
  289. ClusterRegion string
  290. // The following fields are unexported because of avoiding any leak of secrets of these keys.
  291. // Alibaba Access key used specifically in signer interface used to sign API calls
  292. accessKey *credentials.AccessKeyCredential
  293. // Map of regionID to sdk.client to call API for that region
  294. clients map[string]*sdk.Client
  295. }
  296. // GetAlibabaAccessKey return the Access Key used to interact with the Alibaba cloud, if not set it
  297. // set it first by looking at env variables else load it from secret files.
  298. func (alibaba *Alibaba) GetAlibabaAccessKey() (*credentials.AccessKeyCredential, error) {
  299. if alibaba.accessKeyisLoaded() {
  300. return alibaba.accessKey, nil
  301. }
  302. config, err := alibaba.GetConfig()
  303. if err != nil {
  304. return nil, fmt.Errorf("error getting the default config for Alibaba Cloud provider: %w", err)
  305. }
  306. if config.AlibabaServiceKeyName == "" {
  307. config.AlibabaServiceKeyName = env.GetAlibabaAccessKeyID()
  308. }
  309. if config.AlibabaServiceKeySecret == "" {
  310. config.AlibabaServiceKeySecret = env.GetAlibabaAccessKeySecret()
  311. }
  312. if config.AlibabaServiceKeyName == "" && config.AlibabaServiceKeySecret == "" {
  313. log.Debugf("missing service key values for Alibaba cloud integration attempting to use service account integration")
  314. err := alibaba.loadAlibabaAuthSecretAndSetEnv(true)
  315. if err != nil {
  316. return nil, fmt.Errorf("unable to set the Alibaba Cloud key/secret from config file %w", err)
  317. }
  318. config.AlibabaServiceKeyName = env.GetAlibabaAccessKeyID()
  319. config.AlibabaServiceKeySecret = env.GetAlibabaAccessKeySecret()
  320. }
  321. if config.AlibabaServiceKeyName == "" && config.AlibabaServiceKeySecret == "" {
  322. return nil, fmt.Errorf("failed to get the access key for the current alibaba account")
  323. }
  324. // At this point either user is using the alibaba key and secret from secret passed in helm config if not he will use the secret that is passed in custom pricing
  325. // There's no check at this time for if the custom pricing key and secret is valid and that's on the user else there will be errors recorded.
  326. // Key and secret passed in config will supersede key and secret passed while installing Closed source helm chart.
  327. alibaba.accessKey = &credentials.AccessKeyCredential{AccessKeyId: config.AlibabaServiceKeyName, AccessKeySecret: config.AlibabaServiceKeySecret}
  328. return alibaba.accessKey, nil
  329. }
  330. func (alibaba *Alibaba) GetAlibabaCloudInfo() (*AlibabaInfo, error) {
  331. config, err := alibaba.GetConfig()
  332. if err != nil {
  333. return nil, fmt.Errorf("could not retrieve AlibabaCloudInfo %s", err)
  334. }
  335. aak, err := alibaba.GetAlibabaAccessKey()
  336. if err != nil {
  337. return nil, err
  338. }
  339. return &AlibabaInfo{
  340. AlibabaClusterRegion: config.AlibabaClusterRegion,
  341. AlibabaServiceKeyName: aak.AccessKeyId,
  342. AlibabaServiceKeySecret: aak.AccessKeySecret,
  343. AlibabaAccountID: config.ProjectID,
  344. }, nil
  345. }
  346. // DownloadPricingData satisfies the provider interface and downloads the prices for Node instances and PVs.
  347. func (alibaba *Alibaba) DownloadPricingData() error {
  348. alibaba.DownloadPricingDataLock.Lock()
  349. defer alibaba.DownloadPricingDataLock.Unlock()
  350. var aak *credentials.AccessKeyCredential
  351. var err error
  352. if !alibaba.accessKeyisLoaded() {
  353. aak, err = alibaba.GetAlibabaAccessKey()
  354. if err != nil {
  355. return fmt.Errorf("unable to get the access key information: %w", err)
  356. }
  357. } else {
  358. aak = alibaba.accessKey
  359. }
  360. c, err := alibaba.Config.GetCustomPricingData()
  361. if err != nil {
  362. return fmt.Errorf("error downloading default pricing data: %w", err)
  363. }
  364. // Get all the nodes from Alibaba cluster.
  365. nodeList := alibaba.Clientset.GetAllNodes()
  366. var client *sdk.Client
  367. var signer *signers.AccessKeySigner
  368. var ok bool
  369. var lookupKey string
  370. alibaba.clients = make(map[string]*sdk.Client)
  371. alibaba.Pricing = make(map[string]*AlibabaPricing)
  372. for _, node := range nodeList {
  373. pricingObj := &AlibabaPricing{}
  374. slimK8sNode := generateSlimK8sNodeFromV1Node(node)
  375. if client, ok = alibaba.clients[slimK8sNode.RegionID]; !ok {
  376. client, err = sdk.NewClientWithAccessKey(slimK8sNode.RegionID, aak.AccessKeyId, aak.AccessKeySecret)
  377. if err != nil {
  378. return fmt.Errorf("unable to initiate alibaba cloud sdk client for region %s : %w", slimK8sNode.RegionID, err)
  379. }
  380. alibaba.clients[slimK8sNode.RegionID] = client
  381. }
  382. signer = signers.NewAccessKeySigner(aak)
  383. // Adjust the system Disk information of a Node by retrieving the details of associated disk. If unable to retrieve set it to empty
  384. // system disk to pass through and use defaults with Alibaba pricing API.
  385. instanceID := getInstanceIDFromProviderID(slimK8sNode.ProviderID)
  386. slimK8sNode.SystemDisk = getSystemDiskInfoOfANode(instanceID, slimK8sNode.RegionID, client, signer)
  387. lookupKey, err = determineKeyForPricing(slimK8sNode)
  388. if err != nil {
  389. return fmt.Errorf("unable to determine key for pricing: %w", err)
  390. }
  391. if _, ok := alibaba.Pricing[lookupKey]; ok {
  392. log.Debugf("Pricing information for node with same features %s already exists hence skipping", lookupKey)
  393. continue
  394. }
  395. pricingObj, err = processDescribePriceAndCreateAlibabaPricing(client, slimK8sNode, signer, c)
  396. if err != nil {
  397. return fmt.Errorf("failed to create pricing information for node with type %s with error: %w", slimK8sNode.InstanceType, err)
  398. }
  399. alibaba.Pricing[lookupKey] = pricingObj
  400. }
  401. // set the first occurrence of region from the node
  402. if alibaba.ClusterRegion == "" {
  403. for _, node := range nodeList {
  404. if regionID, ok := node.Labels["topology.kubernetes.io/region"]; ok {
  405. alibaba.ClusterRegion = regionID
  406. break
  407. }
  408. }
  409. }
  410. // PV pricing for only Cloud Disk for now.
  411. // TO-DO: Support both NAS(Network Attached storage) and OSS(Object Storage Service) type PVs
  412. pvList := alibaba.Clientset.GetAllPersistentVolumes()
  413. for _, pv := range pvList {
  414. pvRegion := determinePVRegion(pv)
  415. if pvRegion == "" {
  416. pvRegion = alibaba.ClusterRegion
  417. }
  418. pricingObj := &AlibabaPricing{}
  419. slimK8sDisk := generateSlimK8sDiskFromV1PV(pv, pvRegion)
  420. lookupKey, err = determineKeyForPricing(slimK8sDisk)
  421. if err != nil {
  422. return fmt.Errorf("unable to determine key for pricing: %w", err)
  423. }
  424. if _, ok := alibaba.Pricing[lookupKey]; ok {
  425. log.Debugf("Pricing information for pv with same features %s already exists hence skipping", lookupKey)
  426. continue
  427. }
  428. if client, ok = alibaba.clients[slimK8sDisk.RegionID]; !ok {
  429. client, err = sdk.NewClientWithAccessKey(slimK8sDisk.RegionID, aak.AccessKeyId, aak.AccessKeySecret)
  430. if err != nil {
  431. return fmt.Errorf("unable to initiate alibaba cloud sdk client for region %s : %w", slimK8sDisk.RegionID, err)
  432. }
  433. alibaba.clients[slimK8sDisk.RegionID] = client
  434. }
  435. signer = signers.NewAccessKeySigner(aak)
  436. pricingObj, err = processDescribePriceAndCreateAlibabaPricing(client, slimK8sDisk, signer, c)
  437. if err != nil {
  438. return fmt.Errorf("failed to create pricing information for pv with category %s with error: %w", slimK8sDisk.DiskCategory, err)
  439. }
  440. alibaba.Pricing[lookupKey] = pricingObj
  441. }
  442. return nil
  443. }
  444. // AllNodePricing returns all the pricing data for all nodes and pvs
  445. func (alibaba *Alibaba) AllNodePricing() (interface{}, error) {
  446. alibaba.DownloadPricingDataLock.RLock()
  447. defer alibaba.DownloadPricingDataLock.RUnlock()
  448. return alibaba.Pricing, nil
  449. }
  450. // NodePricing gives pricing information of a specific node given by the key
  451. func (alibaba *Alibaba) NodePricing(key models.Key) (*models.Node, models.PricingMetadata, error) {
  452. alibaba.DownloadPricingDataLock.RLock()
  453. defer alibaba.DownloadPricingDataLock.RUnlock()
  454. // Get node features for the key
  455. keyFeature := key.Features()
  456. meta := models.PricingMetadata{}
  457. pricing, ok := alibaba.Pricing[keyFeature]
  458. if !ok {
  459. keys := make([]string, 0, len(alibaba.Pricing))
  460. for k := range alibaba.Pricing {
  461. keys = append(keys, k)
  462. }
  463. kf := key.(*AlibabaNodeKey)
  464. // Try to look up pricing with no disk attached
  465. pricing, ok = alibaba.Pricing[kf.FeaturesWithOtherDisk("")]
  466. if !ok {
  467. log.Errorf("Node pricing information not found for node with feature: %s . Existing keys are: %+v", keyFeature, keys)
  468. return nil, meta, fmt.Errorf("Node pricing information not found for node with feature: %s letting it use default values", keyFeature)
  469. }
  470. }
  471. log.Debugf("returning the node price for the node with feature: %s", keyFeature)
  472. returnNode := pricing.Node
  473. return returnNode, meta, nil
  474. }
  475. func (alibaba *Alibaba) GpuPricing(nodeLabels map[string]string) (string, error) {
  476. return "", nil
  477. }
  478. // PVPricing gives a pricing information of a specific PV given by PVkey
  479. func (alibaba *Alibaba) PVPricing(pvk models.PVKey) (*models.PV, error) {
  480. alibaba.DownloadPricingDataLock.RLock()
  481. defer alibaba.DownloadPricingDataLock.RUnlock()
  482. keyFeature := pvk.Features()
  483. pricing, ok := alibaba.Pricing[keyFeature]
  484. if !ok {
  485. log.Debugf("Persistent Volume pricing not found for PV with feature: %s", keyFeature)
  486. return nil, fmt.Errorf("Persistent Volume pricing not found for PV with feature: %s letting it use default values", keyFeature)
  487. }
  488. log.Debugf("returning the PV price for the node with feature: %s", keyFeature)
  489. return pricing.PV, nil
  490. }
  491. // Inter zone and Inter region network cost are defaulted based on https://www.alibabacloud.com/help/en/cloud-data-transmission/latest/cross-region-data-transfers
  492. // Internet cost is default based on https://www.alibabacloud.com/help/en/elastic-compute-service/latest/public-bandwidth to $0.123
  493. func (alibaba *Alibaba) NetworkPricing() (*models.Network, error) {
  494. cpricing, err := alibaba.Config.GetCustomPricingData()
  495. if err != nil {
  496. return nil, err
  497. }
  498. znec, err := strconv.ParseFloat(cpricing.ZoneNetworkEgress, 64)
  499. if err != nil {
  500. return nil, err
  501. }
  502. rnec, err := strconv.ParseFloat(cpricing.RegionNetworkEgress, 64)
  503. if err != nil {
  504. return nil, err
  505. }
  506. inec, err := strconv.ParseFloat(cpricing.InternetNetworkEgress, 64)
  507. if err != nil {
  508. return nil, err
  509. }
  510. nge, err := strconv.ParseFloat(cpricing.NatGatewayEgress, 64)
  511. if err != nil {
  512. return nil, err
  513. }
  514. ngi, err := strconv.ParseFloat(cpricing.NatGatewayIngress, 64)
  515. if err != nil {
  516. return nil, err
  517. }
  518. return &models.Network{
  519. ZoneNetworkEgressCost: znec,
  520. RegionNetworkEgressCost: rnec,
  521. InternetNetworkEgressCost: inec,
  522. NatGatewayEgressCost: nge,
  523. NatGatewayIngressCost: ngi,
  524. }, nil
  525. }
  526. // Alibaba loadbalancer has three different types https://www.alibabacloud.com/product/server-load-balancer,
  527. // defaulted price to classic load balancer https://www.alibabacloud.com/help/en/server-load-balancer/latest/pay-as-you-go.
  528. func (alibaba *Alibaba) LoadBalancerPricing() (*models.LoadBalancer, error) {
  529. cpricing, err := alibaba.Config.GetCustomPricingData()
  530. if err != nil {
  531. return nil, err
  532. }
  533. lbPricing, err := strconv.ParseFloat(cpricing.DefaultLBPrice, 64)
  534. if err != nil {
  535. return nil, err
  536. }
  537. return &models.LoadBalancer{
  538. Cost: lbPricing,
  539. }, nil
  540. }
  541. func (alibaba *Alibaba) GetConfig() (*models.CustomPricing, error) {
  542. c, err := alibaba.Config.GetCustomPricingData()
  543. if err != nil {
  544. return nil, err
  545. }
  546. if c.Discount == "" {
  547. c.Discount = "0%"
  548. }
  549. if c.NegotiatedDiscount == "" {
  550. c.NegotiatedDiscount = "0%"
  551. }
  552. return c, nil
  553. }
  554. // Load once and cache the result (even on failure). This is an install time secret, so
  555. // we don't expect the secret to change. If it does, however, we can force reload using
  556. // the input parameter.
  557. func (alibaba *Alibaba) loadAlibabaAuthSecretAndSetEnv(force bool) error {
  558. if !force && alibaba.accessKeyisLoaded() {
  559. return nil
  560. }
  561. exists, err := fileutil.FileExists(models.AuthSecretPath)
  562. if !exists || err != nil {
  563. return fmt.Errorf("failed to locate service account file: %s with err: %w", models.AuthSecretPath, err)
  564. }
  565. result, err := os.ReadFile(models.AuthSecretPath)
  566. if err != nil {
  567. return fmt.Errorf("failed to read service account file: %s with err: %w", models.AuthSecretPath, err)
  568. }
  569. var ak *AlibabaAccessKey
  570. err = json.Unmarshal(result, &ak)
  571. if err != nil {
  572. return fmt.Errorf("failed to unmarshall access key id and access key secret with err: %w", err)
  573. }
  574. err = coreenv.Set(env.AlibabaAccessKeyIDEnvVar, ak.AccessKeyID)
  575. if err != nil {
  576. return fmt.Errorf("failed to set environment variable: %s with err: %w", env.AlibabaAccessKeyIDEnvVar, err)
  577. }
  578. err = coreenv.Set(env.AlibabaAccessKeySecretEnvVar, ak.SecretAccessKey)
  579. if err != nil {
  580. return fmt.Errorf("failed to set environment variable: %s with err: %w", env.AlibabaAccessKeySecretEnvVar, err)
  581. }
  582. alibaba.accessKey = &credentials.AccessKeyCredential{
  583. AccessKeyId: ak.AccessKeyID,
  584. AccessKeySecret: ak.SecretAccessKey,
  585. }
  586. return nil
  587. }
  588. // Regions returns a current supported list of Alibaba regions
  589. func (alibaba *Alibaba) Regions() []string {
  590. regionOverrides := env.GetRegionOverrideList()
  591. if len(regionOverrides) > 0 {
  592. log.Debugf("Overriding Alibaba regions with configured region list: %+v", regionOverrides)
  593. return regionOverrides
  594. }
  595. return alibabaRegions
  596. }
  597. // ClusterInfo returns information about Alibaba Cloud cluster, as provided by metadata.
  598. func (alibaba *Alibaba) ClusterInfo() (map[string]string, error) {
  599. c, err := alibaba.GetConfig()
  600. if err != nil {
  601. return nil, fmt.Errorf("failed to getConfig with err: %w", err)
  602. }
  603. var clusterName string
  604. if c.ClusterName != "" {
  605. clusterName = c.ClusterName
  606. }
  607. // Set it to environment clusterID if not set at this point
  608. if clusterName == "" {
  609. clusterName = coreenv.GetClusterID()
  610. }
  611. m := make(map[string]string)
  612. m["name"] = clusterName
  613. m["provider"] = opencost.AlibabaProvider
  614. m["project"] = alibaba.ClusterAccountId
  615. m["region"] = alibaba.ClusterRegion
  616. m["id"] = coreenv.GetClusterID()
  617. return m, nil
  618. }
  619. // Will look at this in Next PR if needed
  620. func (alibaba *Alibaba) GetAddresses() ([]byte, error) {
  621. return nil, nil
  622. }
  623. // Will look at this in Next PR if needed
  624. func (alibaba *Alibaba) GetDisks() ([]byte, error) {
  625. return nil, nil
  626. }
  627. func (alibaba *Alibaba) GetOrphanedResources() ([]models.OrphanedResource, error) {
  628. return nil, errors.New("not implemented")
  629. }
  630. func (alibaba *Alibaba) UpdateConfig(r io.Reader, updateType string) (*models.CustomPricing, error) {
  631. return alibaba.Config.Update(func(c *models.CustomPricing) error {
  632. if updateType != "" {
  633. return fmt.Errorf("UpdateConfig for Alibaba Provider doesn't support updateType %s at this time", updateType)
  634. } else {
  635. a := make(map[string]interface{})
  636. err := json.NewDecoder(r).Decode(&a)
  637. if err != nil {
  638. return err
  639. }
  640. for k, v := range a {
  641. kUpper := utils.ToTitle.String(k) // Just so we consistently supply / receive the same values, uppercase the first letter.
  642. vstr, ok := v.(string)
  643. if ok {
  644. err := models.SetCustomPricingField(c, kUpper, vstr)
  645. if err != nil {
  646. return fmt.Errorf("error setting custom pricing field: %w", err)
  647. }
  648. } else {
  649. return fmt.Errorf("type error while updating config for %s", kUpper)
  650. }
  651. }
  652. }
  653. if env.IsRemoteEnabled() {
  654. err := utils.UpdateClusterMeta(coreenv.GetClusterID(), c.ClusterName)
  655. if err != nil {
  656. return err
  657. }
  658. }
  659. return nil
  660. })
  661. }
  662. func (alibaba *Alibaba) UpdateConfigFromConfigMap(cm map[string]string) (*models.CustomPricing, error) {
  663. return alibaba.Config.UpdateFromMap(cm)
  664. }
  665. // Will look at this in Next PR if needed
  666. func (alibaba *Alibaba) GetManagementPlatform() (string, error) {
  667. return "", nil
  668. }
  669. // Will look at this in Next PR if needed
  670. func (alibaba *Alibaba) ApplyReservedInstancePricing(nodes map[string]*models.Node) {
  671. }
  672. // Will look at this in Next PR if needed
  673. func (alibaba *Alibaba) ServiceAccountStatus() *models.ServiceAccountStatus {
  674. return &models.ServiceAccountStatus{}
  675. }
  676. // Will look at this in Next PR if needed
  677. func (alibaba *Alibaba) PricingSourceStatus() map[string]*models.PricingSource {
  678. return map[string]*models.PricingSource{}
  679. }
  680. // Will look at this in Next PR if needed
  681. func (alibaba *Alibaba) ClusterManagementPricing() (string, float64, error) {
  682. return "", 0.0, nil
  683. }
  684. // Will look at this in Next PR if needed
  685. func (alibaba *Alibaba) CombinedDiscountForNode(string, bool, float64, float64) float64 {
  686. return 0.0
  687. }
  688. func (alibaba *Alibaba) accessKeyisLoaded() bool {
  689. if alibaba.accessKey == nil {
  690. return false
  691. }
  692. if alibaba.accessKey.AccessKeyId == "" {
  693. return false
  694. }
  695. if alibaba.accessKey.AccessKeySecret == "" {
  696. return false
  697. }
  698. return true
  699. }
  700. type AlibabaNodeKey struct {
  701. ProviderID string
  702. RegionID string
  703. InstanceType string
  704. OSType string
  705. OptimizedKeyword string //If IsIoOptimized is true use the word optimize in the Node key and if its not optimized use the word nonoptimize
  706. SystemDiskCategory string
  707. SystemDiskSizeInGiB string
  708. SystemDiskPerformanceLevel string
  709. }
  710. func NewAlibabaNodeKey(node *SlimK8sNode, optimizedKeyword, systemDiskCategory, systemDiskSizeInGiB, systemDiskPerfromanceLevel string) *AlibabaNodeKey {
  711. var providerID, regionID, instanceType, osType string
  712. if node != nil {
  713. providerID = node.ProviderID
  714. regionID = node.RegionID
  715. instanceType = node.InstanceType
  716. osType = node.OSType
  717. }
  718. return &AlibabaNodeKey{
  719. ProviderID: providerID,
  720. RegionID: regionID,
  721. InstanceType: instanceType,
  722. OSType: osType,
  723. OptimizedKeyword: optimizedKeyword,
  724. SystemDiskCategory: systemDiskCategory,
  725. SystemDiskSizeInGiB: systemDiskSizeInGiB,
  726. SystemDiskPerformanceLevel: systemDiskPerfromanceLevel,
  727. }
  728. }
  729. func (alibabaNodeKey *AlibabaNodeKey) ID() string {
  730. return alibabaNodeKey.ProviderID
  731. }
  732. func (alibabaNodeKey *AlibabaNodeKey) Features() string {
  733. keyLookup := stringutil.DeleteEmptyStringsFromArray([]string{alibabaNodeKey.RegionID, alibabaNodeKey.InstanceType, alibabaNodeKey.OSType,
  734. alibabaNodeKey.OptimizedKeyword, alibabaNodeKey.SystemDiskCategory, alibabaNodeKey.SystemDiskSizeInGiB, alibabaNodeKey.SystemDiskPerformanceLevel})
  735. return strings.Join(keyLookup, "::")
  736. }
  737. func (alibabaNodeKey *AlibabaNodeKey) FeaturesWithOtherDisk(overrideDiskCategory string) string {
  738. keyLookup := stringutil.DeleteEmptyStringsFromArray([]string{alibabaNodeKey.RegionID, alibabaNodeKey.InstanceType, alibabaNodeKey.OSType,
  739. alibabaNodeKey.OptimizedKeyword, overrideDiskCategory, alibabaNodeKey.SystemDiskSizeInGiB, alibabaNodeKey.SystemDiskPerformanceLevel})
  740. return strings.Join(keyLookup, "::")
  741. }
  742. func (alibabaNodeKey *AlibabaNodeKey) GPUType() string {
  743. return ""
  744. }
  745. func (alibabaNodeKey *AlibabaNodeKey) GPUCount() int {
  746. return 0
  747. }
  748. // Get's the key for the k8s node input
  749. func (alibaba *Alibaba) GetKey(mapValue map[string]string, node *clustercache.Node) models.Key {
  750. slimK8sNode := generateSlimK8sNodeFromV1Node(node)
  751. var aak *credentials.AccessKeyCredential
  752. var err error
  753. var ok bool
  754. var client *sdk.Client
  755. var signer *signers.AccessKeySigner
  756. optimizedKeyword := ""
  757. if slimK8sNode.IsIoOptimized {
  758. optimizedKeyword = ALIBABA_OPTIMIZE_KEYWORD
  759. } else {
  760. optimizedKeyword = ALIBABA_NON_OPTIMIZE_KEYWORD
  761. }
  762. var diskCategory, diskSizeInGiB, diskPerformanceLevel string
  763. if !alibaba.accessKeyisLoaded() {
  764. aak, err = alibaba.GetAlibabaAccessKey()
  765. if err != nil {
  766. log.Warnf("unable to set the signer for node with providerID %s to retrieve the key skipping SystemDisk Retrieval with err: %v", slimK8sNode.ProviderID, err)
  767. return NewAlibabaNodeKey(slimK8sNode, optimizedKeyword, diskCategory, diskSizeInGiB, diskPerformanceLevel)
  768. }
  769. } else {
  770. aak = alibaba.accessKey
  771. }
  772. signer = signers.NewAccessKeySigner(aak)
  773. if aak == nil {
  774. log.Warnf("unable to retrieve the Alibaba API keys for node with providerID %s hence skipping SystemDisk Retrieval", slimK8sNode.ProviderID)
  775. return NewAlibabaNodeKey(slimK8sNode, optimizedKeyword, diskCategory, diskSizeInGiB, diskPerformanceLevel)
  776. }
  777. if client, ok = alibaba.clients[slimK8sNode.RegionID]; !ok {
  778. client, err = sdk.NewClientWithAccessKey(slimK8sNode.RegionID, aak.AccessKeyId, aak.AccessKeySecret)
  779. if err != nil {
  780. log.Warnf("unable to set the client for node with providerID %s to retrieve the key skipping SystemDisk Retrieval with err: %v", slimK8sNode.ProviderID, err)
  781. return NewAlibabaNodeKey(slimK8sNode, optimizedKeyword, diskCategory, diskSizeInGiB, diskPerformanceLevel)
  782. }
  783. alibaba.clients[slimK8sNode.RegionID] = client
  784. }
  785. instanceID := getInstanceIDFromProviderID(slimK8sNode.ProviderID)
  786. slimK8sNode.SystemDisk = getSystemDiskInfoOfANode(instanceID, slimK8sNode.RegionID, client, signer)
  787. if slimK8sNode.SystemDisk != nil {
  788. diskCategory = slimK8sNode.SystemDisk.DiskCategory
  789. diskSizeInGiB = slimK8sNode.SystemDisk.SizeInGiB
  790. diskPerformanceLevel = slimK8sNode.SystemDisk.PerformanceLevel
  791. }
  792. return NewAlibabaNodeKey(slimK8sNode, optimizedKeyword, diskCategory, diskSizeInGiB, diskPerformanceLevel)
  793. }
  794. type AlibabaPVKey struct {
  795. ProviderID string
  796. RegionID string
  797. PVType string
  798. PVSubType string
  799. PVCategory string
  800. PVPerformaceLevel string
  801. StorageClassName string
  802. SizeInGiB string
  803. }
  804. func (alibaba *Alibaba) GetPVKey(pv *clustercache.PersistentVolume, parameters map[string]string, defaultRegion string) models.PVKey {
  805. regionID := defaultRegion
  806. // If default Region is not passed default it to cluster region ID.
  807. if defaultRegion == "" {
  808. regionID = alibaba.ClusterRegion
  809. }
  810. slimK8sDisk := generateSlimK8sDiskFromV1PV(pv, defaultRegion)
  811. return &AlibabaPVKey{
  812. ProviderID: slimK8sDisk.ProviderID,
  813. RegionID: regionID,
  814. PVType: ALIBABA_PV_CLOUD_DISK_TYPE,
  815. PVSubType: slimK8sDisk.DiskType,
  816. PVCategory: slimK8sDisk.DiskCategory,
  817. PVPerformaceLevel: slimK8sDisk.PerformanceLevel,
  818. StorageClassName: pv.Spec.StorageClassName,
  819. SizeInGiB: slimK8sDisk.SizeInGiB,
  820. }
  821. }
  822. func (alibabaPVKey *AlibabaPVKey) Features() string {
  823. keyLookup := stringutil.DeleteEmptyStringsFromArray([]string{alibabaPVKey.RegionID, alibabaPVKey.PVSubType, alibabaPVKey.PVCategory, alibabaPVKey.PVPerformaceLevel, alibabaPVKey.SizeInGiB})
  824. return strings.Join(keyLookup, "::")
  825. }
  826. func (alibabaPVKey *AlibabaPVKey) ID() string {
  827. return alibabaPVKey.ProviderID
  828. }
  829. // Get storage class information for PV.
  830. func (alibabaPVKey *AlibabaPVKey) GetStorageClass() string {
  831. return alibabaPVKey.StorageClassName
  832. }
  833. // Helper functions for alibabaprovider.go
  834. // createDescribePriceACSRequest creates the HTTP GET request for the required resources' Price information,
  835. // When supporting subscription and Premptible resources this HTTP call needs to be modified with PriceUnit information
  836. // When supporting different new type of instances like Compute Optimized, Memory Optimized etc make sure you add the instance type
  837. // in unit test and check if it works or not to create the ack request and processDescribePriceAndCreateAlibabaPricing function
  838. // else more parameters need to be pulled from kubernetes node response or gather information from elsewhere and function modified.
  839. func createDescribePriceACSRequest(i interface{}) (*requests.CommonRequest, error) {
  840. request := requests.NewCommonRequest()
  841. request.Method = requests.GET
  842. request.Product = ALIBABA_ECS_PRODUCT_CODE
  843. request.Domain = ALIBABA_ECS_DOMAIN
  844. request.Version = ALIBABA_ECS_VERSION
  845. request.Scheme = requests.HTTPS
  846. request.ApiName = ALIBABA_DESCRIBE_PRICE_API_ACTION
  847. switch i.(type) {
  848. case *SlimK8sNode:
  849. node := i.(*SlimK8sNode)
  850. request.QueryParams["RegionId"] = node.RegionID
  851. request.QueryParams["ResourceType"] = ALIBABA_INSTANCE_RESOURCE_TYPE
  852. request.QueryParams["InstanceType"] = node.InstanceType
  853. request.QueryParams["PriceUnit"] = node.PriceUnit
  854. if node.SystemDisk != nil {
  855. // Only if the required information is present it should be overridden else default it via the API
  856. if node.SystemDisk.DiskCategory != "" {
  857. request.QueryParams["SystemDisk.Category"] = node.SystemDisk.DiskCategory
  858. }
  859. if node.SystemDisk.SizeInGiB != "" {
  860. request.QueryParams["SystemDisk.Size"] = node.SystemDisk.SizeInGiB
  861. }
  862. if node.SystemDisk.PerformanceLevel != "" {
  863. request.QueryParams["SystemDisk.PerformanceLevel"] = node.SystemDisk.PerformanceLevel
  864. }
  865. } else {
  866. // When the system disk information is not available, and the instance family is g6e or r6e,
  867. // or the instance generation is 6 or above, the default disk category in DescribePrice should be cloud_essd.
  868. if slices.Contains(alibabaDefaultToCloudEssd, node.InstanceTypeFamily) || getInstanceFamilyGenerationFromType(node.InstanceType) > 6 {
  869. request.QueryParams["SystemDisk.Category"] = ALIBABA_DISK_CLOUD_ESSD_CATEGORY
  870. }
  871. }
  872. request.TransToAcsRequest()
  873. return request, nil
  874. case *SlimK8sDisk:
  875. disk := i.(*SlimK8sDisk)
  876. request.QueryParams["RegionId"] = disk.RegionID
  877. request.QueryParams["PriceUnit"] = disk.PriceUnit
  878. request.QueryParams["ResourceType"] = ALIBABA_DISK_RESOURCE_TYPE
  879. request.QueryParams[fmt.Sprintf("%s.%d.Size", ALIBABA_DATA_DISK_PREFIX, 1)] = disk.SizeInGiB
  880. request.QueryParams[fmt.Sprintf("%s.%d.Category", ALIBABA_DATA_DISK_PREFIX, 1)] = disk.DiskCategory
  881. // Performance level defaults to PL1 if not present in volume attribute.
  882. if disk.PerformanceLevel != "" {
  883. request.QueryParams[fmt.Sprintf("%s.%d.PerformanceLevel", ALIBABA_DATA_DISK_PREFIX, 1)] = disk.PerformanceLevel
  884. }
  885. request.TransToAcsRequest()
  886. return request, nil
  887. default:
  888. return nil, fmt.Errorf("unsupported ECS type (%T) for DescribePrice at this time", i)
  889. }
  890. }
  891. // createDescribeDisksCSRequest creates the HTTP GET Request to map the system disk to the InstanceID
  892. func createDescribeDisksACSRequest(instanceID, regionID, diskType string) (*requests.CommonRequest, error) {
  893. request := requests.NewCommonRequest()
  894. request.Method = requests.GET
  895. request.Product = ALIBABA_ECS_PRODUCT_CODE
  896. request.Domain = ALIBABA_ECS_DOMAIN
  897. request.Version = ALIBABA_ECS_VERSION
  898. request.Scheme = requests.HTTPS
  899. request.ApiName = ALIBABA_DESCRIBE_DISK_API_ACTION
  900. request.QueryParams["RegionId"] = regionID
  901. request.QueryParams["InstanceId"] = instanceID
  902. request.QueryParams["DiskType"] = diskType
  903. request.TransToAcsRequest()
  904. return request, nil
  905. }
  906. // determineKeyForPricing generate a unique key from SlimK8sNode object that is constructed from v1.Node object and
  907. // SlimK8sDisk that is constructed from v1.PersistentVolume.
  908. func determineKeyForPricing(i interface{}) (string, error) {
  909. if i == nil {
  910. return "", fmt.Errorf("nil component passed to determine key")
  911. }
  912. switch i.(type) {
  913. case *SlimK8sNode:
  914. node := i.(*SlimK8sNode)
  915. var diskCategory, diskSizeInGiB, diskPerformanceLevel string
  916. if node.SystemDisk != nil {
  917. diskCategory = node.SystemDisk.DiskCategory
  918. diskSizeInGiB = node.SystemDisk.SizeInGiB
  919. diskPerformanceLevel = node.SystemDisk.PerformanceLevel
  920. }
  921. if node.IsIoOptimized {
  922. keyLookup := stringutil.DeleteEmptyStringsFromArray([]string{node.RegionID, node.InstanceType, node.OSType, ALIBABA_OPTIMIZE_KEYWORD, diskCategory, diskSizeInGiB, diskPerformanceLevel})
  923. return strings.Join(keyLookup, "::"), nil
  924. } else {
  925. keyLookup := stringutil.DeleteEmptyStringsFromArray([]string{node.RegionID, node.InstanceType, node.OSType, ALIBABA_NON_OPTIMIZE_KEYWORD, diskCategory, diskSizeInGiB, diskPerformanceLevel})
  926. return strings.Join(keyLookup, "::"), nil
  927. }
  928. case *SlimK8sDisk:
  929. disk := i.(*SlimK8sDisk)
  930. keyLookup := stringutil.DeleteEmptyStringsFromArray([]string{disk.RegionID, disk.DiskType, disk.DiskCategory, disk.PerformanceLevel, disk.SizeInGiB})
  931. return strings.Join(keyLookup, "::"), nil
  932. default:
  933. return "", fmt.Errorf("unsupported ECS type (%T) at this time", i)
  934. }
  935. }
  936. // Below structs are used to unmarshal json response of Alibaba cloud's API DescribePrice
  937. type Price struct {
  938. OriginalPrice float32 `json:"OriginalPrice"`
  939. ReservedInstanceHourPrice float32 `json:"ReservedInstanceHourPrice"`
  940. DiscountPrice float32 `json:"DiscountPrice"`
  941. Currency string `json:"Currency"`
  942. TradePrice float32 `json:"TradePrice"`
  943. }
  944. type PriceInfo struct {
  945. Price Price `json:"Price"`
  946. }
  947. type DescribePriceResponse struct {
  948. RequestId string `json:"RequestId"`
  949. PriceInfo PriceInfo `json:"PriceInfo"`
  950. }
  951. // processDescribePriceAndCreateAlibabaPricing processes the DescribePrice API and generates the pricing information for alibaba node resource and alibaba pv resource that's backed by cloud disk.
  952. func processDescribePriceAndCreateAlibabaPricing(client *sdk.Client, i interface{}, signer *signers.AccessKeySigner, custom *models.CustomPricing) (pricing *AlibabaPricing, err error) {
  953. pricing = &AlibabaPricing{}
  954. var response DescribePriceResponse
  955. if client == nil {
  956. return nil, fmt.Errorf("nil client passed to process the pricing information")
  957. }
  958. if i == nil {
  959. return nil, fmt.Errorf("nil component passed to process the pricing information")
  960. }
  961. switch i.(type) {
  962. case *SlimK8sNode:
  963. node := i.(*SlimK8sNode)
  964. req, err := createDescribePriceACSRequest(node)
  965. if err != nil {
  966. return nil, err
  967. }
  968. resp, err := client.ProcessCommonRequestWithSigner(req, signer)
  969. pricing.NodeAttributes = NewAlibabaNodeAttributes(node)
  970. if err != nil || resp.GetHttpStatus() != 200 {
  971. // Try again but default the disk to something else
  972. return nil, fmt.Errorf("unable to fetch information for node with InstanceType: %v", node.InstanceType)
  973. } else {
  974. // This is where population of Pricing happens
  975. err = json.Unmarshal(resp.GetHttpContentBytes(), &response)
  976. if err != nil {
  977. return nil, fmt.Errorf("unable to unmarshall json response to custom struct with err: %w", err)
  978. }
  979. // TO-DO : Ask in PR How to get the defaults is it equal to AWS/GCP defaults? And what needs to be returned
  980. pricing.Node = &models.Node{
  981. Cost: fmt.Sprintf("%f", response.PriceInfo.Price.TradePrice),
  982. BaseCPUPrice: custom.CPU,
  983. BaseRAMPrice: custom.RAM,
  984. BaseGPUPrice: custom.GPU,
  985. }
  986. // TO-DO : Currently with Pay-As-You-go Offering TradePrice = HourlyPrice , When support happens to other type HourlyPrice Need to be determined.
  987. 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))
  988. }
  989. case *SlimK8sDisk:
  990. disk := i.(*SlimK8sDisk)
  991. req, err := createDescribePriceACSRequest(disk)
  992. if err != nil {
  993. return nil, err
  994. }
  995. resp, err := client.ProcessCommonRequestWithSigner(req, signer)
  996. if err != nil || resp.GetHttpStatus() != 200 {
  997. return nil, fmt.Errorf("unable to fetch information for disk with DiskType: %v with err: %w", disk.DiskCategory, err)
  998. } else {
  999. // This is where population of Pricing happens
  1000. err = json.Unmarshal(resp.GetHttpContentBytes(), &response)
  1001. if err != nil {
  1002. return nil, fmt.Errorf("unable to unmarshall json response to custom struct with err: %w", err)
  1003. }
  1004. pricing.PVAttributes = NewAlibabaPVAttributes(disk)
  1005. pricing.PV = &models.PV{
  1006. Cost: fmt.Sprintf("%f", response.PriceInfo.Price.TradePrice),
  1007. }
  1008. // 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.
  1009. 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))
  1010. }
  1011. default:
  1012. return nil, fmt.Errorf("unsupported ECS Pricing component of type (%T) at this time", i)
  1013. }
  1014. return pricing, nil
  1015. }
  1016. // This function is to get the InstanceFamily from the InstanceType , convention followed in
  1017. // instance type is ecs.[FamilyName].[DifferentSize], it gets the familyName , if it is unable to get it
  1018. // it lists the instance family name as Unknown.
  1019. func getInstanceFamilyFromType(instanceType string) string {
  1020. splitinstanceType := strings.Split(instanceType, ".")
  1021. if len(splitinstanceType) != 3 {
  1022. log.Warnf("unable to find the family of the instance type %s, returning its family type unknown", instanceType)
  1023. return ALIBABA_UNKNOWN_INSTANCE_FAMILY_TYPE
  1024. }
  1025. return splitinstanceType[1]
  1026. }
  1027. // This function is used to obtain the generation of the instance family from the InstanceType,
  1028. // because when the generation is higher than or equal to 7, the instance disk type will not support cloud_efficiency.
  1029. // In such cases, when calling the DescribePrice interface, the system disk type will default to cloud_essd.
  1030. func getInstanceFamilyGenerationFromType(instanceType string) int {
  1031. // FamilyName format: g7ne or g7 or r7 or r6e,
  1032. familyName := getInstanceFamilyFromType(instanceType)
  1033. match := generationRegEx.FindString(familyName)
  1034. if match != "" {
  1035. generation, err := strconv.Atoi(match)
  1036. if err != nil {
  1037. log.Errorf("unable to convert the generation of the instance type %s to integer", instanceType)
  1038. } else {
  1039. return generation
  1040. }
  1041. }
  1042. log.Warnf("unable to find the generation of the instance type %s,", instanceType)
  1043. return -1
  1044. }
  1045. // getInstanceIDFromProviderID returns the instance ID associated with the Node. A *v1.Node providerID in Alibaba cloud
  1046. // is of <REGION-ID>.<INSTANCE-ID>. This function returns the Instance ID for the given ProviderID. if its unable to interpret
  1047. // it defaults to empty string.
  1048. func getInstanceIDFromProviderID(providerID string) string {
  1049. if providerID == "" {
  1050. return ""
  1051. }
  1052. splitStrings := strings.Split(providerID, ".")
  1053. if len(splitStrings) < 2 {
  1054. return ""
  1055. }
  1056. return splitStrings[1]
  1057. }
  1058. type Disk struct {
  1059. Category string `json:"Category"`
  1060. Size int `json:"Size"`
  1061. PerformanceLevel string `json:"PerformanceLevel"`
  1062. Type string `json:"Type"`
  1063. RegionId string `json:"RegionId"`
  1064. DiskId string `json:"DiskId"`
  1065. DiskChargeType string `json:"DiskChargeType"`
  1066. }
  1067. type Disks struct {
  1068. Disk []*Disk `json:"Disk"`
  1069. }
  1070. type DescribeDiskResponse struct {
  1071. TotalCount int `json:"TotalCount"`
  1072. Disks *Disks `json:"Disks"`
  1073. }
  1074. // getSystemDiskInfoOfANode gets the relevant System disk information associated with the Node given by the instanceID
  1075. // in form of a SlimK8sDisk with only relevant information that can adjust the node pricing. If any error occurs return
  1076. // an empty disk to not impact any default set at the price retrieval of the node.
  1077. func getSystemDiskInfoOfANode(instanceID, regionID string, client *sdk.Client, signer *signers.AccessKeySigner) (systemDisk *SlimK8sDisk) {
  1078. systemDisk = &SlimK8sDisk{}
  1079. var response DescribeDiskResponse
  1080. // if instanceID is empty string return an empty k8s
  1081. if instanceID == "" {
  1082. return
  1083. }
  1084. // if client is nil return an empty disk to not impact default pricing
  1085. if client == nil {
  1086. log.Warnf("unable to set the signer for node with providerID %s to retrieve the key skipping SystemDisk Retrieval with err: nil client", instanceID)
  1087. return
  1088. }
  1089. req, err := createDescribeDisksACSRequest(instanceID, regionID, ALIBABA_SYSTEM_DISK_CATEGORY)
  1090. // if any error occurs return an empty disk to not impact default pricing.
  1091. if err != nil {
  1092. log.Warnf("Unable to create Describe Disk Request with err: %v for node with InstanceID: %s, hence defaulting it to an empty system disk to pass through to defaults", err, instanceID)
  1093. return
  1094. }
  1095. resp, err := client.ProcessCommonRequestWithSigner(req, signer)
  1096. if err != nil || resp.GetHttpStatus() != 200 {
  1097. log.Warnf("Unable to process Describe Disk request with err: %v and errcode: %d for the node with InstanceID: %s, hence defaulting it to an empty system disk to pass through to defaults", err, resp.GetHttpStatus(), instanceID)
  1098. return
  1099. } else {
  1100. // This is where population of Pricing happens
  1101. err = json.Unmarshal(resp.GetHttpContentBytes(), &response)
  1102. if err != nil {
  1103. log.Warnf("Unable to unmarshall Describe Disk response with err: %v for the node with InstanceID: %s, hence defaulting it to an empty system disk to pass through to defaults", err, instanceID)
  1104. return
  1105. }
  1106. // Every instance should only have one system disk per Alibaba Cloud documentation https://www.alibabacloud.com/help/en/elastic-compute-service/latest/block-storage-overview-disks,
  1107. // if TotalCount is not 1 just return empty and let it not impact default pricing.
  1108. if response.TotalCount != 1 {
  1109. log.Warnf("Total count of system disk for node with InstanceID: %s is not 1, hence defaulting it to an empty system disk to pass through to defaults", instanceID)
  1110. return
  1111. }
  1112. if response.Disks == nil {
  1113. log.Warnf("Disks information missing for node with InstanceID: %s, hence defaulting it to an empty system disk to pass through to defaults", instanceID)
  1114. return
  1115. }
  1116. if len(response.Disks.Disk) < 1 {
  1117. log.Warnf("Total number of system disk for node with InstanceID: %s is less than 1, hence defaulting it to an empty system disk to pass through to defaults", instanceID)
  1118. return
  1119. }
  1120. // TO-DO: When supporting Subscription type disk, you can leverge the disk.DiskChargeType here to map it to subscription type.
  1121. systemDisk := response.Disks.Disk[0]
  1122. return NewSlimK8sDisk(systemDisk.Type, systemDisk.RegionId, ALIBABA_HOUR_PRICE_UNIT, systemDisk.Category, systemDisk.PerformanceLevel, systemDisk.DiskId, "", fmt.Sprintf("%d", systemDisk.Size))
  1123. }
  1124. }
  1125. // generateSlimK8sNodeFromV1Node generates SlimK8sNode struct from v1.Node to fetch pricing information and call alibaba API.
  1126. func generateSlimK8sNodeFromV1Node(node *clustercache.Node) *SlimK8sNode {
  1127. var regionID, osType, instanceType, providerID, priceUnit, instanceFamily string
  1128. var memorySizeInKiB string // TO-DO: try to convert it into float
  1129. var ok, IsIoOptimized bool
  1130. if regionID, ok = node.Labels["topology.kubernetes.io/region"]; !ok {
  1131. // HIGHLY UNLIKELY THAT THIS LABEL WONT BE THERE.
  1132. log.Debugf("No RegionID label for the node: %s", node.Name)
  1133. }
  1134. if osType, ok = node.Labels["beta.kubernetes.io/os"]; !ok {
  1135. // HIGHLY UNLIKELY THAT THIS LABEL WONT BE THERE.
  1136. log.Debugf("OS type undetected for the node: %s", node.Name)
  1137. }
  1138. if instanceType, ok = node.Labels["node.kubernetes.io/instance-type"]; !ok {
  1139. // HIGHLY UNLIKELY THAT THIS LABEL WONT BE THERE.
  1140. log.Debugf("Instance Type undetected for the node: %s", node.Name)
  1141. }
  1142. instanceFamily = getInstanceFamilyFromType(instanceType)
  1143. memorySizeInKiB = fmt.Sprintf("%s", node.Status.Capacity.Memory())
  1144. providerID = node.SpecProviderID // Alibaba Cloud provider doesnt follow convention of prefix with cloud provider name
  1145. // 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
  1146. // Basic price Json has it as part of the key so defaulting to true.
  1147. IsIoOptimized = true
  1148. priceUnit = ALIBABA_HOUR_PRICE_UNIT
  1149. systemDisk := &SlimK8sDisk{}
  1150. return NewSlimK8sNode(instanceType, regionID, priceUnit, memorySizeInKiB, osType, providerID, instanceFamily, IsIoOptimized, systemDisk)
  1151. }
  1152. // getNumericalValueFromResourceQuantity converts a Kubernetes PV capacity string (e.g. "20Gi", "48828125Ki")
  1153. // into a whole GiB integer string, as required by the Alibaba DescribePrice API.
  1154. // Returns ALIBABA_DEFAULT_DATADISK_SIZE if the quantity cannot be parsed.
  1155. func getNumericalValueFromResourceQuantity(quantity string) (value string) {
  1156. defer func() {
  1157. if err := recover(); err != nil {
  1158. log.Debugf("panic while parsing PV capacity %q, defaulting to %s: %v", quantity, ALIBABA_DEFAULT_DATADISK_SIZE, err)
  1159. value = ALIBABA_DEFAULT_DATADISK_SIZE
  1160. }
  1161. if value == "" {
  1162. log.Debugf("unable to determine the size of the PV from quantity %q, defaulting to %s", quantity, ALIBABA_DEFAULT_DATADISK_SIZE)
  1163. value = ALIBABA_DEFAULT_DATADISK_SIZE
  1164. }
  1165. }()
  1166. res := sizeRegEx.FindStringSubmatch(strings.TrimSpace(quantity))
  1167. if len(res) < 2 || res[1] == "" {
  1168. return
  1169. }
  1170. numericPart, err := strconv.ParseFloat(res[1], 64)
  1171. if err != nil || numericPart <= 0 {
  1172. return
  1173. }
  1174. unit := ""
  1175. if len(res) >= 3 {
  1176. unit = res[2]
  1177. }
  1178. var sizeInGiB float64
  1179. switch unit {
  1180. case "Ki":
  1181. sizeInGiB = numericPart / (1024 * 1024)
  1182. case "Mi":
  1183. sizeInGiB = numericPart / 1024
  1184. case "Gi":
  1185. sizeInGiB = numericPart
  1186. case "Ti":
  1187. sizeInGiB = numericPart * 1024
  1188. default:
  1189. sizeInGiB = numericPart / (1024 * 1024 * 1024)
  1190. }
  1191. // ceil so we never underreport disk size to the DescribePrice API.
  1192. sizeInGiBInt := int64(math.Ceil(sizeInGiB))
  1193. if sizeInGiBInt <= 0 {
  1194. return
  1195. }
  1196. value = strconv.FormatInt(sizeInGiBInt, 10)
  1197. return
  1198. }
  1199. // generateSlimK8sDiskFromV1PV function generates SlimK8sDisk from v1.PersistentVolume
  1200. // to generate slim disk type that can be used to fetch pricing information for Data disk type.
  1201. func generateSlimK8sDiskFromV1PV(pv *clustercache.PersistentVolume, regionID string) *SlimK8sDisk {
  1202. // All PVs are data disks while local disk are categorized as system disk
  1203. diskType := ALIBABA_DATA_DISK_CATEGORY
  1204. //TO-DO: Disk supports month and hour prices , defaulting to hour
  1205. priceUnit := ALIBABA_HOUR_PRICE_UNIT
  1206. sizeQuantity := fmt.Sprintf("%s", pv.Spec.Capacity.Storage())
  1207. // res := sizeRegEx.FindAllStringSubmatch(sizeQuantity, 1)
  1208. sizeInGiB := getNumericalValueFromResourceQuantity(sizeQuantity)
  1209. providerID := ""
  1210. if pv.Spec.CSI != nil {
  1211. providerID = pv.Spec.CSI.VolumeHandle
  1212. } else {
  1213. providerID = pv.Name // Looks like pv name is same as providerID in Alibaba k8s cluster
  1214. }
  1215. // Performance level being empty string gets defaulted in describePrice to PL1.
  1216. performanceLevel := ""
  1217. diskCategory := ""
  1218. if pv.Spec.CSI != nil {
  1219. if val, ok := pv.Spec.CSI.VolumeAttributes["performanceLevel"]; ok {
  1220. performanceLevel = val
  1221. }
  1222. if val, ok := pv.Spec.CSI.VolumeAttributes["type"]; ok {
  1223. diskCategory = val
  1224. }
  1225. }
  1226. // Highly unlikely that label pv.Spec.CSI.VolumeAttributes["type"] doesn't exist but if occurred default to cloud (most basic disk type)
  1227. if diskCategory == "" {
  1228. diskCategory = ALIBABA_DISK_CLOUD_CATEGORY
  1229. }
  1230. return NewSlimK8sDisk(diskType, regionID, priceUnit, diskCategory, performanceLevel, providerID, pv.Spec.StorageClassName, sizeInGiB)
  1231. }
  1232. // 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!
  1233. // if topology.diskplugin.csi.alibabacloud.com/region label/annotation is passed during PV creation return that as the PV region.
  1234. // if topology.diskplugin.csi.alibabacloud.com/zone label/annotation is passed during PV creation determine the region based on this pv label.
  1235. // if neither of the above label/annotation is present check node affinity for the zone affinity and determine the region based on this zone.
  1236. // if nether of the above yields a region , return empty string to default it to cluster region.
  1237. func determinePVRegion(pv *clustercache.PersistentVolume) string {
  1238. // if "topology.diskplugin.csi.alibabacloud.com/region" is present as a label or annotation return that as the PV region
  1239. if val, ok := pv.Labels[ALIBABA_DISK_TOPOLOGY_REGION_LABEL]; ok {
  1240. log.Debugf("determinePVRegion returned a region value of: %s through label: %s for PV name: %s", val, ALIBABA_DISK_TOPOLOGY_REGION_LABEL, pv.Name)
  1241. return val
  1242. }
  1243. if val, ok := pv.Annotations[ALIBABA_DISK_TOPOLOGY_REGION_LABEL]; ok {
  1244. log.Debugf("determinePVRegion returned a region value of: %s through annotation: %s for PV name: %s", val, ALIBABA_DISK_TOPOLOGY_REGION_LABEL, pv.Name)
  1245. return val
  1246. }
  1247. // 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
  1248. var pvZone string
  1249. if val, ok := pv.Labels[ALIBABA_DISK_TOPOLOGY_ZONE_LABEL]; ok {
  1250. log.Debugf("determinePVRegion will set zone value to: %s through label: %s for PV name: %s", val, ALIBABA_DISK_TOPOLOGY_ZONE_LABEL, pv.Name)
  1251. pvZone = val
  1252. }
  1253. if pvZone == "" {
  1254. if val, ok := pv.Annotations[ALIBABA_DISK_TOPOLOGY_ZONE_LABEL]; ok {
  1255. log.Debugf("determinePVRegion will set zone value to: %s through annotation: %s for PV name: %s", val, ALIBABA_DISK_TOPOLOGY_ZONE_LABEL, pv.Name)
  1256. pvZone = val
  1257. }
  1258. }
  1259. if pvZone == "" {
  1260. // zone and regionID labels are optional in Alibaba PV creation, while PV through UI creation put's a zone PV is associated with and the region
  1261. // can be determined from this information. If pv is provision via yaml and the block is missing that's the only time it gets defaulted to ClusterRegion.
  1262. if pv.Spec.NodeAffinity != nil {
  1263. nodeAffinity := pv.Spec.NodeAffinity
  1264. if nodeAffinity.Required != nil && nodeAffinity.Required.NodeSelectorTerms != nil {
  1265. for _, nodeSelectorTerm := range nodeAffinity.Required.NodeSelectorTerms {
  1266. matchExpression := nodeSelectorTerm.MatchExpressions
  1267. for _, nodeSelectorRequirement := range matchExpression {
  1268. if nodeSelectorRequirement.Key == ALIBABA_DISK_TOPOLOGY_ZONE_LABEL {
  1269. 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)
  1270. pvZone = nodeSelectorRequirement.Values[0]
  1271. }
  1272. }
  1273. }
  1274. }
  1275. }
  1276. }
  1277. regionOverrides := env.GetRegionOverrideList()
  1278. regions := alibabaRegions
  1279. if len(regionOverrides) > 0 {
  1280. regions = regionOverrides
  1281. }
  1282. for _, region := range regions {
  1283. if strings.Contains(pvZone, region) {
  1284. log.Debugf("determinePVRegion determined region of %s through zone affiliation of the PV %s\n", region, pvZone)
  1285. return region
  1286. }
  1287. }
  1288. return ""
  1289. }
  1290. // PricingSourceSummary returns the pricing source summary for the provider.
  1291. // The summary represents what was _parsed_ from the pricing source, not
  1292. // everything that was _available_ in the pricing source.
  1293. func (a *Alibaba) PricingSourceSummary() interface{} {
  1294. return a.Pricing
  1295. }