Support Custom Pod DNS in kubelet, gated by feature gate
This commit is contained in:
parent
44b5cf3e12
commit
9f9c721b20
@ -6,11 +6,14 @@ go_library(
|
||||
importpath = "k8s.io/kubernetes/pkg/kubelet/network/dns",
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//pkg/apis/core/validation:go_default_library",
|
||||
"//pkg/features:go_default_library",
|
||||
"//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library",
|
||||
"//pkg/kubelet/container:go_default_library",
|
||||
"//pkg/kubelet/util/format:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/record:go_default_library",
|
||||
],
|
||||
)
|
||||
@ -21,11 +24,14 @@ go_test(
|
||||
importpath = "k8s.io/kubernetes/pkg/kubelet/network/dns",
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//pkg/kubelet/apis/cri/v1alpha1/runtime:go_default_library",
|
||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||
"//vendor/github.com/stretchr/testify/require:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/record:go_default_library",
|
||||
],
|
||||
)
|
||||
|
@ -26,7 +26,10 @@ import (
|
||||
"strings"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/kubernetes/pkg/apis/core/validation"
|
||||
"k8s.io/kubernetes/pkg/features"
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
|
||||
"k8s.io/kubernetes/pkg/kubelet/util/format"
|
||||
@ -39,6 +42,14 @@ var (
|
||||
defaultDNSOptions = []string{"ndots:5"}
|
||||
)
|
||||
|
||||
type podDNSType int
|
||||
|
||||
const (
|
||||
podDNSCluster podDNSType = iota
|
||||
podDNSHost
|
||||
podDNSNone
|
||||
)
|
||||
|
||||
// Configurer is used for setting up DNS resolver configuration when launching pods.
|
||||
type Configurer struct {
|
||||
recorder record.EventRecorder
|
||||
@ -67,37 +78,35 @@ func NewConfigurer(recorder record.EventRecorder, nodeRef *v1.ObjectReference, n
|
||||
}
|
||||
}
|
||||
|
||||
func omitDuplicates(pod *v1.Pod, combinedSearch []string) []string {
|
||||
uniqueDomains := map[string]bool{}
|
||||
func omitDuplicates(strs []string) []string {
|
||||
uniqueStrs := make(map[string]bool)
|
||||
|
||||
for _, dnsDomain := range combinedSearch {
|
||||
if _, exists := uniqueDomains[dnsDomain]; !exists {
|
||||
combinedSearch[len(uniqueDomains)] = dnsDomain
|
||||
uniqueDomains[dnsDomain] = true
|
||||
var ret []string
|
||||
for _, str := range strs {
|
||||
if !uniqueStrs[str] {
|
||||
ret = append(ret, str)
|
||||
uniqueStrs[str] = true
|
||||
}
|
||||
}
|
||||
return combinedSearch[:len(uniqueDomains)]
|
||||
return ret
|
||||
}
|
||||
|
||||
func (c *Configurer) formDNSSearchFitsLimits(pod *v1.Pod, composedSearch []string) []string {
|
||||
// resolver file Search line current limitations
|
||||
resolvSearchLineDNSDomainsLimit := 6
|
||||
resolvSearchLineLenLimit := 255
|
||||
func (c *Configurer) formDNSSearchFitsLimits(composedSearch []string, pod *v1.Pod) []string {
|
||||
limitsExceeded := false
|
||||
|
||||
if len(composedSearch) > resolvSearchLineDNSDomainsLimit {
|
||||
composedSearch = composedSearch[:resolvSearchLineDNSDomainsLimit]
|
||||
if len(composedSearch) > validation.MaxDNSSearchPaths {
|
||||
composedSearch = composedSearch[:validation.MaxDNSSearchPaths]
|
||||
limitsExceeded = true
|
||||
}
|
||||
|
||||
if resolvSearchhLineStrLen := len(strings.Join(composedSearch, " ")); resolvSearchhLineStrLen > resolvSearchLineLenLimit {
|
||||
if resolvSearchLineStrLen := len(strings.Join(composedSearch, " ")); resolvSearchLineStrLen > validation.MaxDNSSearchListChars {
|
||||
cutDomainsNum := 0
|
||||
cutDoaminsLen := 0
|
||||
cutDomainsLen := 0
|
||||
for i := len(composedSearch) - 1; i >= 0; i-- {
|
||||
cutDoaminsLen += len(composedSearch[i]) + 1
|
||||
cutDomainsLen += len(composedSearch[i]) + 1
|
||||
cutDomainsNum++
|
||||
|
||||
if (resolvSearchhLineStrLen - cutDoaminsLen) <= resolvSearchLineLenLimit {
|
||||
if (resolvSearchLineStrLen - cutDomainsLen) <= validation.MaxDNSSearchListChars {
|
||||
break
|
||||
}
|
||||
}
|
||||
@ -107,39 +116,43 @@ func (c *Configurer) formDNSSearchFitsLimits(pod *v1.Pod, composedSearch []strin
|
||||
}
|
||||
|
||||
if limitsExceeded {
|
||||
log := fmt.Sprintf("Search Line limits were exceeded, some dns names have been omitted, the applied search line is: %s", strings.Join(composedSearch, " "))
|
||||
c.recorder.Event(pod, v1.EventTypeWarning, "DNSSearchForming", log)
|
||||
log := fmt.Sprintf("Search Line limits were exceeded, some search paths have been omitted, the applied search line is: %s", strings.Join(composedSearch, " "))
|
||||
c.recorder.Event(pod, v1.EventTypeWarning, "DNSConfigForming", log)
|
||||
glog.Error(log)
|
||||
}
|
||||
return composedSearch
|
||||
}
|
||||
|
||||
func (c *Configurer) formDNSSearchForDNSDefault(hostSearch []string, pod *v1.Pod) []string {
|
||||
return c.formDNSSearchFitsLimits(pod, hostSearch)
|
||||
func (c *Configurer) formDNSNameserversFitsLimits(nameservers []string, pod *v1.Pod) []string {
|
||||
if len(nameservers) > validation.MaxDNSNameservers {
|
||||
nameservers = nameservers[0:validation.MaxDNSNameservers]
|
||||
log := fmt.Sprintf("Nameserver limits were exceeded, some nameservers have been omitted, the applied nameserver line is: %s", strings.Join(nameservers, " "))
|
||||
c.recorder.Event(pod, v1.EventTypeWarning, "DNSConfigForming", log)
|
||||
glog.Error(log)
|
||||
}
|
||||
return nameservers
|
||||
}
|
||||
|
||||
func (c *Configurer) formDNSSearch(hostSearch []string, pod *v1.Pod) []string {
|
||||
func (c *Configurer) formDNSConfigFitsLimits(dnsConfig *runtimeapi.DNSConfig, pod *v1.Pod) *runtimeapi.DNSConfig {
|
||||
dnsConfig.Servers = c.formDNSNameserversFitsLimits(dnsConfig.Servers, pod)
|
||||
dnsConfig.Searches = c.formDNSSearchFitsLimits(dnsConfig.Searches, pod)
|
||||
return dnsConfig
|
||||
}
|
||||
|
||||
func (c *Configurer) generateSearchesForDNSClusterFirst(hostSearch []string, pod *v1.Pod) []string {
|
||||
if c.ClusterDomain == "" {
|
||||
c.formDNSSearchFitsLimits(pod, hostSearch)
|
||||
return hostSearch
|
||||
}
|
||||
|
||||
nsSvcDomain := fmt.Sprintf("%s.svc.%s", pod.Namespace, c.ClusterDomain)
|
||||
svcDomain := fmt.Sprintf("svc.%s", c.ClusterDomain)
|
||||
dnsSearch := []string{nsSvcDomain, svcDomain, c.ClusterDomain}
|
||||
clusterSearch := []string{nsSvcDomain, svcDomain, c.ClusterDomain}
|
||||
|
||||
combinedSearch := append(dnsSearch, hostSearch...)
|
||||
|
||||
combinedSearch = omitDuplicates(pod, combinedSearch)
|
||||
return c.formDNSSearchFitsLimits(pod, combinedSearch)
|
||||
return omitDuplicates(append(clusterSearch, hostSearch...))
|
||||
}
|
||||
|
||||
// CheckLimitsForResolvConf checks limits in resolv.conf.
|
||||
func (c *Configurer) CheckLimitsForResolvConf() {
|
||||
// resolver file Search line current limitations
|
||||
resolvSearchLineDNSDomainsLimit := 6
|
||||
resolvSearchLineLenLimit := 255
|
||||
|
||||
f, err := os.Open(c.ResolverConfig)
|
||||
if err != nil {
|
||||
c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", err.Error())
|
||||
@ -155,21 +168,21 @@ func (c *Configurer) CheckLimitsForResolvConf() {
|
||||
return
|
||||
}
|
||||
|
||||
domainCntLimit := resolvSearchLineDNSDomainsLimit
|
||||
domainCountLimit := validation.MaxDNSSearchPaths
|
||||
|
||||
if c.ClusterDomain != "" {
|
||||
domainCntLimit -= 3
|
||||
domainCountLimit -= 3
|
||||
}
|
||||
|
||||
if len(hostSearch) > domainCntLimit {
|
||||
log := fmt.Sprintf("Resolv.conf file '%s' contains search line consisting of more than %d domains!", c.ResolverConfig, domainCntLimit)
|
||||
if len(hostSearch) > domainCountLimit {
|
||||
log := fmt.Sprintf("Resolv.conf file '%s' contains search line consisting of more than %d domains!", c.ResolverConfig, domainCountLimit)
|
||||
c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", log)
|
||||
glog.Error("CheckLimitsForResolvConf: " + log)
|
||||
return
|
||||
}
|
||||
|
||||
if len(strings.Join(hostSearch, " ")) > resolvSearchLineLenLimit {
|
||||
log := fmt.Sprintf("Resolv.conf file '%s' contains search line which length is more than allowed %d chars!", c.ResolverConfig, resolvSearchLineLenLimit)
|
||||
if len(strings.Join(hostSearch, " ")) > validation.MaxDNSSearchListChars {
|
||||
log := fmt.Sprintf("Resolv.conf file '%s' contains search line which length is more than allowed %d chars!", c.ResolverConfig, validation.MaxDNSSearchListChars)
|
||||
c.recorder.Event(c.nodeRef, v1.EventTypeWarning, "CheckLimitsForResolvConf", log)
|
||||
glog.Error("CheckLimitsForResolvConf: " + log)
|
||||
return
|
||||
@ -180,7 +193,6 @@ func (c *Configurer) CheckLimitsForResolvConf() {
|
||||
|
||||
// parseResolveConf reads a resolv.conf file from the given reader, and parses
|
||||
// it into nameservers, searches and options, possibly returning an error.
|
||||
// TODO: move to utility package
|
||||
func parseResolvConf(reader io.Reader) (nameservers []string, searches []string, options []string, err error) {
|
||||
file, err := ioutil.ReadAll(reader)
|
||||
if err != nil {
|
||||
@ -218,15 +230,10 @@ func parseResolvConf(reader io.Reader) (nameservers []string, searches []string,
|
||||
}
|
||||
}
|
||||
|
||||
// There used to be code here to scrub DNS for each cloud, but doesn't
|
||||
// make sense anymore since cloudproviders are being factored out.
|
||||
// contact @thockin or @wlan0 for more information
|
||||
|
||||
return nameservers, searches, options, nil
|
||||
}
|
||||
|
||||
// GetPodDNS returns DNS setttings for the pod.
|
||||
func (c *Configurer) GetPodDNS(pod *v1.Pod) (*runtimeapi.DNSConfig, error) {
|
||||
func (c *Configurer) getHostDNSConfig(pod *v1.Pod) (*runtimeapi.DNSConfig, error) {
|
||||
var hostDNS, hostSearch, hostOptions []string
|
||||
// Get host DNS settings
|
||||
if c.ResolverConfig != "" {
|
||||
@ -241,19 +248,117 @@ func (c *Configurer) GetPodDNS(pod *v1.Pod) (*runtimeapi.DNSConfig, error) {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
useClusterFirstPolicy := ((pod.Spec.DNSPolicy == v1.DNSClusterFirst && !kubecontainer.IsHostNetworkPod(pod)) || pod.Spec.DNSPolicy == v1.DNSClusterFirstWithHostNet)
|
||||
if useClusterFirstPolicy && len(c.clusterDNS) == 0 {
|
||||
// clusterDNS is not known.
|
||||
// pod with ClusterDNSFirst Policy cannot be created
|
||||
c.recorder.Eventf(pod, v1.EventTypeWarning, "MissingClusterDNS", "kubelet does not have ClusterDNS IP configured and cannot create Pod using %q policy. Falling back to DNSDefault policy.", pod.Spec.DNSPolicy)
|
||||
log := fmt.Sprintf("kubelet does not have ClusterDNS IP configured and cannot create Pod using %q policy. pod: %q. Falling back to DNSDefault policy.", pod.Spec.DNSPolicy, format.Pod(pod))
|
||||
c.recorder.Eventf(c.nodeRef, v1.EventTypeWarning, "MissingClusterDNS", log)
|
||||
return &runtimeapi.DNSConfig{
|
||||
Servers: hostDNS,
|
||||
Searches: hostSearch,
|
||||
Options: hostOptions,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// fallback to DNSDefault
|
||||
useClusterFirstPolicy = false
|
||||
func getPodDNSType(pod *v1.Pod) (podDNSType, error) {
|
||||
dnsPolicy := pod.Spec.DNSPolicy
|
||||
switch dnsPolicy {
|
||||
case v1.DNSNone:
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.CustomPodDNS) {
|
||||
return podDNSNone, nil
|
||||
}
|
||||
// This should not happen as kube-apiserver should have rejected
|
||||
// setting dnsPolicy to DNSNone when feature gate is disabled.
|
||||
return podDNSCluster, fmt.Errorf(fmt.Sprintf("invalid DNSPolicy=%v: custom pod DNS is disabled", dnsPolicy))
|
||||
case v1.DNSClusterFirstWithHostNet:
|
||||
return podDNSCluster, nil
|
||||
case v1.DNSClusterFirst:
|
||||
if !kubecontainer.IsHostNetworkPod(pod) {
|
||||
return podDNSCluster, nil
|
||||
}
|
||||
// Fallback to DNSDefault for pod on hostnetowrk.
|
||||
fallthrough
|
||||
case v1.DNSDefault:
|
||||
return podDNSHost, nil
|
||||
}
|
||||
// This should not happen as kube-apiserver should have rejected
|
||||
// invalid dnsPolicy.
|
||||
return podDNSCluster, fmt.Errorf(fmt.Sprintf("invalid DNSPolicy=%v", dnsPolicy))
|
||||
}
|
||||
|
||||
// Merge DNS options. If duplicated, entries given by PodDNSConfigOption will
|
||||
// overwrite the existing ones.
|
||||
func mergeDNSOptions(existingDNSConfigOptions []string, dnsConfigOptions []v1.PodDNSConfigOption) []string {
|
||||
optionsMap := make(map[string]string)
|
||||
for _, op := range existingDNSConfigOptions {
|
||||
if index := strings.Index(op, ":"); index != -1 {
|
||||
optionsMap[op[:index]] = op[index+1:]
|
||||
} else {
|
||||
optionsMap[op] = ""
|
||||
}
|
||||
}
|
||||
for _, op := range dnsConfigOptions {
|
||||
if op.Value != nil {
|
||||
optionsMap[op.Name] = *op.Value
|
||||
} else {
|
||||
optionsMap[op.Name] = ""
|
||||
}
|
||||
}
|
||||
// Reconvert DNS options into a string array.
|
||||
options := []string{}
|
||||
for opName, opValue := range optionsMap {
|
||||
op := opName
|
||||
if opValue != "" {
|
||||
op = op + ":" + opValue
|
||||
}
|
||||
options = append(options, op)
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
||||
// appendDNSConfig appends DNS servers, search paths and options given by
|
||||
// PodDNSConfig to the existing DNS config. Duplicated entries will be merged.
|
||||
// This assumes existingDNSConfig and dnsConfig are not nil.
|
||||
func appendDNSConfig(existingDNSConfig *runtimeapi.DNSConfig, dnsConfig *v1.PodDNSConfig) *runtimeapi.DNSConfig {
|
||||
existingDNSConfig.Servers = omitDuplicates(append(existingDNSConfig.Servers, dnsConfig.Nameservers...))
|
||||
existingDNSConfig.Searches = omitDuplicates(append(existingDNSConfig.Searches, dnsConfig.Searches...))
|
||||
existingDNSConfig.Options = mergeDNSOptions(existingDNSConfig.Options, dnsConfig.Options)
|
||||
return existingDNSConfig
|
||||
}
|
||||
|
||||
// GetPodDNS returns DNS setttings for the pod.
|
||||
func (c *Configurer) GetPodDNS(pod *v1.Pod) (*runtimeapi.DNSConfig, error) {
|
||||
dnsConfig, err := c.getHostDNSConfig(pod)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if !useClusterFirstPolicy {
|
||||
dnsType, err := getPodDNSType(pod)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to get DNS type for pod %q: %v. Falling back to DNSClusterFirst policy.", format.Pod(pod), err)
|
||||
dnsType = podDNSCluster
|
||||
}
|
||||
switch dnsType {
|
||||
case podDNSNone:
|
||||
// DNSNone should use empty DNS settings as the base.
|
||||
dnsConfig = &runtimeapi.DNSConfig{}
|
||||
case podDNSCluster:
|
||||
if len(c.clusterDNS) != 0 {
|
||||
// For a pod with DNSClusterFirst policy, the cluster DNS server is
|
||||
// the only nameserver configured for the pod. The cluster DNS server
|
||||
// itself will forward queries to other nameservers that is configured
|
||||
// to use, in case the cluster DNS server cannot resolve the DNS query
|
||||
// itself.
|
||||
dnsConfig.Servers = []string{}
|
||||
for _, ip := range c.clusterDNS {
|
||||
dnsConfig.Servers = append(dnsConfig.Servers, ip.String())
|
||||
}
|
||||
dnsConfig.Searches = c.generateSearchesForDNSClusterFirst(dnsConfig.Searches, pod)
|
||||
dnsConfig.Options = defaultDNSOptions
|
||||
break
|
||||
}
|
||||
// clusterDNS is not known. Pod with ClusterDNSFirst Policy cannot be created.
|
||||
nodeErrorMsg := fmt.Sprintf("kubelet does not have ClusterDNS IP configured and cannot create Pod using %q policy. Falling back to %q policy.", v1.DNSClusterFirst, v1.DNSDefault)
|
||||
c.recorder.Eventf(c.nodeRef, v1.EventTypeWarning, "MissingClusterDNS", nodeErrorMsg)
|
||||
c.recorder.Eventf(pod, v1.EventTypeWarning, "MissingClusterDNS", "pod: %q. %s", format.Pod(pod), nodeErrorMsg)
|
||||
// Fallback to DNSDefault.
|
||||
fallthrough
|
||||
case podDNSHost:
|
||||
// When the kubelet --resolv-conf flag is set to the empty string, use
|
||||
// DNS settings that override the docker default (which is to use
|
||||
// /etc/resolv.conf) and effectively disable DNS lookups. According to
|
||||
@ -262,35 +367,20 @@ func (c *Configurer) GetPodDNS(pod *v1.Pod) (*runtimeapi.DNSConfig, error) {
|
||||
// local machine". A nameserver setting of localhost is equivalent to
|
||||
// this documented behavior.
|
||||
if c.ResolverConfig == "" {
|
||||
hostSearch = []string{"."}
|
||||
switch {
|
||||
case c.nodeIP == nil || c.nodeIP.To4() != nil:
|
||||
hostDNS = []string{"127.0.0.1"}
|
||||
dnsConfig.Servers = []string{"127.0.0.1"}
|
||||
case c.nodeIP.To16() != nil:
|
||||
hostDNS = []string{"::1"}
|
||||
dnsConfig.Servers = []string{"::1"}
|
||||
}
|
||||
} else {
|
||||
hostSearch = c.formDNSSearchForDNSDefault(hostSearch, pod)
|
||||
dnsConfig.Searches = []string{"."}
|
||||
}
|
||||
return &runtimeapi.DNSConfig{
|
||||
Servers: hostDNS,
|
||||
Searches: hostSearch,
|
||||
Options: hostOptions}, nil
|
||||
}
|
||||
|
||||
// for a pod with DNSClusterFirst policy, the cluster DNS server is the only nameserver configured for
|
||||
// the pod. The cluster DNS server itself will forward queries to other nameservers that is configured to use,
|
||||
// in case the cluster DNS server cannot resolve the DNS query itself
|
||||
dns := make([]string, len(c.clusterDNS))
|
||||
for i, ip := range c.clusterDNS {
|
||||
dns[i] = ip.String()
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.CustomPodDNS) && pod.Spec.DNSConfig != nil {
|
||||
dnsConfig = appendDNSConfig(dnsConfig, pod.Spec.DNSConfig)
|
||||
}
|
||||
dnsSearch := c.formDNSSearch(hostSearch, pod)
|
||||
|
||||
return &runtimeapi.DNSConfig{
|
||||
Servers: dns,
|
||||
Searches: dnsSearch,
|
||||
Options: defaultDNSOptions}, nil
|
||||
return c.formDNSConfigFitsLimits(dnsConfig, pod), nil
|
||||
}
|
||||
|
||||
// SetupDNSinContainerizedMounter replaces the nameserver in containerized-mounter's rootfs/etc/resolve.conf with kubelet.ClusterDNS
|
||||
|
@ -25,12 +25,26 @@ import (
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/tools/record"
|
||||
runtimeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
var (
|
||||
fetchEvent = func(recorder *record.FakeRecorder) string {
|
||||
select {
|
||||
case event := <-recorder.Events:
|
||||
return event
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
func TestParseResolvConf(t *testing.T) {
|
||||
testCases := []struct {
|
||||
data string
|
||||
@ -74,7 +88,7 @@ func TestParseResolvConf(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestComposeDNSSearch(t *testing.T) {
|
||||
func TestFormDNSSearchFitsLimits(t *testing.T) {
|
||||
recorder := record.NewFakeRecorder(20)
|
||||
nodeRef := &v1.ObjectReference{
|
||||
Kind: "Node",
|
||||
@ -96,62 +110,272 @@ func TestComposeDNSSearch(t *testing.T) {
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
dnsNames []string
|
||||
hostNames []string
|
||||
resultSearch []string
|
||||
events []string
|
||||
}{
|
||||
{
|
||||
[]string{"testNS.svc.TEST", "svc.TEST", "TEST"},
|
||||
[]string{},
|
||||
[]string{"testNS.svc.TEST", "svc.TEST", "TEST"},
|
||||
[]string{},
|
||||
},
|
||||
|
||||
{
|
||||
[]string{"testNS.svc.TEST", "svc.TEST", "TEST"},
|
||||
[]string{"AAA", "svc.TEST", "BBB", "TEST"},
|
||||
[]string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA", "BBB"},
|
||||
[]string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA", "BBB"},
|
||||
[]string{},
|
||||
},
|
||||
|
||||
{
|
||||
[]string{"testNS.svc.TEST", "svc.TEST", "TEST"},
|
||||
[]string{"AAA", strings.Repeat("B", 256), "BBB"},
|
||||
[]string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA", strings.Repeat("B", 256), "BBB"},
|
||||
[]string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA"},
|
||||
[]string{"Search Line limits were exceeded, some dns names have been omitted, the applied search line is: testNS.svc.TEST svc.TEST TEST AAA"},
|
||||
[]string{"Search Line limits were exceeded, some search paths have been omitted, the applied search line is: testNS.svc.TEST svc.TEST TEST AAA"},
|
||||
},
|
||||
|
||||
{
|
||||
[]string{"testNS.svc.TEST", "svc.TEST", "TEST"},
|
||||
[]string{"AAA", "TEST", "BBB", "TEST", "CCC", "DDD"},
|
||||
[]string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA", "BBB", "CCC", "DDD"},
|
||||
[]string{"testNS.svc.TEST", "svc.TEST", "TEST", "AAA", "BBB", "CCC"},
|
||||
[]string{
|
||||
"Search Line limits were exceeded, some dns names have been omitted, the applied search line is: testNS.svc.TEST svc.TEST TEST AAA BBB CCC",
|
||||
},
|
||||
[]string{"Search Line limits were exceeded, some search paths have been omitted, the applied search line is: testNS.svc.TEST svc.TEST TEST AAA BBB CCC"},
|
||||
},
|
||||
}
|
||||
|
||||
fetchEvent := func(recorder *record.FakeRecorder) string {
|
||||
select {
|
||||
case event := <-recorder.Events:
|
||||
return event
|
||||
default:
|
||||
return "No more events!"
|
||||
}
|
||||
}
|
||||
|
||||
for i, tc := range testCases {
|
||||
dnsSearch := configurer.formDNSSearch(tc.hostNames, pod)
|
||||
dnsSearch := configurer.formDNSSearchFitsLimits(tc.hostNames, pod)
|
||||
assert.EqualValues(t, tc.resultSearch, dnsSearch, "test [%d]", i)
|
||||
for _, expectedEvent := range tc.events {
|
||||
expected := fmt.Sprintf("%s %s %s", v1.EventTypeWarning, "DNSSearchForming", expectedEvent)
|
||||
expected := fmt.Sprintf("%s %s %s", v1.EventTypeWarning, "DNSConfigForming", expectedEvent)
|
||||
event := fetchEvent(recorder)
|
||||
assert.Equal(t, expected, event, "test [%d]", i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFormDNSNameserversFitsLimits(t *testing.T) {
|
||||
recorder := record.NewFakeRecorder(20)
|
||||
nodeRef := &v1.ObjectReference{
|
||||
Kind: "Node",
|
||||
Name: string("testNode"),
|
||||
UID: types.UID("testNode"),
|
||||
Namespace: "",
|
||||
}
|
||||
testClusterDNSDomain := "TEST"
|
||||
|
||||
configurer := NewConfigurer(recorder, nodeRef, nil, nil, testClusterDNSDomain, "")
|
||||
|
||||
pod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "",
|
||||
Name: "test_pod",
|
||||
Namespace: "testNS",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
nameservers []string
|
||||
expectedNameserver []string
|
||||
expectedEvent bool
|
||||
}{
|
||||
{
|
||||
desc: "valid: 1 nameserver",
|
||||
nameservers: []string{"127.0.0.1"},
|
||||
expectedNameserver: []string{"127.0.0.1"},
|
||||
expectedEvent: false,
|
||||
},
|
||||
{
|
||||
desc: "valid: 3 nameservers",
|
||||
nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"},
|
||||
expectedNameserver: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"},
|
||||
expectedEvent: false,
|
||||
},
|
||||
{
|
||||
desc: "invalid: 4 nameservers, trimmed to 3",
|
||||
nameservers: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8", "1.2.3.4"},
|
||||
expectedNameserver: []string{"127.0.0.1", "10.0.0.10", "8.8.8.8"},
|
||||
expectedEvent: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
appliedNameservers := configurer.formDNSNameserversFitsLimits(tc.nameservers, pod)
|
||||
assert.EqualValues(t, tc.expectedNameserver, appliedNameservers, tc.desc)
|
||||
event := fetchEvent(recorder)
|
||||
if tc.expectedEvent && len(event) == 0 {
|
||||
t.Errorf("%s: formDNSNameserversFitsLimits(%v) expected event, got no event.", tc.desc, tc.nameservers)
|
||||
} else if !tc.expectedEvent && len(event) > 0 {
|
||||
t.Errorf("%s: formDNSNameserversFitsLimits(%v) expected no event, got event: %v", tc.desc, tc.nameservers, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergeDNSOptions(t *testing.T) {
|
||||
testOptionValue := "3"
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
existingDNSConfigOptions []string
|
||||
dnsConfigOptions []v1.PodDNSConfigOption
|
||||
expectedOptions []string
|
||||
}{
|
||||
{
|
||||
desc: "Empty dnsConfigOptions",
|
||||
existingDNSConfigOptions: []string{"ndots:5", "debug"},
|
||||
dnsConfigOptions: nil,
|
||||
expectedOptions: []string{"ndots:5", "debug"},
|
||||
},
|
||||
{
|
||||
desc: "No duplicated entries",
|
||||
existingDNSConfigOptions: []string{"ndots:5", "debug"},
|
||||
dnsConfigOptions: []v1.PodDNSConfigOption{
|
||||
{Name: "single-request"},
|
||||
{Name: "attempts", Value: &testOptionValue},
|
||||
},
|
||||
expectedOptions: []string{"ndots:5", "debug", "single-request", "attempts:3"},
|
||||
},
|
||||
{
|
||||
desc: "Overwrite duplicated entries",
|
||||
existingDNSConfigOptions: []string{"ndots:5", "debug"},
|
||||
dnsConfigOptions: []v1.PodDNSConfigOption{
|
||||
{Name: "ndots", Value: &testOptionValue},
|
||||
{Name: "debug"},
|
||||
{Name: "single-request"},
|
||||
{Name: "attempts", Value: &testOptionValue},
|
||||
},
|
||||
expectedOptions: []string{"ndots:3", "debug", "single-request", "attempts:3"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
options := mergeDNSOptions(tc.existingDNSConfigOptions, tc.dnsConfigOptions)
|
||||
// Options order may be changed after conversion.
|
||||
if !sets.NewString(options...).Equal(sets.NewString(tc.expectedOptions...)) {
|
||||
t.Errorf("%s: mergeDNSOptions(%v, %v)=%v, want %v", tc.desc, tc.existingDNSConfigOptions, tc.dnsConfigOptions, options, tc.expectedOptions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPodDNSType(t *testing.T) {
|
||||
customDNSEnabled := utilfeature.DefaultFeatureGate.Enabled("CustomPodDNS")
|
||||
defer func() {
|
||||
// Restoring the old value.
|
||||
if err := utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("CustomPodDNS=%v", customDNSEnabled)); err != nil {
|
||||
t.Errorf("Failed to set CustomPodDNS feature gate: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
recorder := record.NewFakeRecorder(20)
|
||||
nodeRef := &v1.ObjectReference{
|
||||
Kind: "Node",
|
||||
Name: string("testNode"),
|
||||
UID: types.UID("testNode"),
|
||||
Namespace: "",
|
||||
}
|
||||
testClusterDNSDomain := "TEST"
|
||||
clusterNS := "203.0.113.1"
|
||||
testClusterDNS := []net.IP{net.ParseIP(clusterNS)}
|
||||
|
||||
configurer := NewConfigurer(recorder, nodeRef, nil, nil, testClusterDNSDomain, "")
|
||||
|
||||
pod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "",
|
||||
Name: "test_pod",
|
||||
Namespace: "testNS",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
}
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
customPodDNSFeatureGate bool
|
||||
hasClusterDNS bool
|
||||
hostNetwork bool
|
||||
dnsPolicy v1.DNSPolicy
|
||||
expectedDNSType podDNSType
|
||||
expectedError bool
|
||||
}{
|
||||
{
|
||||
desc: "valid DNSClusterFirst without hostnetwork",
|
||||
hasClusterDNS: true,
|
||||
dnsPolicy: v1.DNSClusterFirst,
|
||||
expectedDNSType: podDNSCluster,
|
||||
},
|
||||
{
|
||||
desc: "valid DNSClusterFirstWithHostNet with hostnetwork",
|
||||
hasClusterDNS: true,
|
||||
hostNetwork: true,
|
||||
dnsPolicy: v1.DNSClusterFirstWithHostNet,
|
||||
expectedDNSType: podDNSCluster,
|
||||
},
|
||||
{
|
||||
desc: "valid DNSClusterFirstWithHostNet without hostnetwork",
|
||||
hasClusterDNS: true,
|
||||
dnsPolicy: v1.DNSClusterFirstWithHostNet,
|
||||
expectedDNSType: podDNSCluster,
|
||||
},
|
||||
{
|
||||
desc: "valid DNSDefault without hostnetwork",
|
||||
dnsPolicy: v1.DNSDefault,
|
||||
expectedDNSType: podDNSHost,
|
||||
},
|
||||
{
|
||||
desc: "valid DNSDefault with hostnetwork",
|
||||
hostNetwork: true,
|
||||
dnsPolicy: v1.DNSDefault,
|
||||
expectedDNSType: podDNSHost,
|
||||
},
|
||||
{
|
||||
desc: "DNSClusterFirst with hostnetwork, fallback to DNSDefault",
|
||||
hasClusterDNS: true,
|
||||
hostNetwork: true,
|
||||
dnsPolicy: v1.DNSClusterFirst,
|
||||
expectedDNSType: podDNSHost,
|
||||
},
|
||||
{
|
||||
desc: "valid DNSNone with feature gate",
|
||||
customPodDNSFeatureGate: true,
|
||||
dnsPolicy: v1.DNSNone,
|
||||
expectedDNSType: podDNSNone,
|
||||
},
|
||||
{
|
||||
desc: "DNSNone without feature gate, should return error",
|
||||
dnsPolicy: v1.DNSNone,
|
||||
expectedError: true,
|
||||
},
|
||||
{
|
||||
desc: "invalid DNS policy, should return error",
|
||||
dnsPolicy: "invalidPolicy",
|
||||
expectedError: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
if err := utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("CustomPodDNS=%v", tc.customPodDNSFeatureGate)); err != nil {
|
||||
t.Errorf("Failed to set CustomPodDNS feature gate: %v", err)
|
||||
}
|
||||
|
||||
if tc.hasClusterDNS {
|
||||
configurer.clusterDNS = testClusterDNS
|
||||
} else {
|
||||
configurer.clusterDNS = nil
|
||||
}
|
||||
pod.Spec.DNSPolicy = tc.dnsPolicy
|
||||
pod.Spec.HostNetwork = tc.hostNetwork
|
||||
|
||||
resType, err := getPodDNSType(pod)
|
||||
if tc.expectedError {
|
||||
if err == nil {
|
||||
t.Errorf("%s: GetPodDNSType(%v) got no error, want error", tc.desc, pod)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if resType != tc.expectedDNSType {
|
||||
t.Errorf("%s: GetPodDNSType(%v)=%v, want %v", tc.desc, pod, resType, tc.expectedDNSType)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPodDNS(t *testing.T) {
|
||||
recorder := record.NewFakeRecorder(20)
|
||||
nodeRef := &v1.ObjectReference{
|
||||
@ -247,6 +471,119 @@ func TestGetPodDNS(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetPodDNSCustom(t *testing.T) {
|
||||
customDNSEnabled := utilfeature.DefaultFeatureGate.Enabled("CustomPodDNS")
|
||||
defer func() {
|
||||
// Restoring the old value.
|
||||
if err := utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("CustomPodDNS=%v", customDNSEnabled)); err != nil {
|
||||
t.Errorf("Failed to set CustomPodDNS feature gate: %v", err)
|
||||
}
|
||||
}()
|
||||
|
||||
recorder := record.NewFakeRecorder(20)
|
||||
nodeRef := &v1.ObjectReference{
|
||||
Kind: "Node",
|
||||
Name: string("testNode"),
|
||||
UID: types.UID("testNode"),
|
||||
Namespace: "",
|
||||
}
|
||||
clusterNS := "203.0.113.1"
|
||||
testClusterDNSDomain := "kubernetes.io"
|
||||
testClusterDNS := []net.IP{net.ParseIP(clusterNS)}
|
||||
testOptionValue := "3"
|
||||
|
||||
configurer := NewConfigurer(recorder, nodeRef, nil, testClusterDNS, testClusterDNSDomain, "")
|
||||
|
||||
pod := &v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: "",
|
||||
Name: "test_pod",
|
||||
Namespace: "testNS",
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Spec: v1.PodSpec{
|
||||
DNSPolicy: v1.DNSClusterFirst,
|
||||
},
|
||||
}
|
||||
clusterFirstDNSConfig, err := configurer.GetPodDNS(pod)
|
||||
if err != nil {
|
||||
t.Fatalf("Preparing clusterFirstDNSConfig: GetPodDNS(%v), unexpected error: %v", pod, err)
|
||||
}
|
||||
|
||||
// Overwrite DNSPolicy for testing.
|
||||
pod.Spec.DNSPolicy = v1.DNSNone
|
||||
|
||||
testCases := []struct {
|
||||
desc string
|
||||
customPodDNSFeatureGate bool
|
||||
dnsConfig *v1.PodDNSConfig
|
||||
expectedDNSConfig *runtimeapi.DNSConfig
|
||||
}{
|
||||
{
|
||||
desc: "feature gate is disabled, DNSNone should fallback to DNSClusterFirst",
|
||||
expectedDNSConfig: clusterFirstDNSConfig,
|
||||
},
|
||||
{
|
||||
desc: "feature gate is enabled, DNSNone without DNSConfig should have empty DNS settings",
|
||||
customPodDNSFeatureGate: true,
|
||||
expectedDNSConfig: &runtimeapi.DNSConfig{},
|
||||
},
|
||||
{
|
||||
desc: "feature gate is enabled, DNSNone with DNSConfig should have a merged DNS settings",
|
||||
customPodDNSFeatureGate: true,
|
||||
dnsConfig: &v1.PodDNSConfig{
|
||||
Nameservers: []string{"10.0.0.10"},
|
||||
Searches: []string{"my.domain", "second.domain"},
|
||||
Options: []v1.PodDNSConfigOption{
|
||||
{Name: "ndots", Value: &testOptionValue},
|
||||
{Name: "debug"},
|
||||
},
|
||||
},
|
||||
expectedDNSConfig: &runtimeapi.DNSConfig{
|
||||
Servers: []string{"10.0.0.10"},
|
||||
Searches: []string{"my.domain", "second.domain"},
|
||||
Options: []string{"ndots:3", "debug"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
if err := utilfeature.DefaultFeatureGate.Set(fmt.Sprintf("CustomPodDNS=%v", tc.customPodDNSFeatureGate)); err != nil {
|
||||
t.Errorf("Failed to set CustomPodDNS feature gate: %v", err)
|
||||
}
|
||||
|
||||
pod.Spec.DNSConfig = tc.dnsConfig
|
||||
|
||||
resDNSConfig, err := configurer.GetPodDNS(pod)
|
||||
if err != nil {
|
||||
t.Errorf("%s: GetPodDNS(%v), unexpected error: %v", tc.desc, pod, err)
|
||||
}
|
||||
if !dnsConfigsAreEqual(resDNSConfig, tc.expectedDNSConfig) {
|
||||
t.Errorf("%s: GetPodDNS(%v)=%v, want %v", tc.desc, pod, resDNSConfig, tc.expectedDNSConfig)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func dnsConfigsAreEqual(resConfig, expectedConfig *runtimeapi.DNSConfig) bool {
|
||||
if len(resConfig.Servers) != len(expectedConfig.Servers) ||
|
||||
len(resConfig.Searches) != len(expectedConfig.Searches) ||
|
||||
len(resConfig.Options) != len(expectedConfig.Options) {
|
||||
return false
|
||||
}
|
||||
for i, server := range resConfig.Servers {
|
||||
if expectedConfig.Servers[i] != server {
|
||||
return false
|
||||
}
|
||||
}
|
||||
for i, search := range resConfig.Searches {
|
||||
if expectedConfig.Searches[i] != search {
|
||||
return false
|
||||
}
|
||||
}
|
||||
// Options order may be changed after conversion.
|
||||
return sets.NewString(resConfig.Options...).Equal(sets.NewString(expectedConfig.Options...))
|
||||
}
|
||||
|
||||
func newTestPods(count int) []*v1.Pod {
|
||||
pods := make([]*v1.Pod, count)
|
||||
for i := 0; i < count; i++ {
|
||||
|
Loading…
Reference in New Issue
Block a user