azureprovider.go 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782
  1. package cloud
  2. import (
  3. "context"
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "io/ioutil"
  8. "regexp"
  9. "strconv"
  10. "strings"
  11. "sync"
  12. "github.com/kubecost/cost-model/pkg/clustercache"
  13. "github.com/kubecost/cost-model/pkg/env"
  14. "github.com/kubecost/cost-model/pkg/util"
  15. "github.com/Azure/azure-sdk-for-go/services/compute/mgmt/2017-09-01/skus"
  16. "github.com/Azure/azure-sdk-for-go/services/containerservice/mgmt/2018-03-31/containerservice"
  17. "github.com/Azure/azure-sdk-for-go/services/preview/commerce/mgmt/2015-06-01-preview/commerce"
  18. "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2016-06-01/subscriptions"
  19. "github.com/Azure/azure-sdk-for-go/services/resources/mgmt/2018-05-01/resources"
  20. "github.com/Azure/go-autorest/autorest"
  21. "github.com/Azure/go-autorest/autorest/azure"
  22. "github.com/Azure/go-autorest/autorest/azure/auth"
  23. v1 "k8s.io/api/core/v1"
  24. "k8s.io/klog"
  25. )
  26. const (
  27. AzurePremiumStorageClass = "premium"
  28. AzureStandardStorageClass = "standard"
  29. )
  30. var (
  31. regionCodeMappings = map[string]string{
  32. "ap": "asia",
  33. "au": "australia",
  34. "br": "brazil",
  35. "ca": "canada",
  36. "eu": "europe",
  37. "fr": "france",
  38. "in": "india",
  39. "ja": "japan",
  40. "kr": "korea",
  41. "uk": "uk",
  42. "us": "us",
  43. "za": "southafrica",
  44. }
  45. //mtBasic, _ = regexp.Compile("^BASIC.A\\d+[_Promo]*$")
  46. //mtStandardA, _ = regexp.Compile("^A\\d+[_Promo]*$")
  47. mtStandardB, _ = regexp.Compile(`^Standard_B\d+m?[_v\d]*[_Promo]*$`)
  48. mtStandardD, _ = regexp.Compile(`^Standard_D\d[_v\d]*[_Promo]*$`)
  49. mtStandardE, _ = regexp.Compile(`^Standard_E\d+i?[_v\d]*[_Promo]*$`)
  50. mtStandardF, _ = regexp.Compile(`^Standard_F\d+[_v\d]*[_Promo]*$`)
  51. mtStandardG, _ = regexp.Compile(`^Standard_G\d+[_v\d]*[_Promo]*$`)
  52. mtStandardL, _ = regexp.Compile(`^Standard_L\d+[_v\d]*[_Promo]*$`)
  53. mtStandardM, _ = regexp.Compile(`^Standard_M\d+[m|t|l]*s[_v\d]*[_Promo]*$`)
  54. mtStandardN, _ = regexp.Compile(`^Standard_N[C|D|V]\d+r?[_v\d]*[_Promo]*$`)
  55. )
  56. var loadedAzureSecret bool = false
  57. var azureSecret *AzureServiceKey = nil
  58. type regionParts []string
  59. func (r regionParts) String() string {
  60. var result string
  61. for _, p := range r {
  62. result += p
  63. }
  64. return result
  65. }
  66. func getRegions(service string, subscriptionsClient subscriptions.Client, providersClient resources.ProvidersClient, subscriptionID string) (map[string]string, error) {
  67. allLocations := make(map[string]string)
  68. supLocations := make(map[string]string)
  69. // retrieve all locations for the subscription id (some of them may not be supported by the required provider)
  70. if locations, err := subscriptionsClient.ListLocations(context.TODO(), subscriptionID); err == nil {
  71. // fill up the map: DisplayName - > Name
  72. for _, loc := range *locations.Value {
  73. allLocations[*loc.DisplayName] = *loc.Name
  74. }
  75. } else {
  76. return nil, err
  77. }
  78. // identify supported locations for the namespace and resource type
  79. const (
  80. providerNamespaceForCompute = "Microsoft.Compute"
  81. resourceTypeForCompute = "locations/vmSizes"
  82. providerNamespaceForAks = "Microsoft.ContainerService"
  83. resourceTypeForAks = "managedClusters"
  84. )
  85. switch service {
  86. case "aks":
  87. if providers, err := providersClient.Get(context.TODO(), providerNamespaceForAks, ""); err == nil {
  88. for _, pr := range *providers.ResourceTypes {
  89. if *pr.ResourceType == resourceTypeForAks {
  90. for _, displName := range *pr.Locations {
  91. if loc, ok := allLocations[displName]; ok {
  92. supLocations[loc] = displName
  93. } else {
  94. klog.V(1).Infof("unsupported cloud region %s", loc)
  95. }
  96. }
  97. break
  98. }
  99. }
  100. } else {
  101. return nil, err
  102. }
  103. return supLocations, nil
  104. default:
  105. if providers, err := providersClient.Get(context.TODO(), providerNamespaceForCompute, ""); err == nil {
  106. for _, pr := range *providers.ResourceTypes {
  107. if *pr.ResourceType == resourceTypeForCompute {
  108. for _, displName := range *pr.Locations {
  109. if loc, ok := allLocations[displName]; ok {
  110. supLocations[loc] = displName
  111. } else {
  112. klog.V(1).Infof("unsupported cloud region %s", loc)
  113. }
  114. }
  115. break
  116. }
  117. }
  118. } else {
  119. return nil, err
  120. }
  121. return supLocations, nil
  122. }
  123. }
  124. func toRegionID(meterRegion string, regions map[string]string) (string, error) {
  125. var rp regionParts = strings.Split(strings.ToLower(meterRegion), " ")
  126. regionCode := regionCodeMappings[rp[0]]
  127. lastPart := rp[len(rp)-1]
  128. var regionIds []string
  129. if _, err := strconv.Atoi(lastPart); err == nil {
  130. regionIds = []string{
  131. fmt.Sprintf("%s%s%s", regionCode, rp[1:len(rp)-1], lastPart),
  132. fmt.Sprintf("%s%s%s", rp[1:len(rp)-1], regionCode, lastPart),
  133. }
  134. } else {
  135. regionIds = []string{
  136. fmt.Sprintf("%s%s", regionCode, rp[1:]),
  137. fmt.Sprintf("%s%s", rp[1:], regionCode),
  138. }
  139. }
  140. for _, regionID := range regionIds {
  141. if checkRegionID(regionID, regions) {
  142. return regionID, nil
  143. }
  144. }
  145. return "", fmt.Errorf("Couldn't find region")
  146. }
  147. func checkRegionID(regionID string, regions map[string]string) bool {
  148. for region := range regions {
  149. if regionID == region {
  150. return true
  151. }
  152. }
  153. return false
  154. }
  155. // AzurePricing either contains a Node or PV
  156. type AzurePricing struct {
  157. Node *Node
  158. PV *PV
  159. }
  160. type Azure struct {
  161. Pricing map[string]*AzurePricing
  162. DownloadPricingDataLock sync.RWMutex
  163. Clientset clustercache.ClusterCache
  164. Config *ProviderConfig
  165. }
  166. type azureKey struct {
  167. Labels map[string]string
  168. GPULabel string
  169. GPULabelValue string
  170. }
  171. func (k *azureKey) Features() string {
  172. region := strings.ToLower(k.Labels[v1.LabelZoneRegion])
  173. instance := k.Labels[v1.LabelInstanceType]
  174. usageType := "ondemand"
  175. return fmt.Sprintf("%s,%s,%s", region, instance, usageType)
  176. }
  177. func (k *azureKey) GPUType() string {
  178. if t, ok := k.Labels[k.GPULabel]; ok {
  179. return t
  180. }
  181. return ""
  182. }
  183. func (k *azureKey) ID() string {
  184. return ""
  185. }
  186. // Represents an azure app key
  187. type AzureAppKey struct {
  188. AppID string `json:"appId"`
  189. DisplayName string `json:"displayName"`
  190. Name string `json:"name"`
  191. Password string `json:"password"`
  192. Tenant string `json:"tenant"`
  193. }
  194. // Azure service key for a specific subscription
  195. type AzureServiceKey struct {
  196. SubscriptionID string `json:"subscriptionId"`
  197. ServiceKey *AzureAppKey `json:"serviceKey"`
  198. }
  199. // Validity check on service key
  200. func (ask *AzureServiceKey) IsValid() bool {
  201. return ask.SubscriptionID != "" &&
  202. ask.ServiceKey != nil &&
  203. ask.ServiceKey.AppID != "" &&
  204. ask.ServiceKey.Password != "" &&
  205. ask.ServiceKey.Tenant != ""
  206. }
  207. // Loads the azure authentication via configuration or a secret set at install time.
  208. func (az *Azure) getAzureAuth(forceReload bool, cp *CustomPricing) (subscriptionID, clientID, clientSecret, tenantID string) {
  209. // 1. Check config values first (set from frontend UI)
  210. if cp.AzureSubscriptionID != "" && cp.AzureClientID != "" && cp.AzureClientSecret != "" && cp.AzureTenantID != "" {
  211. subscriptionID = cp.AzureSubscriptionID
  212. clientID = cp.AzureClientID
  213. clientSecret = cp.AzureClientSecret
  214. tenantID = cp.AzureTenantID
  215. return
  216. }
  217. // 2. Check for secret
  218. s, _ := az.loadAzureAuthSecret(forceReload)
  219. if s != nil && s.IsValid() {
  220. subscriptionID = s.SubscriptionID
  221. clientID = s.ServiceKey.AppID
  222. clientSecret = s.ServiceKey.Password
  223. tenantID = s.ServiceKey.Tenant
  224. return
  225. }
  226. // 3. Empty values
  227. return "", "", "", ""
  228. }
  229. // Load once and cache the result (even on failure). This is an install time secret, so
  230. // we don't expect the secret to change. If it does, however, we can force reload using
  231. // the input parameter.
  232. func (az *Azure) loadAzureAuthSecret(force bool) (*AzureServiceKey, error) {
  233. if !force && loadedAzureSecret {
  234. return azureSecret, nil
  235. }
  236. loadedAzureSecret = true
  237. exists, err := util.FileExists(authSecretPath)
  238. if !exists || err != nil {
  239. return nil, fmt.Errorf("Failed to locate service account file: %s", authSecretPath)
  240. }
  241. result, err := ioutil.ReadFile(authSecretPath)
  242. if err != nil {
  243. return nil, err
  244. }
  245. var ask AzureServiceKey
  246. err = json.Unmarshal(result, &ask)
  247. if err != nil {
  248. return nil, err
  249. }
  250. azureSecret = &ask
  251. return azureSecret, nil
  252. }
  253. func (az *Azure) GetKey(labels map[string]string, n *v1.Node) Key {
  254. cfg, err := az.GetConfig()
  255. if err != nil {
  256. klog.Infof("Error loading azure custom pricing information")
  257. }
  258. // azure defaults, see https://docs.microsoft.com/en-us/azure/aks/gpu-cluster
  259. gpuLabel := "accelerator"
  260. gpuLabelValue := "nvidia"
  261. if cfg.GpuLabel != "" {
  262. gpuLabel = cfg.GpuLabel
  263. }
  264. if cfg.GpuLabelValue != "" {
  265. gpuLabelValue = cfg.GpuLabelValue
  266. }
  267. return &azureKey{
  268. Labels: labels,
  269. GPULabel: gpuLabel,
  270. GPULabelValue: gpuLabelValue,
  271. }
  272. }
  273. // CreateString builds strings effectively
  274. func createString(keys ...string) string {
  275. var b strings.Builder
  276. for _, key := range keys {
  277. b.WriteString(key)
  278. }
  279. return b.String()
  280. }
  281. func transformMachineType(subCategory string, mt []string) []string {
  282. switch {
  283. case strings.Contains(subCategory, "Basic"):
  284. return []string{createString("Basic_", mt[0])}
  285. case len(mt) == 2:
  286. return []string{createString("Standard_", mt[0]), createString("Standard_", mt[1])}
  287. default:
  288. return []string{createString("Standard_", mt[0])}
  289. }
  290. }
  291. func addSuffix(mt string, suffixes ...string) []string {
  292. result := make([]string, len(suffixes))
  293. var suffix string
  294. parts := strings.Split(mt, "_")
  295. if len(parts) > 2 {
  296. for _, p := range parts[2:] {
  297. suffix = createString(suffix, "_", p)
  298. }
  299. }
  300. for i, s := range suffixes {
  301. result[i] = createString(parts[0], "_", parts[1], s, suffix)
  302. }
  303. return result
  304. }
  305. func getMachineTypeVariants(mt string) []string {
  306. switch {
  307. case mtStandardB.MatchString(mt):
  308. return []string{createString(mt, "s")}
  309. case mtStandardD.MatchString(mt):
  310. var result []string
  311. result = append(result, addSuffix(mt, "s")[0])
  312. dsType := strings.Replace(mt, "Standard_D", "Standard_DS", -1)
  313. result = append(result, dsType)
  314. result = append(result, addSuffix(dsType, "-1", "-2", "-4", "-8")...)
  315. return result
  316. case mtStandardE.MatchString(mt):
  317. return addSuffix(mt, "s", "-2s", "-4s", "-8s", "-16s", "-32s")
  318. case mtStandardF.MatchString(mt):
  319. return addSuffix(mt, "s")
  320. case mtStandardG.MatchString(mt):
  321. var result []string
  322. gsType := strings.Replace(mt, "Standard_G", "Standard_GS", -1)
  323. result = append(result, gsType)
  324. return append(result, addSuffix(gsType, "-4", "-8", "-16")...)
  325. case mtStandardL.MatchString(mt):
  326. return addSuffix(mt, "s")
  327. case mtStandardM.MatchString(mt) && strings.HasSuffix(mt, "ms"):
  328. base := strings.TrimSuffix(mt, "ms")
  329. return addSuffix(base, "-2ms", "-4ms", "-8ms", "-16ms", "-32ms", "-64ms")
  330. case mtStandardM.MatchString(mt) && (strings.HasSuffix(mt, "ls") || strings.HasSuffix(mt, "ts")):
  331. return []string{}
  332. case mtStandardM.MatchString(mt) && strings.HasSuffix(mt, "s"):
  333. base := strings.TrimSuffix(mt, "s")
  334. return addSuffix(base, "", "m")
  335. case mtStandardN.MatchString(mt):
  336. return addSuffix(mt, "s")
  337. }
  338. return []string{}
  339. }
  340. func (az *Azure) GetManagementPlatform() (string, error) {
  341. nodes := az.Clientset.GetAllNodes()
  342. if len(nodes) > 0 {
  343. n := nodes[0]
  344. providerID := n.Spec.ProviderID
  345. if strings.Contains(providerID, "aks") {
  346. return "aks", nil
  347. }
  348. }
  349. return "", nil
  350. }
  351. // DownloadPricingData uses provided azure "best guesses" for pricing
  352. func (az *Azure) DownloadPricingData() error {
  353. az.DownloadPricingDataLock.Lock()
  354. defer az.DownloadPricingDataLock.Unlock()
  355. config, err := az.GetConfig()
  356. if err != nil {
  357. return err
  358. }
  359. // Load the service provider keys
  360. subscriptionID, clientID, clientSecret, tenantID := az.getAzureAuth(false, config)
  361. config.AzureSubscriptionID = subscriptionID
  362. config.AzureClientID = clientID
  363. config.AzureClientSecret = clientSecret
  364. config.AzureTenantID = tenantID
  365. var authorizer autorest.Authorizer
  366. if config.AzureClientID != "" && config.AzureClientSecret != "" && config.AzureTenantID != "" {
  367. credentialsConfig := auth.NewClientCredentialsConfig(config.AzureClientID, config.AzureClientSecret, config.AzureTenantID)
  368. a, err := credentialsConfig.Authorizer()
  369. if err != nil {
  370. return err
  371. }
  372. authorizer = a
  373. }
  374. if authorizer == nil {
  375. a, err := auth.NewAuthorizerFromEnvironment()
  376. authorizer = a
  377. if err != nil { // Failed to create authorizer from environment, try from file
  378. a, err := auth.NewAuthorizerFromFile(azure.PublicCloud.ResourceManagerEndpoint)
  379. if err != nil {
  380. return err
  381. }
  382. authorizer = a
  383. }
  384. }
  385. sClient := subscriptions.NewClient()
  386. sClient.Authorizer = authorizer
  387. rcClient := commerce.NewRateCardClient(config.AzureSubscriptionID)
  388. rcClient.Authorizer = authorizer
  389. skusClient := skus.NewResourceSkusClient(config.AzureSubscriptionID)
  390. skusClient.Authorizer = authorizer
  391. providersClient := resources.NewProvidersClient(config.AzureSubscriptionID)
  392. providersClient.Authorizer = authorizer
  393. containerServiceClient := containerservice.NewContainerServicesClient(config.AzureSubscriptionID)
  394. containerServiceClient.Authorizer = authorizer
  395. 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)
  396. klog.Infof("Using ratecard query %s", rateCardFilter)
  397. result, err := rcClient.Get(context.TODO(), rateCardFilter)
  398. if err != nil {
  399. return err
  400. }
  401. allPrices := make(map[string]*AzurePricing)
  402. regions, err := getRegions("compute", sClient, providersClient, config.AzureSubscriptionID)
  403. if err != nil {
  404. return err
  405. }
  406. c, err := az.GetConfig()
  407. if err != nil {
  408. return err
  409. }
  410. baseCPUPrice := c.CPU
  411. for _, v := range *result.Meters {
  412. meterName := *v.MeterName
  413. meterRegion := *v.MeterRegion
  414. meterCategory := *v.MeterCategory
  415. meterSubCategory := *v.MeterSubCategory
  416. region, err := toRegionID(meterRegion, regions)
  417. if err != nil {
  418. continue
  419. }
  420. if !strings.Contains(meterSubCategory, "Windows") {
  421. if strings.Contains(meterCategory, "Storage") {
  422. if strings.Contains(meterSubCategory, "HDD") || strings.Contains(meterSubCategory, "SSD") {
  423. var storageClass string = ""
  424. if strings.Contains(meterName, "S4 ") {
  425. storageClass = AzureStandardStorageClass
  426. } else if strings.Contains(meterName, "P4 ") {
  427. storageClass = AzurePremiumStorageClass
  428. }
  429. if storageClass != "" {
  430. var priceInUsd float64
  431. if len(v.MeterRates) < 1 {
  432. klog.V(1).Infof("missing rate info %+v", map[string]interface{}{"MeterSubCategory": *v.MeterSubCategory, "region": region})
  433. continue
  434. }
  435. for _, rate := range v.MeterRates {
  436. priceInUsd += *rate
  437. }
  438. // rate is in disk per month, resolve price per hour, then GB per hour
  439. pricePerHour := priceInUsd / 730.0 / 32.0
  440. priceStr := fmt.Sprintf("%f", pricePerHour)
  441. key := region + "," + storageClass
  442. klog.V(4).Infof("Adding PV.Key: %s, Cost: %s", key, priceStr)
  443. allPrices[key] = &AzurePricing{
  444. PV: &PV{
  445. Cost: priceStr,
  446. Region: region,
  447. },
  448. }
  449. }
  450. }
  451. }
  452. if strings.Contains(meterCategory, "Virtual Machines") {
  453. // not available now
  454. if strings.Contains(meterSubCategory, "Promo") {
  455. continue
  456. }
  457. usageType := ""
  458. if !strings.Contains(meterName, "Low Priority") {
  459. usageType = "ondemand"
  460. } else {
  461. usageType = "preemptible"
  462. }
  463. var instanceTypes []string
  464. name := strings.TrimSuffix(meterName, " Low Priority")
  465. instanceType := strings.Split(name, "/")
  466. for _, it := range instanceType {
  467. instanceTypes = append(instanceTypes, strings.Replace(it, " ", "_", 1))
  468. }
  469. instanceTypes = transformMachineType(meterSubCategory, instanceTypes)
  470. if strings.Contains(name, "Expired") {
  471. instanceTypes = []string{}
  472. }
  473. var priceInUsd float64
  474. if len(v.MeterRates) < 1 {
  475. klog.V(1).Infof("missing rate info %+v", map[string]interface{}{"MeterSubCategory": *v.MeterSubCategory, "region": region})
  476. continue
  477. }
  478. for _, rate := range v.MeterRates {
  479. priceInUsd += *rate
  480. }
  481. priceStr := fmt.Sprintf("%f", priceInUsd)
  482. for _, instanceType := range instanceTypes {
  483. key := fmt.Sprintf("%s,%s,%s", region, instanceType, usageType)
  484. allPrices[key] = &AzurePricing{
  485. Node: &Node{
  486. Cost: priceStr,
  487. BaseCPUPrice: baseCPUPrice,
  488. },
  489. }
  490. }
  491. }
  492. }
  493. }
  494. az.Pricing = allPrices
  495. return nil
  496. }
  497. // AllNodePricing returns the Azure pricing objects stored
  498. func (az *Azure) AllNodePricing() (interface{}, error) {
  499. az.DownloadPricingDataLock.RLock()
  500. defer az.DownloadPricingDataLock.RUnlock()
  501. return az.Pricing, nil
  502. }
  503. // NodePricing returns Azure pricing data for a single node
  504. func (az *Azure) NodePricing(key Key) (*Node, error) {
  505. az.DownloadPricingDataLock.RLock()
  506. defer az.DownloadPricingDataLock.RUnlock()
  507. if n, ok := az.Pricing[key.Features()]; ok {
  508. klog.V(4).Infof("Returning pricing for node %s: %+v from key %s", key, n, key.Features())
  509. if key.GPUType() != "" {
  510. n.Node.GPU = "1" // TODO: support multiple GPUs
  511. }
  512. return n.Node, nil
  513. }
  514. klog.V(1).Infof("[Warning] no pricing data found for %s: %s", key.Features(), key)
  515. c, err := az.GetConfig()
  516. if err != nil {
  517. return nil, fmt.Errorf("No default pricing data available")
  518. }
  519. if key.GPUType() != "" {
  520. return &Node{
  521. VCPUCost: c.CPU,
  522. RAMCost: c.RAM,
  523. GPUCost: c.GPU,
  524. GPU: "1", // TODO: support multiple GPUs
  525. }, nil
  526. }
  527. return &Node{
  528. VCPUCost: c.CPU,
  529. RAMCost: c.RAM,
  530. UsesBaseCPUPrice: true,
  531. }, nil
  532. }
  533. // Stubbed NetworkPricing for Azure. Pull directly from azure.json for now
  534. func (az *Azure) NetworkPricing() (*Network, error) {
  535. cpricing, err := az.Config.GetCustomPricingData()
  536. if err != nil {
  537. return nil, err
  538. }
  539. znec, err := strconv.ParseFloat(cpricing.ZoneNetworkEgress, 64)
  540. if err != nil {
  541. return nil, err
  542. }
  543. rnec, err := strconv.ParseFloat(cpricing.RegionNetworkEgress, 64)
  544. if err != nil {
  545. return nil, err
  546. }
  547. inec, err := strconv.ParseFloat(cpricing.InternetNetworkEgress, 64)
  548. if err != nil {
  549. return nil, err
  550. }
  551. return &Network{
  552. ZoneNetworkEgressCost: znec,
  553. RegionNetworkEgressCost: rnec,
  554. InternetNetworkEgressCost: inec,
  555. }, nil
  556. }
  557. type azurePvKey struct {
  558. Labels map[string]string
  559. StorageClass string
  560. StorageClassParameters map[string]string
  561. DefaultRegion string
  562. }
  563. func (az *Azure) GetPVKey(pv *v1.PersistentVolume, parameters map[string]string, defaultRegion string) PVKey {
  564. return &azurePvKey{
  565. Labels: pv.Labels,
  566. StorageClass: pv.Spec.StorageClassName,
  567. StorageClassParameters: parameters,
  568. DefaultRegion: defaultRegion,
  569. }
  570. }
  571. func (key *azurePvKey) GetStorageClass() string {
  572. return key.StorageClass
  573. }
  574. func (key *azurePvKey) Features() string {
  575. storageClass := key.StorageClassParameters["storageaccounttype"]
  576. if strings.EqualFold(storageClass, "Premium_LRS") {
  577. storageClass = AzurePremiumStorageClass
  578. } else if strings.EqualFold(storageClass, "Standard_LRS") {
  579. storageClass = AzureStandardStorageClass
  580. }
  581. if region, ok := key.Labels[v1.LabelZoneRegion]; ok {
  582. return region + "," + storageClass
  583. }
  584. return key.DefaultRegion + "," + storageClass
  585. }
  586. func (*Azure) GetAddresses() ([]byte, error) {
  587. return nil, nil
  588. }
  589. func (*Azure) GetDisks() ([]byte, error) {
  590. return nil, nil
  591. }
  592. func (az *Azure) ClusterInfo() (map[string]string, error) {
  593. remoteEnabled := env.IsRemoteEnabled()
  594. m := make(map[string]string)
  595. m["name"] = "Azure Cluster #1"
  596. c, err := az.GetConfig()
  597. if err != nil {
  598. return nil, err
  599. }
  600. if c.ClusterName != "" {
  601. m["name"] = c.ClusterName
  602. }
  603. m["provider"] = "azure"
  604. m["remoteReadEnabled"] = strconv.FormatBool(remoteEnabled)
  605. m["id"] = env.GetClusterID()
  606. return m, nil
  607. }
  608. func (az *Azure) UpdateConfigFromConfigMap(a map[string]string) (*CustomPricing, error) {
  609. return az.Config.UpdateFromMap(a)
  610. }
  611. func (az *Azure) UpdateConfig(r io.Reader, updateType string) (*CustomPricing, error) {
  612. defer az.DownloadPricingData()
  613. return az.Config.Update(func(c *CustomPricing) error {
  614. a := make(map[string]interface{})
  615. err := json.NewDecoder(r).Decode(&a)
  616. if err != nil {
  617. return err
  618. }
  619. for k, v := range a {
  620. kUpper := strings.Title(k) // Just so we consistently supply / receive the same values, uppercase the first letter.
  621. vstr, ok := v.(string)
  622. if ok {
  623. err := SetCustomPricingField(c, kUpper, vstr)
  624. if err != nil {
  625. return err
  626. }
  627. } else {
  628. sci := v.(map[string]interface{})
  629. sc := make(map[string]string)
  630. for k, val := range sci {
  631. sc[k] = val.(string)
  632. }
  633. c.SharedCosts = sc //todo: support reflection/multiple map fields
  634. }
  635. }
  636. if env.IsRemoteEnabled() {
  637. err := UpdateClusterMeta(env.GetClusterID(), c.ClusterName)
  638. if err != nil {
  639. return err
  640. }
  641. }
  642. return nil
  643. })
  644. }
  645. func (az *Azure) GetConfig() (*CustomPricing, error) {
  646. c, err := az.Config.GetCustomPricingData()
  647. if c.Discount == "" {
  648. c.Discount = "0%"
  649. }
  650. if c.NegotiatedDiscount == "" {
  651. c.NegotiatedDiscount = "0%"
  652. }
  653. if c.CurrencyCode == "" {
  654. c.CurrencyCode = "USD"
  655. }
  656. if c.AzureBillingRegion == "" {
  657. c.AzureBillingRegion = "US"
  658. }
  659. if err != nil {
  660. return nil, err
  661. }
  662. return c, nil
  663. }
  664. func (az *Azure) ExternalAllocations(string, string, []string, string, string, bool) ([]*OutOfClusterAllocation, error) {
  665. return nil, nil
  666. }
  667. func (az *Azure) ApplyReservedInstancePricing(nodes map[string]*Node) {
  668. }
  669. func (az *Azure) PVPricing(pvk PVKey) (*PV, error) {
  670. az.DownloadPricingDataLock.RLock()
  671. defer az.DownloadPricingDataLock.RUnlock()
  672. pricing, ok := az.Pricing[pvk.Features()]
  673. if !ok {
  674. klog.V(4).Infof("Persistent Volume pricing not found for %s: %s", pvk.GetStorageClass(), pvk.Features())
  675. return &PV{}, nil
  676. }
  677. return pricing.PV, nil
  678. }
  679. func (az *Azure) GetLocalStorageQuery(window, offset string, rate bool, used bool) string {
  680. return ""
  681. }
  682. func (az *Azure) ServiceAccountStatus() *ServiceAccountStatus {
  683. return &ServiceAccountStatus{
  684. Checks: []*ServiceAccountCheck{},
  685. }
  686. }