Add unit tests to existing code

This commit is contained in:
James Sturtevant
2021-11-02 14:47:11 -07:00
committed by James Sturtevant
parent d7bdbb075f
commit c39945c116
3 changed files with 494 additions and 9 deletions

View File

@@ -67,6 +67,7 @@ type criStatsProvider struct {
imageService internalapi.ImageManagerService imageService internalapi.ImageManagerService
// hostStatsProvider is used to get the status of the host filesystem consumed by pods. // hostStatsProvider is used to get the status of the host filesystem consumed by pods.
hostStatsProvider HostStatsProvider hostStatsProvider HostStatsProvider
hcsshimInterface interface{}
// cpuUsageCache caches the cpu usage for containers. // cpuUsageCache caches the cpu usage for containers.
cpuUsageCache map[string]*cpuUsageRecord cpuUsageCache map[string]*cpuUsageRecord

View File

@@ -31,9 +31,30 @@ import (
statsapi "k8s.io/kubelet/pkg/apis/stats/v1alpha1" statsapi "k8s.io/kubelet/pkg/apis/stats/v1alpha1"
) )
type hcsShimInterface interface {
GetContainers(q hcsshim.ComputeSystemQuery) ([]hcsshim.ContainerProperties, error)
GetHNSEndpointByID(endpointID string) (*hcsshim.HNSEndpoint, error)
OpenContainer(id string) (hcsshim.Container, error)
}
type windowshim struct{}
func (s windowshim) GetContainers(q hcsshim.ComputeSystemQuery) ([]hcsshim.ContainerProperties, error) {
return hcsshim.GetContainers(q)
}
func (s windowshim) GetHNSEndpointByID(endpointID string) (*hcsshim.HNSEndpoint, error) {
return hcsshim.GetHNSEndpointByID(endpointID)
}
func (s windowshim) OpenContainer(id string) (hcsshim.Container, error) {
return hcsshim.OpenContainer(id)
}
// listContainerNetworkStats returns the network stats of all the running containers. // listContainerNetworkStats returns the network stats of all the running containers.
func (p *criStatsProvider) listContainerNetworkStats() (map[string]*statsapi.NetworkStats, error) { func (p *criStatsProvider) listContainerNetworkStats() (map[string]*statsapi.NetworkStats, error) {
containers, err := hcsshim.GetContainers(hcsshim.ComputeSystemQuery{ shim := newHcsShim(p)
containers, err := shim.GetContainers(hcsshim.ComputeSystemQuery{
Types: []string{"Container"}, Types: []string{"Container"},
}) })
if err != nil { if err != nil {
@@ -42,24 +63,34 @@ func (p *criStatsProvider) listContainerNetworkStats() (map[string]*statsapi.Net
stats := make(map[string]*statsapi.NetworkStats) stats := make(map[string]*statsapi.NetworkStats)
for _, c := range containers { for _, c := range containers {
cstats, err := fetchContainerStats(c) cstats, err := fetchContainerStats(shim, c)
if err != nil { if err != nil {
klog.V(4).InfoS("Failed to fetch statistics for container, continue to get stats for other containers", "containerID", c.ID, "err", err) klog.V(4).InfoS("Failed to fetch statistics for container, continue to get stats for other containers", "containerID", c.ID, "err", err)
continue continue
} }
if len(cstats.Network) > 0 { if len(cstats.Network) > 0 {
stats[c.ID] = hcsStatsToNetworkStats(cstats.Timestamp, cstats.Network) stats[c.ID] = hcsStatsToNetworkStats(shim, cstats.Timestamp, cstats.Network)
} }
} }
return stats, nil return stats, nil
} }
func fetchContainerStats(c hcsshim.ContainerProperties) (stats hcsshim.Statistics, err error) { func newHcsShim(p *criStatsProvider) hcsShimInterface {
var shim hcsShimInterface
if p.hcsshimInterface == nil {
shim = windowshim{}
} else {
shim = p.hcsshimInterface.(hcsShimInterface)
}
return shim
}
func fetchContainerStats(hcsshimInterface hcsShimInterface, c hcsshim.ContainerProperties) (stats hcsshim.Statistics, err error) {
var ( var (
container hcsshim.Container container hcsshim.Container
) )
container, err = hcsshim.OpenContainer(c.ID) container, err = hcsshimInterface.OpenContainer(c.ID)
if err != nil { if err != nil {
return return
} }
@@ -77,7 +108,7 @@ func fetchContainerStats(c hcsshim.ContainerProperties) (stats hcsshim.Statistic
} }
// hcsStatsToNetworkStats converts hcsshim.Statistics.Network to statsapi.NetworkStats // hcsStatsToNetworkStats converts hcsshim.Statistics.Network to statsapi.NetworkStats
func hcsStatsToNetworkStats(timestamp time.Time, hcsStats []hcsshim.NetworkStats) *statsapi.NetworkStats { func hcsStatsToNetworkStats(hcsshimInterface hcsShimInterface, timestamp time.Time, hcsStats []hcsshim.NetworkStats) *statsapi.NetworkStats {
result := &statsapi.NetworkStats{ result := &statsapi.NetworkStats{
Time: metav1.NewTime(timestamp), Time: metav1.NewTime(timestamp),
Interfaces: make([]statsapi.InterfaceStats, 0), Interfaces: make([]statsapi.InterfaceStats, 0),
@@ -85,7 +116,7 @@ func hcsStatsToNetworkStats(timestamp time.Time, hcsStats []hcsshim.NetworkStats
adapters := sets.NewString() adapters := sets.NewString()
for _, stat := range hcsStats { for _, stat := range hcsStats {
iStat, err := hcsStatsToInterfaceStats(stat) iStat, err := hcsStatsToInterfaceStats(hcsshimInterface, stat)
if err != nil { if err != nil {
klog.InfoS("Failed to get HNS endpoint, continue to get stats for other endpoints", "endpointID", stat.EndpointId, "err", err) klog.InfoS("Failed to get HNS endpoint, continue to get stats for other endpoints", "endpointID", stat.EndpointId, "err", err)
continue continue
@@ -109,8 +140,8 @@ func hcsStatsToNetworkStats(timestamp time.Time, hcsStats []hcsshim.NetworkStats
} }
// hcsStatsToInterfaceStats converts hcsshim.NetworkStats to statsapi.InterfaceStats. // hcsStatsToInterfaceStats converts hcsshim.NetworkStats to statsapi.InterfaceStats.
func hcsStatsToInterfaceStats(stat hcsshim.NetworkStats) (*statsapi.InterfaceStats, error) { func hcsStatsToInterfaceStats(hcsshimInterface hcsShimInterface, stat hcsshim.NetworkStats) (*statsapi.InterfaceStats, error) {
endpoint, err := hcsshim.GetHNSEndpointByID(stat.EndpointId) endpoint, err := hcsshimInterface.GetHNSEndpointByID(stat.EndpointId)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@@ -0,0 +1,453 @@
package stats
import (
"reflect"
"testing"
"time"
"github.com/Microsoft/hcsshim"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
statsapi "k8s.io/kubelet/pkg/apis/stats/v1alpha1"
)
type fakeConatiner struct {
stat hcsshim.Statistics
}
func (f fakeConatiner) Start() error {
return nil
}
func (f fakeConatiner) Shutdown() error {
return nil
}
func (f fakeConatiner) Terminate() error {
return nil
}
func (f fakeConatiner) Wait() error {
return nil
}
func (f fakeConatiner) WaitTimeout(duration time.Duration) error {
return nil
}
func (f fakeConatiner) Pause() error {
return nil
}
func (f fakeConatiner) Resume() error {
return nil
}
func (f fakeConatiner) HasPendingUpdates() (bool, error) {
return false, nil
}
func (f fakeConatiner) Statistics() (hcsshim.Statistics, error) {
return f.stat, nil
}
func (f fakeConatiner) ProcessList() ([]hcsshim.ProcessListItem, error) {
return []hcsshim.ProcessListItem{}, nil
}
func (f fakeConatiner) MappedVirtualDisks() (map[int]hcsshim.MappedVirtualDiskController, error) {
return map[int]hcsshim.MappedVirtualDiskController{}, nil
}
func (f fakeConatiner) CreateProcess(c *hcsshim.ProcessConfig) (hcsshim.Process, error) {
return nil, nil
}
func (f fakeConatiner) OpenProcess(pid int) (hcsshim.Process, error) {
return nil, nil
}
func (f fakeConatiner) Close() error {
return nil
}
func (f fakeConatiner) Modify(config *hcsshim.ResourceModificationRequestResponse) error {
return nil
}
func (s fakehcsshim) GetContainers(q hcsshim.ComputeSystemQuery) ([]hcsshim.ContainerProperties, error) {
cp := []hcsshim.ContainerProperties{}
for _, c := range s.containers {
cp = append(cp, c.container)
}
return cp, nil
}
func (s fakehcsshim) GetHNSEndpointByID(endpointID string) (*hcsshim.HNSEndpoint, error) {
e := hcsshim.HNSEndpoint{
Name: endpointID,
}
return &e, nil
}
func (s fakehcsshim) OpenContainer(id string) (hcsshim.Container, error) {
fc := fakeConatiner{}
for _, c := range s.containers {
if c.container.ID == id {
for _, s := range c.hcsStats{
fc.stat.Network = append(fc.stat.Network, s)
}
}
}
return fc, nil
}
type fakehcsshim struct {
containers []containerStats
}
type containerStats struct {
container hcsshim.ContainerProperties
hcsStats []hcsshim.NetworkStats
}
func Test_criStatsProvider_listContainerNetworkStats(t *testing.T) {
tests := []struct {
name string
fields fakehcsshim
want map[string]*statsapi.NetworkStats
wantErr bool
}{
{
name: "basic example",
fields: fakehcsshim{
containers: []containerStats{
{
container: hcsshim.ContainerProperties{
ID: "c1",
}, hcsStats: []hcsshim.NetworkStats{
{
BytesReceived: 1,
BytesSent: 10,
EndpointId: "test",
InstanceId: "test",
},
},
},
{
container: hcsshim.ContainerProperties{
ID: "c2",
}, hcsStats: []hcsshim.NetworkStats{
{
BytesReceived: 2,
BytesSent: 20,
EndpointId: "test2",
InstanceId: "test2",
},
},
},
},
},
want: map[string]*statsapi.NetworkStats{
"c1": &statsapi.NetworkStats{
Time: v1.Time{},
InterfaceStats: statsapi.InterfaceStats{
Name: "test",
RxBytes: toP(1),
TxBytes: toP(10),
},
Interfaces: []statsapi.InterfaceStats{
{
Name: "test",
RxBytes: toP(1),
TxBytes: toP(10),
},
},
},
"c2": &statsapi.NetworkStats{
Time: v1.Time{},
InterfaceStats: statsapi.InterfaceStats{
Name: "test2",
RxBytes: toP(2),
TxBytes: toP(20),
},
Interfaces: []statsapi.InterfaceStats{
{
Name: "test2",
RxBytes: toP(2),
TxBytes: toP(20),
},
},
},
},
wantErr: false,
},
{
name: "multiple containers same endpoint",
fields: fakehcsshim{
containers: []containerStats{
{
container: hcsshim.ContainerProperties{
ID: "c1",
}, hcsStats: []hcsshim.NetworkStats{
{
BytesReceived: 1,
BytesSent: 10,
EndpointId: "test",
InstanceId: "test",
},
},
},
{
container: hcsshim.ContainerProperties{
ID: "c2",
}, hcsStats: []hcsshim.NetworkStats{
{
BytesReceived: 2,
BytesSent: 20,
EndpointId: "test2",
InstanceId: "test2",
},
},
},
{
container: hcsshim.ContainerProperties{
ID: "c3",
}, hcsStats: []hcsshim.NetworkStats{
{
BytesReceived: 3,
BytesSent: 30,
EndpointId: "test2",
InstanceId: "test3",
},
},
},
},
},
want: map[string]*statsapi.NetworkStats{
"c1": &statsapi.NetworkStats{
Time: v1.Time{},
InterfaceStats: statsapi.InterfaceStats{
Name: "test",
RxBytes: toP(1),
TxBytes: toP(10),
},
Interfaces: []statsapi.InterfaceStats{
{
Name: "test",
RxBytes: toP(1),
TxBytes: toP(10),
},
},
},
"c2": &statsapi.NetworkStats{
Time: v1.Time{},
InterfaceStats: statsapi.InterfaceStats{
Name: "test2",
RxBytes: toP(2),
TxBytes: toP(20),
},
Interfaces: []statsapi.InterfaceStats{
{
Name: "test2",
RxBytes: toP(2),
TxBytes: toP(20),
},
},
},
"c3": &statsapi.NetworkStats{
Time: v1.Time{},
InterfaceStats: statsapi.InterfaceStats{
Name: "test2",
RxBytes: toP(3),
TxBytes: toP(30),
},
Interfaces: []statsapi.InterfaceStats{
{
Name: "test2",
RxBytes: toP(3),
TxBytes: toP(30),
},
},
},
},
wantErr: false,
},
{
name: "multiple stats instances of same interface only picks up first",
fields: fakehcsshim{
containers: []containerStats{
{
container: hcsshim.ContainerProperties{
ID: "c1",
}, hcsStats: []hcsshim.NetworkStats{
{
BytesReceived: 1,
BytesSent: 10,
EndpointId: "test",
InstanceId: "test",
},
{
BytesReceived: 3,
BytesSent: 30,
EndpointId: "test",
InstanceId: "test3",
},
},
},
{
container: hcsshim.ContainerProperties{
ID: "c2",
}, hcsStats: []hcsshim.NetworkStats{
{
BytesReceived: 2,
BytesSent: 20,
EndpointId: "test2",
InstanceId: "test2",
},
},
},
},
},
want: map[string]*statsapi.NetworkStats{
"c1": &statsapi.NetworkStats{
Time: v1.Time{},
InterfaceStats: statsapi.InterfaceStats{
Name: "test",
RxBytes: toP(1),
TxBytes: toP(10),
},
Interfaces: []statsapi.InterfaceStats{
{
Name: "test",
RxBytes: toP(1),
TxBytes: toP(10),
},
},
},
"c2": &statsapi.NetworkStats{
Time: v1.Time{},
InterfaceStats: statsapi.InterfaceStats{
Name: "test2",
RxBytes: toP(2),
TxBytes: toP(20),
},
Interfaces: []statsapi.InterfaceStats{
{
Name: "test2",
RxBytes: toP(2),
TxBytes: toP(20),
},
},
},
},
wantErr: false,
},
{
name: "multiple endpoints per container",
fields: fakehcsshim{
containers: []containerStats{
{
container: hcsshim.ContainerProperties{
ID: "c1",
}, hcsStats: []hcsshim.NetworkStats{
{
BytesReceived: 1,
BytesSent: 10,
EndpointId: "test",
InstanceId: "test",
},
{
BytesReceived: 3,
BytesSent: 30,
EndpointId: "test3",
InstanceId: "test3",
},
},
},
{
container: hcsshim.ContainerProperties{
ID: "c2",
}, hcsStats: []hcsshim.NetworkStats{
{
BytesReceived: 2,
BytesSent: 20,
EndpointId: "test2",
InstanceId: "test2",
},
},
},
},
},
want: map[string]*statsapi.NetworkStats{
"c1": &statsapi.NetworkStats{
Time: v1.Time{},
InterfaceStats: statsapi.InterfaceStats{
Name: "test",
RxBytes: toP(1),
TxBytes: toP(10),
},
Interfaces: []statsapi.InterfaceStats{
{
Name: "test",
RxBytes: toP(1),
TxBytes: toP(10),
},
{
Name: "test3",
RxBytes: toP(3),
TxBytes: toP(30),
},
},
},
"c2": &statsapi.NetworkStats{
Time: v1.Time{},
InterfaceStats: statsapi.InterfaceStats{
Name: "test2",
RxBytes: toP(2),
TxBytes: toP(20),
},
Interfaces: []statsapi.InterfaceStats{
{
Name: "test2",
RxBytes: toP(2),
TxBytes: toP(20),
},
},
},
},
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
p := &criStatsProvider{
hcsshimInterface: fakehcsshim{
containers: tt.fields.containers,
},
}
got, err := p.listContainerNetworkStats()
if (err != nil) != tt.wantErr {
t.Errorf("listContainerNetworkStats() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("listContainerNetworkStats() got = %v, want %v", got, tt.want)
}
})
}
}
func toP(i uint64) *uint64 {
return &i
}