cloudcostitem_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. package kubecost
  2. import (
  3. "testing"
  4. "time"
  5. "github.com/opencost/opencost/pkg/util/timeutil"
  6. )
  7. var cciProperties1 = CloudCostItemProperties{
  8. ProviderID: "providerid1",
  9. Provider: "provider1",
  10. WorkGroupID: "workgroup1",
  11. BillingID: "billing1",
  12. Service: "service1",
  13. Category: "category1",
  14. Labels: map[string]string{
  15. "label1": "value1",
  16. "label2": "value2",
  17. },
  18. }
  19. // TestCloudCostItem_LoadCloudCostItem checks that loaded CloudCostItems end up in the correct set in the
  20. // correct proportions
  21. func TestCloudCostItem_LoadCloudCostItem(t *testing.T) {
  22. // create values for 3 day Range tests
  23. end := RoundBack(time.Now().UTC(), timeutil.Day)
  24. start := end.Add(-3 * timeutil.Day)
  25. dayWindows, _ := GetWindows(start, end, timeutil.Day)
  26. emtpyCCISR, _ := NewCloudCostItemSetRange(start, end, timeutil.Day, "integration")
  27. testCases := map[string]struct {
  28. cci []*CloudCostItem
  29. ccisr *CloudCostItemSetRange
  30. expected []*CloudCostItemSet
  31. }{
  32. "Load Single Day On Grid": {
  33. cci: []*CloudCostItem{
  34. {
  35. Properties: cciProperties1,
  36. Window: dayWindows[0],
  37. IsKubernetes: true,
  38. Cost: 100,
  39. NetCost: 80,
  40. },
  41. },
  42. ccisr: emtpyCCISR.Clone(),
  43. expected: []*CloudCostItemSet{
  44. {
  45. Integration: "integration",
  46. Window: dayWindows[0],
  47. CloudCostItems: map[string]*CloudCostItem{
  48. cciProperties1.Key(): {
  49. Properties: cciProperties1,
  50. Window: dayWindows[0],
  51. IsKubernetes: true,
  52. Cost: 100,
  53. NetCost: 80,
  54. },
  55. },
  56. },
  57. {
  58. Integration: "integration",
  59. Window: dayWindows[1],
  60. CloudCostItems: map[string]*CloudCostItem{},
  61. },
  62. {
  63. Integration: "integration",
  64. Window: dayWindows[2],
  65. CloudCostItems: map[string]*CloudCostItem{},
  66. },
  67. },
  68. },
  69. "Load Single Day Off Grid": {
  70. cci: []*CloudCostItem{
  71. {
  72. Properties: cciProperties1,
  73. Window: NewClosedWindow(start.Add(12*time.Hour), start.Add(36*time.Hour)),
  74. IsKubernetes: true,
  75. Cost: 100,
  76. NetCost: 80,
  77. },
  78. },
  79. ccisr: emtpyCCISR.Clone(),
  80. expected: []*CloudCostItemSet{
  81. {
  82. Integration: "integration",
  83. Window: dayWindows[0],
  84. CloudCostItems: map[string]*CloudCostItem{
  85. cciProperties1.Key(): {
  86. Properties: cciProperties1,
  87. Window: NewClosedWindow(start.Add(12*time.Hour), start.Add(24*time.Hour)),
  88. IsKubernetes: true,
  89. Cost: 50,
  90. NetCost: 40,
  91. },
  92. },
  93. },
  94. {
  95. Integration: "integration",
  96. Window: dayWindows[1],
  97. CloudCostItems: map[string]*CloudCostItem{
  98. cciProperties1.Key(): {
  99. Properties: cciProperties1,
  100. Window: NewClosedWindow(start.Add(24*time.Hour), start.Add(36*time.Hour)),
  101. IsKubernetes: true,
  102. Cost: 50,
  103. NetCost: 40,
  104. },
  105. },
  106. },
  107. {
  108. Integration: "integration",
  109. Window: dayWindows[2],
  110. CloudCostItems: map[string]*CloudCostItem{},
  111. },
  112. },
  113. },
  114. "Load Single Day Off Grid Before Range Window": {
  115. cci: []*CloudCostItem{
  116. {
  117. Properties: cciProperties1,
  118. Window: NewClosedWindow(start.Add(-12*time.Hour), start.Add(12*time.Hour)),
  119. IsKubernetes: true,
  120. Cost: 100,
  121. NetCost: 80,
  122. },
  123. },
  124. ccisr: emtpyCCISR.Clone(),
  125. expected: []*CloudCostItemSet{
  126. {
  127. Integration: "integration",
  128. Window: dayWindows[0],
  129. CloudCostItems: map[string]*CloudCostItem{
  130. cciProperties1.Key(): {
  131. Properties: cciProperties1,
  132. Window: NewClosedWindow(start, start.Add(12*time.Hour)),
  133. IsKubernetes: true,
  134. Cost: 50,
  135. NetCost: 40,
  136. },
  137. },
  138. },
  139. {
  140. Integration: "integration",
  141. Window: dayWindows[1],
  142. CloudCostItems: map[string]*CloudCostItem{},
  143. },
  144. {
  145. Integration: "integration",
  146. Window: dayWindows[2],
  147. CloudCostItems: map[string]*CloudCostItem{},
  148. },
  149. },
  150. },
  151. "Load Single Day Off Grid After Range Window": {
  152. cci: []*CloudCostItem{
  153. {
  154. Properties: cciProperties1,
  155. Window: NewClosedWindow(end.Add(-12*time.Hour), end.Add(12*time.Hour)),
  156. IsKubernetes: true,
  157. Cost: 100,
  158. NetCost: 80,
  159. },
  160. },
  161. ccisr: emtpyCCISR.Clone(),
  162. expected: []*CloudCostItemSet{
  163. {
  164. Integration: "integration",
  165. Window: dayWindows[0],
  166. CloudCostItems: map[string]*CloudCostItem{},
  167. },
  168. {
  169. Integration: "integration",
  170. Window: dayWindows[1],
  171. CloudCostItems: map[string]*CloudCostItem{},
  172. },
  173. {
  174. Integration: "integration",
  175. Window: dayWindows[2],
  176. CloudCostItems: map[string]*CloudCostItem{
  177. cciProperties1.Key(): {
  178. Properties: cciProperties1,
  179. Window: NewClosedWindow(end.Add(-12*time.Hour), end),
  180. IsKubernetes: true,
  181. Cost: 50,
  182. NetCost: 40,
  183. },
  184. },
  185. },
  186. },
  187. },
  188. "Single Day Kubecost Percent": {
  189. cci: []*CloudCostItem{
  190. {
  191. Properties: cciProperties1,
  192. Window: dayWindows[1],
  193. IsKubernetes: true,
  194. Cost: 75,
  195. NetCost: 60,
  196. },
  197. {
  198. Properties: cciProperties1,
  199. Window: dayWindows[1],
  200. IsKubernetes: true,
  201. Cost: 25,
  202. NetCost: 20,
  203. },
  204. },
  205. ccisr: emtpyCCISR.Clone(),
  206. expected: []*CloudCostItemSet{
  207. {
  208. Integration: "integration",
  209. Window: dayWindows[0],
  210. CloudCostItems: map[string]*CloudCostItem{},
  211. },
  212. {
  213. Integration: "integration",
  214. Window: dayWindows[1],
  215. CloudCostItems: map[string]*CloudCostItem{
  216. cciProperties1.Key(): {
  217. Properties: cciProperties1,
  218. Window: dayWindows[1],
  219. IsKubernetes: true,
  220. Cost: 100,
  221. NetCost: 80,
  222. },
  223. },
  224. },
  225. {
  226. Integration: "integration",
  227. Window: dayWindows[2],
  228. CloudCostItems: map[string]*CloudCostItem{},
  229. },
  230. },
  231. },
  232. }
  233. for name, tc := range testCases {
  234. t.Run(name, func(t *testing.T) {
  235. // load Cloud Cost Items
  236. for _, cci := range tc.cci {
  237. tc.ccisr.LoadCloudCostItem(cci)
  238. }
  239. if len(tc.ccisr.CloudCostItemSets) != len(tc.expected) {
  240. t.Errorf("the CloudCostItemSetRanges did not have the expected length")
  241. }
  242. for i, ccis := range tc.ccisr.CloudCostItemSets {
  243. if !ccis.Equal(tc.expected[i]) {
  244. t.Errorf("CloudCostItemSet at index: %d did not match expected", i)
  245. }
  246. }
  247. })
  248. }
  249. }
  250. func TestGetAWSClusterFromCCI(t *testing.T) {
  251. awsCCIWithLabeleksClusterName, eksClusterName := GenerateAWSMockCCIAndPID(1, 1, AWSMatchLabel1, ComputeCategory)
  252. awsCCIWithLabeleksCtlClusterName, eksCtlClusterName := GenerateAWSMockCCIAndPID(2, 2, AWSMatchLabel2, ComputeCategory)
  253. awsCCIWithLabelWithRandomLabel, _ := GenerateAWSMockCCIAndPID(1, 1, "randomLabel", ComputeCategory)
  254. awsCCINetworkCategory, _ := GenerateAWSMockCCIAndPID(1, 1, AWSMatchLabel1, NetworkCategory)
  255. alibabaCCI, _ := GenerateAlibabaMockCCIAndPID(4, 4, AlibabaMatchLabel1, ComputeCategory)
  256. testCases := map[string]struct {
  257. testcci *CloudCostItem
  258. expected string
  259. }{
  260. "cluster in label eks_cluster_name": {
  261. testcci: awsCCIWithLabeleksClusterName,
  262. expected: eksClusterName,
  263. },
  264. "cluster in label alpha_eksctl_io_cluster_name": {
  265. testcci: awsCCIWithLabeleksCtlClusterName,
  266. expected: eksCtlClusterName,
  267. },
  268. "cluster name in random label either not eks_cluster_name or eks_cluster_name": {
  269. testcci: awsCCIWithLabelWithRandomLabel,
  270. expected: "",
  271. },
  272. "Not a AWS provider": {
  273. testcci: alibabaCCI,
  274. expected: "",
  275. },
  276. "Not a compute resource": {
  277. testcci: awsCCINetworkCategory,
  278. expected: "",
  279. },
  280. }
  281. for name, testCase := range testCases {
  282. t.Run(name, func(t *testing.T) {
  283. actual := testCase.testcci.GetAWSCluster()
  284. if actual != testCase.expected {
  285. t.Errorf("incorrect result: Actual: '%s', Expected: '%s", actual, testCase.expected)
  286. }
  287. })
  288. }
  289. }
  290. func TestGetAzureClusterFromCCI(t *testing.T) {
  291. testCases := map[string]struct {
  292. testcci *CloudCostItem
  293. expected string
  294. }{
  295. "cluster in ProviderID complete": {
  296. testcci: &CloudCostItem{
  297. IsKubernetes: true,
  298. Window: Window{},
  299. Properties: CloudCostItemProperties{
  300. Labels: map[string]string{
  301. "randomLabel": "value1",
  302. },
  303. Provider: AzureProvider,
  304. Category: ComputeCategory,
  305. ProviderID: "azure:///subscriptions/0bd50fdf-c923-4e1e-850c-196dd3dcc5d3/resourceGroups/mc_dev_dev-1_eastus/providers/Microsoft.Compute/virtualMachineScaleSets/aks-devsysz1-24570986-vmss/virtualMachines/0",
  306. },
  307. },
  308. expected: "mc_dev_dev-1_eastus",
  309. },
  310. "cluster in ProviderID complete but missing some values": {
  311. testcci: &CloudCostItem{
  312. IsKubernetes: true,
  313. Window: Window{},
  314. Properties: CloudCostItemProperties{
  315. Labels: map[string]string{
  316. "randomLabel": "value1",
  317. },
  318. Provider: AzureProvider,
  319. Category: ComputeCategory,
  320. ProviderID: "azure:///subscriptions//resourceGroups/mc_dev_dev-1_eastus/providers/Microsoft.Compute/virtualMachineScaleSets/aks-devsysz1-XXXXX-vmss/virtualMachines/0",
  321. },
  322. },
  323. expected: "mc_dev_dev-1_eastus",
  324. },
  325. "Not having enough split content in providerID": {
  326. testcci: &CloudCostItem{
  327. IsKubernetes: true,
  328. Window: Window{},
  329. Properties: CloudCostItemProperties{
  330. Labels: map[string]string{
  331. "randomLabel": "value1",
  332. },
  333. Provider: AzureProvider,
  334. Category: ComputeCategory,
  335. ProviderID: "test1",
  336. },
  337. },
  338. expected: "",
  339. },
  340. "Not a Azure provider": {
  341. testcci: &CloudCostItem{
  342. IsKubernetes: true,
  343. Window: Window{},
  344. Properties: CloudCostItemProperties{
  345. Labels: map[string]string{
  346. "randomLabel": "value1",
  347. },
  348. Provider: AWSProvider,
  349. Category: ComputeCategory,
  350. ProviderID: "test1",
  351. },
  352. },
  353. expected: "",
  354. },
  355. "Not a compute resource": {
  356. testcci: &CloudCostItem{
  357. IsKubernetes: true,
  358. Window: Window{},
  359. Properties: CloudCostItemProperties{
  360. Labels: map[string]string{
  361. "randomLabel": "value1",
  362. },
  363. Provider: AzureProvider,
  364. Category: StorageCategory,
  365. ProviderID: "pvc-xyz",
  366. },
  367. },
  368. expected: "",
  369. },
  370. }
  371. for name, testCase := range testCases {
  372. t.Run(name, func(t *testing.T) {
  373. actual := testCase.testcci.GetAzureCluster()
  374. if actual != testCase.expected {
  375. t.Errorf("incorrect result: Actual: '%s', Expected: '%s", actual, testCase.expected)
  376. }
  377. })
  378. }
  379. }
  380. func TestGetAlibabaClusterFromCCI(t *testing.T) {
  381. alibabaCCIWithACKAliyunCom, clusterName1 := GenerateAlibabaMockCCIAndPID(4, 4, AlibabaMatchLabel1, ComputeCategory)
  382. awsCCI, _ := GenerateAWSMockCCIAndPID(1, 1, AWSMatchLabel1, ComputeCategory)
  383. alibabaCCINetworkCategory, clusterName1 := GenerateAlibabaMockCCIAndPID(4, 4, AlibabaMatchLabel1, NetworkCategory)
  384. testCases := map[string]struct {
  385. testcci *CloudCostItem
  386. expected string
  387. }{
  388. "cluster in label ack.aliyun.com": {
  389. testcci: alibabaCCIWithACKAliyunCom,
  390. expected: clusterName1,
  391. },
  392. "Not a Alibaba provider": {
  393. testcci: awsCCI,
  394. expected: "",
  395. },
  396. "Not a compute resource": {
  397. testcci: alibabaCCINetworkCategory,
  398. expected: "",
  399. },
  400. }
  401. for name, testCase := range testCases {
  402. t.Run(name, func(t *testing.T) {
  403. actual := testCase.testcci.GetAlibabaCluster()
  404. if actual != testCase.expected {
  405. t.Errorf("incorrect result: Actual: '%s', Expected: '%s", actual, testCase.expected)
  406. }
  407. })
  408. }
  409. }