Commit 9f520cf3 authored by k8s-merge-robot's avatar k8s-merge-robot

Merge pull request #25991 from quinton-hoole/fed-service-controller

Automatic merge from submit-queue Integrate federated service DNS record management Integrate federated service DNS record management.
parents 63e58995 65e1feca
...@@ -30,6 +30,7 @@ import ( ...@@ -30,6 +30,7 @@ import (
"k8s.io/kubernetes/federation/pkg/dnsprovider" "k8s.io/kubernetes/federation/pkg/dnsprovider"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal" "k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs"
"k8s.io/kubernetes/pkg/cloudprovider/providers/gce" "k8s.io/kubernetes/pkg/cloudprovider/providers/gce"
) )
...@@ -99,3 +100,17 @@ func CreateInterface(projectID string, tokenSource oauth2.TokenSource) (*Interfa ...@@ -99,3 +100,17 @@ func CreateInterface(projectID string, tokenSource oauth2.TokenSource) (*Interfa
glog.Infof("Successfully got DNS service: %v\n", service) glog.Infof("Successfully got DNS service: %v\n", service)
return newInterfaceWithStub(projectID, internal.NewService(service)), nil return newInterfaceWithStub(projectID, internal.NewService(service)), nil
} }
// NewFakeInterface returns a fake clouddns interface, useful for unit testing purposes.
func NewFakeInterface() (dnsprovider.Interface, error) {
service := stubs.NewService()
interface_ := newInterfaceWithStub("", service)
zones := service.ManagedZones_
// Add a fake zone to test against.
zone := &stubs.ManagedZone{zones, "example.com", []stubs.ResourceRecordSet{}}
call := zones.Create(interface_.project(), zone)
if _, err := call.Do(); err != nil {
return nil, err
}
return interface_, nil
}
...@@ -23,7 +23,6 @@ import ( ...@@ -23,7 +23,6 @@ import (
"testing" "testing"
"k8s.io/kubernetes/federation/pkg/dnsprovider" "k8s.io/kubernetes/federation/pkg/dnsprovider"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns/internal/stubs"
"k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype" "k8s.io/kubernetes/federation/pkg/dnsprovider/rrstype"
) )
...@@ -31,21 +30,7 @@ func newTestInterface() (dnsprovider.Interface, error) { ...@@ -31,21 +30,7 @@ func newTestInterface() (dnsprovider.Interface, error) {
// Use this to test the real cloud service - insert appropriate project-id. Default token source will be used. See // Use this to test the real cloud service - insert appropriate project-id. Default token source will be used. See
// https://github.com/golang/oauth2/blob/master/google/default.go for details. // https://github.com/golang/oauth2/blob/master/google/default.go for details.
// i, err := dnsprovider.GetDnsProvider(ProviderName, strings.NewReader("\n[global]\nproject-id = federation0-cluster00")) // i, err := dnsprovider.GetDnsProvider(ProviderName, strings.NewReader("\n[global]\nproject-id = federation0-cluster00"))
return newFakeInterface() // Use this to stub out the entire cloud service return NewFakeInterface() // Use this to stub out the entire cloud service
}
func newFakeInterface() (dnsprovider.Interface, error) {
service := stubs.NewService()
interface_ := newInterfaceWithStub("", service)
zones := service.ManagedZones_
// Add a fake zone to test against.
zone := &stubs.ManagedZone{zones, "example.com", []stubs.ResourceRecordSet{}}
call := zones.Create(interface_.project(), zone)
_, err := call.Do()
if err != nil {
return nil, err
}
return interface_, nil
} }
var interface_ dnsprovider.Interface var interface_ dnsprovider.Interface
......
...@@ -42,7 +42,7 @@ func (sc *ServiceController) clusterEndpointWorker() { ...@@ -42,7 +42,7 @@ func (sc *ServiceController) clusterEndpointWorker() {
return return
} }
defer cache.endpointQueue.Done(key) defer cache.endpointQueue.Done(key)
err := sc.clusterCache.syncEndpoint(key.(string), clusterName, cache, sc.serviceCache, fedClient) err := sc.clusterCache.syncEndpoint(key.(string), clusterName, cache, sc.serviceCache, fedClient, sc)
if err != nil { if err != nil {
glog.V(2).Infof("Failed to sync endpoint: %+v", err) glog.V(2).Infof("Failed to sync endpoint: %+v", err)
} }
...@@ -54,7 +54,7 @@ func (sc *ServiceController) clusterEndpointWorker() { ...@@ -54,7 +54,7 @@ func (sc *ServiceController) clusterEndpointWorker() {
// Whenever there is change on endpoint, the federation service should be updated // Whenever there is change on endpoint, the federation service should be updated
// key is the namespaced name of endpoint // key is the namespaced name of endpoint
func (cc *clusterClientCache) syncEndpoint(key, clusterName string, clusterCache *clusterCache, serviceCache *serviceCache, fedClient federationclientset.Interface) error { func (cc *clusterClientCache) syncEndpoint(key, clusterName string, clusterCache *clusterCache, serviceCache *serviceCache, fedClient federationclientset.Interface, serviceController *ServiceController) error {
cachedService, ok := serviceCache.get(key) cachedService, ok := serviceCache.get(key)
if !ok { if !ok {
// here we filtered all non-federation services // here we filtered all non-federation services
...@@ -70,19 +70,19 @@ func (cc *clusterClientCache) syncEndpoint(key, clusterName string, clusterCache ...@@ -70,19 +70,19 @@ func (cc *clusterClientCache) syncEndpoint(key, clusterName string, clusterCache
endpoint, ok := endpointInterface.(*api.Endpoints) endpoint, ok := endpointInterface.(*api.Endpoints)
if ok { if ok {
glog.V(4).Infof("Found endpoint for federation service %s/%s from cluster %s", endpoint.Namespace, endpoint.Name, clusterName) glog.V(4).Infof("Found endpoint for federation service %s/%s from cluster %s", endpoint.Namespace, endpoint.Name, clusterName)
err = cc.processEndpointUpdate(cachedService, endpoint, clusterName) err = cc.processEndpointUpdate(cachedService, endpoint, clusterName, serviceController)
} else { } else {
_, ok := endpointInterface.(cache.DeletedFinalStateUnknown) _, ok := endpointInterface.(cache.DeletedFinalStateUnknown)
if !ok { if !ok {
return fmt.Errorf("Object contained wasn't a service or a deleted key: %+v", endpointInterface) return fmt.Errorf("Object contained wasn't a service or a deleted key: %+v", endpointInterface)
} }
glog.Infof("Found tombstone for %v", key) glog.Infof("Found tombstone for %v", key)
err = cc.processEndpointDeletion(cachedService, clusterName) err = cc.processEndpointDeletion(cachedService, clusterName, serviceController)
} }
} else { } else {
// service absence in store means watcher caught the deletion, ensure LB info is cleaned // service absence in store means watcher caught the deletion, ensure LB info is cleaned
glog.Infof("Can not get endpoint %v for cluster %s from endpointStore", key, clusterName) glog.Infof("Can not get endpoint %v for cluster %s from endpointStore", key, clusterName)
err = cc.processEndpointDeletion(cachedService, clusterName) err = cc.processEndpointDeletion(cachedService, clusterName, serviceController)
} }
if err != nil { if err != nil {
glog.Errorf("Failed to sync service: %+v, put back to service queue", err) glog.Errorf("Failed to sync service: %+v, put back to service queue", err)
...@@ -92,7 +92,7 @@ func (cc *clusterClientCache) syncEndpoint(key, clusterName string, clusterCache ...@@ -92,7 +92,7 @@ func (cc *clusterClientCache) syncEndpoint(key, clusterName string, clusterCache
return nil return nil
} }
func (cc *clusterClientCache) processEndpointDeletion(cachedService *cachedService, clusterName string) error { func (cc *clusterClientCache) processEndpointDeletion(cachedService *cachedService, clusterName string, serviceController *ServiceController) error {
glog.V(4).Infof("Processing endpoint update for %s/%s, cluster %s", cachedService.lastState.Namespace, cachedService.lastState.Name, clusterName) glog.V(4).Infof("Processing endpoint update for %s/%s, cluster %s", cachedService.lastState.Namespace, cachedService.lastState.Name, clusterName)
var err error var err error
cachedService.rwlock.Lock() cachedService.rwlock.Lock()
...@@ -103,13 +103,12 @@ func (cc *clusterClientCache) processEndpointDeletion(cachedService *cachedServi ...@@ -103,13 +103,12 @@ func (cc *clusterClientCache) processEndpointDeletion(cachedService *cachedServi
if ok { if ok {
// endpoints lost, clean dns record // endpoints lost, clean dns record
glog.V(4).Infof("Cached endpoint was not found for %s/%s, cluster %s, building one", cachedService.lastState.Namespace, cachedService.lastState.Name, clusterName) glog.V(4).Infof("Cached endpoint was not found for %s/%s, cluster %s, building one", cachedService.lastState.Namespace, cachedService.lastState.Name, clusterName)
// TODO: need to integrate with dns.go:ensureDNSRecords
for i := 0; i < clientRetryCount; i++ { for i := 0; i < clientRetryCount; i++ {
err := ensureDNSRecords(clusterName, cachedService) if err := serviceController.ensureDnsRecords(clusterName, cachedService); err == nil {
if err == nil {
delete(cachedService.endpointMap, clusterName) delete(cachedService.endpointMap, clusterName)
return nil return nil
} }
glog.V(4).Infof("Error ensuring DNS Records: %v", err)
time.Sleep(cachedService.nextDNSUpdateDelay()) time.Sleep(cachedService.nextDNSUpdateDelay())
} }
} }
...@@ -118,7 +117,7 @@ func (cc *clusterClientCache) processEndpointDeletion(cachedService *cachedServi ...@@ -118,7 +117,7 @@ func (cc *clusterClientCache) processEndpointDeletion(cachedService *cachedServi
// Update dns info when endpoint update event received // Update dns info when endpoint update event received
// We do not care about the endpoint info, what we need to make sure here is len(endpoints.subsets)>0 // We do not care about the endpoint info, what we need to make sure here is len(endpoints.subsets)>0
func (cc *clusterClientCache) processEndpointUpdate(cachedService *cachedService, endpoint *api.Endpoints, clusterName string) error { func (cc *clusterClientCache) processEndpointUpdate(cachedService *cachedService, endpoint *api.Endpoints, clusterName string, serviceController *ServiceController) error {
glog.V(4).Infof("Processing endpoint update for %s/%s, cluster %s", endpoint.Namespace, endpoint.Name, clusterName) glog.V(4).Infof("Processing endpoint update for %s/%s, cluster %s", endpoint.Namespace, endpoint.Name, clusterName)
cachedService.rwlock.Lock() cachedService.rwlock.Lock()
defer cachedService.rwlock.Unlock() defer cachedService.rwlock.Unlock()
...@@ -132,12 +131,11 @@ func (cc *clusterClientCache) processEndpointUpdate(cachedService *cachedService ...@@ -132,12 +131,11 @@ func (cc *clusterClientCache) processEndpointUpdate(cachedService *cachedService
// first time get endpoints, update dns record // first time get endpoints, update dns record
glog.V(4).Infof("Cached endpoint was not found for %s/%s, cluster %s, building one", endpoint.Namespace, endpoint.Name, clusterName) glog.V(4).Infof("Cached endpoint was not found for %s/%s, cluster %s, building one", endpoint.Namespace, endpoint.Name, clusterName)
cachedService.endpointMap[clusterName] = 1 cachedService.endpointMap[clusterName] = 1
err := ensureDNSRecords(clusterName, cachedService) if err := serviceController.ensureDnsRecords(clusterName, cachedService); err != nil {
if err != nil { glog.V(4).Infof("Error ensuring DNS Records: %v", err)
// TODO: need to integrate with dns.go:ensureDNSRecords
for i := 0; i < clientRetryCount; i++ { for i := 0; i < clientRetryCount; i++ {
time.Sleep(cachedService.nextDNSUpdateDelay()) time.Sleep(cachedService.nextDNSUpdateDelay())
err := ensureDNSRecords(clusterName, cachedService) err := serviceController.ensureDnsRecords(clusterName, cachedService)
if err == nil { if err == nil {
return nil return nil
} }
......
...@@ -19,9 +19,25 @@ package service ...@@ -19,9 +19,25 @@ package service
import ( import (
"testing" "testing"
"k8s.io/kubernetes/federation/apis/federation"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns" // Only for unit testing purposes.
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/util/sets"
) )
var fakeDns, _ = clouddns.NewFakeInterface() // No need to check for unsupported interfaces, as the fake interface supports everything that's required.
var fakeDnsZones, _ = fakeDns.Zones()
var fakeServiceController = ServiceController{
dns: fakeDns,
dnsZones: fakeDnsZones,
serviceCache: &serviceCache{fedServiceMap: make(map[string]*cachedService)},
clusterCache: &clusterClientCache{
clientMap: make(map[string]*clusterCache),
},
knownClusterSet: make(sets.String),
}
func buildEndpoint(subsets [][]string) *api.Endpoints { func buildEndpoint(subsets [][]string) *api.Endpoints {
endpoint := &api.Endpoints{ endpoint := &api.Endpoints{
Subsets: []api.EndpointSubset{ Subsets: []api.EndpointSubset{
...@@ -69,8 +85,9 @@ func TestProcessEndpointUpdate(t *testing.T) { ...@@ -69,8 +85,9 @@ func TestProcessEndpointUpdate(t *testing.T) {
1, 1,
}, },
} }
for _, test := range tests { for _, test := range tests {
cc.processEndpointUpdate(test.cachedService, test.endpoint, test.clusterName) cc.processEndpointUpdate(test.cachedService, test.endpoint, test.clusterName, &fakeServiceController)
if test.expectResult != test.cachedService.endpointMap[test.clusterName] { if test.expectResult != test.cachedService.endpointMap[test.clusterName] {
t.Errorf("Test failed for %s, expected %v, saw %v", test.name, test.expectResult, test.cachedService.endpointMap[test.clusterName]) t.Errorf("Test failed for %s, expected %v, saw %v", test.name, test.expectResult, test.cachedService.endpointMap[test.clusterName])
} }
...@@ -78,8 +95,18 @@ func TestProcessEndpointUpdate(t *testing.T) { ...@@ -78,8 +95,18 @@ func TestProcessEndpointUpdate(t *testing.T) {
} }
func TestProcessEndpointDeletion(t *testing.T) { func TestProcessEndpointDeletion(t *testing.T) {
clusterName := "foo"
cc := clusterClientCache{ cc := clusterClientCache{
clientMap: make(map[string]*clusterCache), clientMap: map[string]*clusterCache{
clusterName: {
cluster: &federation.Cluster{
Status: federation.ClusterStatus{
Zones: []string{"foozone"},
Region: "fooregion",
},
},
},
},
} }
tests := []struct { tests := []struct {
name string name string
...@@ -95,7 +122,7 @@ func TestProcessEndpointDeletion(t *testing.T) { ...@@ -95,7 +122,7 @@ func TestProcessEndpointDeletion(t *testing.T) {
endpointMap: make(map[string]int), endpointMap: make(map[string]int),
}, },
buildEndpoint([][]string{{"ip1", ""}}), buildEndpoint([][]string{{"ip1", ""}}),
"foo", clusterName,
0, 0,
}, },
{ {
...@@ -103,16 +130,17 @@ func TestProcessEndpointDeletion(t *testing.T) { ...@@ -103,16 +130,17 @@ func TestProcessEndpointDeletion(t *testing.T) {
&cachedService{ &cachedService{
lastState: &api.Service{}, lastState: &api.Service{},
endpointMap: map[string]int{ endpointMap: map[string]int{
"foo": 1, clusterName: 1,
}, },
}, },
buildEndpoint([][]string{{"ip1", ""}}), buildEndpoint([][]string{{"ip1", ""}}),
"foo", clusterName,
0, 0,
}, },
} }
fakeServiceController.clusterCache = &cc
for _, test := range tests { for _, test := range tests {
cc.processEndpointDeletion(test.cachedService, test.clusterName) cc.processEndpointDeletion(test.cachedService, test.clusterName, &fakeServiceController)
if test.expectResult != test.cachedService.endpointMap[test.clusterName] { if test.expectResult != test.cachedService.endpointMap[test.clusterName] {
t.Errorf("Test failed for %s, expected %v, saw %v", test.name, test.expectResult, test.cachedService.endpointMap[test.clusterName]) t.Errorf("Test failed for %s, expected %v, saw %v", test.name, test.expectResult, test.cachedService.endpointMap[test.clusterName])
} }
......
...@@ -78,14 +78,10 @@ type cachedService struct { ...@@ -78,14 +78,10 @@ type cachedService struct {
// key clusterName // key clusterName
// value is a flag that if there is ready address, 1 means there is ready address, 0 means no ready address // value is a flag that if there is ready address, 1 means there is ready address, 0 means no ready address
endpointMap map[string]int endpointMap map[string]int
// cluster service map hold serivice status info from kubernetes clusters // serviceStatusMap map holds service status info from kubernetes clusters, keyed on clusterName
// key clusterName
serviceStatusMap map[string]api.LoadBalancerStatus serviceStatusMap map[string]api.LoadBalancerStatus
// Ensures only one goroutine can operate on this service at any given time. // Ensures only one goroutine can operate on this service at any given time.
rwlock sync.Mutex rwlock sync.Mutex
// Controls error back-off for procceeding federation service to k8s clusters // Controls error back-off for procceeding federation service to k8s clusters
lastRetryDelay time.Duration lastRetryDelay time.Duration
// Controls error back-off for updating federation service back to federation apiserver // Controls error back-off for updating federation service back to federation apiserver
...@@ -104,9 +100,11 @@ type serviceCache struct { ...@@ -104,9 +100,11 @@ type serviceCache struct {
type ServiceController struct { type ServiceController struct {
dns dnsprovider.Interface dns dnsprovider.Interface
federationClient federationclientset.Interface federationClient federationclientset.Interface
zones []dnsprovider.Zone federationName string
serviceCache *serviceCache // each federation should be configured with a single zone (e.g. "mycompany.com")
clusterCache *clusterClientCache dnsZones dnsprovider.Zones
serviceCache *serviceCache
clusterCache *clusterClientCache
// A store of services, populated by the serviceController // A store of services, populated by the serviceController
serviceStore cache.StoreToServiceLister serviceStore cache.StoreToServiceLister
// Watches changes to all services // Watches changes to all services
...@@ -690,7 +688,7 @@ func (s *ServiceController) lockedUpdateDNSRecords(service *cachedService, clust ...@@ -690,7 +688,7 @@ func (s *ServiceController) lockedUpdateDNSRecords(service *cachedService, clust
for key := range s.clusterCache.clientMap { for key := range s.clusterCache.clientMap {
for _, clusterName := range clusterNames { for _, clusterName := range clusterNames {
if key == clusterName { if key == clusterName {
ensureDNSRecords(clusterName, service) s.ensureDnsRecords(clusterName, service)
} }
} }
} }
......
...@@ -17,22 +17,38 @@ limitations under the License. ...@@ -17,22 +17,38 @@ limitations under the License.
package service package service
import ( import (
"sync"
"testing" "testing"
"k8s.io/kubernetes/federation/apis/federation" "k8s.io/kubernetes/federation/apis/federation"
"k8s.io/kubernetes/federation/pkg/dnsprovider/providers/google/clouddns" // Only for unit testing purposes.
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/util/sets"
) )
func TestGetClusterConditionPredicate(t *testing.T) { func TestGetClusterConditionPredicate(t *testing.T) {
fakedns, _ := clouddns.NewFakeInterface() // No need to check for unsupported interfaces, as the fake interface supports everything that's required.
serviceController := ServiceController{
dns: fakedns,
serviceCache: &serviceCache{fedServiceMap: make(map[string]*cachedService)},
clusterCache: &clusterClientCache{
rwlock: sync.Mutex{},
clientMap: make(map[string]*clusterCache),
},
knownClusterSet: make(sets.String),
}
tests := []struct { tests := []struct {
cluster federation.Cluster cluster federation.Cluster
expectAccept bool expectAccept bool
name string name string
serviceController *ServiceController
}{ }{
{ {
cluster: federation.Cluster{}, cluster: federation.Cluster{},
expectAccept: false, expectAccept: false,
name: "empty", name: "empty",
serviceController: &serviceController,
}, },
{ {
cluster: federation.Cluster{ cluster: federation.Cluster{
...@@ -42,8 +58,9 @@ func TestGetClusterConditionPredicate(t *testing.T) { ...@@ -42,8 +58,9 @@ func TestGetClusterConditionPredicate(t *testing.T) {
}, },
}, },
}, },
expectAccept: true, expectAccept: true,
name: "basic", name: "basic",
serviceController: &serviceController,
}, },
{ {
cluster: federation.Cluster{ cluster: federation.Cluster{
...@@ -53,8 +70,9 @@ func TestGetClusterConditionPredicate(t *testing.T) { ...@@ -53,8 +70,9 @@ func TestGetClusterConditionPredicate(t *testing.T) {
}, },
}, },
}, },
expectAccept: false, expectAccept: false,
name: "notready", name: "notready",
serviceController: &serviceController,
}, },
} }
pred := getClusterConditionPredicate() pred := getClusterConditionPredicate()
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment