diff --git a/pkg/monitoring/metrics/virt-controller/vmistats_collector.go b/pkg/monitoring/metrics/virt-controller/vmistats_collector.go index 9fa7e881062e..db045721422d 100644 --- a/pkg/monitoring/metrics/virt-controller/vmistats_collector.go +++ b/pkg/monitoring/metrics/virt-controller/vmistats_collector.go @@ -61,7 +61,7 @@ var ( Name: "kubevirt_vmi_phase_count", Help: "Sum of VMIs per phase and node. `phase` can be one of the following: [`Pending`, `Scheduling`, `Scheduled`, `Running`, `Succeeded`, `Failed`, `Unknown`].", }, - []string{"node", "phase", "os", "workload", "flavor", "instance_type", "preference"}, + []string{"node", "phase", "os", "workload", "flavor", "instance_type", "preference", "guest_os_kernel_release", "guest_os_machine", "guest_os_name", "guest_os_version_id"}, ) vmiEvictionBlocker = operatormetrics.NewGaugeVec( @@ -74,13 +74,17 @@ var ( ) type vmiCountMetric struct { - Phase string - OS string - Workload string - Flavor string - InstanceType string - Preference string - NodeName string + Phase string + OS string + Workload string + Flavor string + InstanceType string + Preference string + NodeName string + GuestOSKernelRelease string + GuestOSMachine string + GuestOSName string + GuestOSVersionID string } func vmiStatsCollectorCallback() []operatormetrics.CollectorResult { @@ -117,8 +121,9 @@ func getVmisPhase(vmis []*k6tv1.VirtualMachineInstance) []operatormetrics.Collec for vmc, count := range countMap { cr = append(cr, operatormetrics.CollectorResult{ Metric: vmiCount, - Labels: []string{vmc.NodeName, vmc.Phase, vmc.OS, vmc.Workload, vmc.Flavor, vmc.InstanceType, vmc.Preference}, - Value: float64(count), + Labels: []string{vmc.NodeName, vmc.Phase, vmc.OS, vmc.Workload, vmc.Flavor, + vmc.InstanceType, vmc.Preference, vmc.GuestOSKernelRelease, vmc.GuestOSMachine, vmc.GuestOSName, vmc.GuestOSVersionID}, + Value: float64(count), }) } @@ -137,16 +142,21 @@ func makeVMICountMetricMap(vmis []*k6tv1.VirtualMachineInstance) map[vmiCountMet func newVMICountMetric(vmi *k6tv1.VirtualMachineInstance) vmiCountMetric { vmc := vmiCountMetric{ - Phase: strings.ToLower(string(vmi.Status.Phase)), - OS: none, - Workload: none, - Flavor: none, - InstanceType: none, - Preference: none, - NodeName: vmi.Status.NodeName, + Phase: strings.ToLower(string(vmi.Status.Phase)), + OS: none, + Workload: none, + Flavor: none, + InstanceType: none, + Preference: none, + GuestOSKernelRelease: none, + GuestOSMachine: none, + GuestOSName: none, + GuestOSVersionID: none, + NodeName: vmi.Status.NodeName, } updateFromAnnotations(&vmc, vmi.Annotations) + updateFromGuestOSInfo(&vmc, vmi.Status.GuestOSInfo) return vmc } @@ -228,6 +238,26 @@ func setPreferenceFromAnnotations(vmc *vmiCountMetric, annotations map[string]st } } +func updateFromGuestOSInfo(vmc *vmiCountMetric, guestOSInfo k6tv1.VirtualMachineInstanceGuestOSInfo) { + if guestOSInfo != (k6tv1.VirtualMachineInstanceGuestOSInfo{}) { + if guestOSInfo.KernelRelease != "" { + vmc.GuestOSKernelRelease = guestOSInfo.KernelRelease + } + + if guestOSInfo.Machine != "" { + vmc.GuestOSMachine = guestOSInfo.Machine + } + + if guestOSInfo.Name != "" { + vmc.GuestOSName = guestOSInfo.Name + } + + if guestOSInfo.VersionID != "" { + vmc.GuestOSVersionID = guestOSInfo.VersionID + } + } +} + func getEvictionBlocker(vmis []*k6tv1.VirtualMachineInstance) []operatormetrics.CollectorResult { var cr []operatormetrics.CollectorResult diff --git a/pkg/monitoring/metrics/virt-controller/vmistats_collector_test.go b/pkg/monitoring/metrics/virt-controller/vmistats_collector_test.go index 4d24e447ac21..49bb8703f702 100644 --- a/pkg/monitoring/metrics/virt-controller/vmistats_collector_test.go +++ b/pkg/monitoring/metrics/virt-controller/vmistats_collector_test.go @@ -152,6 +152,12 @@ var _ = Describe("Utility functions", func() { }, Status: k6tv1.VirtualMachineInstanceStatus{ Phase: "Pending", + GuestOSInfo: k6tv1.VirtualMachineInstanceGuestOSInfo{ + KernelRelease: "6.5.6-300.fc39.x86_64", + Machine: "x86_64", + Name: "Fedora Linux", + VersionID: "39", + }, }, }, { @@ -189,28 +195,40 @@ var _ = Describe("Utility functions", func() { Expect(countMap).To(HaveLen(3)) running := vmiCountMetric{ - Phase: "running", - OS: "centos8", - Workload: "server", - Flavor: "tiny", - InstanceType: "", - Preference: "", + Phase: "running", + OS: "centos8", + Workload: "server", + Flavor: "tiny", + GuestOSKernelRelease: "", + GuestOSMachine: "", + GuestOSName: "", + GuestOSVersionID: "", + InstanceType: "", + Preference: "", } pending := vmiCountMetric{ - Phase: "pending", - OS: "fedora33", - Workload: "workstation", - Flavor: "large", - InstanceType: "", - Preference: "", + Phase: "pending", + OS: "fedora33", + Workload: "workstation", + Flavor: "large", + InstanceType: "", + Preference: "", + GuestOSKernelRelease: "6.5.6-300.fc39.x86_64", + GuestOSMachine: "x86_64", + GuestOSName: "Fedora Linux", + GuestOSVersionID: "39", } scheduling := vmiCountMetric{ - Phase: "scheduling", - OS: "centos7", - Workload: "server", - Flavor: "medium", - InstanceType: "", - Preference: "", + Phase: "scheduling", + OS: "centos7", + Workload: "server", + Flavor: "medium", + GuestOSKernelRelease: "", + GuestOSMachine: "", + GuestOSName: "", + GuestOSVersionID: "", + InstanceType: "", + Preference: "", } bogus := vmiCountMetric{ Phase: "bogus", @@ -243,7 +261,7 @@ var _ = Describe("Utility functions", func() { Expect(phaseResultMetric).ToNot(BeNil()) Expect(phaseResultMetric.Metric.GetOpts().Name).To(ContainSubstring("kubevirt_vmi_phase_count")) Expect(phaseResultMetric.Value).To(BeEquivalentTo(1)) - Expect(phaseResultMetric.Labels).To(HaveLen(7)) + Expect(phaseResultMetric.Labels).To(HaveLen(11)) Expect(phaseResultMetric.Labels[5]).To(Equal(expected)) }, Entry("with no instance type expect ", k6tv1.InstancetypeAnnotation, "", ""), @@ -276,7 +294,7 @@ var _ = Describe("Utility functions", func() { Expect(phaseResultMetric.Metric.GetOpts().Name).To(ContainSubstring("kubevirt_vmi_phase_count")) Expect(phaseResultMetric.Value).To(BeEquivalentTo(1)) - Expect(phaseResultMetric.Labels).To(HaveLen(7)) + Expect(phaseResultMetric.Labels).To(HaveLen(11)) Expect(phaseResultMetric.Labels[6]).To(Equal(expected)) }, Entry("with no preference expect ", k6tv1.PreferenceAnnotation, "", ""), diff --git a/tests/monitoring/BUILD.bazel b/tests/monitoring/BUILD.bazel index 546ea098e84e..f5171138be33 100644 --- a/tests/monitoring/BUILD.bazel +++ b/tests/monitoring/BUILD.bazel @@ -39,6 +39,7 @@ go_library( "//vendor/github.com/machadovilaca/operator-observability/pkg/operatormetrics:go_default_library", "//vendor/github.com/onsi/ginkgo/v2:go_default_library", "//vendor/github.com/onsi/gomega:go_default_library", + "//vendor/github.com/onsi/gomega/gstruct:go_default_library", "//vendor/github.com/onsi/gomega/types:go_default_library", "//vendor/github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1:go_default_library", "//vendor/github.com/prometheus/client_golang/prometheus:go_default_library", diff --git a/tests/monitoring/vm_monitoring.go b/tests/monitoring/vm_monitoring.go index 48362f86413e..b73f4853a628 100644 --- a/tests/monitoring/vm_monitoring.go +++ b/tests/monitoring/vm_monitoring.go @@ -28,6 +28,7 @@ import ( . "github.com/onsi/ginkgo/v2" . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gstruct" "github.com/onsi/gomega/types" corev1 "k8s.io/api/core/v1" @@ -246,6 +247,20 @@ var _ = Describe("[Serial][sig-monitoring]VM Monitoring", Serial, decorators.Sig }) }) + Context("VM metrics that are based on the guest agent", func() { + It("should have kubevirt_vmi_phase_count correctly configured with guest OS labels", func() { + agentVMI := createAgentVMI() + labels := map[string]string{ + "guest_os_kernel_release": agentVMI.Status.GuestOSInfo.KernelRelease, + "guest_os_machine": agentVMI.Status.GuestOSInfo.Machine, + "guest_os_name": agentVMI.Status.GuestOSInfo.Name, + "guest_os_version_id": agentVMI.Status.GuestOSInfo.VersionID, + } + + libmonitoring.WaitForMetricValueWithLabels(virtClient, "kubevirt_vmi_phase_count", 1, labels, 1) + }) + }) + Context("VM alerts", func() { var scales *libmonitoring.Scaling @@ -304,3 +319,30 @@ var _ = Describe("[Serial][sig-monitoring]VM Monitoring", Serial, decorators.Sig }) }) }) + +func createAgentVMI() *v1.VirtualMachineInstance { + virtClient := kubevirt.Client() + agentVMI := libvmifact.NewFedora(libnet.WithMasqueradeNetworking()...) + + By("Starting a VirtualMachineInstance") + agentVMI, err := virtClient.VirtualMachineInstance(testsuite.GetTestNamespace(agentVMI)).Create(context.Background(), agentVMI, metav1.CreateOptions{}) + Expect(err).ToNot(HaveOccurred()) + libwait.WaitForSuccessfulVMIStart(agentVMI) + + getOptions := metav1.GetOptions{} + var VMI *v1.VirtualMachineInstance + + By("VMI has the guest agent connected condition") + Eventually(func() []v1.VirtualMachineInstanceCondition { + VMI, err = virtClient.VirtualMachineInstance(testsuite.GetTestNamespace(agentVMI)).Get(context.Background(), agentVMI.Name, getOptions) + Expect(err).ToNot(HaveOccurred()) + return VMI.Status.Conditions + }, 240*time.Second, 1*time.Second).Should( + ContainElement( + MatchFields( + IgnoreExtras, + Fields{"Type": Equal(v1.VirtualMachineInstanceAgentConnected)})), + "Should have agent connected condition") + + return VMI +}