walinator_test.go 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347
  1. package metric
  2. import (
  3. "reflect"
  4. "testing"
  5. "time"
  6. "github.com/opencost/opencost/core/pkg/exporter"
  7. "github.com/opencost/opencost/core/pkg/storage"
  8. "github.com/opencost/opencost/core/pkg/util/timeutil"
  9. "github.com/opencost/opencost/modules/collector-source/pkg/metric/aggregator"
  10. "github.com/opencost/opencost/modules/collector-source/pkg/util"
  11. )
  12. const TestActiveMinutesID = "TestActiveMinutes"
  13. const TestAverageID = "TestAverage"
  14. const TestMetric = "test_metric"
  15. func testMetricCollector() MetricStore {
  16. memStore := NewInMemoryMetricStore()
  17. memStore.Register(NewMetricCollector(
  18. TestActiveMinutesID,
  19. TestMetric,
  20. []string{
  21. "test",
  22. },
  23. aggregator.ActiveMinutes,
  24. nil,
  25. ))
  26. memStore.Register(NewMetricCollector(
  27. TestAverageID,
  28. TestMetric,
  29. []string{
  30. "test",
  31. },
  32. aggregator.AverageOverTime,
  33. nil,
  34. ))
  35. return memStore
  36. }
  37. func TestWalinator_Update(t *testing.T) {
  38. time2 := time.Now().UTC().Truncate(timeutil.Day)
  39. time1 := time2.Add(-timeutil.Day)
  40. store := storage.NewMemoryStorage()
  41. res1d, _ := util.NewResolution(util.ResolutionConfiguration{
  42. Interval: "1d",
  43. Retention: 3,
  44. })
  45. resolutions := []*util.Resolution{
  46. res1d,
  47. }
  48. repo := NewMetricRepository(
  49. resolutions,
  50. testMetricCollector,
  51. )
  52. wal, _ := NewWalinator(
  53. "test",
  54. store,
  55. resolutions,
  56. repo,
  57. )
  58. inputUpdates1 := []Update{
  59. {
  60. Name: TestMetric,
  61. Labels: map[string]string{
  62. "test": "test",
  63. },
  64. Value: 1,
  65. AdditionalInfo: nil,
  66. },
  67. }
  68. wal.Update(&UpdateSet{
  69. Timestamp: time1,
  70. Updates: inputUpdates1,
  71. })
  72. // check that the repo has a collector
  73. if len(repo.resolutionStores["1d"].collectors) != 1 {
  74. t.Error("call to Update did not update repository correctly")
  75. }
  76. files, _ := store.List(wal.paths.Dir())
  77. // check storage
  78. if len(files) != 1 {
  79. t.Error("Update did not update storage")
  80. }
  81. }
  82. func TestWalinator_restore(t *testing.T) {
  83. time3 := time.Now().UTC().Truncate(timeutil.Day)
  84. time2 := time3.Add(-12 * time.Hour)
  85. time1 := time3.Add(-timeutil.Day)
  86. store := storage.NewMemoryStorage()
  87. res1d, _ := util.NewResolution(util.ResolutionConfiguration{
  88. Interval: "1d",
  89. Retention: 3,
  90. })
  91. resolutions := []*util.Resolution{
  92. res1d,
  93. }
  94. repo := NewMetricRepository(
  95. resolutions,
  96. testMetricCollector,
  97. )
  98. wal, _ := NewWalinator(
  99. "test",
  100. store,
  101. resolutions,
  102. repo,
  103. )
  104. inputUpdates1 := []Update{
  105. {
  106. Name: TestMetric,
  107. Labels: map[string]string{
  108. "test": "test",
  109. },
  110. Value: 1,
  111. AdditionalInfo: nil,
  112. },
  113. }
  114. inputUpdates2 := []Update{
  115. {
  116. Name: TestMetric,
  117. Labels: map[string]string{
  118. "test": "test",
  119. },
  120. Value: 2,
  121. AdditionalInfo: nil,
  122. },
  123. }
  124. inputUpdates3 := []Update{
  125. {
  126. Name: TestMetric,
  127. Labels: map[string]string{
  128. "test": "test",
  129. },
  130. Value: 3,
  131. AdditionalInfo: nil,
  132. },
  133. }
  134. wal.Update(&UpdateSet{
  135. Timestamp: time1,
  136. Updates: inputUpdates1,
  137. })
  138. wal.Update(&UpdateSet{
  139. Timestamp: time2,
  140. Updates: inputUpdates2,
  141. })
  142. wal.Update(&UpdateSet{
  143. Timestamp: time3,
  144. Updates: inputUpdates3,
  145. })
  146. repo2 := NewMetricRepository(
  147. resolutions,
  148. testMetricCollector,
  149. )
  150. // replace the repo in the walinator
  151. wal.updater = repo2
  152. wal.restore()
  153. collector1, err := repo.GetCollector("1d", time3)
  154. if err != nil {
  155. t.Fatalf("failed to get collector from repo1: %s", err.Error())
  156. }
  157. activeMinutesRes1, err := collector1.Query(TestActiveMinutesID)
  158. if err != nil {
  159. t.Fatalf("failed to query %s from repo1: %s", TestActiveMinutesID, err.Error())
  160. }
  161. averageRes1, err := collector1.Query(TestAverageID)
  162. if err != nil {
  163. t.Fatalf("failed to query %s from repo1: %s", TestAverageID, err.Error())
  164. }
  165. collector2, err := repo2.GetCollector("1d", time3)
  166. if err != nil {
  167. t.Fatalf("failed to get collector from repo2: %s", err.Error())
  168. }
  169. activeMinutesRes2, err := collector2.Query(TestActiveMinutesID)
  170. if err != nil {
  171. t.Fatalf("failed to query %s from repo2: %s", TestActiveMinutesID, err.Error())
  172. }
  173. averageRes2, err := collector2.Query(TestAverageID)
  174. if err != nil {
  175. t.Fatalf("failed to query %s from repo2: %s", TestAverageID, err.Error())
  176. }
  177. if !reflect.DeepEqual(activeMinutesRes1, activeMinutesRes2) {
  178. t.Errorf("active minute query results did not match 1: %v, 2: %v", activeMinutesRes1, activeMinutesRes2)
  179. }
  180. if !reflect.DeepEqual(averageRes1, averageRes2) {
  181. t.Errorf("average query results did not match 1: %v, 2: %v", averageRes1, averageRes2)
  182. }
  183. }
  184. func TestWalinator_clean(t *testing.T) {
  185. time3 := time.Now().UTC().Truncate(timeutil.Day)
  186. time2 := time3.Add(-timeutil.Day)
  187. time1 := time2.Add(-timeutil.Day)
  188. store := storage.NewMemoryStorage()
  189. res1d, _ := util.NewResolution(util.ResolutionConfiguration{
  190. Interval: "1d",
  191. Retention: 2,
  192. })
  193. resolutions := []*util.Resolution{
  194. res1d,
  195. }
  196. repo := NewMetricRepository(
  197. resolutions,
  198. testMetricCollector,
  199. )
  200. wal, _ := NewWalinator(
  201. "test",
  202. store,
  203. resolutions,
  204. repo,
  205. )
  206. inputUpdates1 := []Update{
  207. {
  208. Name: TestMetric,
  209. Labels: map[string]string{
  210. "test": "test",
  211. },
  212. Value: 1,
  213. AdditionalInfo: nil,
  214. },
  215. }
  216. wal.Update(&UpdateSet{
  217. Timestamp: time1,
  218. Updates: inputUpdates1,
  219. })
  220. wal.Update(&UpdateSet{
  221. Timestamp: time2,
  222. Updates: inputUpdates1,
  223. })
  224. wal.Update(&UpdateSet{
  225. Timestamp: time3,
  226. Updates: inputUpdates1,
  227. })
  228. files, err := wal.getFileInfos()
  229. if err != nil {
  230. t.Errorf("failed to retrieve file info: %s", err.Error())
  231. }
  232. if len(files) != 3 {
  233. t.Errorf("incorrect number of files after updates: wanted %d, got %d", 3, len(files))
  234. }
  235. wal.clean()
  236. files, err = wal.getFileInfos()
  237. if err != nil {
  238. t.Errorf("failed to retrieve file info: %s", err.Error())
  239. }
  240. if len(files) != 2 {
  241. t.Errorf("incorrect number of files after clean: wanted %d, got %d", 2, len(files))
  242. }
  243. }
  244. func Test_deserializeUpdateSet(t *testing.T) {
  245. inputUpdateSet1 := &UpdateSet{
  246. Updates: []Update{
  247. {
  248. Name: TestMetric,
  249. Labels: map[string]string{
  250. "test": "test",
  251. },
  252. Value: 1,
  253. AdditionalInfo: nil,
  254. },
  255. },
  256. }
  257. jsonEncoder := exporter.NewJSONEncoder[UpdateSet]()
  258. gZipJsonEncoder := exporter.NewGZipEncoder(exporter.NewJSONEncoder[UpdateSet]())
  259. invalidBytes := []byte("invalid")
  260. jsonBytes1, _ := jsonEncoder.Encode(inputUpdateSet1)
  261. gZipJsonBytes1, _ := gZipJsonEncoder.Encode(inputUpdateSet1)
  262. tests := map[string]struct {
  263. ext string
  264. b []byte
  265. want *UpdateSet
  266. wantErr bool
  267. }{
  268. "json with invalid": {
  269. ext: "json",
  270. b: invalidBytes,
  271. want: nil,
  272. wantErr: true,
  273. },
  274. "json with json": {
  275. ext: "json",
  276. b: jsonBytes1,
  277. want: inputUpdateSet1,
  278. wantErr: false,
  279. },
  280. "json with gzipjson": {
  281. ext: "json",
  282. b: gZipJsonBytes1,
  283. want: nil,
  284. wantErr: true,
  285. },
  286. "json.gz with invalid": {
  287. ext: "json.gz",
  288. b: invalidBytes,
  289. want: nil,
  290. wantErr: true,
  291. },
  292. "json.gz with json": {
  293. ext: "json.gz",
  294. b: jsonBytes1,
  295. want: nil,
  296. wantErr: true,
  297. },
  298. "json.gz with gzipjson": {
  299. ext: "json.gz",
  300. b: gZipJsonBytes1,
  301. want: inputUpdateSet1,
  302. wantErr: false,
  303. },
  304. }
  305. for name, tt := range tests {
  306. t.Run(name, func(t *testing.T) {
  307. got, err := deserializeUpdateSet(tt.ext, tt.b)
  308. if (err != nil) != tt.wantErr {
  309. t.Errorf("deserializeUpdateSet() error = %v, wantErr %v", err, tt.wantErr)
  310. return
  311. }
  312. if !reflect.DeepEqual(got, tt.want) {
  313. t.Errorf("deserializeUpdateSet() got = %v, want %v", got, tt.want)
  314. }
  315. })
  316. }
  317. }