gcpprovider_test.go 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. package cloud
  2. import (
  3. "bytes"
  4. "io/ioutil"
  5. "reflect"
  6. "testing"
  7. "github.com/opencost/opencost/pkg/cloud/models"
  8. )
  9. func TestParseGCPInstanceTypeLabel(t *testing.T) {
  10. cases := []struct {
  11. input string
  12. expected string
  13. }{
  14. {
  15. input: "n1-standard-2",
  16. expected: "n1standard",
  17. },
  18. {
  19. input: "e2-medium",
  20. expected: "e2medium",
  21. },
  22. {
  23. input: "k3s",
  24. expected: "unknown",
  25. },
  26. {
  27. input: "custom-n1-standard-2",
  28. expected: "custom",
  29. },
  30. {
  31. input: "n2d-highmem-8",
  32. expected: "n2dstandard",
  33. },
  34. }
  35. for _, test := range cases {
  36. result := parseGCPInstanceTypeLabel(test.input)
  37. if result != test.expected {
  38. t.Errorf("Input: %s, Expected: %s, Actual: %s", test.input, test.expected, result)
  39. }
  40. }
  41. }
  42. func TestParseGCPProjectID(t *testing.T) {
  43. cases := []struct {
  44. input string
  45. expected string
  46. }{
  47. {
  48. input: "gce://guestbook-12345/...",
  49. expected: "guestbook-12345",
  50. },
  51. {
  52. input: "gce:/guestbook-12345/...",
  53. expected: "",
  54. },
  55. {
  56. input: "asdfa",
  57. expected: "",
  58. },
  59. {
  60. input: "",
  61. expected: "",
  62. },
  63. }
  64. for _, test := range cases {
  65. result := parseGCPProjectID(test.input)
  66. if result != test.expected {
  67. t.Errorf("Input: %s, Expected: %s, Actual: %s", test.input, test.expected, result)
  68. }
  69. }
  70. }
  71. func TestGetUsageType(t *testing.T) {
  72. cases := []struct {
  73. input map[string]string
  74. expected string
  75. }{
  76. {
  77. input: map[string]string{
  78. GKEPreemptibleLabel: "true",
  79. },
  80. expected: "preemptible",
  81. },
  82. {
  83. input: map[string]string{
  84. GKESpotLabel: "true",
  85. },
  86. expected: "preemptible",
  87. },
  88. {
  89. input: map[string]string{
  90. KarpenterCapacityTypeLabel: KarpenterCapacitySpotTypeValue,
  91. },
  92. expected: "preemptible",
  93. },
  94. {
  95. input: map[string]string{
  96. "someotherlabel": "true",
  97. },
  98. expected: "ondemand",
  99. },
  100. {
  101. input: map[string]string{},
  102. expected: "ondemand",
  103. },
  104. }
  105. for _, test := range cases {
  106. result := getUsageType(test.input)
  107. if result != test.expected {
  108. t.Errorf("Input: %v, Expected: %s, Actual: %s", test.input, test.expected, result)
  109. }
  110. }
  111. }
  112. // tests basic parsing of GCP pricing API responses
  113. // Load a reader object on a portion of a GCP api response
  114. // Confirm that the resting *GCP object contains the correctly parsed pricing info
  115. func TestParsePage(t *testing.T) {
  116. gcpSkuString := `
  117. {
  118. "skus": [
  119. {
  120. "name": "services/6F81-5844-456A/skus/039F-D0DA-4055",
  121. "skuId": "039F-D0DA-4055",
  122. "description": "Nvidia Tesla A100 GPU running in Americas",
  123. "category": {
  124. "serviceDisplayName": "Compute Engine",
  125. "resourceFamily": "Compute",
  126. "resourceGroup": "GPU",
  127. "usageType": "OnDemand"
  128. },
  129. "serviceRegions": [
  130. "us-central1",
  131. "us-east1",
  132. "us-west1"
  133. ],
  134. "pricingInfo": [
  135. {
  136. "summary": "",
  137. "pricingExpression": {
  138. "usageUnit": "h",
  139. "displayQuantity": 1,
  140. "tieredRates": [
  141. {
  142. "startUsageAmount": 0,
  143. "unitPrice": {
  144. "currencyCode": "USD",
  145. "units": "2",
  146. "nanos": 933908000
  147. }
  148. }
  149. ],
  150. "usageUnitDescription": "hour",
  151. "baseUnit": "s",
  152. "baseUnitDescription": "second",
  153. "baseUnitConversionFactor": 3600
  154. },
  155. "currencyConversionRate": 1,
  156. "effectiveTime": "2023-03-24T10:52:50.681Z"
  157. }
  158. ],
  159. "serviceProviderName": "Google",
  160. "geoTaxonomy": {
  161. "type": "MULTI_REGIONAL",
  162. "regions": [
  163. "us-central1",
  164. "us-east1",
  165. "us-west1"
  166. ]
  167. }
  168. },
  169. {
  170. "name": "services/6F81-5844-456A/skus/2390-DCAF-DA38",
  171. "skuId": "2390-DCAF-DA38",
  172. "description": "A2 Instance Ram running in Americas",
  173. "category": {
  174. "serviceDisplayName": "Compute Engine",
  175. "resourceFamily": "Compute",
  176. "resourceGroup": "RAM",
  177. "usageType": "OnDemand"
  178. },
  179. "serviceRegions": [
  180. "us-central1",
  181. "us-east1",
  182. "us-west1"
  183. ],
  184. "pricingInfo": [
  185. {
  186. "summary": "",
  187. "pricingExpression": {
  188. "usageUnit": "GiBy.h",
  189. "displayQuantity": 1,
  190. "tieredRates": [
  191. {
  192. "startUsageAmount": 0,
  193. "unitPrice": {
  194. "currencyCode": "USD",
  195. "units": "0",
  196. "nanos": 4237000
  197. }
  198. }
  199. ],
  200. "usageUnitDescription": "gibibyte hour",
  201. "baseUnit": "By.s",
  202. "baseUnitDescription": "byte second",
  203. "baseUnitConversionFactor": 3865470566400
  204. },
  205. "currencyConversionRate": 1,
  206. "effectiveTime": "2023-03-24T10:52:50.681Z"
  207. }
  208. ],
  209. "serviceProviderName": "Google",
  210. "geoTaxonomy": {
  211. "type": "MULTI_REGIONAL",
  212. "regions": [
  213. "us-central1",
  214. "us-east1",
  215. "us-west1"
  216. ]
  217. }
  218. },
  219. {
  220. "name": "services/6F81-5844-456A/skus/2922-40C5-B19F",
  221. "skuId": "2922-40C5-B19F",
  222. "description": "A2 Instance Core running in Americas",
  223. "category": {
  224. "serviceDisplayName": "Compute Engine",
  225. "resourceFamily": "Compute",
  226. "resourceGroup": "CPU",
  227. "usageType": "OnDemand"
  228. },
  229. "serviceRegions": [
  230. "us-central1",
  231. "us-east1",
  232. "us-west1"
  233. ],
  234. "pricingInfo": [
  235. {
  236. "summary": "",
  237. "pricingExpression": {
  238. "usageUnit": "h",
  239. "displayQuantity": 1,
  240. "tieredRates": [
  241. {
  242. "startUsageAmount": 0,
  243. "unitPrice": {
  244. "currencyCode": "USD",
  245. "units": "0",
  246. "nanos": 31611000
  247. }
  248. }
  249. ],
  250. "usageUnitDescription": "hour",
  251. "baseUnit": "s",
  252. "baseUnitDescription": "second",
  253. "baseUnitConversionFactor": 3600
  254. },
  255. "currencyConversionRate": 1,
  256. "effectiveTime": "2023-03-24T10:52:50.681Z"
  257. }
  258. ],
  259. "serviceProviderName": "Google",
  260. "geoTaxonomy": {
  261. "type": "MULTI_REGIONAL",
  262. "regions": [
  263. "us-central1",
  264. "us-east1",
  265. "us-west1"
  266. ]
  267. }
  268. }
  269. ],
  270. "nextPageToken": "APKCS1HVa0YpwgyTFbqbJ1eGwzKZmsPwLqzMZPTSNia5ck1Hc54Tx_Kz3oBxwSnRIdGVxXoSPdf-XlDpyNBf4QuxKcIEgtrQ1LDLWAgZowI0ns7HjrGta2s="
  271. }
  272. `
  273. reader := ioutil.NopCloser(bytes.NewBufferString(gcpSkuString))
  274. testGcp := &GCP{}
  275. inputKeys := map[string]models.Key{
  276. "us-central1,a2highgpu,ondemand,gpu": &gcpKey{
  277. Labels: map[string]string{
  278. "node.kubernetes.io/instance-type": "a2-highgpu-1g",
  279. "cloud.google.com/gke-gpu": "true",
  280. "cloud.google.com/gke-accelerator": "nvidia-tesla-a100",
  281. "topology.kubernetes.io/region": "us-central1",
  282. },
  283. },
  284. }
  285. pvKeys := map[string]models.PVKey{}
  286. actualPrices, token, err := testGcp.parsePage(reader, inputKeys, pvKeys)
  287. if err != nil {
  288. t.Fatalf("got error parsing page: %v", err)
  289. }
  290. const expectedToken = "APKCS1HVa0YpwgyTFbqbJ1eGwzKZmsPwLqzMZPTSNia5ck1Hc54Tx_Kz3oBxwSnRIdGVxXoSPdf-XlDpyNBf4QuxKcIEgtrQ1LDLWAgZowI0ns7HjrGta2s="
  291. if token != expectedToken {
  292. t.Fatalf("error parsing GCP next page token, parsed %s but expected %s", token, expectedToken)
  293. }
  294. expectedActualPrices := map[string]*GCPPricing{
  295. "us-central1,a2highgpu,ondemand,gpu": &GCPPricing{
  296. Name: "services/6F81-5844-456A/skus/039F-D0DA-4055",
  297. SKUID: "039F-D0DA-4055",
  298. Description: "Nvidia Tesla A100 GPU running in Americas",
  299. Category: &GCPResourceInfo{
  300. ServiceDisplayName: "Compute Engine",
  301. ResourceFamily: "Compute",
  302. ResourceGroup: "GPU",
  303. UsageType: "OnDemand",
  304. },
  305. ServiceRegions: []string{"us-central1", "us-east1", "us-west1"},
  306. PricingInfo: []*PricingInfo{
  307. &PricingInfo{
  308. Summary: "",
  309. PricingExpression: &PricingExpression{
  310. UsageUnit: "h",
  311. UsageUnitDescription: "hour",
  312. BaseUnit: "s",
  313. BaseUnitConversionFactor: 0,
  314. DisplayQuantity: 1,
  315. TieredRates: []*TieredRates{
  316. &TieredRates{
  317. StartUsageAmount: 0,
  318. UnitPrice: &UnitPriceInfo{
  319. CurrencyCode: "USD",
  320. Units: "2",
  321. Nanos: 933908000,
  322. },
  323. },
  324. },
  325. },
  326. CurrencyConversionRate: 1,
  327. EffectiveTime: "2023-03-24T10:52:50.681Z",
  328. },
  329. },
  330. ServiceProviderName: "Google",
  331. Node: &models.Node{
  332. VCPUCost: "0.031611",
  333. RAMCost: "0.004237",
  334. UsesBaseCPUPrice: false,
  335. GPU: "1",
  336. GPUName: "nvidia-tesla-a100",
  337. GPUCost: "2.933908",
  338. },
  339. },
  340. "us-central1,a2highgpu,ondemand": &GCPPricing{
  341. Node: &models.Node{
  342. VCPUCost: "0.031611",
  343. RAMCost: "0.004237",
  344. UsesBaseCPUPrice: false,
  345. UsageType: "ondemand",
  346. },
  347. },
  348. }
  349. if !reflect.DeepEqual(actualPrices, expectedActualPrices) {
  350. t.Fatalf("error parsing GCP prices. parsed %v but expected %v", actualPrices, expectedActualPrices)
  351. }
  352. }