Commit 97d6494c authored by Quinton Hoole's avatar Quinton Hoole

Add Federated Ingress Controller.

parent b8990593
...@@ -30,6 +30,7 @@ import ( ...@@ -30,6 +30,7 @@ import (
"k8s.io/kubernetes/federation/cmd/federation-controller-manager/app/options" "k8s.io/kubernetes/federation/cmd/federation-controller-manager/app/options"
"k8s.io/kubernetes/federation/pkg/dnsprovider" "k8s.io/kubernetes/federation/pkg/dnsprovider"
clustercontroller "k8s.io/kubernetes/federation/pkg/federation-controller/cluster" clustercontroller "k8s.io/kubernetes/federation/pkg/federation-controller/cluster"
ingresscontroller "k8s.io/kubernetes/federation/pkg/federation-controller/ingress"
namespacecontroller "k8s.io/kubernetes/federation/pkg/federation-controller/namespace" namespacecontroller "k8s.io/kubernetes/federation/pkg/federation-controller/namespace"
replicasetcontroller "k8s.io/kubernetes/federation/pkg/federation-controller/replicaset" replicasetcontroller "k8s.io/kubernetes/federation/pkg/federation-controller/replicaset"
secretcontroller "k8s.io/kubernetes/federation/pkg/federation-controller/secret" secretcontroller "k8s.io/kubernetes/federation/pkg/federation-controller/secret"
...@@ -136,20 +137,19 @@ func Run(s *options.CMServer) error { ...@@ -136,20 +137,19 @@ func Run(s *options.CMServer) error {
} }
func StartControllers(s *options.CMServer, restClientCfg *restclient.Config) error { func StartControllers(s *options.CMServer, restClientCfg *restclient.Config) error {
glog.Infof("Loading client config for cluster controller %q", "cluster-controller")
ccClientset := federationclientset.NewForConfigOrDie(restclient.AddUserAgent(restClientCfg, "cluster-controller")) ccClientset := federationclientset.NewForConfigOrDie(restclient.AddUserAgent(restClientCfg, "cluster-controller"))
glog.Infof("Running cluster controller")
go clustercontroller.NewclusterController(ccClientset, s.ClusterMonitorPeriod.Duration).Run() go clustercontroller.NewclusterController(ccClientset, s.ClusterMonitorPeriod.Duration).Run()
dns, err := dnsprovider.InitDnsProvider(s.DnsProvider, s.DnsConfigFile) dns, err := dnsprovider.InitDnsProvider(s.DnsProvider, s.DnsConfigFile)
if err != nil { if err != nil {
glog.Fatalf("Cloud provider could not be initialized: %v", err) glog.Fatalf("Cloud provider could not be initialized: %v", err)
} }
scClientset := federationclientset.NewForConfigOrDie(restclient.AddUserAgent(restClientCfg, servicecontroller.UserAgentName))
servicecontroller := servicecontroller.New(scClientset, dns, s.FederationName, s.ZoneName)
if err := servicecontroller.Run(s.ConcurrentServiceSyncs, wait.NeverStop); err != nil {
glog.Errorf("Failed to start service controller: %v", err)
}
glog.Infof("Loading client config for namespace controller %q", "namespace-controller")
nsClientset := federationclientset.NewForConfigOrDie(restclient.AddUserAgent(restClientCfg, "namespace-controller")) nsClientset := federationclientset.NewForConfigOrDie(restclient.AddUserAgent(restClientCfg, "namespace-controller"))
namespaceController := namespacecontroller.NewNamespaceController(nsClientset) namespaceController := namespacecontroller.NewNamespaceController(nsClientset)
glog.Infof("Running namespace controller")
namespaceController.Run(wait.NeverStop) namespaceController.Run(wait.NeverStop)
secretcontrollerClientset := federationclientset.NewForConfigOrDie(restclient.AddUserAgent(restClientCfg, "secret-controller")) secretcontrollerClientset := federationclientset.NewForConfigOrDie(restclient.AddUserAgent(restClientCfg, "secret-controller"))
...@@ -160,5 +160,19 @@ func StartControllers(s *options.CMServer, restClientCfg *restclient.Config) err ...@@ -160,5 +160,19 @@ func StartControllers(s *options.CMServer, restClientCfg *restclient.Config) err
replicaSetController := replicasetcontroller.NewReplicaSetController(replicaSetClientset) replicaSetController := replicasetcontroller.NewReplicaSetController(replicaSetClientset)
go replicaSetController.Run(s.ConcurrentReplicaSetSyncs, wait.NeverStop) go replicaSetController.Run(s.ConcurrentReplicaSetSyncs, wait.NeverStop)
glog.Infof("Loading client config for ingress controller %q", "ingress-controller")
ingClientset := federationclientset.NewForConfigOrDie(restclient.AddUserAgent(restClientCfg, "ingress-controller"))
ingressController := ingresscontroller.NewIngressController(ingClientset)
glog.Infof("Running ingress controller")
ingressController.Run(wait.NeverStop)
glog.Infof("Loading client config for service controller %q", servicecontroller.UserAgentName)
scClientset := federationclientset.NewForConfigOrDie(restclient.AddUserAgent(restClientCfg, servicecontroller.UserAgentName))
servicecontroller := servicecontroller.New(scClientset, dns, s.FederationName, s.ZoneName)
glog.Infof("Running service controller")
if err := servicecontroller.Run(s.ConcurrentServiceSyncs, wait.NeverStop); err != nil {
glog.Errorf("Failed to start service controller: %v", err)
}
select {} select {}
} }
/*
Copyright 2016 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 ingress
import (
"fmt"
"reflect"
"testing"
"time"
federation_api "k8s.io/kubernetes/federation/apis/federation/v1beta1"
// federation_release_1_4 "k8s.io/kubernetes/federation/client/clientset_generated/federation_release_1_4"
fake_federation_release_1_4 "k8s.io/kubernetes/federation/client/clientset_generated/federation_release_1_4/fake"
"k8s.io/kubernetes/federation/pkg/federation-controller/util"
api_v1 "k8s.io/kubernetes/pkg/api/v1"
extensions_v1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
kube_release_1_4 "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_4"
fake_kube_release_1_4 "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_4/fake"
"k8s.io/kubernetes/pkg/client/testing/core"
"k8s.io/kubernetes/pkg/runtime"
"k8s.io/kubernetes/pkg/watch"
"github.com/stretchr/testify/assert"
)
func TestIngressController(t *testing.T) {
cluster1 := mkCluster("cluster1", api_v1.ConditionTrue)
cluster2 := mkCluster("cluster2", api_v1.ConditionTrue)
fakeClient := &fake_federation_release_1_4.Clientset{}
RegisterList("clusters", &fakeClient.Fake, &federation_api.ClusterList{Items: []federation_api.Cluster{*cluster1}})
RegisterList("ingresses", &fakeClient.Fake, &extensions_v1beta1.IngressList{Items: []extensions_v1beta1.Ingress{}})
ingressWatch := RegisterWatch("ingresses", &fakeClient.Fake)
clusterWatch := RegisterWatch("clusters", &fakeClient.Fake)
cluster1Client := &fake_kube_release_1_4.Clientset{}
cluster1Watch := RegisterWatch("ingresses", &cluster1Client.Fake)
RegisterList("ingresses", &cluster1Client.Fake, &extensions_v1beta1.IngressList{Items: []extensions_v1beta1.Ingress{}})
cluster1CreateChan := RegisterCopyOnCreate("ingresses", &cluster1Client.Fake, cluster1Watch)
cluster1UpdateChan := RegisterCopyOnUpdate("ingresses", &cluster1Client.Fake, cluster1Watch)
cluster2Client := &fake_kube_release_1_4.Clientset{}
cluster2Watch := RegisterWatch("ingresses", &cluster2Client.Fake)
RegisterList("ingresses", &cluster2Client.Fake, &extensions_v1beta1.IngressList{Items: []extensions_v1beta1.Ingress{}})
cluster2CreateChan := RegisterCopyOnCreate("ingresses", &cluster2Client.Fake, cluster2Watch)
ingressController := NewIngressController(fakeClient)
informer := toFederatedInformerForTestOnly(ingressController.ingressFederatedInformer)
informer.SetClientFactory(func(cluster *federation_api.Cluster) (kube_release_1_4.Interface, error) {
switch cluster.Name {
case cluster1.Name:
return cluster1Client, nil
case cluster2.Name:
return cluster2Client, nil
default:
return nil, fmt.Errorf("Unknown cluster")
}
})
ingressController.clusterAvailableDelay = time.Second
ingressController.ingressReviewDelay = 50 * time.Millisecond
ingressController.smallDelay = 20 * time.Millisecond
ingressController.updateTimeout = 5 * time.Second
stop := make(chan struct{})
ingressController.Run(stop)
ing1 := extensions_v1beta1.Ingress{
ObjectMeta: api_v1.ObjectMeta{
Name: "test-ingress",
Namespace: "mynamespace",
},
}
// Test add federated ingress.
ingressWatch.Add(&ing1)
createdIngress := GetIngressFromChan(cluster1CreateChan)
assert.NotNil(t, createdIngress)
assert.True(t, reflect.DeepEqual(&ing1, createdIngress))
// Test update federated ingress.
ing1.Annotations = map[string]string{
"A": "B",
}
ingressWatch.Modify(&ing1)
updatedIngress := GetIngressFromChan(cluster1UpdateChan)
assert.NotNil(t, updatedIngress)
assert.True(t, reflect.DeepEqual(&ing1, updatedIngress))
// Test add cluster
ing1.Annotations[staticIPAnnotationKey] = "foo" // Make sure that the base object has a static IP name first.
ingressWatch.Modify(&ing1)
clusterWatch.Add(cluster2)
createdIngress2 := GetIngressFromChan(cluster2CreateChan)
assert.NotNil(t, createdIngress2)
assert.True(t, reflect.DeepEqual(&ing1, createdIngress2))
close(stop)
}
func toFederatedInformerForTestOnly(informer util.FederatedInformer) util.FederatedInformerForTestOnly {
inter := informer.(interface{})
return inter.(util.FederatedInformerForTestOnly)
}
func mkCluster(name string, readyStatus api_v1.ConditionStatus) *federation_api.Cluster {
return &federation_api.Cluster{
ObjectMeta: api_v1.ObjectMeta{
Name: name,
},
Status: federation_api.ClusterStatus{
Conditions: []federation_api.ClusterCondition{
{Type: federation_api.ClusterReady, Status: readyStatus},
},
},
}
}
func RegisterWatch(resource string, client *core.Fake) *watch.FakeWatcher {
watcher := watch.NewFake()
client.AddWatchReactor(resource, func(action core.Action) (bool, watch.Interface, error) { return true, watcher, nil })
return watcher
}
func RegisterList(resource string, client *core.Fake, obj runtime.Object) {
client.AddReactor("list", resource, func(action core.Action) (bool, runtime.Object, error) {
return true, obj, nil
})
}
func RegisterCopyOnCreate(resource string, client *core.Fake, watcher *watch.FakeWatcher) chan runtime.Object {
objChan := make(chan runtime.Object, 100)
client.AddReactor("create", resource, func(action core.Action) (bool, runtime.Object, error) {
createAction := action.(core.CreateAction)
obj := createAction.GetObject()
go func() {
watcher.Add(obj)
objChan <- obj
}()
return true, obj, nil
})
return objChan
}
func RegisterCopyOnUpdate(resource string, client *core.Fake, watcher *watch.FakeWatcher) chan runtime.Object {
objChan := make(chan runtime.Object, 100)
client.AddReactor("update", resource, func(action core.Action) (bool, runtime.Object, error) {
updateAction := action.(core.UpdateAction)
obj := updateAction.GetObject()
go func() {
watcher.Modify(obj)
objChan <- obj
}()
return true, obj, nil
})
return objChan
}
func GetIngressFromChan(c chan runtime.Object) *extensions_v1beta1.Ingress {
select {
case obj := <-c:
ingress := obj.(*extensions_v1beta1.Ingress)
return ingress
case <-time.After(time.Minute):
return nil
}
}
...@@ -38,7 +38,7 @@ import ( ...@@ -38,7 +38,7 @@ import (
const ( const (
clusterSyncPeriod = 10 * time.Minute clusterSyncPeriod = 10 * time.Minute
userAgentName = "federation-service-controller" userAgentName = "federation-controller"
) )
// An object with an origin information. // An object with an origin information.
......
/*
Copyright 2016 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 util
import (
"reflect"
"k8s.io/kubernetes/pkg/api/v1"
)
/*
ObjectMetaIsEquivalent determines whether two ObjectMeta's (typically one from a federated API object,
and the other from a cluster object) are equivalent.
*/
func ObjectMetaIsEquivalent(m1, m2 v1.ObjectMeta) bool {
// First make all of the read-only fields equal, then perform a deep equality comparison
m1.SelfLink = m2.SelfLink // Might be different in different cluster contexts.
m1.UID = m2.UID // Definitely different in different cluster contexts
m1.ResourceVersion = m2.ResourceVersion // Definitely different in different cluster contexts
m1.Generation = m2.Generation // Might be different in different cluster contexts.
m1.CreationTimestamp = m2.CreationTimestamp // Definitely different in different cluster contexts.
m1.DeletionTimestamp = m2.DeletionTimestamp // Might be different in different cluster contexts.
m1.OwnerReferences = nil // Might be different in different cluster contexts.
m2.OwnerReferences = nil
m1.Finalizers = nil // Might be different in different cluster contexts.
m2.Finalizers = nil
return reflect.DeepEqual(m1, m2)
}
...@@ -16,6 +16,11 @@ limitations under the License. ...@@ -16,6 +16,11 @@ limitations under the License.
package types package types
import (
"fmt"
"strings"
)
// NamespacedName comprises a resource name, with a mandatory namespace, // NamespacedName comprises a resource name, with a mandatory namespace,
// rendered as "<namespace>/<name>". Being a type captures intent and // rendered as "<namespace>/<name>". Being a type captures intent and
// helps make sure that UIDs, namespaced names and non-namespaced names // helps make sure that UIDs, namespaced names and non-namespaced names
...@@ -29,7 +34,27 @@ type NamespacedName struct { ...@@ -29,7 +34,27 @@ type NamespacedName struct {
Name string Name string
} }
const (
Separator = '/'
)
// String returns the general purpose string representation // String returns the general purpose string representation
func (n NamespacedName) String() string { func (n NamespacedName) String() string {
return n.Namespace + "/" + n.Name return fmt.Sprintf("%s%c%s", n.Namespace, Separator, n.Name)
}
// NewNamespacedNameFromString parses the provided string and returns a NamespacedName.
// The expected format is as per String() above.
// If the input string is invalid, the returned NamespacedName has all empty string field values.
// This allows a single-value return from this function, while still allowing error checks in the caller.
// Note that an input string which does not include exactly one Separator is not a valid input (as it could never
// have neem returned by String() )
func NewNamespacedNameFromString(s string) NamespacedName {
nn := NamespacedName{}
result := strings.Split(s, string(Separator))
if len(result) == 2 {
nn.Namespace = result[0]
nn.Name = result[1]
}
return nn
} }
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