Node resource analyzer

This commit is contained in:
Marc Campbell
2020-01-29 23:16:40 +00:00
parent 299f3e0da7
commit 879c3a67d7
6 changed files with 386 additions and 653 deletions

View File

@@ -0,0 +1,38 @@
apiVersion: troubleshoot.replicated.com/v1beta1
kind: Preflight
metadata:
name: sample
spec:
analyzers:
- nodeResources:
checkName: Must have at least 3 nodes in the cluster
outcomes:
- fail:
when: "< 3"
message: This application requires at least 3 nodes
- warn:
when: "< 5"
message: This application recommends at last 5 nodes.
- pass:
message: This cluster has enough nodes.
- nodeResources:
checkName: Must have 3 nodes with at least 6 cores
filters:
cpuCapacity: "6"
outcomes:
- fail:
when: "< 3"
message: This application requires at least 3 nodes with 6 cores each
- pass:
message: This cluster has enough nodes with enough codes
- nodeResources:
checkName: Must have 1 node with 16 GB (available) memory and 5 cores (on a single node)
filters:
allocatableMemory: 16Gi
cpuCapacity: "5"
outcomes:
- fail:
when: "< 1"
message: This application requires at least 1 node with 16GB available memory
- pass:
message: This cluster has a node with enough memory.

View File

