Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

unittests: Fixes unit tests for Windows (part 3) #110403

Merged
merged 1 commit into from Oct 31, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 2 additions & 2 deletions pkg/kubelet/cm/devicemanager/manager_test.go
Expand Up @@ -81,8 +81,8 @@ func tmpSocketDir() (socketDir, socketName, pluginSocketName string, err error)
if err != nil {
return
}
socketName = socketDir + "/server.sock"
pluginSocketName = socketDir + "/device-plugin.sock"
socketName = filepath.Join(socketDir, "server.sock")
pluginSocketName = filepath.Join(socketDir, "device-plugin.sock")
os.MkdirAll(socketDir, 0755)
return
}
Expand Down
46 changes: 24 additions & 22 deletions pkg/kubelet/pluginmanager/pluginwatcher/plugin_watcher_test.go
Expand Up @@ -20,6 +20,7 @@ import (
"flag"
"fmt"
"os"
"path/filepath"
"sync"
"testing"
"time"
Expand All @@ -32,8 +33,6 @@ import (
)

var (
socketDir string

supportedVersions = []string{"v1beta1", "v1beta2"}
)

Expand All @@ -44,18 +43,17 @@ func init() {
flag.Set("alsologtostderr", fmt.Sprintf("%t", true))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The init() routine in general seems odd. I wonder if we can remove it (in a separate PR).

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO, it is pretty useful. It increases the log verbosity, which would make debugging there plugin tests easier, there would be more useful logs collected, especially since there were some issues with plugins before.

flag.StringVar(&logLevel, "logLevel", "6", "test")
flag.Lookup("v").Value.Set(logLevel)
}

func initTempDir(t *testing.T) string {
// Creating a different directory. os.RemoveAll is not atomic enough;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any documentation about why this happens? - Can we reference it here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't have any documentation about why this happens, just empirical evidence.

Basically, the tests would fail with something like this:

    plugin_watcher_test.go:272:
                Error Trace:    plugin_watcher_test.go:272
                                                        plugin_watcher_test.go:182
                Error:          Received unexpected error:
                                error (re-)creating root C:\Users\capi\AppData\Local\Temp\plugin_test1745122989: mk
dir C:\Users\capi\AppData\Local\Temp\plugin_test1745122989: Access is denied.
                Test:           TestPluginReRegistration
    plugin_watcher_test.go:59:
                Error Trace:    plugin_watcher_test.go:59
                                                        panic.go:482
                                                        testing.go:864
                                                        plugin_watcher_test.go:272
                                                        plugin_watcher_test.go:182
                Error:          Received unexpected error:
                                CreateFile C:\Users\capi\AppData\Local\Temp\plugin_test1745122989: Access is denied
.
                Test:           TestPluginReRegistration

Basically, it failed while trying to run os.MkdirAll on a folder that was just removed with os.RemoveAll. This can become more evident if we keep the cleanup function, adding an additional require.NoError for the os.MkdirAll call:

func cleanup(t *testing.T) {
        require.NoError(t, os.RemoveAll(socketDir))
        require.NoError(t, os.MkdirAll(socketDir, 0755))
}

With the above, the failure now becomes:

    plugin_watcher_test.go:60:
                Error Trace:    plugin_watcher_test.go:60
                                                        plugin_watcher_test.go:173
                Error:          Received unexpected error:
                                mkdir C:\Users\capi\AppData\Local\Temp\plugin_test771705996: Access is denied.
                Test:           TestPluginRegistrationSameName

But, if we introduce a time.Sleep(time.Second) in between the 2 operations, there are no errors anymore.

--- PASS: TestPluginRegistration (6.67s)
--- PASS: TestPluginRegistrationSameName (6.10s)
--- PASS: TestPluginReRegistration (11.22s)

Plus, having separate folders per test would be useful if we do need to run tests in parallel.

// os.MkdirAll can get into an "Access Denied" error on Windows.
d, err := os.MkdirTemp("", "plugin_test")
if err != nil {
panic(fmt.Sprintf("Could not create a temp directory: %s", d))
t.Fatalf("Could not create a temp directory %s: %v", d, err)
}

socketDir = d
}

func cleanup(t *testing.T) {
require.NoError(t, os.RemoveAll(socketDir))
os.MkdirAll(socketDir, 0755)
return d
}

func waitForRegistration(
Expand Down Expand Up @@ -106,13 +104,14 @@ func retryWithExponentialBackOff(initialDuration time.Duration, fn wait.Conditio
}

func TestPluginRegistration(t *testing.T) {
defer cleanup(t)
socketDir := initTempDir(t)
defer os.RemoveAll(socketDir)

dsw := cache.NewDesiredStateOfWorld()
newWatcher(t, dsw, wait.NeverStop)
newWatcher(t, socketDir, dsw, wait.NeverStop)

for i := 0; i < 10; i++ {
socketPath := fmt.Sprintf("%s/plugin-%d.sock", socketDir, i)
socketPath := filepath.Join(socketDir, fmt.Sprintf("plugin-%d.sock", i))
pluginName := fmt.Sprintf("example-plugin-%d", i)

p := NewTestExamplePlugin(pluginName, registerapi.DevicePlugin, socketPath, supportedVersions...)
Expand All @@ -139,16 +138,17 @@ func TestPluginRegistration(t *testing.T) {
}

func TestPluginRegistrationSameName(t *testing.T) {
defer cleanup(t)
socketDir := initTempDir(t)
defer os.RemoveAll(socketDir)

dsw := cache.NewDesiredStateOfWorld()
newWatcher(t, dsw, wait.NeverStop)
newWatcher(t, socketDir, dsw, wait.NeverStop)

// Make 10 plugins with the same name and same type but different socket path;
// all 10 should be in desired state of world cache
pluginName := "dep-example-plugin"
for i := 0; i < 10; i++ {
socketPath := fmt.Sprintf("%s/plugin-%d.sock", socketDir, i)
socketPath := filepath.Join(socketDir, fmt.Sprintf("plugin-%d.sock", i))
p := NewTestExamplePlugin(pluginName, registerapi.DevicePlugin, socketPath, supportedVersions...)
require.NoError(t, p.Serve("v1beta1", "v1beta2"))

Expand All @@ -164,14 +164,15 @@ func TestPluginRegistrationSameName(t *testing.T) {
}

func TestPluginReRegistration(t *testing.T) {
defer cleanup(t)
socketDir := initTempDir(t)
defer os.RemoveAll(socketDir)

dsw := cache.NewDesiredStateOfWorld()
newWatcher(t, dsw, wait.NeverStop)
newWatcher(t, socketDir, dsw, wait.NeverStop)

// Create a plugin first, we are then going to remove the plugin, update the plugin with a different name
// and recreate it.
socketPath := fmt.Sprintf("%s/plugin-reregistration.sock", socketDir)
socketPath := filepath.Join(socketDir, "plugin-reregistration.sock")
pluginName := "reregister-plugin"
p := NewTestExamplePlugin(pluginName, registerapi.DevicePlugin, socketPath, supportedVersions...)
require.NoError(t, p.Serve("v1beta1", "v1beta2"))
Expand Down Expand Up @@ -206,12 +207,13 @@ func TestPluginReRegistration(t *testing.T) {
}

func TestPluginRegistrationAtKubeletStart(t *testing.T) {
defer cleanup(t)
socketDir := initTempDir(t)
defer os.RemoveAll(socketDir)

plugins := make([]*examplePlugin, 10)

for i := 0; i < len(plugins); i++ {
socketPath := fmt.Sprintf("%s/plugin-%d.sock", socketDir, i)
socketPath := filepath.Join(socketDir, fmt.Sprintf("plugin-%d.sock", i))
pluginName := fmt.Sprintf("example-plugin-%d", i)

p := NewTestExamplePlugin(pluginName, registerapi.DevicePlugin, socketPath, supportedVersions...)
Expand All @@ -224,7 +226,7 @@ func TestPluginRegistrationAtKubeletStart(t *testing.T) {
}

dsw := cache.NewDesiredStateOfWorld()
newWatcher(t, dsw, wait.NeverStop)
newWatcher(t, socketDir, dsw, wait.NeverStop)

var wg sync.WaitGroup
for i := 0; i < len(plugins); i++ {
Expand Down Expand Up @@ -252,7 +254,7 @@ func TestPluginRegistrationAtKubeletStart(t *testing.T) {
}
}

func newWatcher(t *testing.T, desiredStateOfWorldCache cache.DesiredStateOfWorld, stopCh <-chan struct{}) *Watcher {
func newWatcher(t *testing.T, socketDir string, desiredStateOfWorldCache cache.DesiredStateOfWorld, stopCh <-chan struct{}) *Watcher {
w := NewWatcher(socketDir, desiredStateOfWorldCache)
require.NoError(t, w.Start(stopCh))

Expand Down
7 changes: 4 additions & 3 deletions pkg/kubelet/pluginmanager/reconciler/reconciler_test.go
Expand Up @@ -19,6 +19,7 @@ package reconciler
import (
"fmt"
"os"
"path/filepath"
"testing"
"time"

Expand Down Expand Up @@ -187,7 +188,7 @@ func Test_Run_Positive_Register(t *testing.T) {
stopChan := make(chan struct{})
defer close(stopChan)
go reconciler.Run(stopChan)
socketPath := fmt.Sprintf("%s/plugin.sock", socketDir)
socketPath := filepath.Join(socketDir, "plugin.sock")
pluginName := fmt.Sprintf("example-plugin")
p := pluginwatcher.NewTestExamplePlugin(pluginName, registerapi.DevicePlugin, socketPath, supportedVersions...)
require.NoError(t, p.Serve("v1beta1", "v1beta2"))
Expand Down Expand Up @@ -233,7 +234,7 @@ func Test_Run_Positive_RegisterThenUnregister(t *testing.T) {
defer close(stopChan)
go reconciler.Run(stopChan)

socketPath := fmt.Sprintf("%s/plugin.sock", socketDir)
socketPath := filepath.Join(socketDir, "plugin.sock")
pluginName := fmt.Sprintf("example-plugin")
p := pluginwatcher.NewTestExamplePlugin(pluginName, registerapi.DevicePlugin, socketPath, supportedVersions...)
require.NoError(t, p.Serve("v1beta1", "v1beta2"))
Expand Down Expand Up @@ -289,7 +290,7 @@ func Test_Run_Positive_ReRegister(t *testing.T) {
defer close(stopChan)
go reconciler.Run(stopChan)

socketPath := fmt.Sprintf("%s/plugin2.sock", socketDir)
socketPath := filepath.Join(socketDir, "plugin2.sock")
pluginName := fmt.Sprintf("example-plugin2")
p := pluginwatcher.NewTestExamplePlugin(pluginName, registerapi.DevicePlugin, socketPath, supportedVersions...)
require.NoError(t, p.Serve("v1beta1", "v1beta2"))
Expand Down
9 changes: 8 additions & 1 deletion pkg/proxy/apis/config/validation/validation_test.go
Expand Up @@ -427,6 +427,11 @@ func TestValidateKubeProxyConfiguration(t *testing.T) {
}

for name, testCase := range testCases {
if runtime.GOOS == "windows" && testCase.config.Mode == kubeproxyconfig.ProxyModeIPVS {
// IPVS is not supported on Windows.
t.Log("Skipping test on Windows: ", name)
continue
}
t.Run(name, func(t *testing.T) {
errs := Validate(&testCase.config)
if len(testCase.expectedErrs) != len(errs) {
Expand Down Expand Up @@ -694,9 +699,11 @@ func TestValidateKubeProxyConntrackConfiguration(t *testing.T) {
func TestValidateProxyMode(t *testing.T) {
newPath := field.NewPath("KubeProxyConfiguration")
successCases := []kubeproxyconfig.ProxyMode{""}
expectedNonExistentErrorMsg := "must be iptables,ipvs or blank (blank means the best-available proxy [currently iptables])"

if runtime.GOOS == "windows" {
successCases = append(successCases, kubeproxyconfig.ProxyModeKernelspace)
expectedNonExistentErrorMsg = "must be kernelspace or blank (blank means the most-available proxy [currently kernelspace])"
} else {
successCases = append(successCases, kubeproxyconfig.ProxyModeIPTables, kubeproxyconfig.ProxyModeIPVS)
}
Expand All @@ -717,7 +724,7 @@ func TestValidateProxyMode(t *testing.T) {
},
"invalid mode non-existent": {
mode: kubeproxyconfig.ProxyMode("non-existing"),
expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ProxyMode"), "non-existing", "must be iptables,ipvs or blank (blank means the best-available proxy [currently iptables])")},
expectedErrs: field.ErrorList{field.Invalid(newPath.Child("ProxyMode"), "non-existing", expectedNonExistentErrorMsg)},
},
}
for _, testCase := range testCases {
Expand Down
4 changes: 2 additions & 2 deletions pkg/proxy/winkernel/hns_test.go
Expand Up @@ -132,11 +132,11 @@ func TestGetEndpointByIpAddressAndName(t *testing.T) {
t.Errorf("%v does not match %v", endpoint.ip, Endpoint.IpConfigurations[0].IpAddress)
}

endpoint, err = hns.getEndpointByName(Endpoint.Name)
endpoint2, err := hns.getEndpointByName(Endpoint.Name)
if err != nil {
t.Error(err)
}
diff := cmp.Diff(endpoint, Endpoint)
diff := cmp.Diff(endpoint, endpoint2)
if diff != "" {
t.Errorf("getEndpointByName(%s) returned a different endpoint. Diff: %s ", Endpoint.Name, diff)
}
Expand Down
25 changes: 13 additions & 12 deletions pkg/proxy/winkernel/proxier_test.go
Expand Up @@ -138,18 +138,19 @@ func NewFakeProxier(syncPeriod time.Duration, minSyncPeriod time.Duration, clust
networkType: networkType,
}
proxier := &Proxier{
serviceMap: make(proxy.ServiceMap),
endpointsMap: make(proxy.EndpointsMap),
clusterCIDR: clusterCIDR,
hostname: testHostName,
nodeIP: nodeIP,
serviceHealthServer: healthcheck.NewFakeServiceHealthServer(),
network: *hnsNetworkInfo,
sourceVip: sourceVip,
hostMac: macAddress,
isDSR: false,
hns: newFakeHNS(),
endPointsRefCount: make(endPointsReferenceCountMap),
serviceMap: make(proxy.ServiceMap),
endpointsMap: make(proxy.EndpointsMap),
clusterCIDR: clusterCIDR,
hostname: testHostName,
nodeIP: nodeIP,
serviceHealthServer: healthcheck.NewFakeServiceHealthServer(),
network: *hnsNetworkInfo,
sourceVip: sourceVip,
hostMac: macAddress,
isDSR: false,
hns: newFakeHNS(),
endPointsRefCount: make(endPointsReferenceCountMap),
forwardHealthCheckVip: true,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@daschott @sbangari fyi - this looks to fix the kubeproxy tests we were discussing

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like the fowardHealthCheckVip option is configurable. Is it that there are test cases that require this flag to be set, or that this is a required field for all cases?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a test that requires this field set to True:

=== RUN   TestCreateDsrLoadBalancer
I1018 12:48:45.146328    3172 proxier.go:1160] "Updating network to check for new remote subnet policies" networkName
="TestNetwork"
I1018 12:48:45.146328    3172 proxier.go:1218] "Endpoint on overlay network" ip="192.168.2.3" hnsNetworkName="TestNet
work" isNodeIP=false isPublicIP=false
    proxier_test.go:788: The Hns Loadbalancer HealthCheck Id  does not match 123ABC. ServicePortName "ns1/svc1:p80"
--- FAIL: TestCreateDsrLoadBalancer (0.00s)

Setting it to true results in the test passing. With that flag, an HNS Health Check LoadBalancer resource is created.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We end up setting this to True for all test cases. Is there any value in testing with it set to False?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as forwardHealthCheckVip goes, not at this moment. This is only used in 2 cases, and there's no else for it. There won't be any additional test coverage if we have this set to False.

}

serviceChanges := proxy.NewServiceChangeTracker(proxier.newServiceInfo, v1.IPv4Protocol, nil, proxier.serviceMapChange)
Expand Down
26 changes: 26 additions & 0 deletions pkg/routes/const_other.go
@@ -0,0 +1,26 @@
//go:build !windows
// +build !windows

/*
Copyright 2022 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package routes

import "syscall"

const (
fileNameTooLong = syscall.ENAMETOOLONG
)
23 changes: 23 additions & 0 deletions pkg/routes/const_windows.go
@@ -0,0 +1,23 @@
/*
Copyright 2022 The Kubernetes Authors.

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/

package routes

import "golang.org/x/sys/windows"

const (
fileNameTooLong = windows.ERROR_FILENAME_EXCED_RANGE
)
3 changes: 1 addition & 2 deletions pkg/routes/logs.go
Expand Up @@ -20,7 +20,6 @@ import (
"net/http"
"os"
"path"
"syscall"

"github.com/emicklei/go-restful/v3"
)
Expand Down Expand Up @@ -63,7 +62,7 @@ func logFileListHandler(req *restful.Request, resp *restful.Response) {
func logFileNameIsTooLong(filePath string) bool {
_, err := os.Stat(filePath)
if err != nil {
if e, ok := err.(*os.PathError); ok && e.Err == syscall.ENAMETOOLONG {
if e, ok := err.(*os.PathError); ok && e.Err == fileNameTooLong {
return true
}
}
Expand Down
14 changes: 12 additions & 2 deletions pkg/routes/logs_test.go
Expand Up @@ -19,11 +19,13 @@ package routes
import (
"fmt"
"os"
"path/filepath"
"testing"
)

func TestPreCheckLogFileNameLength(t *testing.T) {
oversizeFileName := fmt.Sprintf("%0256s", "a")
// In windows, with long file name support enabled, file names can have up to 32,767 characters.
oversizeFileName := fmt.Sprintf("%032768s", "a")
normalFileName := fmt.Sprintf("%0255s", "a")

// check file with oversize name.
Expand All @@ -37,11 +39,19 @@ func TestPreCheckLogFileNameLength(t *testing.T) {
}

// check file with normal name which does exist.
_, err := os.Create(normalFileName)
dir, err := os.MkdirTemp("", "logs")
if err != nil {
t.Fatal("failed to create temp dir")
}
defer os.RemoveAll(dir)

normalFileName = filepath.Join(dir, normalFileName)
f, err := os.Create(normalFileName)
if err != nil {
t.Error("failed to create test file")
}
defer os.Remove(normalFileName)
defer f.Close()
if logFileNameIsTooLong(normalFileName) {
t.Error("failed to check normal filename")
}
Expand Down
8 changes: 7 additions & 1 deletion pkg/scheduler/internal/queue/scheduling_queue_test.go
Expand Up @@ -1957,8 +1957,14 @@ func TestMoveAllToActiveOrBackoffQueue_PreEnqueueChecks(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
q := NewTestQueue(ctx, newDefaultQueueSort())
for _, podInfo := range tt.podInfos {
for i, podInfo := range tt.podInfos {
q.AddUnschedulableIfNotPresent(podInfo, q.schedulingCycle)
// NOTE: On Windows, time.Now() is not as precise, 2 consecutive calls may return the same timestamp,
// resulting in 0 time delta / latency. This will cause the pods to be backed off in a random
// order, which would cause this test to fail, since the expectation is for them to be backed off
// in a certain order.
// See: https://github.com/golang/go/issues/8687
podInfo.Timestamp = podInfo.Timestamp.Add(time.Duration((i - len(tt.podInfos))) * time.Millisecond)
}
q.MoveAllToActiveOrBackoffQueue(TestEvent, tt.preEnqueueCheck)
var got []string
Expand Down
3 changes: 2 additions & 1 deletion pkg/util/removeall/removeall_test.go
Expand Up @@ -20,6 +20,7 @@ import (
"errors"
"os"
"path"
"path/filepath"
"strings"
"testing"

Expand All @@ -33,7 +34,7 @@ type fakeMounter struct {

// IsLikelyNotMountPoint overrides mount.FakeMounter.IsLikelyNotMountPoint for our use.
func (f *fakeMounter) IsLikelyNotMountPoint(file string) (bool, error) {
name := path.Base(file)
name := filepath.Base(file)
if strings.HasPrefix(name, "mount") {
return false, nil
}
Expand Down