provider_test.go 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496
  1. package aws
  2. import (
  3. "bytes"
  4. "io/ioutil"
  5. "net/http"
  6. "net/url"
  7. "reflect"
  8. "testing"
  9. "github.com/opencost/opencost/pkg/cloud/models"
  10. )
  11. func Test_awsKey_getUsageType(t *testing.T) {
  12. type fields struct {
  13. Labels map[string]string
  14. ProviderID string
  15. }
  16. type args struct {
  17. labels map[string]string
  18. }
  19. tests := []struct {
  20. name string
  21. fields fields
  22. args args
  23. want string
  24. }{
  25. {
  26. // test with no labels should return false
  27. name: "Label does not have the capacityType label associated with it",
  28. args: args{
  29. labels: map[string]string{},
  30. },
  31. want: "",
  32. },
  33. {
  34. name: "EKS label with a capacityType set to empty string should return empty string",
  35. args: args{
  36. labels: map[string]string{
  37. EKSCapacityTypeLabel: "",
  38. },
  39. },
  40. want: "",
  41. },
  42. {
  43. name: "EKS label with capacityType set to a random value should return empty string",
  44. args: args{
  45. labels: map[string]string{
  46. EKSCapacityTypeLabel: "TEST_ME",
  47. },
  48. },
  49. want: "",
  50. },
  51. {
  52. name: "EKS label with capacityType set to spot should return spot",
  53. args: args{
  54. labels: map[string]string{
  55. EKSCapacityTypeLabel: EKSCapacitySpotTypeValue,
  56. },
  57. },
  58. want: PreemptibleType,
  59. },
  60. {
  61. name: "Karpenter label with a capacityType set to empty string should return empty string",
  62. args: args{
  63. labels: map[string]string{
  64. models.KarpenterCapacityTypeLabel: "",
  65. },
  66. },
  67. want: "",
  68. },
  69. {
  70. name: "Karpenter label with capacityType set to a random value should return empty string",
  71. args: args{
  72. labels: map[string]string{
  73. models.KarpenterCapacityTypeLabel: "TEST_ME",
  74. },
  75. },
  76. want: "",
  77. },
  78. {
  79. name: "Karpenter label with capacityType set to spot should return spot",
  80. args: args{
  81. labels: map[string]string{
  82. models.KarpenterCapacityTypeLabel: models.KarpenterCapacitySpotTypeValue,
  83. },
  84. },
  85. want: PreemptibleType,
  86. },
  87. }
  88. for _, tt := range tests {
  89. t.Run(tt.name, func(t *testing.T) {
  90. k := &awsKey{
  91. Labels: tt.fields.Labels,
  92. ProviderID: tt.fields.ProviderID,
  93. }
  94. if got := k.getUsageType(tt.args.labels); got != tt.want {
  95. t.Errorf("getUsageType() = %v, want %v", got, tt.want)
  96. }
  97. })
  98. }
  99. }
  100. // Test_populate_pricing
  101. //
  102. // Objective: To test core pricing population logic for AWS
  103. //
  104. // Case 0: US endpoints
  105. // Take a portion of json returned from ondemand terms in us endpoints
  106. // load the request into the http response and give it to the function
  107. // inspect the resulting aws object after the function returns and validate fields
  108. // Case 1: Chinese endpoints
  109. // Same as above US test case, except using CN PV offer codes
  110. // Validate populated fields in AWS object
  111. func Test_populate_pricing(t *testing.T) {
  112. awsTest := AWS{
  113. ValidPricingKeys: map[string]bool{},
  114. }
  115. inputkeys := map[string]bool{
  116. "us-east-2,m5.large,linux": true,
  117. }
  118. // Case 0
  119. awsUSEastString := `
  120. {
  121. "formatVersion" : "v1.0",
  122. "disclaimer" : "This pricing list is for informational purposes only. All prices are subject to the additional terms included in the pricing pages on http://aws.amazon.com. All Free Tier prices are also subject to the terms included at https://aws.amazon.com/free/",
  123. "offerCode" : "AmazonEC2",
  124. "version" : "20230322145651",
  125. "publicationDate" : "2023-03-22T14:56:51Z",
  126. "products" : {
  127. "8D49XP354UEYTHGM" : {
  128. "sku" : "8D49XP354UEYTHGM",
  129. "productFamily" : "Compute Instance",
  130. "attributes" : {
  131. "servicecode" : "AmazonEC2",
  132. "location" : "US East (Ohio)",
  133. "locationType" : "AWS Region",
  134. "instanceType" : "m5.large",
  135. "currentGeneration" : "Yes",
  136. "instanceFamily" : "General purpose",
  137. "vcpu" : "2",
  138. "physicalProcessor" : "Intel Xeon Platinum 8175",
  139. "clockSpeed" : "3.1 GHz",
  140. "memory" : "8 GiB",
  141. "storage" : "EBS only",
  142. "networkPerformance" : "Up to 10 Gigabit",
  143. "processorArchitecture" : "64-bit",
  144. "tenancy" : "Shared",
  145. "operatingSystem" : "Linux",
  146. "licenseModel" : "No License required",
  147. "usagetype" : "USE2-BoxUsage:m5.large",
  148. "operation" : "RunInstances",
  149. "availabilityzone" : "NA",
  150. "capacitystatus" : "Used",
  151. "classicnetworkingsupport" : "false",
  152. "dedicatedEbsThroughput" : "Up to 2120 Mbps",
  153. "ecu" : "10",
  154. "enhancedNetworkingSupported" : "Yes",
  155. "gpuMemory" : "NA",
  156. "intelAvxAvailable" : "Yes",
  157. "intelAvx2Available" : "Yes",
  158. "intelTurboAvailable" : "Yes",
  159. "marketoption" : "OnDemand",
  160. "normalizationSizeFactor" : "4",
  161. "preInstalledSw" : "NA",
  162. "processorFeatures" : "Intel AVX; Intel AVX2; Intel AVX512; Intel Turbo",
  163. "regionCode" : "us-east-2",
  164. "servicename" : "Amazon Elastic Compute Cloud",
  165. "vpcnetworkingsupport" : "true"
  166. }
  167. },
  168. "9ZEEN7WWWQKAG292" : {
  169. "sku" : "9ZEEN7WWWQKAG292",
  170. "productFamily" : "Compute Instance",
  171. "attributes" : {
  172. "servicecode" : "AmazonEC2",
  173. "location" : "US East (Ohio)",
  174. "locationType" : "AWS Region",
  175. "instanceType" : "p3.8xlarge",
  176. "currentGeneration" : "Yes",
  177. "instanceFamily" : "GPU instance",
  178. "vcpu" : "32",
  179. "physicalProcessor" : "Intel Xeon E5-2686 v4 (Broadwell)",
  180. "clockSpeed" : "2.3 GHz",
  181. "memory" : "244 GiB",
  182. "storage" : "EBS only",
  183. "networkPerformance" : "10 Gigabit",
  184. "processorArchitecture" : "64-bit",
  185. "tenancy" : "Shared",
  186. "operatingSystem" : "Windows",
  187. "licenseModel" : "Bring your own license",
  188. "usagetype" : "USE2-BoxUsage:p3.8xlarge",
  189. "operation" : "RunInstances:0800",
  190. "availabilityzone" : "NA",
  191. "capacitystatus" : "Used",
  192. "classicnetworkingsupport" : "false",
  193. "dedicatedEbsThroughput" : "7000 Mbps",
  194. "ecu" : "97",
  195. "enhancedNetworkingSupported" : "Yes",
  196. "gpu" : "4",
  197. "gpuMemory" : "NA",
  198. "intelAvxAvailable" : "Yes",
  199. "intelAvx2Available" : "Yes",
  200. "intelTurboAvailable" : "Yes",
  201. "marketoption" : "OnDemand",
  202. "normalizationSizeFactor" : "64",
  203. "preInstalledSw" : "NA",
  204. "processorFeatures" : "Intel AVX; Intel AVX2; Intel Turbo",
  205. "regionCode" : "us-east-2",
  206. "servicename" : "Amazon Elastic Compute Cloud",
  207. "vpcnetworkingsupport" : "true"
  208. }
  209. },
  210. "M6UGCCQ3CDJQAA37" : {
  211. "sku" : "M6UGCCQ3CDJQAA37",
  212. "productFamily" : "Storage",
  213. "attributes" : {
  214. "servicecode" : "AmazonEC2",
  215. "location" : "US East (Ohio)",
  216. "locationType" : "AWS Region",
  217. "storageMedia" : "SSD-backed",
  218. "volumeType" : "General Purpose",
  219. "maxVolumeSize" : "16 TiB",
  220. "maxIopsvolume" : "16000",
  221. "maxThroughputvolume" : "1000 MiB/s",
  222. "usagetype" : "USE2-EBS:VolumeUsage.gp3",
  223. "operation" : "",
  224. "regionCode" : "us-east-2",
  225. "servicename" : "Amazon Elastic Compute Cloud",
  226. "volumeApiName" : "gp3"
  227. }
  228. }
  229. },
  230. "terms" : {
  231. "OnDemand" : {
  232. "M6UGCCQ3CDJQAA37" : {
  233. "M6UGCCQ3CDJQAA37.JRTCKXETXF" : {
  234. "offerTermCode" : "JRTCKXETXF",
  235. "sku" : "M6UGCCQ3CDJQAA37",
  236. "effectiveDate" : "2023-03-01T00:00:00Z",
  237. "priceDimensions" : {
  238. "M6UGCCQ3CDJQAA37.JRTCKXETXF.6YS6EN2CT7" : {
  239. "rateCode" : "M6UGCCQ3CDJQAA37.JRTCKXETXF.6YS6EN2CT7",
  240. "description" : "$0.08 per GB-month of General Purpose (gp3) provisioned storage - US East (Ohio)",
  241. "beginRange" : "0",
  242. "endRange" : "Inf",
  243. "unit" : "GB-Mo",
  244. "pricePerUnit" : {
  245. "USD" : "0.0800000000"
  246. },
  247. "appliesTo" : [ ]
  248. }
  249. },
  250. "termAttributes" : { }
  251. }
  252. },
  253. "9ZEEN7WWWQKAG292" : {
  254. "9ZEEN7WWWQKAG292.JRTCKXETXF" : {
  255. "offerTermCode" : "JRTCKXETXF",
  256. "sku" : "9ZEEN7WWWQKAG292",
  257. "effectiveDate" : "2023-03-01T00:00:00Z",
  258. "priceDimensions" : {
  259. "9ZEEN7WWWQKAG292.JRTCKXETXF.6YS6EN2CT7" : {
  260. "rateCode" : "9ZEEN7WWWQKAG292.JRTCKXETXF.6YS6EN2CT7",
  261. "description" : "$12.24 per On Demand Windows BYOL p3.8xlarge Instance Hour",
  262. "beginRange" : "0",
  263. "endRange" : "Inf",
  264. "unit" : "Hrs",
  265. "pricePerUnit" : {
  266. "USD" : "12.2400000000"
  267. },
  268. "appliesTo" : [ ]
  269. }
  270. },
  271. "termAttributes" : { }
  272. }
  273. },
  274. "8D49XP354UEYTHGM" : {
  275. "8D49XP354UEYTHGM.MZU6U2429S" : {
  276. "offerTermCode" : "MZU6U2429S",
  277. "sku" : "8D49XP354UEYTHGM",
  278. "effectiveDate" : "2019-01-01T00:00:00Z",
  279. "priceDimensions" : {
  280. "8D49XP354UEYTHGM.MZU6U2429S.2TG2D8R56U" : {
  281. "rateCode" : "8D49XP354UEYTHGM.MZU6U2429S.2TG2D8R56U",
  282. "description" : "Upfront Fee",
  283. "unit" : "Quantity",
  284. "pricePerUnit" : {
  285. "USD" : "1161"
  286. },
  287. "appliesTo" : [ ]
  288. },
  289. },
  290. "termAttributes" : {
  291. "LeaseContractLength" : "3yr",
  292. "OfferingClass" : "convertible",
  293. "PurchaseOption" : "All Upfront"
  294. }
  295. }
  296. }
  297. }
  298. },
  299. "attributesList" : { }
  300. }
  301. `
  302. testResponse := http.Response{
  303. Body: ioutil.NopCloser(bytes.NewBufferString(awsUSEastString)),
  304. Request: &http.Request{
  305. URL: &url.URL{
  306. Scheme: "https",
  307. Host: "test-aws-http-endpoint:443",
  308. },
  309. },
  310. }
  311. awsTest.populatePricing(&testResponse, inputkeys)
  312. expectedProdTermsDisk := &AWSProductTerms{
  313. Sku: "M6UGCCQ3CDJQAA37",
  314. Memory: "",
  315. Storage: "",
  316. VCpu: "",
  317. GPU: "",
  318. OnDemand: &AWSOfferTerm{
  319. Sku: "M6UGCCQ3CDJQAA37",
  320. OfferTermCode: "JRTCKXETXF",
  321. PriceDimensions: map[string]*AWSRateCode{
  322. "M6UGCCQ3CDJQAA37.JRTCKXETXF.6YS6EN2CT7": {
  323. Unit: "GB-Mo",
  324. PricePerUnit: AWSCurrencyCode{
  325. USD: "0.0800000000",
  326. CNY: "",
  327. },
  328. },
  329. },
  330. },
  331. PV: &models.PV{
  332. Cost: "0.00010958904109589041",
  333. CostPerIO: "",
  334. Class: "gp3",
  335. Size: "",
  336. Region: "us-east-2",
  337. ProviderID: "",
  338. },
  339. }
  340. expectedProdTermsInstanceOndemand := &AWSProductTerms{
  341. Sku: "8D49XP354UEYTHGM",
  342. Memory: "8 GiB",
  343. Storage: "EBS only",
  344. VCpu: "2",
  345. GPU: "",
  346. OnDemand: &AWSOfferTerm{
  347. Sku: "",
  348. OfferTermCode: "",
  349. PriceDimensions: nil,
  350. },
  351. }
  352. expectedProdTermsInstanceSpot := &AWSProductTerms{
  353. Sku: "8D49XP354UEYTHGM",
  354. Memory: "8 GiB",
  355. Storage: "EBS only",
  356. VCpu: "2",
  357. GPU: "",
  358. OnDemand: &AWSOfferTerm{
  359. Sku: "",
  360. OfferTermCode: "",
  361. PriceDimensions: nil,
  362. },
  363. }
  364. expectedPricing := map[string]*AWSProductTerms{
  365. "us-east-2,EBS:VolumeUsage.gp3": expectedProdTermsDisk,
  366. "us-east-2,EBS:VolumeUsage.gp3,preemptible": expectedProdTermsDisk,
  367. "us-east-2,m5.large,linux": expectedProdTermsInstanceOndemand,
  368. "us-east-2,m5.large,linux,preemptible": expectedProdTermsInstanceSpot,
  369. }
  370. if !reflect.DeepEqual(expectedPricing, awsTest.Pricing) {
  371. t.Fatalf("expected parsed pricing did not match actual parsed result (us-east-1)")
  372. }
  373. // Case 1
  374. awsCnString := `
  375. {
  376. "formatVersion" : "v1.0",
  377. "disclaimer" : "This pricing list is for informational purposes only. All prices are subject to the additional terms included in the pricing pages on http://www.amazonaws.cn.",
  378. "offerCode" : "AmazonEC2",
  379. "version" : "20230314154740",
  380. "publicationDate" : "2023-03-14T15:47:40Z",
  381. "products" : {
  382. "R83VXG9NAPDASEGN" : {
  383. "sku" : "R83VXG9NAPDASEGN",
  384. "productFamily" : "Storage",
  385. "attributes" : {
  386. "servicecode" : "AmazonEC2",
  387. "location" : "China (Ningxia)",
  388. "locationType" : "AWS Region",
  389. "storageMedia" : "SSD-backed",
  390. "volumeType" : "General Purpose",
  391. "maxVolumeSize" : "16 TiB",
  392. "maxIopsvolume" : "16000",
  393. "maxThroughputvolume" : "1000 MiB/s",
  394. "usagetype" : "CNW1-EBS:VolumeUsage.gp3",
  395. "operation" : "",
  396. "regionCode" : "cn-northwest-1",
  397. "servicename" : "Amazon Elastic Compute Cloud",
  398. "volumeApiName" : "gp3"
  399. }
  400. }
  401. },
  402. "terms" : {
  403. "OnDemand" : {
  404. "R83VXG9NAPDASEGN" : {
  405. "R83VXG9NAPDASEGN.5Y9WH78GDR" : {
  406. "offerTermCode" : "5Y9WH78GDR",
  407. "sku" : "R83VXG9NAPDASEGN",
  408. "effectiveDate" : "2023-03-01T00:00:00Z",
  409. "priceDimensions" : {
  410. "R83VXG9NAPDASEGN.5Y9WH78GDR.Q7UJUT2CE6" : {
  411. "rateCode" : "R83VXG9NAPDASEGN.5Y9WH78GDR.Q7UJUT2CE6",
  412. "description" : "0.5312 CNY per GB-month of General Purpose (gp3) provisioned storage - China (Ningxia)",
  413. "beginRange" : "0",
  414. "endRange" : "Inf",
  415. "unit" : "GB-Mo",
  416. "pricePerUnit" : {
  417. "CNY" : "0.5312000000"
  418. },
  419. "appliesTo" : [ ]
  420. }
  421. },
  422. "termAttributes" : { }
  423. }
  424. }
  425. }
  426. },
  427. "attributesList" : { }
  428. }
  429. `
  430. awsTest = AWS{
  431. ValidPricingKeys: map[string]bool{},
  432. }
  433. testResponse = http.Response{
  434. Body: ioutil.NopCloser(bytes.NewBufferString(awsCnString)),
  435. Request: &http.Request{
  436. URL: &url.URL{
  437. Scheme: "https",
  438. Host: "test-aws-http-endpoint:443",
  439. },
  440. },
  441. }
  442. awsTest.populatePricing(&testResponse, inputkeys)
  443. expectedProdTermsDisk = &AWSProductTerms{
  444. Sku: "R83VXG9NAPDASEGN",
  445. Memory: "",
  446. Storage: "",
  447. VCpu: "",
  448. GPU: "",
  449. OnDemand: &AWSOfferTerm{
  450. Sku: "R83VXG9NAPDASEGN",
  451. OfferTermCode: "5Y9WH78GDR",
  452. PriceDimensions: map[string]*AWSRateCode{
  453. "R83VXG9NAPDASEGN.5Y9WH78GDR.Q7UJUT2CE6": {
  454. Unit: "GB-Mo",
  455. PricePerUnit: AWSCurrencyCode{
  456. USD: "",
  457. CNY: "0.5312000000",
  458. },
  459. },
  460. },
  461. },
  462. PV: &models.PV{
  463. Cost: "0.0007276712328767123",
  464. CostPerIO: "",
  465. Class: "gp3",
  466. Size: "",
  467. Region: "cn-northwest-1",
  468. ProviderID: "",
  469. },
  470. }
  471. expectedPricing = map[string]*AWSProductTerms{
  472. "cn-northwest-1,EBS:VolumeUsage.gp3": expectedProdTermsDisk,
  473. "cn-northwest-1,EBS:VolumeUsage.gp3,preemptible": expectedProdTermsDisk,
  474. }
  475. if !reflect.DeepEqual(expectedPricing, awsTest.Pricing) {
  476. t.Fatalf("expected parsed pricing did not match actual parsed result (cn)")
  477. }
  478. }