audit.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363
  1. package kubecost
  2. import (
  3. "sync"
  4. "time"
  5. "golang.org/x/exp/slices"
  6. )
  7. // AuditType the types of Audits, each of which should be contained in an AuditSet
  8. type AuditType string
  9. const (
  10. AuditAllocationReconciliation AuditType = "AuditAllocationReconciliation"
  11. AuditAllocationTotalStore AuditType = "AuditAllocationTotalStore"
  12. AuditAllocationAggStore AuditType = "AuditAllocationAggStore"
  13. AuditAssetReconciliation AuditType = "AuditAssetReconciliation"
  14. AuditAssetTotalStore AuditType = "AuditAssetTotalStore"
  15. AuditAssetAggStore AuditType = "AuditAssetAggStore"
  16. AuditClusterEquality AuditType = "AuditClusterEquality"
  17. AuditAll AuditType = ""
  18. AuditInvalidType AuditType = "InvalidType"
  19. )
  20. // ToAuditType converts a string to an Audit type
  21. func ToAuditType(check string) AuditType {
  22. switch check {
  23. case string(AuditAllocationReconciliation):
  24. return AuditAllocationReconciliation
  25. case string(AuditAllocationTotalStore):
  26. return AuditAllocationTotalStore
  27. case string(AuditAllocationAggStore):
  28. return AuditAllocationAggStore
  29. case string(AuditAssetReconciliation):
  30. return AuditAssetReconciliation
  31. case string(AuditAssetTotalStore):
  32. return AuditAssetTotalStore
  33. case string(AuditAssetAggStore):
  34. return AuditAssetAggStore
  35. case string(AuditClusterEquality):
  36. return AuditClusterEquality
  37. case string(AuditAll):
  38. return AuditAll
  39. default:
  40. return AuditInvalidType
  41. }
  42. }
  43. // AuditStatus are possible outcomes of an audit
  44. type AuditStatus string
  45. const (
  46. FailedStatus AuditStatus = "Failed"
  47. WarningStatus = "Warning"
  48. PassedStatus = "Passed"
  49. )
  50. // AuditMissingValue records when a value that should be present in a store or in the audit generated results are missing
  51. type AuditMissingValue struct {
  52. Description string
  53. Key string
  54. }
  55. // AuditFloatResult structure for holding the results of a failed audit on a float value, Expected should be the value
  56. // calculated by the Audit func while Actual is what is contained in the relevant store.
  57. type AuditFloatResult struct {
  58. Expected float64
  59. Actual float64
  60. }
  61. // Clone returns a deep copy of the caller
  62. func (afr *AuditFloatResult) Clone() *AuditFloatResult {
  63. return &AuditFloatResult{
  64. Expected: afr.Expected,
  65. Actual: afr.Actual,
  66. }
  67. }
  68. // AllocationReconciliationAudit records the differences of between compute resources (cpu, ram, gpu) costs between
  69. // allocations by nodes and node assets keyed on node name and compute resource
  70. type AllocationReconciliationAudit struct {
  71. Status AuditStatus
  72. Description string
  73. LastRun time.Time
  74. Resources map[string]map[string]*AuditFloatResult
  75. MissingValues []*AuditMissingValue
  76. }
  77. // Clone returns a deep copy of the caller
  78. func (ara *AllocationReconciliationAudit) Clone() *AllocationReconciliationAudit {
  79. if ara == nil {
  80. return nil
  81. }
  82. resources := make(map[string]map[string]*AuditFloatResult, len(ara.Resources))
  83. for node, resourceMap := range ara.Resources {
  84. copyResourceMap := make(map[string]*AuditFloatResult, len(resourceMap))
  85. for resourceName, val := range resourceMap {
  86. copyResourceMap[resourceName] = val.Clone()
  87. }
  88. resources[node] = copyResourceMap
  89. }
  90. return &AllocationReconciliationAudit{
  91. Status: ara.Status,
  92. Description: ara.Description,
  93. LastRun: ara.LastRun,
  94. Resources: resources,
  95. MissingValues: slices.Clone(ara.MissingValues),
  96. }
  97. }
  98. // TotalAudit records the differences between a total store and the totaled results of the store that it is based on
  99. // keyed by cluster and node names
  100. type TotalAudit struct {
  101. Status AuditStatus
  102. Description string
  103. LastRun time.Time
  104. TotalByNode map[string]*AuditFloatResult
  105. TotalByCluster map[string]*AuditFloatResult
  106. MissingValues []*AuditMissingValue
  107. }
  108. // Clone returns a deep copy of the caller
  109. func (ta *TotalAudit) Clone() *TotalAudit {
  110. if ta == nil {
  111. return nil
  112. }
  113. tbn := make(map[string]*AuditFloatResult, len(ta.TotalByNode))
  114. for k, v := range ta.TotalByNode {
  115. tbn[k] = v
  116. }
  117. tbc := make(map[string]*AuditFloatResult, len(ta.TotalByNode))
  118. for k, v := range ta.TotalByCluster {
  119. tbc[k] = v
  120. }
  121. return &TotalAudit{
  122. Status: ta.Status,
  123. Description: ta.Description,
  124. LastRun: ta.LastRun,
  125. TotalByNode: tbn,
  126. TotalByCluster: tbc,
  127. MissingValues: slices.Clone(ta.MissingValues),
  128. }
  129. }
  130. // AggAudit contains the results of an Audit on an AggStore keyed on aggregation prop and Allocation key
  131. type AggAudit struct {
  132. Status AuditStatus
  133. Description string
  134. LastRun time.Time
  135. Results map[string]map[string]*AuditFloatResult
  136. MissingValues []*AuditMissingValue
  137. }
  138. // Clone returns a deep copy of the caller
  139. func (aa *AggAudit) Clone() *AggAudit {
  140. if aa == nil {
  141. return nil
  142. }
  143. res := make(map[string]map[string]*AuditFloatResult, len(aa.Results))
  144. for aggType, aggResults := range aa.Results {
  145. copyAggResult := make(map[string]*AuditFloatResult, len(aggResults))
  146. for aggName, auditFloatResult := range aggResults {
  147. copyAggResult[aggName] = auditFloatResult
  148. }
  149. res[aggType] = copyAggResult
  150. }
  151. return &AggAudit{
  152. Status: aa.Status,
  153. Description: aa.Description,
  154. LastRun: aa.LastRun,
  155. Results: res,
  156. MissingValues: slices.Clone(aa.MissingValues),
  157. }
  158. }
  159. // AssetReconciliationAudit records differences in assets and the Cloud
  160. type AssetReconciliationAudit struct {
  161. Status AuditStatus
  162. Description string
  163. LastRun time.Time
  164. Results map[string]map[string]*AuditFloatResult
  165. MissingValues []*AuditMissingValue
  166. }
  167. // Clone returns a deep copy of the caller
  168. func (ara *AssetReconciliationAudit) Clone() *AssetReconciliationAudit {
  169. res := make(map[string]map[string]*AuditFloatResult, len(ara.Results))
  170. for aggType, aggResults := range ara.Results {
  171. copyAggResult := make(map[string]*AuditFloatResult, len(aggResults))
  172. for aggName, auditFloatResult := range aggResults {
  173. copyAggResult[aggName] = auditFloatResult
  174. }
  175. res[aggType] = copyAggResult
  176. }
  177. return &AssetReconciliationAudit{
  178. Status: ara.Status,
  179. Description: ara.Description,
  180. LastRun: ara.LastRun,
  181. Results: res,
  182. MissingValues: slices.Clone(ara.MissingValues),
  183. }
  184. }
  185. // EqualityAudit records the difference in cost between Allocations and Assets aggregated by cluster and keyed on cluster
  186. type EqualityAudit struct {
  187. Status AuditStatus
  188. Description string
  189. LastRun time.Time
  190. Clusters map[string]*AuditFloatResult
  191. MissingValues []*AuditMissingValue
  192. }
  193. // Clone returns a deep copy of the caller
  194. func (ea *EqualityAudit) Clone() *EqualityAudit {
  195. if ea == nil {
  196. return nil
  197. }
  198. clusters := make(map[string]*AuditFloatResult, len(ea.Clusters))
  199. for k, v := range ea.Clusters {
  200. clusters[k] = v
  201. }
  202. return &EqualityAudit{
  203. Status: ea.Status,
  204. Description: ea.Description,
  205. LastRun: ea.LastRun,
  206. Clusters: clusters,
  207. MissingValues: slices.Clone(ea.MissingValues),
  208. }
  209. }
  210. // AuditCoverage tracks coverage of each audit type
  211. type AuditCoverage struct {
  212. AllocationReconciliation Window `json:"allocationReconciliation"`
  213. AllocationAgg Window `json:"allocationAgg"`
  214. AllocationTotal Window `json:"allocationTotal"`
  215. AssetTotal Window `json:"assetTotal"`
  216. AssetReconciliation Window `json:"assetReconciliation"`
  217. ClusterEquality Window `json:"clusterEquality"`
  218. }
  219. // NewAuditCoverage create default AuditCoverage
  220. func NewAuditCoverage() *AuditCoverage {
  221. return &AuditCoverage{}
  222. }
  223. // Update expands the coverage of each Window in the coverage that the given AuditSet's Window if the corresponding Audit is not nil
  224. // Note: This means of determining coverage can lead to holes in the given window
  225. func (ac *AuditCoverage) Update(as *AuditSet) {
  226. if as != nil && as.AllocationReconciliation != nil {
  227. ac.AllocationReconciliation.Expand(as.Window)
  228. ac.AllocationAgg.Expand(as.Window)
  229. ac.AllocationTotal.Expand(as.Window)
  230. ac.AssetTotal.Expand(as.Window)
  231. ac.AssetReconciliation.Expand(as.Window)
  232. ac.ClusterEquality.Expand(as.Window)
  233. }
  234. }
  235. // AuditSet is a ETLSet which contains all kind of Audits for a given Window
  236. type AuditSet struct {
  237. sync.RWMutex
  238. AllocationReconciliation *AllocationReconciliationAudit `json:"allocationReconciliation"`
  239. AllocationAgg *AggAudit `json:"allocationAgg"`
  240. AllocationTotal *TotalAudit `json:"allocationTotal"`
  241. AssetTotal *TotalAudit `json:"assetTotal"`
  242. AssetReconciliation *AssetReconciliationAudit `json:"assetReconciliation"`
  243. ClusterEquality *EqualityAudit `json:"clusterEquality"`
  244. Window Window `json:"window"`
  245. }
  246. // NewAuditSet creates an empty AuditSet with the given window
  247. func NewAuditSet(start, end time.Time) *AuditSet {
  248. return &AuditSet{
  249. Window: NewWindow(&start, &end),
  250. }
  251. }
  252. // UpdateAuditSet overwrites any audit fields in the caller with those in the given AuditSet which are not nil
  253. func (as *AuditSet) UpdateAuditSet(that *AuditSet) *AuditSet {
  254. if as == nil {
  255. return that
  256. }
  257. if that.AllocationReconciliation != nil {
  258. as.AllocationReconciliation = that.AllocationReconciliation
  259. }
  260. if that.AllocationAgg != nil {
  261. as.AllocationAgg = that.AllocationAgg
  262. }
  263. if that.AllocationTotal != nil {
  264. as.AllocationTotal = that.AllocationTotal
  265. }
  266. if that.AssetTotal != nil {
  267. as.AssetTotal = that.AssetTotal
  268. }
  269. if that.AssetReconciliation != nil {
  270. as.AssetReconciliation = that.AssetReconciliation
  271. }
  272. if that.ClusterEquality != nil {
  273. as.ClusterEquality = that.ClusterEquality
  274. }
  275. return as
  276. }
  277. // ConstructSet fulfills the ETLSet interface to provide an empty version of itself so that it can be initialized in its
  278. // generic form.
  279. func (as *AuditSet) ConstructSet() ETLSet {
  280. return &AuditSet{}
  281. }
  282. // IsEmpty returns true if any of the audits are non-nil
  283. func (as *AuditSet) IsEmpty() bool {
  284. return as == nil || (as.AllocationReconciliation == nil &&
  285. as.AllocationAgg == nil &&
  286. as.AllocationTotal == nil &&
  287. as.AssetTotal == nil &&
  288. as.AssetReconciliation == nil &&
  289. as.ClusterEquality == nil)
  290. }
  291. // GetWindow returns AuditSet Window
  292. func (as *AuditSet) GetWindow() Window {
  293. return as.Window
  294. }
  295. // Clone returns a deep copy of the caller
  296. func (as *AuditSet) Clone() *AuditSet {
  297. if as == nil {
  298. return nil
  299. }
  300. as.RLock()
  301. defer as.RUnlock()
  302. return &AuditSet{
  303. AllocationReconciliation: as.AllocationReconciliation.Clone(),
  304. AllocationAgg: as.AllocationAgg.Clone(),
  305. AllocationTotal: as.AllocationTotal.Clone(),
  306. AssetTotal: as.AssetTotal.Clone(),
  307. AssetReconciliation: as.AssetReconciliation.Clone(),
  308. ClusterEquality: as.ClusterEquality.Clone(),
  309. Window: as.Window.Clone(),
  310. }
  311. }
  312. // CloneSet returns a deep copy of the caller and returns set
  313. func (as *AuditSet) CloneSet() ETLSet {
  314. return as.Clone()
  315. }
  316. // AuditSetRange SetRange of AuditSets
  317. type AuditSetRange struct {
  318. SetRange[*AuditSet]
  319. }