provider_test.go 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978
  1. package alibaba
  2. import (
  3. "fmt"
  4. "testing"
  5. "github.com/aliyun/alibaba-cloud-sdk-go/sdk"
  6. "github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/credentials"
  7. "github.com/aliyun/alibaba-cloud-sdk-go/sdk/auth/signers"
  8. "github.com/opencost/opencost/pkg/cloud/models"
  9. "github.com/opencost/opencost/pkg/clustercache"
  10. v1 "k8s.io/api/core/v1"
  11. "k8s.io/apimachinery/pkg/api/resource"
  12. )
  13. func TestCreateDescribePriceACSRequest(t *testing.T) {
  14. node := &SlimK8sNode{
  15. InstanceType: "ecs.g6.large",
  16. RegionID: "cn-hangzhou",
  17. PriceUnit: "Hour",
  18. MemorySizeInKiB: "16KiB",
  19. IsIoOptimized: true,
  20. OSType: "Linux",
  21. ProviderID: "Ali-XXX-node-01",
  22. InstanceTypeFamily: "g6",
  23. }
  24. disk := &SlimK8sDisk{
  25. DiskType: "data",
  26. RegionID: "cn-hangzhou",
  27. PriceUnit: "Hour",
  28. SizeInGiB: "20",
  29. DiskCategory: "diskCategory",
  30. PerformanceLevel: "cloud_essd",
  31. ProviderID: "d-Ali-XXX-01",
  32. StorageClass: "testStorageClass",
  33. }
  34. cases := []struct {
  35. name string
  36. testStruct interface{}
  37. expectedError error
  38. }{
  39. {
  40. name: "test CreateDescribePriceACSRequest with SlimK8sNode struct Object",
  41. testStruct: node,
  42. expectedError: nil,
  43. },
  44. {
  45. name: "test CreateDescribePriceACSRequest with SlimK8sDisk struct Object",
  46. testStruct: disk,
  47. expectedError: nil,
  48. },
  49. }
  50. for _, c := range cases {
  51. t.Run(c.name, func(t *testing.T) {
  52. _, err := createDescribePriceACSRequest(c.testStruct)
  53. if err != nil && c.expectedError == nil {
  54. t.Fatalf("Case name %s: Error converting to Alibaba cloud request", c.name)
  55. }
  56. })
  57. }
  58. }
  59. func TestProcessDescribePriceAndCreateAlibabaPricing(t *testing.T) {
  60. // Skipping this test case since it exposes secret but a good test case to verify when
  61. // supporting a new family of instances, steps to perform are
  62. // STEP 1: Comment the t.Skip() line and then replace XXX_KEY_ID with the alibaba key id of your account and XXX_SECRET_ID with alibaba cloud secret of your account.
  63. // STEP 2: Once you verify describePrice is working and no change needed in processDescribePriceAndCreateAlibabaPricing, you can go ahead and revert the step 1 changes.
  64. // This test case was use to test all general puprose instances
  65. t.Skip()
  66. client, err := sdk.NewClientWithAccessKey("cn-hangzhou", "XXX_KEY_ID", "XXX_SECRET_ID")
  67. if err != nil {
  68. t.Errorf("Error connecting to the Alibaba cloud")
  69. }
  70. aak := credentials.NewAccessKeyCredential("XXX_KEY_ID", "XXX_SECRET_ID")
  71. signer := signers.NewAccessKeySigner(aak)
  72. cases := []struct {
  73. name string
  74. teststruct interface{}
  75. expectedError error
  76. }{
  77. {
  78. name: "test General Purpose Type g7 instance family",
  79. teststruct: &SlimK8sNode{
  80. InstanceType: "ecs.g7.4xlarge",
  81. RegionID: "cn-hangzhou",
  82. PriceUnit: "Hour",
  83. MemorySizeInKiB: "16777216KiB",
  84. IsIoOptimized: true,
  85. OSType: "Linux",
  86. ProviderID: "cn-hangzhou.i-test-01a",
  87. InstanceTypeFamily: "g7",
  88. },
  89. expectedError: nil,
  90. },
  91. {
  92. name: "test General Purpose Type g7a instance family",
  93. teststruct: &SlimK8sNode{
  94. InstanceType: "ecs.g7a.8xlarge",
  95. RegionID: "cn-hangzhou",
  96. PriceUnit: "Hour",
  97. MemorySizeInKiB: "33554432KiB",
  98. IsIoOptimized: true,
  99. OSType: "Linux",
  100. ProviderID: "cn-hangzhou.i-test-01b",
  101. InstanceTypeFamily: "g7a",
  102. },
  103. expectedError: nil,
  104. },
  105. {
  106. name: "test General Purpose Type g8a instance family",
  107. teststruct: &SlimK8sNode{
  108. InstanceType: "ecs.g8a.8xlarge",
  109. RegionID: "cn-hangzhou",
  110. PriceUnit: "Hour",
  111. MemorySizeInKiB: "33554432KiB",
  112. IsIoOptimized: true,
  113. OSType: "Linux",
  114. ProviderID: "cn-hangzhou.i-test-01c",
  115. InstanceTypeFamily: "g8a",
  116. },
  117. expectedError: nil,
  118. },
  119. {
  120. name: "test Enhanced General Purpose Type g6e instance family",
  121. teststruct: &SlimK8sNode{
  122. InstanceType: "ecs.g6e.xlarge",
  123. RegionID: "cn-hangzhou",
  124. PriceUnit: "Hour",
  125. MemorySizeInKiB: "16777216KiB",
  126. IsIoOptimized: true,
  127. OSType: "Linux",
  128. ProviderID: "cn-hangzhou.i-test-01",
  129. InstanceTypeFamily: "g6e",
  130. },
  131. expectedError: nil,
  132. },
  133. {
  134. name: "test General Purpose Type g6 instance family",
  135. teststruct: &SlimK8sNode{
  136. InstanceType: "ecs.g6.3xlarge",
  137. RegionID: "cn-hangzhou",
  138. PriceUnit: "Hour",
  139. MemorySizeInKiB: "50331648KiB",
  140. IsIoOptimized: true,
  141. OSType: "Linux",
  142. ProviderID: "cn-hangzhou.i-test-02",
  143. InstanceTypeFamily: "g6",
  144. },
  145. expectedError: nil,
  146. },
  147. {
  148. name: "test General Purpose Type g5 instance family",
  149. teststruct: &SlimK8sNode{
  150. InstanceType: "ecs.g5.2xlarge",
  151. RegionID: "cn-hangzhou",
  152. PriceUnit: "Hour",
  153. MemorySizeInKiB: "33554432KiB",
  154. IsIoOptimized: true,
  155. OSType: "Linux",
  156. ProviderID: "cn-hangzhou.i-test-03",
  157. InstanceTypeFamily: "g5",
  158. },
  159. expectedError: nil,
  160. },
  161. {
  162. name: "test General Purpose Type sn2 instance family",
  163. teststruct: &SlimK8sNode{
  164. InstanceType: "ecs.sn2.large",
  165. RegionID: "cn-hangzhou",
  166. PriceUnit: "Hour",
  167. MemorySizeInKiB: "16777216KiB",
  168. IsIoOptimized: true,
  169. OSType: "Linux",
  170. ProviderID: "cn-hangzhou.i-test-04",
  171. InstanceTypeFamily: "sn2",
  172. },
  173. expectedError: nil,
  174. },
  175. {
  176. name: "test General Purpose Type with Enhanced Network Performance sn2ne instance family",
  177. teststruct: &SlimK8sNode{
  178. InstanceType: "ecs.sn2ne.2xlarge",
  179. RegionID: "cn-hangzhou",
  180. PriceUnit: "Hour",
  181. MemorySizeInKiB: "33554432KiB",
  182. IsIoOptimized: true,
  183. OSType: "Linux",
  184. ProviderID: "cn-hangzhou.i-test-05",
  185. InstanceTypeFamily: "sn2ne",
  186. },
  187. expectedError: nil,
  188. },
  189. {
  190. name: "test Memory Optmized instance type r7 instance family",
  191. teststruct: &SlimK8sNode{
  192. InstanceType: "ecs.r7.6xlarge",
  193. RegionID: "cn-hangzhou",
  194. PriceUnit: "Hour",
  195. MemorySizeInKiB: "2013265592KiB",
  196. IsIoOptimized: true,
  197. OSType: "Linux",
  198. ProviderID: "cn-hangzhou.i-test-06",
  199. InstanceTypeFamily: "r7",
  200. },
  201. expectedError: nil,
  202. },
  203. {
  204. name: "test Memory Optmized instance type r7a instance family",
  205. teststruct: &SlimK8sNode{
  206. InstanceType: "ecs.r7a.8xlarge",
  207. RegionID: "cn-hangzhou",
  208. PriceUnit: "Hour",
  209. MemorySizeInKiB: "33554432KiB",
  210. IsIoOptimized: true,
  211. OSType: "Linux",
  212. ProviderID: "cn-hangzhou.i-test-06a",
  213. InstanceTypeFamily: "r7a",
  214. },
  215. expectedError: nil,
  216. },
  217. {
  218. name: "test Enhanced Memory Optmized instance type r6e instance family",
  219. teststruct: &SlimK8sNode{
  220. InstanceType: "ecs.r6e.4xlarge",
  221. RegionID: "cn-hangzhou",
  222. PriceUnit: "Hour",
  223. MemorySizeInKiB: "2013265592KiB",
  224. IsIoOptimized: true,
  225. OSType: "Linux",
  226. ProviderID: "cn-hangzhou.i-test-07",
  227. InstanceTypeFamily: "r6e",
  228. },
  229. expectedError: nil,
  230. },
  231. {
  232. name: "test Memory Optmized instance type r6a instance family",
  233. teststruct: &SlimK8sNode{
  234. InstanceType: "ecs.r6a.8xlarge",
  235. RegionID: "cn-hangzhou",
  236. PriceUnit: "Hour",
  237. MemorySizeInKiB: "33554432KiB",
  238. IsIoOptimized: true,
  239. OSType: "Linux",
  240. ProviderID: "cn-hangzhou.i-test-07a",
  241. InstanceTypeFamily: "r6a",
  242. },
  243. expectedError: nil,
  244. },
  245. {
  246. name: "test Memory Optmized instance type r6 instance family",
  247. teststruct: &SlimK8sNode{
  248. InstanceType: "ecs.r6.8xlarge",
  249. RegionID: "cn-hangzhou",
  250. PriceUnit: "Hour",
  251. MemorySizeInKiB: "33554432KiB",
  252. IsIoOptimized: true,
  253. OSType: "Linux",
  254. ProviderID: "cn-hangzhou.i-test-08",
  255. InstanceTypeFamily: "r6",
  256. },
  257. expectedError: nil,
  258. },
  259. {
  260. name: "test Memory type instance and r5 instance family",
  261. teststruct: &SlimK8sNode{
  262. InstanceType: "ecs.r5.xlarge",
  263. RegionID: "cn-hangzhou",
  264. PriceUnit: "Hour",
  265. MemorySizeInKiB: "33554432KiB",
  266. IsIoOptimized: true,
  267. OSType: "Linux",
  268. ProviderID: "cn-hangzhou.i-test-09",
  269. InstanceTypeFamily: "r5",
  270. },
  271. expectedError: nil,
  272. },
  273. {
  274. name: "test Memory Optmized instance type with se1 instance family",
  275. teststruct: &SlimK8sNode{
  276. InstanceType: "ecs.se1.4xlarge",
  277. RegionID: "cn-hangzhou",
  278. PriceUnit: "Hour",
  279. MemorySizeInKiB: "16777216KiB",
  280. IsIoOptimized: true,
  281. OSType: "Linux",
  282. ProviderID: "cn-hangzhou.i-test-10",
  283. InstanceTypeFamily: "se1",
  284. },
  285. expectedError: nil,
  286. },
  287. {
  288. name: "test Memory Optmized instance type with Enhanced Network Performance se1ne instance family",
  289. teststruct: &SlimK8sNode{
  290. InstanceType: "ecs.se1ne.3xlarge",
  291. RegionID: "cn-hangzhou",
  292. PriceUnit: "Hour",
  293. MemorySizeInKiB: "100663296KiB",
  294. IsIoOptimized: true,
  295. OSType: "Linux",
  296. ProviderID: "cn-hangzhou.i-test-11",
  297. InstanceTypeFamily: "se1ne",
  298. },
  299. expectedError: nil,
  300. },
  301. {
  302. name: "test High Memory type with re6 instance family",
  303. teststruct: &SlimK8sNode{
  304. InstanceType: "ecs.re6.8xlarge",
  305. RegionID: "cn-hangzhou",
  306. PriceUnit: "Hour",
  307. MemorySizeInKiB: "33554432KiB",
  308. IsIoOptimized: true,
  309. OSType: "Linux",
  310. ProviderID: "cn-hangzhou.i-test-12",
  311. InstanceTypeFamily: "re6",
  312. },
  313. expectedError: nil,
  314. },
  315. {
  316. name: "test Persistent Memory Optimized type with re6p instance family",
  317. teststruct: &SlimK8sNode{
  318. InstanceType: "ecs.re6p.4xlarge",
  319. RegionID: "cn-hangzhou",
  320. PriceUnit: "Hour",
  321. MemorySizeInKiB: "33554432KiB",
  322. IsIoOptimized: true,
  323. OSType: "Linux",
  324. ProviderID: "cn-hangzhou.i-test-13",
  325. InstanceTypeFamily: "re6p",
  326. },
  327. expectedError: nil,
  328. },
  329. {
  330. name: "test Memory type with re4 instance family",
  331. teststruct: &SlimK8sNode{
  332. InstanceType: "ecs.re4.10xlarge",
  333. RegionID: "cn-hangzhou",
  334. PriceUnit: "Hour",
  335. MemorySizeInKiB: "41943040KiB",
  336. IsIoOptimized: true,
  337. OSType: "Linux",
  338. ProviderID: "cn-hangzhou.i-test-14",
  339. InstanceTypeFamily: "re4",
  340. },
  341. expectedError: nil,
  342. },
  343. {
  344. name: "test Memory optimized type with se1 instance family",
  345. teststruct: &SlimK8sNode{
  346. InstanceType: "ecs.se1.8xlarge",
  347. RegionID: "cn-hangzhou",
  348. PriceUnit: "Hour",
  349. MemorySizeInKiB: "33554432KiB",
  350. IsIoOptimized: true,
  351. OSType: "Linux",
  352. ProviderID: "cn-hangzhou.i-test-15",
  353. InstanceTypeFamily: "se1",
  354. },
  355. expectedError: nil,
  356. },
  357. {
  358. name: "test for a nil information",
  359. teststruct: nil,
  360. expectedError: fmt.Errorf("unsupported ECS pricing component at this time"),
  361. },
  362. {
  363. name: "test Cloud Disk with Category cloud representing basic disk",
  364. teststruct: &SlimK8sDisk{
  365. DiskType: "data",
  366. RegionID: "cn-hangzhou",
  367. PriceUnit: "Hour",
  368. SizeInGiB: "20",
  369. DiskCategory: "cloud",
  370. ProviderID: "d-Ali-cloud-XXX-01",
  371. StorageClass: "temp",
  372. },
  373. expectedError: nil,
  374. },
  375. {
  376. name: "test Cloud Disk with Category cloud_efficiency representing ultra disk",
  377. teststruct: &SlimK8sDisk{
  378. DiskType: "data",
  379. RegionID: "cn-hangzhou",
  380. PriceUnit: "Hour",
  381. SizeInGiB: "40",
  382. DiskCategory: "cloud_efficiency",
  383. ProviderID: "d-Ali-cloud-XXX-02",
  384. StorageClass: "temp",
  385. },
  386. expectedError: nil,
  387. },
  388. {
  389. name: "test Cloud Disk with Category cloud_ssd representing standard SSD",
  390. teststruct: &SlimK8sDisk{
  391. DiskType: "data",
  392. RegionID: "cn-hangzhou",
  393. PriceUnit: "Hour",
  394. SizeInGiB: "40",
  395. DiskCategory: "cloud_efficiency",
  396. ProviderID: "d-Ali-cloud-XXX-02",
  397. StorageClass: "temp",
  398. },
  399. expectedError: nil,
  400. },
  401. {
  402. name: "test Cloud Disk with Category cloud_essd representing Enhanced SSD with PL2 performance level",
  403. teststruct: &SlimK8sDisk{
  404. DiskType: "data",
  405. RegionID: "cn-hangzhou",
  406. PriceUnit: "Hour",
  407. SizeInGiB: "80",
  408. DiskCategory: "cloud_ssd",
  409. PerformanceLevel: "PL2",
  410. ProviderID: "d-Ali-cloud-XXX-04",
  411. StorageClass: "temp",
  412. },
  413. expectedError: nil,
  414. },
  415. {
  416. name: "test incorrect disk type",
  417. teststruct: &SlimK8sNode{
  418. InstanceType: "ecs.g6.xlarge",
  419. RegionID: "ap-northeast-1",
  420. PriceUnit: "Hour",
  421. MemorySizeInKiB: "33554432KiB",
  422. IsIoOptimized: true,
  423. OSType: "Linux",
  424. ProviderID: "cn-hangzhou.i-test-15",
  425. InstanceTypeFamily: "se1",
  426. SystemDisk: &SlimK8sDisk{
  427. DiskType: "data",
  428. RegionID: "ap-northeast-1",
  429. PriceUnit: "Hour",
  430. SizeInGiB: "40",
  431. DiskCategory: "cloud_essd",
  432. PerformanceLevel: "PL1",
  433. ProviderID: "d-Ali-cloud-XXX-04",
  434. StorageClass: "temp",
  435. },
  436. },
  437. expectedError: nil,
  438. },
  439. }
  440. custom := &models.CustomPricing{}
  441. for _, c := range cases {
  442. t.Run(c.name, func(t *testing.T) {
  443. pricingObj, err := processDescribePriceAndCreateAlibabaPricing(client, c.teststruct, signer, custom)
  444. if err != nil && c.expectedError == nil {
  445. t.Fatalf("Case name %s: got an error %s", c.name, err)
  446. }
  447. if c.teststruct != nil {
  448. if pricingObj == nil {
  449. t.Fatalf("Case name %s: got a nil pricing object", c.name)
  450. }
  451. t.Logf("Case name %s: Pricing Information gathered for instanceType is %v", c.name, pricingObj.PricingTerms.PricingDetails.TradePrice)
  452. }
  453. })
  454. }
  455. }
  456. func TestGetInstanceFamilyFromType(t *testing.T) {
  457. cases := []struct {
  458. name string
  459. instanceType string
  460. expectedInstanceFamily string
  461. }{
  462. {
  463. name: "test if ecs.[instance-family].[different-type] work",
  464. instanceType: "ecs.sn2ne.2xlarge",
  465. expectedInstanceFamily: "sn2ne",
  466. },
  467. {
  468. name: "test if random word gives you ALIBABA_UNKNOWN_INSTANCE_FAMILY_TYPE value ",
  469. instanceType: "random.value",
  470. expectedInstanceFamily: ALIBABA_UNKNOWN_INSTANCE_FAMILY_TYPE,
  471. },
  472. }
  473. for _, c := range cases {
  474. t.Run(c.name, func(t *testing.T) {
  475. returnValue := getInstanceFamilyFromType(c.instanceType)
  476. if returnValue != c.expectedInstanceFamily {
  477. t.Fatalf("Case name %s: expected instance family of type %s but got %s", c.name, c.expectedInstanceFamily, returnValue)
  478. }
  479. })
  480. }
  481. }
  482. func TestDetermineKeyForPricing(t *testing.T) {
  483. type randomK8sStruct struct {
  484. name string
  485. }
  486. cases := []struct {
  487. name string
  488. testVar interface{}
  489. expectedKey string
  490. expectedError error
  491. }{
  492. {
  493. name: "test when all RegionID, InstanceType, OSType & ALIBABA_OPTIMIZE_KEYWORD words are used in Node key",
  494. testVar: &SlimK8sNode{
  495. InstanceType: "ecs.sn2.large",
  496. RegionID: "cn-hangzhou",
  497. PriceUnit: "Hour",
  498. MemorySizeInKiB: "16777216KiB",
  499. IsIoOptimized: true,
  500. OSType: "linux",
  501. ProviderID: "cn-hangzhou.i-test-04",
  502. InstanceTypeFamily: "sn2",
  503. },
  504. expectedKey: "cn-hangzhou::ecs.sn2.large::linux::optimize",
  505. expectedError: nil,
  506. },
  507. {
  508. name: "test missing InstanceType to create Node key",
  509. testVar: &SlimK8sNode{
  510. RegionID: "cn-hangzhou",
  511. PriceUnit: "Hour",
  512. MemorySizeInKiB: "16777216KiB",
  513. IsIoOptimized: true,
  514. OSType: "linux",
  515. ProviderID: "cn-hangzhou.i-test-04",
  516. },
  517. expectedKey: "cn-hangzhou::linux::optimize",
  518. expectedError: nil,
  519. },
  520. {
  521. name: "test when node has a systemDisk Information with missing Performance level",
  522. testVar: &SlimK8sNode{
  523. InstanceType: "ecs.sn2.large",
  524. RegionID: "cn-hangzhou",
  525. PriceUnit: "Hour",
  526. MemorySizeInKiB: "16777216KiB",
  527. IsIoOptimized: true,
  528. OSType: "linux",
  529. ProviderID: "cn-hangzhou.i-test-04",
  530. InstanceTypeFamily: "sn2",
  531. SystemDisk: &SlimK8sDisk{
  532. DiskType: "system",
  533. RegionID: "cn-hangzhou",
  534. PriceUnit: "Hour",
  535. SizeInGiB: "40",
  536. DiskCategory: "cloud_efficiency",
  537. ProviderID: "d-Ali-cloud-XXX-i1",
  538. StorageClass: "",
  539. },
  540. },
  541. expectedKey: "cn-hangzhou::ecs.sn2.large::linux::optimize::cloud_efficiency::40",
  542. expectedError: nil,
  543. },
  544. {
  545. name: "test when node has a systemDisk Information with all information",
  546. testVar: &SlimK8sNode{
  547. InstanceType: "ecs.sn2.large",
  548. RegionID: "cn-hangzhou",
  549. PriceUnit: "Hour",
  550. MemorySizeInKiB: "16777216KiB",
  551. IsIoOptimized: true,
  552. OSType: "linux",
  553. ProviderID: "cn-hangzhou.i-test-04",
  554. InstanceTypeFamily: "sn2",
  555. SystemDisk: &SlimK8sDisk{
  556. DiskType: "data",
  557. RegionID: "cn-hangzhou",
  558. PriceUnit: "Hour",
  559. SizeInGiB: "80",
  560. DiskCategory: "cloud_ssd",
  561. PerformanceLevel: "PL2",
  562. ProviderID: "d-Ali-cloud-XXX-04",
  563. StorageClass: "",
  564. },
  565. },
  566. expectedKey: "cn-hangzhou::ecs.sn2.large::linux::optimize::cloud_ssd::80::PL2",
  567. expectedError: nil,
  568. },
  569. {
  570. name: "test random k8s struct should return unsupported error",
  571. testVar: &randomK8sStruct{
  572. name: "test struct",
  573. },
  574. expectedKey: "",
  575. expectedError: fmt.Errorf("unsupported ECS type randomK8sStruct for DescribePrice at this time"),
  576. },
  577. {
  578. name: "test for nil check",
  579. testVar: nil,
  580. expectedKey: "",
  581. expectedError: fmt.Errorf("unsupported ECS type randomK8sStruct for DescribePrice at this time"),
  582. },
  583. {
  584. name: "test when all RegionID, InstanceType, OSType & ALIBABA_OPTIMIZE_KEYWORD words are used to key",
  585. testVar: &SlimK8sDisk{
  586. DiskType: "data",
  587. RegionID: "cn-hangzhou",
  588. PriceUnit: "Hour",
  589. SizeInGiB: "40",
  590. DiskCategory: "cloud_efficiency",
  591. ProviderID: "d-Ali-cloud-XXX-02",
  592. StorageClass: "temp",
  593. },
  594. expectedKey: "cn-hangzhou::data::cloud_efficiency::40",
  595. expectedError: nil,
  596. },
  597. {
  598. name: "test missing InstanceType to create key",
  599. testVar: &SlimK8sDisk{
  600. DiskType: "data",
  601. RegionID: "cn-hangzhou",
  602. PriceUnit: "Hour",
  603. SizeInGiB: "80",
  604. DiskCategory: "cloud_ssd",
  605. PerformanceLevel: "PL2",
  606. ProviderID: "d-Ali-cloud-XXX-04",
  607. StorageClass: "temp",
  608. },
  609. expectedKey: "cn-hangzhou::data::cloud_ssd::PL2::80",
  610. expectedError: nil,
  611. },
  612. }
  613. for _, c := range cases {
  614. t.Run(c.name, func(t *testing.T) {
  615. returnString, returnErr := determineKeyForPricing(c.testVar)
  616. if c.expectedError == nil && returnErr != nil {
  617. t.Fatalf("Case name %s: expected error was nil but received error %v", c.name, returnErr)
  618. }
  619. if returnString != c.expectedKey {
  620. t.Fatalf("Case name %s: determineKeyForPricing received %s but expected %s", c.name, returnString, c.expectedKey)
  621. }
  622. })
  623. }
  624. }
  625. func TestGenerateSlimK8sNodeFromV1Node(t *testing.T) {
  626. testv1Node := &clustercache.Node{}
  627. testv1Node.Labels = make(map[string]string)
  628. testv1Node.Labels["topology.kubernetes.io/region"] = "us-east-1"
  629. testv1Node.Labels["beta.kubernetes.io/os"] = "linux"
  630. testv1Node.Labels["node.kubernetes.io/instance-type"] = "ecs.sn2ne.2xlarge"
  631. testv1Node.Status.Capacity = v1.ResourceList{
  632. v1.ResourceMemory: *resource.NewQuantity(16, resource.BinarySI),
  633. }
  634. cases := []struct {
  635. name string
  636. testNode *clustercache.Node
  637. expectedSlimNode *SlimK8sNode
  638. }{
  639. {
  640. name: "test a generic *v1.Node to *SlimK8sNode Conversion",
  641. testNode: testv1Node,
  642. expectedSlimNode: &SlimK8sNode{
  643. InstanceType: "ecs.sn2ne.2xlarge",
  644. RegionID: "us-east-1",
  645. PriceUnit: ALIBABA_HOUR_PRICE_UNIT,
  646. MemorySizeInKiB: "16",
  647. IsIoOptimized: true,
  648. OSType: "linux",
  649. InstanceTypeFamily: "sn2ne",
  650. },
  651. },
  652. }
  653. for _, c := range cases {
  654. t.Run(c.name, func(t *testing.T) {
  655. returnSlimK8sNode := generateSlimK8sNodeFromV1Node(c.testNode)
  656. if returnSlimK8sNode.InstanceType != c.expectedSlimNode.InstanceType {
  657. t.Fatalf("unexpected conversion in function generateSlimK8sNodeFromV1Node expected InstanceType: %s , received InstanceType: %s", c.expectedSlimNode.InstanceType, returnSlimK8sNode.InstanceType)
  658. }
  659. if returnSlimK8sNode.RegionID != c.expectedSlimNode.RegionID {
  660. t.Fatalf("unexpected conversion in function generateSlimK8sNodeFromV1Node expected RegionID: %s , received RegionID: %s", c.expectedSlimNode.RegionID, returnSlimK8sNode.RegionID)
  661. }
  662. if returnSlimK8sNode.PriceUnit != c.expectedSlimNode.PriceUnit {
  663. t.Fatalf("unexpected conversion in function generateSlimK8sNodeFromV1Node expected PriceUnit: %s , received PriceUnit: %s", c.expectedSlimNode.PriceUnit, returnSlimK8sNode.PriceUnit)
  664. }
  665. if returnSlimK8sNode.MemorySizeInKiB != c.expectedSlimNode.MemorySizeInKiB {
  666. t.Fatalf("unexpected conversion in function generateSlimK8sNodeFromV1Node expected MemorySizeInKiB: %s , received MemorySizeInKiB: %s", c.expectedSlimNode.MemorySizeInKiB, returnSlimK8sNode.MemorySizeInKiB)
  667. }
  668. if returnSlimK8sNode.OSType != c.expectedSlimNode.OSType {
  669. t.Fatalf("unexpected conversion in function generateSlimK8sNodeFromV1Node expected OSType: %s , received OSType: %s", c.expectedSlimNode.OSType, returnSlimK8sNode.OSType)
  670. }
  671. if returnSlimK8sNode.InstanceTypeFamily != c.expectedSlimNode.InstanceTypeFamily {
  672. t.Fatalf("unexpected conversion in function generateSlimK8sNodeFromV1Node expected InstanceTypeFamily: %s , received InstanceTypeFamily: %s", c.expectedSlimNode.InstanceTypeFamily, returnSlimK8sNode.InstanceTypeFamily)
  673. }
  674. })
  675. }
  676. }
  677. func TestGenerateSlimK8sDiskFromV1PV(t *testing.T) {
  678. testv1PV := &clustercache.PersistentVolume{}
  679. testv1PV.Spec.Capacity = v1.ResourceList{
  680. v1.ResourceStorage: *resource.NewQuantity(16*1024*1024*1024, resource.BinarySI),
  681. }
  682. testv1PV.Spec.CSI = &v1.CSIPersistentVolumeSource{}
  683. testv1PV.Spec.CSI.VolumeHandle = "testPV"
  684. testv1PV.Spec.CSI.VolumeAttributes = map[string]string{
  685. "performanceLevel": "PL2",
  686. "type": "cloud_essd",
  687. }
  688. testv1PV.Spec.CSI.VolumeHandle = "testPV"
  689. testv1PV.Spec.StorageClassName = "testStorageClass"
  690. cases := []struct {
  691. name string
  692. testPV *clustercache.PersistentVolume
  693. expectedSlimDisk *SlimK8sDisk
  694. inpRegionID string
  695. }{
  696. {
  697. name: "test a generic *v1.Node to *SlimK8sNode Conversion",
  698. testPV: testv1PV,
  699. expectedSlimDisk: &SlimK8sDisk{
  700. DiskType: ALIBABA_DATA_DISK_CATEGORY,
  701. RegionID: "us-east-1",
  702. PriceUnit: ALIBABA_HOUR_PRICE_UNIT,
  703. SizeInGiB: "16",
  704. DiskCategory: "cloud_essd",
  705. PerformanceLevel: "PL2",
  706. ProviderID: "testPV",
  707. StorageClass: "testStorageClass",
  708. },
  709. inpRegionID: "us-east-1",
  710. },
  711. }
  712. for _, c := range cases {
  713. t.Run(c.name, func(t *testing.T) {
  714. returnSlimK8sDisk := generateSlimK8sDiskFromV1PV(c.testPV, c.inpRegionID)
  715. if returnSlimK8sDisk.DiskType != c.expectedSlimDisk.DiskType {
  716. t.Fatalf("unexpected conversion in function generateSlimK8sDiskFromV1PV expected DiskType: %s , received DiskType: %s", c.expectedSlimDisk.DiskType, returnSlimK8sDisk.DiskType)
  717. }
  718. if returnSlimK8sDisk.RegionID != c.expectedSlimDisk.RegionID {
  719. t.Fatalf("unexpected conversion in function generateSlimK8sDiskFromV1PV expected RegionID: %s , received RegionID Type: %s", c.expectedSlimDisk.RegionID, returnSlimK8sDisk.RegionID)
  720. }
  721. if returnSlimK8sDisk.PriceUnit != c.expectedSlimDisk.PriceUnit {
  722. t.Fatalf("unexpected conversion in function generateSlimK8sDiskFromV1PV expected PriceUnit: %s , received PriceUnit Type: %s", c.expectedSlimDisk.PriceUnit, returnSlimK8sDisk.PriceUnit)
  723. }
  724. if returnSlimK8sDisk.SizeInGiB != c.expectedSlimDisk.SizeInGiB {
  725. t.Fatalf("unexpected conversion in function generateSlimK8sDiskFromV1PV expected SizeInGiB: %s , received SizeInGiB Type: %s", c.expectedSlimDisk.SizeInGiB, returnSlimK8sDisk.SizeInGiB)
  726. }
  727. if returnSlimK8sDisk.DiskCategory != c.expectedSlimDisk.DiskCategory {
  728. t.Fatalf("unexpected conversion in function generateSlimK8sDiskFromV1PV expected DiskCategory: %s , received DiskCategory Type: %s", c.expectedSlimDisk.DiskCategory, returnSlimK8sDisk.DiskCategory)
  729. }
  730. if returnSlimK8sDisk.PerformanceLevel != c.expectedSlimDisk.PerformanceLevel {
  731. t.Fatalf("unexpected conversion in function generateSlimK8sDiskFromV1PV expected PerformanceLevel: %s , received PerformanceLevel Type: %s", c.expectedSlimDisk.PerformanceLevel, returnSlimK8sDisk.PerformanceLevel)
  732. }
  733. if returnSlimK8sDisk.ProviderID != c.expectedSlimDisk.ProviderID {
  734. t.Fatalf("unexpected conversion in function generateSlimK8sDiskFromV1PV expected ProviderID: %s , received ProviderID Type: %s", c.expectedSlimDisk.ProviderID, returnSlimK8sDisk.ProviderID)
  735. }
  736. if returnSlimK8sDisk.StorageClass != c.expectedSlimDisk.StorageClass {
  737. t.Fatalf("unexpected conversion in function generateSlimK8sDiskFromV1PV expected StorageClass: %s , received StorageClass Type: %s", c.expectedSlimDisk.StorageClass, returnSlimK8sDisk.StorageClass)
  738. }
  739. })
  740. }
  741. }
  742. func TestGetNumericalValueFromResourceQuantity(t *testing.T) {
  743. cases := []struct {
  744. name string
  745. inputResourceQuanity string
  746. expectedValue string
  747. }{
  748. {
  749. name: "positive scenario: when inputResourceQuantity is 10Gi",
  750. inputResourceQuanity: "10Gi",
  751. expectedValue: "10",
  752. },
  753. {
  754. name: "negative scenario: when inputResourceQuantity is Gi",
  755. inputResourceQuanity: "Gi",
  756. expectedValue: ALIBABA_DEFAULT_DATADISK_SIZE,
  757. },
  758. {
  759. name: "negative scenario: when inputResourceQuantity is 10",
  760. inputResourceQuanity: "10",
  761. expectedValue: ALIBABA_DEFAULT_DATADISK_SIZE,
  762. },
  763. {
  764. name: "negative scenario: when inputResourceQuantity is empty string",
  765. inputResourceQuanity: "",
  766. expectedValue: ALIBABA_DEFAULT_DATADISK_SIZE,
  767. },
  768. }
  769. for _, c := range cases {
  770. t.Run(c.name, func(t *testing.T) {
  771. returnValue := getNumericalValueFromResourceQuantity(c.inputResourceQuanity)
  772. if c.expectedValue != returnValue {
  773. t.Fatalf("Case name %s: getNumericalValueFromResourceQuantity received %s but expected %s", c.name, returnValue, c.expectedValue)
  774. }
  775. })
  776. }
  777. }
  778. func TestDeterminePVRegion(t *testing.T) {
  779. genericNodeAffinityTestStruct := v1.NodeSelectorTerm{
  780. MatchExpressions: []v1.NodeSelectorRequirement{
  781. {
  782. Key: "topology.diskplugin.csi.alibabacloud.com/zone",
  783. Operator: v1.NodeSelectorOpIn,
  784. Values: []string{"us-east-1a"},
  785. },
  786. },
  787. MatchFields: []v1.NodeSelectorRequirement{},
  788. }
  789. // testPV1 contains the Label with region information as well as node affinity in spec
  790. testPV1 := &clustercache.PersistentVolume{}
  791. testPV1.Name = "testPV1"
  792. testPV1.Labels = make(map[string]string)
  793. testPV1.Labels[ALIBABA_DISK_TOPOLOGY_REGION_LABEL] = "us-east-1"
  794. testPV1.Spec.NodeAffinity = &v1.VolumeNodeAffinity{
  795. Required: &v1.NodeSelector{
  796. NodeSelectorTerms: []v1.NodeSelectorTerm{genericNodeAffinityTestStruct},
  797. },
  798. }
  799. // testPV2 contains the only zone label
  800. testPV2 := &clustercache.PersistentVolume{}
  801. testPV2.Name = "testPV2"
  802. testPV2.Labels = make(map[string]string)
  803. testPV2.Labels[ALIBABA_DISK_TOPOLOGY_ZONE_LABEL] = "us-east-1a"
  804. // testPV3 contains only node affinity in spec
  805. testPV3 := &clustercache.PersistentVolume{}
  806. testPV3.Name = "testPV3"
  807. testPV3.Spec.NodeAffinity = &v1.VolumeNodeAffinity{
  808. Required: &v1.NodeSelector{
  809. NodeSelectorTerms: []v1.NodeSelectorTerm{genericNodeAffinityTestStruct},
  810. },
  811. }
  812. // testPV4 contains no label/annotation or any node affinity
  813. testPV4 := &clustercache.PersistentVolume{}
  814. testPV4.Name = "testPV4"
  815. cases := []struct {
  816. name string
  817. inputPV *clustercache.PersistentVolume
  818. expectedRegion string
  819. }{
  820. {
  821. name: "When Region label topology.diskplugin.csi.alibabacloud.com/region is present along with node affinity details",
  822. inputPV: testPV1,
  823. expectedRegion: "us-east-1",
  824. },
  825. {
  826. name: "When zone label topology.diskplugin.csi.alibabacloud.com/zone is present function has to determine region",
  827. inputPV: testPV2,
  828. expectedRegion: "us-east-1",
  829. },
  830. {
  831. name: "When only node affinity detail is present function has to determine the region",
  832. inputPV: testPV3,
  833. expectedRegion: "us-east-1",
  834. },
  835. {
  836. name: "When no region/zone information is present function returns empty to default to cluster region",
  837. inputPV: testPV4,
  838. expectedRegion: "",
  839. },
  840. }
  841. for _, c := range cases {
  842. t.Run(c.name, func(t *testing.T) {
  843. returnRegion := determinePVRegion(c.inputPV)
  844. if c.expectedRegion != returnRegion {
  845. t.Fatalf("Case name %s: determinePVRegion received region :%s but expected region: %s", c.name, returnRegion, c.expectedRegion)
  846. }
  847. })
  848. }
  849. }
  850. func TestGetInstanceFamilyGenerationFromType(t *testing.T) {
  851. cases := []struct {
  852. name string
  853. instanceType string
  854. expectedInstanceFamilyGeneration int
  855. }{
  856. {
  857. name: "test if ecs.[instance-family].[different-type] work",
  858. instanceType: "ecs.sn2ne.2xlarge",
  859. expectedInstanceFamilyGeneration: 2,
  860. },
  861. {
  862. name: "test if ecs.[instance-family].[different-type] work",
  863. instanceType: "ecs.g7.large",
  864. expectedInstanceFamilyGeneration: 7,
  865. },
  866. {
  867. name: "test if random word gives you ALIBABA_UNKNOWN_INSTANCE_FAMILY_TYPE value ",
  868. instanceType: "random.value",
  869. expectedInstanceFamilyGeneration: -1,
  870. },
  871. }
  872. for _, c := range cases {
  873. t.Run(c.name, func(t *testing.T) {
  874. returnValue := getInstanceFamilyGenerationFromType(c.instanceType)
  875. if returnValue != c.expectedInstanceFamilyGeneration {
  876. t.Fatalf("Case name %s: expected instance family generation of type %d but got %d", c.name, c.expectedInstanceFamilyGeneration, returnValue)
  877. }
  878. })
  879. }
  880. }
  881. func TestCreateDescribeNodePriceACSRequest(t *testing.T) {
  882. cases := []struct {
  883. name string
  884. testStruct interface{}
  885. expectedError error
  886. expectedDiskCategory string
  887. }{
  888. {
  889. // Test case for instance type ecs.g6.large
  890. name: "test request parma when instance type is ecs.g6.large",
  891. testStruct: &SlimK8sNode{
  892. InstanceType: "ecs.g6.large",
  893. RegionID: "cn-hangzhou",
  894. PriceUnit: "Hour",
  895. MemorySizeInKiB: "16KiB",
  896. IsIoOptimized: true,
  897. OSType: "Linux",
  898. ProviderID: "Ali-XXX-node-01",
  899. InstanceTypeFamily: "g6",
  900. },
  901. expectedError: nil,
  902. expectedDiskCategory: "",
  903. },
  904. {
  905. // Test case for instance type ecs.g7.large
  906. name: "test request parma when instance type is ecs.g7.large",
  907. testStruct: &SlimK8sNode{
  908. InstanceType: "ecs.g7.large",
  909. RegionID: "cn-hangzhou",
  910. PriceUnit: "Hour",
  911. MemorySizeInKiB: "16KiB",
  912. IsIoOptimized: true,
  913. OSType: "Linux",
  914. ProviderID: "Ali-XXX-node-02",
  915. InstanceTypeFamily: "g7",
  916. },
  917. expectedError: nil,
  918. expectedDiskCategory: ALIBABA_DISK_CLOUD_ESSD_CATEGORY,
  919. },
  920. {
  921. // Test case for instance type ecs.g7.large, this instance type is in 'alibabaDefaultToCloudEssd'
  922. name: "test request parma when instance type is ecs.g6e.large",
  923. testStruct: &SlimK8sNode{
  924. InstanceType: "ecs.g6e.large",
  925. RegionID: "cn-hangzhou",
  926. PriceUnit: "Hour",
  927. MemorySizeInKiB: "16KiB",
  928. IsIoOptimized: true,
  929. OSType: "Linux",
  930. ProviderID: "Ali-XXX-node-03",
  931. InstanceTypeFamily: "g6e",
  932. },
  933. expectedError: nil,
  934. expectedDiskCategory: ALIBABA_DISK_CLOUD_ESSD_CATEGORY,
  935. },
  936. }
  937. for _, c := range cases {
  938. t.Run(c.name, func(t *testing.T) {
  939. req, err := createDescribePriceACSRequest(c.testStruct)
  940. t.Logf("Request Params SystemDisk.Category: %v", req.QueryParams["SystemDisk.Category"])
  941. if err != nil && c.expectedError != nil {
  942. t.Fatalf("Case name %s: Error converting to Alibaba cloud request", c.name)
  943. }
  944. if c.expectedDiskCategory != req.QueryParams["SystemDisk.Category"] {
  945. t.Fatalf("Case name %s: Disk Category is not set correctly", c.name)
  946. }
  947. })
  948. }
  949. }