audit.go 11 KB

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