mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-19 18:02:01 +00:00
Merge pull request #110566 from claudiubelu/unittests-5
Adds Pod DNS Policies support for Windows pods
This commit is contained in:
commit
37937bb227
@ -60,6 +60,7 @@ const (
|
||||
// Configurer is used for setting up DNS resolver configuration when launching pods.
|
||||
type Configurer struct {
|
||||
recorder record.EventRecorder
|
||||
getHostDNSConfig func(string) (*runtimeapi.DNSConfig, error)
|
||||
nodeRef *v1.ObjectReference
|
||||
nodeIPs []net.IP
|
||||
|
||||
@ -77,6 +78,7 @@ type Configurer struct {
|
||||
func NewConfigurer(recorder record.EventRecorder, nodeRef *v1.ObjectReference, nodeIPs []net.IP, clusterDNS []net.IP, clusterDomain, resolverConfig string) *Configurer {
|
||||
return &Configurer{
|
||||
recorder: recorder,
|
||||
getHostDNSConfig: getHostDNSConfig,
|
||||
nodeRef: nodeRef,
|
||||
nodeIPs: nodeIPs,
|
||||
clusterDNS: clusterDNS,
|
||||
@ -279,28 +281,6 @@ func parseResolvConf(reader io.Reader) (nameservers []string, searches []string,
|
||||
return nameservers, searches, options, utilerrors.NewAggregate(allErrors)
|
||||
}
|
||||
|
||||
func (c *Configurer) getHostDNSConfig() (*runtimeapi.DNSConfig, error) {
|
||||
var hostDNS, hostSearch, hostOptions []string
|
||||
// Get host DNS settings
|
||||
if c.ResolverConfig != "" {
|
||||
f, err := os.Open(c.ResolverConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
hostDNS, hostSearch, hostOptions, err = parseResolvConf(f)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &runtimeapi.DNSConfig{
|
||||
Servers: hostDNS,
|
||||
Searches: hostSearch,
|
||||
Options: hostOptions,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getPodDNSType(pod *v1.Pod) (podDNSType, error) {
|
||||
dnsPolicy := pod.Spec.DNSPolicy
|
||||
switch dnsPolicy {
|
||||
@ -384,7 +364,7 @@ func appendDNSConfig(existingDNSConfig *runtimeapi.DNSConfig, dnsConfig *v1.PodD
|
||||
|
||||
// GetPodDNS returns DNS settings for the pod.
|
||||
func (c *Configurer) GetPodDNS(pod *v1.Pod) (*runtimeapi.DNSConfig, error) {
|
||||
dnsConfig, err := c.getHostDNSConfig()
|
||||
dnsConfig, err := c.getHostDNSConfig(c.ResolverConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
53
pkg/kubelet/network/dns/dns_other.go
Normal file
53
pkg/kubelet/network/dns/dns_other.go
Normal file
@ -0,0 +1,53 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
/*
|
||||
Copyright 2023 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 dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
func getHostDNSConfig(resolverConfig string) (*runtimeapi.DNSConfig, error) {
|
||||
var hostDNS, hostSearch, hostOptions []string
|
||||
// Get host DNS settings
|
||||
if resolverConfig != "" {
|
||||
f, err := os.Open(resolverConfig)
|
||||
if err != nil {
|
||||
klog.ErrorS(err, "Could not open resolv conf file.")
|
||||
return nil, err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
hostDNS, hostSearch, hostOptions, err = parseResolvConf(f)
|
||||
if err != nil {
|
||||
err := fmt.Errorf("Encountered error while parsing resolv conf file. Error: %w", err)
|
||||
klog.ErrorS(err, "Could not parse resolv conf file.")
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &runtimeapi.DNSConfig{
|
||||
Servers: hostDNS,
|
||||
Searches: hostSearch,
|
||||
Options: hostOptions,
|
||||
}, nil
|
||||
}
|
57
pkg/kubelet/network/dns/dns_other_test.go
Normal file
57
pkg/kubelet/network/dns/dns_other_test.go
Normal file
@ -0,0 +1,57 @@
|
||||
//go:build !windows
|
||||
// +build !windows
|
||||
|
||||
/*
|
||||
Copyright 2023 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 dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultResolvConf = "/etc/resolv.conf"
|
||||
// configurer.getHostDNSConfig is faked on Windows, while it is not faked on Linux.
|
||||
fakeGetHostDNSConfigCustom = getHostDNSConfig
|
||||
)
|
||||
|
||||
// getResolvConf returns a temporary resolv.conf file containing the testHostNameserver nameserver and
|
||||
// testHostDomain search field, and a cleanup function for the temporary file.
|
||||
func getResolvConf(t *testing.T) (string, func()) {
|
||||
resolvConfContent := []byte(fmt.Sprintf("nameserver %s\nsearch %s\n", testHostNameserver, testHostDomain))
|
||||
tmpfile, err := os.CreateTemp("", "tmpResolvConf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
cleanup := func() {
|
||||
os.Remove(tmpfile.Name())
|
||||
}
|
||||
|
||||
if _, err := tmpfile.Write(resolvConfContent); err != nil {
|
||||
cleanup()
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := tmpfile.Close(); err != nil {
|
||||
cleanup()
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
return tmpfile.Name(), cleanup
|
||||
}
|
@ -19,8 +19,6 @@ package dns
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
goruntime "runtime"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
@ -42,6 +40,9 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
// testHostNameserver and testHostDomain are also being used in dns_windows_test.go.
|
||||
testHostNameserver = "8.8.8.8"
|
||||
testHostDomain = "host.domain"
|
||||
fetchEvent = func(recorder *record.FakeRecorder) string {
|
||||
select {
|
||||
case event := <-recorder.Events:
|
||||
@ -457,26 +458,19 @@ func TestGetPodDNS(t *testing.T) {
|
||||
testCases := []struct {
|
||||
desc string
|
||||
expandedDNSConfig bool
|
||||
skipOnWindows bool
|
||||
}{
|
||||
{
|
||||
desc: "Not ExpandedDNSConfig",
|
||||
expandedDNSConfig: false,
|
||||
skipOnWindows: true,
|
||||
},
|
||||
{
|
||||
desc: "ExpandedDNSConfig",
|
||||
expandedDNSConfig: true,
|
||||
skipOnWindows: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.desc, func(t *testing.T) {
|
||||
// Skip tests that fail on Windows, as discussed during the SIG Testing meeting from January 10, 2023
|
||||
if tc.skipOnWindows && goruntime.GOOS == "windows" {
|
||||
t.Skip("Skipping test that fails on Windows")
|
||||
}
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, features.ExpandedDNSConfig, tc.expandedDNSConfig)()
|
||||
testGetPodDNS(t)
|
||||
})
|
||||
@ -541,8 +535,7 @@ func testGetPodDNS(t *testing.T) {
|
||||
t.Errorf("expected search \".\", got %+v", options[3].DNSSearch)
|
||||
}
|
||||
|
||||
testResolverConfig := "/etc/resolv.conf"
|
||||
configurer = NewConfigurer(recorder, nodeRef, nil, testClusterDNS, testClusterDNSDomain, testResolverConfig)
|
||||
configurer = NewConfigurer(recorder, nodeRef, nil, testClusterDNS, testClusterDNSDomain, defaultResolvConf)
|
||||
for i, pod := range pods {
|
||||
var err error
|
||||
dnsConfig, err := configurer.GetPodDNS(pod)
|
||||
@ -599,8 +592,6 @@ func TestGetPodDNSCustom(t *testing.T) {
|
||||
testSvcDomain := fmt.Sprintf("svc.%s", testClusterDNSDomain)
|
||||
testNsSvcDomain := fmt.Sprintf("%s.svc.%s", testPodNamespace, testClusterDNSDomain)
|
||||
testNdotsOptionValue := "3"
|
||||
testHostNameserver := "8.8.8.8"
|
||||
testHostDomain := "host.domain"
|
||||
|
||||
testPod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
@ -609,20 +600,11 @@ func TestGetPodDNSCustom(t *testing.T) {
|
||||
},
|
||||
}
|
||||
|
||||
resolvConfContent := []byte(fmt.Sprintf("nameserver %s\nsearch %s\n", testHostNameserver, testHostDomain))
|
||||
tmpfile, err := os.CreateTemp("", "tmpResolvConf")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer os.Remove(tmpfile.Name())
|
||||
if _, err := tmpfile.Write(resolvConfContent); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := tmpfile.Close(); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
resolvConf, cleanup := getResolvConf(t)
|
||||
defer cleanup()
|
||||
|
||||
configurer := NewConfigurer(recorder, nodeRef, nil, []net.IP{netutils.ParseIPSloppy(testClusterNameserver)}, testClusterDNSDomain, tmpfile.Name())
|
||||
configurer := NewConfigurer(recorder, nodeRef, nil, []net.IP{netutils.ParseIPSloppy(testClusterNameserver)}, testClusterDNSDomain, resolvConf)
|
||||
configurer.getHostDNSConfig = fakeGetHostDNSConfigCustom
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
|
199
pkg/kubelet/network/dns/dns_windows.go
Normal file
199
pkg/kubelet/network/dns/dns_windows.go
Normal file
@ -0,0 +1,199 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
/*
|
||||
Copyright 2023 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 dns
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"syscall"
|
||||
"unsafe"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
"golang.org/x/sys/windows/registry"
|
||||
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
netRegistry = `System\CurrentControlSet\Services\TCPIP\Parameters`
|
||||
netIfacesRegistry = `System\CurrentControlSet\Services\TCPIP\Parameters\Interfaces`
|
||||
maxHostnameLen = 128
|
||||
maxDomainNameLen = 128
|
||||
maxScopeIDLen = 256
|
||||
|
||||
// getHostDNSConfig will return the host's DNS configuration if the given resolverConfig argument
|
||||
// is set to "Host".
|
||||
hostResolvConf = "Host"
|
||||
)
|
||||
|
||||
// FixedInfo information: https://docs.microsoft.com/en-us/windows/win32/api/iptypes/ns-iptypes-fixed_info_w2ksp1
|
||||
type FixedInfo struct {
|
||||
HostName [maxHostnameLen + 4]byte
|
||||
DomainName [maxDomainNameLen + 4]byte
|
||||
CurrentDNSServer *syscall.IpAddrString
|
||||
DNSServerList syscall.IpAddrString
|
||||
NodeType uint32
|
||||
ScopeID [maxScopeIDLen + 4]byte
|
||||
EnableRouting uint32
|
||||
EnableProxy uint32
|
||||
EnableDNS uint32
|
||||
}
|
||||
|
||||
var (
|
||||
// GetNetworkParams can be found in iphlpapi.dll
|
||||
// see: https://docs.microsoft.com/windows/win32/api/iphlpapi/nf-iphlpapi-getnetworkparams
|
||||
iphlpapidll = windows.MustLoadDLL("iphlpapi.dll")
|
||||
procGetNetworkParams = iphlpapidll.MustFindProc("GetNetworkParams")
|
||||
)
|
||||
|
||||
func getHostDNSConfig(resolverConfig string) (*runtimeapi.DNSConfig, error) {
|
||||
if resolverConfig != "" && resolverConfig != hostResolvConf {
|
||||
err := fmt.Errorf(`Unexpected resolver config value: "%s". Expected "" or "%s".`, resolverConfig, hostResolvConf)
|
||||
klog.ErrorS(err, "Cannot get host DNS Configuration.")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var (
|
||||
hostDNS, hostSearch []string
|
||||
err error
|
||||
)
|
||||
// Get host DNS settings
|
||||
if resolverConfig == hostResolvConf {
|
||||
hostDNS, err = getDNSServerList()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Could not get the host's DNS Server List. Error: %w", err)
|
||||
klog.ErrorS(err, "Encountered error while getting host's DNS Server List.")
|
||||
return nil, err
|
||||
}
|
||||
hostSearch, err = getDNSSuffixList()
|
||||
if err != nil {
|
||||
err = fmt.Errorf("Could not get the host's DNS Suffix List. Error: %w", err)
|
||||
klog.ErrorS(err, "Encountered error while getting host's DNS Suffix List.")
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return &runtimeapi.DNSConfig{
|
||||
Servers: hostDNS,
|
||||
Searches: hostSearch,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func elemInList(elem string, list []string) bool {
|
||||
for _, e := range list {
|
||||
if e == elem {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func getRegistryStringValue(reg, key string) (string, error) {
|
||||
regKey, err := registry.OpenKey(registry.LOCAL_MACHINE, reg, registry.QUERY_VALUE)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
defer regKey.Close()
|
||||
|
||||
regValue, _, err := regKey.GetStringValue(key)
|
||||
return regValue, err
|
||||
}
|
||||
|
||||
// getDNSSuffixList reads DNS config file and returns the list of configured DNS suffixes
|
||||
func getDNSSuffixList() ([]string, error) {
|
||||
// We start with the general suffix list that apply to all network connections.
|
||||
allSuffixes := []string{}
|
||||
suffixes, err := getRegistryStringValue(netRegistry, "SearchList")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
allSuffixes = strings.Split(suffixes, ",")
|
||||
|
||||
// Then we append the network-specific DNS suffix lists.
|
||||
regKey, err := registry.OpenKey(registry.LOCAL_MACHINE, netIfacesRegistry, registry.ENUMERATE_SUB_KEYS)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer regKey.Close()
|
||||
|
||||
ifaces, err := regKey.ReadSubKeyNames(0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, iface := range ifaces {
|
||||
suffixes, err := getRegistryStringValue(fmt.Sprintf("%s\\%s", netIfacesRegistry, iface), "SearchList")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if suffixes == "" {
|
||||
continue
|
||||
}
|
||||
for _, suffix := range strings.Split(suffixes, ",") {
|
||||
if !elemInList(suffix, allSuffixes) {
|
||||
allSuffixes = append(allSuffixes, suffix)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return allSuffixes, nil
|
||||
}
|
||||
|
||||
func getNetworkParams() (*FixedInfo, error) {
|
||||
// We don't know how big we should make the byte buffer, but the call will tell us by
|
||||
// setting the size afterwards.
|
||||
var size int = 1
|
||||
buffer := make([]byte, 1)
|
||||
ret, _, err := procGetNetworkParams.Call(
|
||||
uintptr(unsafe.Pointer(&buffer[0])),
|
||||
uintptr(unsafe.Pointer(&size)),
|
||||
)
|
||||
if ret != uintptr(syscall.ERROR_BUFFER_OVERFLOW) {
|
||||
err = fmt.Errorf("Unexpected return value %d from GetNetworkParams. Expected: %d. Error: %w", ret, syscall.ERROR_BUFFER_OVERFLOW, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
buffer = make([]byte, size)
|
||||
ret, _, err = procGetNetworkParams.Call(
|
||||
uintptr(unsafe.Pointer(&buffer[0])),
|
||||
uintptr(unsafe.Pointer(&size)),
|
||||
)
|
||||
if ret != 0 {
|
||||
err = fmt.Errorf("Unexpected return value %d from GetNetworkParams. Expected: 0. Error: %w", ret, err)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
info := (*FixedInfo)(unsafe.Pointer(&buffer[0]))
|
||||
return info, nil
|
||||
}
|
||||
|
||||
func getDNSServerList() ([]string, error) {
|
||||
dnsServerList := []string{}
|
||||
fixedInfo, err := getNetworkParams()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list := &(fixedInfo.DNSServerList)
|
||||
for list != nil {
|
||||
dnsServer := strings.TrimRight(string(list.IpAddress.String[:]), "\x00")
|
||||
dnsServerList = append(dnsServerList, dnsServer)
|
||||
list = list.Next
|
||||
}
|
||||
return dnsServerList, nil
|
||||
}
|
42
pkg/kubelet/network/dns/dns_windows_test.go
Normal file
42
pkg/kubelet/network/dns/dns_windows_test.go
Normal file
@ -0,0 +1,42 @@
|
||||
//go:build windows
|
||||
// +build windows
|
||||
|
||||
/*
|
||||
Copyright 2023 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 dns
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
runtimeapi "k8s.io/cri-api/pkg/apis/runtime/v1"
|
||||
)
|
||||
|
||||
var (
|
||||
defaultResolvConf = hostResolvConf
|
||||
)
|
||||
|
||||
func fakeGetHostDNSConfigCustom(resolverConfig string) (*runtimeapi.DNSConfig, error) {
|
||||
return &runtimeapi.DNSConfig{
|
||||
Servers: []string{testHostNameserver},
|
||||
Searches: []string{testHostDomain},
|
||||
}, nil
|
||||
}
|
||||
|
||||
// getResolvConf returns the hostResolvConf string, which will be used to get the Host's DNS configuration.
|
||||
func getResolvConf(t *testing.T) (string, func()) {
|
||||
return hostResolvConf, func() {}
|
||||
}
|
Loading…
Reference in New Issue
Block a user