diff --git a/pkg/analyze/node_resources.go b/pkg/analyze/node_resources.go index f23afa3f9..211671500 100644 --- a/pkg/analyze/node_resources.go +++ b/pkg/analyze/node_resources.go @@ -2,6 +2,8 @@ package analyzer import ( "encoding/json" + "fmt" + "regexp" "strconv" "strings" @@ -17,12 +19,12 @@ func analyzeNodeResources(analyzer *troubleshootv1beta1.NodeResources, getCollec return nil, errors.Wrap(err, "failed to get contents of nodes.json") } - var nodes []corev1.Node + nodes := []corev1.Node{} if err := json.Unmarshal(collected, &nodes); err != nil { return nil, errors.Wrap(err, "failed to unmarshal node list") } - matchingNodeCount := 0 + matchingNodes := []corev1.Node{} for _, node := range nodes { isMatch, err := nodeMatchesFilters(node, analyzer.Filters) @@ -31,7 +33,7 @@ func analyzeNodeResources(analyzer *troubleshootv1beta1.NodeResources, getCollec } if isMatch { - matchingNodeCount++ + matchingNodes = append(matchingNodes, node) } } @@ -46,7 +48,7 @@ func analyzeNodeResources(analyzer *troubleshootv1beta1.NodeResources, getCollec for _, outcome := range analyzer.Outcomes { if outcome.Fail != nil { - isWhenMatch, err := compareNodeResourceConditionalToActual(outcome.Fail.When, matchingNodeCount) + isWhenMatch, err := compareNodeResourceConditionalToActual(outcome.Fail.When, matchingNodes, len(nodes)) if err != nil { return nil, errors.Wrap(err, "failed to parse when") } @@ -59,7 +61,7 @@ func analyzeNodeResources(analyzer *troubleshootv1beta1.NodeResources, getCollec return result, nil } } else if outcome.Warn != nil { - isWhenMatch, err := compareNodeResourceConditionalToActual(outcome.Warn.When, matchingNodeCount) + isWhenMatch, err := compareNodeResourceConditionalToActual(outcome.Warn.When, matchingNodes, len(nodes)) if err != nil { return nil, errors.Wrap(err, "failed to parse when") } @@ -72,7 +74,7 @@ func analyzeNodeResources(analyzer *troubleshootv1beta1.NodeResources, getCollec return result, nil } } else if outcome.Pass != nil { - isWhenMatch, err := compareNodeResourceConditionalToActual(outcome.Pass.When, matchingNodeCount) + isWhenMatch, err := compareNodeResourceConditionalToActual(outcome.Pass.When, matchingNodes, len(nodes)) if err != nil { return nil, errors.Wrap(err, "failed to parse when") } @@ -90,39 +92,361 @@ func analyzeNodeResources(analyzer *troubleshootv1beta1.NodeResources, getCollec return result, nil } -func compareNodeResourceConditionalToActual(conditional string, actual int) (bool, error) { +func compareNodeResourceConditionalToActual(conditional string, matchingNodes []corev1.Node, totalNodeCount int) (bool, error) { if conditional == "" { return true, nil } - parts := strings.Split(strings.TrimSpace(conditional), " ") + parts := strings.Fields(strings.TrimSpace(conditional)) - if len(parts) != 2 { + if len(parts) == 2 { + parts = append([]string{"count"}, parts...) + } + + if len(parts) != 3 { return false, errors.New("unable to parse nodeResources conditional") } - operator := parts[0] - desiredValue, err := strconv.Atoi(parts[1]) - if err != nil { - return false, errors.Wrap(err, "failed to parse nodeResource value") + operator := parts[1] + + var desiredValue interface{} + desiredValue = parts[2] + + parsedDesiredValue, err := strconv.Atoi(parts[2]) + if err == nil { + desiredValue = parsedDesiredValue + } + + reg := regexp.MustCompile(`(?P.*)\((?P.*)\)`) + match := reg.FindStringSubmatch(parts[0]) + + if match == nil { + // We support this as equivalent to the count() function + match = reg.FindStringSubmatch(fmt.Sprintf("count() == %s", parts[0])) + } + + if match == nil || len(match) != 3 { + return false, errors.New("conditional does not match pattern of function(property?)") + } + + function := match[1] + property := match[2] + + var actualValue interface{} + + switch function { + case "count": + actualValue = len(matchingNodes) + break + case "min": + av, err := findMin(matchingNodes, property) + if err != nil { + return false, errors.Wrap(err, "failed to find min") + } + actualValue = av + case "max": + av, err := findMax(matchingNodes, property) + if err != nil { + return false, errors.Wrap(err, "failed to find max") + } + actualValue = av + case "sum": + sum, err := findSum(matchingNodes, property) + if err != nil { + return false, errors.Wrap(err, "failed to find sum") + } + actualValue = sum } switch operator { case "=", "==", "===": - return desiredValue == actual, nil + if _, ok := actualValue.(int); ok { + if _, ok := desiredValue.(int); ok { + return actualValue.(int) == desiredValue.(int), nil + } + } + + if _, ok := desiredValue.(string); ok { + return actualValue.(*resource.Quantity).Cmp(resource.MustParse(desiredValue.(string))) == 0, nil + } + + return actualValue.(*resource.Quantity).Cmp(resource.MustParse(strconv.Itoa(desiredValue.(int)))) == 0, nil + case "<": - return actual < desiredValue, nil - case "<=": - return actual <= desiredValue, nil + if _, ok := actualValue.(int); ok { + if _, ok := desiredValue.(int); ok { + return actualValue.(int) < desiredValue.(int), nil + } + } + if _, ok := desiredValue.(string); ok { + return actualValue.(*resource.Quantity).Cmp(resource.MustParse(desiredValue.(string))) == -1, nil + } + + return actualValue.(*resource.Quantity).Cmp(resource.MustParse(strconv.Itoa(desiredValue.(int)))) == -1, nil + case ">": - return actual > desiredValue, nil + if _, ok := actualValue.(int); ok { + if _, ok := desiredValue.(int); ok { + return actualValue.(int) > desiredValue.(int), nil + } + } + if _, ok := desiredValue.(string); ok { + return actualValue.(*resource.Quantity).Cmp(resource.MustParse(desiredValue.(string))) == 1, nil + } + + return actualValue.(*resource.Quantity).Cmp(resource.MustParse(strconv.Itoa(desiredValue.(int)))) == 1, nil + + case "<=": + if _, ok := actualValue.(int); ok { + if _, ok := desiredValue.(int); ok { + return actualValue.(int) <= desiredValue.(int), nil + } + } + if _, ok := desiredValue.(string); ok { + return actualValue.(*resource.Quantity).Cmp(resource.MustParse(desiredValue.(string))) == 0 || + actualValue.(*resource.Quantity).Cmp(resource.MustParse(desiredValue.(string))) == -1, nil + } + + return actualValue.(*resource.Quantity).Cmp(resource.MustParse(strconv.Itoa(desiredValue.(int)))) == 0 || + actualValue.(*resource.Quantity).Cmp(resource.MustParse(strconv.Itoa(desiredValue.(int)))) == -1, nil + case ">=": - return actual >= desiredValue, nil + if _, ok := actualValue.(int); ok { + if _, ok := desiredValue.(int); ok { + return actualValue.(int) >= desiredValue.(int), nil + } + } + if _, ok := desiredValue.(string); ok { + return actualValue.(*resource.Quantity).Cmp(resource.MustParse(desiredValue.(string))) == 0 || + actualValue.(*resource.Quantity).Cmp(resource.MustParse(desiredValue.(string))) == 1, nil + } + + return actualValue.(*resource.Quantity).Cmp(resource.MustParse(strconv.Itoa(desiredValue.(int)))) == 0 || + actualValue.(*resource.Quantity).Cmp(resource.MustParse(strconv.Itoa(desiredValue.(int)))) == 1, nil } return false, errors.New("unexpected conditional in nodeResources") } +func findSum(nodes []corev1.Node, property string) (*resource.Quantity, error) { + sum := resource.Quantity{} + + for _, node := range nodes { + switch property { + case "cpuCapacity": + if node.Status.Capacity.Cpu() != nil { + sum.Add(*node.Status.Capacity.Cpu()) + } + break + case "cpuAllocatable": + if node.Status.Allocatable.Cpu() != nil { + sum.Add(*node.Status.Allocatable.Cpu()) + } + break + case "memoryCapacity": + if node.Status.Capacity.Memory() != nil { + sum.Add(*node.Status.Capacity.Memory()) + } + break + case "memoryAllocatable": + if node.Status.Allocatable.Memory() != nil { + sum.Add(*node.Status.Allocatable.Cpu()) + } + break + case "podCapacity": + if node.Status.Capacity.Pods() != nil { + sum.Add(*node.Status.Capacity.Pods()) + } + break + case "podAllocatable": + if node.Status.Allocatable.Pods() != nil { + sum.Add(*node.Status.Allocatable.Cpu()) + } + break + case "ephemeralStorageCapacity": + if node.Status.Capacity.StorageEphemeral() != nil { + sum.Add(*node.Status.Capacity.StorageEphemeral()) + } + break + case "ephemeralStorageAllocatable": + if node.Status.Allocatable.StorageEphemeral() != nil { + sum.Add(*node.Status.Allocatable.StorageEphemeral()) + } + break + } + } + + return &sum, nil +} + +func findMin(nodes []corev1.Node, property string) (*resource.Quantity, error) { + var min *resource.Quantity + + for _, node := range nodes { + switch property { + case "cpuCapacity": + if min == nil { + min = node.Status.Capacity.Cpu() + } else { + if node.Status.Capacity.Cpu().Cmp(*min) == -1 { + min = node.Status.Capacity.Cpu() + } + } + break + case "cpuAllocatable": + if min == nil { + min = node.Status.Allocatable.Cpu() + } else { + if node.Status.Allocatable.Cpu().Cmp(*min) == -1 { + min = node.Status.Allocatable.Cpu() + } + } + break + case "memoryCapacity": + if min == nil { + min = node.Status.Capacity.Memory() + } else { + if node.Status.Capacity.Memory().Cmp(*min) == -1 { + min = node.Status.Capacity.Memory() + } + } + break + case "memoryAllocatable": + if min == nil { + min = node.Status.Allocatable.Memory() + } else { + if node.Status.Allocatable.Memory().Cmp(*min) == -1 { + min = node.Status.Allocatable.Memory() + } + } + break + case "podCapacity": + if min == nil { + min = node.Status.Capacity.Pods() + } else { + if node.Status.Capacity.Pods().Cmp(*min) == -1 { + min = node.Status.Capacity.Pods() + } + } + break + case "podAllocatable": + if min == nil { + min = node.Status.Allocatable.Pods() + } else { + if node.Status.Allocatable.Pods().Cmp(*min) == -1 { + min = node.Status.Allocatable.Pods() + } + } + break + case "ephemeralStorageCapacity": + if min == nil { + min = node.Status.Capacity.StorageEphemeral() + } else { + if node.Status.Capacity.StorageEphemeral().Cmp(*min) == -1 { + min = node.Status.Capacity.StorageEphemeral() + } + } + break + case "ephemeralStorageAllocatable": + if min == nil { + min = node.Status.Allocatable.StorageEphemeral() + } else { + if node.Status.Allocatable.StorageEphemeral().Cmp(*min) == -1 { + min = node.Status.Allocatable.StorageEphemeral() + } + } + break + + } + } + + return min, nil +} + +func findMax(nodes []corev1.Node, property string) (*resource.Quantity, error) { + var max *resource.Quantity + + for _, node := range nodes { + switch property { + case "cpuCapacity": + if max == nil { + max = node.Status.Capacity.Cpu() + } else { + if node.Status.Capacity.Cpu().Cmp(*max) == 1 { + max = node.Status.Capacity.Cpu() + } + } + break + case "cpuAllocatable": + if max == nil { + max = node.Status.Allocatable.Cpu() + } else { + if node.Status.Allocatable.Cpu().Cmp(*max) == 1 { + max = node.Status.Allocatable.Cpu() + } + } + break + case "memoryCapacity": + if max == nil { + max = node.Status.Capacity.Memory() + } else { + if node.Status.Capacity.Memory().Cmp(*max) == 1 { + max = node.Status.Capacity.Memory() + } + } + break + case "memoryAllocatable": + if max == nil { + max = node.Status.Allocatable.Memory() + } else { + if node.Status.Allocatable.Memory().Cmp(*max) == 1 { + max = node.Status.Allocatable.Memory() + } + } + break + case "podCapacity": + if max == nil { + max = node.Status.Capacity.Pods() + } else { + if node.Status.Capacity.Pods().Cmp(*max) == 1 { + max = node.Status.Capacity.Pods() + } + } + break + case "podAllocatable": + if max == nil { + max = node.Status.Allocatable.Pods() + } else { + if node.Status.Allocatable.Pods().Cmp(*max) == 1 { + max = node.Status.Allocatable.Pods() + } + } + break + case "ephemeralStorageCapacity": + if max == nil { + max = node.Status.Capacity.StorageEphemeral() + } else { + if node.Status.Capacity.StorageEphemeral().Cmp(*max) == 1 { + max = node.Status.Capacity.StorageEphemeral() + } + } + break + case "ephemeralStorageAllocatable": + if max == nil { + max = node.Status.Allocatable.StorageEphemeral() + } else { + if node.Status.Allocatable.StorageEphemeral().Cmp(*max) == 1 { + max = node.Status.Allocatable.StorageEphemeral() + } + } + break + + } + } + + return max, nil +} + func nodeMatchesFilters(node corev1.Node, filters *troubleshootv1beta1.NodeResourceFilters) (bool, error) { if filters == nil { return true, nil diff --git a/pkg/analyze/node_resources_test.go b/pkg/analyze/node_resources_test.go index 9353c271b..166844e01 100644 --- a/pkg/analyze/node_resources_test.go +++ b/pkg/analyze/node_resources_test.go @@ -8,44 +8,589 @@ import ( "github.com/stretchr/testify/require" corev1 "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" ) func Test_compareNodeResourceConditionalToActual(t *testing.T) { tests := []struct { - name string - conditional string - actual int - expected bool + name string + conditional string + totalNodeCount int + matchingNodes []corev1.Node + expected bool }{ { name: "=", - conditional: "= 5", - actual: 5, - expected: true, + conditional: "= 2", + matchingNodes: []corev1.Node{ + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + }, + }, + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node2", + }, + }, + }, + totalNodeCount: 2, + expected: true, + }, + { + name: "count()", + conditional: "count() == 2", + matchingNodes: []corev1.Node{ + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + }, + }, + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node2", + }, + }, + }, + totalNodeCount: 2, + expected: true, + }, + { + name: "<", + conditional: "< 3", + matchingNodes: []corev1.Node{ + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + }, + }, + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node2", + }, + }, + }, + totalNodeCount: 2, + expected: true, + }, + { + name: "count() <", + conditional: "count() < 3", + matchingNodes: []corev1.Node{ + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + }, + }, + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node2", + }, + }, + }, + totalNodeCount: 2, + expected: true, + }, + { + name: ">", + conditional: "> 2", + matchingNodes: []corev1.Node{ + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + }, + }, + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node2", + }, + }, + }, + totalNodeCount: 2, + expected: false, + }, + { + name: "count() >", + conditional: "count() > 1", + matchingNodes: []corev1.Node{ + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + }, + }, + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node2", + }, + }, + }, + totalNodeCount: 2, + expected: true, + }, + { + name: "count() >= 1 (true)", + conditional: "count() > 1", + matchingNodes: []corev1.Node{ + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + }, + }, + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node2", + }, + }, + }, + totalNodeCount: 2, + expected: true, + }, + { + name: "count() <= 2 (true)", + conditional: "count() <= 2", + matchingNodes: []corev1.Node{ + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + }, + }, + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node2", + }, + }, + }, + totalNodeCount: 2, + expected: true, }, { - name: "<= (pass)", - conditional: "<= 5", - actual: 4, - expected: true, + name: "count() <= 1 (false)", + conditional: "count() <= 1", + matchingNodes: []corev1.Node{ + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + }, + }, + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node2", + }, + }, + }, + totalNodeCount: 2, + expected: false, }, { - name: "<= (fail)", - conditional: "<= 5", - actual: 6, - expected: false, + name: "min(memoryCapacity) <= 4Gi (true)", + conditional: "min(memoryCapacity) <= 4Gi", + matchingNodes: []corev1.Node{ + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + }, + Status: corev1.NodeStatus{ + Capacity: corev1.ResourceList{ + "cpu": resource.MustParse("2"), + "ephemeral-storage": resource.MustParse("20959212Ki"), + "memory": resource.MustParse("3999Ki"), + "pods": resource.MustParse("29"), + }, + Allocatable: corev1.ResourceList{ + "cpu": resource.MustParse("2"), + "ephemeral-storage": resource.MustParse("19316009748"), + "memory": resource.MustParse("16Ki"), + "pods": resource.MustParse("29"), + }, + }, + }, + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node2", + }, + Status: corev1.NodeStatus{ + Capacity: corev1.ResourceList{ + "cpu": resource.MustParse("2"), + "ephemeral-storage": resource.MustParse("20959212Ki"), + "memory": resource.MustParse("7951376Ki"), + "pods": resource.MustParse("29"), + }, + Allocatable: corev1.ResourceList{ + "cpu": resource.MustParse("2"), + "ephemeral-storage": resource.MustParse("19316009748"), + "memory": resource.MustParse("7848976Ki"), + "pods": resource.MustParse("29"), + }, + }, + }, + }, + totalNodeCount: 2, + expected: true, }, { - name: "> (pass)", - conditional: "> 5", - actual: 6, - expected: true, + name: "min(memoryCapacity) <= 4Gi (false)", + conditional: "min(memoryCapacity) <= 4Gi", + matchingNodes: []corev1.Node{ + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + }, + Status: corev1.NodeStatus{ + Capacity: corev1.ResourceList{ + "cpu": resource.MustParse("2"), + "ephemeral-storage": resource.MustParse("20959212Ki"), + "memory": resource.MustParse("17951376Ki"), + "pods": resource.MustParse("29"), + }, + Allocatable: corev1.ResourceList{ + "cpu": resource.MustParse("2"), + "ephemeral-storage": resource.MustParse("19316009748"), + "memory": resource.MustParse("7848976Ki"), + "pods": resource.MustParse("29"), + }, + }, + }, + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node2", + }, + Status: corev1.NodeStatus{ + Capacity: corev1.ResourceList{ + "cpu": resource.MustParse("2"), + "ephemeral-storage": resource.MustParse("20959212Ki"), + "memory": resource.MustParse("7951376Ki"), + "pods": resource.MustParse("29"), + }, + Allocatable: corev1.ResourceList{ + "cpu": resource.MustParse("2"), + "ephemeral-storage": resource.MustParse("19316009748"), + "memory": resource.MustParse("7848976Ki"), + "pods": resource.MustParse("29"), + }, + }, + }, + }, + totalNodeCount: 2, + expected: false, }, { - name: ">=(fail)", - conditional: ">= 5", - actual: 4, - expected: false, + name: "max(cpuCapacity) == 12 (false)", + conditional: "max(cpuCapacity) == 12", + matchingNodes: []corev1.Node{ + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + }, + Status: corev1.NodeStatus{ + Capacity: corev1.ResourceList{ + "cpu": resource.MustParse("2"), + "ephemeral-storage": resource.MustParse("20959212Ki"), + "memory": resource.MustParse("17951376Ki"), + "pods": resource.MustParse("29"), + }, + Allocatable: corev1.ResourceList{ + "cpu": resource.MustParse("2"), + "ephemeral-storage": resource.MustParse("19316009748"), + "memory": resource.MustParse("7848976Ki"), + "pods": resource.MustParse("29"), + }, + }, + }, + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node2", + }, + Status: corev1.NodeStatus{ + Capacity: corev1.ResourceList{ + "cpu": resource.MustParse("2"), + "ephemeral-storage": resource.MustParse("20959212Ki"), + "memory": resource.MustParse("7951376Ki"), + "pods": resource.MustParse("29"), + }, + Allocatable: corev1.ResourceList{ + "cpu": resource.MustParse("2"), + "ephemeral-storage": resource.MustParse("19316009748"), + "memory": resource.MustParse("7848976Ki"), + "pods": resource.MustParse("29"), + }, + }, + }, + }, + totalNodeCount: 2, + expected: false, + }, + { + name: "max(cpuCapacity) == 2 (true)", + conditional: "max(cpuCapacity) == 2", + matchingNodes: []corev1.Node{ + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + }, + Status: corev1.NodeStatus{ + Capacity: corev1.ResourceList{ + "cpu": resource.MustParse("2"), + "ephemeral-storage": resource.MustParse("20959212Ki"), + "memory": resource.MustParse("17951376Ki"), + "pods": resource.MustParse("29"), + }, + Allocatable: corev1.ResourceList{ + "cpu": resource.MustParse("2"), + "ephemeral-storage": resource.MustParse("19316009748"), + "memory": resource.MustParse("7848976Ki"), + "pods": resource.MustParse("29"), + }, + }, + }, + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node2", + }, + Status: corev1.NodeStatus{ + Capacity: corev1.ResourceList{ + "cpu": resource.MustParse("2"), + "ephemeral-storage": resource.MustParse("20959212Ki"), + "memory": resource.MustParse("7951376Ki"), + "pods": resource.MustParse("29"), + }, + Allocatable: corev1.ResourceList{ + "cpu": resource.MustParse("2"), + "ephemeral-storage": resource.MustParse("19316009748"), + "memory": resource.MustParse("7848976Ki"), + "pods": resource.MustParse("29"), + }, + }, + }, + }, + totalNodeCount: 2, + expected: true, + }, + { + name: "sum(cpuCapacity) > 32 (true)", + conditional: "sum(cpuCapacity) > 32", + matchingNodes: []corev1.Node{ + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node1", + }, + Status: corev1.NodeStatus{ + Capacity: corev1.ResourceList{ + "cpu": resource.MustParse("8"), + "ephemeral-storage": resource.MustParse("20959212Ki"), + "memory": resource.MustParse("17951376Ki"), + "pods": resource.MustParse("29"), + }, + Allocatable: corev1.ResourceList{ + "cpu": resource.MustParse("8"), + "ephemeral-storage": resource.MustParse("19316009748"), + "memory": resource.MustParse("7848976Ki"), + "pods": resource.MustParse("29"), + }, + }, + }, + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node2", + }, + Status: corev1.NodeStatus{ + Capacity: corev1.ResourceList{ + "cpu": resource.MustParse("8"), + "ephemeral-storage": resource.MustParse("20959212Ki"), + "memory": resource.MustParse("7951376Ki"), + "pods": resource.MustParse("29"), + }, + Allocatable: corev1.ResourceList{ + "cpu": resource.MustParse("8"), + "ephemeral-storage": resource.MustParse("19316009748"), + "memory": resource.MustParse("7848976Ki"), + "pods": resource.MustParse("29"), + }, + }, + }, + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node3", + }, + Status: corev1.NodeStatus{ + Capacity: corev1.ResourceList{ + "cpu": resource.MustParse("8"), + "ephemeral-storage": resource.MustParse("20959212Ki"), + "memory": resource.MustParse("7951376Ki"), + "pods": resource.MustParse("29"), + }, + Allocatable: corev1.ResourceList{ + "cpu": resource.MustParse("8"), + "ephemeral-storage": resource.MustParse("19316009748"), + "memory": resource.MustParse("7848976Ki"), + "pods": resource.MustParse("29"), + }, + }, + }, + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node4", + }, + Status: corev1.NodeStatus{ + Capacity: corev1.ResourceList{ + "cpu": resource.MustParse("8"), + "ephemeral-storage": resource.MustParse("20959212Ki"), + "memory": resource.MustParse("7951376Ki"), + "pods": resource.MustParse("29"), + }, + Allocatable: corev1.ResourceList{ + "cpu": resource.MustParse("8"), + "ephemeral-storage": resource.MustParse("19316009748"), + "memory": resource.MustParse("7848976Ki"), + "pods": resource.MustParse("29"), + }, + }, + }, + corev1.Node{ + TypeMeta: metav1.TypeMeta{ + APIVersion: "v1", + Kind: "Node", + }, + ObjectMeta: metav1.ObjectMeta{ + Name: "node5", + }, + Status: corev1.NodeStatus{ + Capacity: corev1.ResourceList{ + "cpu": resource.MustParse("8"), + "ephemeral-storage": resource.MustParse("20959212Ki"), + "memory": resource.MustParse("7951376Ki"), + "pods": resource.MustParse("29"), + }, + Allocatable: corev1.ResourceList{ + "cpu": resource.MustParse("8"), + "ephemeral-storage": resource.MustParse("19316009748"), + "memory": resource.MustParse("7848976Ki"), + "pods": resource.MustParse("29"), + }, + }, + }, + }, + totalNodeCount: 2, + expected: true, }, } @@ -53,7 +598,7 @@ func Test_compareNodeResourceConditionalToActual(t *testing.T) { t.Run(test.name, func(t *testing.T) { req := require.New(t) - actual, err := compareNodeResourceConditionalToActual(test.conditional, test.actual) + actual, err := compareNodeResourceConditionalToActual(test.conditional, test.matchingNodes, test.totalNodeCount) req.NoError(err) assert.Equal(t, test.expected, actual)