azureprovider.go 38 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333
  1. package cloud
  2. import (
  3. "context"
  4. "fmt"
  5. "io"
  6. "io/ioutil"
  7. "net/http"
  8. "net/url"
  9. "regexp"
  10. "strconv"
  11. "strings"
  12. "sync"
  13. "time"
  14. "github.com/kubecost/cost-model/pkg/log"
  15. "github.com/kubecost/cost-model/pkg/clustercache"
  16. "github.com/kubecost/cost-model/pkg/env"
  17. "github.com/kubecost/cost-model/pkg/util"
  18. "github.com/kubecost/cost-model/pkg/util/fileutil"
  19. "github.com/kubecost/cost-model/pkg/util/json"
  20. "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2017-09-01/skus"
  21. "github.com/Azure/azure-sdk-for-go/services/containerservice/mgmt/2018-03-31/containerservice"
  22. "github.com/Azure/azure-sdk-for-go/services/preview/commerce/mgmt/2015-06-01-preview/commerce"
  23. "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2016-06-01/subscriptions"
  24. "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2018-05-01/resources"
  25. "github.com/Azure/go-autorest/autorest"
  26. "github.com/Azure/go-autorest/autorest/azure"
  27. "github.com/Azure/go-autorest/autorest/azure/auth"
  28. v1 "k8s.io/api/core/v1"
  29. "k8s.io/klog"
  30. )
  31. const (
  32. AzureFilePremiumStorageClass = "premium_smb"
  33. AzureFileStandardStorageClass = "standard_smb"
  34. AzureDiskPremiumSSDStorageClass = "premium_ssd"
  35. AzureDiskStandardSSDStorageClass = "standard_ssd"
  36. AzureDiskStandardStorageClass = "standard_hdd"
  37. defaultSpotLabel = "kubernetes.azure.com/scalesetpriority"
  38. defaultSpotLabelValue = "spot"
  39. )
  40. var (
  41. regionCodeMappings = map[string]string{
  42. "ap": "asia",
  43. "au": "australia",
  44. "br": "brazil",
  45. "ca": "canada",
  46. "eu": "europe",
  47. "fr": "france",
  48. "in": "india",
  49. "ja": "japan",
  50. "kr": "korea",
  51. "uk": "uk",
  52. "us": "us",
  53. "za": "southafrica",
  54. "no": "norway",
  55. }
  56. //mtBasic, _ = regexp.Compile("^BASIC.A\\d+[_Promo]*$")
  57. //mtStandardA, _ = regexp.Compile("^A\\d+[_Promo]*$")
  58. mtStandardB, _ = regexp.Compile(`^Standard_B\d+m?[_v\d]*[_Promo]*$`)
  59. mtStandardD, _ = regexp.Compile(`^Standard_D\d[_v\d]*[_Promo]*$`)
  60. mtStandardE, _ = regexp.Compile(`^Standard_E\d+i?[_v\d]*[_Promo]*$`)
  61. mtStandardF, _ = regexp.Compile(`^Standard_F\d+[_v\d]*[_Promo]*$`)
  62. mtStandardG, _ = regexp.Compile(`^Standard_G\d+[_v\d]*[_Promo]*$`)
  63. mtStandardL, _ = regexp.Compile(`^Standard_L\d+[_v\d]*[_Promo]*$`)
  64. mtStandardM, _ = regexp.Compile(`^Standard_M\d+[m|t|l]*s[_v\d]*[_Promo]*$`)
  65. mtStandardN, _ = regexp.Compile(`^Standard_N[C|D|V]\d+r?[_v\d]*[_Promo]*$`)
  66. )
  67. // List obtained by installing the Azure CLI tool "az", described here:
  68. // https://docs.microsoft.com/en-us/cli/azure/install-azure-cli-linux?pivots=apt
  69. // logging into an Azure account, and running command `az account list-locations`
  70. var azureRegions = []string{
  71. "eastus",
  72. "eastus2",
  73. "southcentralus",
  74. "westus2",
  75. "westus3",
  76. "australiaeast",
  77. "southeastasia",
  78. "northeurope",
  79. "swedencentral",
  80. "uksouth",
  81. "westeurope",
  82. "centralus",
  83. "northcentralus",
  84. "westus",
  85. "southafricanorth",
  86. "centralindia",
  87. "eastasia",
  88. "japaneast",
  89. "jioindiawest",
  90. "koreacentral",
  91. "canadacentral",
  92. "francecentral",
  93. "germanywestcentral",
  94. "norwayeast",
  95. "switzerlandnorth",
  96. "uaenorth",
  97. "brazilsouth",
  98. "centralusstage",
  99. "eastusstage",
  100. "eastus2stage",
  101. "northcentralusstage",
  102. "southcentralusstage",
  103. "westusstage",
  104. "westus2stage",
  105. "asia",
  106. "asiapacific",
  107. "australia",
  108. "brazil",
  109. "canada",
  110. "europe",
  111. "france",
  112. "germany",
  113. "global",
  114. "india",
  115. "japan",
  116. "korea",
  117. "norway",
  118. "southafrica",
  119. "switzerland",
  120. "uae",
  121. "uk",
  122. "unitedstates",
  123. "eastasiastage",
  124. "southeastasiastage",
  125. "centraluseuap",
  126. "eastus2euap",
  127. "westcentralus",
  128. "southafricawest",
  129. "australiacentral",
  130. "australiacentral2",
  131. "australiasoutheast",
  132. "japanwest",
  133. "jioindiacentral",
  134. "koreasouth",
  135. "southindia",
  136. "westindia",
  137. "canadaeast",
  138. "francesouth",
  139. "germanynorth",
  140. "norwaywest",
  141. "switzerlandwest",
  142. "ukwest",
  143. "uaecentral",
  144. "brazilsoutheast",
  145. }
  146. type regionParts []string
  147. func (r regionParts) String() string {
  148. var result string
  149. for _, p := range r {
  150. result += p
  151. }
  152. return result
  153. }
  154. func getRegions(service string, subscriptionsClient subscriptions.Client, providersClient resources.ProvidersClient, subscriptionID string) (map[string]string, error) {
  155. allLocations := make(map[string]string)
  156. supLocations := make(map[string]string)
  157. // retrieve all locations for the subscription id (some of them may not be supported by the required provider)
  158. if locations, err := subscriptionsClient.ListLocations(context.TODO(), subscriptionID); err == nil {
  159. // fill up the map: DisplayName - > Name
  160. for _, loc := range *locations.Value {
  161. allLocations[*loc.DisplayName] = *loc.Name
  162. }
  163. } else {
  164. return nil, err
  165. }
  166. // identify supported locations for the namespace and resource type
  167. const (
  168. providerNamespaceForCompute = "Microsoft.Compute"
  169. resourceTypeForCompute = "locations/vmSizes"
  170. providerNamespaceForAks = "Microsoft.ContainerService"
  171. resourceTypeForAks = "managedClusters"
  172. )
  173. switch service {
  174. case "aks":
  175. if providers, err := providersClient.Get(context.TODO(), providerNamespaceForAks, ""); err == nil {
  176. for _, pr := range *providers.ResourceTypes {
  177. if *pr.ResourceType == resourceTypeForAks {
  178. for _, displName := range *pr.Locations {
  179. if loc, ok := allLocations[displName]; ok {
  180. supLocations[loc] = displName
  181. } else {
  182. klog.V(1).Infof("unsupported cloud region %s", loc)
  183. }
  184. }
  185. break
  186. }
  187. }
  188. } else {
  189. return nil, err
  190. }
  191. return supLocations, nil
  192. default:
  193. if providers, err := providersClient.Get(context.TODO(), providerNamespaceForCompute, ""); err == nil {
  194. for _, pr := range *providers.ResourceTypes {
  195. if *pr.ResourceType == resourceTypeForCompute {
  196. for _, displName := range *pr.Locations {
  197. if loc, ok := allLocations[displName]; ok {
  198. supLocations[loc] = displName
  199. } else {
  200. klog.V(1).Infof("unsupported cloud region %s", loc)
  201. }
  202. }
  203. break
  204. }
  205. }
  206. } else {
  207. return nil, err
  208. }
  209. return supLocations, nil
  210. }
  211. }
  212. func getRetailPrice(region string, skuName string, currencyCode string, spot bool) (string, error) {
  213. pricingURL := "https://prices.azure.com/api/retail/prices?$skip=0"
  214. if currencyCode != "" {
  215. pricingURL += fmt.Sprintf("&currencyCode='%s'", currencyCode)
  216. }
  217. var filterParams []string
  218. if region != "" {
  219. regionParam := fmt.Sprintf("armRegionName eq '%s'", region)
  220. filterParams = append(filterParams, regionParam)
  221. }
  222. if skuName != "" {
  223. skuNameParam := fmt.Sprintf("armSkuName eq '%s'", skuName)
  224. filterParams = append(filterParams, skuNameParam)
  225. }
  226. if len(filterParams) > 0 {
  227. filterParamsEscaped := url.QueryEscape(strings.Join(filterParams[:], " and "))
  228. pricingURL += fmt.Sprintf("&$filter=%s", filterParamsEscaped)
  229. }
  230. log.Infof("starting download retail price payload from \"%s\"", pricingURL)
  231. resp, err := http.Get(pricingURL)
  232. if err != nil {
  233. return "", fmt.Errorf("bogus fetch of \"%s\": %v", pricingURL, err)
  234. }
  235. if resp.StatusCode < 200 && resp.StatusCode > 299 {
  236. return "", fmt.Errorf("retail price responded with error status code %d", resp.StatusCode)
  237. }
  238. pricingPayload := AzureRetailPricing{}
  239. body, err := ioutil.ReadAll(resp.Body)
  240. if err != nil {
  241. return "", fmt.Errorf("Error getting response: %v", err)
  242. }
  243. jsonErr := json.Unmarshal(body, &pricingPayload)
  244. if jsonErr != nil {
  245. return "", fmt.Errorf("Error unmarshalling data: %v", jsonErr)
  246. }
  247. retailPrice := ""
  248. for _, item := range pricingPayload.Items {
  249. if item.Type == "Consumption" && !strings.Contains(item.ProductName, "Windows") {
  250. // if spot is true SkuName should contain "spot, if it is false it should not
  251. if spot == strings.Contains(strings.ToLower(item.SkuName), " spot") {
  252. retailPrice = fmt.Sprintf("%f", item.RetailPrice)
  253. }
  254. }
  255. }
  256. log.DedupedInfof(5, "done parsing retail price payload from \"%s\"\n", pricingURL)
  257. if retailPrice == "" {
  258. return retailPrice, fmt.Errorf("Couldn't find price for product \"%s\" in \"%s\" region", skuName, region)
  259. }
  260. return retailPrice, nil
  261. }
  262. func toRegionID(meterRegion string, regions map[string]string) (string, error) {
  263. var rp regionParts = strings.Split(strings.ToLower(meterRegion), " ")
  264. regionCode := regionCodeMappings[rp[0]]
  265. lastPart := rp[len(rp)-1]
  266. var regionIds []string
  267. if _, err := strconv.Atoi(lastPart); err == nil {
  268. regionIds = []string{
  269. fmt.Sprintf("%s%s%s", regionCode, rp[1:len(rp)-1], lastPart),
  270. fmt.Sprintf("%s%s%s", rp[1:len(rp)-1], regionCode, lastPart),
  271. }
  272. } else {
  273. regionIds = []string{
  274. fmt.Sprintf("%s%s", regionCode, rp[1:]),
  275. fmt.Sprintf("%s%s", rp[1:], regionCode),
  276. }
  277. }
  278. for _, regionID := range regionIds {
  279. if checkRegionID(regionID, regions) {
  280. return regionID, nil
  281. }
  282. }
  283. return "", fmt.Errorf("Couldn't find region")
  284. }
  285. func checkRegionID(regionID string, regions map[string]string) bool {
  286. for region := range regions {
  287. if regionID == region {
  288. return true
  289. }
  290. }
  291. return false
  292. }
  293. // AzureRetailPricing struct for unmarshalling Azure Retail pricing api JSON response
  294. type AzureRetailPricing struct {
  295. BillingCurrency string `json:"BillingCurrency"`
  296. CustomerEntityId string `json:"CustomerEntityId"`
  297. CustomerEntityType string `json:"CustomerEntityType"`
  298. Items []AzureRetailPricingAttributes `json:"Items"`
  299. NextPageLink string `json:"NextPageLink"`
  300. Count int `json:"Count"`
  301. }
  302. //AzureRetailPricingAttributes struct for unmarshalling Azure Retail pricing api JSON response
  303. type AzureRetailPricingAttributes struct {
  304. CurrencyCode string `json:"currencyCode"`
  305. TierMinimumUnits float32 `json:"tierMinimumUnits"`
  306. RetailPrice float32 `json:"retailPrice"`
  307. UnitPrice float32 `json:"unitPrice"`
  308. ArmRegionName string `json:"armRegionName"`
  309. Location string `json:"location"`
  310. EffectiveStartDate *time.Time `json:"effectiveStartDate"`
  311. EffectiveEndDate *time.Time `json:"effectiveEndDate"`
  312. MeterId string `json:"meterId"`
  313. MeterName string `json:"meterName"`
  314. ProductId string `json:"productId"`
  315. SkuId string `json:"skuId"`
  316. ProductName string `json:"productName"`
  317. SkuName string `json:"skuName"`
  318. ServiceName string `json:"serviceName"`
  319. ServiceId string `json:"serviceId"`
  320. ServiceFamily string `json:"serviceFamily"`
  321. UnitOfMeasure string `json:"unitOfMeasure"`
  322. Type string `json:"type"`
  323. IsPrimaryMeterRegion bool `json:"isPrimaryMeterRegion"`
  324. ArmSkuName string `json:"armSkuName"`
  325. }
  326. // AzurePricing either contains a Node or PV
  327. type AzurePricing struct {
  328. Node *Node
  329. PV *PV
  330. }
  331. type Azure struct {
  332. Pricing map[string]*AzurePricing
  333. DownloadPricingDataLock sync.RWMutex
  334. Clientset clustercache.ClusterCache
  335. Config *ProviderConfig
  336. ServiceAccountChecks map[string]*ServiceAccountCheck
  337. RateCardPricingError error
  338. clusterAccountId string
  339. clusterRegion string
  340. loadedAzureSecret bool
  341. azureSecret *AzureServiceKey
  342. loadedAzureStorageConfigSecret bool
  343. azureStorageConfig *AzureStorageConfig
  344. }
  345. type azureKey struct {
  346. Labels map[string]string
  347. GPULabel string
  348. GPULabelValue string
  349. }
  350. func (k *azureKey) Features() string {
  351. r, _ := util.GetRegion(k.Labels)
  352. region := strings.ToLower(r)
  353. instance, _ := util.GetInstanceType(k.Labels)
  354. usageType := "ondemand"
  355. return fmt.Sprintf("%s,%s,%s", region, instance, usageType)
  356. }
  357. // GPUType returns value of GPULabel if present
  358. func (k *azureKey) GPUType() string {
  359. if t, ok := k.Labels[k.GPULabel]; ok {
  360. return t
  361. }
  362. return ""
  363. }
  364. func (k *azureKey) isValidGPUNode() bool {
  365. return k.GPUType() == k.GPULabelValue && k.GetGPUCount() != "0"
  366. }
  367. func (k *azureKey) ID() string {
  368. return ""
  369. }
  370. func (k *azureKey) GetGPUCount() string {
  371. instance, _ := util.GetInstanceType(k.Labels)
  372. // Double digits that could get matches lower in logic
  373. if strings.Contains(instance, "NC64") {
  374. return "4"
  375. }
  376. if strings.Contains(instance, "ND96") ||
  377. strings.Contains(instance, "ND40") {
  378. return "8"
  379. }
  380. // Ordered asc because of some series have different gpu counts on different versions
  381. if strings.Contains(instance, "NC6") ||
  382. strings.Contains(instance, "NC4") ||
  383. strings.Contains(instance, "NC8") ||
  384. strings.Contains(instance, "NC16") ||
  385. strings.Contains(instance, "ND6") ||
  386. strings.Contains(instance, "NV12s") ||
  387. strings.Contains(instance, "NV6") {
  388. return "1"
  389. }
  390. if strings.Contains(instance, "NC12") ||
  391. strings.Contains(instance, "ND12") ||
  392. strings.Contains(instance, "NV24s") ||
  393. strings.Contains(instance, "NV12") {
  394. return "2"
  395. }
  396. if strings.Contains(instance, "NC24") ||
  397. strings.Contains(instance, "ND24") ||
  398. strings.Contains(instance, "NV48s") ||
  399. strings.Contains(instance, "NV24") {
  400. return "4"
  401. }
  402. return "0"
  403. }
  404. // Represents an azure storage config
  405. type AzureStorageConfig struct {
  406. SubscriptionId string `json:"azureSubscriptionID"`
  407. AccountName string `json:"azureStorageAccount"`
  408. AccessKey string `json:"azureStorageAccessKey"`
  409. ContainerName string `json:"azureStorageContainer"`
  410. }
  411. // Represents an azure app key
  412. type AzureAppKey struct {
  413. AppID string `json:"appId"`
  414. DisplayName string `json:"displayName"`
  415. Name string `json:"name"`
  416. Password string `json:"password"`
  417. Tenant string `json:"tenant"`
  418. }
  419. // Azure service key for a specific subscription
  420. type AzureServiceKey struct {
  421. SubscriptionID string `json:"subscriptionId"`
  422. ServiceKey *AzureAppKey `json:"serviceKey"`
  423. }
  424. // Validity check on service key
  425. func (ask *AzureServiceKey) IsValid() bool {
  426. return ask.SubscriptionID != "" &&
  427. ask.ServiceKey != nil &&
  428. ask.ServiceKey.AppID != "" &&
  429. ask.ServiceKey.Password != "" &&
  430. ask.ServiceKey.Tenant != ""
  431. }
  432. // Loads the azure authentication via configuration or a secret set at install time.
  433. func (az *Azure) getAzureAuth(forceReload bool, cp *CustomPricing) (subscriptionID, clientID, clientSecret, tenantID string) {
  434. // 1. Check config values first (set from frontend UI)
  435. if cp.AzureSubscriptionID != "" && cp.AzureClientID != "" && cp.AzureClientSecret != "" && cp.AzureTenantID != "" {
  436. subscriptionID = cp.AzureSubscriptionID
  437. clientID = cp.AzureClientID
  438. clientSecret = cp.AzureClientSecret
  439. tenantID = cp.AzureTenantID
  440. return
  441. }
  442. // 2. Check for secret
  443. s, _ := az.loadAzureAuthSecret(forceReload)
  444. if s != nil && s.IsValid() {
  445. subscriptionID = s.SubscriptionID
  446. clientID = s.ServiceKey.AppID
  447. clientSecret = s.ServiceKey.Password
  448. tenantID = s.ServiceKey.Tenant
  449. return
  450. }
  451. // 3. Empty values
  452. return "", "", "", ""
  453. }
  454. func (az *Azure) ConfigureAzureStorage() error {
  455. subscriptionID, accessKey, accountName, containerName := az.getAzureStorageConfig(false)
  456. if subscriptionID != "" && accessKey != "" && accountName != "" && containerName != "" {
  457. err := env.Set(env.AzureStorageSubscriptionIDEnvVar, subscriptionID)
  458. if err != nil {
  459. return err
  460. }
  461. err = env.Set(env.AzureStorageAccessKeyEnvVar, accessKey)
  462. if err != nil {
  463. return err
  464. }
  465. err = env.Set(env.AzureStorageAccountNameEnvVar, accountName)
  466. if err != nil {
  467. return err
  468. }
  469. err = env.Set(env.AzureStorageContainerNameEnvVar, containerName)
  470. if err != nil {
  471. return err
  472. }
  473. }
  474. return nil
  475. }
  476. func (az *Azure) getAzureStorageConfig(forceReload bool) (subscriptionId, accessKey, accountName, containerName string) {
  477. // retrieve config for default subscription id
  478. defaultSubscriptionID := ""
  479. config, err := az.GetConfig()
  480. if err == nil {
  481. defaultSubscriptionID = config.AzureSubscriptionID
  482. }
  483. if az.ServiceAccountChecks == nil {
  484. az.ServiceAccountChecks = make(map[string]*ServiceAccountCheck)
  485. }
  486. // 1. Check for secret
  487. s, err := az.loadAzureStorageConfig(forceReload)
  488. if err != nil {
  489. log.Errorf("Error, %s", err.Error())
  490. }
  491. if s != nil && s.AccessKey != "" && s.AccountName != "" && s.ContainerName != "" {
  492. az.ServiceAccountChecks["hasStorage"] = &ServiceAccountCheck{
  493. Message: "Azure Storage Config exists",
  494. Status: true,
  495. }
  496. // To support already configured users, subscriptionID may not be set in secret in which case, the subscriptionID
  497. // for the rate card API is used
  498. subscriptionId = defaultSubscriptionID
  499. if s.SubscriptionId != "" {
  500. subscriptionId = s.SubscriptionId
  501. }
  502. accessKey = s.AccessKey
  503. accountName = s.AccountName
  504. containerName = s.ContainerName
  505. return
  506. }
  507. // 3. Fall back to env vars
  508. subscriptionId = env.Get(env.AzureStorageSubscriptionIDEnvVar, config.AzureSubscriptionID)
  509. accountName = env.Get(env.AzureStorageAccountNameEnvVar, "")
  510. accessKey = env.Get(env.AzureStorageAccessKeyEnvVar, "")
  511. containerName = env.Get(env.AzureStorageContainerNameEnvVar, "")
  512. if accessKey != "" && accountName != "" && containerName != "" {
  513. az.ServiceAccountChecks["hasStorage"] = &ServiceAccountCheck{
  514. Message: "Azure Storage Config exists",
  515. Status: true,
  516. }
  517. } else {
  518. az.ServiceAccountChecks["hasStorage"] = &ServiceAccountCheck{
  519. Message: "Azure Storage Config exists",
  520. Status: false,
  521. }
  522. }
  523. return
  524. }
  525. // Load once and cache the result (even on failure). This is an install time secret, so
  526. // we don't expect the secret to change. If it does, however, we can force reload using
  527. // the input parameter.
  528. func (az *Azure) loadAzureAuthSecret(force bool) (*AzureServiceKey, error) {
  529. if !force && az.loadedAzureSecret {
  530. return az.azureSecret, nil
  531. }
  532. az.loadedAzureSecret = true
  533. exists, err := fileutil.FileExists(authSecretPath)
  534. if !exists || err != nil {
  535. return nil, fmt.Errorf("Failed to locate service account file: %s", authSecretPath)
  536. }
  537. result, err := ioutil.ReadFile(authSecretPath)
  538. if err != nil {
  539. return nil, err
  540. }
  541. var ask AzureServiceKey
  542. err = json.Unmarshal(result, &ask)
  543. if err != nil {
  544. return nil, err
  545. }
  546. az.azureSecret = &ask
  547. return &ask, nil
  548. }
  549. // Load once and cache the result (even on failure). This is an install time secret, so
  550. // we don't expect the secret to change. If it does, however, we can force reload using
  551. // the input parameter.
  552. func (az *Azure) loadAzureStorageConfig(force bool) (*AzureStorageConfig, error) {
  553. if !force && az.loadedAzureStorageConfigSecret {
  554. return az.azureStorageConfig, nil
  555. }
  556. az.loadedAzureStorageConfigSecret = true
  557. exists, err := fileutil.FileExists(storageConfigSecretPath)
  558. if !exists || err != nil {
  559. return nil, fmt.Errorf("Failed to locate azure storage config file: %s", storageConfigSecretPath)
  560. }
  561. result, err := ioutil.ReadFile(storageConfigSecretPath)
  562. if err != nil {
  563. return nil, err
  564. }
  565. var asc AzureStorageConfig
  566. err = json.Unmarshal(result, &asc)
  567. if err != nil {
  568. return nil, err
  569. }
  570. az.azureStorageConfig = &asc
  571. return &asc, nil
  572. }
  573. func (az *Azure) GetKey(labels map[string]string, n *v1.Node) Key {
  574. cfg, err := az.GetConfig()
  575. if err != nil {
  576. klog.Infof("Error loading azure custom pricing information")
  577. }
  578. // azure defaults, see https://docs.microsoft.com/en-us/azure/aks/gpu-cluster
  579. gpuLabel := "accelerator"
  580. gpuLabelValue := "nvidia"
  581. if cfg.GpuLabel != "" {
  582. gpuLabel = cfg.GpuLabel
  583. }
  584. if cfg.GpuLabelValue != "" {
  585. gpuLabelValue = cfg.GpuLabelValue
  586. }
  587. return &azureKey{
  588. Labels: labels,
  589. GPULabel: gpuLabel,
  590. GPULabelValue: gpuLabelValue,
  591. }
  592. }
  593. // CreateString builds strings effectively
  594. func createString(keys ...string) string {
  595. var b strings.Builder
  596. for _, key := range keys {
  597. b.WriteString(key)
  598. }
  599. return b.String()
  600. }
  601. func transformMachineType(subCategory string, mt []string) []string {
  602. switch {
  603. case strings.Contains(subCategory, "Basic"):
  604. return []string{createString("Basic_", mt[0])}
  605. case len(mt) == 2:
  606. return []string{createString("Standard_", mt[0]), createString("Standard_", mt[1])}
  607. default:
  608. return []string{createString("Standard_", mt[0])}
  609. }
  610. }
  611. func addSuffix(mt string, suffixes ...string) []string {
  612. result := make([]string, len(suffixes))
  613. var suffix string
  614. parts := strings.Split(mt, "_")
  615. if len(parts) > 2 {
  616. for _, p := range parts[2:] {
  617. suffix = createString(suffix, "_", p)
  618. }
  619. }
  620. for i, s := range suffixes {
  621. result[i] = createString(parts[0], "_", parts[1], s, suffix)
  622. }
  623. return result
  624. }
  625. func getMachineTypeVariants(mt string) []string {
  626. switch {
  627. case mtStandardB.MatchString(mt):
  628. return []string{createString(mt, "s")}
  629. case mtStandardD.MatchString(mt):
  630. var result []string
  631. result = append(result, addSuffix(mt, "s")[0])
  632. dsType := strings.Replace(mt, "Standard_D", "Standard_DS", -1)
  633. result = append(result, dsType)
  634. result = append(result, addSuffix(dsType, "-1", "-2", "-4", "-8")...)
  635. return result
  636. case mtStandardE.MatchString(mt):
  637. return addSuffix(mt, "s", "-2s", "-4s", "-8s", "-16s", "-32s")
  638. case mtStandardF.MatchString(mt):
  639. return addSuffix(mt, "s")
  640. case mtStandardG.MatchString(mt):
  641. var result []string
  642. gsType := strings.Replace(mt, "Standard_G", "Standard_GS", -1)
  643. result = append(result, gsType)
  644. return append(result, addSuffix(gsType, "-4", "-8", "-16")...)
  645. case mtStandardL.MatchString(mt):
  646. return addSuffix(mt, "s")
  647. case mtStandardM.MatchString(mt) && strings.HasSuffix(mt, "ms"):
  648. base := strings.TrimSuffix(mt, "ms")
  649. return addSuffix(base, "-2ms", "-4ms", "-8ms", "-16ms", "-32ms", "-64ms")
  650. case mtStandardM.MatchString(mt) && (strings.HasSuffix(mt, "ls") || strings.HasSuffix(mt, "ts")):
  651. return []string{}
  652. case mtStandardM.MatchString(mt) && strings.HasSuffix(mt, "s"):
  653. base := strings.TrimSuffix(mt, "s")
  654. return addSuffix(base, "", "m")
  655. case mtStandardN.MatchString(mt):
  656. return addSuffix(mt, "s")
  657. }
  658. return []string{}
  659. }
  660. func (az *Azure) GetManagementPlatform() (string, error) {
  661. nodes := az.Clientset.GetAllNodes()
  662. if len(nodes) > 0 {
  663. n := nodes[0]
  664. providerID := n.Spec.ProviderID
  665. if strings.Contains(providerID, "aks") {
  666. return "aks", nil
  667. }
  668. }
  669. return "", nil
  670. }
  671. // DownloadPricingData uses provided azure "best guesses" for pricing
  672. func (az *Azure) DownloadPricingData() error {
  673. az.DownloadPricingDataLock.Lock()
  674. defer az.DownloadPricingDataLock.Unlock()
  675. config, err := az.GetConfig()
  676. if err != nil {
  677. az.RateCardPricingError = err
  678. return err
  679. }
  680. // Load the service provider keys
  681. subscriptionID, clientID, clientSecret, tenantID := az.getAzureAuth(false, config)
  682. config.AzureSubscriptionID = subscriptionID
  683. config.AzureClientID = clientID
  684. config.AzureClientSecret = clientSecret
  685. config.AzureTenantID = tenantID
  686. var authorizer autorest.Authorizer
  687. azureEnv := determineCloudByRegion(config.AzureBillingRegion)
  688. if config.AzureClientID != "" && config.AzureClientSecret != "" && config.AzureTenantID != "" {
  689. credentialsConfig := NewClientCredentialsConfig(config.AzureClientID, config.AzureClientSecret, config.AzureTenantID, azureEnv)
  690. a, err := credentialsConfig.Authorizer()
  691. if err != nil {
  692. az.RateCardPricingError = err
  693. return err
  694. }
  695. authorizer = a
  696. }
  697. if authorizer == nil {
  698. a, err := auth.NewAuthorizerFromEnvironment()
  699. authorizer = a
  700. if err != nil {
  701. a, err := auth.NewAuthorizerFromFile(azureEnv.ResourceManagerEndpoint)
  702. if err != nil {
  703. az.RateCardPricingError = err
  704. return err
  705. }
  706. authorizer = a
  707. }
  708. }
  709. sClient := subscriptions.NewClient()
  710. sClient.Authorizer = authorizer
  711. rcClient := commerce.NewRateCardClient(config.AzureSubscriptionID)
  712. rcClient.Authorizer = authorizer
  713. skusClient := skus.NewResourceSkusClient(config.AzureSubscriptionID)
  714. skusClient.Authorizer = authorizer
  715. providersClient := resources.NewProvidersClient(config.AzureSubscriptionID)
  716. providersClient.Authorizer = authorizer
  717. containerServiceClient := containerservice.NewContainerServicesClient(config.AzureSubscriptionID)
  718. containerServiceClient.Authorizer = authorizer
  719. rateCardFilter := fmt.Sprintf("OfferDurableId eq 'MS-AZR-0003p' and Currency eq '%s' and Locale eq 'en-US' and RegionInfo eq '%s'", config.CurrencyCode, config.AzureBillingRegion)
  720. klog.Infof("Using ratecard query %s", rateCardFilter)
  721. result, err := rcClient.Get(context.TODO(), rateCardFilter)
  722. if err != nil {
  723. klog.Warningf("Error in pricing download query from API")
  724. az.RateCardPricingError = err
  725. return err
  726. }
  727. allPrices := make(map[string]*AzurePricing)
  728. regions, err := getRegions("compute", sClient, providersClient, config.AzureSubscriptionID)
  729. if err != nil {
  730. klog.Warningf("Error in pricing download regions from API")
  731. az.RateCardPricingError = err
  732. return err
  733. }
  734. baseCPUPrice := config.CPU
  735. for _, v := range *result.Meters {
  736. meterName := *v.MeterName
  737. meterRegion := *v.MeterRegion
  738. meterCategory := *v.MeterCategory
  739. meterSubCategory := *v.MeterSubCategory
  740. region, err := toRegionID(meterRegion, regions)
  741. if err != nil {
  742. continue
  743. }
  744. if !strings.Contains(meterSubCategory, "Windows") {
  745. if strings.Contains(meterCategory, "Storage") {
  746. if strings.Contains(meterSubCategory, "HDD") || strings.Contains(meterSubCategory, "SSD") || strings.Contains(meterSubCategory, "Premium Files") {
  747. var storageClass string = ""
  748. if strings.Contains(meterName, "P4 ") {
  749. storageClass = AzureDiskPremiumSSDStorageClass
  750. } else if strings.Contains(meterName, "E4 ") {
  751. storageClass = AzureDiskStandardSSDStorageClass
  752. } else if strings.Contains(meterName, "S4 ") {
  753. storageClass = AzureDiskStandardStorageClass
  754. } else if strings.Contains(meterName, "LRS Provisioned") {
  755. storageClass = AzureFilePremiumStorageClass
  756. }
  757. if storageClass != "" {
  758. var priceInUsd float64
  759. if len(v.MeterRates) < 1 {
  760. klog.V(1).Infof("missing rate info %+v", map[string]interface{}{"MeterSubCategory": *v.MeterSubCategory, "region": region})
  761. continue
  762. }
  763. for _, rate := range v.MeterRates {
  764. priceInUsd += *rate
  765. }
  766. // rate is in disk per month, resolve price per hour, then GB per hour
  767. pricePerHour := priceInUsd / 730.0 / 32.0
  768. priceStr := fmt.Sprintf("%f", pricePerHour)
  769. key := region + "," + storageClass
  770. klog.V(4).Infof("Adding PV.Key: %s, Cost: %s", key, priceStr)
  771. allPrices[key] = &AzurePricing{
  772. PV: &PV{
  773. Cost: priceStr,
  774. Region: region,
  775. },
  776. }
  777. }
  778. }
  779. }
  780. if strings.Contains(meterCategory, "Virtual Machines") {
  781. usageType := ""
  782. if !strings.Contains(meterName, "Low Priority") {
  783. usageType = "ondemand"
  784. } else {
  785. usageType = "preemptible"
  786. }
  787. var instanceTypes []string
  788. name := strings.TrimSuffix(meterName, " Low Priority")
  789. instanceType := strings.Split(name, "/")
  790. for _, it := range instanceType {
  791. if strings.Contains(meterSubCategory, "Promo") {
  792. it = it + " Promo"
  793. }
  794. instanceTypes = append(instanceTypes, strings.Replace(it, " ", "_", 1))
  795. }
  796. instanceTypes = transformMachineType(meterSubCategory, instanceTypes)
  797. if strings.Contains(name, "Expired") {
  798. instanceTypes = []string{}
  799. }
  800. var priceInUsd float64
  801. if len(v.MeterRates) < 1 {
  802. klog.V(1).Infof("missing rate info %+v", map[string]interface{}{"MeterSubCategory": *v.MeterSubCategory, "region": region})
  803. continue
  804. }
  805. for _, rate := range v.MeterRates {
  806. priceInUsd += *rate
  807. }
  808. priceStr := fmt.Sprintf("%f", priceInUsd)
  809. for _, instanceType := range instanceTypes {
  810. key := fmt.Sprintf("%s,%s,%s", region, instanceType, usageType)
  811. allPrices[key] = &AzurePricing{
  812. Node: &Node{
  813. Cost: priceStr,
  814. BaseCPUPrice: baseCPUPrice,
  815. UsageType: usageType,
  816. },
  817. }
  818. }
  819. }
  820. }
  821. }
  822. // There is no easy way of supporting Standard Azure-File, because it's billed per used GB
  823. // this will set the price to "0" as a workaround to not spam with `Persistent Volume pricing not found for` error
  824. // check https://github.com/kubecost/cost-model/issues/159 for more information (same problem on AWS)
  825. zeroPrice := "0.0"
  826. for region := range regions {
  827. key := region + "," + AzureFileStandardStorageClass
  828. klog.V(4).Infof("Adding PV.Key: %s, Cost: %s", key, zeroPrice)
  829. allPrices[key] = &AzurePricing{
  830. PV: &PV{
  831. Cost: zeroPrice,
  832. Region: region,
  833. },
  834. }
  835. }
  836. az.Pricing = allPrices
  837. az.RateCardPricingError = nil
  838. return nil
  839. }
  840. // determineCloudByRegion uses region name to pick the correct Cloud Environment for the azure provider to use
  841. func determineCloudByRegion(region string) azure.Environment {
  842. lcRegion := strings.ToLower(region)
  843. if strings.Contains(lcRegion, "china") {
  844. return azure.ChinaCloud
  845. }
  846. if strings.Contains(lcRegion, "gov") || strings.Contains(lcRegion, "dod") {
  847. return azure.USGovernmentCloud
  848. }
  849. // Default to public cloud
  850. return azure.PublicCloud
  851. }
  852. // NewClientCredentialsConfig creates an AuthorizerConfig object configured to obtain an Authorizer through Client Credentials.
  853. func NewClientCredentialsConfig(clientID string, clientSecret string, tenantID string, env azure.Environment) auth.ClientCredentialsConfig {
  854. return auth.ClientCredentialsConfig{
  855. ClientID: clientID,
  856. ClientSecret: clientSecret,
  857. TenantID: tenantID,
  858. Resource: env.ResourceManagerEndpoint,
  859. AADEndpoint: env.ActiveDirectoryEndpoint,
  860. }
  861. }
  862. func (az *Azure) addPricing(features string, azurePricing *AzurePricing) {
  863. if az.Pricing == nil {
  864. az.Pricing = map[string]*AzurePricing{}
  865. }
  866. az.Pricing[features] = azurePricing
  867. }
  868. // AllNodePricing returns the Azure pricing objects stored
  869. func (az *Azure) AllNodePricing() (interface{}, error) {
  870. az.DownloadPricingDataLock.RLock()
  871. defer az.DownloadPricingDataLock.RUnlock()
  872. return az.Pricing, nil
  873. }
  874. // NodePricing returns Azure pricing data for a single node
  875. func (az *Azure) NodePricing(key Key) (*Node, error) {
  876. az.DownloadPricingDataLock.RLock()
  877. defer az.DownloadPricingDataLock.RUnlock()
  878. azKey, ok := key.(*azureKey)
  879. if !ok {
  880. return nil, fmt.Errorf("azure: NodePricing: key is of type %T", key)
  881. }
  882. config, _ := az.GetConfig()
  883. if slv, ok := azKey.Labels[config.SpotLabel]; ok && slv == config.SpotLabelValue && config.SpotLabel != "" && config.SpotLabelValue != "" {
  884. features := strings.Split(azKey.Features(), ",")
  885. region := features[0]
  886. instance := features[1]
  887. spotFeatures := fmt.Sprintf("%s,%s,%s", region, instance, "spot")
  888. if n, ok := az.Pricing[spotFeatures]; ok {
  889. log.DedupedInfof(5, "Returning pricing for node %s: %+v from key %s", azKey, n, spotFeatures)
  890. if azKey.isValidGPUNode() {
  891. n.Node.GPU = "1" // TODO: support multiple GPUs
  892. }
  893. return n.Node, nil
  894. }
  895. log.Infof("[Info] found spot instance, trying to get retail price for %s: %s, ", spotFeatures, azKey)
  896. spotCost, err := getRetailPrice(region, instance, config.CurrencyCode, true)
  897. if err != nil {
  898. log.DedupedWarningf(5, "failed to retrieve spot retail pricing")
  899. } else {
  900. gpu := ""
  901. if azKey.isValidGPUNode() {
  902. gpu = "1"
  903. }
  904. spotNode := &Node{
  905. Cost: spotCost,
  906. UsageType: "spot",
  907. GPU: gpu,
  908. }
  909. az.addPricing(spotFeatures, &AzurePricing{
  910. Node: spotNode,
  911. })
  912. return spotNode, nil
  913. }
  914. }
  915. if n, ok := az.Pricing[azKey.Features()]; ok {
  916. klog.V(4).Infof("Returning pricing for node %s: %+v from key %s", azKey, n, azKey.Features())
  917. if azKey.isValidGPUNode() {
  918. n.Node.GPU = azKey.GetGPUCount()
  919. }
  920. return n.Node, nil
  921. }
  922. klog.V(1).Infof("[Warning] no pricing data found for %s: %s", azKey.Features(), azKey)
  923. c, err := az.GetConfig()
  924. if err != nil {
  925. return nil, fmt.Errorf("No default pricing data available")
  926. }
  927. if azKey.isValidGPUNode() {
  928. return &Node{
  929. VCPUCost: c.CPU,
  930. RAMCost: c.RAM,
  931. UsesBaseCPUPrice: true,
  932. GPUCost: c.GPU,
  933. GPU: azKey.GetGPUCount(),
  934. }, nil
  935. }
  936. return &Node{
  937. VCPUCost: c.CPU,
  938. RAMCost: c.RAM,
  939. UsesBaseCPUPrice: true,
  940. }, nil
  941. }
  942. // Stubbed NetworkPricing for Azure. Pull directly from azure.json for now
  943. func (az *Azure) NetworkPricing() (*Network, error) {
  944. cpricing, err := az.Config.GetCustomPricingData()
  945. if err != nil {
  946. return nil, err
  947. }
  948. znec, err := strconv.ParseFloat(cpricing.ZoneNetworkEgress, 64)
  949. if err != nil {
  950. return nil, err
  951. }
  952. rnec, err := strconv.ParseFloat(cpricing.RegionNetworkEgress, 64)
  953. if err != nil {
  954. return nil, err
  955. }
  956. inec, err := strconv.ParseFloat(cpricing.InternetNetworkEgress, 64)
  957. if err != nil {
  958. return nil, err
  959. }
  960. return &Network{
  961. ZoneNetworkEgressCost: znec,
  962. RegionNetworkEgressCost: rnec,
  963. InternetNetworkEgressCost: inec,
  964. }, nil
  965. }
  966. func (azr *Azure) LoadBalancerPricing() (*LoadBalancer, error) {
  967. fffrc := 0.025
  968. afrc := 0.010
  969. lbidc := 0.008
  970. numForwardingRules := 1.0
  971. dataIngressGB := 0.0
  972. var totalCost float64
  973. if numForwardingRules < 5 {
  974. totalCost = fffrc*numForwardingRules + lbidc*dataIngressGB
  975. } else {
  976. totalCost = fffrc*5 + afrc*(numForwardingRules-5) + lbidc*dataIngressGB
  977. }
  978. return &LoadBalancer{
  979. Cost: totalCost,
  980. }, nil
  981. }
  982. type azurePvKey struct {
  983. Labels map[string]string
  984. StorageClass string
  985. StorageClassParameters map[string]string
  986. DefaultRegion string
  987. ProviderId string
  988. }
  989. func (az *Azure) GetPVKey(pv *v1.PersistentVolume, parameters map[string]string, defaultRegion string) PVKey {
  990. providerID := ""
  991. if pv.Spec.AzureDisk != nil {
  992. providerID = pv.Spec.AzureDisk.DiskName
  993. }
  994. return &azurePvKey{
  995. Labels: pv.Labels,
  996. StorageClass: pv.Spec.StorageClassName,
  997. StorageClassParameters: parameters,
  998. DefaultRegion: defaultRegion,
  999. ProviderId: providerID,
  1000. }
  1001. }
  1002. func (key *azurePvKey) ID() string {
  1003. return key.ProviderId
  1004. }
  1005. func (key *azurePvKey) GetStorageClass() string {
  1006. return key.StorageClass
  1007. }
  1008. func (key *azurePvKey) Features() string {
  1009. storageClass := key.StorageClassParameters["storageaccounttype"]
  1010. storageSKU := key.StorageClassParameters["skuName"]
  1011. if storageClass != "" {
  1012. if strings.EqualFold(storageClass, "Premium_LRS") {
  1013. storageClass = AzureDiskPremiumSSDStorageClass
  1014. } else if strings.EqualFold(storageClass, "StandardSSD_LRS") {
  1015. storageClass = AzureDiskStandardSSDStorageClass
  1016. } else if strings.EqualFold(storageClass, "Standard_LRS") {
  1017. storageClass = AzureDiskStandardStorageClass
  1018. }
  1019. } else {
  1020. if strings.EqualFold(storageSKU, "Premium_LRS") {
  1021. storageClass = AzureFilePremiumStorageClass
  1022. } else if strings.EqualFold(storageSKU, "Standard_LRS") {
  1023. storageClass = AzureFileStandardStorageClass
  1024. }
  1025. }
  1026. if region, ok := util.GetRegion(key.Labels); ok {
  1027. return region + "," + storageClass
  1028. }
  1029. return key.DefaultRegion + "," + storageClass
  1030. }
  1031. func (*Azure) GetAddresses() ([]byte, error) {
  1032. return nil, nil
  1033. }
  1034. func (*Azure) GetDisks() ([]byte, error) {
  1035. return nil, nil
  1036. }
  1037. func (az *Azure) ClusterInfo() (map[string]string, error) {
  1038. remoteEnabled := env.IsRemoteEnabled()
  1039. m := make(map[string]string)
  1040. m["name"] = "Azure Cluster #1"
  1041. c, err := az.GetConfig()
  1042. if err != nil {
  1043. return nil, err
  1044. }
  1045. if c.ClusterName != "" {
  1046. m["name"] = c.ClusterName
  1047. }
  1048. m["provider"] = "azure"
  1049. m["account"] = az.clusterAccountId
  1050. m["region"] = az.clusterRegion
  1051. m["remoteReadEnabled"] = strconv.FormatBool(remoteEnabled)
  1052. m["id"] = env.GetClusterID()
  1053. return m, nil
  1054. }
  1055. func (az *Azure) UpdateConfigFromConfigMap(a map[string]string) (*CustomPricing, error) {
  1056. return az.Config.UpdateFromMap(a)
  1057. }
  1058. func (az *Azure) UpdateConfig(r io.Reader, updateType string) (*CustomPricing, error) {
  1059. defer az.DownloadPricingData()
  1060. return az.Config.Update(func(c *CustomPricing) error {
  1061. a := make(map[string]interface{})
  1062. err := json.NewDecoder(r).Decode(&a)
  1063. if err != nil {
  1064. return err
  1065. }
  1066. for k, v := range a {
  1067. kUpper := strings.Title(k) // Just so we consistently supply / receive the same values, uppercase the first letter.
  1068. vstr, ok := v.(string)
  1069. if ok {
  1070. err := SetCustomPricingField(c, kUpper, vstr)
  1071. if err != nil {
  1072. return err
  1073. }
  1074. } else {
  1075. return fmt.Errorf("type error while updating config for %s", kUpper)
  1076. }
  1077. }
  1078. if env.IsRemoteEnabled() {
  1079. err := UpdateClusterMeta(env.GetClusterID(), c.ClusterName)
  1080. if err != nil {
  1081. return err
  1082. }
  1083. }
  1084. return nil
  1085. })
  1086. }
  1087. func (az *Azure) GetConfig() (*CustomPricing, error) {
  1088. c, err := az.Config.GetCustomPricingData()
  1089. if err != nil {
  1090. return nil, err
  1091. }
  1092. if c.Discount == "" {
  1093. c.Discount = "0%"
  1094. }
  1095. if c.NegotiatedDiscount == "" {
  1096. c.NegotiatedDiscount = "0%"
  1097. }
  1098. if c.CurrencyCode == "" {
  1099. c.CurrencyCode = "USD"
  1100. }
  1101. if c.AzureBillingRegion == "" {
  1102. c.AzureBillingRegion = "US"
  1103. }
  1104. if c.ShareTenancyCosts == "" {
  1105. c.ShareTenancyCosts = defaultShareTenancyCost
  1106. }
  1107. if c.SpotLabel == "" {
  1108. c.SpotLabel = defaultSpotLabel
  1109. }
  1110. if c.SpotLabelValue == "" {
  1111. c.SpotLabelValue = defaultSpotLabelValue
  1112. }
  1113. return c, nil
  1114. }
  1115. func (az *Azure) ApplyReservedInstancePricing(nodes map[string]*Node) {
  1116. }
  1117. func (az *Azure) PVPricing(pvk PVKey) (*PV, error) {
  1118. az.DownloadPricingDataLock.RLock()
  1119. defer az.DownloadPricingDataLock.RUnlock()
  1120. pricing, ok := az.Pricing[pvk.Features()]
  1121. if !ok {
  1122. klog.V(4).Infof("Persistent Volume pricing not found for %s: %s", pvk.GetStorageClass(), pvk.Features())
  1123. return &PV{}, nil
  1124. }
  1125. return pricing.PV, nil
  1126. }
  1127. func (az *Azure) GetLocalStorageQuery(window, offset time.Duration, rate bool, used bool) string {
  1128. return ""
  1129. }
  1130. func (az *Azure) ServiceAccountStatus() *ServiceAccountStatus {
  1131. checks := []*ServiceAccountCheck{}
  1132. for _, v := range az.ServiceAccountChecks {
  1133. checks = append(checks, v)
  1134. }
  1135. return &ServiceAccountStatus{
  1136. Checks: checks,
  1137. }
  1138. }
  1139. const rateCardPricingSource = "Rate Card API"
  1140. // PricingSourceStatus returns the status of the rate card api
  1141. func (az *Azure) PricingSourceStatus() map[string]*PricingSource {
  1142. sources := make(map[string]*PricingSource)
  1143. errMsg := ""
  1144. if az.RateCardPricingError != nil {
  1145. errMsg = az.RateCardPricingError.Error()
  1146. }
  1147. rcps := &PricingSource{
  1148. Name: rateCardPricingSource,
  1149. Error: errMsg,
  1150. }
  1151. if rcps.Error != "" {
  1152. rcps.Available = false
  1153. } else if len(az.Pricing) == 0 {
  1154. rcps.Error = "No Pricing Data Available"
  1155. rcps.Available = false
  1156. } else {
  1157. rcps.Available = true
  1158. }
  1159. sources[rateCardPricingSource] = rcps
  1160. return sources
  1161. }
  1162. func (*Azure) ClusterManagementPricing() (string, float64, error) {
  1163. return "", 0.0, nil
  1164. }
  1165. func (az *Azure) CombinedDiscountForNode(instanceType string, isPreemptible bool, defaultDiscount, negotiatedDiscount float64) float64 {
  1166. return 1.0 - ((1.0 - defaultDiscount) * (1.0 - negotiatedDiscount))
  1167. }
  1168. func (az *Azure) Regions() []string {
  1169. return azureRegions
  1170. }
  1171. func parseAzureSubscriptionID(id string) string {
  1172. // azure:///subscriptions/0badafdf-1234-abcd-wxyz-123456789/...
  1173. // => 0badafdf-1234-abcd-wxyz-123456789
  1174. rx := regexp.MustCompile("azure:///subscriptions/([^/]*)/*")
  1175. match := rx.FindStringSubmatch(id)
  1176. if len(match) >= 2 {
  1177. return match[1]
  1178. }
  1179. // Return empty string if an account could not be parsed from provided string
  1180. return ""
  1181. }