Replace godep with dep
This commit is contained in:
parent
1e7489927c
commit
bf5616c65b
14883 changed files with 3937406 additions and 361781 deletions
98
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/BUILD
generated
vendored
Normal file
98
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"backoff.go",
|
||||
"cluster_util.go",
|
||||
"configmap.go",
|
||||
"delaying_deliverer.go",
|
||||
"deployment.go",
|
||||
"federated_informer.go",
|
||||
"federated_updater.go",
|
||||
"handlers.go",
|
||||
"meta.go",
|
||||
"secret.go",
|
||||
],
|
||||
deps = [
|
||||
"//federation/apis/federation/v1beta1:go_default_library",
|
||||
"//federation/client/clientset_generated/federation_clientset:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/controller/deployment/util:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/net:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/clientcmd/api:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/record:go_default_library",
|
||||
"//vendor/k8s.io/client-go/util/flowcontrol:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = [
|
||||
"delaying_deliverer_test.go",
|
||||
"deployment_test.go",
|
||||
"federated_informer_test.go",
|
||||
"federated_updater_test.go",
|
||||
"handlers_test.go",
|
||||
"meta_test.go",
|
||||
],
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//federation/apis/federation/v1beta1:go_default_library",
|
||||
"//federation/client/clientset_generated/federation_clientset/fake:go_default_library",
|
||||
"//pkg/controller/deployment/util:go_default_library",
|
||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
|
||||
"//vendor/k8s.io/client-go/testing:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/cache:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//federation/pkg/federation-controller/util/clusterselector:all-srcs",
|
||||
"//federation/pkg/federation-controller/util/deletionhelper:all-srcs",
|
||||
"//federation/pkg/federation-controller/util/eventsink:all-srcs",
|
||||
"//federation/pkg/federation-controller/util/finalizers:all-srcs",
|
||||
"//federation/pkg/federation-controller/util/hpa:all-srcs",
|
||||
"//federation/pkg/federation-controller/util/planner:all-srcs",
|
||||
"//federation/pkg/federation-controller/util/podanalyzer:all-srcs",
|
||||
"//federation/pkg/federation-controller/util/replicapreferences:all-srcs",
|
||||
"//federation/pkg/federation-controller/util/test:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
36
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/backoff.go
generated
vendored
Normal file
36
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/backoff.go
generated
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
/*
|
||||
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 (
|
||||
"time"
|
||||
|
||||
"k8s.io/client-go/util/flowcontrol"
|
||||
)
|
||||
|
||||
func StartBackoffGC(backoff *flowcontrol.Backoff, stopCh <-chan struct{}) {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-time.After(time.Minute):
|
||||
backoff.GC()
|
||||
case <-stopCh:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
147
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/cluster_util.go
generated
vendored
Normal file
147
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/cluster_util.go
generated
vendored
Normal file
|
|
@ -0,0 +1,147 @@
|
|||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/golang/glog"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
federation_v1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
)
|
||||
|
||||
const (
|
||||
KubeAPIQPS = 20.0
|
||||
KubeAPIBurst = 30
|
||||
KubeconfigSecretDataKey = "kubeconfig"
|
||||
getSecretTimeout = 1 * time.Minute
|
||||
)
|
||||
|
||||
func BuildClusterConfig(c *federation_v1beta1.Cluster) (*restclient.Config, error) {
|
||||
var serverAddress string
|
||||
var clusterConfig *restclient.Config
|
||||
hostIP, err := utilnet.ChooseHostInterface()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, item := range c.Spec.ServerAddressByClientCIDRs {
|
||||
_, cidrnet, err := net.ParseCIDR(item.ClientCIDR)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
myaddr := net.ParseIP(hostIP.String())
|
||||
if cidrnet.Contains(myaddr) == true {
|
||||
serverAddress = item.ServerAddress
|
||||
break
|
||||
}
|
||||
}
|
||||
if serverAddress != "" {
|
||||
if c.Spec.SecretRef == nil {
|
||||
glog.Infof("didn't find secretRef for cluster %s. Trying insecure access", c.Name)
|
||||
clusterConfig, err = clientcmd.BuildConfigFromFlags(serverAddress, "")
|
||||
} else {
|
||||
if c.Spec.SecretRef.Name == "" {
|
||||
return nil, fmt.Errorf("found secretRef but no secret name for cluster %s", c.Name)
|
||||
}
|
||||
secret, err := getSecret(c.Spec.SecretRef.Name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Pre-1.7, the secret contained a serialized kubeconfig which contained appropriate credentials.
|
||||
// Post-1.7, the secret contains credentials for a service account.
|
||||
// Check for the service account credentials, and use them if they exist; if not, use the
|
||||
// serialized kubeconfig.
|
||||
token, tokenFound := secret.Data["token"]
|
||||
ca, caFound := secret.Data["ca.crt"]
|
||||
if tokenFound != caFound {
|
||||
return nil, fmt.Errorf("secret should have values for either both 'ca.crt' and 'token' in its Data, or neither: %v", secret)
|
||||
} else if tokenFound && caFound {
|
||||
clusterConfig, err = clientcmd.BuildConfigFromFlags(serverAddress, "")
|
||||
clusterConfig.CAData = ca
|
||||
clusterConfig.BearerToken = string(token)
|
||||
} else {
|
||||
kubeconfigGetter := KubeconfigGetterForSecret(secret)
|
||||
clusterConfig, err = clientcmd.BuildConfigFromKubeconfigGetter(serverAddress, kubeconfigGetter)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clusterConfig.QPS = KubeAPIQPS
|
||||
clusterConfig.Burst = KubeAPIBurst
|
||||
}
|
||||
return clusterConfig, nil
|
||||
}
|
||||
|
||||
// getSecret gets a secret from the cluster.
|
||||
func getSecret(secretName string) (*api.Secret, error) {
|
||||
// Get the namespace this is running in from the env variable.
|
||||
namespace := os.Getenv("POD_NAMESPACE")
|
||||
if namespace == "" {
|
||||
return nil, fmt.Errorf("unexpected: POD_NAMESPACE env var returned empty string")
|
||||
}
|
||||
// Get a client to talk to the k8s apiserver, to fetch secrets from it.
|
||||
cc, err := restclient.InClusterConfig()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in creating in-cluster config: %s", err)
|
||||
}
|
||||
client, err := clientset.NewForConfig(cc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in creating in-cluster client: %s", err)
|
||||
}
|
||||
var secret *api.Secret
|
||||
err = wait.PollImmediate(1*time.Second, getSecretTimeout, func() (bool, error) {
|
||||
secret, err = client.Core().Secrets(namespace).Get(secretName, metav1.GetOptions{})
|
||||
if err == nil {
|
||||
return true, nil
|
||||
}
|
||||
glog.Warningf("error in fetching secret: %s", err)
|
||||
return false, nil
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("timed out waiting for secret: %s", err)
|
||||
}
|
||||
if secret == nil {
|
||||
return nil, fmt.Errorf("unexpected: received null secret %s", secretName)
|
||||
}
|
||||
return secret, nil
|
||||
}
|
||||
|
||||
// KubeconfigGetterForSecret gets the kubeconfig from the given secret.
|
||||
// This is to inject a different KubeconfigGetter in tests. We don't use
|
||||
// the standard one which calls NewInCluster in tests to avoid having to
|
||||
// set up service accounts and mount files with secret tokens.
|
||||
var KubeconfigGetterForSecret = func(secret *api.Secret) clientcmd.KubeconfigGetter {
|
||||
return func() (*clientcmdapi.Config, error) {
|
||||
data, ok := secret.Data[KubeconfigSecretDataKey]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("secret does not have data with key %s", KubeconfigSecretDataKey)
|
||||
}
|
||||
return clientcmd.Load(data)
|
||||
}
|
||||
}
|
||||
40
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/clusterselector/BUILD
generated
vendored
Normal file
40
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/clusterselector/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["clusterselector_test.go"],
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//federation/apis/federation/v1beta1:go_default_library",
|
||||
"//vendor/github.com/stretchr/testify/require:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["clusterselector.go"],
|
||||
deps = [
|
||||
"//federation/apis/federation/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/labels:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/selection:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
86
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/clusterselector/clusterselector.go
generated
vendored
Normal file
86
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/clusterselector/clusterselector.go
generated
vendored
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
Copyright 2017 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 clusterselector
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/selection"
|
||||
federation_v1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1"
|
||||
)
|
||||
|
||||
// Parses the cluster selector annotation to find out if the object with that annotation should be forwarded to a cluster with the given clusterLabels.
|
||||
func SendToCluster(clusterLabels map[string]string, annotations map[string]string) (bool, error) {
|
||||
// Check if a ClusterSelector annotation exists and send to all clusters when it does not exist
|
||||
val, ok := annotations[federation_v1beta1.FederationClusterSelectorAnnotation]
|
||||
if !ok {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
selector, err := getSelector(val)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return selector.Matches(labels.Set(clusterLabels)), nil
|
||||
}
|
||||
|
||||
func getSelector(annotation string) (labels.Selector, error) {
|
||||
selector := labels.NewSelector()
|
||||
requirements := make([]federation_v1beta1.ClusterSelectorRequirement, 0)
|
||||
err := json.Unmarshal([]byte(annotation), &requirements)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, requirement := range requirements {
|
||||
r, err := labels.NewRequirement(requirement.Key, ConvertOperator(requirement.Operator), requirement.Values)
|
||||
if err != nil {
|
||||
// Stop processing and assume failure since we have no way of knowing the end users intent for this or any other clusters.
|
||||
return nil, err
|
||||
}
|
||||
selector = selector.Add(*r)
|
||||
}
|
||||
|
||||
return selector, nil
|
||||
}
|
||||
|
||||
// ConvertOperator converts a string operator into selection.Operator type
|
||||
func ConvertOperator(source string) selection.Operator {
|
||||
var op selection.Operator
|
||||
switch source {
|
||||
case "!", "DoesNotExist":
|
||||
op = selection.DoesNotExist
|
||||
case "=":
|
||||
op = selection.Equals
|
||||
case "==":
|
||||
op = selection.DoubleEquals
|
||||
case "in", "In":
|
||||
op = selection.In
|
||||
case "!=":
|
||||
op = selection.NotEquals
|
||||
case "notin", "NotIn":
|
||||
op = selection.NotIn
|
||||
case "exists", "Exists":
|
||||
op = selection.Exists
|
||||
case "gt", "Gt", ">":
|
||||
op = selection.GreaterThan
|
||||
case "lt", "Lt", "<":
|
||||
op = selection.LessThan
|
||||
}
|
||||
return op
|
||||
}
|
||||
98
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/clusterselector/clusterselector_test.go
generated
vendored
Normal file
98
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/clusterselector/clusterselector_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
Copyright 2017 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 clusterselector
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1"
|
||||
)
|
||||
|
||||
func TestSendToCluster(t *testing.T) {
|
||||
|
||||
clusterLabels := map[string]string{
|
||||
"location": "europe",
|
||||
"environment": "prod",
|
||||
"version": "15",
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
objectAnnotations map[string]string
|
||||
expectedResult bool
|
||||
expectedErr bool
|
||||
}{
|
||||
"match with single annotation": {
|
||||
objectAnnotations: map[string]string{
|
||||
federationapi.FederationClusterSelectorAnnotation: `[{"key": "location", "operator": "in", "values": ["europe"]}]`,
|
||||
},
|
||||
expectedResult: true,
|
||||
},
|
||||
"match on multiple annotations": {
|
||||
objectAnnotations: map[string]string{
|
||||
federationapi.FederationClusterSelectorAnnotation: `[{"key": "location", "operator": "in", "values": ["europe"]}, {"key": "environment", "operator": "==", "values": ["prod"]}]`,
|
||||
},
|
||||
expectedResult: true,
|
||||
},
|
||||
"mismatch on one annotation": {
|
||||
objectAnnotations: map[string]string{
|
||||
federationapi.FederationClusterSelectorAnnotation: `[{"key": "location", "operator": "in", "values": ["europe"]}, {"key": "environment", "operator": "==", "values": ["test"]}]`,
|
||||
},
|
||||
expectedResult: false,
|
||||
},
|
||||
"match on not equal annotation": {
|
||||
objectAnnotations: map[string]string{
|
||||
federationapi.FederationClusterSelectorAnnotation: `[{"key": "location", "operator": "!=", "values": ["usa"]}, {"key": "environment", "operator": "in", "values": ["prod"]}]`,
|
||||
},
|
||||
expectedResult: true,
|
||||
},
|
||||
"match on greater than annotation": {
|
||||
objectAnnotations: map[string]string{
|
||||
federationapi.FederationClusterSelectorAnnotation: `[{"key": "version", "operator": ">", "values": ["14"]}]`,
|
||||
},
|
||||
expectedResult: true,
|
||||
},
|
||||
"mismatch on greater than annotation": {
|
||||
objectAnnotations: map[string]string{
|
||||
federationapi.FederationClusterSelectorAnnotation: `[{"key": "version", "operator": ">", "values": ["15"]}]`,
|
||||
},
|
||||
expectedResult: false,
|
||||
},
|
||||
"unable to parse annotation": {
|
||||
objectAnnotations: map[string]string{
|
||||
federationapi.FederationClusterSelectorAnnotation: `[{"not able to parse",}]`,
|
||||
},
|
||||
expectedResult: false,
|
||||
expectedErr: true,
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range testCases {
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
result, err := SendToCluster(clusterLabels, testCase.objectAnnotations)
|
||||
|
||||
if testCase.expectedErr {
|
||||
require.Error(t, err, "An error was expected")
|
||||
} else {
|
||||
require.NoError(t, err, "An error was not expected")
|
||||
}
|
||||
|
||||
require.Equal(t, testCase.expectedResult, result, "Unexpected response from SendToCluster")
|
||||
})
|
||||
}
|
||||
}
|
||||
31
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/configmap.go
generated
vendored
Normal file
31
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/configmap.go
generated
vendored
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
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"
|
||||
|
||||
api_v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// Checks if cluster-independent, user provided data in two given ConfigMaps are equal. If in
|
||||
// the future the ConfigMap structure is expanded then any field that is not populated.
|
||||
// by the api server should be included here.
|
||||
func ConfigMapEquivalent(s1, s2 *api_v1.ConfigMap) bool {
|
||||
return ObjectMetaEquivalent(s1.ObjectMeta, s2.ObjectMeta) &&
|
||||
reflect.DeepEqual(s1.Data, s2.Data)
|
||||
}
|
||||
183
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/delaying_deliverer.go
generated
vendored
Normal file
183
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/delaying_deliverer.go
generated
vendored
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
/*
|
||||
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.
|
||||
*/
|
||||
|
||||
// TODO: consider moving it to a more generic package.
|
||||
package util
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
// TODO: Investigate what capacity is right.
|
||||
delayingDelivererUpdateChanCapacity = 1000
|
||||
)
|
||||
|
||||
// DelayingDelivererItem is structure delivered by DelayingDeliverer to the
|
||||
// target channel.
|
||||
type DelayingDelivererItem struct {
|
||||
// Key under which the value was added to deliverer.
|
||||
Key string
|
||||
// Value of the item.
|
||||
Value interface{}
|
||||
// When the item should be delivered.
|
||||
DeliveryTime time.Time
|
||||
}
|
||||
|
||||
type delivererHeap struct {
|
||||
keyPosition map[string]int
|
||||
data []*DelayingDelivererItem
|
||||
}
|
||||
|
||||
// Functions required by container.Heap.
|
||||
|
||||
func (dh *delivererHeap) Len() int { return len(dh.data) }
|
||||
func (dh *delivererHeap) Less(i, j int) bool {
|
||||
return dh.data[i].DeliveryTime.Before(dh.data[j].DeliveryTime)
|
||||
}
|
||||
func (dh *delivererHeap) Swap(i, j int) {
|
||||
dh.keyPosition[dh.data[i].Key] = j
|
||||
dh.keyPosition[dh.data[j].Key] = i
|
||||
dh.data[i], dh.data[j] = dh.data[j], dh.data[i]
|
||||
}
|
||||
|
||||
func (dh *delivererHeap) Push(x interface{}) {
|
||||
item := x.(*DelayingDelivererItem)
|
||||
dh.data = append(dh.data, item)
|
||||
dh.keyPosition[item.Key] = len(dh.data) - 1
|
||||
}
|
||||
|
||||
func (dh *delivererHeap) Pop() interface{} {
|
||||
n := len(dh.data)
|
||||
item := dh.data[n-1]
|
||||
dh.data = dh.data[:n-1]
|
||||
delete(dh.keyPosition, item.Key)
|
||||
return item
|
||||
}
|
||||
|
||||
// A structure that pushes the items to the target channel at a given time.
|
||||
type DelayingDeliverer struct {
|
||||
// Channel to deliver the data when their time comes.
|
||||
targetChannel chan *DelayingDelivererItem
|
||||
// Store for data
|
||||
heap *delivererHeap
|
||||
// Channel to feed the main goroutine with updates.
|
||||
updateChannel chan *DelayingDelivererItem
|
||||
// To stop the main goroutine.
|
||||
stopChannel chan struct{}
|
||||
}
|
||||
|
||||
func NewDelayingDeliverer() *DelayingDeliverer {
|
||||
return NewDelayingDelivererWithChannel(make(chan *DelayingDelivererItem, 100))
|
||||
}
|
||||
|
||||
func NewDelayingDelivererWithChannel(targetChannel chan *DelayingDelivererItem) *DelayingDeliverer {
|
||||
return &DelayingDeliverer{
|
||||
targetChannel: targetChannel,
|
||||
heap: &delivererHeap{
|
||||
keyPosition: make(map[string]int),
|
||||
data: make([]*DelayingDelivererItem, 0),
|
||||
},
|
||||
updateChannel: make(chan *DelayingDelivererItem, delayingDelivererUpdateChanCapacity),
|
||||
stopChannel: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Deliver all items due before or equal to timestamp.
|
||||
func (d *DelayingDeliverer) deliver(timestamp time.Time) {
|
||||
for d.heap.Len() > 0 {
|
||||
if timestamp.Before(d.heap.data[0].DeliveryTime) {
|
||||
return
|
||||
}
|
||||
item := heap.Pop(d.heap).(*DelayingDelivererItem)
|
||||
d.targetChannel <- item
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DelayingDeliverer) run() {
|
||||
for {
|
||||
now := time.Now()
|
||||
d.deliver(now)
|
||||
|
||||
nextWakeUp := now.Add(time.Hour)
|
||||
if d.heap.Len() > 0 {
|
||||
nextWakeUp = d.heap.data[0].DeliveryTime
|
||||
}
|
||||
sleepTime := nextWakeUp.Sub(now)
|
||||
|
||||
select {
|
||||
case <-time.After(sleepTime):
|
||||
break // just wake up and process the data
|
||||
case item := <-d.updateChannel:
|
||||
if position, found := d.heap.keyPosition[item.Key]; found {
|
||||
if item.DeliveryTime.Before(d.heap.data[position].DeliveryTime) {
|
||||
d.heap.data[position] = item
|
||||
heap.Fix(d.heap, position)
|
||||
}
|
||||
// Ignore if later.
|
||||
} else {
|
||||
heap.Push(d.heap, item)
|
||||
}
|
||||
case <-d.stopChannel:
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Starts the DelayingDeliverer.
|
||||
func (d *DelayingDeliverer) Start() {
|
||||
go d.run()
|
||||
}
|
||||
|
||||
// Stops the DelayingDeliverer. Undelivered items are discarded.
|
||||
func (d *DelayingDeliverer) Stop() {
|
||||
close(d.stopChannel)
|
||||
}
|
||||
|
||||
// Delivers value at the given time.
|
||||
func (d *DelayingDeliverer) DeliverAt(key string, value interface{}, deliveryTime time.Time) {
|
||||
d.updateChannel <- &DelayingDelivererItem{
|
||||
Key: key,
|
||||
Value: value,
|
||||
DeliveryTime: deliveryTime,
|
||||
}
|
||||
}
|
||||
|
||||
// Delivers value after the given delay.
|
||||
func (d *DelayingDeliverer) DeliverAfter(key string, value interface{}, delay time.Duration) {
|
||||
d.DeliverAt(key, value, time.Now().Add(delay))
|
||||
}
|
||||
|
||||
// Gets target channel of the deliverer.
|
||||
func (d *DelayingDeliverer) GetTargetChannel() chan *DelayingDelivererItem {
|
||||
return d.targetChannel
|
||||
}
|
||||
|
||||
// Starts Delaying deliverer with a handler listening on the target channel.
|
||||
func (d *DelayingDeliverer) StartWithHandler(handler func(*DelayingDelivererItem)) {
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case item := <-d.targetChannel:
|
||||
handler(item)
|
||||
case <-d.stopChannel:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
d.Start()
|
||||
}
|
||||
63
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/delaying_deliverer_test.go
generated
vendored
Normal file
63
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/delaying_deliverer_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
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 (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDelayingDeliverer(t *testing.T) {
|
||||
targetChannel := make(chan *DelayingDelivererItem)
|
||||
now := time.Now()
|
||||
d := NewDelayingDelivererWithChannel(targetChannel)
|
||||
d.Start()
|
||||
defer d.Stop()
|
||||
startupDelay := time.Second
|
||||
d.DeliverAt("a", "aaa", now.Add(startupDelay+2*time.Millisecond))
|
||||
d.DeliverAt("b", "bbb", now.Add(startupDelay+3*time.Millisecond))
|
||||
d.DeliverAt("c", "ccc", now.Add(startupDelay+1*time.Millisecond))
|
||||
d.DeliverAt("e", "eee", now.Add(time.Hour))
|
||||
d.DeliverAt("e", "eee", now)
|
||||
|
||||
d.DeliverAt("d", "ddd", now.Add(time.Hour))
|
||||
|
||||
i0 := <-targetChannel
|
||||
assert.Equal(t, "e", i0.Key)
|
||||
assert.Equal(t, "eee", i0.Value.(string))
|
||||
assert.Equal(t, now, i0.DeliveryTime)
|
||||
|
||||
i1 := <-targetChannel
|
||||
received1 := time.Now()
|
||||
assert.True(t, received1.Sub(now).Nanoseconds() > startupDelay.Nanoseconds())
|
||||
assert.Equal(t, "c", i1.Key)
|
||||
|
||||
i2 := <-targetChannel
|
||||
assert.Equal(t, "a", i2.Key)
|
||||
|
||||
i3 := <-targetChannel
|
||||
assert.Equal(t, "b", i3.Key)
|
||||
|
||||
select {
|
||||
case <-targetChannel:
|
||||
t.Fatalf("Nothing should be received")
|
||||
case <-time.After(time.Second):
|
||||
// Ok. Expected
|
||||
}
|
||||
}
|
||||
32
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/deletionhelper/BUILD
generated
vendored
Normal file
32
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/deletionhelper/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["deletion_helper.go"],
|
||||
deps = [
|
||||
"//federation/pkg/federation-controller/util:go_default_library",
|
||||
"//federation/pkg/federation-controller/util/finalizers:go_default_library",
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
210
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/deletionhelper/deletion_helper.go
generated
vendored
Normal file
210
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/deletionhelper/deletion_helper.go
generated
vendored
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
/*
|
||||
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 to help federation controllers to delete federated resources from
|
||||
// underlying clusters when the resource is deleted from federation control
|
||||
// plane.
|
||||
package deletionhelper
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/kubernetes/federation/pkg/federation-controller/util"
|
||||
finalizersutil "k8s.io/kubernetes/federation/pkg/federation-controller/util/finalizers"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
const (
|
||||
// Add this finalizer to a federation resource if the resource should be
|
||||
// deleted from all underlying clusters before being deleted from
|
||||
// federation control plane.
|
||||
// This is ignored if FinalizerOrphan is also present on the resource.
|
||||
// In that case, both finalizers are removed from the resource and the
|
||||
// resource is deleted from federation control plane without affecting
|
||||
// the underlying clusters.
|
||||
FinalizerDeleteFromUnderlyingClusters string = "federation.kubernetes.io/delete-from-underlying-clusters"
|
||||
)
|
||||
|
||||
type UpdateObjFunc func(runtime.Object) (runtime.Object, error)
|
||||
type ObjNameFunc func(runtime.Object) string
|
||||
|
||||
type DeletionHelper struct {
|
||||
updateObjFunc UpdateObjFunc
|
||||
objNameFunc ObjNameFunc
|
||||
informer util.FederatedInformer
|
||||
updater util.FederatedUpdater
|
||||
}
|
||||
|
||||
func NewDeletionHelper(
|
||||
updateObjFunc UpdateObjFunc, objNameFunc ObjNameFunc,
|
||||
informer util.FederatedInformer, updater util.FederatedUpdater) *DeletionHelper {
|
||||
return &DeletionHelper{
|
||||
updateObjFunc: updateObjFunc,
|
||||
objNameFunc: objNameFunc,
|
||||
informer: informer,
|
||||
updater: updater,
|
||||
}
|
||||
}
|
||||
|
||||
// Ensures that the given object has both FinalizerDeleteFromUnderlyingClusters
|
||||
// and FinalizerOrphan finalizers.
|
||||
// We do this so that the controller is always notified when a federation resource is deleted.
|
||||
// If user deletes the resource with nil DeleteOptions or
|
||||
// DeletionOptions.OrphanDependents = true then the apiserver removes the orphan finalizer
|
||||
// and deletion helper does a cascading deletion.
|
||||
// Otherwise, deletion helper just removes the federation resource and orphans
|
||||
// the corresponding resources in underlying clusters.
|
||||
// This method should be called before creating objects in underlying clusters.
|
||||
func (dh *DeletionHelper) EnsureFinalizers(obj runtime.Object) (
|
||||
runtime.Object, error) {
|
||||
finalizers := sets.String{}
|
||||
hasFinalizer, err := finalizersutil.HasFinalizer(obj, FinalizerDeleteFromUnderlyingClusters)
|
||||
if err != nil {
|
||||
return obj, err
|
||||
}
|
||||
if !hasFinalizer {
|
||||
finalizers.Insert(FinalizerDeleteFromUnderlyingClusters)
|
||||
}
|
||||
hasFinalizer, err = finalizersutil.HasFinalizer(obj, metav1.FinalizerOrphanDependents)
|
||||
if err != nil {
|
||||
return obj, err
|
||||
}
|
||||
if !hasFinalizer {
|
||||
finalizers.Insert(metav1.FinalizerOrphanDependents)
|
||||
}
|
||||
if finalizers.Len() != 0 {
|
||||
glog.V(2).Infof("Adding finalizers %v to %s", finalizers.List(), dh.objNameFunc(obj))
|
||||
return dh.addFinalizers(obj, finalizers)
|
||||
}
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
// Deletes the resources corresponding to the given federated resource from
|
||||
// all underlying clusters, unless it has the FinalizerOrphan finalizer.
|
||||
// Removes FinalizerOrphan and FinalizerDeleteFromUnderlyingClusters finalizers
|
||||
// when done.
|
||||
// Callers are expected to keep calling this (with appropriate backoff) until
|
||||
// it succeeds.
|
||||
func (dh *DeletionHelper) HandleObjectInUnderlyingClusters(obj runtime.Object) (
|
||||
runtime.Object, error) {
|
||||
objName := dh.objNameFunc(obj)
|
||||
glog.V(2).Infof("Handling deletion of federated dependents for object: %s", objName)
|
||||
hasFinalizer, err := finalizersutil.HasFinalizer(obj, FinalizerDeleteFromUnderlyingClusters)
|
||||
if err != nil {
|
||||
return obj, err
|
||||
}
|
||||
if !hasFinalizer {
|
||||
glog.V(2).Infof("obj does not have %s finalizer. Nothing to do", FinalizerDeleteFromUnderlyingClusters)
|
||||
return obj, nil
|
||||
}
|
||||
hasOrphanFinalizer, err := finalizersutil.HasFinalizer(obj, metav1.FinalizerOrphanDependents)
|
||||
if err != nil {
|
||||
return obj, err
|
||||
}
|
||||
if hasOrphanFinalizer {
|
||||
glog.V(2).Infof("Found finalizer orphan. Nothing to do, just remove the finalizer")
|
||||
// If the obj has FinalizerOrphan finalizer, then we need to orphan the
|
||||
// corresponding objects in underlying clusters.
|
||||
// Just remove both the finalizers in that case.
|
||||
finalizers := sets.NewString(FinalizerDeleteFromUnderlyingClusters, metav1.FinalizerOrphanDependents)
|
||||
return dh.removeFinalizers(obj, finalizers)
|
||||
}
|
||||
|
||||
glog.V(2).Infof("Deleting obj %s from underlying clusters", objName)
|
||||
// Else, we need to delete the obj from all underlying clusters.
|
||||
unreadyClusters, err := dh.informer.GetUnreadyClusters()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get a list of unready clusters: %v", err)
|
||||
}
|
||||
// TODO: Handle the case when cluster resource is watched after this is executed.
|
||||
// This can happen if a namespace is deleted before its creation had been
|
||||
// observed in all underlying clusters.
|
||||
storeKey := dh.informer.GetTargetStore().GetKeyFor(obj)
|
||||
clusterNsObjs, err := dh.informer.GetTargetStore().GetFromAllClusters(storeKey)
|
||||
glog.V(3).Infof("Found %d objects in underlying clusters", len(clusterNsObjs))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get object %s from underlying clusters: %v", objName, err)
|
||||
}
|
||||
operations := make([]util.FederatedOperation, 0)
|
||||
for _, clusterNsObj := range clusterNsObjs {
|
||||
operations = append(operations, util.FederatedOperation{
|
||||
Type: util.OperationTypeDelete,
|
||||
ClusterName: clusterNsObj.ClusterName,
|
||||
Obj: clusterNsObj.Object.(runtime.Object),
|
||||
Key: objName,
|
||||
})
|
||||
}
|
||||
err = dh.updater.Update(operations)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to execute updates for obj %s: %v", objName, err)
|
||||
}
|
||||
if len(operations) > 0 {
|
||||
// We have deleted a bunch of resources.
|
||||
// Wait for the store to observe all the deletions.
|
||||
var clusterNames []string
|
||||
for _, op := range operations {
|
||||
clusterNames = append(clusterNames, op.ClusterName)
|
||||
}
|
||||
return nil, fmt.Errorf("waiting for object %s to be deleted from clusters: %s", objName, strings.Join(clusterNames, ", "))
|
||||
}
|
||||
|
||||
// We have now deleted the object from all *ready* clusters.
|
||||
// But still need to wait for clusters that are not ready to ensure that
|
||||
// the object has been deleted from *all* clusters.
|
||||
if len(unreadyClusters) != 0 {
|
||||
var clusterNames []string
|
||||
for _, cluster := range unreadyClusters {
|
||||
clusterNames = append(clusterNames, cluster.Name)
|
||||
}
|
||||
return nil, fmt.Errorf("waiting for clusters %s to become ready to verify that obj %s has been deleted", strings.Join(clusterNames, ", "), objName)
|
||||
}
|
||||
|
||||
// All done. Just remove the finalizer.
|
||||
return dh.removeFinalizers(obj, sets.NewString(FinalizerDeleteFromUnderlyingClusters))
|
||||
}
|
||||
|
||||
// Adds the given finalizers to the given objects ObjectMeta.
|
||||
func (dh *DeletionHelper) addFinalizers(obj runtime.Object, finalizers sets.String) (runtime.Object, error) {
|
||||
isUpdated, err := finalizersutil.AddFinalizers(obj, finalizers)
|
||||
if err != nil || !isUpdated {
|
||||
return obj, err
|
||||
}
|
||||
// Send the update to apiserver.
|
||||
updatedObj, err := dh.updateObjFunc(obj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to add finalizers %v to object %s: %v", finalizers, dh.objNameFunc(obj), err)
|
||||
}
|
||||
return updatedObj, nil
|
||||
}
|
||||
|
||||
// Removes the given finalizers from the given objects ObjectMeta.
|
||||
func (dh *DeletionHelper) removeFinalizers(obj runtime.Object, finalizers sets.String) (runtime.Object, error) {
|
||||
isUpdated, err := finalizersutil.RemoveFinalizers(obj, finalizers)
|
||||
if err != nil || !isUpdated {
|
||||
return obj, err
|
||||
}
|
||||
// Send the update to apiserver.
|
||||
updatedObj, err := dh.updateObjFunc(obj)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to remove finalizers %v from object %s: %v", finalizers, dh.objNameFunc(obj), err)
|
||||
}
|
||||
return updatedObj, nil
|
||||
}
|
||||
75
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/deployment.go
generated
vendored
Normal file
75
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/deployment.go
generated
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
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"
|
||||
|
||||
extensions_v1 "k8s.io/api/extensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
deputils "k8s.io/kubernetes/pkg/controller/deployment/util"
|
||||
)
|
||||
|
||||
// Checks if cluster-independent, user provided data in two given Deployment are equal.
|
||||
// This function assumes that revisions are not kept in sync across the clusters.
|
||||
func DeploymentEquivalent(a, b *extensions_v1.Deployment) bool {
|
||||
if a.Name != b.Name {
|
||||
return false
|
||||
}
|
||||
if a.Namespace != b.Namespace {
|
||||
return false
|
||||
}
|
||||
if !reflect.DeepEqual(a.Labels, b.Labels) && (len(a.Labels) != 0 || len(b.Labels) != 0) {
|
||||
return false
|
||||
}
|
||||
hasKeysAndVals := func(x, y map[string]string) bool {
|
||||
if x == nil {
|
||||
x = map[string]string{}
|
||||
}
|
||||
if y == nil {
|
||||
y = map[string]string{}
|
||||
}
|
||||
for k, v := range x {
|
||||
if k == deputils.RevisionAnnotation {
|
||||
continue
|
||||
}
|
||||
v2, found := y[k]
|
||||
if !found || v != v2 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
return hasKeysAndVals(a.Annotations, b.Annotations) &&
|
||||
hasKeysAndVals(b.Annotations, a.Annotations) &&
|
||||
reflect.DeepEqual(a.Spec, b.Spec)
|
||||
}
|
||||
|
||||
// Copies object meta for Deployment, skipping revision information.
|
||||
func DeepCopyDeploymentObjectMeta(meta metav1.ObjectMeta) metav1.ObjectMeta {
|
||||
meta = DeepCopyRelevantObjectMeta(meta)
|
||||
delete(meta.Annotations, deputils.RevisionAnnotation)
|
||||
return meta
|
||||
}
|
||||
|
||||
// Copies object meta for Deployment, skipping revision information.
|
||||
func DeepCopyDeployment(a *extensions_v1.Deployment) *extensions_v1.Deployment {
|
||||
return &extensions_v1.Deployment{
|
||||
ObjectMeta: DeepCopyDeploymentObjectMeta(a.ObjectMeta),
|
||||
Spec: *(DeepCopyApiTypeOrPanic(&a.Spec).(*extensions_v1.DeploymentSpec)),
|
||||
}
|
||||
}
|
||||
70
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/deployment_test.go
generated
vendored
Normal file
70
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/deployment_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,70 @@
|
|||
/*
|
||||
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 (
|
||||
"testing"
|
||||
|
||||
extensionsv1 "k8s.io/api/extensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
deputils "k8s.io/kubernetes/pkg/controller/deployment/util"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestDeploymentEquivalent(t *testing.T) {
|
||||
d1 := newDeployment()
|
||||
d2 := newDeployment()
|
||||
d2.Annotations = make(map[string]string)
|
||||
|
||||
d3 := newDeployment()
|
||||
d3.Annotations = map[string]string{"a": "b"}
|
||||
|
||||
d4 := newDeployment()
|
||||
d4.Annotations = map[string]string{deputils.RevisionAnnotation: "9"}
|
||||
|
||||
assert.True(t, DeploymentEquivalent(d1, d2))
|
||||
assert.True(t, DeploymentEquivalent(d1, d2))
|
||||
assert.True(t, DeploymentEquivalent(d1, d4))
|
||||
assert.True(t, DeploymentEquivalent(d4, d1))
|
||||
assert.False(t, DeploymentEquivalent(d3, d4))
|
||||
assert.False(t, DeploymentEquivalent(d3, d1))
|
||||
assert.True(t, DeploymentEquivalent(d3, d3))
|
||||
}
|
||||
|
||||
func TestDeploymentCopy(t *testing.T) {
|
||||
d1 := newDeployment()
|
||||
d1.Annotations = map[string]string{deputils.RevisionAnnotation: "9", "a": "b"}
|
||||
d2 := DeepCopyDeployment(d1)
|
||||
assert.True(t, DeploymentEquivalent(d1, d2))
|
||||
assert.Contains(t, d2.Annotations, "a")
|
||||
assert.NotContains(t, d2.Annotations, deputils.RevisionAnnotation)
|
||||
}
|
||||
|
||||
func newDeployment() *extensionsv1.Deployment {
|
||||
replicas := int32(5)
|
||||
return &extensionsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "wrr",
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
SelfLink: "/api/v1/namespaces/default/deployments/name123",
|
||||
},
|
||||
Spec: extensionsv1.DeploymentSpec{
|
||||
Replicas: &replicas,
|
||||
},
|
||||
}
|
||||
}
|
||||
50
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink/BUILD
generated
vendored
Normal file
50
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["eventsink.go"],
|
||||
deps = [
|
||||
"//federation/client/clientset_generated/federation_clientset: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/conversion:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/types:go_default_library",
|
||||
"//vendor/k8s.io/client-go/tools/record:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["eventsink_test.go"],
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//federation/client/clientset_generated/federation_clientset/fake:go_default_library",
|
||||
"//federation/pkg/federation-controller/util/test:go_default_library",
|
||||
"//vendor/github.com/stretchr/testify/assert: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/runtime:go_default_library",
|
||||
"//vendor/k8s.io/client-go/testing:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
111
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink/eventsink.go
generated
vendored
Normal file
111
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink/eventsink.go
generated
vendored
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
/*
|
||||
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 eventsink
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/conversion"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/client-go/tools/record"
|
||||
fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset"
|
||||
)
|
||||
|
||||
// Implements k8s.io/client-go/tools/record.EventSink.
|
||||
type FederatedEventSink struct {
|
||||
clientset fedclientset.Interface
|
||||
}
|
||||
|
||||
// To check if all required functions are implemented.
|
||||
var _ record.EventSink = &FederatedEventSink{}
|
||||
|
||||
func NewFederatedEventSink(clientset fedclientset.Interface) *FederatedEventSink {
|
||||
return &FederatedEventSink{
|
||||
clientset: clientset,
|
||||
}
|
||||
}
|
||||
|
||||
// TODO this is uses a reflection conversion path and is very expensive. federation should update to use client-go
|
||||
|
||||
var scheme = runtime.NewScheme()
|
||||
|
||||
func init() {
|
||||
// register client-go's and kube's Event type under two different GroupVersions
|
||||
// TODO: switch to client-go client for events
|
||||
scheme.AddKnownTypes(v1.SchemeGroupVersion, &v1.Event{})
|
||||
scheme.AddKnownTypes(schema.GroupVersion{Group: "fake-kube-" + v1.SchemeGroupVersion.Group, Version: v1.SchemeGroupVersion.Version}, &v1.Event{})
|
||||
|
||||
if err := scheme.AddConversionFuncs(
|
||||
metav1.Convert_unversioned_Time_To_unversioned_Time,
|
||||
); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := scheme.AddGeneratedDeepCopyFuncs(
|
||||
conversion.GeneratedDeepCopyFunc{
|
||||
Fn: func(in, out interface{}, c *conversion.Cloner) error {
|
||||
in.(*metav1.Time).DeepCopyInto(out.(*metav1.Time))
|
||||
return nil
|
||||
},
|
||||
InType: reflect.TypeOf(&metav1.Time{}),
|
||||
},
|
||||
); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (fes *FederatedEventSink) Create(event *v1.Event) (*v1.Event, error) {
|
||||
ret, err := fes.clientset.Core().Events(event.Namespace).Create(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
retEvent := &v1.Event{}
|
||||
if err := scheme.Convert(ret, retEvent, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return retEvent, nil
|
||||
}
|
||||
|
||||
func (fes *FederatedEventSink) Update(event *v1.Event) (*v1.Event, error) {
|
||||
ret, err := fes.clientset.Core().Events(event.Namespace).Update(event)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
retEvent := &v1.Event{}
|
||||
if err := scheme.Convert(ret, retEvent, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return retEvent, nil
|
||||
}
|
||||
|
||||
func (fes *FederatedEventSink) Patch(event *v1.Event, data []byte) (*v1.Event, error) {
|
||||
ret, err := fes.clientset.Core().Events(event.Namespace).Patch(event.Name, types.StrategicMergePatchType, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
retEvent := &v1.Event{}
|
||||
if err := scheme.Convert(ret, retEvent, nil); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return retEvent, nil
|
||||
}
|
||||
71
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink/eventsink_test.go
generated
vendored
Normal file
71
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink/eventsink_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
/*
|
||||
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 eventsink
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
core "k8s.io/client-go/testing"
|
||||
fakefedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/fake"
|
||||
. "k8s.io/kubernetes/federation/pkg/federation-controller/util/test"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestEventSink(t *testing.T) {
|
||||
fakeFederationClient := &fakefedclientset.Clientset{}
|
||||
createdChan := make(chan runtime.Object, 100)
|
||||
fakeFederationClient.AddReactor("create", "events", func(action core.Action) (bool, runtime.Object, error) {
|
||||
createAction := action.(core.CreateAction)
|
||||
obj := createAction.GetObject()
|
||||
createdChan <- obj
|
||||
return true, obj, nil
|
||||
})
|
||||
updateChan := make(chan runtime.Object, 100)
|
||||
fakeFederationClient.AddReactor("update", "events", func(action core.Action) (bool, runtime.Object, error) {
|
||||
updateAction := action.(core.UpdateAction)
|
||||
obj := updateAction.GetObject()
|
||||
updateChan <- obj
|
||||
return true, obj, nil
|
||||
})
|
||||
|
||||
event := v1.Event{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "bzium",
|
||||
Namespace: "ns",
|
||||
},
|
||||
}
|
||||
sink := NewFederatedEventSink(fakeFederationClient)
|
||||
eventUpdated, err := sink.Create(&event)
|
||||
assert.NoError(t, err)
|
||||
eventV1 := GetObjectFromChan(createdChan).(*v1.Event)
|
||||
assert.NotNil(t, eventV1)
|
||||
// Just some simple sanity checks.
|
||||
assert.Equal(t, event.Name, eventV1.Name)
|
||||
assert.Equal(t, event.Name, eventUpdated.Name)
|
||||
|
||||
eventUpdated, err = sink.Update(&event)
|
||||
assert.NoError(t, err)
|
||||
eventV1 = GetObjectFromChan(updateChan).(*v1.Event)
|
||||
assert.NotNil(t, eventV1)
|
||||
// Just some simple sanity checks.
|
||||
assert.Equal(t, event.Name, eventV1.Name)
|
||||
assert.Equal(t, event.Name, eventUpdated.Name)
|
||||
}
|
||||
524
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/federated_informer.go
generated
vendored
Normal file
524
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/federated_informer.go
generated
vendored
Normal file
|
|
@ -0,0 +1,524 @@
|
|||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
pkgruntime "k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
kubeclientset "k8s.io/client-go/kubernetes"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1"
|
||||
federationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
const (
|
||||
clusterSyncPeriod = 10 * time.Minute
|
||||
userAgentName = "federation-controller"
|
||||
)
|
||||
|
||||
// An object with an origin information.
|
||||
type FederatedObject struct {
|
||||
Object interface{}
|
||||
ClusterName string
|
||||
}
|
||||
|
||||
// FederatedReadOnlyStore is an overlay over multiple stores created in federated clusters.
|
||||
type FederatedReadOnlyStore interface {
|
||||
// Returns all items in the store.
|
||||
List() ([]FederatedObject, error)
|
||||
|
||||
// Returns all items from a cluster.
|
||||
ListFromCluster(clusterName string) ([]interface{}, error)
|
||||
|
||||
// GetKeyFor returns the key under which the item would be put in the store.
|
||||
GetKeyFor(item interface{}) string
|
||||
|
||||
// GetByKey returns the item stored under the given key in the specified cluster (if exist).
|
||||
GetByKey(clusterName string, key string) (interface{}, bool, error)
|
||||
|
||||
// Returns the items stored under the given key in all clusters.
|
||||
GetFromAllClusters(key string) ([]FederatedObject, error)
|
||||
|
||||
// Checks whether stores for all clusters form the lists (and only these) are there and
|
||||
// are synced. This is only a basic check whether the data inside of the store is usable.
|
||||
// It is not a full synchronization/locking mechanism it only tries to ensure that out-of-sync
|
||||
// issues occur less often. All users of the interface should assume
|
||||
// that there may be significant delays in content updates of all kinds and write their
|
||||
// code that it doesn't break if something is slightly out-of-sync.
|
||||
ClustersSynced(clusters []*federationapi.Cluster) bool
|
||||
}
|
||||
|
||||
// An interface to access federation members and clients.
|
||||
type FederationView interface {
|
||||
// GetClientsetForCluster returns a clientset for the cluster, if present.
|
||||
GetClientsetForCluster(clusterName string) (kubeclientset.Interface, error)
|
||||
|
||||
// GetUnreadyClusters returns a list of all clusters that are not ready yet.
|
||||
GetUnreadyClusters() ([]*federationapi.Cluster, error)
|
||||
|
||||
// GetReadyClusters returns all clusters for which the sub-informers are run.
|
||||
GetReadyClusters() ([]*federationapi.Cluster, error)
|
||||
|
||||
// GetReadyCluster returns the cluster with the given name, if found.
|
||||
GetReadyCluster(name string) (*federationapi.Cluster, bool, error)
|
||||
|
||||
// ClustersSynced returns true if the view is synced (for the first time).
|
||||
ClustersSynced() bool
|
||||
}
|
||||
|
||||
// A structure that combines an informer running against federated api server and listening for cluster updates
|
||||
// with multiple Kubernetes API informers (called target informers) running against federation members. Whenever a new
|
||||
// cluster is added to the federation an informer is created for it using TargetInformerFactory. Informers are stopped
|
||||
// when a cluster is either put offline of deleted. It is assumed that some controller keeps an eye on the cluster list
|
||||
// and thus the clusters in ETCD are up to date.
|
||||
type FederatedInformer interface {
|
||||
FederationView
|
||||
|
||||
// Returns a store created over all stores from target informers.
|
||||
GetTargetStore() FederatedReadOnlyStore
|
||||
|
||||
// Starts all the processes.
|
||||
Start()
|
||||
|
||||
// Stops all the processes inside the informer.
|
||||
Stop()
|
||||
}
|
||||
|
||||
// FederatedInformer with extra method for setting fake clients.
|
||||
type FederatedInformerForTestOnly interface {
|
||||
FederatedInformer
|
||||
|
||||
SetClientFactory(func(*federationapi.Cluster) (kubeclientset.Interface, error))
|
||||
}
|
||||
|
||||
// A function that should be used to create an informer on the target object. Store should use
|
||||
// cache.DeletionHandlingMetaNamespaceKeyFunc as a keying function.
|
||||
type TargetInformerFactory func(*federationapi.Cluster, kubeclientset.Interface) (cache.Store, cache.Controller)
|
||||
|
||||
// A structure with cluster lifecycle handler functions. Cluster is available (and ClusterAvailable is fired)
|
||||
// when it is created in federated etcd and ready. Cluster becomes unavailable (and ClusterUnavailable is fired)
|
||||
// when it is either deleted or becomes not ready. When cluster spec (IP)is modified both ClusterAvailable
|
||||
// and ClusterUnavailable are fired.
|
||||
type ClusterLifecycleHandlerFuncs struct {
|
||||
// Fired when the cluster becomes available.
|
||||
ClusterAvailable func(*federationapi.Cluster)
|
||||
// Fired when the cluster becomes unavailable. The second arg contains data that was present
|
||||
// in the cluster before deletion.
|
||||
ClusterUnavailable func(*federationapi.Cluster, []interface{})
|
||||
}
|
||||
|
||||
// Builds a FederatedInformer for the given federation client and factory.
|
||||
func NewFederatedInformer(
|
||||
federationClient federationclientset.Interface,
|
||||
targetInformerFactory TargetInformerFactory,
|
||||
clusterLifecycle *ClusterLifecycleHandlerFuncs) FederatedInformer {
|
||||
|
||||
federatedInformer := &federatedInformerImpl{
|
||||
targetInformerFactory: targetInformerFactory,
|
||||
clientFactory: func(cluster *federationapi.Cluster) (kubeclientset.Interface, error) {
|
||||
clusterConfig, err := BuildClusterConfig(cluster)
|
||||
if err == nil && clusterConfig != nil {
|
||||
clientset := kubeclientset.NewForConfigOrDie(restclient.AddUserAgent(clusterConfig, userAgentName))
|
||||
return clientset, nil
|
||||
}
|
||||
return nil, err
|
||||
},
|
||||
targetInformers: make(map[string]informer),
|
||||
}
|
||||
|
||||
getClusterData := func(name string) []interface{} {
|
||||
data, err := federatedInformer.GetTargetStore().ListFromCluster(name)
|
||||
if err != nil {
|
||||
glog.Errorf("Failed to list %s content: %v", name, err)
|
||||
return make([]interface{}, 0)
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
federatedInformer.clusterInformer.store, federatedInformer.clusterInformer.controller = cache.NewInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (pkgruntime.Object, error) {
|
||||
return federationClient.Federation().Clusters().List(options)
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
return federationClient.Federation().Clusters().Watch(options)
|
||||
},
|
||||
},
|
||||
&federationapi.Cluster{},
|
||||
clusterSyncPeriod,
|
||||
cache.ResourceEventHandlerFuncs{
|
||||
DeleteFunc: func(old interface{}) {
|
||||
oldCluster, ok := old.(*federationapi.Cluster)
|
||||
if ok {
|
||||
var data []interface{}
|
||||
if clusterLifecycle.ClusterUnavailable != nil {
|
||||
data = getClusterData(oldCluster.Name)
|
||||
}
|
||||
federatedInformer.deleteCluster(oldCluster)
|
||||
if clusterLifecycle.ClusterUnavailable != nil {
|
||||
clusterLifecycle.ClusterUnavailable(oldCluster, data)
|
||||
}
|
||||
}
|
||||
},
|
||||
AddFunc: func(cur interface{}) {
|
||||
curCluster, ok := cur.(*federationapi.Cluster)
|
||||
if ok && isClusterReady(curCluster) {
|
||||
federatedInformer.addCluster(curCluster)
|
||||
if clusterLifecycle.ClusterAvailable != nil {
|
||||
clusterLifecycle.ClusterAvailable(curCluster)
|
||||
}
|
||||
} else {
|
||||
glog.Errorf("Cluster %v not added. Not of correct type, or cluster not ready.", cur)
|
||||
}
|
||||
},
|
||||
UpdateFunc: func(old, cur interface{}) {
|
||||
oldCluster, ok := old.(*federationapi.Cluster)
|
||||
if !ok {
|
||||
glog.Errorf("Internal error: Cluster %v not updated. Old cluster not of correct type.", old)
|
||||
return
|
||||
}
|
||||
curCluster, ok := cur.(*federationapi.Cluster)
|
||||
if !ok {
|
||||
glog.Errorf("Internal error: Cluster %v not updated. New cluster not of correct type.", cur)
|
||||
return
|
||||
}
|
||||
if isClusterReady(oldCluster) != isClusterReady(curCluster) || !reflect.DeepEqual(oldCluster.Spec, curCluster.Spec) || !reflect.DeepEqual(oldCluster.ObjectMeta.Annotations, curCluster.ObjectMeta.Annotations) {
|
||||
var data []interface{}
|
||||
if clusterLifecycle.ClusterUnavailable != nil {
|
||||
data = getClusterData(oldCluster.Name)
|
||||
}
|
||||
federatedInformer.deleteCluster(oldCluster)
|
||||
if clusterLifecycle.ClusterUnavailable != nil {
|
||||
clusterLifecycle.ClusterUnavailable(oldCluster, data)
|
||||
}
|
||||
|
||||
if isClusterReady(curCluster) {
|
||||
federatedInformer.addCluster(curCluster)
|
||||
if clusterLifecycle.ClusterAvailable != nil {
|
||||
clusterLifecycle.ClusterAvailable(curCluster)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
glog.V(4).Infof("Cluster %v not updated to %v as ready status and specs are identical", oldCluster, curCluster)
|
||||
}
|
||||
},
|
||||
},
|
||||
)
|
||||
return federatedInformer
|
||||
}
|
||||
|
||||
func isClusterReady(cluster *federationapi.Cluster) bool {
|
||||
for _, condition := range cluster.Status.Conditions {
|
||||
if condition.Type == federationapi.ClusterReady {
|
||||
if condition.Status == apiv1.ConditionTrue {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
type informer struct {
|
||||
controller cache.Controller
|
||||
store cache.Store
|
||||
stopChan chan struct{}
|
||||
}
|
||||
|
||||
type federatedInformerImpl struct {
|
||||
sync.Mutex
|
||||
|
||||
// Informer on federated clusters.
|
||||
clusterInformer informer
|
||||
|
||||
// Target informers factory
|
||||
targetInformerFactory TargetInformerFactory
|
||||
|
||||
// Structures returned by targetInformerFactory
|
||||
targetInformers map[string]informer
|
||||
|
||||
// A function to build clients.
|
||||
clientFactory func(*federationapi.Cluster) (kubeclientset.Interface, error)
|
||||
}
|
||||
|
||||
// *federatedInformerImpl implements FederatedInformer interface.
|
||||
var _ FederatedInformer = &federatedInformerImpl{}
|
||||
|
||||
type federatedStoreImpl struct {
|
||||
federatedInformer *federatedInformerImpl
|
||||
}
|
||||
|
||||
func (f *federatedInformerImpl) Stop() {
|
||||
glog.V(4).Infof("Stopping federated informer.")
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
glog.V(4).Infof("... Closing cluster informer channel.")
|
||||
close(f.clusterInformer.stopChan)
|
||||
for key, informer := range f.targetInformers {
|
||||
glog.V(4).Infof("... Closing informer channel for %q.", key)
|
||||
close(informer.stopChan)
|
||||
// Remove each informer after it has been stopped to prevent
|
||||
// subsequent cluster deletion from attempting to double close
|
||||
// an informer's stop channel.
|
||||
delete(f.targetInformers, key)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *federatedInformerImpl) Start() {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.clusterInformer.stopChan = make(chan struct{})
|
||||
go f.clusterInformer.controller.Run(f.clusterInformer.stopChan)
|
||||
}
|
||||
|
||||
func (f *federatedInformerImpl) SetClientFactory(clientFactory func(*federationapi.Cluster) (kubeclientset.Interface, error)) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
f.clientFactory = clientFactory
|
||||
}
|
||||
|
||||
// GetClientsetForCluster returns a clientset for the cluster, if present.
|
||||
func (f *federatedInformerImpl) GetClientsetForCluster(clusterName string) (kubeclientset.Interface, error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
return f.getClientsetForClusterUnlocked(clusterName)
|
||||
}
|
||||
|
||||
func (f *federatedInformerImpl) getClientsetForClusterUnlocked(clusterName string) (kubeclientset.Interface, error) {
|
||||
// No locking needed. Will happen in f.GetCluster.
|
||||
glog.V(4).Infof("Getting clientset for cluster %q", clusterName)
|
||||
if cluster, found, err := f.getReadyClusterUnlocked(clusterName); found && err == nil {
|
||||
glog.V(4).Infof("Got clientset for cluster %q", clusterName)
|
||||
return f.clientFactory(cluster)
|
||||
} else {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("cluster %q not found", clusterName)
|
||||
}
|
||||
|
||||
func (f *federatedInformerImpl) GetUnreadyClusters() ([]*federationapi.Cluster, error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
items := f.clusterInformer.store.List()
|
||||
result := make([]*federationapi.Cluster, 0, len(items))
|
||||
for _, item := range items {
|
||||
if cluster, ok := item.(*federationapi.Cluster); ok {
|
||||
if !isClusterReady(cluster) {
|
||||
result = append(result, cluster)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("wrong data in FederatedInformerImpl cluster store: %v", item)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetReadyClusters returns all clusters for which the sub-informers are run.
|
||||
func (f *federatedInformerImpl) GetReadyClusters() ([]*federationapi.Cluster, error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
|
||||
items := f.clusterInformer.store.List()
|
||||
result := make([]*federationapi.Cluster, 0, len(items))
|
||||
for _, item := range items {
|
||||
if cluster, ok := item.(*federationapi.Cluster); ok {
|
||||
if isClusterReady(cluster) {
|
||||
result = append(result, cluster)
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("wrong data in FederatedInformerImpl cluster store: %v", item)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetCluster returns the cluster with the given name, if found.
|
||||
func (f *federatedInformerImpl) GetReadyCluster(name string) (*federationapi.Cluster, bool, error) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
return f.getReadyClusterUnlocked(name)
|
||||
}
|
||||
|
||||
func (f *federatedInformerImpl) getReadyClusterUnlocked(name string) (*federationapi.Cluster, bool, error) {
|
||||
if obj, exist, err := f.clusterInformer.store.GetByKey(name); exist && err == nil {
|
||||
if cluster, ok := obj.(*federationapi.Cluster); ok {
|
||||
if isClusterReady(cluster) {
|
||||
return cluster, true, nil
|
||||
}
|
||||
return nil, false, nil
|
||||
|
||||
}
|
||||
return nil, false, fmt.Errorf("wrong data in FederatedInformerImpl cluster store: %v", obj)
|
||||
|
||||
} else {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
|
||||
// Synced returns true if the view is synced (for the first time)
|
||||
func (f *federatedInformerImpl) ClustersSynced() bool {
|
||||
return f.clusterInformer.controller.HasSynced()
|
||||
}
|
||||
|
||||
// Adds the given cluster to federated informer.
|
||||
func (f *federatedInformerImpl) addCluster(cluster *federationapi.Cluster) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
name := cluster.Name
|
||||
if client, err := f.getClientsetForClusterUnlocked(name); err == nil {
|
||||
store, controller := f.targetInformerFactory(cluster, client)
|
||||
targetInformer := informer{
|
||||
controller: controller,
|
||||
store: store,
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
f.targetInformers[name] = targetInformer
|
||||
go targetInformer.controller.Run(targetInformer.stopChan)
|
||||
} else {
|
||||
// TODO: create also an event for cluster.
|
||||
glog.Errorf("Failed to create a client for cluster: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Removes the cluster from federated informer.
|
||||
func (f *federatedInformerImpl) deleteCluster(cluster *federationapi.Cluster) {
|
||||
f.Lock()
|
||||
defer f.Unlock()
|
||||
name := cluster.Name
|
||||
if targetInformer, found := f.targetInformers[name]; found {
|
||||
close(targetInformer.stopChan)
|
||||
}
|
||||
delete(f.targetInformers, name)
|
||||
}
|
||||
|
||||
// Returns a store created over all stores from target informers.
|
||||
func (f *federatedInformerImpl) GetTargetStore() FederatedReadOnlyStore {
|
||||
return &federatedStoreImpl{
|
||||
federatedInformer: f,
|
||||
}
|
||||
}
|
||||
|
||||
// Returns all items in the store.
|
||||
func (fs *federatedStoreImpl) List() ([]FederatedObject, error) {
|
||||
fs.federatedInformer.Lock()
|
||||
defer fs.federatedInformer.Unlock()
|
||||
|
||||
result := make([]FederatedObject, 0)
|
||||
for clusterName, targetInformer := range fs.federatedInformer.targetInformers {
|
||||
for _, value := range targetInformer.store.List() {
|
||||
result = append(result, FederatedObject{ClusterName: clusterName, Object: value})
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Returns all items in the given cluster.
|
||||
func (fs *federatedStoreImpl) ListFromCluster(clusterName string) ([]interface{}, error) {
|
||||
fs.federatedInformer.Lock()
|
||||
defer fs.federatedInformer.Unlock()
|
||||
|
||||
result := make([]interface{}, 0)
|
||||
if targetInformer, found := fs.federatedInformer.targetInformers[clusterName]; found {
|
||||
values := targetInformer.store.List()
|
||||
result = append(result, values...)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetByKey returns the item stored under the given key in the specified cluster (if exist).
|
||||
func (fs *federatedStoreImpl) GetByKey(clusterName string, key string) (interface{}, bool, error) {
|
||||
fs.federatedInformer.Lock()
|
||||
defer fs.federatedInformer.Unlock()
|
||||
if targetInformer, found := fs.federatedInformer.targetInformers[clusterName]; found {
|
||||
return targetInformer.store.GetByKey(key)
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// Returns the items stored under the given key in all clusters.
|
||||
func (fs *federatedStoreImpl) GetFromAllClusters(key string) ([]FederatedObject, error) {
|
||||
fs.federatedInformer.Lock()
|
||||
defer fs.federatedInformer.Unlock()
|
||||
|
||||
result := make([]FederatedObject, 0)
|
||||
for clusterName, targetInformer := range fs.federatedInformer.targetInformers {
|
||||
value, exist, err := targetInformer.store.GetByKey(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if exist {
|
||||
result = append(result, FederatedObject{ClusterName: clusterName, Object: value})
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetKeyFor returns the key under which the item would be put in the store.
|
||||
func (fs *federatedStoreImpl) GetKeyFor(item interface{}) string {
|
||||
// TODO: support other keying functions.
|
||||
key, _ := cache.DeletionHandlingMetaNamespaceKeyFunc(item)
|
||||
return key
|
||||
}
|
||||
|
||||
// Checks whether stores for all clusters form the lists (and only these) are there and
|
||||
// are synced.
|
||||
func (fs *federatedStoreImpl) ClustersSynced(clusters []*federationapi.Cluster) bool {
|
||||
|
||||
// Get the list of informers to check under a lock and check it outside.
|
||||
okSoFar, informersToCheck := func() (bool, []informer) {
|
||||
fs.federatedInformer.Lock()
|
||||
defer fs.federatedInformer.Unlock()
|
||||
|
||||
if len(fs.federatedInformer.targetInformers) != len(clusters) {
|
||||
return false, []informer{}
|
||||
}
|
||||
informersToCheck := make([]informer, 0, len(clusters))
|
||||
for _, cluster := range clusters {
|
||||
if targetInformer, found := fs.federatedInformer.targetInformers[cluster.Name]; found {
|
||||
informersToCheck = append(informersToCheck, targetInformer)
|
||||
} else {
|
||||
return false, []informer{}
|
||||
}
|
||||
}
|
||||
return true, informersToCheck
|
||||
}()
|
||||
|
||||
if !okSoFar {
|
||||
return false
|
||||
}
|
||||
for _, informerToCheck := range informersToCheck {
|
||||
if !informerToCheck.controller.HasSynced() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
150
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/federated_informer_test.go
generated
vendored
Normal file
150
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/federated_informer_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
/*
|
||||
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 (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
kubeclientset "k8s.io/client-go/kubernetes"
|
||||
fakekubeclientset "k8s.io/client-go/kubernetes/fake"
|
||||
core "k8s.io/client-go/testing"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1"
|
||||
fakefederationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_clientset/fake"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Basic test for Federated Informer. Checks whether the subinformer are added and deleted
|
||||
// when the corresponding cluster entries appear and disappear from etcd.
|
||||
func TestFederatedInformer(t *testing.T) {
|
||||
fakeFederationClient := &fakefederationclientset.Clientset{}
|
||||
|
||||
// Add a single cluster to federation and remove it when needed.
|
||||
cluster := federationapi.Cluster{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "mycluster",
|
||||
},
|
||||
Status: federationapi.ClusterStatus{
|
||||
Conditions: []federationapi.ClusterCondition{
|
||||
{Type: federationapi.ClusterReady, Status: apiv1.ConditionTrue},
|
||||
},
|
||||
},
|
||||
}
|
||||
fakeFederationClient.AddReactor("list", "clusters", func(action core.Action) (bool, runtime.Object, error) {
|
||||
return true, &federationapi.ClusterList{Items: []federationapi.Cluster{cluster}}, nil
|
||||
})
|
||||
deleteChan := make(chan struct{})
|
||||
fakeFederationClient.AddWatchReactor("clusters", func(action core.Action) (bool, watch.Interface, error) {
|
||||
fakeWatch := watch.NewFake()
|
||||
go func() {
|
||||
<-deleteChan
|
||||
fakeWatch.Delete(&cluster)
|
||||
}()
|
||||
return true, fakeWatch, nil
|
||||
})
|
||||
|
||||
fakeKubeClient := &fakekubeclientset.Clientset{}
|
||||
// There is a single service ns1/s1 in cluster mycluster.
|
||||
service := apiv1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s1",
|
||||
},
|
||||
}
|
||||
fakeKubeClient.AddReactor("list", "services", func(action core.Action) (bool, runtime.Object, error) {
|
||||
return true, &apiv1.ServiceList{Items: []apiv1.Service{service}}, nil
|
||||
})
|
||||
fakeKubeClient.AddWatchReactor("services", func(action core.Action) (bool, watch.Interface, error) {
|
||||
return true, watch.NewFake(), nil
|
||||
})
|
||||
|
||||
targetInformerFactory := func(cluster *federationapi.Cluster, clientset kubeclientset.Interface) (cache.Store, cache.Controller) {
|
||||
return cache.NewInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
return clientset.Core().Services(metav1.NamespaceAll).List(options)
|
||||
},
|
||||
WatchFunc: func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
return clientset.Core().Services(metav1.NamespaceAll).Watch(options)
|
||||
},
|
||||
},
|
||||
&apiv1.Service{},
|
||||
10*time.Second,
|
||||
cache.ResourceEventHandlerFuncs{})
|
||||
}
|
||||
|
||||
addedClusters := make(chan string, 1)
|
||||
deletedClusters := make(chan string, 1)
|
||||
lifecycle := ClusterLifecycleHandlerFuncs{
|
||||
ClusterAvailable: func(cluster *federationapi.Cluster) {
|
||||
addedClusters <- cluster.Name
|
||||
close(addedClusters)
|
||||
},
|
||||
ClusterUnavailable: func(cluster *federationapi.Cluster, _ []interface{}) {
|
||||
deletedClusters <- cluster.Name
|
||||
close(deletedClusters)
|
||||
},
|
||||
}
|
||||
|
||||
informer := NewFederatedInformer(fakeFederationClient, targetInformerFactory, &lifecycle).(*federatedInformerImpl)
|
||||
informer.clientFactory = func(cluster *federationapi.Cluster) (kubeclientset.Interface, error) {
|
||||
return fakeKubeClient, nil
|
||||
}
|
||||
assert.NotNil(t, informer)
|
||||
informer.Start()
|
||||
|
||||
// Wait until mycluster is synced.
|
||||
for !informer.GetTargetStore().ClustersSynced([]*federationapi.Cluster{&cluster}) {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
readyClusters, err := informer.GetReadyClusters()
|
||||
assert.NoError(t, err)
|
||||
assert.Contains(t, readyClusters, &cluster)
|
||||
serviceList, err := informer.GetTargetStore().List()
|
||||
assert.NoError(t, err)
|
||||
federatedService := FederatedObject{ClusterName: "mycluster", Object: &service}
|
||||
assert.Contains(t, serviceList, federatedService)
|
||||
service1, found, err := informer.GetTargetStore().GetByKey("mycluster", "ns1/s1")
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, found)
|
||||
assert.EqualValues(t, &service, service1)
|
||||
assert.Equal(t, "mycluster", <-addedClusters)
|
||||
|
||||
// All checked, lets delete the cluster.
|
||||
deleteChan <- struct{}{}
|
||||
for !informer.GetTargetStore().ClustersSynced([]*federationapi.Cluster{}) {
|
||||
time.Sleep(time.Millisecond * 100)
|
||||
}
|
||||
readyClusters, err = informer.GetReadyClusters()
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, readyClusters)
|
||||
|
||||
serviceList, err = informer.GetTargetStore().List()
|
||||
assert.NoError(t, err)
|
||||
assert.Empty(t, serviceList)
|
||||
|
||||
assert.Equal(t, "mycluster", <-deletedClusters)
|
||||
|
||||
// Test complete.
|
||||
informer.Stop()
|
||||
}
|
||||
157
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/federated_updater.go
generated
vendored
Normal file
157
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/federated_updater.go
generated
vendored
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
pkgruntime "k8s.io/apimachinery/pkg/runtime"
|
||||
kubeclientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/client-go/tools/record"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
// Type of the operation that can be executed in Federated.
|
||||
type FederatedOperationType string
|
||||
|
||||
const (
|
||||
OperationTypeAdd = "add"
|
||||
OperationTypeUpdate = "update"
|
||||
OperationTypeDelete = "delete"
|
||||
)
|
||||
|
||||
// FederatedOperation definition contains type (add/update/delete) and the object itself.
|
||||
type FederatedOperation struct {
|
||||
Type FederatedOperationType
|
||||
ClusterName string
|
||||
Obj pkgruntime.Object
|
||||
Key string
|
||||
}
|
||||
|
||||
// A helper that executes the given set of updates on federation, in parallel.
|
||||
type FederatedUpdater interface {
|
||||
// Executes the given set of operations.
|
||||
Update([]FederatedOperation) error
|
||||
}
|
||||
|
||||
// A function that executes some operation using the passed client and object.
|
||||
type FederatedOperationHandler func(kubeclientset.Interface, pkgruntime.Object) error
|
||||
|
||||
type federatedUpdaterImpl struct {
|
||||
federation FederationView
|
||||
|
||||
kind string
|
||||
|
||||
timeout time.Duration
|
||||
|
||||
eventRecorder record.EventRecorder
|
||||
|
||||
addFunction FederatedOperationHandler
|
||||
updateFunction FederatedOperationHandler
|
||||
deleteFunction FederatedOperationHandler
|
||||
}
|
||||
|
||||
func NewFederatedUpdater(federation FederationView, kind string, timeout time.Duration, recorder record.EventRecorder, add, update, del FederatedOperationHandler) FederatedUpdater {
|
||||
return &federatedUpdaterImpl{
|
||||
federation: federation,
|
||||
kind: kind,
|
||||
timeout: timeout,
|
||||
eventRecorder: recorder,
|
||||
addFunction: add,
|
||||
updateFunction: update,
|
||||
deleteFunction: del,
|
||||
}
|
||||
}
|
||||
|
||||
func (fu *federatedUpdaterImpl) recordEvent(obj runtime.Object, eventType, eventVerb string, args ...interface{}) {
|
||||
messageFmt := eventVerb + " %s %q in cluster %s"
|
||||
fu.eventRecorder.Eventf(obj, api.EventTypeNormal, eventType, messageFmt, args...)
|
||||
}
|
||||
|
||||
// Update executes the given set of operations within the timeout specified for
|
||||
// the instance. Timeout is best-effort. There is no guarantee that the
|
||||
// underlying operations are stopped when it is reached. However the function
|
||||
// will return after the timeout with a non-nil error.
|
||||
func (fu *federatedUpdaterImpl) Update(ops []FederatedOperation) error {
|
||||
done := make(chan error, len(ops))
|
||||
for _, op := range ops {
|
||||
go func(op FederatedOperation) {
|
||||
clusterName := op.ClusterName
|
||||
|
||||
// TODO: Ensure that the clientset has reasonable timeout.
|
||||
clientset, err := fu.federation.GetClientsetForCluster(clusterName)
|
||||
if err != nil {
|
||||
done <- err
|
||||
return
|
||||
}
|
||||
|
||||
eventArgs := []interface{}{fu.kind, op.Key, clusterName}
|
||||
baseEventType := fmt.Sprintf("%s", op.Type)
|
||||
eventType := fmt.Sprintf("%sInCluster", strings.Title(baseEventType))
|
||||
|
||||
switch op.Type {
|
||||
case OperationTypeAdd:
|
||||
// TODO s+OperationTypeAdd+OperationTypeCreate+
|
||||
baseEventType = "create"
|
||||
eventType := "CreateInCluster"
|
||||
|
||||
fu.recordEvent(op.Obj, eventType, "Creating", eventArgs...)
|
||||
err = fu.addFunction(clientset, op.Obj)
|
||||
case OperationTypeUpdate:
|
||||
fu.recordEvent(op.Obj, eventType, "Updating", eventArgs...)
|
||||
err = fu.updateFunction(clientset, op.Obj)
|
||||
case OperationTypeDelete:
|
||||
fu.recordEvent(op.Obj, eventType, "Deleting", eventArgs...)
|
||||
err = fu.deleteFunction(clientset, op.Obj)
|
||||
// IsNotFound error is fine since that means the object is deleted already.
|
||||
if errors.IsNotFound(err) {
|
||||
err = nil
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
eventType := eventType + "Failed"
|
||||
messageFmt := "Failed to " + baseEventType + " %s %q in cluster %s: %v"
|
||||
eventArgs = append(eventArgs, err)
|
||||
fu.eventRecorder.Eventf(op.Obj, api.EventTypeWarning, eventType, messageFmt, eventArgs...)
|
||||
}
|
||||
|
||||
done <- err
|
||||
}(op)
|
||||
}
|
||||
start := time.Now()
|
||||
for i := 0; i < len(ops); i++ {
|
||||
now := time.Now()
|
||||
if !now.Before(start.Add(fu.timeout)) {
|
||||
return fmt.Errorf("failed to finish all operations in %v", fu.timeout)
|
||||
}
|
||||
select {
|
||||
case err := <-done:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case <-time.After(start.Add(fu.timeout).Sub(now)):
|
||||
return fmt.Errorf("failed to finish all operations in %v", fu.timeout)
|
||||
}
|
||||
}
|
||||
// All operations finished in time.
|
||||
return nil
|
||||
}
|
||||
157
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/federated_updater_test.go
generated
vendored
Normal file
157
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/federated_updater_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
pkgruntime "k8s.io/apimachinery/pkg/runtime"
|
||||
kubeclientset "k8s.io/client-go/kubernetes"
|
||||
fakekubeclientset "k8s.io/client-go/kubernetes/fake"
|
||||
federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// Fake federation view.
|
||||
type fakeFederationView struct {
|
||||
}
|
||||
|
||||
// Verify that fakeFederationView implements FederationView interface
|
||||
var _ FederationView = &fakeFederationView{}
|
||||
|
||||
func (f *fakeFederationView) GetClientsetForCluster(clusterName string) (kubeclientset.Interface, error) {
|
||||
return &fakekubeclientset.Clientset{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeFederationView) GetReadyClusters() ([]*federationapi.Cluster, error) {
|
||||
return []*federationapi.Cluster{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeFederationView) GetUnreadyClusters() ([]*federationapi.Cluster, error) {
|
||||
return []*federationapi.Cluster{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeFederationView) GetReadyCluster(name string) (*federationapi.Cluster, bool, error) {
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
func (f *fakeFederationView) ClustersSynced() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
type fakeEventRecorder struct{}
|
||||
|
||||
func (f *fakeEventRecorder) Event(object pkgruntime.Object, eventtype, reason, message string) {}
|
||||
func (f *fakeEventRecorder) Eventf(object pkgruntime.Object, eventtype, reason, messageFmt string, args ...interface{}) {
|
||||
}
|
||||
func (f *fakeEventRecorder) PastEventf(object pkgruntime.Object, timestamp metav1.Time, eventtype, reason, messageFmt string, args ...interface{}) {
|
||||
}
|
||||
|
||||
func TestFederatedUpdaterOK(t *testing.T) {
|
||||
addChan := make(chan string, 5)
|
||||
updateChan := make(chan string, 5)
|
||||
|
||||
updater := NewFederatedUpdater(&fakeFederationView{}, "foo", time.Minute, &fakeEventRecorder{},
|
||||
func(_ kubeclientset.Interface, obj pkgruntime.Object) error {
|
||||
service := obj.(*apiv1.Service)
|
||||
addChan <- service.Name
|
||||
return nil
|
||||
},
|
||||
func(_ kubeclientset.Interface, obj pkgruntime.Object) error {
|
||||
service := obj.(*apiv1.Service)
|
||||
updateChan <- service.Name
|
||||
return nil
|
||||
},
|
||||
noop)
|
||||
|
||||
err := updater.Update([]FederatedOperation{
|
||||
{
|
||||
Type: OperationTypeAdd,
|
||||
Obj: makeService("A", "s1"),
|
||||
},
|
||||
{
|
||||
Type: OperationTypeUpdate,
|
||||
Obj: makeService("B", "s2"),
|
||||
},
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
add := <-addChan
|
||||
update := <-updateChan
|
||||
assert.Equal(t, "s1", add)
|
||||
assert.Equal(t, "s2", update)
|
||||
}
|
||||
|
||||
func TestFederatedUpdaterError(t *testing.T) {
|
||||
updater := NewFederatedUpdater(&fakeFederationView{}, "foo", time.Minute, &fakeEventRecorder{},
|
||||
func(_ kubeclientset.Interface, obj pkgruntime.Object) error {
|
||||
return fmt.Errorf("boom")
|
||||
}, noop, noop)
|
||||
|
||||
err := updater.Update([]FederatedOperation{
|
||||
{
|
||||
Type: OperationTypeAdd,
|
||||
Obj: makeService("A", "s1"),
|
||||
},
|
||||
{
|
||||
Type: OperationTypeUpdate,
|
||||
Obj: makeService("B", "s1"),
|
||||
},
|
||||
})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestFederatedUpdaterTimeout(t *testing.T) {
|
||||
start := time.Now()
|
||||
updater := NewFederatedUpdater(&fakeFederationView{}, "foo", time.Second, &fakeEventRecorder{},
|
||||
func(_ kubeclientset.Interface, obj pkgruntime.Object) error {
|
||||
time.Sleep(time.Minute)
|
||||
return nil
|
||||
},
|
||||
noop, noop)
|
||||
|
||||
err := updater.Update([]FederatedOperation{
|
||||
{
|
||||
Type: OperationTypeAdd,
|
||||
Obj: makeService("A", "s1"),
|
||||
},
|
||||
{
|
||||
Type: OperationTypeUpdate,
|
||||
Obj: makeService("B", "s1"),
|
||||
},
|
||||
})
|
||||
end := time.Now()
|
||||
assert.Error(t, err)
|
||||
assert.True(t, start.Add(10*time.Second).After(end))
|
||||
}
|
||||
|
||||
func makeService(cluster, name string) *apiv1.Service {
|
||||
return &apiv1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func noop(_ kubeclientset.Interface, _ pkgruntime.Object) error {
|
||||
return nil
|
||||
}
|
||||
43
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/finalizers/BUILD
generated
vendored
Normal file
43
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/finalizers/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["finalizers.go"],
|
||||
deps = [
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["finalizers_test.go"],
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
],
|
||||
)
|
||||
66
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/finalizers/finalizers.go
generated
vendored
Normal file
66
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/finalizers/finalizers.go
generated
vendored
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/*
|
||||
Copyright 2017 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.
|
||||
*/
|
||||
|
||||
// Helper functions for manipulating finalizers.
|
||||
package finalizers
|
||||
|
||||
import (
|
||||
meta "k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// HasFinalizer returns true if the given object has the given finalizer in its ObjectMeta.
|
||||
func HasFinalizer(obj runtime.Object, finalizer string) (bool, error) {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
finalizers := sets.NewString(accessor.GetFinalizers()...)
|
||||
return finalizers.Has(finalizer), nil
|
||||
}
|
||||
|
||||
// AddFinalizers adds the given finalizers to the given objects ObjectMeta.
|
||||
// Returns true if the object was updated.
|
||||
func AddFinalizers(obj runtime.Object, newFinalizers sets.String) (bool, error) {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
oldFinalizers := sets.NewString(accessor.GetFinalizers()...)
|
||||
if oldFinalizers.IsSuperset(newFinalizers) {
|
||||
return false, nil
|
||||
}
|
||||
allFinalizers := oldFinalizers.Union(newFinalizers)
|
||||
accessor.SetFinalizers(allFinalizers.List())
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// RemoveFinalizers removes the given finalizers from the given objects ObjectMeta.
|
||||
// Returns true if the object was updated.
|
||||
func RemoveFinalizers(obj runtime.Object, finalizers sets.String) (bool, error) {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
oldFinalizers := sets.NewString(accessor.GetFinalizers()...)
|
||||
if oldFinalizers.Intersection(finalizers).Len() == 0 {
|
||||
return false, nil
|
||||
}
|
||||
newFinalizers := oldFinalizers.Difference(finalizers)
|
||||
accessor.SetFinalizers(newFinalizers.List())
|
||||
return true, nil
|
||||
}
|
||||
171
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/finalizers/finalizers_test.go
generated
vendored
Normal file
171
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/finalizers/finalizers_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
Copyright 2017 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 finalizers
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/api/core/v1"
|
||||
meta "k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
func newObj(finalizers []string) runtime.Object {
|
||||
pod := v1.Pod{}
|
||||
pod.ObjectMeta.Finalizers = finalizers
|
||||
return &pod
|
||||
}
|
||||
|
||||
func TestHasFinalizer(t *testing.T) {
|
||||
testCases := []struct {
|
||||
obj runtime.Object
|
||||
finalizer string
|
||||
result bool
|
||||
}{
|
||||
{
|
||||
newObj([]string{}),
|
||||
"",
|
||||
false,
|
||||
},
|
||||
{
|
||||
newObj([]string{}),
|
||||
"someFinalizer",
|
||||
false,
|
||||
},
|
||||
{
|
||||
newObj([]string{"someFinalizer"}),
|
||||
"",
|
||||
false,
|
||||
},
|
||||
{
|
||||
newObj([]string{"someFinalizer"}),
|
||||
"anotherFinalizer",
|
||||
false,
|
||||
},
|
||||
{
|
||||
newObj([]string{"someFinalizer"}),
|
||||
"someFinalizer",
|
||||
true,
|
||||
},
|
||||
{
|
||||
newObj([]string{"anotherFinalizer", "someFinalizer"}),
|
||||
"someFinalizer",
|
||||
true,
|
||||
},
|
||||
}
|
||||
for index, test := range testCases {
|
||||
hasFinalizer, _ := HasFinalizer(test.obj, test.finalizer)
|
||||
assert.Equal(t, hasFinalizer, test.result, fmt.Sprintf("Test case %d failed. Expected: %v, actual: %v", index, test.result, hasFinalizer))
|
||||
}
|
||||
}
|
||||
|
||||
func TestAddFinalizers(t *testing.T) {
|
||||
testCases := []struct {
|
||||
obj runtime.Object
|
||||
finalizers sets.String
|
||||
isUpdated bool
|
||||
newFinalizers []string
|
||||
}{
|
||||
{
|
||||
newObj([]string{}),
|
||||
sets.NewString(),
|
||||
false,
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
newObj([]string{}),
|
||||
sets.NewString("someFinalizer"),
|
||||
true,
|
||||
[]string{"someFinalizer"},
|
||||
},
|
||||
{
|
||||
newObj([]string{"someFinalizer"}),
|
||||
sets.NewString(),
|
||||
false,
|
||||
[]string{"someFinalizer"},
|
||||
},
|
||||
{
|
||||
newObj([]string{"someFinalizer"}),
|
||||
sets.NewString("anotherFinalizer"),
|
||||
true,
|
||||
[]string{"anotherFinalizer", "someFinalizer"},
|
||||
},
|
||||
{
|
||||
newObj([]string{"someFinalizer"}),
|
||||
sets.NewString("someFinalizer"),
|
||||
false,
|
||||
[]string{"someFinalizer"},
|
||||
},
|
||||
}
|
||||
for index, test := range testCases {
|
||||
isUpdated, _ := AddFinalizers(test.obj, test.finalizers)
|
||||
assert.Equal(t, isUpdated, test.isUpdated, fmt.Sprintf("Test case %d failed. Expected isUpdated: %v, actual: %v", index, test.isUpdated, isUpdated))
|
||||
accessor, _ := meta.Accessor(test.obj)
|
||||
newFinalizers := accessor.GetFinalizers()
|
||||
assert.Equal(t, test.newFinalizers, newFinalizers, fmt.Sprintf("Test case %d failed. Expected finalizers: %v, actual: %v", index, test.newFinalizers, newFinalizers))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRemoveFinalizers(t *testing.T) {
|
||||
testCases := []struct {
|
||||
obj runtime.Object
|
||||
finalizers sets.String
|
||||
isUpdated bool
|
||||
newFinalizers []string
|
||||
}{
|
||||
{
|
||||
newObj([]string{}),
|
||||
sets.NewString(),
|
||||
false,
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
newObj([]string{}),
|
||||
sets.NewString("someFinalizer"),
|
||||
false,
|
||||
[]string{},
|
||||
},
|
||||
{
|
||||
newObj([]string{"someFinalizer"}),
|
||||
sets.NewString(),
|
||||
false,
|
||||
[]string{"someFinalizer"},
|
||||
},
|
||||
{
|
||||
newObj([]string{"someFinalizer"}),
|
||||
sets.NewString("anotherFinalizer"),
|
||||
false,
|
||||
[]string{"someFinalizer"},
|
||||
},
|
||||
{
|
||||
newObj([]string{"someFinalizer", "anotherFinalizer"}),
|
||||
sets.NewString("someFinalizer"),
|
||||
true,
|
||||
[]string{"anotherFinalizer"},
|
||||
},
|
||||
}
|
||||
for index, test := range testCases {
|
||||
isUpdated, _ := RemoveFinalizers(test.obj, test.finalizers)
|
||||
assert.Equal(t, isUpdated, test.isUpdated, fmt.Sprintf("Test case %d failed. Expected isUpdated: %v, actual: %v", index, test.isUpdated, isUpdated))
|
||||
accessor, _ := meta.Accessor(test.obj)
|
||||
newFinalizers := accessor.GetFinalizers()
|
||||
assert.Equal(t, test.newFinalizers, newFinalizers, fmt.Sprintf("Test case %d failed. Expected finalizers: %v, actual: %v", index, test.newFinalizers, newFinalizers))
|
||||
}
|
||||
}
|
||||
112
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/handlers.go
generated
vendored
Normal file
112
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/handlers.go
generated
vendored
Normal file
|
|
@ -0,0 +1,112 @@
|
|||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"reflect"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
pkgruntime "k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// Returns cache.ResourceEventHandlerFuncs that trigger the given function
|
||||
// on all object changes.
|
||||
func NewTriggerOnAllChanges(triggerFunc func(pkgruntime.Object)) *cache.ResourceEventHandlerFuncs {
|
||||
return &cache.ResourceEventHandlerFuncs{
|
||||
DeleteFunc: func(old interface{}) {
|
||||
oldObj := old.(pkgruntime.Object)
|
||||
triggerFunc(oldObj)
|
||||
},
|
||||
AddFunc: func(cur interface{}) {
|
||||
curObj := cur.(pkgruntime.Object)
|
||||
triggerFunc(curObj)
|
||||
},
|
||||
UpdateFunc: func(old, cur interface{}) {
|
||||
curObj := cur.(pkgruntime.Object)
|
||||
if !reflect.DeepEqual(old, cur) {
|
||||
triggerFunc(curObj)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Returns cache.ResourceEventHandlerFuncs that trigger the given function
|
||||
// on object add and delete as well as spec/object meta on update.
|
||||
func NewTriggerOnMetaAndSpecChanges(triggerFunc func(pkgruntime.Object)) *cache.ResourceEventHandlerFuncs {
|
||||
getFieldOrPanic := func(obj interface{}, fieldName string) interface{} {
|
||||
val := reflect.ValueOf(obj).Elem().FieldByName(fieldName)
|
||||
if val.IsValid() {
|
||||
return val.Interface()
|
||||
} else {
|
||||
panic(fmt.Errorf("field not found: %s", fieldName))
|
||||
}
|
||||
}
|
||||
return &cache.ResourceEventHandlerFuncs{
|
||||
DeleteFunc: func(old interface{}) {
|
||||
oldObj := old.(pkgruntime.Object)
|
||||
triggerFunc(oldObj)
|
||||
},
|
||||
AddFunc: func(cur interface{}) {
|
||||
curObj := cur.(pkgruntime.Object)
|
||||
triggerFunc(curObj)
|
||||
},
|
||||
UpdateFunc: func(old, cur interface{}) {
|
||||
curObj := cur.(pkgruntime.Object)
|
||||
oldMeta := getFieldOrPanic(old, "ObjectMeta").(metav1.ObjectMeta)
|
||||
curMeta := getFieldOrPanic(cur, "ObjectMeta").(metav1.ObjectMeta)
|
||||
if !ObjectMetaEquivalent(oldMeta, curMeta) ||
|
||||
!reflect.DeepEqual(oldMeta.DeletionTimestamp, curMeta.DeletionTimestamp) ||
|
||||
!reflect.DeepEqual(getFieldOrPanic(old, "Spec"), getFieldOrPanic(cur, "Spec")) {
|
||||
triggerFunc(curObj)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Returns cache.ResourceEventHandlerFuncs that trigger the given function
|
||||
// on object add/delete or ObjectMeta or given field is updated.
|
||||
func NewTriggerOnMetaAndFieldChanges(field string, triggerFunc func(pkgruntime.Object)) *cache.ResourceEventHandlerFuncs {
|
||||
getFieldOrPanic := func(obj interface{}, fieldName string) interface{} {
|
||||
val := reflect.ValueOf(obj).Elem().FieldByName(fieldName)
|
||||
if val.IsValid() {
|
||||
return val.Interface()
|
||||
} else {
|
||||
panic(fmt.Errorf("field not found: %s", fieldName))
|
||||
}
|
||||
}
|
||||
return &cache.ResourceEventHandlerFuncs{
|
||||
DeleteFunc: func(old interface{}) {
|
||||
oldObj := old.(pkgruntime.Object)
|
||||
triggerFunc(oldObj)
|
||||
},
|
||||
AddFunc: func(cur interface{}) {
|
||||
curObj := cur.(pkgruntime.Object)
|
||||
triggerFunc(curObj)
|
||||
},
|
||||
UpdateFunc: func(old, cur interface{}) {
|
||||
curObj := cur.(pkgruntime.Object)
|
||||
oldMeta := getFieldOrPanic(old, "ObjectMeta").(metav1.ObjectMeta)
|
||||
curMeta := getFieldOrPanic(cur, "ObjectMeta").(metav1.ObjectMeta)
|
||||
if !ObjectMetaEquivalent(oldMeta, curMeta) ||
|
||||
!reflect.DeepEqual(getFieldOrPanic(old, field), getFieldOrPanic(cur, field)) {
|
||||
triggerFunc(curObj)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
100
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/handlers_test.go
generated
vendored
Normal file
100
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/handlers_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
/*
|
||||
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 (
|
||||
"testing"
|
||||
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
pkgruntime "k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestHandlers(t *testing.T) {
|
||||
// There is a single service ns1/s1 in cluster mycluster.
|
||||
service := apiv1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s1",
|
||||
},
|
||||
}
|
||||
service2 := apiv1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s1",
|
||||
Annotations: map[string]string{
|
||||
"A": "B",
|
||||
},
|
||||
},
|
||||
}
|
||||
triggerChan := make(chan struct{}, 1)
|
||||
triggered := func() bool {
|
||||
select {
|
||||
case <-triggerChan:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
trigger := NewTriggerOnAllChanges(
|
||||
func(obj pkgruntime.Object) {
|
||||
triggerChan <- struct{}{}
|
||||
})
|
||||
|
||||
trigger.OnAdd(&service)
|
||||
assert.True(t, triggered())
|
||||
trigger.OnDelete(&service)
|
||||
assert.True(t, triggered())
|
||||
trigger.OnUpdate(&service, &service)
|
||||
assert.False(t, triggered())
|
||||
trigger.OnUpdate(&service, &service2)
|
||||
assert.True(t, triggered())
|
||||
|
||||
trigger2 := NewTriggerOnMetaAndSpecChanges(
|
||||
func(obj pkgruntime.Object) {
|
||||
triggerChan <- struct{}{}
|
||||
},
|
||||
)
|
||||
|
||||
trigger2.OnAdd(&service)
|
||||
assert.True(t, triggered())
|
||||
trigger2.OnDelete(&service)
|
||||
assert.True(t, triggered())
|
||||
trigger2.OnUpdate(&service, &service)
|
||||
assert.False(t, triggered())
|
||||
trigger2.OnUpdate(&service, &service2)
|
||||
assert.True(t, triggered())
|
||||
|
||||
service3 := apiv1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s1",
|
||||
},
|
||||
Status: apiv1.ServiceStatus{
|
||||
LoadBalancer: apiv1.LoadBalancerStatus{
|
||||
Ingress: []apiv1.LoadBalancerIngress{{
|
||||
Hostname: "A",
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
trigger2.OnUpdate(&service, &service3)
|
||||
assert.False(t, triggered())
|
||||
}
|
||||
37
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/hpa/BUILD
generated
vendored
Normal file
37
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/hpa/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["hpa.go"],
|
||||
visibility = ["//visibility:public"],
|
||||
deps = [
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["hpa_test.go"],
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//vendor/github.com/stretchr/testify/require:go_default_library",
|
||||
"//vendor/k8s.io/api/autoscaling/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:public"],
|
||||
)
|
||||
75
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/hpa/hpa.go
generated
vendored
Normal file
75
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/hpa/hpa.go
generated
vendored
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
Copyright 2017 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 hpa
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
const (
|
||||
// FederatedAnnotationOnHpaTargetObj as key, is used by hpa controller to
|
||||
// set selected cluster name list as annotation on the target object.
|
||||
FederatedAnnotationOnHpaTargetObj = "federation.kubernetes.io/hpa-target-cluster-list"
|
||||
)
|
||||
|
||||
// ClusterNames stores the list of clusters represented by names as appearing on federation
|
||||
// cluster objects. This is set by federation hpa and used by target objects federation
|
||||
// controller to restrict that target object to only these clusters.
|
||||
type ClusterNames struct {
|
||||
Names []string
|
||||
}
|
||||
|
||||
func (cn *ClusterNames) String() string {
|
||||
annotationBytes, _ := json.Marshal(cn)
|
||||
return string(annotationBytes[:])
|
||||
}
|
||||
|
||||
// GetHpaTargetClusterList is used to get the list of clusters from the target object
|
||||
// annotations.
|
||||
func GetHpaTargetClusterList(obj runtime.Object) (*ClusterNames, error) {
|
||||
accessor, _ := meta.Accessor(obj)
|
||||
targetObjAnno := accessor.GetAnnotations()
|
||||
if targetObjAnno == nil {
|
||||
return nil, nil
|
||||
}
|
||||
targetObjAnnoString, exists := targetObjAnno[FederatedAnnotationOnHpaTargetObj]
|
||||
if !exists {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
clusterNames := &ClusterNames{}
|
||||
if err := json.Unmarshal([]byte(targetObjAnnoString), clusterNames); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return clusterNames, nil
|
||||
}
|
||||
|
||||
// SetHpaTargetClusterList is used to set the list of clusters on the target object
|
||||
// annotations.
|
||||
func SetHpaTargetClusterList(obj runtime.Object, clusterNames ClusterNames) runtime.Object {
|
||||
accessor, _ := meta.Accessor(obj)
|
||||
anno := accessor.GetAnnotations()
|
||||
if anno == nil {
|
||||
anno = make(map[string]string)
|
||||
accessor.SetAnnotations(anno)
|
||||
}
|
||||
anno[FederatedAnnotationOnHpaTargetObj] = clusterNames.String()
|
||||
return obj
|
||||
}
|
||||
115
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/hpa/hpa_test.go
generated
vendored
Normal file
115
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/hpa/hpa_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,115 @@
|
|||
/*
|
||||
Copyright 2017 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 hpa
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
autoscalingv1 "k8s.io/api/autoscaling/v1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestGetHpaTargetClusterList(t *testing.T) {
|
||||
// Any object is fine for this test.
|
||||
obj := &autoscalingv1.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "myhpa",
|
||||
Namespace: "myNamespace",
|
||||
SelfLink: "/api/mylink",
|
||||
},
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
clusterNames *ClusterNames
|
||||
expectedErr bool
|
||||
}{
|
||||
"Wrong data set on annotations should return unmarshalling error when retrieving": {
|
||||
expectedErr: true,
|
||||
},
|
||||
"Get clusternames on annotations with 2 clusters, should have same names, which were set": {
|
||||
clusterNames: &ClusterNames{
|
||||
Names: []string{
|
||||
"c1",
|
||||
"c2",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range testCases {
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
accessor, _ := meta.Accessor(obj)
|
||||
anno := accessor.GetAnnotations()
|
||||
if anno == nil {
|
||||
anno = make(map[string]string)
|
||||
accessor.SetAnnotations(anno)
|
||||
}
|
||||
if testCase.expectedErr {
|
||||
anno[FederatedAnnotationOnHpaTargetObj] = "{" //some random string
|
||||
} else {
|
||||
anno[FederatedAnnotationOnHpaTargetObj] = testCase.clusterNames.String()
|
||||
}
|
||||
|
||||
readNames, err := GetHpaTargetClusterList(obj)
|
||||
|
||||
if testCase.expectedErr {
|
||||
require.Error(t, err, "An error was expected")
|
||||
} else {
|
||||
require.Equal(t, testCase.clusterNames, readNames, "Names should have been equal")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetHpaTargetClusterList(t *testing.T) {
|
||||
// Any object is fine for this test.
|
||||
obj := &autoscalingv1.HorizontalPodAutoscaler{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "myhpa",
|
||||
Namespace: "myNamespace",
|
||||
SelfLink: "/api/mylink",
|
||||
},
|
||||
}
|
||||
|
||||
testCases := map[string]struct {
|
||||
clusterNames ClusterNames
|
||||
expectedErr bool
|
||||
}{
|
||||
"Get clusternames on annotations with 2 clusters, should have same names, which were set": {
|
||||
clusterNames: ClusterNames{
|
||||
Names: []string{
|
||||
"c1",
|
||||
"c2",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for testName, testCase := range testCases {
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
|
||||
SetHpaTargetClusterList(obj, testCase.clusterNames)
|
||||
readNames, err := GetHpaTargetClusterList(obj)
|
||||
require.NoError(t, err, "An error should not have happened")
|
||||
require.Equal(t, &testCase.clusterNames, readNames, "Names should have been equal")
|
||||
|
||||
})
|
||||
}
|
||||
}
|
||||
94
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/meta.go
generated
vendored
Normal file
94
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/meta.go
generated
vendored
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
/*
|
||||
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"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
)
|
||||
|
||||
// Copies cluster-independent, user provided data from the given ObjectMeta struct. If in
|
||||
// the future the ObjectMeta structure is expanded then any field that is not populated
|
||||
// by the api server should be included here.
|
||||
func copyObjectMeta(obj metav1.ObjectMeta) metav1.ObjectMeta {
|
||||
return metav1.ObjectMeta{
|
||||
Name: obj.Name,
|
||||
Namespace: obj.Namespace,
|
||||
Labels: obj.Labels,
|
||||
Annotations: obj.Annotations,
|
||||
}
|
||||
}
|
||||
|
||||
// Deep copies cluster-independent, user provided data from the given ObjectMeta struct. If in
|
||||
// the future the ObjectMeta structure is expanded then any field that is not populated
|
||||
// by the api server should be included here.
|
||||
func DeepCopyRelevantObjectMeta(obj metav1.ObjectMeta) metav1.ObjectMeta {
|
||||
copyMeta := copyObjectMeta(obj)
|
||||
if obj.Labels != nil {
|
||||
copyMeta.Labels = make(map[string]string)
|
||||
for key, val := range obj.Labels {
|
||||
copyMeta.Labels[key] = val
|
||||
}
|
||||
}
|
||||
if obj.Annotations != nil {
|
||||
copyMeta.Annotations = make(map[string]string)
|
||||
for key, val := range obj.Annotations {
|
||||
copyMeta.Annotations[key] = val
|
||||
}
|
||||
}
|
||||
return copyMeta
|
||||
}
|
||||
|
||||
// Checks if cluster-independent, user provided data in two given ObjectMeta are equal. If in
|
||||
// the future the ObjectMeta structure is expanded then any field that is not populated
|
||||
// by the api server should be included here.
|
||||
func ObjectMetaEquivalent(a, b metav1.ObjectMeta) bool {
|
||||
if a.Name != b.Name {
|
||||
return false
|
||||
}
|
||||
if a.Namespace != b.Namespace {
|
||||
return false
|
||||
}
|
||||
if !reflect.DeepEqual(a.Labels, b.Labels) && (len(a.Labels) != 0 || len(b.Labels) != 0) {
|
||||
return false
|
||||
}
|
||||
if !reflect.DeepEqual(a.Annotations, b.Annotations) && (len(a.Annotations) != 0 || len(b.Annotations) != 0) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Checks if cluster-independent, user provided data in ObjectMeta and Spec in two given top
|
||||
// level api objects are equivalent.
|
||||
func ObjectMetaAndSpecEquivalent(a, b runtime.Object) bool {
|
||||
objectMetaA := reflect.ValueOf(a).Elem().FieldByName("ObjectMeta").Interface().(metav1.ObjectMeta)
|
||||
objectMetaB := reflect.ValueOf(b).Elem().FieldByName("ObjectMeta").Interface().(metav1.ObjectMeta)
|
||||
specA := reflect.ValueOf(a).Elem().FieldByName("Spec").Interface()
|
||||
specB := reflect.ValueOf(b).Elem().FieldByName("Spec").Interface()
|
||||
return ObjectMetaEquivalent(objectMetaA, objectMetaB) && reflect.DeepEqual(specA, specB)
|
||||
}
|
||||
|
||||
func DeepCopyApiTypeOrPanic(item interface{}) interface{} {
|
||||
result, err := api.Scheme.DeepCopy(item)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return result
|
||||
}
|
||||
117
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/meta_test.go
generated
vendored
Normal file
117
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/meta_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,117 @@
|
|||
/*
|
||||
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 (
|
||||
"testing"
|
||||
|
||||
api_v1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestObjectMeta(t *testing.T) {
|
||||
o1 := metav1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s1",
|
||||
UID: "1231231412",
|
||||
ResourceVersion: "999",
|
||||
}
|
||||
o2 := copyObjectMeta(o1)
|
||||
o3 := metav1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s1",
|
||||
UID: "1231231412",
|
||||
Annotations: map[string]string{"A": "B"},
|
||||
}
|
||||
o4 := metav1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s1",
|
||||
UID: "1231255531412",
|
||||
Annotations: map[string]string{"A": "B"},
|
||||
}
|
||||
o5 := metav1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s1",
|
||||
ResourceVersion: "1231231412",
|
||||
Annotations: map[string]string{"A": "B"},
|
||||
}
|
||||
o6 := metav1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s1",
|
||||
ResourceVersion: "1231255531412",
|
||||
Annotations: map[string]string{"A": "B"},
|
||||
}
|
||||
o7 := metav1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s1",
|
||||
ResourceVersion: "1231255531412",
|
||||
Annotations: map[string]string{},
|
||||
Labels: map[string]string{},
|
||||
}
|
||||
o8 := metav1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s1",
|
||||
ResourceVersion: "1231255531412",
|
||||
}
|
||||
assert.Equal(t, 0, len(o2.UID))
|
||||
assert.Equal(t, 0, len(o2.ResourceVersion))
|
||||
assert.Equal(t, o1.Name, o2.Name)
|
||||
assert.True(t, ObjectMetaEquivalent(o1, o2))
|
||||
assert.False(t, ObjectMetaEquivalent(o1, o3))
|
||||
assert.True(t, ObjectMetaEquivalent(o3, o4))
|
||||
assert.True(t, ObjectMetaEquivalent(o5, o6))
|
||||
assert.True(t, ObjectMetaEquivalent(o3, o5))
|
||||
assert.True(t, ObjectMetaEquivalent(o7, o8))
|
||||
assert.True(t, ObjectMetaEquivalent(o8, o7))
|
||||
}
|
||||
|
||||
func TestObjectMetaAndSpec(t *testing.T) {
|
||||
s1 := api_v1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s1",
|
||||
},
|
||||
Spec: api_v1.ServiceSpec{
|
||||
ExternalName: "Service1",
|
||||
},
|
||||
}
|
||||
s1b := s1
|
||||
s2 := api_v1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s2",
|
||||
},
|
||||
Spec: api_v1.ServiceSpec{
|
||||
ExternalName: "Service1",
|
||||
},
|
||||
}
|
||||
s3 := api_v1.Service{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s1",
|
||||
},
|
||||
Spec: api_v1.ServiceSpec{
|
||||
ExternalName: "Service2",
|
||||
},
|
||||
}
|
||||
assert.True(t, ObjectMetaAndSpecEquivalent(&s1, &s1b))
|
||||
assert.False(t, ObjectMetaAndSpecEquivalent(&s1, &s2))
|
||||
assert.False(t, ObjectMetaAndSpecEquivalent(&s1, &s3))
|
||||
assert.False(t, ObjectMetaAndSpecEquivalent(&s2, &s3))
|
||||
}
|
||||
36
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/planner/BUILD
generated
vendored
Normal file
36
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/planner/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["planner.go"],
|
||||
deps = ["//federation/apis/federation:go_default_library"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["planner_test.go"],
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//federation/apis/federation:go_default_library",
|
||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
238
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/planner/planner.go
generated
vendored
Normal file
238
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/planner/planner.go
generated
vendored
Normal file
|
|
@ -0,0 +1,238 @@
|
|||
/*
|
||||
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 planner
|
||||
|
||||
import (
|
||||
"hash/fnv"
|
||||
"sort"
|
||||
|
||||
fedapi "k8s.io/kubernetes/federation/apis/federation"
|
||||
)
|
||||
|
||||
// Planner decides how many out of the given replicas should be placed in each of the
|
||||
// federated clusters.
|
||||
type Planner struct {
|
||||
preferences *fedapi.ReplicaAllocationPreferences
|
||||
}
|
||||
|
||||
type namedClusterPreferences struct {
|
||||
clusterName string
|
||||
hash uint32
|
||||
fedapi.ClusterPreferences
|
||||
}
|
||||
|
||||
type byWeight []*namedClusterPreferences
|
||||
|
||||
func (a byWeight) Len() int { return len(a) }
|
||||
func (a byWeight) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
|
||||
|
||||
// Preferences are sorted according by decreasing weight and increasing hash (built on top of cluster name and rs name).
|
||||
// Sorting is made by a hash to avoid assigning single-replica rs to the alphabetically smallest cluster.
|
||||
func (a byWeight) Less(i, j int) bool {
|
||||
return (a[i].Weight > a[j].Weight) || (a[i].Weight == a[j].Weight && a[i].hash < a[j].hash)
|
||||
}
|
||||
|
||||
func NewPlanner(preferences *fedapi.ReplicaAllocationPreferences) *Planner {
|
||||
return &Planner{
|
||||
preferences: preferences,
|
||||
}
|
||||
}
|
||||
|
||||
// Distribute the desired number of replicas among the given cluster according to the planner preferences.
|
||||
// The function tries its best to assign each cluster the preferred number of replicas, however if
|
||||
// sum of MinReplicas for all cluster is bigger thant replicasToDistribute then some cluster will not
|
||||
// have all of the replicas assigned. In such case a cluster with higher weight has priority over
|
||||
// cluster with lower weight (or with lexicographically smaller name in case of draw).
|
||||
// It can also use the current replica count and estimated capacity to provide better planning and
|
||||
// adhere to rebalance policy. To avoid prioritization of clusters with smaller lexicographical names
|
||||
// a semi-random string (like replica set name) can be provided.
|
||||
// Two maps are returned:
|
||||
// * a map that contains information how many replicas will be possible to run in a cluster.
|
||||
// * a map that contains information how many extra replicas would be nice to schedule in a cluster so,
|
||||
// if by chance, they are scheduled we will be closer to the desired replicas layout.
|
||||
func (p *Planner) Plan(replicasToDistribute int64, availableClusters []string, currentReplicaCount map[string]int64,
|
||||
estimatedCapacity map[string]int64, replicaSetKey string) (map[string]int64, map[string]int64) {
|
||||
|
||||
preferences := make([]*namedClusterPreferences, 0, len(availableClusters))
|
||||
plan := make(map[string]int64, len(preferences))
|
||||
overflow := make(map[string]int64, len(preferences))
|
||||
|
||||
named := func(name string, pref fedapi.ClusterPreferences) *namedClusterPreferences {
|
||||
// Seems to work better than addler for our case.
|
||||
hasher := fnv.New32()
|
||||
hasher.Write([]byte(name))
|
||||
hasher.Write([]byte(replicaSetKey))
|
||||
|
||||
return &namedClusterPreferences{
|
||||
clusterName: name,
|
||||
hash: hasher.Sum32(),
|
||||
ClusterPreferences: pref,
|
||||
}
|
||||
}
|
||||
|
||||
for _, cluster := range availableClusters {
|
||||
if localRSP, found := p.preferences.Clusters[cluster]; found {
|
||||
preferences = append(preferences, named(cluster, localRSP))
|
||||
} else {
|
||||
if localRSP, found := p.preferences.Clusters["*"]; found {
|
||||
preferences = append(preferences, named(cluster, localRSP))
|
||||
} else {
|
||||
plan[cluster] = int64(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
sort.Sort(byWeight(preferences))
|
||||
|
||||
remainingReplicas := replicasToDistribute
|
||||
|
||||
// Assign each cluster the minimum number of replicas it requested.
|
||||
for _, preference := range preferences {
|
||||
min := minInt64(preference.MinReplicas, remainingReplicas)
|
||||
if capacity, hasCapacity := estimatedCapacity[preference.clusterName]; hasCapacity {
|
||||
min = minInt64(min, capacity)
|
||||
}
|
||||
remainingReplicas -= min
|
||||
plan[preference.clusterName] = min
|
||||
}
|
||||
|
||||
// This map contains information how many replicas were assigned to
|
||||
// the cluster based only on the current replica count and
|
||||
// rebalance=false preference. It will be later used in remaining replica
|
||||
// distribution code.
|
||||
preallocated := make(map[string]int64)
|
||||
|
||||
if p.preferences.Rebalance == false {
|
||||
for _, preference := range preferences {
|
||||
planned := plan[preference.clusterName]
|
||||
count, hasSome := currentReplicaCount[preference.clusterName]
|
||||
if hasSome && count > planned {
|
||||
target := count
|
||||
if preference.MaxReplicas != nil {
|
||||
target = minInt64(*preference.MaxReplicas, target)
|
||||
}
|
||||
if capacity, hasCapacity := estimatedCapacity[preference.clusterName]; hasCapacity {
|
||||
target = minInt64(capacity, target)
|
||||
}
|
||||
extra := minInt64(target-planned, remainingReplicas)
|
||||
if extra < 0 {
|
||||
extra = 0
|
||||
}
|
||||
remainingReplicas -= extra
|
||||
preallocated[preference.clusterName] = extra
|
||||
plan[preference.clusterName] = extra + planned
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
modified := true
|
||||
|
||||
// It is possible single pass of the loop is not enough to distribute all replicas among clusters due
|
||||
// to weight, max and rounding corner cases. In such case we iterate until either
|
||||
// there is no replicas or no cluster gets any more replicas or the number
|
||||
// of attempts is less than available cluster count. If there is no preallocated pods
|
||||
// every loop either distributes all remainingReplicas or maxes out at least one cluster.
|
||||
// If there are preallocated then the replica spreading may take longer.
|
||||
// We reduce the number of pending preallocated replicas by at least half with each iteration so
|
||||
// we may need log(replicasAtStart) iterations.
|
||||
// TODO: Prove that clusterCount * log(replicas) iterations solves the problem or adjust the number.
|
||||
// TODO: This algorithm is O(clusterCount^2 * log(replicas)) which is good for up to 100 clusters.
|
||||
// Find something faster.
|
||||
for trial := 0; modified && remainingReplicas > 0; trial++ {
|
||||
|
||||
modified = false
|
||||
weightSum := int64(0)
|
||||
for _, preference := range preferences {
|
||||
weightSum += preference.Weight
|
||||
}
|
||||
newPreferences := make([]*namedClusterPreferences, 0, len(preferences))
|
||||
|
||||
distributeInThisLoop := remainingReplicas
|
||||
|
||||
for _, preference := range preferences {
|
||||
if weightSum > 0 {
|
||||
start := plan[preference.clusterName]
|
||||
// Distribute the remaining replicas, rounding fractions always up.
|
||||
extra := (distributeInThisLoop*preference.Weight + weightSum - 1) / weightSum
|
||||
extra = minInt64(extra, remainingReplicas)
|
||||
|
||||
// Account preallocated.
|
||||
prealloc := preallocated[preference.clusterName]
|
||||
usedPrealloc := minInt64(extra, prealloc)
|
||||
preallocated[preference.clusterName] = prealloc - usedPrealloc
|
||||
extra = extra - usedPrealloc
|
||||
if usedPrealloc > 0 {
|
||||
modified = true
|
||||
}
|
||||
|
||||
// In total there should be the amount that was there at start plus whatever is due
|
||||
// in this iteration
|
||||
total := start + extra
|
||||
|
||||
// Check if we don't overflow the cluster, and if yes don't consider this cluster
|
||||
// in any of the following iterations.
|
||||
full := false
|
||||
if preference.MaxReplicas != nil && total > *preference.MaxReplicas {
|
||||
total = *preference.MaxReplicas
|
||||
full = true
|
||||
}
|
||||
if capacity, hasCapacity := estimatedCapacity[preference.clusterName]; hasCapacity && total > capacity {
|
||||
overflow[preference.clusterName] = total - capacity
|
||||
total = capacity
|
||||
full = true
|
||||
}
|
||||
|
||||
if !full {
|
||||
newPreferences = append(newPreferences, preference)
|
||||
}
|
||||
|
||||
// Only total-start replicas were actually taken.
|
||||
remainingReplicas -= (total - start)
|
||||
plan[preference.clusterName] = total
|
||||
|
||||
// Something extra got scheduled on this cluster.
|
||||
if total > start {
|
||||
modified = true
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
preferences = newPreferences
|
||||
}
|
||||
|
||||
if p.preferences.Rebalance {
|
||||
return plan, overflow
|
||||
} else {
|
||||
// If rebalance = false then overflow is trimmed at the level
|
||||
// of replicas that it failed to place somewhere.
|
||||
newOverflow := make(map[string]int64)
|
||||
for key, value := range overflow {
|
||||
value = minInt64(value, remainingReplicas)
|
||||
if value > 0 {
|
||||
newOverflow[key] = value
|
||||
}
|
||||
}
|
||||
return plan, newOverflow
|
||||
}
|
||||
}
|
||||
|
||||
func minInt64(a int64, b int64) int64 {
|
||||
if a < b {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
348
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/planner/planner_test.go
generated
vendored
Normal file
348
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/planner/planner_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,348 @@
|
|||
/*
|
||||
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 planner
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
fedapi "k8s.io/kubernetes/federation/apis/federation"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func doCheck(t *testing.T, pref map[string]fedapi.ClusterPreferences, replicas int64, clusters []string, expected map[string]int64) {
|
||||
planer := NewPlanner(&fedapi.ReplicaAllocationPreferences{
|
||||
Clusters: pref,
|
||||
})
|
||||
plan, overflow := planer.Plan(replicas, clusters, map[string]int64{}, map[string]int64{}, "")
|
||||
assert.EqualValues(t, expected, plan)
|
||||
assert.Equal(t, 0, len(overflow))
|
||||
}
|
||||
|
||||
func doCheckWithExisting(t *testing.T, pref map[string]fedapi.ClusterPreferences, replicas int64, clusters []string,
|
||||
existing map[string]int64, expected map[string]int64) {
|
||||
planer := NewPlanner(&fedapi.ReplicaAllocationPreferences{
|
||||
Clusters: pref,
|
||||
})
|
||||
plan, overflow := planer.Plan(replicas, clusters, existing, map[string]int64{}, "")
|
||||
assert.Equal(t, 0, len(overflow))
|
||||
assert.EqualValues(t, expected, plan)
|
||||
}
|
||||
|
||||
func doCheckWithExistingAndCapacity(t *testing.T, rebalance bool, pref map[string]fedapi.ClusterPreferences, replicas int64, clusters []string,
|
||||
existing map[string]int64,
|
||||
capacity map[string]int64,
|
||||
expected map[string]int64,
|
||||
expectedOverflow map[string]int64) {
|
||||
planer := NewPlanner(&fedapi.ReplicaAllocationPreferences{
|
||||
Rebalance: rebalance,
|
||||
Clusters: pref,
|
||||
})
|
||||
plan, overflow := planer.Plan(replicas, clusters, existing, capacity, "")
|
||||
assert.EqualValues(t, expected, plan)
|
||||
assert.Equal(t, expectedOverflow, overflow)
|
||||
}
|
||||
|
||||
func pint(val int64) *int64 {
|
||||
return &val
|
||||
}
|
||||
|
||||
func TestEqual(t *testing.T) {
|
||||
doCheck(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {Weight: 1}},
|
||||
50, []string{"A", "B", "C"},
|
||||
// hash dependent
|
||||
map[string]int64{"A": 16, "B": 17, "C": 17})
|
||||
|
||||
doCheck(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {Weight: 1}},
|
||||
50, []string{"A", "B"},
|
||||
map[string]int64{"A": 25, "B": 25})
|
||||
|
||||
doCheck(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {Weight: 1}},
|
||||
1, []string{"A", "B"},
|
||||
// hash dependent
|
||||
map[string]int64{"A": 0, "B": 1})
|
||||
|
||||
doCheck(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {Weight: 1}},
|
||||
1, []string{"A", "B", "C", "D"},
|
||||
// hash dependent
|
||||
map[string]int64{"A": 0, "B": 0, "C": 0, "D": 1})
|
||||
|
||||
doCheck(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {Weight: 1}},
|
||||
1, []string{"A"},
|
||||
map[string]int64{"A": 1})
|
||||
|
||||
doCheck(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {Weight: 1}},
|
||||
1, []string{},
|
||||
map[string]int64{})
|
||||
}
|
||||
|
||||
func TestEqualWithExisting(t *testing.T) {
|
||||
doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {Weight: 1}},
|
||||
50, []string{"A", "B", "C"},
|
||||
map[string]int64{"C": 30},
|
||||
map[string]int64{"A": 10, "B": 10, "C": 30})
|
||||
|
||||
doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {Weight: 1}},
|
||||
50, []string{"A", "B"},
|
||||
map[string]int64{"A": 30},
|
||||
map[string]int64{"A": 30, "B": 20})
|
||||
|
||||
doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {Weight: 1}},
|
||||
15, []string{"A", "B"},
|
||||
map[string]int64{"A": 0, "B": 8},
|
||||
map[string]int64{"A": 7, "B": 8})
|
||||
|
||||
doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {Weight: 1}},
|
||||
15, []string{"A", "B"},
|
||||
map[string]int64{"A": 1, "B": 8},
|
||||
map[string]int64{"A": 7, "B": 8})
|
||||
|
||||
doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {Weight: 1}},
|
||||
15, []string{"A", "B"},
|
||||
map[string]int64{"A": 4, "B": 8},
|
||||
map[string]int64{"A": 7, "B": 8})
|
||||
|
||||
doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {Weight: 1}},
|
||||
15, []string{"A", "B"},
|
||||
map[string]int64{"A": 5, "B": 8},
|
||||
map[string]int64{"A": 7, "B": 8})
|
||||
|
||||
doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {Weight: 1}},
|
||||
15, []string{"A", "B"},
|
||||
map[string]int64{"A": 6, "B": 8},
|
||||
map[string]int64{"A": 7, "B": 8})
|
||||
|
||||
doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {Weight: 1}},
|
||||
15, []string{"A", "B"},
|
||||
map[string]int64{"A": 7, "B": 8},
|
||||
map[string]int64{"A": 7, "B": 8})
|
||||
|
||||
doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {Weight: 1}},
|
||||
500000, []string{"A", "B"},
|
||||
map[string]int64{"A": 300000},
|
||||
map[string]int64{"A": 300000, "B": 200000})
|
||||
|
||||
doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {Weight: 1}},
|
||||
50, []string{"A", "B"},
|
||||
map[string]int64{"A": 10},
|
||||
map[string]int64{"A": 25, "B": 25})
|
||||
|
||||
doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {Weight: 1}},
|
||||
50, []string{"A", "B"},
|
||||
map[string]int64{"A": 10, "B": 70},
|
||||
// hash dependent
|
||||
// TODO: Should be 10:40, update algorithm. Issue: #31816
|
||||
map[string]int64{"A": 0, "B": 50})
|
||||
|
||||
doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {Weight: 1}},
|
||||
1, []string{"A", "B"},
|
||||
map[string]int64{"A": 30},
|
||||
map[string]int64{"A": 1, "B": 0})
|
||||
|
||||
doCheckWithExisting(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {Weight: 1}},
|
||||
50, []string{"A", "B"},
|
||||
map[string]int64{"A": 10, "B": 20},
|
||||
map[string]int64{"A": 25, "B": 25})
|
||||
}
|
||||
|
||||
func TestWithExistingAndCapacity(t *testing.T) {
|
||||
// desired without capacity: map[string]int64{"A": 17, "B": 17, "C": 16})
|
||||
doCheckWithExistingAndCapacity(t, true, map[string]fedapi.ClusterPreferences{
|
||||
"*": {Weight: 1}},
|
||||
50, []string{"A", "B", "C"},
|
||||
map[string]int64{},
|
||||
map[string]int64{"C": 10},
|
||||
map[string]int64{"A": 20, "B": 20, "C": 10},
|
||||
map[string]int64{"C": 7})
|
||||
|
||||
// desired B:50 C:0
|
||||
doCheckWithExistingAndCapacity(t, true, map[string]fedapi.ClusterPreferences{
|
||||
"A": {Weight: 10000},
|
||||
"B": {Weight: 1}},
|
||||
50, []string{"B", "C"},
|
||||
map[string]int64{},
|
||||
map[string]int64{"B": 10},
|
||||
map[string]int64{"B": 10, "C": 0},
|
||||
map[string]int64{"B": 40},
|
||||
)
|
||||
|
||||
// desired A:20 B:40
|
||||
doCheckWithExistingAndCapacity(t, true, map[string]fedapi.ClusterPreferences{
|
||||
"A": {Weight: 1},
|
||||
"B": {Weight: 2}},
|
||||
60, []string{"A", "B", "C"},
|
||||
map[string]int64{},
|
||||
map[string]int64{"B": 10},
|
||||
map[string]int64{"A": 50, "B": 10, "C": 0},
|
||||
map[string]int64{"B": 30})
|
||||
|
||||
// map[string]int64{"A": 10, "B": 30, "C": 21, "D": 10})
|
||||
doCheckWithExistingAndCapacity(t, true, map[string]fedapi.ClusterPreferences{
|
||||
"A": {Weight: 10000, MaxReplicas: pint(10)},
|
||||
"B": {Weight: 1},
|
||||
"C": {Weight: 1, MaxReplicas: pint(21)},
|
||||
"D": {Weight: 1, MaxReplicas: pint(10)}},
|
||||
71, []string{"A", "B", "C", "D"},
|
||||
map[string]int64{},
|
||||
map[string]int64{"C": 10},
|
||||
map[string]int64{"A": 10, "B": 41, "C": 10, "D": 10},
|
||||
map[string]int64{"C": 11},
|
||||
)
|
||||
|
||||
// desired A:20 B:20
|
||||
doCheckWithExistingAndCapacity(t, false, map[string]fedapi.ClusterPreferences{
|
||||
"A": {Weight: 1},
|
||||
"B": {Weight: 1}},
|
||||
60, []string{"A", "B", "C"},
|
||||
map[string]int64{},
|
||||
map[string]int64{"A": 10, "B": 10},
|
||||
map[string]int64{"A": 10, "B": 10, "C": 0},
|
||||
map[string]int64{"A": 20, "B": 20})
|
||||
|
||||
// desired A:10 B:50 although A:50 B:10 is fuly acceptable because rebalance = false
|
||||
doCheckWithExistingAndCapacity(t, false, map[string]fedapi.ClusterPreferences{
|
||||
"A": {Weight: 1},
|
||||
"B": {Weight: 5}},
|
||||
60, []string{"A", "B", "C"},
|
||||
map[string]int64{},
|
||||
map[string]int64{"B": 10},
|
||||
map[string]int64{"A": 50, "B": 10, "C": 0},
|
||||
map[string]int64{})
|
||||
|
||||
doCheckWithExistingAndCapacity(t, false, map[string]fedapi.ClusterPreferences{
|
||||
"*": {MinReplicas: 20, Weight: 0}},
|
||||
50, []string{"A", "B", "C"},
|
||||
map[string]int64{},
|
||||
map[string]int64{"B": 10},
|
||||
map[string]int64{"A": 20, "B": 10, "C": 20},
|
||||
map[string]int64{})
|
||||
|
||||
// Actually we would like to have extra 20 in B but 15 is also good.
|
||||
doCheckWithExistingAndCapacity(t, true, map[string]fedapi.ClusterPreferences{
|
||||
"*": {MinReplicas: 20, Weight: 1}},
|
||||
60, []string{"A", "B"},
|
||||
map[string]int64{},
|
||||
map[string]int64{"B": 10},
|
||||
map[string]int64{"A": 50, "B": 10},
|
||||
map[string]int64{"B": 15})
|
||||
}
|
||||
|
||||
func TestMin(t *testing.T) {
|
||||
doCheck(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {MinReplicas: 2, Weight: 0}},
|
||||
50, []string{"A", "B", "C"},
|
||||
map[string]int64{"A": 2, "B": 2, "C": 2})
|
||||
|
||||
doCheck(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {MinReplicas: 20, Weight: 0}},
|
||||
50, []string{"A", "B", "C"},
|
||||
// hash dependant.
|
||||
map[string]int64{"A": 10, "B": 20, "C": 20})
|
||||
|
||||
doCheck(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {MinReplicas: 20, Weight: 0},
|
||||
"A": {MinReplicas: 100, Weight: 1}},
|
||||
50, []string{"A", "B", "C"},
|
||||
map[string]int64{"A": 50, "B": 0, "C": 0})
|
||||
|
||||
doCheck(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {MinReplicas: 10, Weight: 1, MaxReplicas: pint(12)}},
|
||||
50, []string{"A", "B", "C"},
|
||||
map[string]int64{"A": 12, "B": 12, "C": 12})
|
||||
}
|
||||
|
||||
func TestMax(t *testing.T) {
|
||||
doCheck(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {Weight: 1, MaxReplicas: pint(2)}},
|
||||
50, []string{"A", "B", "C"},
|
||||
map[string]int64{"A": 2, "B": 2, "C": 2})
|
||||
|
||||
doCheck(t, map[string]fedapi.ClusterPreferences{
|
||||
"*": {Weight: 0, MaxReplicas: pint(2)}},
|
||||
50, []string{"A", "B", "C"},
|
||||
map[string]int64{"A": 0, "B": 0, "C": 0})
|
||||
}
|
||||
|
||||
func TestWeight(t *testing.T) {
|
||||
doCheck(t, map[string]fedapi.ClusterPreferences{
|
||||
"A": {Weight: 1},
|
||||
"B": {Weight: 2}},
|
||||
60, []string{"A", "B", "C"},
|
||||
map[string]int64{"A": 20, "B": 40, "C": 0})
|
||||
|
||||
doCheck(t, map[string]fedapi.ClusterPreferences{
|
||||
"A": {Weight: 10000},
|
||||
"B": {Weight: 1}},
|
||||
50, []string{"A", "B", "C"},
|
||||
map[string]int64{"A": 50, "B": 0, "C": 0})
|
||||
|
||||
doCheck(t, map[string]fedapi.ClusterPreferences{
|
||||
"A": {Weight: 10000},
|
||||
"B": {Weight: 1}},
|
||||
50, []string{"B", "C"},
|
||||
map[string]int64{"B": 50, "C": 0})
|
||||
|
||||
doCheck(t, map[string]fedapi.ClusterPreferences{
|
||||
"A": {Weight: 10000, MaxReplicas: pint(10)},
|
||||
"B": {Weight: 1},
|
||||
"C": {Weight: 1}},
|
||||
50, []string{"A", "B", "C"},
|
||||
map[string]int64{"A": 10, "B": 20, "C": 20})
|
||||
|
||||
doCheck(t, map[string]fedapi.ClusterPreferences{
|
||||
"A": {Weight: 10000, MaxReplicas: pint(10)},
|
||||
"B": {Weight: 1},
|
||||
"C": {Weight: 1, MaxReplicas: pint(10)}},
|
||||
50, []string{"A", "B", "C"},
|
||||
map[string]int64{"A": 10, "B": 30, "C": 10})
|
||||
|
||||
doCheck(t, map[string]fedapi.ClusterPreferences{
|
||||
"A": {Weight: 10000, MaxReplicas: pint(10)},
|
||||
"B": {Weight: 1},
|
||||
"C": {Weight: 1, MaxReplicas: pint(21)},
|
||||
"D": {Weight: 1, MaxReplicas: pint(10)}},
|
||||
71, []string{"A", "B", "C", "D"},
|
||||
map[string]int64{"A": 10, "B": 30, "C": 21, "D": 10})
|
||||
|
||||
doCheck(t, map[string]fedapi.ClusterPreferences{
|
||||
"A": {Weight: 10000, MaxReplicas: pint(10)},
|
||||
"B": {Weight: 1},
|
||||
"C": {Weight: 1, MaxReplicas: pint(21)},
|
||||
"D": {Weight: 1, MaxReplicas: pint(10)},
|
||||
"E": {Weight: 1}},
|
||||
91, []string{"A", "B", "C", "D", "E"},
|
||||
map[string]int64{"A": 10, "B": 25, "C": 21, "D": 10, "E": 25})
|
||||
}
|
||||
38
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/podanalyzer/BUILD
generated
vendored
Normal file
38
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/podanalyzer/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["pod_helper.go"],
|
||||
deps = ["//vendor/k8s.io/api/core/v1:go_default_library"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["pod_helper_test.go"],
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
63
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/podanalyzer/pod_helper.go
generated
vendored
Normal file
63
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/podanalyzer/pod_helper.go
generated
vendored
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
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 podanalyzer
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
api_v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
type PodAnalysisResult struct {
|
||||
// Total number of pods created.
|
||||
Total int
|
||||
// Number of pods that are running and ready.
|
||||
RunningAndReady int
|
||||
// Number of pods that have been in unschedulable state for UnshedulableThreshold seconds.
|
||||
Unschedulable int
|
||||
|
||||
// TODO: Handle other scenarios like pod waiting too long for scheduler etc.
|
||||
}
|
||||
|
||||
const (
|
||||
// TODO: make it configurable
|
||||
UnschedulableThreshold = 60 * time.Second
|
||||
)
|
||||
|
||||
// AnalyzePods calculates how many pods from the list are in one of
|
||||
// the meaningful (from the replica set perspective) states. This function is
|
||||
// a temporary workaround against the current lack of ownerRef in pods.
|
||||
func AnalyzePods(pods *api_v1.PodList, currentTime time.Time) PodAnalysisResult {
|
||||
result := PodAnalysisResult{}
|
||||
for _, pod := range pods.Items {
|
||||
result.Total++
|
||||
for _, condition := range pod.Status.Conditions {
|
||||
if pod.Status.Phase == api_v1.PodRunning {
|
||||
if condition.Type == api_v1.PodReady {
|
||||
result.RunningAndReady++
|
||||
}
|
||||
} else if condition.Type == api_v1.PodScheduled &&
|
||||
condition.Status == api_v1.ConditionFalse &&
|
||||
condition.Reason == api_v1.PodReasonUnschedulable &&
|
||||
condition.LastTransitionTime.Add(UnschedulableThreshold).Before(currentTime) {
|
||||
|
||||
result.Unschedulable++
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
98
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/podanalyzer/pod_helper_test.go
generated
vendored
Normal file
98
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/podanalyzer/pod_helper_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
/*
|
||||
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 podanalyzer
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
api_v1 "k8s.io/api/core/v1"
|
||||
"k8s.io/api/extensions/v1beta1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAnalyze(t *testing.T) {
|
||||
now := time.Now()
|
||||
podRunning := newPod("p1",
|
||||
api_v1.PodStatus{
|
||||
Phase: api_v1.PodRunning,
|
||||
Conditions: []api_v1.PodCondition{
|
||||
{
|
||||
Type: api_v1.PodReady,
|
||||
Status: api_v1.ConditionTrue,
|
||||
},
|
||||
},
|
||||
})
|
||||
podUnschedulable := newPod("pU",
|
||||
api_v1.PodStatus{
|
||||
Phase: api_v1.PodPending,
|
||||
Conditions: []api_v1.PodCondition{
|
||||
{
|
||||
Type: api_v1.PodScheduled,
|
||||
Status: api_v1.ConditionFalse,
|
||||
Reason: api_v1.PodReasonUnschedulable,
|
||||
LastTransitionTime: metav1.Time{Time: now.Add(-10 * time.Minute)},
|
||||
},
|
||||
},
|
||||
})
|
||||
podOther := newPod("pO",
|
||||
api_v1.PodStatus{
|
||||
Phase: api_v1.PodPending,
|
||||
Conditions: []api_v1.PodCondition{},
|
||||
})
|
||||
|
||||
result := AnalyzePods(&api_v1.PodList{Items: []api_v1.Pod{*podRunning, *podRunning, *podRunning, *podUnschedulable, *podUnschedulable}}, now)
|
||||
assert.Equal(t, PodAnalysisResult{
|
||||
Total: 5,
|
||||
RunningAndReady: 3,
|
||||
Unschedulable: 2,
|
||||
}, result)
|
||||
|
||||
result = AnalyzePods(&api_v1.PodList{Items: []api_v1.Pod{*podOther}}, now)
|
||||
assert.Equal(t, PodAnalysisResult{
|
||||
Total: 1,
|
||||
RunningAndReady: 0,
|
||||
Unschedulable: 0,
|
||||
}, result)
|
||||
}
|
||||
|
||||
func newReplicaSet(selectorMap map[string]string) *v1beta1.ReplicaSet {
|
||||
replicas := int32(3)
|
||||
rs := &v1beta1.ReplicaSet{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foobar",
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
},
|
||||
Spec: v1beta1.ReplicaSetSpec{
|
||||
Replicas: &replicas,
|
||||
Selector: &metav1.LabelSelector{MatchLabels: selectorMap},
|
||||
},
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
func newPod(name string, status api_v1.PodStatus) *api_v1.Pod {
|
||||
return &api_v1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
},
|
||||
Status: status,
|
||||
}
|
||||
}
|
||||
43
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/replicapreferences/BUILD
generated
vendored
Normal file
43
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/replicapreferences/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["preferences.go"],
|
||||
deps = [
|
||||
"//federation/apis/federation:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["preferences_test.go"],
|
||||
library = ":go_default_library",
|
||||
deps = [
|
||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||
"//vendor/k8s.io/api/extensions/v1beta1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
],
|
||||
)
|
||||
55
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/replicapreferences/preferences.go
generated
vendored
Normal file
55
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/replicapreferences/preferences.go
generated
vendored
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
Copyright 2017 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 replicapreferences
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
fed "k8s.io/kubernetes/federation/apis/federation"
|
||||
)
|
||||
|
||||
// GetAllocationPreferences reads the preferences from the annotations on the given object.
|
||||
// It takes in an object and determines the supported types.
|
||||
// Callers need to pass the string key used to store the annotations.
|
||||
// Returns nil if the annotations with the given key are not found.
|
||||
func GetAllocationPreferences(obj runtime.Object, key string) (*fed.ReplicaAllocationPreferences, error) {
|
||||
if obj == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
annotations := accessor.GetAnnotations()
|
||||
if annotations == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
prefString, found := annotations[key]
|
||||
if !found {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
var pref fed.ReplicaAllocationPreferences
|
||||
if err := json.Unmarshal([]byte(prefString), &pref); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &pref, nil
|
||||
}
|
||||
92
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/replicapreferences/preferences_test.go
generated
vendored
Normal file
92
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/replicapreferences/preferences_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
/*
|
||||
Copyright 2017 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 replicapreferences
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
extensionsv1 "k8s.io/api/extensions/v1beta1"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
const (
|
||||
TestPreferencesAnnotationKey = "federation.kubernetes.io/test-preferences"
|
||||
)
|
||||
|
||||
func TestGetAllocationPreferences(t *testing.T) {
|
||||
testCases := []struct {
|
||||
testname string
|
||||
prefs string
|
||||
obj runtime.Object
|
||||
errorExpected bool
|
||||
}{
|
||||
{
|
||||
testname: "good preferences",
|
||||
prefs: `{"rebalance": true,
|
||||
"clusters": {
|
||||
"k8s-1": {"minReplicas": 10, "maxReplicas": 20, "weight": 2},
|
||||
"*": {"weight": 1}
|
||||
}}`,
|
||||
obj: &extensionsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-obj",
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
SelfLink: "/api/v1/namespaces/default/obj/test-obj",
|
||||
},
|
||||
},
|
||||
errorExpected: false,
|
||||
},
|
||||
{
|
||||
testname: "failed preferences",
|
||||
prefs: `{`, // bad json
|
||||
obj: &extensionsv1.Deployment{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test-obj",
|
||||
Namespace: metav1.NamespaceDefault,
|
||||
SelfLink: "/api/v1/namespaces/default/obj/test-obj",
|
||||
},
|
||||
},
|
||||
errorExpected: true,
|
||||
},
|
||||
}
|
||||
|
||||
// prepare the objects
|
||||
for _, tc := range testCases {
|
||||
accessor, _ := meta.Accessor(tc.obj)
|
||||
anno := accessor.GetAnnotations()
|
||||
if anno == nil {
|
||||
anno = make(map[string]string)
|
||||
accessor.SetAnnotations(anno)
|
||||
}
|
||||
anno[TestPreferencesAnnotationKey] = tc.prefs
|
||||
}
|
||||
|
||||
// test get preferences
|
||||
for _, tc := range testCases {
|
||||
pref, err := GetAllocationPreferences(tc.obj, TestPreferencesAnnotationKey)
|
||||
if tc.errorExpected {
|
||||
assert.NotNil(t, err)
|
||||
} else {
|
||||
assert.NotNil(t, pref)
|
||||
assert.Nil(t, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
32
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/secret.go
generated
vendored
Normal file
32
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/secret.go
generated
vendored
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
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"
|
||||
|
||||
api_v1 "k8s.io/api/core/v1"
|
||||
)
|
||||
|
||||
// Checks if cluster-independent, user provided data in two given Secrets are equal. If in
|
||||
// the future the Secret structure is expanded then any field that is not populated.
|
||||
// by the api server should be included here.
|
||||
func SecretEquivalent(s1, s2 api_v1.Secret) bool {
|
||||
return ObjectMetaEquivalent(s1.ObjectMeta, s2.ObjectMeta) &&
|
||||
reflect.DeepEqual(s1.Data, s2.Data) &&
|
||||
reflect.DeepEqual(s1.Type, s2.Type)
|
||||
}
|
||||
39
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/test/BUILD
generated
vendored
Normal file
39
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/test/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["test_helper.go"],
|
||||
deps = [
|
||||
"//federation/apis/federation/v1beta1:go_default_library",
|
||||
"//federation/pkg/federation-controller/util:go_default_library",
|
||||
"//federation/pkg/federation-controller/util/finalizers:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//vendor/github.com/golang/glog: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/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/wait:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/watch:go_default_library",
|
||||
"//vendor/k8s.io/client-go/testing:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
454
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/test/test_helper.go
generated
vendored
Normal file
454
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/test/test_helper.go
generated
vendored
Normal file
|
|
@ -0,0 +1,454 @@
|
|||
/*
|
||||
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 testutil
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"runtime/pprof"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
apiv1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
core "k8s.io/client-go/testing"
|
||||
federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1"
|
||||
"k8s.io/kubernetes/federation/pkg/federation-controller/util"
|
||||
finalizersutil "k8s.io/kubernetes/federation/pkg/federation-controller/util/finalizers"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
|
||||
"github.com/golang/glog"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
const (
|
||||
pushTimeout = 5 * time.Second
|
||||
)
|
||||
|
||||
// A structure that distributes events to multiple watchers.
|
||||
type WatcherDispatcher struct {
|
||||
sync.Mutex
|
||||
watchers []*watch.RaceFreeFakeWatcher
|
||||
eventsSoFar []*watch.Event
|
||||
orderExecution chan func()
|
||||
stopChan chan struct{}
|
||||
}
|
||||
|
||||
func (wd *WatcherDispatcher) register(watcher *watch.RaceFreeFakeWatcher) {
|
||||
wd.Lock()
|
||||
defer wd.Unlock()
|
||||
wd.watchers = append(wd.watchers, watcher)
|
||||
for _, event := range wd.eventsSoFar {
|
||||
watcher.Action(event.Type, event.Object)
|
||||
}
|
||||
}
|
||||
|
||||
func (wd *WatcherDispatcher) Stop() {
|
||||
wd.Lock()
|
||||
defer wd.Unlock()
|
||||
close(wd.stopChan)
|
||||
glog.Infof("Stopping WatcherDispatcher")
|
||||
for _, watcher := range wd.watchers {
|
||||
watcher.Stop()
|
||||
}
|
||||
}
|
||||
|
||||
func copy(obj runtime.Object) runtime.Object {
|
||||
objCopy, err := api.Scheme.DeepCopy(obj)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return objCopy.(runtime.Object)
|
||||
}
|
||||
|
||||
// Add sends an add event.
|
||||
func (wd *WatcherDispatcher) Add(obj runtime.Object) {
|
||||
wd.Lock()
|
||||
defer wd.Unlock()
|
||||
wd.eventsSoFar = append(wd.eventsSoFar, &watch.Event{Type: watch.Added, Object: copy(obj)})
|
||||
for _, watcher := range wd.watchers {
|
||||
if !watcher.IsStopped() {
|
||||
watcher.Add(copy(obj))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Modify sends a modify event.
|
||||
func (wd *WatcherDispatcher) Modify(obj runtime.Object) {
|
||||
wd.Lock()
|
||||
defer wd.Unlock()
|
||||
glog.V(4).Infof("->WatcherDispatcher.Modify(%v)", obj)
|
||||
wd.eventsSoFar = append(wd.eventsSoFar, &watch.Event{Type: watch.Modified, Object: copy(obj)})
|
||||
for i, watcher := range wd.watchers {
|
||||
if !watcher.IsStopped() {
|
||||
glog.V(4).Infof("->Watcher(%d).Modify(%v)", i, obj)
|
||||
watcher.Modify(copy(obj))
|
||||
} else {
|
||||
glog.V(4).Infof("->Watcher(%d) is stopped. Not calling Modify(%v)", i, obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Delete sends a delete event.
|
||||
func (wd *WatcherDispatcher) Delete(lastValue runtime.Object) {
|
||||
wd.Lock()
|
||||
defer wd.Unlock()
|
||||
wd.eventsSoFar = append(wd.eventsSoFar, &watch.Event{Type: watch.Deleted, Object: copy(lastValue)})
|
||||
for _, watcher := range wd.watchers {
|
||||
if !watcher.IsStopped() {
|
||||
watcher.Delete(copy(lastValue))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Error sends an Error event.
|
||||
func (wd *WatcherDispatcher) Error(errValue runtime.Object) {
|
||||
wd.Lock()
|
||||
defer wd.Unlock()
|
||||
wd.eventsSoFar = append(wd.eventsSoFar, &watch.Event{Type: watch.Error, Object: copy(errValue)})
|
||||
for _, watcher := range wd.watchers {
|
||||
if !watcher.IsStopped() {
|
||||
watcher.Error(copy(errValue))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Action sends an event of the requested type, for table-based testing.
|
||||
func (wd *WatcherDispatcher) Action(action watch.EventType, obj runtime.Object) {
|
||||
wd.Lock()
|
||||
defer wd.Unlock()
|
||||
wd.eventsSoFar = append(wd.eventsSoFar, &watch.Event{Type: action, Object: copy(obj)})
|
||||
for _, watcher := range wd.watchers {
|
||||
if !watcher.IsStopped() {
|
||||
watcher.Action(action, copy(obj))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterFakeWatch adds a new fake watcher for the specified resource in the given fake client.
|
||||
// All subsequent requests for a watch on the client will result in returning this fake watcher.
|
||||
func RegisterFakeWatch(resource string, client *core.Fake) *WatcherDispatcher {
|
||||
dispatcher := &WatcherDispatcher{
|
||||
watchers: make([]*watch.RaceFreeFakeWatcher, 0),
|
||||
eventsSoFar: make([]*watch.Event, 0),
|
||||
orderExecution: make(chan func(), 100),
|
||||
stopChan: make(chan struct{}),
|
||||
}
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case fun := <-dispatcher.orderExecution:
|
||||
fun()
|
||||
case <-dispatcher.stopChan:
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
client.AddWatchReactor(resource, func(action core.Action) (bool, watch.Interface, error) {
|
||||
watcher := watch.NewRaceFreeFake()
|
||||
dispatcher.register(watcher)
|
||||
return true, watcher, nil
|
||||
})
|
||||
return dispatcher
|
||||
}
|
||||
|
||||
// RegisterFakeList registers a list response for the specified resource inside the given fake client.
|
||||
// The passed value will be returned with every list call.
|
||||
func RegisterFakeList(resource string, client *core.Fake, obj runtime.Object) {
|
||||
client.AddReactor("list", resource, func(action core.Action) (bool, runtime.Object, error) {
|
||||
return true, obj, nil
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterFakeClusterGet registers a get response for the cluster resource inside the given fake client.
|
||||
func RegisterFakeClusterGet(client *core.Fake, obj runtime.Object) {
|
||||
clusterList, ok := obj.(*federationapi.ClusterList)
|
||||
client.AddReactor("get", "clusters", func(action core.Action) (bool, runtime.Object, error) {
|
||||
name := action.(core.GetAction).GetName()
|
||||
if ok {
|
||||
for _, cluster := range clusterList.Items {
|
||||
if cluster.Name == name {
|
||||
return true, &cluster, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return false, nil, fmt.Errorf("could not find the requested cluster: %s", name)
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterFakeOnCreate registers a reactor in the given fake client that passes
|
||||
// all created objects to the given watcher.
|
||||
func RegisterFakeOnCreate(resource string, client *core.Fake, watcher *WatcherDispatcher) {
|
||||
client.AddReactor("create", resource, func(action core.Action) (bool, runtime.Object, error) {
|
||||
createAction := action.(core.CreateAction)
|
||||
originalObj := createAction.GetObject()
|
||||
// Create a copy of the object here to prevent data races while reading the object in go routine.
|
||||
obj := copy(originalObj)
|
||||
watcher.orderExecution <- func() {
|
||||
glog.V(4).Infof("Object created: %v", obj)
|
||||
watcher.Add(obj)
|
||||
}
|
||||
return true, originalObj, nil
|
||||
})
|
||||
}
|
||||
|
||||
// RegisterFakeCopyOnCreate registers a reactor in the given fake client that passes
|
||||
// all created objects to the given watcher and also copies them to a channel for
|
||||
// in-test inspection.
|
||||
func RegisterFakeCopyOnCreate(resource string, client *core.Fake, watcher *WatcherDispatcher) 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)
|
||||
originalObj := createAction.GetObject()
|
||||
// Create a copy of the object here to prevent data races while reading the object in go routine.
|
||||
obj := copy(originalObj)
|
||||
watcher.orderExecution <- func() {
|
||||
glog.V(4).Infof("Object created. Writing to channel: %v", obj)
|
||||
watcher.Add(obj)
|
||||
objChan <- obj
|
||||
}
|
||||
return true, originalObj, nil
|
||||
})
|
||||
return objChan
|
||||
}
|
||||
|
||||
// RegisterFakeOnUpdate registers a reactor in the given fake client that passes
|
||||
// all updated objects to the given watcher.
|
||||
func RegisterFakeOnUpdate(resource string, client *core.Fake, watcher *WatcherDispatcher) {
|
||||
client.AddReactor("update", resource, func(action core.Action) (bool, runtime.Object, error) {
|
||||
updateAction := action.(core.UpdateAction)
|
||||
originalObj := updateAction.GetObject()
|
||||
glog.V(7).Infof("Updating %s: %v", resource, updateAction.GetObject())
|
||||
|
||||
// Create a copy of the object here to prevent data races while reading the object in go routine.
|
||||
obj := copy(originalObj)
|
||||
operation := func() {
|
||||
glog.V(4).Infof("Object updated %v", obj)
|
||||
watcher.Modify(obj)
|
||||
}
|
||||
select {
|
||||
case watcher.orderExecution <- operation:
|
||||
break
|
||||
case <-time.After(pushTimeout):
|
||||
glog.Errorf("Fake client execution channel blocked")
|
||||
glog.Errorf("Tried to push %v", updateAction)
|
||||
}
|
||||
return true, originalObj, nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// RegisterFakeCopyOnUpdate registers a reactor in the given fake client that passes
|
||||
// all updated objects to the given watcher and also copies them to a channel for
|
||||
// in-test inspection.
|
||||
func RegisterFakeCopyOnUpdate(resource string, client *core.Fake, watcher *WatcherDispatcher) 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)
|
||||
originalObj := updateAction.GetObject()
|
||||
glog.V(7).Infof("Updating %s: %v", resource, updateAction.GetObject())
|
||||
|
||||
// Create a copy of the object here to prevent data races while reading the object in go routine.
|
||||
obj := copy(originalObj)
|
||||
operation := func() {
|
||||
glog.V(4).Infof("Object updated. Writing to channel: %v", obj)
|
||||
watcher.Modify(obj)
|
||||
objChan <- obj
|
||||
}
|
||||
select {
|
||||
case watcher.orderExecution <- operation:
|
||||
break
|
||||
case <-time.After(pushTimeout):
|
||||
glog.Errorf("Fake client execution channel blocked")
|
||||
glog.Errorf("Tried to push %v", updateAction)
|
||||
}
|
||||
return true, originalObj, nil
|
||||
})
|
||||
return objChan
|
||||
}
|
||||
|
||||
// RegisterFakeOnDelete registers a reactor in the given fake client that passes
|
||||
// all deleted objects to the given watcher. Since we could get only name of the
|
||||
// deleted object from DeleteAction, this register function relies on the getObject
|
||||
// function passed to get the object by name and pass it watcher.
|
||||
func RegisterFakeOnDelete(resource string, client *core.Fake, watcher *WatcherDispatcher, getObject func(name, namespace string) runtime.Object) {
|
||||
client.AddReactor("delete", resource, func(action core.Action) (bool, runtime.Object, error) {
|
||||
deleteAction := action.(core.DeleteAction)
|
||||
obj := getObject(deleteAction.GetName(), deleteAction.GetNamespace())
|
||||
glog.V(7).Infof("Deleting %s: %v", resource, obj)
|
||||
|
||||
operation := func() {
|
||||
glog.V(4).Infof("Object deleted %v", obj)
|
||||
watcher.Delete(obj)
|
||||
}
|
||||
select {
|
||||
case watcher.orderExecution <- operation:
|
||||
break
|
||||
case <-time.After(pushTimeout):
|
||||
glog.Errorf("Fake client execution channel blocked")
|
||||
glog.Errorf("Tried to push %v", deleteAction)
|
||||
}
|
||||
return true, obj, nil
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
// Adds an update reactor to the given fake client.
|
||||
// The reactor just returns the object passed to update action.
|
||||
// This is used as a hack to workaround https://github.com/kubernetes/kubernetes/issues/40939.
|
||||
// Without this, all update actions using fake client return empty objects.
|
||||
func AddFakeUpdateReactor(resource string, client *core.Fake) {
|
||||
client.AddReactor("update", resource, func(action core.Action) (bool, runtime.Object, error) {
|
||||
updateAction := action.(core.UpdateAction)
|
||||
originalObj := updateAction.GetObject()
|
||||
return true, originalObj, nil
|
||||
})
|
||||
}
|
||||
|
||||
// GetObjectFromChan tries to get an api object from the given channel
|
||||
// within a reasonable time.
|
||||
func GetObjectFromChan(c chan runtime.Object) runtime.Object {
|
||||
select {
|
||||
case obj := <-c:
|
||||
return obj
|
||||
case <-time.After(wait.ForeverTestTimeout):
|
||||
pprof.Lookup("goroutine").WriteTo(os.Stderr, 1)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
type CheckingFunction func(runtime.Object) error
|
||||
|
||||
// CheckObjectFromChan tries to get an object matching the given check function
|
||||
// within a reasonable time.
|
||||
func CheckObjectFromChan(c chan runtime.Object, checkFunction CheckingFunction) error {
|
||||
delay := 20 * time.Second
|
||||
var lastError error
|
||||
for {
|
||||
select {
|
||||
case obj := <-c:
|
||||
if lastError = checkFunction(obj); lastError == nil {
|
||||
return nil
|
||||
}
|
||||
glog.Infof("Check function failed with %v", lastError)
|
||||
delay = 5 * time.Second
|
||||
case <-time.After(delay):
|
||||
pprof.Lookup("goroutine").WriteTo(os.Stderr, 1)
|
||||
if lastError == nil {
|
||||
return fmt.Errorf("Failed to get an object from channel")
|
||||
} else {
|
||||
return lastError
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// CompareObjectMeta returns an error when the given objects are not equivalent.
|
||||
func CompareObjectMeta(a, b metav1.ObjectMeta) error {
|
||||
if a.Namespace != b.Namespace {
|
||||
return fmt.Errorf("Different namespace expected:%s observed:%s", a.Namespace, b.Namespace)
|
||||
}
|
||||
if a.Name != b.Name {
|
||||
return fmt.Errorf("Different name expected:%s observed:%s", a.Name, b.Name)
|
||||
}
|
||||
if !reflect.DeepEqual(a.Labels, b.Labels) && (len(a.Labels) != 0 || len(b.Labels) != 0) {
|
||||
return fmt.Errorf("Labels are different expected:%v observed:%v", a.Labels, b.Labels)
|
||||
}
|
||||
if !reflect.DeepEqual(a.Annotations, b.Annotations) && (len(a.Annotations) != 0 || len(b.Annotations) != 0) {
|
||||
return fmt.Errorf("Annotations are different expected:%v observed:%v", a.Annotations, b.Annotations)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ToFederatedInformerForTestOnly(informer util.FederatedInformer) util.FederatedInformerForTestOnly {
|
||||
inter := informer.(interface{})
|
||||
return inter.(util.FederatedInformerForTestOnly)
|
||||
}
|
||||
|
||||
// NewCluster builds a new cluster object.
|
||||
func NewCluster(name string, readyStatus apiv1.ConditionStatus) *federationapi.Cluster {
|
||||
return &federationapi.Cluster{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Annotations: map[string]string{},
|
||||
Labels: map[string]string{"cluster": name},
|
||||
},
|
||||
Status: federationapi.ClusterStatus{
|
||||
Conditions: []federationapi.ClusterCondition{
|
||||
{Type: federationapi.ClusterReady, Status: readyStatus},
|
||||
},
|
||||
Zones: []string{"foozone"},
|
||||
Region: "fooregion",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure a key is in the store before returning (or timeout w/ error)
|
||||
func WaitForStoreUpdate(store util.FederatedReadOnlyStore, clusterName, key string, timeout time.Duration) error {
|
||||
retryInterval := 100 * time.Millisecond
|
||||
err := wait.PollImmediate(retryInterval, timeout, func() (bool, error) {
|
||||
_, found, err := store.GetByKey(clusterName, key)
|
||||
return found, err
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
// Ensure a key is in the store before returning (or timeout w/ error)
|
||||
func WaitForStoreUpdateChecking(store util.FederatedReadOnlyStore, clusterName, key string, timeout time.Duration,
|
||||
checkFunction CheckingFunction) error {
|
||||
retryInterval := 500 * time.Millisecond
|
||||
var lastError error
|
||||
err := wait.PollImmediate(retryInterval, timeout, func() (bool, error) {
|
||||
item, found, err := store.GetByKey(clusterName, key)
|
||||
if err != nil || !found {
|
||||
return found, err
|
||||
}
|
||||
runtimeObj := item.(runtime.Object)
|
||||
lastError = checkFunction(runtimeObj)
|
||||
glog.V(2).Infof("Check function failed for %s %v %v", key, runtimeObj, lastError)
|
||||
return lastError == nil, nil
|
||||
})
|
||||
return err
|
||||
}
|
||||
|
||||
func MetaAndSpecCheckingFunction(expected runtime.Object) CheckingFunction {
|
||||
return func(obj runtime.Object) error {
|
||||
if util.ObjectMetaAndSpecEquivalent(obj, expected) {
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("Object different expected=%#v received=%#v", expected, obj)
|
||||
}
|
||||
}
|
||||
|
||||
func AssertHasFinalizer(t *testing.T, obj runtime.Object, finalizer string) {
|
||||
hasFinalizer, err := finalizersutil.HasFinalizer(obj, finalizer)
|
||||
require.Nil(t, err)
|
||||
assert.True(t, hasFinalizer)
|
||||
}
|
||||
|
||||
func NewInt32(val int32) *int32 {
|
||||
p := new(int32)
|
||||
*p = val
|
||||
return p
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue