mirror of
				https://github.com/coredns/coredns.git
				synced 2025-10-30 17:53:21 -04:00 
			
		
		
		
	plugin/kubernetes: Add support for dual stack ClusterIP Services (#4339)
* support dual stack clusterIPs Signed-off-by: Chris O'Haver <cohaver@infoblox.com> * stickler Signed-off-by: Chris O'Haver <cohaver@infoblox.com> * fix ClusterIPs make Signed-off-by: Chris O'Haver <cohaver@infoblox.com>
This commit is contained in:
		| @@ -190,7 +190,7 @@ var svcIndexExternal = map[string][]*object.Service{ | |||||||
| 			Name:        "svc1", | 			Name:        "svc1", | ||||||
| 			Namespace:   "testns", | 			Namespace:   "testns", | ||||||
| 			Type:        api.ServiceTypeClusterIP, | 			Type:        api.ServiceTypeClusterIP, | ||||||
| 			ClusterIP:   "10.0.0.1", | 			ClusterIPs:  []string{"10.0.0.1"}, | ||||||
| 			ExternalIPs: []string{"1.2.3.4"}, | 			ExternalIPs: []string{"1.2.3.4"}, | ||||||
| 			Ports:       []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}}, | 			Ports:       []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}}, | ||||||
| 		}, | 		}, | ||||||
| @@ -200,7 +200,7 @@ var svcIndexExternal = map[string][]*object.Service{ | |||||||
| 			Name:        "svc6", | 			Name:        "svc6", | ||||||
| 			Namespace:   "testns", | 			Namespace:   "testns", | ||||||
| 			Type:        api.ServiceTypeClusterIP, | 			Type:        api.ServiceTypeClusterIP, | ||||||
| 			ClusterIP:   "10.0.0.3", | 			ClusterIPs:  []string{"10.0.0.3"}, | ||||||
| 			ExternalIPs: []string{"1:2::5"}, | 			ExternalIPs: []string{"1:2::5"}, | ||||||
| 			Ports:       []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}}, | 			Ports:       []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}}, | ||||||
| 		}, | 		}, | ||||||
|   | |||||||
| @@ -200,11 +200,13 @@ func svcIPIndexFunc(obj interface{}) ([]string, error) { | |||||||
| 	if !ok { | 	if !ok { | ||||||
| 		return nil, errObj | 		return nil, errObj | ||||||
| 	} | 	} | ||||||
|  | 	idx := make([]string, len(svc.ClusterIPs)+len(svc.ExternalIPs)) | ||||||
|  | 	copy(idx, svc.ClusterIPs) | ||||||
| 	if len(svc.ExternalIPs) == 0 { | 	if len(svc.ExternalIPs) == 0 { | ||||||
| 		return []string{svc.ClusterIP}, nil | 		return idx, nil | ||||||
| 	} | 	} | ||||||
|  | 	copy(idx[len(svc.ClusterIPs):], svc.ExternalIPs) | ||||||
| 	return append([]string{svc.ClusterIP}, svc.ExternalIPs...), nil | 	return idx, nil | ||||||
| } | } | ||||||
|  |  | ||||||
| func svcNameNamespaceIndexFunc(obj interface{}) ([]string, error) { | func svcNameNamespaceIndexFunc(obj interface{}) ([]string, error) { | ||||||
|   | |||||||
| @@ -105,7 +105,7 @@ var svcIndexExternal = map[string][]*object.Service{ | |||||||
| 			Name:        "svc1", | 			Name:        "svc1", | ||||||
| 			Namespace:   "testns", | 			Namespace:   "testns", | ||||||
| 			Type:        api.ServiceTypeClusterIP, | 			Type:        api.ServiceTypeClusterIP, | ||||||
| 			ClusterIP:   "10.0.0.1", | 			ClusterIPs:  []string{"10.0.0.1"}, | ||||||
| 			ExternalIPs: []string{"1.2.3.4"}, | 			ExternalIPs: []string{"1.2.3.4"}, | ||||||
| 			Ports:       []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}}, | 			Ports:       []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}}, | ||||||
| 		}, | 		}, | ||||||
| @@ -115,7 +115,7 @@ var svcIndexExternal = map[string][]*object.Service{ | |||||||
| 			Name:        "svc6", | 			Name:        "svc6", | ||||||
| 			Namespace:   "testns", | 			Namespace:   "testns", | ||||||
| 			Type:        api.ServiceTypeClusterIP, | 			Type:        api.ServiceTypeClusterIP, | ||||||
| 			ClusterIP:   "10.0.0.3", | 			ClusterIPs:  []string{"10.0.0.3"}, | ||||||
| 			ExternalIPs: []string{"1:2::5"}, | 			ExternalIPs: []string{"1:2::5"}, | ||||||
| 			Ports:       []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}}, | 			Ports:       []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}}, | ||||||
| 		}, | 		}, | ||||||
|   | |||||||
| @@ -372,6 +372,30 @@ var dnsTestCases = []test.Case{ | |||||||
| 			test.SOA("cluster.local.	5	IN	SOA	ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 5"), | 			test.SOA("cluster.local.	5	IN	SOA	ns.dns.cluster.local. hostmaster.cluster.local. 1499347823 7200 1800 86400 5"), | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
|  | 	// Dual Stack ClusterIP Services | ||||||
|  | 	{ | ||||||
|  | 		Qname: "svc-dual-stack.testns.svc.cluster.local.", Qtype: dns.TypeA, | ||||||
|  | 		Rcode: dns.RcodeSuccess, | ||||||
|  | 		Answer: []dns.RR{ | ||||||
|  | 			test.A("svc-dual-stack.testns.svc.cluster.local.	5	IN	A	10.0.0.3"), | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		Qname: "svc-dual-stack.testns.svc.cluster.local.", Qtype: dns.TypeAAAA, | ||||||
|  | 		Rcode: dns.RcodeSuccess, | ||||||
|  | 		Answer: []dns.RR{ | ||||||
|  | 			test.AAAA("svc-dual-stack.testns.svc.cluster.local.	5	IN	AAAA	10::3"), | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		Qname: "svc-dual-stack.testns.svc.cluster.local.", Qtype: dns.TypeSRV, | ||||||
|  | 		Rcode: dns.RcodeSuccess, | ||||||
|  | 		Answer: []dns.RR{test.SRV("svc-dual-stack.testns.svc.cluster.local.	5	IN	SRV	0 50 80 svc-dual-stack.testns.svc.cluster.local.")}, | ||||||
|  | 		Extra: []dns.RR{ | ||||||
|  | 			test.A("svc-dual-stack.testns.svc.cluster.local.  5       IN      A       10.0.0.3"), | ||||||
|  | 			test.AAAA("svc-dual-stack.testns.svc.cluster.local.  5       IN      AAAA       10::3"), | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
| } | } | ||||||
|  |  | ||||||
| func TestServeDNS(t *testing.T) { | func TestServeDNS(t *testing.T) { | ||||||
| @@ -539,10 +563,10 @@ func (APIConnServeTest) PodIndex(ip string) []*object.Pod { | |||||||
| var svcIndex = map[string][]*object.Service{ | var svcIndex = map[string][]*object.Service{ | ||||||
| 	"svc1.testns": { | 	"svc1.testns": { | ||||||
| 		{ | 		{ | ||||||
| 			Name:      "svc1", | 			Name:       "svc1", | ||||||
| 			Namespace: "testns", | 			Namespace:  "testns", | ||||||
| 			Type:      api.ServiceTypeClusterIP, | 			Type:       api.ServiceTypeClusterIP, | ||||||
| 			ClusterIP: "10.0.0.1", | 			ClusterIPs: []string{"10.0.0.1"}, | ||||||
| 			Ports: []api.ServicePort{ | 			Ports: []api.ServicePort{ | ||||||
| 				{Name: "http", Protocol: "tcp", Port: 80}, | 				{Name: "http", Protocol: "tcp", Port: 80}, | ||||||
| 			}, | 			}, | ||||||
| @@ -550,10 +574,10 @@ var svcIndex = map[string][]*object.Service{ | |||||||
| 	}, | 	}, | ||||||
| 	"svcempty.testns": { | 	"svcempty.testns": { | ||||||
| 		{ | 		{ | ||||||
| 			Name:      "svcempty", | 			Name:       "svcempty", | ||||||
| 			Namespace: "testns", | 			Namespace:  "testns", | ||||||
| 			Type:      api.ServiceTypeClusterIP, | 			Type:       api.ServiceTypeClusterIP, | ||||||
| 			ClusterIP: "10.0.0.1", | 			ClusterIPs: []string{"10.0.0.1"}, | ||||||
| 			Ports: []api.ServicePort{ | 			Ports: []api.ServicePort{ | ||||||
| 				{Name: "http", Protocol: "tcp", Port: 80}, | 				{Name: "http", Protocol: "tcp", Port: 80}, | ||||||
| 			}, | 			}, | ||||||
| @@ -561,10 +585,10 @@ var svcIndex = map[string][]*object.Service{ | |||||||
| 	}, | 	}, | ||||||
| 	"svc6.testns": { | 	"svc6.testns": { | ||||||
| 		{ | 		{ | ||||||
| 			Name:      "svc6", | 			Name:       "svc6", | ||||||
| 			Namespace: "testns", | 			Namespace:  "testns", | ||||||
| 			Type:      api.ServiceTypeClusterIP, | 			Type:       api.ServiceTypeClusterIP, | ||||||
| 			ClusterIP: "1234:abcd::1", | 			ClusterIPs: []string{"1234:abcd::1"}, | ||||||
| 			Ports: []api.ServicePort{ | 			Ports: []api.ServicePort{ | ||||||
| 				{Name: "http", Protocol: "tcp", Port: 80}, | 				{Name: "http", Protocol: "tcp", Port: 80}, | ||||||
| 			}, | 			}, | ||||||
| @@ -572,10 +596,10 @@ var svcIndex = map[string][]*object.Service{ | |||||||
| 	}, | 	}, | ||||||
| 	"hdls1.testns": { | 	"hdls1.testns": { | ||||||
| 		{ | 		{ | ||||||
| 			Name:      "hdls1", | 			Name:       "hdls1", | ||||||
| 			Namespace: "testns", | 			Namespace:  "testns", | ||||||
| 			Type:      api.ServiceTypeClusterIP, | 			Type:       api.ServiceTypeClusterIP, | ||||||
| 			ClusterIP: api.ClusterIPNone, | 			ClusterIPs: []string{api.ClusterIPNone}, | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
| 	"external.testns": { | 	"external.testns": { | ||||||
| @@ -602,23 +626,33 @@ var svcIndex = map[string][]*object.Service{ | |||||||
| 	}, | 	}, | ||||||
| 	"hdlsprtls.testns": { | 	"hdlsprtls.testns": { | ||||||
| 		{ | 		{ | ||||||
| 			Name:      "hdlsprtls", | 			Name:       "hdlsprtls", | ||||||
| 			Namespace: "testns", | 			Namespace:  "testns", | ||||||
| 			Type:      api.ServiceTypeClusterIP, | 			Type:       api.ServiceTypeClusterIP, | ||||||
| 			ClusterIP: api.ClusterIPNone, | 			ClusterIPs: []string{api.ClusterIPNone}, | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
| 	"svc1.unexposedns": { | 	"svc1.unexposedns": { | ||||||
| 		{ | 		{ | ||||||
| 			Name:      "svc1", | 			Name:       "svc1", | ||||||
| 			Namespace: "unexposedns", | 			Namespace:  "unexposedns", | ||||||
| 			Type:      api.ServiceTypeClusterIP, | 			Type:       api.ServiceTypeClusterIP, | ||||||
| 			ClusterIP: "10.0.0.2", | 			ClusterIPs: []string{"10.0.0.2"}, | ||||||
| 			Ports: []api.ServicePort{ | 			Ports: []api.ServicePort{ | ||||||
| 				{Name: "http", Protocol: "tcp", Port: 80}, | 				{Name: "http", Protocol: "tcp", Port: 80}, | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 	}, | 	}, | ||||||
|  | 	"svc-dual-stack.testns": { | ||||||
|  | 		{ | ||||||
|  | 			Name:       "svc-dual-stack", | ||||||
|  | 			Namespace:  "testns", | ||||||
|  | 			Type:       api.ServiceTypeClusterIP, | ||||||
|  | 			ClusterIPs: []string{"10.0.0.3", "10::3"}, Ports: []api.ServicePort{ | ||||||
|  | 				{Name: "http", Protocol: "tcp", Port: 80}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 	}, | ||||||
| } | } | ||||||
|  |  | ||||||
| func (APIConnServeTest) SvcIndex(s string) []*object.Service { return svcIndex[s] } | func (APIConnServeTest) SvcIndex(s string) []*object.Service { return svcIndex[s] } | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| package kubernetes | package kubernetes | ||||||
|  |  | ||||||
| import ( | import ( | ||||||
|  | 	"fmt" | ||||||
| 	"testing" | 	"testing" | ||||||
|  |  | ||||||
| 	"github.com/coredns/coredns/plugin/kubernetes/object" | 	"github.com/coredns/coredns/plugin/kubernetes/object" | ||||||
| @@ -21,11 +22,19 @@ func TestDefaultProcessor(t *testing.T) { | |||||||
| func testProcessor(t *testing.T, processor cache.ProcessFunc, idx cache.Indexer) { | func testProcessor(t *testing.T, processor cache.ProcessFunc, idx cache.Indexer) { | ||||||
| 	obj := &api.Service{ | 	obj := &api.Service{ | ||||||
| 		ObjectMeta: metav1.ObjectMeta{Name: "service1", Namespace: "test1"}, | 		ObjectMeta: metav1.ObjectMeta{Name: "service1", Namespace: "test1"}, | ||||||
| 		Spec:       api.ServiceSpec{ClusterIP: "1.2.3.4", Ports: []api.ServicePort{{Port: 80}}}, | 		Spec: api.ServiceSpec{ | ||||||
|  | 			ClusterIP:  "1.2.3.4", | ||||||
|  | 			ClusterIPs: []string{"1.2.3.4"}, | ||||||
|  | 			Ports:      []api.ServicePort{{Port: 80}}, | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
| 	obj2 := &api.Service{ | 	obj2 := &api.Service{ | ||||||
| 		ObjectMeta: metav1.ObjectMeta{Name: "service2", Namespace: "test1"}, | 		ObjectMeta: metav1.ObjectMeta{Name: "service2", Namespace: "test1"}, | ||||||
| 		Spec:       api.ServiceSpec{ClusterIP: "5.6.7.8", Ports: []api.ServicePort{{Port: 80}}}, | 		Spec: api.ServiceSpec{ | ||||||
|  | 			ClusterIP:  "5.6.7.8", | ||||||
|  | 			ClusterIPs: []string{"5.6.7.8"}, | ||||||
|  | 			Ports:      []api.ServicePort{{Port: 80}}, | ||||||
|  | 		}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Add the objects | 	// Add the objects | ||||||
| @@ -47,8 +56,8 @@ func testProcessor(t *testing.T, processor cache.ProcessFunc, idx cache.Indexer) | |||||||
| 	if !ok { | 	if !ok { | ||||||
| 		t.Fatal("object in index was incorrect type") | 		t.Fatal("object in index was incorrect type") | ||||||
| 	} | 	} | ||||||
| 	if svc.ClusterIP != obj.Spec.ClusterIP { | 	if fmt.Sprintf("%v", svc.ClusterIPs) != fmt.Sprintf("%v", obj.Spec.ClusterIPs) { | ||||||
| 		t.Fatalf("expected %v, got %v", obj.Spec.ClusterIP, svc.ClusterIP) | 		t.Fatalf("expected '%v', got '%v'", obj.Spec.ClusterIPs, svc.ClusterIPs) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Update an object | 	// Update an object | ||||||
| @@ -71,8 +80,8 @@ func testProcessor(t *testing.T, processor cache.ProcessFunc, idx cache.Indexer) | |||||||
| 	if !ok { | 	if !ok { | ||||||
| 		t.Fatal("object in index was incorrect type") | 		t.Fatal("object in index was incorrect type") | ||||||
| 	} | 	} | ||||||
| 	if svc.ClusterIP != obj.Spec.ClusterIP { | 	if fmt.Sprintf("%v", svc.ClusterIPs) != fmt.Sprintf("%v", obj.Spec.ClusterIPs) { | ||||||
| 		t.Fatalf("expected %v, got %v", obj.Spec.ClusterIP, svc.ClusterIP) | 		t.Fatalf("expected '%v', got '%v'", obj.Spec.ClusterIPs, svc.ClusterIPs) | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	// Delete an object | 	// Delete an object | ||||||
|   | |||||||
| @@ -433,8 +433,7 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg. | |||||||
|  |  | ||||||
| 		// If "ignore empty_service" option is set and no endpoints exist, return NXDOMAIN unless | 		// If "ignore empty_service" option is set and no endpoints exist, return NXDOMAIN unless | ||||||
| 		// it's a headless or externalName service (covered below). | 		// it's a headless or externalName service (covered below). | ||||||
| 		if k.opts.ignoreEmptyService && svc.ClusterIP != api.ClusterIPNone && svc.Type != api.ServiceTypeExternalName { | 		if k.opts.ignoreEmptyService && svc.Type != api.ServiceTypeExternalName && !svc.Headless() { // serve NXDOMAIN if no endpoint is able to answer | ||||||
| 			// serve NXDOMAIN if no endpoint is able to answer |  | ||||||
| 			podsCount := 0 | 			podsCount := 0 | ||||||
| 			for _, ep := range endpointsListFunc() { | 			for _, ep := range endpointsListFunc() { | ||||||
| 				for _, eps := range ep.Subsets { | 				for _, eps := range ep.Subsets { | ||||||
| @@ -447,8 +446,20 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg. | |||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
|  | 		// External service | ||||||
|  | 		if svc.Type == api.ServiceTypeExternalName { | ||||||
|  | 			s := msg.Service{Key: strings.Join([]string{zonePath, Svc, svc.Namespace, svc.Name}, "/"), Host: svc.ExternalName, TTL: k.ttl} | ||||||
|  | 			if t, _ := s.HostType(); t == dns.TypeCNAME { | ||||||
|  | 				s.Key = strings.Join([]string{zonePath, Svc, svc.Namespace, svc.Name}, "/") | ||||||
|  | 				services = append(services, s) | ||||||
|  |  | ||||||
|  | 				err = nil | ||||||
|  | 			} | ||||||
|  | 			continue | ||||||
|  | 		} | ||||||
|  |  | ||||||
| 		// Endpoint query or headless service | 		// Endpoint query or headless service | ||||||
| 		if svc.ClusterIP == api.ClusterIPNone || r.endpoint != "" { | 		if svc.Headless() || r.endpoint != "" { | ||||||
| 			if endpointsList == nil { | 			if endpointsList == nil { | ||||||
| 				endpointsList = endpointsListFunc() | 				endpointsList = endpointsListFunc() | ||||||
| 			} | 			} | ||||||
| @@ -485,18 +496,6 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg. | |||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		// External service |  | ||||||
| 		if svc.Type == api.ServiceTypeExternalName { |  | ||||||
| 			s := msg.Service{Key: strings.Join([]string{zonePath, Svc, svc.Namespace, svc.Name}, "/"), Host: svc.ExternalName, TTL: k.ttl} |  | ||||||
| 			if t, _ := s.HostType(); t == dns.TypeCNAME { |  | ||||||
| 				s.Key = strings.Join([]string{zonePath, Svc, svc.Namespace, svc.Name}, "/") |  | ||||||
| 				services = append(services, s) |  | ||||||
|  |  | ||||||
| 				err = nil |  | ||||||
| 			} |  | ||||||
| 			continue |  | ||||||
| 		} |  | ||||||
|  |  | ||||||
| 		// ClusterIP service | 		// ClusterIP service | ||||||
| 		for _, p := range svc.Ports { | 		for _, p := range svc.Ports { | ||||||
| 			if !(match(r.port, p.Name) && match(r.protocol, string(p.Protocol))) { | 			if !(match(r.port, p.Name) && match(r.protocol, string(p.Protocol))) { | ||||||
| @@ -505,10 +504,11 @@ func (k *Kubernetes) findServices(r recordRequest, zone string) (services []msg. | |||||||
|  |  | ||||||
| 			err = nil | 			err = nil | ||||||
|  |  | ||||||
| 			s := msg.Service{Host: svc.ClusterIP, Port: int(p.Port), TTL: k.ttl} | 			for _, ip := range svc.ClusterIPs { | ||||||
| 			s.Key = strings.Join([]string{zonePath, Svc, svc.Namespace, svc.Name}, "/") | 				s := msg.Service{Host: ip, Port: int(p.Port), TTL: k.ttl} | ||||||
|  | 				s.Key = strings.Join([]string{zonePath, Svc, svc.Namespace, svc.Name}, "/") | ||||||
| 			services = append(services, s) | 				services = append(services, s) | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	return services, err | 	return services, err | ||||||
|   | |||||||
| @@ -71,17 +71,25 @@ func (APIConnServiceTest) Modified() int64                           { return 0 | |||||||
| func (APIConnServiceTest) SvcIndex(string) []*object.Service { | func (APIConnServiceTest) SvcIndex(string) []*object.Service { | ||||||
| 	svcs := []*object.Service{ | 	svcs := []*object.Service{ | ||||||
| 		{ | 		{ | ||||||
| 			Name:      "svc1", | 			Name:       "svc1", | ||||||
| 			Namespace: "testns", | 			Namespace:  "testns", | ||||||
| 			ClusterIP: "10.0.0.1", | 			ClusterIPs: []string{"10.0.0.1"}, | ||||||
| 			Ports: []api.ServicePort{ | 			Ports: []api.ServicePort{ | ||||||
| 				{Name: "http", Protocol: "tcp", Port: 80}, | 				{Name: "http", Protocol: "tcp", Port: 80}, | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			Name:      "hdls1", | 			Name:       "svc-dual-stack", | ||||||
| 			Namespace: "testns", | 			Namespace:  "testns", | ||||||
| 			ClusterIP: api.ClusterIPNone, | 			ClusterIPs: []string{"10.0.0.2", "10::2"}, | ||||||
|  | 			Ports: []api.ServicePort{ | ||||||
|  | 				{Name: "http", Protocol: "tcp", Port: 80}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Name:       "hdls1", | ||||||
|  | 			Namespace:  "testns", | ||||||
|  | 			ClusterIPs: []string{api.ClusterIPNone}, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			Name:         "external", | 			Name:         "external", | ||||||
| @@ -99,17 +107,25 @@ func (APIConnServiceTest) SvcIndex(string) []*object.Service { | |||||||
| func (APIConnServiceTest) ServiceList() []*object.Service { | func (APIConnServiceTest) ServiceList() []*object.Service { | ||||||
| 	svcs := []*object.Service{ | 	svcs := []*object.Service{ | ||||||
| 		{ | 		{ | ||||||
| 			Name:      "svc1", | 			Name:       "svc1", | ||||||
| 			Namespace: "testns", | 			Namespace:  "testns", | ||||||
| 			ClusterIP: "10.0.0.1", | 			ClusterIPs: []string{"10.0.0.1"}, | ||||||
| 			Ports: []api.ServicePort{ | 			Ports: []api.ServicePort{ | ||||||
| 				{Name: "http", Protocol: "tcp", Port: 80}, | 				{Name: "http", Protocol: "tcp", Port: 80}, | ||||||
| 			}, | 			}, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			Name:      "hdls1", | 			Name:       "svc-dual-stack", | ||||||
| 			Namespace: "testns", | 			Namespace:  "testns", | ||||||
| 			ClusterIP: api.ClusterIPNone, | 			ClusterIPs: []string{"10.0.0.2", "10::2"}, | ||||||
|  | 			Ports: []api.ServicePort{ | ||||||
|  | 				{Name: "http", Protocol: "tcp", Port: 80}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  | 		{ | ||||||
|  | 			Name:       "hdls1", | ||||||
|  | 			Namespace:  "testns", | ||||||
|  | 			ClusterIPs: []string{api.ClusterIPNone}, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			Name:         "external", | 			Name:         "external", | ||||||
| @@ -256,19 +272,29 @@ func TestServices(t *testing.T) { | |||||||
| 	type svcTest struct { | 	type svcTest struct { | ||||||
| 		qname  string | 		qname  string | ||||||
| 		qtype  uint16 | 		qtype  uint16 | ||||||
| 		answer svcAns | 		answer []svcAns | ||||||
| 	} | 	} | ||||||
| 	tests := []svcTest{ | 	tests := []svcTest{ | ||||||
| 		// Cluster IP Services | 		// Cluster IP Services | ||||||
| 		{qname: "svc1.testns.svc.interwebs.test.", qtype: dns.TypeA, answer: svcAns{host: "10.0.0.1", key: "/" + coredns + "/test/interwebs/svc/testns/svc1"}}, | 		{qname: "svc1.testns.svc.interwebs.test.", qtype: dns.TypeA, answer: []svcAns{{host: "10.0.0.1", key: "/" + coredns + "/test/interwebs/svc/testns/svc1"}}}, | ||||||
| 		{qname: "_http._tcp.svc1.testns.svc.interwebs.test.", qtype: dns.TypeSRV, answer: svcAns{host: "10.0.0.1", key: "/" + coredns + "/test/interwebs/svc/testns/svc1"}}, | 		{qname: "_http._tcp.svc1.testns.svc.interwebs.test.", qtype: dns.TypeSRV, answer: []svcAns{{host: "10.0.0.1", key: "/" + coredns + "/test/interwebs/svc/testns/svc1"}}}, | ||||||
| 		{qname: "ep1a.svc1.testns.svc.interwebs.test.", qtype: dns.TypeA, answer: svcAns{host: "172.0.0.1", key: "/" + coredns + "/test/interwebs/svc/testns/svc1/ep1a"}}, | 		{qname: "ep1a.svc1.testns.svc.interwebs.test.", qtype: dns.TypeA, answer: []svcAns{{host: "172.0.0.1", key: "/" + coredns + "/test/interwebs/svc/testns/svc1/ep1a"}}}, | ||||||
|  |  | ||||||
|  | 		// Dual-Stack Cluster IP Service | ||||||
|  | 		{ | ||||||
|  | 			qname: "_http._tcp.svc-dual-stack.testns.svc.interwebs.test.", | ||||||
|  | 			qtype: dns.TypeSRV, | ||||||
|  | 			answer: []svcAns{ | ||||||
|  | 				{host: "10.0.0.2", key: "/" + coredns + "/test/interwebs/svc/testns/svc-dual-stack"}, | ||||||
|  | 				{host: "10::2", key: "/" + coredns + "/test/interwebs/svc/testns/svc-dual-stack"}, | ||||||
|  | 			}, | ||||||
|  | 		}, | ||||||
|  |  | ||||||
| 		// External Services | 		// External Services | ||||||
| 		{qname: "external.testns.svc.interwebs.test.", qtype: dns.TypeCNAME, answer: svcAns{host: "coredns.io", key: "/" + coredns + "/test/interwebs/svc/testns/external"}}, | 		{qname: "external.testns.svc.interwebs.test.", qtype: dns.TypeCNAME, answer: []svcAns{{host: "coredns.io", key: "/" + coredns + "/test/interwebs/svc/testns/external"}}}, | ||||||
|  |  | ||||||
| 		// Headless Services | 		// Headless Services | ||||||
| 		{qname: "hdls1.testns.svc.interwebs.test.", qtype: dns.TypeA, answer: svcAns{host: "172.0.0.2", key: "/" + coredns + "/test/interwebs/svc/testns/hdls1/172-0-0-2"}}, | 		{qname: "hdls1.testns.svc.interwebs.test.", qtype: dns.TypeA, answer: []svcAns{{host: "172.0.0.2", key: "/" + coredns + "/test/interwebs/svc/testns/hdls1/172-0-0-2"}}}, | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
| 	for i, test := range tests { | 	for i, test := range tests { | ||||||
| @@ -281,16 +307,18 @@ func TestServices(t *testing.T) { | |||||||
| 			t.Errorf("Test %d: got error '%v'", i, e) | 			t.Errorf("Test %d: got error '%v'", i, e) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
| 		if len(svcs) != 1 { | 		if len(svcs) != len(test.answer) { | ||||||
| 			t.Errorf("Test %d, expected 1 answer, got %v", i, len(svcs)) | 			t.Errorf("Test %d, expected %v answer, got %v", i, len(test.answer), len(svcs)) | ||||||
| 			continue | 			continue | ||||||
| 		} | 		} | ||||||
|  |  | ||||||
| 		if test.answer.host != svcs[0].Host { | 		for j := range svcs { | ||||||
| 			t.Errorf("Test %d, expected host '%v', got '%v'", i, test.answer.host, svcs[0].Host) | 			if test.answer[j].host != svcs[j].Host { | ||||||
| 		} | 				t.Errorf("Test %d, expected host '%v', got '%v'", i, test.answer[j].host, svcs[j].Host) | ||||||
| 		if test.answer.key != svcs[0].Key { | 			} | ||||||
| 			t.Errorf("Test %d, expected key '%v', got '%v'", i, test.answer.key, svcs[0].Key) | 			if test.answer[j].key != svcs[j].Key { | ||||||
|  | 				t.Errorf("Test %d, expected key '%v', got '%v'", i, test.answer[j].key, svcs[j].Key) | ||||||
|  | 			} | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ import ( | |||||||
| 	"strings" | 	"strings" | ||||||
|  |  | ||||||
| 	"github.com/miekg/dns" | 	"github.com/miekg/dns" | ||||||
| 	api "k8s.io/api/core/v1" |  | ||||||
| ) | ) | ||||||
|  |  | ||||||
| func isDefaultNS(name, zone string) bool { | func isDefaultNS(name, zone string) bool { | ||||||
| @@ -37,7 +36,7 @@ func (k *Kubernetes) nsAddrs(external bool, zone string) []dns.RR { | |||||||
| 					continue | 					continue | ||||||
| 				} | 				} | ||||||
| 				svcName := strings.Join([]string{svc.Name, svc.Namespace, Svc, zone}, ".") | 				svcName := strings.Join([]string{svc.Name, svc.Namespace, Svc, zone}, ".") | ||||||
| 				if svc.ClusterIP == api.ClusterIPNone { | 				if svc.Headless() { | ||||||
| 					// For a headless service, use the endpoints IPs | 					// For a headless service, use the endpoints IPs | ||||||
| 					for _, s := range endpoint.Subsets { | 					for _, s := range endpoint.Subsets { | ||||||
| 						for _, a := range s.Addresses { | 						for _, a := range s.Addresses { | ||||||
| @@ -46,8 +45,10 @@ func (k *Kubernetes) nsAddrs(external bool, zone string) []dns.RR { | |||||||
| 						} | 						} | ||||||
| 					} | 					} | ||||||
| 				} else { | 				} else { | ||||||
| 					svcNames = append(svcNames, svcName) | 					for _, clusterIP := range svc.ClusterIPs { | ||||||
| 					svcIPs = append(svcIPs, net.ParseIP(svc.ClusterIP)) | 						svcNames = append(svcNames, svcName) | ||||||
|  | 						svcIPs = append(svcIPs, net.ParseIP(clusterIP)) | ||||||
|  | 					} | ||||||
| 				} | 				} | ||||||
| 			} | 			} | ||||||
| 		} | 		} | ||||||
|   | |||||||
| @@ -37,19 +37,19 @@ func (a APIConnTest) SvcIndex(s string) []*object.Service { | |||||||
| func (APIConnTest) ServiceList() []*object.Service { | func (APIConnTest) ServiceList() []*object.Service { | ||||||
| 	svcs := []*object.Service{ | 	svcs := []*object.Service{ | ||||||
| 		{ | 		{ | ||||||
| 			Name:      "dns-service", | 			Name:       "dns-service", | ||||||
| 			Namespace: "kube-system", | 			Namespace:  "kube-system", | ||||||
| 			ClusterIP: "10.0.0.111", | 			ClusterIPs: []string{"10.0.0.111"}, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			Name:      "hdls-dns-service", | 			Name:       "hdls-dns-service", | ||||||
| 			Namespace: "kube-system", | 			Namespace:  "kube-system", | ||||||
| 			ClusterIP: api.ClusterIPNone, | 			ClusterIPs: []string{api.ClusterIPNone}, | ||||||
| 		}, | 		}, | ||||||
| 		{ | 		{ | ||||||
| 			Name:      "dns6-service", | 			Name:       "dns6-service", | ||||||
| 			Namespace: "kube-system", | 			Namespace:  "kube-system", | ||||||
| 			ClusterIP: "10::111", | 			ClusterIPs: []string{"10::111"}, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 	return svcs | 	return svcs | ||||||
|   | |||||||
| @@ -67,7 +67,7 @@ func (l *EndpointLatencyRecorder) record() { | |||||||
| 	// don't change very often (comparing to much more frequent endpoints changes), cases when this method | 	// don't change very often (comparing to much more frequent endpoints changes), cases when this method | ||||||
| 	// will return wrong answer should be relatively rare. Because of that we intentionally accept this | 	// will return wrong answer should be relatively rare. Because of that we intentionally accept this | ||||||
| 	// flaw to keep the solution simple. | 	// flaw to keep the solution simple. | ||||||
| 	isHeadless := len(l.Services) == 1 && l.Services[0].ClusterIP == api.ClusterIPNone | 	isHeadless := len(l.Services) == 1 && l.Services[0].Headless() | ||||||
|  |  | ||||||
| 	if !isHeadless || l.TT.IsZero() { | 	if !isHeadless || l.TT.IsZero() { | ||||||
| 		return | 		return | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ type Service struct { | |||||||
| 	Name         string | 	Name         string | ||||||
| 	Namespace    string | 	Namespace    string | ||||||
| 	Index        string | 	Index        string | ||||||
| 	ClusterIP    string | 	ClusterIPs   []string | ||||||
| 	Type         api.ServiceType | 	Type         api.ServiceType | ||||||
| 	ExternalName string | 	ExternalName string | ||||||
| 	Ports        []api.ServicePort | 	Ports        []api.ServicePort | ||||||
| @@ -40,13 +40,19 @@ func ToService(obj meta.Object) (meta.Object, error) { | |||||||
| 		Name:         svc.GetName(), | 		Name:         svc.GetName(), | ||||||
| 		Namespace:    svc.GetNamespace(), | 		Namespace:    svc.GetNamespace(), | ||||||
| 		Index:        ServiceKey(svc.GetName(), svc.GetNamespace()), | 		Index:        ServiceKey(svc.GetName(), svc.GetNamespace()), | ||||||
| 		ClusterIP:    svc.Spec.ClusterIP, |  | ||||||
| 		Type:         svc.Spec.Type, | 		Type:         svc.Spec.Type, | ||||||
| 		ExternalName: svc.Spec.ExternalName, | 		ExternalName: svc.Spec.ExternalName, | ||||||
|  |  | ||||||
| 		ExternalIPs: make([]string, len(svc.Status.LoadBalancer.Ingress)+len(svc.Spec.ExternalIPs)), | 		ExternalIPs: make([]string, len(svc.Status.LoadBalancer.Ingress)+len(svc.Spec.ExternalIPs)), | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | 	if len(svc.Spec.ClusterIPs) > 0 { | ||||||
|  | 		s.ClusterIPs = make([]string, len(svc.Spec.ClusterIPs)) | ||||||
|  | 		copy(s.ClusterIPs, svc.Spec.ClusterIPs) | ||||||
|  | 	} else { | ||||||
|  | 		s.ClusterIPs = []string{svc.Spec.ClusterIP} | ||||||
|  | 	} | ||||||
|  |  | ||||||
| 	if len(svc.Spec.Ports) == 0 { | 	if len(svc.Spec.Ports) == 0 { | ||||||
| 		// Add sentinel if there are no ports. | 		// Add sentinel if there are no ports. | ||||||
| 		s.Ports = []api.ServicePort{{Port: -1}} | 		s.Ports = []api.ServicePort{{Port: -1}} | ||||||
| @@ -70,6 +76,11 @@ func ToService(obj meta.Object) (meta.Object, error) { | |||||||
| 	return s, nil | 	return s, nil | ||||||
| } | } | ||||||
|  |  | ||||||
|  | // Headless returns true if the service is headless | ||||||
|  | func (s *Service) Headless() bool { | ||||||
|  | 	return s.ClusterIPs[0] == api.ClusterIPNone | ||||||
|  | } | ||||||
|  |  | ||||||
| var _ runtime.Object = &Service{} | var _ runtime.Object = &Service{} | ||||||
|  |  | ||||||
| // DeepCopyObject implements the ObjectKind interface. | // DeepCopyObject implements the ObjectKind interface. | ||||||
| @@ -79,12 +90,13 @@ func (s *Service) DeepCopyObject() runtime.Object { | |||||||
| 		Name:         s.Name, | 		Name:         s.Name, | ||||||
| 		Namespace:    s.Namespace, | 		Namespace:    s.Namespace, | ||||||
| 		Index:        s.Index, | 		Index:        s.Index, | ||||||
| 		ClusterIP:    s.ClusterIP, |  | ||||||
| 		Type:         s.Type, | 		Type:         s.Type, | ||||||
| 		ExternalName: s.ExternalName, | 		ExternalName: s.ExternalName, | ||||||
|  | 		ClusterIPs:   make([]string, len(s.ClusterIPs)), | ||||||
| 		Ports:        make([]api.ServicePort, len(s.Ports)), | 		Ports:        make([]api.ServicePort, len(s.Ports)), | ||||||
| 		ExternalIPs:  make([]string, len(s.ExternalIPs)), | 		ExternalIPs:  make([]string, len(s.ExternalIPs)), | ||||||
| 	} | 	} | ||||||
|  | 	copy(s1.ClusterIPs, s.ClusterIPs) | ||||||
| 	copy(s1.Ports, s.Ports) | 	copy(s1.Ports, s.Ports) | ||||||
| 	copy(s1.ExternalIPs, s.ExternalIPs) | 	copy(s1.ExternalIPs, s.ExternalIPs) | ||||||
| 	return s1 | 	return s1 | ||||||
|   | |||||||
| @@ -30,10 +30,10 @@ func (APIConnReverseTest) SvcIndex(svc string) []*object.Service { | |||||||
| 	} | 	} | ||||||
| 	svcs := []*object.Service{ | 	svcs := []*object.Service{ | ||||||
| 		{ | 		{ | ||||||
| 			Name:      "svc1", | 			Name:       "svc1", | ||||||
| 			Namespace: "testns", | 			Namespace:  "testns", | ||||||
| 			ClusterIP: "192.168.1.100", | 			ClusterIPs: []string{"192.168.1.100"}, | ||||||
| 			Ports:     []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}}, | 			Ports:      []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}}, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 	return svcs | 	return svcs | ||||||
| @@ -46,10 +46,10 @@ func (APIConnReverseTest) SvcIndexReverse(ip string) []*object.Service { | |||||||
| 	} | 	} | ||||||
| 	svcs := []*object.Service{ | 	svcs := []*object.Service{ | ||||||
| 		{ | 		{ | ||||||
| 			Name:      "svc1", | 			Name:       "svc1", | ||||||
| 			Namespace: "testns", | 			Namespace:  "testns", | ||||||
| 			ClusterIP: "192.168.1.100", | 			ClusterIPs: []string{"192.168.1.100"}, | ||||||
| 			Ports:     []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}}, | 			Ports:      []api.ServicePort{{Name: "http", Protocol: "tcp", Port: 80}}, | ||||||
| 		}, | 		}, | ||||||
| 	} | 	} | ||||||
| 	return svcs | 	return svcs | ||||||
|   | |||||||
| @@ -50,13 +50,16 @@ func (k *Kubernetes) Transfer(zone string, serial uint32) (<-chan []dns.RR, erro | |||||||
| 			switch svc.Type { | 			switch svc.Type { | ||||||
|  |  | ||||||
| 			case api.ServiceTypeClusterIP, api.ServiceTypeNodePort, api.ServiceTypeLoadBalancer: | 			case api.ServiceTypeClusterIP, api.ServiceTypeNodePort, api.ServiceTypeLoadBalancer: | ||||||
| 				clusterIP := net.ParseIP(svc.ClusterIP) | 				clusterIP := net.ParseIP(svc.ClusterIPs[0]) | ||||||
| 				if clusterIP != nil { | 				if clusterIP != nil { | ||||||
| 					s := msg.Service{Host: svc.ClusterIP, TTL: k.ttl} | 					var host string | ||||||
| 					s.Key = strings.Join(svcBase, "/") | 					for _, ip := range svc.ClusterIPs { | ||||||
|  | 						s := msg.Service{Host: ip, TTL: k.ttl} | ||||||
|  | 						s.Key = strings.Join(svcBase, "/") | ||||||
|  |  | ||||||
| 					// Change host from IP to Name for SRV records | 						// Change host from IP to Name for SRV records | ||||||
| 					host := emitAddressRecord(ch, s) | 						host = emitAddressRecord(ch, s) | ||||||
|  | 					} | ||||||
|  |  | ||||||
| 					for _, p := range svc.Ports { | 					for _, p := range svc.Ports { | ||||||
| 						s := msg.Service{Host: host, Port: int(p.Port), TTL: k.ttl} | 						s := msg.Service{Host: host, Port: int(p.Port), TTL: k.ttl} | ||||||
|   | |||||||
| @@ -113,6 +113,10 @@ hdls1.testns.svc.cluster.local.	5	IN	AAAA	5678:abcd::2 | |||||||
| _http._tcp.hdls1.testns.svc.cluster.local.	5	IN	SRV	0 16 80 5678-abcd--2.hdls1.testns.svc.cluster.local. | _http._tcp.hdls1.testns.svc.cluster.local.	5	IN	SRV	0 16 80 5678-abcd--2.hdls1.testns.svc.cluster.local. | ||||||
| hdlsprtls.testns.svc.cluster.local.	5	IN	A	172.0.0.20 | hdlsprtls.testns.svc.cluster.local.	5	IN	A	172.0.0.20 | ||||||
| 172-0-0-20.hdlsprtls.testns.svc.cluster.local.	5	IN	A	172.0.0.20 | 172-0-0-20.hdlsprtls.testns.svc.cluster.local.	5	IN	A	172.0.0.20 | ||||||
|  | svc-dual-stack.testns.svc.cluster.local.	5	IN	A	10.0.0.3 | ||||||
|  | svc-dual-stack.testns.svc.cluster.local.	5	IN	AAAA	10::3 | ||||||
|  | svc-dual-stack.testns.svc.cluster.local.	5	IN	SRV	0 100 80 svc-dual-stack.testns.svc.cluster.local. | ||||||
|  | _http._tcp.svc-dual-stack.testns.svc.cluster.local.	5	IN	SRV	0 100 80 svc-dual-stack.testns.svc.cluster.local. | ||||||
| svc1.testns.svc.cluster.local.	5	IN	A	10.0.0.1 | svc1.testns.svc.cluster.local.	5	IN	A	10.0.0.1 | ||||||
| svc1.testns.svc.cluster.local.	5	IN	SRV	0 100 80 svc1.testns.svc.cluster.local. | svc1.testns.svc.cluster.local.	5	IN	SRV	0 100 80 svc1.testns.svc.cluster.local. | ||||||
| _http._tcp.svc1.testns.svc.cluster.local.	5	IN	SRV	0 100 80 svc1.testns.svc.cluster.local. | _http._tcp.svc1.testns.svc.cluster.local.	5	IN	SRV	0 100 80 svc1.testns.svc.cluster.local. | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user