diff --git a/pkg/kubelet/apis/stats/v1alpha1/types.go b/pkg/kubelet/apis/stats/v1alpha1/types.go index e08128658d2..1d30836d535 100644 --- a/pkg/kubelet/apis/stats/v1alpha1/types.go +++ b/pkg/kubelet/apis/stats/v1alpha1/types.go @@ -130,10 +130,10 @@ type PodReference struct { UID string `json:"uid"` } -// NetworkStats contains data about network resources. -type NetworkStats struct { - // The time at which these stats were updated. - Time metav1.Time `json:"time"` +// InterfaceStats contains resource value data about interface. +type InterfaceStats struct { + // The name of the interface + Name string `json:"name"` // Cumulative count of bytes received. // +optional RxBytes *uint64 `json:"rxBytes,omitempty"` @@ -148,6 +148,17 @@ type NetworkStats struct { TxErrors *uint64 `json:"txErrors,omitempty"` } +// NetworkStats contains data about network resources. +type NetworkStats struct { + // The time at which these stats were updated. + Time metav1.Time `json:"time"` + + // Stats for the default interface, if found + InterfaceStats `json:",inline"` + + Interfaces []InterfaceStats `json:"interfaces,omitempty"` +} + // CPUStats contains data about CPU usage. type CPUStats struct { // The time at which these stats were updated. diff --git a/pkg/kubelet/stats/helper.go b/pkg/kubelet/stats/helper.go index 091a0d51da1..7e81e654ff4 100644 --- a/pkg/kubelet/stats/helper.go +++ b/pkg/kubelet/stats/helper.go @@ -127,19 +127,29 @@ func cadvisorInfoToNetworkStats(name string, info *cadvisorapiv2.ContainerInfo) if !found { return nil } - for _, inter := range cstat.Network.Interfaces { - if inter.Name == network.DefaultInterfaceName { - return &statsapi.NetworkStats{ - Time: metav1.NewTime(cstat.Timestamp), - RxBytes: &inter.RxBytes, - RxErrors: &inter.RxErrors, - TxBytes: &inter.TxBytes, - TxErrors: &inter.TxErrors, - } - } + + iStats := statsapi.NetworkStats{ + Time: metav1.NewTime(cstat.Timestamp), } - glog.V(4).Infof("Missing default interface %q for %s", network.DefaultInterfaceName, name) - return nil + + for i := range cstat.Network.Interfaces { + inter := cstat.Network.Interfaces[i] + iStat := statsapi.InterfaceStats{ + Name: inter.Name, + RxBytes: &inter.RxBytes, + RxErrors: &inter.RxErrors, + TxBytes: &inter.TxBytes, + TxErrors: &inter.TxErrors, + } + + if inter.Name == network.DefaultInterfaceName { + iStats.InterfaceStats = iStat + } + + iStats.Interfaces = append(iStats.Interfaces, iStat) + } + + return &iStats } // cadvisorInfoToUserDefinedMetrics returns the statsapi.UserDefinedMetric diff --git a/pkg/kubelet/stats/stats_provider_test.go b/pkg/kubelet/stats/stats_provider_test.go index 69cb50c443d..e0c7560887c 100644 --- a/pkg/kubelet/stats/stats_provider_test.go +++ b/pkg/kubelet/stats/stats_provider_test.go @@ -525,10 +525,26 @@ func testTime(base time.Time, seed int) time.Time { func checkNetworkStats(t *testing.T, label string, seed int, stats *statsapi.NetworkStats) { assert.NotNil(t, stats) assert.EqualValues(t, testTime(timestamp, seed).Unix(), stats.Time.Time.Unix(), label+".Net.Time") + assert.EqualValues(t, "eth0", stats.Name, "default interface name is not eth0") assert.EqualValues(t, seed+offsetNetRxBytes, *stats.RxBytes, label+".Net.RxBytes") assert.EqualValues(t, seed+offsetNetRxErrors, *stats.RxErrors, label+".Net.RxErrors") assert.EqualValues(t, seed+offsetNetTxBytes, *stats.TxBytes, label+".Net.TxBytes") assert.EqualValues(t, seed+offsetNetTxErrors, *stats.TxErrors, label+".Net.TxErrors") + + assert.EqualValues(t, 2, len(stats.Interfaces), "network interfaces should contain 2 elements") + + assert.EqualValues(t, "eth0", stats.Interfaces[0].Name, "default interface name is ont eth0") + assert.EqualValues(t, seed+offsetNetRxBytes, *stats.Interfaces[0].RxBytes, label+".Net.TxErrors") + assert.EqualValues(t, seed+offsetNetRxErrors, *stats.Interfaces[0].RxErrors, label+".Net.TxErrors") + assert.EqualValues(t, seed+offsetNetTxBytes, *stats.Interfaces[0].TxBytes, label+".Net.TxErrors") + assert.EqualValues(t, seed+offsetNetTxErrors, *stats.Interfaces[0].TxErrors, label+".Net.TxErrors") + + assert.EqualValues(t, "cbr0", stats.Interfaces[1].Name, "cbr0 interface name is ont cbr0") + assert.EqualValues(t, 100, *stats.Interfaces[1].RxBytes, label+".Net.TxErrors") + assert.EqualValues(t, 100, *stats.Interfaces[1].RxErrors, label+".Net.TxErrors") + assert.EqualValues(t, 100, *stats.Interfaces[1].TxBytes, label+".Net.TxErrors") + assert.EqualValues(t, 100, *stats.Interfaces[1].TxErrors, label+".Net.TxErrors") + } func checkCPUStats(t *testing.T, label string, seed int, stats *statsapi.CPUStats) { diff --git a/test/e2e_node/summary_test.go b/test/e2e_node/summary_test.go index f65559cd9cd..7afa39384c3 100644 --- a/test/e2e_node/summary_test.go +++ b/test/e2e_node/summary_test.go @@ -173,11 +173,15 @@ var _ = framework.KubeDescribe("Summary API", func() { }), }), "Network": ptrMatchAllFields(gstruct.Fields{ - "Time": recent(maxStatsAge), - "RxBytes": bounded(10, 10*framework.Mb), - "RxErrors": bounded(0, 1000), - "TxBytes": bounded(10, 10*framework.Mb), - "TxErrors": bounded(0, 1000), + "Time": recent(maxStatsAge), + "InterfaceStats": gstruct.MatchAllFields(gstruct.Fields{ + "Name": Equal("eth0"), + "RxBytes": bounded(10, 10*framework.Mb), + "RxErrors": bounded(0, 1000), + "TxBytes": bounded(10, 10*framework.Mb), + "TxErrors": bounded(0, 1000), + }), + "Interfaces": Not(BeNil()), }), "VolumeStats": gstruct.MatchAllElements(summaryObjectID, gstruct.Elements{ "test-empty-dir": gstruct.MatchAllFields(gstruct.Fields{ @@ -222,13 +226,17 @@ var _ = framework.KubeDescribe("Summary API", func() { "MajorPageFaults": bounded(0, 100000), }), // TODO(#28407): Handle non-eth0 network interface names. - "Network": Or(BeNil(), ptrMatchAllFields(gstruct.Fields{ - "Time": recent(maxStatsAge), - "RxBytes": bounded(1*framework.Mb, 100*framework.Gb), - "RxErrors": bounded(0, 100000), - "TxBytes": bounded(10*framework.Kb, 10*framework.Gb), - "TxErrors": bounded(0, 100000), - })), + "Network": ptrMatchAllFields(gstruct.Fields{ + "Time": recent(maxStatsAge), + "InterfaceStats": gstruct.MatchAllFields(gstruct.Fields{ + "Name": Or(BeEmpty(), Equal("eth0")), + "RxBytes": Or(BeNil(), bounded(1*framework.Mb, 100*framework.Gb)), + "RxErrors": Or(BeNil(), bounded(0, 100000)), + "TxBytes": Or(BeNil(), bounded(10*framework.Kb, 10*framework.Gb)), + "TxErrors": Or(BeNil(), bounded(0, 100000)), + }), + "Interfaces": Not(BeNil()), + }), "Fs": ptrMatchAllFields(gstruct.Fields{ "Time": recent(maxStatsAge), "AvailableBytes": fsCapacityBounds,