@@ -1,618 +1,5 @@
package analyzer
var collectedNodes = `
[
{
"metadata": {
"name": "ip-192-168-28-59.us-east-2.compute.internal",
"selfLink": "/api/v1/nodes/ip-192-168-28-59.us-east-2.compute.internal",
"uid": "6d679c35-1dfe-11ea-89c7-0ab299bbd38c",
"resourceVersion": "7417849",
"creationTimestamp": "2019-12-13T23:15:18Z",
"labels": {
"alpha.eksctl.io/cluster-name": "schemahero-demo",
"alpha.eksctl.io/instance-id": "i-026467ed98dc19788",
"alpha.eksctl.io/nodegroup-name": "ng-f2f5f9a5",
"beta.kubernetes.io/arch": "amd64",
"beta.kubernetes.io/instance-type": "m5.large",
"beta.kubernetes.io/os": "linux",
"failure-domain.beta.kubernetes.io/region": "us-east-2",
"failure-domain.beta.kubernetes.io/zone": "us-east-2a",
"kubernetes.io/arch": "amd64",
"kubernetes.io/hostname": "ip-192-168-28-59.us-east-2.compute.internal",
"kubernetes.io/os": "linux"
},
"annotations": {
"node.alpha.kubernetes.io/ttl": "0",
"volumes.kubernetes.io/controller-managed-attach-detach": "true"
}
},
"spec": {
"providerID": "aws:///us-east-2a/i-026467ed98dc19788"
},
"status": {
"capacity": {
"attachable-volumes-aws-ebs": "25",
"cpu": "2",
"ephemeral-storage": "20959212Ki",
"hugepages-1Gi": "0",
"hugepages-2Mi": "0",
"memory": "7951376Ki",
"pods": "29"
},
"allocatable": {
"attachable-volumes-aws-ebs": "25",
"cpu": "2",
"ephemeral-storage": "19316009748",
"hugepages-1Gi": "0",
"hugepages-2Mi": "0",
"memory": "7848976Ki",
"pods": "29"
},
"conditions": [
{
"type": "MemoryPressure",
"status": "False",
"lastHeartbeatTime": "2020-01-29T18:50:15Z",
"lastTransitionTime": "2020-01-22T02:01:14Z",
"reason": "KubeletHasSufficientMemory",
"message": "kubelet has sufficient memory available"
},
{
"type": "DiskPressure",
"status": "False",
"lastHeartbeatTime": "2020-01-29T18:50:15Z",
"lastTransitionTime": "2020-01-24T01:31:53Z",
"reason": "KubeletHasNoDiskPressure",
"message": "kubelet has no disk pressure"
},
{
"type": "PIDPressure",
"status": "False",
"lastHeartbeatTime": "2020-01-29T18:50:15Z",
"lastTransitionTime": "2020-01-22T02:01:14Z",
"reason": "KubeletHasSufficientPID",
"message": "kubelet has sufficient PID available"
},
{
"type": "Ready",
"status": "True",
"lastHeartbeatTime": "2020-01-29T18:50:15Z",
"lastTransitionTime": "2020-01-22T02:01:14Z",
"reason": "KubeletReady",
"message": "kubelet is posting ready status"
}
],
"addresses": [
{
"type": "InternalIP",
"address": "***HIDDEN***"
},
{
"type": "ExternalIP",
"address": "***HIDDEN***"
},
{
"type": "Hostname",
"address": "ip-192-168-28-59.us-east-2.compute.internal"
},
{
"type": "InternalDNS",
"address": "ip-192-168-28-59.us-east-2.compute.internal"
},
{
"type": "ExternalDNS",
"address": "ec2-3-133-126-65.us-east-2.compute.amazonaws.com"
}
],
"daemonEndpoints": {
"kubeletEndpoint": {
"Port": 10250
}
},
"nodeInfo": {
"machineID": "ec2d1877f782e9caa8d0f7cb5c6154b8",
"systemUUID": "EC2D1877-F782-E9CA-A8D0-F7CB5C6154B8",
"bootID": "8e91eddd-e115-4efe-a4e1-a32affdbab61",
"kernelVersion": "4.14.146-119.123.amzn2.x86_64",
"osImage": "Amazon Linux 2",
"containerRuntimeVersion": "docker://18.6.1",
"kubeletVersion": "v1.14.7-eks-1861c5",
"kubeProxyVersion": "v1.14.7-eks-1861c5",
"operatingSystem": "linux",
"architecture": "amd64"
},
"images": [
{
"names": [
"kotsadm/kotsadm-api@sha256:257efb64c42c4e83f51618bc94b2898687292b7a1763c8c1165a0b8fb52b2c47",
"kotsadm/kotsadm-api:v1.11.4"
],
"sizeBytes": 1025604685
},
{
"names": [
"kotsadm/kotsadm-api@sha256:bbdaf7b3abf9864953e3a25fb5d58746ee8b3056d4dbf1c9c477c4c08d7b3e6f",
"kotsadm/kotsadm-api:v1.11.1"
],
"sizeBytes": 1025603901
},
{
"names": [
"kotsadm/kotsadm-api@sha256:0781a0d3ab73147db616a5359e7385dad5c1f942eb4dbf0032d965fc56342600"
],
"sizeBytes": 1025603901
},
{
"names": [
"kotsadm/kotsadm-api@sha256:29084f5f9896baaf947caf96b56e05af1d28a662dd437f222063df7d835f90e4"
],
"sizeBytes": 1025603901
},
{
"names": [
"kotsadm/kotsadm-api@sha256:000572c198f73af001713b10bd4869710c99313dde81d5589445a069271c0338"
],
"sizeBytes": 1025603901
},
{
"names": [
"sentry@sha256:5a9fb82278c8ee4deb0fc9cb98dfcb6e1e0e184f7267a6a2c9074e0c687a0cd2",
"sentry:9.1.2"
],
"sizeBytes": 868746022
},
{
"names": [
"602401143452.dkr.ecr.us-east-2.amazonaws.com/amazon-k8s-cni@sha256:c071dfc45cd957fc6ab2db769ae6374b1f59a08db90b0ff0b9166b8531497a35",
"602401143452.dkr.ecr.us-east-2.amazonaws.com/amazon-k8s-cni:v1.5.3"
],
"sizeBytes": 290731139
},
{
"names": [
"bitnami/postgresql@sha256:7b8f251a3ffdc3a5392b6b7bd1ac863d34f7cb1e9cc0ec3b2f92a45f9570eae5",
"bitnami/postgresql:11.5.0-debian-9-r60"
],
"sizeBytes": 165095931
},
{
"names": [
"kotsadm/kotsadm-migrations@sha256:1c19f3d507876e62889c0f592b20e15324effc579f2cd0591039fa0cdbac633d",
"kotsadm/kotsadm-migrations:v1.11.1"
],
"sizeBytes": 156079510
},
{
"names": [
"kotsadm/kotsadm-migrations@sha256:5ae3fd834b72a37d92c801cc5b281b2339c17865be0c5298e4fdff62a9c4dde4"
],
"sizeBytes": 156079510
},
{
"names": [
"kotsadm/kotsadm-migrations@sha256:c1a7dce8fc27c2fedbf567370d24e0a3759c1840fa16a46a8569d6c4a3e09152",
"kotsadm/kotsadm-migrations:v1.11.4"
],
"sizeBytes": 156079510
},
{
"names": [
"bitnami/redis@sha256:505188ab03eae7d63902fed9e2ab1bcfc2bf98a0244ba69f488cc6018eb6f330",
"bitnami/redis:5.0.5-debian-9-r141"
],
"sizeBytes": 96707700
},
{
"names": [
"602401143452.dkr.ecr.us-east-2.amazonaws.com/eks/kube-proxy@sha256:d3a6122f63202665aa50f3c08644ef504dbe56c76a1e0ab05f8e296328f3a6b4",
"602401143452.dkr.ecr.us-east-2.amazonaws.com/eks/kube-proxy:v1.14.6"
],
"sizeBytes": 82044796
},
{
"names": [
"bitnami/minideb@sha256:7f79535202f3610cf637b4ce9d92d7e28600ce9d7e05284f7c861c6ef35dcd1f",
"bitnami/minideb:stretch"
],
"sizeBytes": 53743451
},
{
"names": [
"ttl.sh/sdfsdfsdf/minideb@sha256:b02b0c29f37f90a013e0a7a38f47667a219a5785b55aba6af0bbb54c5ad691b8",
"ttl.sh/sdfsdfsdf/minideb:stretch"
],
"sizeBytes": 53743418
},
{
"names": [
"kotsadm/minio@sha256:a68fb7b34d58c8167d11a93ebe887ab44ccb9447593e9ce7c36ac940c78221d4",
"kotsadm/minio:v1.11.1"
],
"sizeBytes": 51885319
},
{
"names": [
"602401143452.dkr.ecr.us-east-2.amazonaws.com/eks/coredns@sha256:c85954b828a5627b9f3c4540893ab9d8a4be5f8da7513882ad122e08f5c2e60a",
"602401143452.dkr.ecr.us-east-2.amazonaws.com/eks/coredns:v1.3.1"
],
"sizeBytes": 35174083
},
{
"names": [
"602401143452.dkr.ecr.us-east-2.amazonaws.com/eks/pause-amd64@sha256:bea77c323c47f7b573355516acf927691182d1333333d1f41b7544012fab7adf",
"602401143452.dkr.ecr.us-east-2.amazonaws.com/eks/pause-amd64:3.1"
],
"sizeBytes": 742472
}
]
}
},
{
"metadata": {
"name": "ip-192-168-71-129.us-east-2.compute.internal",
"selfLink": "/api/v1/nodes/ip-192-168-71-129.us-east-2.compute.internal",
"uid": "6c8f3260-1dfe-11ea-89c7-0ab299bbd38c",
"resourceVersion": "7417782",
"creationTimestamp": "2019-12-13T23:15:16Z",
"labels": {
"alpha.eksctl.io/cluster-name": "schemahero-demo",
"alpha.eksctl.io/instance-id": "i-0b7ad3f63b3a123b8",
"alpha.eksctl.io/nodegroup-name": "ng-f2f5f9a5",
"beta.kubernetes.io/arch": "amd64",
"beta.kubernetes.io/instance-type": "m5.large",
"beta.kubernetes.io/os": "linux",
"failure-domain.beta.kubernetes.io/region": "us-east-2",
"failure-domain.beta.kubernetes.io/zone": "us-east-2c",
"kubernetes.io/arch": "amd64",
"kubernetes.io/hostname": "ip-192-168-71-129.us-east-2.compute.internal",
"kubernetes.io/os": "linux"
},
"annotations": {
"node.alpha.kubernetes.io/ttl": "0",
"volumes.kubernetes.io/controller-managed-attach-detach": "true"
}
},
"spec": {
"providerID": "aws:///us-east-2c/i-0b7ad3f63b3a123b8"
},
"status": {
"capacity": {
"attachable-volumes-aws-ebs": "25",
"cpu": "2",
"ephemeral-storage": "20959212Ki",
"hugepages-1Gi": "0",
"hugepages-2Mi": "0",
"memory": "7865360Ki",
"pods": "29"
},
"allocatable": {
"attachable-volumes-aws-ebs": "25",
"cpu": "2",
"ephemeral-storage": "19316009748",
"hugepages-1Gi": "0",
"hugepages-2Mi": "0",
"memory": "7762960Ki",
"pods": "29"
},
"conditions": [
{
"type": "MemoryPressure",
"status": "False",
"lastHeartbeatTime": "2020-01-29T18:49:36Z",
"lastTransitionTime": "2019-12-13T23:15:16Z",
"reason": "KubeletHasSufficientMemory",
"message": "kubelet has sufficient memory available"
},
{
"type": "DiskPressure",
"status": "False",
"lastHeartbeatTime": "2020-01-29T18:49:36Z",
"lastTransitionTime": "2020-01-22T14:30:11Z",
"reason": "KubeletHasNoDiskPressure",
"message": "kubelet has no disk pressure"
},
{
"type": "PIDPressure",
"status": "False",
"lastHeartbeatTime": "2020-01-29T18:49:36Z",
"lastTransitionTime": "2019-12-13T23:15:16Z",
"reason": "KubeletHasSufficientPID",
"message": "kubelet has sufficient PID available"
},
{
"type": "Ready",
"status": "True",
"lastHeartbeatTime": "2020-01-29T18:49:36Z",
"lastTransitionTime": "2019-12-13T23:16:06Z",
"reason": "KubeletReady",
"message": "kubelet is posting ready status"
}
],
"addresses": [
{
"type": "InternalIP",
"address": "***HIDDEN***"
},
{
"type": "ExternalIP",
"address": "***HIDDEN***"
},
{
"type": "Hostname",
"address": "ip-192-168-71-129.us-east-2.compute.internal"
},
{
"type": "InternalDNS",
"address": "ip-192-168-71-129.us-east-2.compute.internal"
},
{
"type": "ExternalDNS",
"address": "ec2-3-18-214-18.us-east-2.compute.amazonaws.com"
}
],
"daemonEndpoints": {
"kubeletEndpoint": {
"Port": 10250
}
},
"nodeInfo": {
"machineID": "ec2502eb42ac572c0fc598fd2854029d",
"systemUUID": "EC2502EB-42AC-572C-0FC5-98FD2854029D",
"bootID": "d6ce6c46-98af-44c0-8f0a-7c6f0affba35",
"kernelVersion": "4.14.146-119.123.amzn2.x86_64",
"osImage": "Amazon Linux 2",
"containerRuntimeVersion": "docker://18.6.1",
"kubeletVersion": "v1.14.7-eks-1861c5",
"kubeProxyVersion": "v1.14.7-eks-1861c5",
"operatingSystem": "linux",
"architecture": "amd64"
},
"images": [
{
"names": [
"kotsadm/kotsadm-api@sha256:9bc79559156f04a1e086b865db962c7e3ca32575f654c0f09d5fdc4acf118d8a",
"kotsadm/kotsadm-api:alpha"
],
"sizeBytes": 1025599949
},
{
"names": [
"codescope/mjml-tcpserver@sha256:5a3f0c82a483f10255a06be5c74a34686f844b37b818a8b07c137f9c1bb1e8d7",
"codescope/mjml-tcpserver:0.8.0"
],
"sizeBytes": 920297781
},
{
"names": [
"sentry@sha256:5a9fb82278c8ee4deb0fc9cb98dfcb6e1e0e184f7267a6a2c9074e0c687a0cd2",
"sentry:9.1.2"
],
"sizeBytes": 868746022
},
{
"names": [
"kotsadm/kotsadm-operator@sha256:849ba88648a4d85e8eff5c845477af593b139d0a695a1ad5c44ba0a9eec80b54"
],
"sizeBytes": 478334600
},
{
"names": [
"kotsadm/kotsadm-operator@sha256:e8ebbbad7cdc44f9ddf8237ec38e75a9211d83e2ebec7dffd9c8c5f40f888cd3",
"kotsadm/kotsadm-operator:v1.11.1"
],
"sizeBytes": 478334600
},
{
"names": [
"kotsadm/kotsadm-operator@sha256:207b23336e6ac227c5bba39990107e42eaffe9f237e94e49abf9520e70826aa8",
"kotsadm/kotsadm-operator:v1.11.0"
],
"sizeBytes": 478334600
},
{
"names": [
"kotsadm/kotsadm-operator@sha256:9790dd5bc5450520db67b272b4bd38da595ee4d99af17307ae01ecf05b2844db"
],
"sizeBytes": 478334600
},
{
"names": [
"kotsadm/kotsadm-operator@sha256:61fdc4c1b80106717ea364de6a1dff31f0634215d5408b4bca86e1cfa84f37eb"
],
"sizeBytes": 478334600
},
{
"names": [
"kotsadm/kotsadm-operator@sha256:66cefcfd42ebab1ac441a9bf0ff755584d82631da853e40f84e5059ec928a985",
"kotsadm/kotsadm-operator:v1.11.4"
],
"sizeBytes": 478334600
},
{
"names": [
"kotsadm/kotsadm-operator@sha256:a19bf2afcbc318c169db4dbd6c6f8cdca02e6b0ee9922555441025f67c9e21f4",
"kotsadm/kotsadm-operator:v1.10.3"
],
"sizeBytes": 478317692
},
{
"names": [
"codescope/core@sha256:c8914b21b47d8394969e71054f1b964466fc1fe69cd70a778c142b947d8d08bf",
"codescope/core:1.5.0"
],
"sizeBytes": 405019853
},
{
"names": [
"kotsadm/kotsadm@sha256:6363777cbc9e57939ee33032dcfdd4619cee1b73428d031c8966948ec8172499",
"kotsadm/kotsadm:v1.11.4"
],
"sizeBytes": 300000312
},
{
"names": [
"kotsadm/kotsadm@sha256:dcff0ff224cb18e19026928a5d7a27ccfa9950032900b0c2a5fa5de8d6456ef2",
"kotsadm/kotsadm:v1.11.1"
],
"sizeBytes": 299996710
},
{
"names": [
"kotsadm/kotsadm@sha256:3fdeedc495df96c5831a3a198190c1b5b2708f5a438fea940f4798085e0a70c1"
],
"sizeBytes": 299996710
},
{
"names": [
"kotsadm/kotsadm@sha256:271ba33be8a1d0d51fe387e7df8709809fcaa00a5501e7f107253afb5628999a"
],
"sizeBytes": 299996560
},
{
"names": [
"602401143452.dkr.ecr.us-east-2.amazonaws.com/amazon-k8s-cni@sha256:c071dfc45cd957fc6ab2db769ae6374b1f59a08db90b0ff0b9166b8531497a35",
"602401143452.dkr.ecr.us-east-2.amazonaws.com/amazon-k8s-cni:v1.5.3"
],
"sizeBytes": 290731139
},
{
"names": [
"kotsadm/kotsadm@sha256:08de237443b718d8b0ee260701dddc4b8c9b67fee5b5929b051a20312bf9aa39"
],
"sizeBytes": 255742861
},
{
"names": [
"postgres@sha256:cc8fb6b149b387fed332b5bebd144f810df544e2df514383f82f6e61698b2aea",
"postgres:10.7"
],
"sizeBytes": 229651900
},
{
"names": [
"bitnami/postgresql@sha256:7b8f251a3ffdc3a5392b6b7bd1ac863d34f7cb1e9cc0ec3b2f92a45f9570eae5",
"bitnami/postgresql:11.5.0-debian-9-r60"
],
"sizeBytes": 165095931
},
{
"names": [
"kotsadm/kotsadm-migrations@sha256:9ebee83999219df4226d7f85b1da71420c3ebd3011cb79012a15c0fb805b9b3e"
],
"sizeBytes": 156079510
},
{
"names": [
"kotsadm/kotsadm-migrations@sha256:1c19f3d507876e62889c0f592b20e15324effc579f2cd0591039fa0cdbac633d"
],
"sizeBytes": 156079510
},
{
"names": [
"kotsadm/kotsadm-migrations@sha256:5ae3fd834b72a37d92c801cc5b281b2339c17865be0c5298e4fdff62a9c4dde4",
"kotsadm/kotsadm-migrations:v1.11.1"
],
"sizeBytes": 156079510
},
{
"names": [
"kotsadm/kotsadm-migrations@sha256:1f467665d4e6714b19d8b82a9c859e958537c91452588c67a09db6f66b751af3",
"kotsadm/kotsadm-migrations:alpha"
],
"sizeBytes": 156079510
},
{
"names": [
"bitnami/redis@sha256:505188ab03eae7d63902fed9e2ab1bcfc2bf98a0244ba69f488cc6018eb6f330",
"bitnami/redis:5.0.5-debian-9-r141"
],
"sizeBytes": 96707700
},
{
"names": [
"602401143452.dkr.ecr.us-east-2.amazonaws.com/eks/kube-proxy@sha256:d3a6122f63202665aa50f3c08644ef504dbe56c76a1e0ab05f8e296328f3a6b4",
"602401143452.dkr.ecr.us-east-2.amazonaws.com/eks/kube-proxy:v1.14.6"
],
"sizeBytes": 82044796
},
{
"names": [
"bitnami/minideb@sha256:7f79535202f3610cf637b4ce9d92d7e28600ce9d7e05284f7c861c6ef35dcd1f",
"bitnami/minideb:stretch"
],
"sizeBytes": 53743451
},
{
"names": [
"kotsadm/minio@sha256:3b1aadcd350f2c5b003b1e736bc89b23a636c2cf4eb3bbc7e459452a504e18ef",
"kotsadm/minio:alpha"
],
"sizeBytes": 51885319
},
{
"names": [
"kotsadm/minio@sha256:a68fb7b34d58c8167d11a93ebe887ab44ccb9447593e9ce7c36ac940c78221d4",
"kotsadm/minio:v1.11.1"
],
"sizeBytes": 51885319
},
{
"names": [
"kotsadm/minio@sha256:38c18cc2d92573cfce813931aaf04183b8c23b87a5ba0d672a8cfc1ca4f1acc6",
"kotsadm/minio:v1.10.3"
],
"sizeBytes": 51885319
},
{
"names": [
"kotsadm/minio@sha256:1c7c8a0e953fccbe44f44134a29431fd86a5cfc7845b84adb12a808b503cf847",
"kotsadm/minio:v1.11.0"
],
"sizeBytes": 51885319
},
{
"names": [
"kotsadm/minio@sha256:ffc3a26ce3fca3a6f5802444ceb6fee7a98a136c8e60b7f0020c6ce036ec628c",
"kotsadm/minio:v1.11.4"
],
"sizeBytes": 51885319
},
{
"names": [
"flungo/netutils@sha256:cf2a22cf9edee0640bae64fc33b8916fef524cc7f454e0279d91509cc1aecd60",
"flungo/netutils:latest"
],
"sizeBytes": 42668818
},
{
"names": [
"codescope/ui@sha256:147be2359690e9b4237462010111986234ec253cf19f2f7fed8e9a5c1ea59938",
"codescope/ui:1.6.3"
],
"sizeBytes": 25522501
},
{
"names": [
"codescope/router@sha256:fea5f5bf3b2fe8c872c769c91762108e7d6ff791e793c92d068035462d149de7",
"codescope/router:0.4.2"
],
"sizeBytes": 23233618
},
{
"names": [
"602401143452.dkr.ecr.us-east-2.amazonaws.com/eks/pause-amd64@sha256:bea77c323c47f7b573355516acf927691182d1333333d1f41b7544012fab7adf",
"602401143452.dkr.ecr.us-east-2.amazonaws.com/eks/pause-amd64:3.1"
],
"sizeBytes": 742472
}
]
}
}
]
`
var collectedDeployments = `[
{
"metadata": {

View File

@@ -2,16 +2,17 @@ package analyzer
import (
"encoding/json"
"strconv"
"strings"
"github.com/blang/semver"
"github.com/pkg/errors"
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"github.com/replicatedhq/troubleshoot/pkg/collect"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
)
func analyzeNoderesources(analyzer *troubleshootv1beta1.NodeResources, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
collected, err := getCollectedFileContents("cluster-info/nods.json")
func analyzeNodeResources(analyzer *troubleshootv1beta1.NodeResources, getCollectedFileContents func(string) ([]byte, error)) (*AnalyzeResult, error) {
collected, err := getCollectedFileContents("cluster-resources/nodes.json")
if err != nil {
return nil, errors.Wrap(err, "failed to get contents of nodes.json")
}
@@ -24,18 +25,194 @@ func analyzeNoderesources(analyzer *troubleshootv1beta1.NodeResources, getCollec
matchingNodeCount := 0
for _, node := range nodes {
matches, err := nodeMatchesFilters(node, analyzer.Filters)
isMatch, err := nodeMatchesFilters(node, analyzer.Filters)
if err != nil {
return nil, errors.Wrap(err, "failed to check if node matches filter")
}
if isMatch {
matchingNodeCount++
}
}
return analyzeClusterVersionResult(k8sVersion, analyzer.Outcomes, analyzer.CheckName)
title := analyzer.CheckName
if title == "" {
title = "Node Resources"
}
result := &AnalyzeResult{
Title: title,
}
for _, outcome := range analyzer.Outcomes {
if outcome.Fail != nil {
isWhenMatch, err := compareNodeResourceConditionalToActual(outcome.Fail.When, matchingNodeCount)
if err != nil {
return nil, errors.Wrap(err, "failed to parse when")
}
if isWhenMatch {
result.IsFail = true
result.Message = outcome.Fail.Message
result.URI = outcome.Fail.URI
return result, nil
}
} else if outcome.Warn != nil {
isWhenMatch, err := compareNodeResourceConditionalToActual(outcome.Warn.When, matchingNodeCount)
if err != nil {
return nil, errors.Wrap(err, "failed to parse when")
}
if isWhenMatch {
result.IsWarn = true
result.Message = outcome.Warn.Message
result.URI = outcome.Warn.URI
return result, nil
}
} else if outcome.Pass != nil {
isWhenMatch, err := compareNodeResourceConditionalToActual(outcome.Pass.When, matchingNodeCount)
if err != nil {
return nil, errors.Wrap(err, "failed to parse when")
}
if isWhenMatch {
result.IsPass = true
result.Message = outcome.Pass.Message
result.URI = outcome.Pass.URI
return result, nil
}
}
}
return result, nil
}
func nodeMatchesFilters(node *corev1.Node, filters *troubleshootv1beta1.NodeResourceFilters) (bool, error) {
func compareNodeResourceConditionalToActual(conditional string, actual int) (bool, error) {
if conditional == "" {
return true, nil
}
parts := strings.Split(strings.TrimSpace(conditional), " ")
if len(parts) != 2 {
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")
}
switch operator {
case "=", "==", "===":
return desiredValue == actual, nil
case "<":
return actual < desiredValue, nil
case "<=":
return actual <= desiredValue, nil
case ">":
return actual > desiredValue, nil
case ">=":
return actual >= desiredValue, nil
}
return false, errors.New("unexpected conditional in nodeResources")
}
func nodeMatchesFilters(node corev1.Node, filters *troubleshootv1beta1.NodeResourceFilters) (bool, error) {
if filters == nil {
return true, nil
}
return false, nil
// all filters must pass for this to pass
if filters.CPUCapacity != "" {
parsed, err := resource.ParseQuantity(filters.CPUCapacity)
if err != nil {
return false, errors.Wrap(err, "failed to parse cpu capacity")
}
if node.Status.Capacity.Cpu().Cmp(parsed) == -1 {
return false, nil
}
}
if filters.CPUAllocatable != "" {
parsed, err := resource.ParseQuantity(filters.CPUAllocatable)
if err != nil {
return false, errors.Wrap(err, "failed to parse cpu allocatable")
}
if node.Status.Allocatable.Cpu().Cmp(parsed) == -1 {
return false, nil
}
}
if filters.MemoryCapacity != "" {
parsed, err := resource.ParseQuantity(filters.MemoryCapacity)
if err != nil {
return false, errors.Wrap(err, "failed to parse memory capacity")
}
if node.Status.Capacity.Memory().Cmp(parsed) == -1 {
return false, nil
}
}
if filters.MemoryAllocatable != "" {
parsed, err := resource.ParseQuantity(filters.MemoryAllocatable)
if err != nil {
return false, errors.Wrap(err, "failed to parse memory allocatable")
}
if node.Status.Allocatable.Memory().Cmp(parsed) == -1 {
return false, nil
}
}
if filters.PodCapacity != "" {
parsed, err := resource.ParseQuantity(filters.PodCapacity)
if err != nil {
return false, errors.Wrap(err, "failed to parse pod capacity")
}
if node.Status.Capacity.Pods().Cmp(parsed) == -1 {
return false, nil
}
}
if filters.PodAllocatable != "" {
parsed, err := resource.ParseQuantity(filters.PodAllocatable)
if err != nil {
return false, errors.Wrap(err, "failed to parse pod allocatable")
}
if node.Status.Allocatable.Pods().Cmp(parsed) == -1 {
return false, nil
}
}
if filters.EphemeralStorageCapacity != "" {
parsed, err := resource.ParseQuantity(filters.EphemeralStorageCapacity)
if err != nil {
return false, errors.Wrap(err, "failed to parse ephemeralstorage capacity")
}
if node.Status.Capacity.StorageEphemeral().Cmp(parsed) == -1 {
return false, nil
}
}
if filters.EphemeralStorageAllocatable != "" {
parsed, err := resource.ParseQuantity(filters.EphemeralStorageAllocatable)
if err != nil {
return false, errors.Wrap(err, "failed to parse ephemeralstorage allocatable")
}
if node.Status.Allocatable.StorageEphemeral().Cmp(parsed) == -1 {
return false, nil
}
}
return true, nil
}

View File

@@ -6,21 +6,91 @@ import (
troubleshootv1beta1 "github.com/replicatedhq/troubleshoot/pkg/apis/troubleshoot/v1beta1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/resource"
)
func Test_nodeMatchesRfilters(t *testing.T) {
func Test_compareNodeResourceConditionalToActual(t *testing.T) {
tests := []struct {
name string
conditional string
actual int
expected bool
}{
{
name: "=",
conditional: "= 5",
actual: 5,
expected: true,
},
{
name: "<= (pass)",
conditional: "<= 5",
actual: 4,
expected: true,
},
{
name: "<= (fail)",
conditional: "<= 5",
actual: 6,
expected: false,
},
{
name: "> (pass)",
conditional: "> 5",
actual: 6,
expected: true,
},
{
name: ">=(fail)",
conditional: ">= 5",
actual: 4,
expected: false,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
req := require.New(t)
actual, err := compareNodeResourceConditionalToActual(test.conditional, test.actual)
req.NoError(err)
assert.Equal(t, test.expected, actual)
})
}
}
func Test_nodeMatchesFilters(t *testing.T) {
tests := []struct {
name string
node *corev1.Node
node corev1.Node
filters *troubleshootv1beta1.NodeResourceFilters
expectResult bool
}{
{
name: "true when empty filters",
node: &corev1.Node{
node: corev1.Node{
Status: corev1.NodeStatus{
Capacity: corev1.Sometrhing{},
Allocatable: corev1.Something{},
Capacity: corev1.ResourceList{
"attachable-volumes-aws-ebs": resource.MustParse("25"),
"cpu": resource.MustParse("2"),
"ephemeral-storage": resource.MustParse("20959212Ki"),
"hugepages-1Gi": resource.MustParse("0"),
"hugepages-2Mi": resource.MustParse("0"),
"memory": resource.MustParse("7951376Ki"),
"pods": resource.MustParse("29"),
},
Allocatable: corev1.ResourceList{
"attachable-volumes-aws-ebs": resource.MustParse("25"),
"cpu": resource.MustParse("2"),
"ephemeral-storage": resource.MustParse("19316009748"),
"hugepages-1Gi": resource.MustParse("0"),
"hugepages-2Mi": resource.MustParse("0"),
"memory": resource.MustParse("7848976Ki"),
"pods": resource.MustParse("29"),
},
},
},
filters: &troubleshootv1beta1.NodeResourceFilters{},
@@ -28,33 +98,81 @@ func Test_nodeMatchesRfilters(t *testing.T) {
},
{
name: "true while nil/missing filters",
node: &corev1.Node{
node: corev1.Node{
Status: corev1.NodeStatus{
Capacity: corev1.Sometrhing{},
Allocatable: corev1.Something{},
Capacity: corev1.ResourceList{
"attachable-volumes-aws-ebs": resource.MustParse("25"),
"cpu": resource.MustParse("2"),
"ephemeral-storage": resource.MustParse("20959212Ki"),
"hugepages-1Gi": resource.MustParse("0"),
"hugepages-2Mi": resource.MustParse("0"),
"memory": resource.MustParse("7951376Ki"),
"pods": resource.MustParse("29"),
},
Allocatable: corev1.ResourceList{
"attachable-volumes-aws-ebs": resource.MustParse("25"),
"cpu": resource.MustParse("2"),
"ephemeral-storage": resource.MustParse("19316009748"),
"hugepages-1Gi": resource.MustParse("0"),
"hugepages-2Mi": resource.MustParse("0"),
"memory": resource.MustParse("7848976Ki"),
"pods": resource.MustParse("29"),
},
},
},
expectResult: true,
},
{
name: "false when allocatable memory is too high",
node: &corev1.Node{
node: corev1.Node{
Status: corev1.NodeStatus{
Capacity: corev1.Sometrhing{},
Allocatable: corev1.Something{},
Capacity: corev1.ResourceList{
"attachable-volumes-aws-ebs": resource.MustParse("25"),
"cpu": resource.MustParse("2"),
"ephemeral-storage": resource.MustParse("20959212Ki"),
"hugepages-1Gi": resource.MustParse("0"),
"hugepages-2Mi": resource.MustParse("0"),
"memory": resource.MustParse("7951376Ki"),
"pods": resource.MustParse("29"),
},
Allocatable: corev1.ResourceList{
"attachable-volumes-aws-ebs": resource.MustParse("25"),
"cpu": resource.MustParse("2"),
"ephemeral-storage": resource.MustParse("19316009748"),
"hugepages-1Gi": resource.MustParse("0"),
"hugepages-2Mi": resource.MustParse("0"),
"memory": resource.MustParse("7848976Ki"),
"pods": resource.MustParse("29"),
},
},
},
filters: &troubleshootv1beta1.NodeResourceFilters{
MemoryAllocatable: "32Gi",
MemoryAllocatable: "16Gi",
},
expectResult: false,
},
{
name: "true when allocatable memory is available",
node: &corev1.Node{
node: corev1.Node{
Status: corev1.NodeStatus{
Capacity: corev1.Sometrhing{},
Allocatable: corev1.Something{},
Capacity: corev1.ResourceList{
"attachable-volumes-aws-ebs": resource.MustParse("25"),
"cpu": resource.MustParse("2"),
"ephemeral-storage": resource.MustParse("20959212Ki"),
"hugepages-1Gi": resource.MustParse("0"),
"hugepages-2Mi": resource.MustParse("0"),
"memory": resource.MustParse("7951376Ki"),
"pods": resource.MustParse("29"),
},
Allocatable: corev1.ResourceList{
"attachable-volumes-aws-ebs": resource.MustParse("25"),
"cpu": resource.MustParse("2"),
"ephemeral-storage": resource.MustParse("19316009748"),
"hugepages-1Gi": resource.MustParse("0"),
"hugepages-2Mi": resource.MustParse("0"),
"memory": resource.MustParse("7848976Ki"),
"pods": resource.MustParse("29"),
},
},
},
filters: &troubleshootv1beta1.NodeResourceFilters{
@@ -71,7 +189,7 @@ func Test_nodeMatchesRfilters(t *testing.T) {
actual, err := nodeMatchesFilters(test.node, test.filters)
req.NoError(err)
assert.Equal(t, &test.expectResult, actual)
assert.Equal(t, test.expectResult, actual)
})
}

View File

@@ -1,9 +1,5 @@
package v1beta1
import (
"k8s.io/apimachinery/pkg/util/intstr"
)
type SingleOutcome struct {
When string `json:"when,omitempty" yaml:"when,omitempty"`
Message string `json:"message,omitempty" yaml:"message,omitempty"`
@@ -79,20 +75,20 @@ type Distribution struct {
}
type NodeResources struct {
// AnalyzeMeta `json:",inline" yaml:",inline"`
// Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
// Filters *NodeResourceFilters `json:"filters,omitempty" yaml:"filters,omitempty"`
AnalyzeMeta `json:",inline" yaml:",inline"`
Outcomes []*Outcome `json:"outcomes" yaml:"outcomes"`
Filters *NodeResourceFilters `json:"filters,omitempty" yaml:"filters,omitempty"`
}
type NodeResourceFilters struct {
// CPUCapacity *intstr.Intstr `json:"cpuCapacity,omitempty" yaml:"cpuCapacity,omitempty"`
// CPUAllocatable *intstr.Intstr `json:"cpuAllocatable,omitempty" yaml:"cpuAllocatable,omitempty"`
// MemoryCapacity *intstr.Intstr `json:"memoryCapacity,omitempty" yaml:"memoryCapacity,omitempty"`
// MemoryAllocatable *intstr.Intstr `json:"memoryAllocatable,omitempty" yaml:"memoryAllocatable,omitempty"`
// PodCapacity *intstr.Intstr `json:"podCapacity,omitempty" yaml:"podCapacity,omitempty"`
// PodAllocatable *intstr.Intstr `json:"podAllocatable,omitempty" yaml:"podAllocatable,omitempty"`
// EphemeralStorageCapacity *intstr.Intstr `json:"ephemeralStorageCapacity,omitempty" yaml:"ephemeralStorageCapacity,omitempty"`
// EphemeralStorageAllocatable*intstr.Intstr `json:"ephemeralStorageAllocatable,omitempty" yaml:"ephemeralStorageAllocatable,omitempty"`
CPUCapacity string `json:"cpuCapacity,omitempty" yaml:"cpuCapacity,omitempty"`
CPUAllocatable string `json:"cpuAllocatable,omitempty" yaml:"cpuAllocatable,omitempty"`
MemoryCapacity string `json:"memoryCapacity,omitempty" yaml:"memoryCapacity,omitempty"`
MemoryAllocatable string `json:"memoryAllocatable,omitempty" yaml:"memoryAllocatable,omitempty"`
PodCapacity string `json:"podCapacity,omitempty" yaml:"podCapacity,omitempty"`
PodAllocatable string `json:"podAllocatable,omitempty" yaml:"podAllocatable,omitempty"`
EphemeralStorageCapacity string `json:"ephemeralStorageCapacity,omitempty" yaml:"ephemeralStorageCapacity,omitempty"`
EphemeralStorageAllocatable string `json:"ephemeralStorageAllocatable,omitempty" yaml:"ephemeralStorageAllocatable,omitempty"`
}
type TextAnalyze struct {

View File

@@ -105,7 +105,7 @@ func (in *Analyze) DeepCopyInto(out *Analyze) {
if in.NodeResources != nil {
in, out := &in.NodeResources, &out.NodeResources
*out = new(NodeResources)
**out = **in
(*in).DeepCopyInto(*out)
}
if in.TextAnalyze != nil {
in, out := &in.TextAnalyze, &out.TextAnalyze
@@ -1067,6 +1067,23 @@ func (in *NodeResourceFilters) DeepCopy() *NodeResourceFilters {
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *NodeResources) DeepCopyInto(out *NodeResources) {
*out = *in
out.AnalyzeMeta = in.AnalyzeMeta
if in.Outcomes != nil {
in, out := &in.Outcomes, &out.Outcomes
*out = make([]*Outcome, len(*in))
for i := range *in {
if (*in)[i] != nil {
in, out := &(*in)[i], &(*out)[i]
*out = new(Outcome)
(*in).DeepCopyInto(*out)
}
}
}
if in.Filters != nil {
in, out := &in.Filters, &out.Filters
*out = new(NodeResourceFilters)
**out = **in
}
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new NodeResources.