mirror of https://github.com/k3s-io/k3s
add ClusterSelector to services
parent
6f7eac63c2
commit
c68bf0b260
|
@ -23,6 +23,7 @@ go_library(
|
||||||
"//federation/pkg/dnsprovider:go_default_library",
|
"//federation/pkg/dnsprovider:go_default_library",
|
||||||
"//federation/pkg/dnsprovider/rrstype:go_default_library",
|
"//federation/pkg/dnsprovider/rrstype:go_default_library",
|
||||||
"//federation/pkg/federation-controller/util:go_default_library",
|
"//federation/pkg/federation-controller/util:go_default_library",
|
||||||
|
"//federation/pkg/federation-controller/util/clusterselector:go_default_library",
|
||||||
"//federation/pkg/federation-controller/util/deletionhelper:go_default_library",
|
"//federation/pkg/federation-controller/util/deletionhelper:go_default_library",
|
||||||
"//pkg/api:go_default_library",
|
"//pkg/api:go_default_library",
|
||||||
"//pkg/api/v1:go_default_library",
|
"//pkg/api/v1:go_default_library",
|
||||||
|
|
|
@ -42,6 +42,7 @@ import (
|
||||||
v1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1"
|
v1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1"
|
||||||
fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset"
|
fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset"
|
||||||
fedutil "k8s.io/kubernetes/federation/pkg/federation-controller/util"
|
fedutil "k8s.io/kubernetes/federation/pkg/federation-controller/util"
|
||||||
|
"k8s.io/kubernetes/federation/pkg/federation-controller/util/clusterselector"
|
||||||
"k8s.io/kubernetes/federation/pkg/federation-controller/util/deletionhelper"
|
"k8s.io/kubernetes/federation/pkg/federation-controller/util/deletionhelper"
|
||||||
"k8s.io/kubernetes/pkg/api"
|
"k8s.io/kubernetes/pkg/api"
|
||||||
"k8s.io/kubernetes/pkg/api/v1"
|
"k8s.io/kubernetes/pkg/api/v1"
|
||||||
|
@ -475,7 +476,7 @@ func (s *ServiceController) reconcileService(key string) reconciliationStatus {
|
||||||
operations := make([]fedutil.FederatedOperation, 0)
|
operations := make([]fedutil.FederatedOperation, 0)
|
||||||
for _, cluster := range clusters {
|
for _, cluster := range clusters {
|
||||||
// Aggregate all operations to perform on all federated clusters
|
// Aggregate all operations to perform on all federated clusters
|
||||||
operation, err := s.getOperationsToPerformOnCluster(cluster, fedService)
|
operation, err := getOperationsToPerformOnCluster(s.federatedInformer, cluster, fedService, clusterselector.SendToCluster)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return statusRecoverableError
|
return statusRecoverableError
|
||||||
}
|
}
|
||||||
|
@ -529,43 +530,40 @@ func (s *ServiceController) reconcileService(key string) reconciliationStatus {
|
||||||
return statusAllOk
|
return statusAllOk
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type clusterSelectorFunc func(map[string]string, map[string]string) (bool, error)
|
||||||
|
|
||||||
// getOperationsToPerformOnCluster returns the operations to be performed so that clustered service is in sync with federated service
|
// getOperationsToPerformOnCluster returns the operations to be performed so that clustered service is in sync with federated service
|
||||||
func (s *ServiceController) getOperationsToPerformOnCluster(cluster *v1beta1.Cluster, fedService *v1.Service) (*fedutil.FederatedOperation, error) {
|
func getOperationsToPerformOnCluster(informer fedutil.FederatedInformer, cluster *v1beta1.Cluster, fedService *v1.Service, selector clusterSelectorFunc) (*fedutil.FederatedOperation, error) {
|
||||||
var operation *fedutil.FederatedOperation
|
var operation *fedutil.FederatedOperation
|
||||||
|
var operationType fedutil.FederatedOperationType = ""
|
||||||
|
|
||||||
key := types.NamespacedName{Namespace: fedService.Namespace, Name: fedService.Name}.String()
|
key := types.NamespacedName{Namespace: fedService.Namespace, Name: fedService.Name}.String()
|
||||||
clusterServiceObj, serviceFound, err := s.federatedInformer.GetTargetStore().GetByKey(cluster.Name, key)
|
clusterServiceObj, found, err := informer.GetTargetStore().GetByKey(cluster.Name, key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
runtime.HandleError(fmt.Errorf("Failed to get %s service from %s: %v", key, cluster.Name, err))
|
runtime.HandleError(fmt.Errorf("Failed to get %s service from %s: %v", key, cluster.Name, err))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
if !serviceFound {
|
|
||||||
|
send, err := selector(cluster.Labels, fedService.ObjectMeta.Annotations)
|
||||||
|
if err != nil {
|
||||||
|
glog.Errorf("Error processing ClusterSelector cluster: %s for service map: %s error: %s", cluster.Name, key, err.Error())
|
||||||
|
return nil, err
|
||||||
|
} else if !send {
|
||||||
|
glog.V(5).Infof("Skipping cluster: %s for service: %s reason: cluster selectors do not match: %-v %-v", cluster.Name, key, cluster.ObjectMeta.Labels, fedService.ObjectMeta.Annotations[v1beta1.FederationClusterSelectorAnnotation])
|
||||||
|
}
|
||||||
|
|
||||||
desiredService := &v1.Service{
|
desiredService := &v1.Service{
|
||||||
ObjectMeta: fedutil.DeepCopyRelevantObjectMeta(fedService.ObjectMeta),
|
ObjectMeta: fedutil.DeepCopyRelevantObjectMeta(fedService.ObjectMeta),
|
||||||
Spec: *(fedutil.DeepCopyApiTypeOrPanic(&fedService.Spec).(*v1.ServiceSpec)),
|
Spec: *(fedutil.DeepCopyApiTypeOrPanic(&fedService.Spec).(*v1.ServiceSpec)),
|
||||||
}
|
}
|
||||||
desiredService.ResourceVersion = ""
|
switch {
|
||||||
|
case found && send:
|
||||||
glog.V(4).Infof("Creating service in underlying cluster %s: %+v", cluster.Name, desiredService)
|
|
||||||
|
|
||||||
operation = &fedutil.FederatedOperation{
|
|
||||||
Type: fedutil.OperationTypeAdd,
|
|
||||||
Obj: desiredService,
|
|
||||||
ClusterName: cluster.Name,
|
|
||||||
Key: key,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
clusterService, ok := clusterServiceObj.(*v1.Service)
|
clusterService, ok := clusterServiceObj.(*v1.Service)
|
||||||
if !ok {
|
if !ok {
|
||||||
runtime.HandleError(fmt.Errorf("Unexpected error for %q: %v", key, err))
|
runtime.HandleError(fmt.Errorf("Unexpected error for %q: %v", key, err))
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
desiredService := &v1.Service{
|
|
||||||
ObjectMeta: fedutil.DeepCopyRelevantObjectMeta(clusterService.ObjectMeta),
|
|
||||||
Spec: *(fedutil.DeepCopyApiTypeOrPanic(&fedService.Spec).(*v1.ServiceSpec)),
|
|
||||||
}
|
|
||||||
|
|
||||||
// ClusterIP and NodePort are allocated to Service by cluster, so retain the same if any while updating
|
// ClusterIP and NodePort are allocated to Service by cluster, so retain the same if any while updating
|
||||||
desiredService.Spec.ClusterIP = clusterService.Spec.ClusterIP
|
desiredService.Spec.ClusterIP = clusterService.Spec.ClusterIP
|
||||||
for _, cPort := range clusterService.Spec.Ports {
|
for _, cPort := range clusterService.Spec.Ports {
|
||||||
|
@ -583,21 +581,32 @@ func (s *ServiceController) getOperationsToPerformOnCluster(cluster *v1beta1.Clu
|
||||||
|
|
||||||
// Update existing service, if needed.
|
// Update existing service, if needed.
|
||||||
if !Equivalent(desiredService, clusterService) {
|
if !Equivalent(desiredService, clusterService) {
|
||||||
|
operationType = fedutil.OperationTypeUpdate
|
||||||
|
|
||||||
glog.V(4).Infof("Service in underlying cluster %s does not match, Desired: %+v, Existing: %+v", cluster.Name, desiredService, clusterService)
|
glog.V(4).Infof("Service in underlying cluster %s does not match, Desired: %+v, Existing: %+v", cluster.Name, desiredService, clusterService)
|
||||||
|
|
||||||
// ResourceVersion of cluster service can be different from federated service,
|
// ResourceVersion of cluster service can be different from federated service,
|
||||||
// so do not update ResourceVersion while updating cluster service
|
// so do not update ResourceVersion while updating cluster service
|
||||||
desiredService.ResourceVersion = clusterService.ResourceVersion
|
desiredService.ResourceVersion = clusterService.ResourceVersion
|
||||||
|
} else {
|
||||||
|
glog.V(5).Infof("Service in underlying cluster %s is up to date: %+v", cluster.Name, desiredService)
|
||||||
|
}
|
||||||
|
case found && !send:
|
||||||
|
operationType = fedutil.OperationTypeDelete
|
||||||
|
case !found && send:
|
||||||
|
operationType = fedutil.OperationTypeAdd
|
||||||
|
desiredService.ResourceVersion = ""
|
||||||
|
|
||||||
|
glog.V(4).Infof("Creating service in underlying cluster %s: %+v", cluster.Name, desiredService)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(operationType) > 0 {
|
||||||
operation = &fedutil.FederatedOperation{
|
operation = &fedutil.FederatedOperation{
|
||||||
Type: fedutil.OperationTypeUpdate,
|
Type: operationType,
|
||||||
Obj: desiredService,
|
Obj: desiredService,
|
||||||
ClusterName: cluster.Name,
|
ClusterName: cluster.Name,
|
||||||
Key: key,
|
Key: key,
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
glog.V(5).Infof("Service in underlying cluster %s is up to date: %+v", cluster.Name, desiredService)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return operation, nil
|
return operation, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -53,6 +53,8 @@ const (
|
||||||
serviceEndpoint2 = "192.168.1.1"
|
serviceEndpoint2 = "192.168.1.1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var awfulError error = errors.NewGone("Something bad happened")
|
||||||
|
|
||||||
func TestServiceController(t *testing.T) {
|
func TestServiceController(t *testing.T) {
|
||||||
glog.Infof("Creating fake infrastructure")
|
glog.Infof("Creating fake infrastructure")
|
||||||
fedClient := &fakefedclientset.Clientset{}
|
fedClient := &fakefedclientset.Clientset{}
|
||||||
|
@ -207,6 +209,53 @@ func TestServiceController(t *testing.T) {
|
||||||
close(stop)
|
close(stop)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetOperationsToPerformOnCluster(t *testing.T) {
|
||||||
|
obj := NewService("test-service-1", 80)
|
||||||
|
cluster1 := NewClusterWithRegionZone("cluster1", v1.ConditionTrue, "region1", "zone1")
|
||||||
|
fedClient := &fakefedclientset.Clientset{}
|
||||||
|
sc := New(fedClient)
|
||||||
|
|
||||||
|
testCases := map[string]struct {
|
||||||
|
expectedSendErr bool
|
||||||
|
sendToCluster bool
|
||||||
|
operationType fedutil.FederatedOperationType
|
||||||
|
}{
|
||||||
|
"sendToCluster error returned": {
|
||||||
|
expectedSendErr: true,
|
||||||
|
},
|
||||||
|
"Missing object and not matching ClusterSelector should result in no operations": {
|
||||||
|
sendToCluster: false,
|
||||||
|
},
|
||||||
|
"Missing object and matching ClusterSelector should result in add operation": {
|
||||||
|
operationType: fedutil.OperationTypeAdd,
|
||||||
|
sendToCluster: true,
|
||||||
|
},
|
||||||
|
// Update and Delete scenarios are tested in TestServiceController
|
||||||
|
}
|
||||||
|
for testName, testCase := range testCases {
|
||||||
|
t.Run(testName, func(t *testing.T) {
|
||||||
|
|
||||||
|
operations, err := getOperationsToPerformOnCluster(sc.federatedInformer, cluster1, obj, func(map[string]string, map[string]string) (bool, error) {
|
||||||
|
if testCase.expectedSendErr {
|
||||||
|
return false, awfulError
|
||||||
|
}
|
||||||
|
return testCase.sendToCluster, nil
|
||||||
|
})
|
||||||
|
if testCase.expectedSendErr {
|
||||||
|
require.Error(t, err, "An error was expected")
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err, "An error was not expected")
|
||||||
|
}
|
||||||
|
if len(testCase.operationType) == 0 {
|
||||||
|
require.Nil(t, operations, "An operation was not expected")
|
||||||
|
} else {
|
||||||
|
require.NotNil(t, operations, "A single operation was expected")
|
||||||
|
require.Equal(t, testCase.operationType, operations.Type, "Unexpected operation returned")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func NewService(name string, port int32) *v1.Service {
|
func NewService(name string, port int32) *v1.Service {
|
||||||
return &v1.Service{
|
return &v1.Service{
|
||||||
ObjectMeta: metav1.ObjectMeta{
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
|
Loading…
Reference in New Issue