| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208 |
- package kubecost
- import "github.com/kubecost/cost-model/pkg/log"
- // FilterField is an enum that represents Allocation-specific fields that can be
- // filtered on (namespace, label, etc.)
- type FilterField int
- // If you add a FilterField, MAKE SURE TO UPDATE ALL FILTER IMPLEMENTATIONS! Go
- // does not enforce exhaustive pattern matching on "enum" types.
- const (
- FilterClusterID FilterField = iota
- FilterNode
- FilterNamespace
- FilterControllerKind
- FilterControllerName
- FilterPod
- FilterContainer
- // Filtering based on label aliases (team, department, etc.) should be a
- // responsibility of the query handler. By the time it reaches this
- // structured representation, we shouldn't have to be aware of what is
- // aliased to what.
- FilterLabel
- FilterAnnotation
- )
- // FilterOp is an enum that represents operations that can be performed
- // when filtering (equality, inequality, etc.)
- type FilterOp int
- // If you add a FilterOp, MAKE SURE TO UPDATE ALL FILTER IMPLEMENTATIONS! Go
- // does not enforce exhaustive pattern matching on "enum" types.
- const (
- FilterEquals FilterOp = iota
- FilterNotEquals
- )
- // AllocationFilter is a mini-DSL for filtering Allocation data by different
- // conditions. By specifying a more strict DSL instead of using arbitrary
- // functions we gain the ability to take advantage of storage-level filtering
- // performance improvements like indexes in databases. We can create a
- // transformation from our DSL to the storage's specific query language. We
- // also gain the ability to define a more feature-rich query language for
- // users, supporting more operators and more complex logic, if desired.
- type AllocationFilter interface {
- // Matches is the canonical in-Go function for determing if an Allocation
- // matches a filter.
- Matches(a *Allocation) bool
- }
- // AllocationFilterCondition is the lowest-level type of filter. It represents
- // the a filter operation (equality, inequality, etc.) on a field (namespace,
- // label, etc.).
- type AllocationFilterCondition struct {
- Field FilterField
- Op FilterOp
- // Key is for filters that require key-value pairs, like labels or
- // annotations.
- //
- // A filter of 'label[app]:"foo"' has Key="app" and Value="foo"
- Key string
- // Value is for _all_ filters. A filter of 'namespace:"kubecost"' has
- // Value="kubecost"
- Value string
- }
- // AllocationFilterOr is a set of filters that should be evaluated as a logical
- // OR.
- type AllocationFilterOr struct {
- Filters []AllocationFilter
- }
- // AllocationFilterOr is a set of filters that should be evaluated as a logical
- // AND.
- type AllocationFilterAnd struct {
- Filters []AllocationFilter
- }
- func (filter AllocationFilterCondition) Matches(a *Allocation) bool {
- // TODO: For these nil cases, what about != filters?
- if a == nil {
- return false
- }
- if a.Properties == nil {
- return false
- }
- // TODO Controller PARSING should allow controllerkind:controllername
- // syntax, converted to:
- // (AND (ControllerName Equals) (ControllerKind Equals))
- // The Allocation's value for the field to compare
- var valueToCompare string
- // toCompareMissing will be true if the value to be compared is missing in
- // the Allocation. For example, if we're filtering based on the value of
- // the "app" label, but the Allocation doesn't have an "app" label, this
- // will become true. This lets us deal with != gracefully.
- toCompareMissing := false
- // This switch maps the filter.Field to the field to be compared in
- // a.Properties and sets valueToCompare from the value in a.Properties.
- switch filter.Field {
- case FilterClusterID:
- valueToCompare = a.Properties.Cluster
- case FilterNode:
- valueToCompare = a.Properties.Node
- case FilterNamespace:
- valueToCompare = a.Properties.Namespace
- case FilterControllerKind:
- valueToCompare = a.Properties.ControllerKind
- case FilterControllerName:
- valueToCompare = a.Properties.Controller
- case FilterPod:
- valueToCompare = a.Properties.Pod
- case FilterContainer:
- valueToCompare = a.Properties.Container
- // Comes from GetAnnotation/LabelFilterFunc in KCM
- case FilterLabel:
- val, ok := a.Properties.Labels[filter.Key]
- if !ok {
- toCompareMissing = true
- } else {
- valueToCompare = val
- }
- case FilterAnnotation:
- val, ok := a.Properties.Annotations[filter.Key]
- if !ok {
- toCompareMissing = true
- } else {
- valueToCompare = val
- }
- default:
- log.Errorf("Allocation Filter: Unhandled filter field. This is a filter implementation error and requires immediate patching. Field: %d", filter.Field)
- return false
- }
- switch filter.Op {
- case FilterEquals:
- if toCompareMissing {
- return false
- }
- // namespace:"__unallocated__" should match a.Properties.Namespace = ""
- if valueToCompare == "" {
- return filter.Value == UnallocatedSuffix
- }
- if valueToCompare == filter.Value {
- return true
- }
- case FilterNotEquals:
- if toCompareMissing {
- return true
- }
- // namespace!:"__unallocated__" should match a.Properties.Namespace != ""
- if filter.Value == UnallocatedSuffix {
- return valueToCompare != ""
- }
- if valueToCompare != filter.Value {
- return true
- }
- default:
- log.Errorf("Allocation Filter: Unhandled filter op. This is a filter implementation error and requires immediate patching. Op: %d", filter.Op)
- return false
- }
- return false
- }
- func (and AllocationFilterAnd) Matches(a *Allocation) bool {
- filters := and.Filters
- if len(filters) == 0 {
- // TODO: Should an empty set of ANDs be true or false?
- return true
- }
- for _, filter := range filters {
- if !filter.Matches(a) {
- return false
- }
- }
- return true
- }
- func (or AllocationFilterOr) Matches(a *Allocation) bool {
- filters := or.Filters
- if len(filters) == 0 {
- return true
- }
- for _, filter := range filters {
- if filter.Matches(a) {
- return true
- }
- }
- return false
- }
|