kubemodel_test.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331
  1. package kubemodel
  2. import (
  3. "errors"
  4. "fmt"
  5. "testing"
  6. "time"
  7. "github.com/stretchr/testify/require"
  8. )
  9. func TestCheckWindow(t *testing.T) {
  10. start := time.Now().UTC().Truncate(time.Hour)
  11. end := start.Add(time.Hour)
  12. window := Window{Start: start, End: end}
  13. windowErrMsg := func(s, e time.Time) string {
  14. return fmt.Sprintf(
  15. "start or end time (%s-%s) is outside of the window %s-%s",
  16. s.Format(time.RFC3339),
  17. e.Format(time.RFC3339),
  18. start.Format(time.RFC3339),
  19. end.Format(time.RFC3339),
  20. )
  21. }
  22. tests := []struct {
  23. name string
  24. start time.Time
  25. end time.Time
  26. wantErr string
  27. }{
  28. {
  29. name: "start before window",
  30. start: start.Add(-time.Minute),
  31. end: end,
  32. wantErr: windowErrMsg(start.Add(-time.Minute), end),
  33. },
  34. {
  35. name: "end after window",
  36. start: start,
  37. end: end.Add(time.Minute),
  38. wantErr: windowErrMsg(start, end.Add(time.Minute)),
  39. },
  40. {
  41. name: "entirely outside window",
  42. start: end.Add(time.Hour),
  43. end: end.Add(2 * time.Hour),
  44. wantErr: windowErrMsg(end.Add(time.Hour), end.Add(2*time.Hour)),
  45. },
  46. {
  47. name: "exact window boundaries",
  48. start: start,
  49. end: end,
  50. },
  51. {
  52. name: "within window",
  53. start: start.Add(15 * time.Minute),
  54. end: end.Add(-15 * time.Minute),
  55. },
  56. }
  57. for _, tt := range tests {
  58. t.Run(tt.name, func(t *testing.T) {
  59. err := checkWindow(window, tt.start, tt.end)
  60. if tt.wantErr != "" {
  61. require.EqualError(t, err, tt.wantErr)
  62. } else {
  63. require.NoError(t, err)
  64. }
  65. })
  66. }
  67. }
  68. func TestKubeModel(t *testing.T) {
  69. start := time.Now().UTC().Truncate(time.Hour)
  70. end := start.Add(time.Hour)
  71. t.Run("RegisterError", func(t *testing.T) {
  72. kms := NewKubeModelSet(start, end)
  73. require.NotNil(t, kms.Metadata)
  74. require.Len(t, kms.GetErrors(), 0)
  75. kms.Error(errors.New("test error"))
  76. require.Len(t, kms.GetErrors(), 1)
  77. require.Equal(t, "test error", kms.GetErrors()[0].Message)
  78. kms.Error(errors.New("test error 2"))
  79. require.Len(t, kms.GetErrors(), 2)
  80. require.Equal(t, "test error 2", kms.GetErrors()[1].Message)
  81. })
  82. t.Run("RegisterCluster", func(t *testing.T) {
  83. t.Run("empty cluster UID", func(t *testing.T) {
  84. var err error
  85. kms := NewKubeModelSet(start, end)
  86. err = kms.RegisterCluster(&Cluster{UID: ""})
  87. require.NotNil(t, err)
  88. require.Len(t, kms.GetErrors(), 1)
  89. require.Equal(t, "RegisterCluster: invalid cluster: UID is missing for Cluster with name ''", kms.GetErrors()[0].Message)
  90. require.Nil(t, kms.Cluster)
  91. })
  92. t.Run("new cluster UID", func(t *testing.T) {
  93. var err error
  94. var clusterUID = "cluster-uid"
  95. kms := NewKubeModelSet(start, end)
  96. err = kms.RegisterCluster(&Cluster{UID: clusterUID, Start: start, End: end})
  97. require.Nil(t, err)
  98. require.Len(t, kms.GetErrors(), 0)
  99. require.NotNil(t, kms.Cluster)
  100. require.Equal(t, clusterUID, kms.Cluster.UID)
  101. })
  102. t.Run("multiple Register calls", func(t *testing.T) {
  103. var err error
  104. var clusterUID = "cluster-uid"
  105. kms := NewKubeModelSet(start, end)
  106. err = kms.RegisterCluster(&Cluster{UID: clusterUID, Start: start, End: end})
  107. require.Nil(t, err)
  108. require.Len(t, kms.GetErrors(), 0)
  109. require.NotNil(t, kms.Cluster)
  110. require.Equal(t, clusterUID, kms.Cluster.UID)
  111. // Register cluster with same UID, expect no-op on second try
  112. err = kms.RegisterCluster(&Cluster{UID: clusterUID, Start: start, End: end})
  113. require.Nil(t, err)
  114. require.Len(t, kms.GetErrors(), 0)
  115. require.NotNil(t, kms.Cluster)
  116. require.Equal(t, clusterUID, kms.Cluster.UID)
  117. // Register cluster with another UID (should not happen), expect no-op
  118. err = kms.RegisterCluster(&Cluster{UID: "another-uid", Start: start, End: end})
  119. require.Nil(t, err)
  120. require.Len(t, kms.GetWarnings(), 1)
  121. require.Equal(t, "RegisterCluster(another-uid): attempting to change cluster UID from cluster-uid to another-uid", kms.GetWarnings()[0].Message)
  122. require.NotNil(t, kms.Cluster)
  123. require.Equal(t, clusterUID, kms.Cluster.UID) // original kms.Cluster is not modified
  124. })
  125. })
  126. t.Run("RegisterNamespace", func(t *testing.T) {
  127. t.Run("empty namespace UID", func(t *testing.T) {
  128. var err error
  129. kms := NewKubeModelSet(start, end)
  130. err = kms.RegisterNamespace(&Namespace{UID: "", Name: ""})
  131. require.NotNil(t, err)
  132. require.Len(t, kms.GetErrors(), 1)
  133. require.Equal(t, "RegisterNamespace: invalid namespace: UID is missing for Namespace with name ''", kms.GetErrors()[0].Message)
  134. require.Len(t, kms.Namespaces, 0)
  135. })
  136. t.Run("register namespace on KMS w/o cluster", func(t *testing.T) {
  137. var err error
  138. kms := NewKubeModelSet(start, end)
  139. testUID := "uid"
  140. testName := "name"
  141. err = kms.RegisterNamespace(&Namespace{UID: testUID, Name: testName, Start: start, End: end})
  142. require.Nil(t, err)
  143. require.Len(t, kms.GetWarnings(), 1)
  144. require.Equal(t, "RegisterNamespace: Cluster is nil", kms.GetWarnings()[0].Message)
  145. testNamespace := &Namespace{UID: testUID, Name: testName, Start: start, End: end}
  146. require.NotNil(t, kms.Namespaces[testUID])
  147. require.Equal(t, testNamespace, kms.Namespaces[testUID])
  148. require.NotNil(t, kms.idx.namespaceByName[testName])
  149. require.Equal(t, testNamespace, kms.idx.namespaceByName[testName])
  150. require.Equal(t, 1, kms.Metadata.ObjectCount)
  151. })
  152. t.Run("register namespace on KMS w/ cluster", func(t *testing.T) {
  153. var err error
  154. kms := NewKubeModelSet(start, end)
  155. err = kms.RegisterCluster(&Cluster{UID: "cluster-uid", Start: start, End: end})
  156. require.Nil(t, err)
  157. // At this point we have a KMS with a cluster registered
  158. testUID := "uid"
  159. testName := "name"
  160. err = kms.RegisterNamespace(&Namespace{UID: testUID, Name: testName, Start: start, End: end})
  161. require.Nil(t, err)
  162. require.Len(t, kms.GetErrors(), 0)
  163. require.NotNil(t, kms.Namespaces[testUID])
  164. testNamespace := &Namespace{UID: testUID, Name: testName, Start: start, End: end}
  165. require.Equal(t, testNamespace, kms.Namespaces[testUID])
  166. require.Equal(t, testNamespace, kms.idx.namespaceByName[testName])
  167. require.Equal(t, 1, kms.Metadata.ObjectCount)
  168. // Register same namespace again, expect no-op on second try
  169. err = kms.RegisterNamespace(&Namespace{UID: testUID, Name: testName, Start: start, End: end})
  170. require.Nil(t, err)
  171. require.Len(t, kms.GetErrors(), 0)
  172. require.NotNil(t, kms.Namespaces[testUID])
  173. require.Equal(t, testNamespace, kms.Namespaces[testUID])
  174. require.Equal(t, testNamespace, kms.idx.namespaceByName[testName])
  175. require.Equal(t, 1, kms.Metadata.ObjectCount) // remains 1
  176. })
  177. })
  178. t.Run("RegisterResourceQuota", func(t *testing.T) {
  179. t.Run("empty resourceQuota UID", func(t *testing.T) {
  180. var err error
  181. kms := NewKubeModelSet(start, end)
  182. err = kms.RegisterResourceQuota(&ResourceQuota{UID: "", Name: "test"})
  183. require.NotNil(t, err)
  184. require.Len(t, kms.GetErrors(), 1)
  185. require.Equal(t, "RegisterResourceQuota: invalid resource quota: UID is missing for ResourceQuota with name 'test'", kms.GetErrors()[0].Message)
  186. require.Len(t, kms.ResourceQuotas, 0)
  187. })
  188. t.Run("register resource quota with empty NamespaceUID", func(t *testing.T) {
  189. var err error
  190. kms := NewKubeModelSet(start, end)
  191. err = kms.RegisterResourceQuota(&ResourceQuota{UID: "uid", Name: "name", NamespaceUID: ""})
  192. require.NotNil(t, err)
  193. require.Len(t, kms.GetErrors(), 1)
  194. require.Equal(t, "RegisterResourceQuota: invalid resource quota: NamespaceUID is missing for ResourceQuota 'uid'", kms.GetErrors()[0].Message)
  195. require.Len(t, kms.ResourceQuotas, 0)
  196. })
  197. t.Run("register resource quota on KMS w/ namespace", func(t *testing.T) {
  198. kms := NewKubeModelSet(start, end)
  199. kms.RegisterCluster(&Cluster{UID: "cluster-uid", Start: start, End: end})
  200. kms.RegisterNamespace(&Namespace{UID: "namespace-uid", Name: "namespace", Start: start, End: end})
  201. // At this point we have a KMS with a cluster and namespace registered
  202. testUID := "uid"
  203. testName := "name"
  204. kms.RegisterResourceQuota(&ResourceQuota{UID: testUID, Name: testName, NamespaceUID: "namespace-uid", Start: start, End: end})
  205. testRQ := &ResourceQuota{
  206. UID: "uid",
  207. NamespaceUID: "namespace-uid",
  208. Name: "name",
  209. Start: start,
  210. End: end,
  211. Spec: &ResourceQuotaSpec{Hard: &ResourceQuotaSpecHard{}},
  212. Status: &ResourceQuotaStatus{Used: &ResourceQuotaStatusUsed{}},
  213. }
  214. require.Len(t, kms.GetErrors(), 0)
  215. require.NotNil(t, kms.ResourceQuotas[testUID])
  216. require.Equal(t, testRQ, kms.ResourceQuotas[testUID])
  217. require.Equal(t, 2, kms.Metadata.ObjectCount) // 1 namespace and 1 RQ
  218. // Register same RQ again, expect no-op on second try
  219. kms.RegisterResourceQuota(&ResourceQuota{UID: testUID, Name: testName, NamespaceUID: "namespace-uid", Start: start, End: end})
  220. require.Len(t, kms.GetErrors(), 0)
  221. require.NotNil(t, kms.ResourceQuotas[testUID])
  222. require.Equal(t, testRQ, kms.ResourceQuotas[testUID])
  223. require.Equal(t, 2, kms.Metadata.ObjectCount) // 1 namespace and 1 RQ
  224. })
  225. t.Run("register multiple RQs in multiple namespaces", func(t *testing.T) {
  226. kms := NewKubeModelSet(start, end)
  227. kms.RegisterCluster(&Cluster{UID: "cluster-uid", Start: start, End: end})
  228. kms.RegisterNamespace(&Namespace{UID: "namespace-1-uid", Name: "namespace-1", Start: start, End: end})
  229. kms.RegisterNamespace(&Namespace{UID: "namespace-2-uid", Name: "namespace-2", Start: start, End: end})
  230. kms.RegisterResourceQuota(&ResourceQuota{UID: "uid-1", Name: "name-1", NamespaceUID: "namespace-1-uid", Start: start, End: end})
  231. kms.RegisterResourceQuota(&ResourceQuota{UID: "uid-2", Name: "name-2", NamespaceUID: "namespace-2-uid", Start: start, End: end})
  232. require.Len(t, kms.GetErrors(), 0)
  233. require.NotNil(t, kms.ResourceQuotas)
  234. require.Len(t, kms.ResourceQuotas, 2)
  235. testRQ1 := &ResourceQuota{
  236. UID: "uid-1",
  237. NamespaceUID: "namespace-1-uid",
  238. Name: "name-1",
  239. Start: start,
  240. End: end,
  241. Spec: &ResourceQuotaSpec{Hard: &ResourceQuotaSpecHard{}},
  242. Status: &ResourceQuotaStatus{Used: &ResourceQuotaStatusUsed{}},
  243. }
  244. testRQ2 := &ResourceQuota{
  245. UID: "uid-2",
  246. NamespaceUID: "namespace-2-uid",
  247. Name: "name-2",
  248. Start: start,
  249. End: end,
  250. Spec: &ResourceQuotaSpec{Hard: &ResourceQuotaSpecHard{}},
  251. Status: &ResourceQuotaStatus{Used: &ResourceQuotaStatusUsed{}},
  252. }
  253. require.Equal(t, testRQ1, kms.ResourceQuotas["uid-1"])
  254. require.Equal(t, testRQ2, kms.ResourceQuotas["uid-2"])
  255. require.Equal(t, 4, kms.Metadata.ObjectCount) // 2 namespaces and 2 RQs
  256. // Register a third RQ with empty NamespaceUID — expect error, not registered
  257. err := kms.RegisterResourceQuota(&ResourceQuota{UID: "uid-3", Name: "name-3", NamespaceUID: ""})
  258. require.NotNil(t, err)
  259. require.Len(t, kms.GetErrors(), 1)
  260. require.Equal(t, "RegisterResourceQuota: invalid resource quota: NamespaceUID is missing for ResourceQuota 'uid-3'", kms.GetErrors()[0].Message)
  261. require.Len(t, kms.ResourceQuotas, 2) // still 2
  262. require.Equal(t, 4, kms.Metadata.ObjectCount) // unchanged
  263. })
  264. })
  265. }