Add glide.yaml and vendor deps
This commit is contained in:
parent
db918f12ad
commit
5b3d5e81bd
18880 changed files with 5166045 additions and 1 deletions
78
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/BUILD
generated
vendored
Normal file
78
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
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",
|
||||
"versionize_listoptions.go",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//federation/apis/federation/v1beta1:go_default_library",
|
||||
"//federation/client/clientset_generated/federation_release_1_5:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/apis/extensions/v1beta1:go_default_library",
|
||||
"//pkg/client/cache:go_default_library",
|
||||
"//pkg/client/clientset_generated/internalclientset:go_default_library",
|
||||
"//pkg/client/clientset_generated/release_1_5:go_default_library",
|
||||
"//pkg/client/restclient:go_default_library",
|
||||
"//pkg/client/unversioned/clientcmd:go_default_library",
|
||||
"//pkg/client/unversioned/clientcmd/api:go_default_library",
|
||||
"//pkg/controller/deployment/util:go_default_library",
|
||||
"//pkg/conversion:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
"//pkg/util/flowcontrol:go_default_library",
|
||||
"//pkg/util/net:go_default_library",
|
||||
"//pkg/util/wait:go_default_library",
|
||||
"//pkg/watch:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
],
|
||||
)
|
||||
|
||||
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",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//federation/apis/federation/v1beta1:go_default_library",
|
||||
"//federation/client/clientset_generated/federation_release_1_5/fake:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/apis/extensions/v1beta1:go_default_library",
|
||||
"//pkg/client/cache:go_default_library",
|
||||
"//pkg/client/clientset_generated/release_1_5:go_default_library",
|
||||
"//pkg/client/clientset_generated/release_1_5/fake:go_default_library",
|
||||
"//pkg/client/testing/core:go_default_library",
|
||||
"//pkg/controller/deployment/util:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
"//pkg/watch:go_default_library",
|
||||
"//vendor:github.com/stretchr/testify/assert",
|
||||
],
|
||||
)
|
||||
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/kubernetes/pkg/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"
|
||||
federation_v1beta1 "k8s.io/kubernetes/federation/apis/federation/v1beta1"
|
||||
fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_release_1_5"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
clientset "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
"k8s.io/kubernetes/pkg/client/unversioned/clientcmd"
|
||||
clientcmdapi "k8s.io/kubernetes/pkg/client/unversioned/clientcmd/api"
|
||||
utilnet "k8s.io/kubernetes/pkg/util/net"
|
||||
"k8s.io/kubernetes/pkg/util/wait"
|
||||
)
|
||||
|
||||
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 {
|
||||
kubeconfigGetter := KubeconfigGetterForCluster(c)
|
||||
clusterConfig, err = clientcmd.BuildConfigFromKubeconfigGetter(serverAddress, kubeconfigGetter)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
clusterConfig.QPS = KubeAPIQPS
|
||||
clusterConfig.Burst = KubeAPIBurst
|
||||
}
|
||||
return clusterConfig, nil
|
||||
}
|
||||
|
||||
// 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 setup service accounts and mount files with secret tokens.
|
||||
var KubeconfigGetterForCluster = func(c *federation_v1beta1.Cluster) clientcmd.KubeconfigGetter {
|
||||
return func() (*clientcmdapi.Config, error) {
|
||||
secretRefName := ""
|
||||
if c.Spec.SecretRef != nil {
|
||||
secretRefName = c.Spec.SecretRef.Name
|
||||
} else {
|
||||
glog.Infof("didn't find secretRef for cluster %s. Trying insecure access", c.Name)
|
||||
}
|
||||
return KubeconfigGetterForSecret(secretRefName)()
|
||||
}
|
||||
}
|
||||
|
||||
// KubeconfigGettterForSecret is used to get the kubeconfig from the given secret.
|
||||
var KubeconfigGetterForSecret = func(secretName string) clientcmd.KubeconfigGetter {
|
||||
return func() (*clientcmdapi.Config, error) {
|
||||
var data []byte
|
||||
if secretName != "" {
|
||||
// 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 client: %s", err)
|
||||
}
|
||||
client, err := clientset.NewForConfig(cc)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error in creating in-cluster client: %s", err)
|
||||
}
|
||||
data = []byte{}
|
||||
var secret *api.Secret
|
||||
err = wait.PollImmediate(1*time.Second, getSecretTimeout, func() (bool, error) {
|
||||
secret, err = client.Core().Secrets(namespace).Get(secretName)
|
||||
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)
|
||||
}
|
||||
ok := false
|
||||
data, ok = secret.Data[KubeconfigSecretDataKey]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("secret does not have data with key: %s", KubeconfigSecretDataKey)
|
||||
}
|
||||
}
|
||||
return clientcmd.Load(data)
|
||||
}
|
||||
}
|
||||
|
||||
// Retruns Clientset for the given cluster.
|
||||
func GetClientsetForCluster(cluster *federation_v1beta1.Cluster) (*fedclientset.Clientset, error) {
|
||||
clusterConfig, err := BuildClusterConfig(cluster)
|
||||
if err != nil && clusterConfig != nil {
|
||||
clientset := fedclientset.NewForConfigOrDie(restclient.AddUserAgent(clusterConfig, userAgentName))
|
||||
return clientset, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
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/kubernetes/pkg/api/v1"
|
||||
)
|
||||
|
||||
// Checks if cluster-independent, user provided data in two given ConfigMapss are eqaul. 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
|
||||
}
|
||||
}
|
||||
25
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/deletionhelper/BUILD
generated
vendored
Normal file
25
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/deletionhelper/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["deletion_helper.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//federation/pkg/federation-controller/util:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/client/record:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
],
|
||||
)
|
||||
190
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/deletionhelper/deletion_helper.go
generated
vendored
Normal file
190
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/deletionhelper/deletion_helper.go
generated
vendored
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
/*
|
||||
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"
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/federation/pkg/federation-controller/util"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
api_v1 "k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/client/record"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
|
||||
"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 HasFinalizerFunc func(runtime.Object, string) bool
|
||||
type RemoveFinalizerFunc func(runtime.Object, string) (runtime.Object, error)
|
||||
type AddFinalizerFunc func(runtime.Object, string) (runtime.Object, error)
|
||||
type ObjNameFunc func(runtime.Object) string
|
||||
|
||||
type DeletionHelper struct {
|
||||
hasFinalizerFunc HasFinalizerFunc
|
||||
removeFinalizerFunc RemoveFinalizerFunc
|
||||
addFinalizerFunc AddFinalizerFunc
|
||||
objNameFunc ObjNameFunc
|
||||
updateTimeout time.Duration
|
||||
eventRecorder record.EventRecorder
|
||||
informer util.FederatedInformer
|
||||
updater util.FederatedUpdater
|
||||
}
|
||||
|
||||
func NewDeletionHelper(
|
||||
hasFinalizerFunc HasFinalizerFunc, removeFinalizerFunc RemoveFinalizerFunc,
|
||||
addFinalizerFunc AddFinalizerFunc, objNameFunc ObjNameFunc,
|
||||
updateTimeout time.Duration, eventRecorder record.EventRecorder,
|
||||
informer util.FederatedInformer,
|
||||
updater util.FederatedUpdater) *DeletionHelper {
|
||||
return &DeletionHelper{
|
||||
hasFinalizerFunc: hasFinalizerFunc,
|
||||
removeFinalizerFunc: removeFinalizerFunc,
|
||||
addFinalizerFunc: addFinalizerFunc,
|
||||
objNameFunc: objNameFunc,
|
||||
updateTimeout: updateTimeout,
|
||||
eventRecorder: eventRecorder,
|
||||
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) {
|
||||
if !dh.hasFinalizerFunc(obj, FinalizerDeleteFromUnderlyingClusters) {
|
||||
glog.V(2).Infof("Adding finalizer %s to %s", FinalizerDeleteFromUnderlyingClusters, dh.objNameFunc(obj))
|
||||
obj, err := dh.addFinalizerFunc(obj, FinalizerDeleteFromUnderlyingClusters)
|
||||
if err != nil {
|
||||
return obj, err
|
||||
}
|
||||
}
|
||||
if !dh.hasFinalizerFunc(obj, api_v1.FinalizerOrphan) {
|
||||
glog.V(2).Infof("Adding finalizer %s to %s", api_v1.FinalizerOrphan, dh.objNameFunc(obj))
|
||||
obj, err := dh.addFinalizerFunc(obj, api_v1.FinalizerOrphan)
|
||||
if err != nil {
|
||||
return obj, err
|
||||
}
|
||||
}
|
||||
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)
|
||||
if !dh.hasFinalizerFunc(obj, FinalizerDeleteFromUnderlyingClusters) {
|
||||
glog.V(2).Infof("obj does not have %s finalizer. Nothing to do", FinalizerDeleteFromUnderlyingClusters)
|
||||
return obj, nil
|
||||
}
|
||||
hasOrphanFinalizer := dh.hasFinalizerFunc(obj, api_v1.FinalizerOrphan)
|
||||
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.
|
||||
obj, err := dh.removeFinalizerFunc(obj, api_v1.FinalizerOrphan)
|
||||
if err != nil {
|
||||
return obj, err
|
||||
}
|
||||
return dh.removeFinalizerFunc(obj, FinalizerDeleteFromUnderlyingClusters)
|
||||
}
|
||||
|
||||
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),
|
||||
})
|
||||
}
|
||||
err = dh.updater.UpdateWithOnError(operations, dh.updateTimeout, func(op util.FederatedOperation, operror error) {
|
||||
objName := dh.objNameFunc(op.Obj)
|
||||
dh.eventRecorder.Eventf(obj, api.EventTypeNormal, "DeleteInClusterFailed",
|
||||
"Failed to delete obj %s in cluster %s: %v", objName, op.ClusterName, operror)
|
||||
})
|
||||
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.removeFinalizerFunc(obj, FinalizerDeleteFromUnderlyingClusters)
|
||||
}
|
||||
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"
|
||||
|
||||
api_v1 "k8s.io/kubernetes/pkg/api/v1"
|
||||
extensions_v1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
|
||||
deputils "k8s.io/kubernetes/pkg/controller/deployment/util"
|
||||
)
|
||||
|
||||
// Checks if cluster-independent, user provided data in two given Deployment are eqaul.
|
||||
// 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 api_v1.ObjectMeta) api_v1.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"
|
||||
|
||||
apiv1 "k8s.io/kubernetes/pkg/api/v1"
|
||||
extensionsv1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
|
||||
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: apiv1.ObjectMeta{
|
||||
Name: "wrr",
|
||||
Namespace: apiv1.NamespaceDefault,
|
||||
SelfLink: "/api/v1/namespaces/default/deployments/name123",
|
||||
},
|
||||
Spec: extensionsv1.DeploymentSpec{
|
||||
Replicas: &replicas,
|
||||
},
|
||||
}
|
||||
}
|
||||
38
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink/BUILD
generated
vendored
Normal file
38
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["eventsink.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//federation/client/clientset_generated/federation_release_1_5:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/client/record:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["eventsink_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//federation/client/clientset_generated/federation_release_1_5/fake:go_default_library",
|
||||
"//federation/pkg/federation-controller/util/test:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/client/testing/core:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
"//vendor:github.com/stretchr/testify/assert",
|
||||
],
|
||||
)
|
||||
50
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink/eventsink.go
generated
vendored
Normal file
50
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink/eventsink.go
generated
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
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 (
|
||||
fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_release_1_5"
|
||||
api "k8s.io/kubernetes/pkg/api"
|
||||
api_v1 "k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/client/record"
|
||||
)
|
||||
|
||||
// Implemnts k8s.io/kubernetes/pkg/client/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,
|
||||
}
|
||||
}
|
||||
|
||||
func (fes *FederatedEventSink) Create(event *api_v1.Event) (*api_v1.Event, error) {
|
||||
return fes.clientset.Core().Events(event.Namespace).Create(event)
|
||||
}
|
||||
|
||||
func (fes *FederatedEventSink) Update(event *api_v1.Event) (*api_v1.Event, error) {
|
||||
return fes.clientset.Core().Events(event.Namespace).Update(event)
|
||||
}
|
||||
|
||||
func (fes *FederatedEventSink) Patch(event *api_v1.Event, data []byte) (*api_v1.Event, error) {
|
||||
return fes.clientset.Core().Events(event.Namespace).Patch(event.Name, api.StrategicMergePatchType, data)
|
||||
}
|
||||
70
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink/eventsink_test.go
generated
vendored
Normal file
70
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/eventsink/eventsink_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 eventsink
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
fakefedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_release_1_5/fake"
|
||||
. "k8s.io/kubernetes/federation/pkg/federation-controller/util/test"
|
||||
apiv1 "k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/client/testing/core"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
|
||||
"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 := apiv1.Event{
|
||||
ObjectMeta: apiv1.ObjectMeta{
|
||||
Name: "bzium",
|
||||
Namespace: "ns",
|
||||
},
|
||||
}
|
||||
sink := NewFederatedEventSink(fakeFederationClient)
|
||||
eventUpdated, err := sink.Create(&event)
|
||||
assert.NoError(t, err)
|
||||
eventV1 := GetObjectFromChan(createdChan).(*apiv1.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).(*apiv1.Event)
|
||||
assert.NotNil(t, eventV1)
|
||||
// Just some simple sanity checks.
|
||||
assert.Equal(t, event.Name, eventV1.Name)
|
||||
assert.Equal(t, event.Name, eventUpdated.Name)
|
||||
}
|
||||
519
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/federated_informer.go
generated
vendored
Normal file
519
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/federated_informer.go
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
149
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/federated_informer_test.go
generated
vendored
Normal file
149
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/federated_informer_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,149 @@
|
|||
/*
|
||||
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"
|
||||
|
||||
federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1"
|
||||
fakefederationclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_release_1_5/fake"
|
||||
apiv1 "k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
kubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5"
|
||||
fakekubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5/fake"
|
||||
"k8s.io/kubernetes/pkg/client/testing/core"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/watch"
|
||||
|
||||
"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: apiv1.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: apiv1.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.ControllerInterface) {
|
||||
return cache.NewInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options apiv1.ListOptions) (runtime.Object, error) {
|
||||
return clientset.Core().Services(apiv1.NamespaceAll).List(options)
|
||||
},
|
||||
WatchFunc: func(options apiv1.ListOptions) (watch.Interface, error) {
|
||||
return clientset.Core().Services(apiv1.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()
|
||||
}
|
||||
122
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/federated_updater.go
generated
vendored
Normal file
122
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/federated_updater.go
generated
vendored
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
/*
|
||||
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"
|
||||
"time"
|
||||
|
||||
kubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5"
|
||||
pkgruntime "k8s.io/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// A helper that executes the given set of updates on federation, in parallel.
|
||||
type FederatedUpdater interface {
|
||||
// Executes the given set of operations within the specified timeout.
|
||||
// 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.
|
||||
Update([]FederatedOperation, time.Duration) error
|
||||
|
||||
UpdateWithOnError([]FederatedOperation, time.Duration, func(FederatedOperation, error)) 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
|
||||
|
||||
addFunction FederatedOperationHandler
|
||||
updateFunction FederatedOperationHandler
|
||||
deleteFunction FederatedOperationHandler
|
||||
}
|
||||
|
||||
func NewFederatedUpdater(federation FederationView, add, update, del FederatedOperationHandler) FederatedUpdater {
|
||||
return &federatedUpdaterImpl{
|
||||
federation: federation,
|
||||
addFunction: add,
|
||||
updateFunction: update,
|
||||
deleteFunction: del,
|
||||
}
|
||||
}
|
||||
|
||||
func (fu *federatedUpdaterImpl) Update(ops []FederatedOperation, timeout time.Duration) error {
|
||||
return fu.UpdateWithOnError(ops, timeout, nil)
|
||||
}
|
||||
|
||||
func (fu *federatedUpdaterImpl) UpdateWithOnError(ops []FederatedOperation, timeout time.Duration, onError func(FederatedOperation, error)) 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
|
||||
}
|
||||
|
||||
switch op.Type {
|
||||
case OperationTypeAdd:
|
||||
err = fu.addFunction(clientset, op.Obj)
|
||||
case OperationTypeUpdate:
|
||||
err = fu.updateFunction(clientset, op.Obj)
|
||||
case OperationTypeDelete:
|
||||
err = fu.deleteFunction(clientset, op.Obj)
|
||||
}
|
||||
if err != nil && onError != nil {
|
||||
onError(op, err)
|
||||
}
|
||||
done <- err
|
||||
}(op)
|
||||
}
|
||||
start := time.Now()
|
||||
for i := 0; i < len(ops); i++ {
|
||||
now := time.Now()
|
||||
if !now.Before(start.Add(timeout)) {
|
||||
return fmt.Errorf("failed to finish all operations in %v", timeout)
|
||||
}
|
||||
select {
|
||||
case err := <-done:
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
case <-time.After(start.Add(timeout).Sub(now)):
|
||||
return fmt.Errorf("failed to finish all operations in %v", timeout)
|
||||
}
|
||||
}
|
||||
// All operations finished in time.
|
||||
return nil
|
||||
}
|
||||
148
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/federated_updater_test.go
generated
vendored
Normal file
148
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/federated_updater_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,148 @@
|
|||
/*
|
||||
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"
|
||||
|
||||
federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1"
|
||||
apiv1 "k8s.io/kubernetes/pkg/api/v1"
|
||||
kubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5"
|
||||
fakekubeclientset "k8s.io/kubernetes/pkg/client/clientset_generated/release_1_5/fake"
|
||||
pkgruntime "k8s.io/kubernetes/pkg/runtime"
|
||||
|
||||
"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
|
||||
}
|
||||
|
||||
func TestFederatedUpdaterOK(t *testing.T) {
|
||||
addChan := make(chan string, 5)
|
||||
updateChan := make(chan string, 5)
|
||||
|
||||
updater := NewFederatedUpdater(&fakeFederationView{},
|
||||
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"),
|
||||
},
|
||||
}, time.Minute)
|
||||
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{},
|
||||
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"),
|
||||
},
|
||||
}, time.Minute)
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestFederatedUpdaterTimeout(t *testing.T) {
|
||||
start := time.Now()
|
||||
updater := NewFederatedUpdater(&fakeFederationView{},
|
||||
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"),
|
||||
},
|
||||
}, time.Second)
|
||||
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: apiv1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: name,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func noop(_ kubeclientset.Interface, _ pkgruntime.Object) error {
|
||||
return nil
|
||||
}
|
||||
79
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/handlers.go
generated
vendored
Normal file
79
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/handlers.go
generated
vendored
Normal file
|
|
@ -0,0 +1,79 @@
|
|||
/*
|
||||
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"
|
||||
|
||||
apiv1 "k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/client/cache"
|
||||
pkgruntime "k8s.io/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
// 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").(apiv1.ObjectMeta)
|
||||
curMeta := getFieldOrPanic(cur, "ObjectMeta").(apiv1.ObjectMeta)
|
||||
if !ObjectMetaEquivalent(oldMeta, curMeta) ||
|
||||
!reflect.DeepEqual(getFieldOrPanic(old, "Spec"), getFieldOrPanic(cur, "Spec")) {
|
||||
triggerFunc(curObj)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
99
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/handlers_test.go
generated
vendored
Normal file
99
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/handlers_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,99 @@
|
|||
/*
|
||||
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/kubernetes/pkg/api/v1"
|
||||
pkgruntime "k8s.io/kubernetes/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: apiv1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s1",
|
||||
},
|
||||
}
|
||||
service2 := apiv1.Service{
|
||||
ObjectMeta: apiv1.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: apiv1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s1",
|
||||
},
|
||||
Status: apiv1.ServiceStatus{
|
||||
LoadBalancer: apiv1.LoadBalancerStatus{
|
||||
Ingress: []apiv1.LoadBalancerIngress{{
|
||||
Hostname: "A",
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
trigger2.OnUpdate(&service, &service3)
|
||||
assert.False(t, triggered())
|
||||
}
|
||||
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"
|
||||
|
||||
api_v1 "k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/conversion"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
)
|
||||
|
||||
// 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 api_v1.ObjectMeta) api_v1.ObjectMeta {
|
||||
return api_v1.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 api_v1.ObjectMeta) api_v1.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 api_v1.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().(api_v1.ObjectMeta)
|
||||
objectMetaB := reflect.ValueOf(b).Elem().FieldByName("ObjectMeta").Interface().(api_v1.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 := conversion.NewCloner().DeepCopy(item)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return result
|
||||
}
|
||||
116
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/meta_test.go
generated
vendored
Normal file
116
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/meta_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
/*
|
||||
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/kubernetes/pkg/api/v1"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestObjectMeta(t *testing.T) {
|
||||
o1 := api_v1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s1",
|
||||
UID: "1231231412",
|
||||
ResourceVersion: "999",
|
||||
}
|
||||
o2 := copyObjectMeta(o1)
|
||||
o3 := api_v1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s1",
|
||||
UID: "1231231412",
|
||||
Annotations: map[string]string{"A": "B"},
|
||||
}
|
||||
o4 := api_v1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s1",
|
||||
UID: "1231255531412",
|
||||
Annotations: map[string]string{"A": "B"},
|
||||
}
|
||||
o5 := api_v1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s1",
|
||||
ResourceVersion: "1231231412",
|
||||
Annotations: map[string]string{"A": "B"},
|
||||
}
|
||||
o6 := api_v1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s1",
|
||||
ResourceVersion: "1231255531412",
|
||||
Annotations: map[string]string{"A": "B"},
|
||||
}
|
||||
o7 := api_v1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s1",
|
||||
ResourceVersion: "1231255531412",
|
||||
Annotations: map[string]string{},
|
||||
Labels: map[string]string{},
|
||||
}
|
||||
o8 := api_v1.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: api_v1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s1",
|
||||
},
|
||||
Spec: api_v1.ServiceSpec{
|
||||
ExternalName: "Service1",
|
||||
},
|
||||
}
|
||||
s1b := s1
|
||||
s2 := api_v1.Service{
|
||||
ObjectMeta: api_v1.ObjectMeta{
|
||||
Namespace: "ns1",
|
||||
Name: "s2",
|
||||
},
|
||||
Spec: api_v1.ServiceSpec{
|
||||
ExternalName: "Service1",
|
||||
},
|
||||
}
|
||||
s3 := api_v1.Service{
|
||||
ObjectMeta: api_v1.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))
|
||||
}
|
||||
29
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/planner/BUILD
generated
vendored
Normal file
29
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/planner/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["planner.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = ["//federation/apis/federation:go_default_library"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["planner_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//federation/apis/federation:go_default_library",
|
||||
"//vendor:github.com/stretchr/testify/assert",
|
||||
],
|
||||
)
|
||||
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.FederatedReplicaSetPreferences
|
||||
}
|
||||
|
||||
type namedClusterReplicaSetPreferences struct {
|
||||
clusterName string
|
||||
hash uint32
|
||||
fedapi.ClusterReplicaSetPreferences
|
||||
}
|
||||
|
||||
type byWeight []*namedClusterReplicaSetPreferences
|
||||
|
||||
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.FederatedReplicaSetPreferences) *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 lexiconographical 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 scheudled 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([]*namedClusterReplicaSetPreferences, 0, len(availableClusters))
|
||||
plan := make(map[string]int64, len(preferences))
|
||||
overflow := make(map[string]int64, len(preferences))
|
||||
|
||||
named := func(name string, pref fedapi.ClusterReplicaSetPreferences) *namedClusterReplicaSetPreferences {
|
||||
// Seems to work better than addler for our case.
|
||||
hasher := fnv.New32()
|
||||
hasher.Write([]byte(name))
|
||||
hasher.Write([]byte(replicaSetKey))
|
||||
|
||||
return &namedClusterReplicaSetPreferences{
|
||||
clusterName: name,
|
||||
hash: hasher.Sum32(),
|
||||
ClusterReplicaSetPreferences: 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 distribue 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([]*namedClusterReplicaSetPreferences, 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.ClusterReplicaSetPreferences, replicas int64, clusters []string, expected map[string]int64) {
|
||||
planer := NewPlanner(&fedapi.FederatedReplicaSetPreferences{
|
||||
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.ClusterReplicaSetPreferences, replicas int64, clusters []string,
|
||||
existing map[string]int64, expected map[string]int64) {
|
||||
planer := NewPlanner(&fedapi.FederatedReplicaSetPreferences{
|
||||
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.ClusterReplicaSetPreferences, replicas int64, clusters []string,
|
||||
existing map[string]int64,
|
||||
capacity map[string]int64,
|
||||
expected map[string]int64,
|
||||
expectedOverflow map[string]int64) {
|
||||
planer := NewPlanner(&fedapi.FederatedReplicaSetPreferences{
|
||||
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.ClusterReplicaSetPreferences{
|
||||
"*": {Weight: 1}},
|
||||
50, []string{"A", "B", "C"},
|
||||
// hash dependent
|
||||
map[string]int64{"A": 16, "B": 17, "C": 17})
|
||||
|
||||
doCheck(t, map[string]fedapi.ClusterReplicaSetPreferences{
|
||||
"*": {Weight: 1}},
|
||||
50, []string{"A", "B"},
|
||||
map[string]int64{"A": 25, "B": 25})
|
||||
|
||||
doCheck(t, map[string]fedapi.ClusterReplicaSetPreferences{
|
||||
"*": {Weight: 1}},
|
||||
1, []string{"A", "B"},
|
||||
// hash dependent
|
||||
map[string]int64{"A": 0, "B": 1})
|
||||
|
||||
doCheck(t, map[string]fedapi.ClusterReplicaSetPreferences{
|
||||
"*": {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.ClusterReplicaSetPreferences{
|
||||
"*": {Weight: 1}},
|
||||
1, []string{"A"},
|
||||
map[string]int64{"A": 1})
|
||||
|
||||
doCheck(t, map[string]fedapi.ClusterReplicaSetPreferences{
|
||||
"*": {Weight: 1}},
|
||||
1, []string{},
|
||||
map[string]int64{})
|
||||
}
|
||||
|
||||
func TestEqualWithExisting(t *testing.T) {
|
||||
doCheckWithExisting(t, map[string]fedapi.ClusterReplicaSetPreferences{
|
||||
"*": {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.ClusterReplicaSetPreferences{
|
||||
"*": {Weight: 1}},
|
||||
50, []string{"A", "B"},
|
||||
map[string]int64{"A": 30},
|
||||
map[string]int64{"A": 30, "B": 20})
|
||||
|
||||
doCheckWithExisting(t, map[string]fedapi.ClusterReplicaSetPreferences{
|
||||
"*": {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.ClusterReplicaSetPreferences{
|
||||
"*": {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.ClusterReplicaSetPreferences{
|
||||
"*": {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.ClusterReplicaSetPreferences{
|
||||
"*": {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.ClusterReplicaSetPreferences{
|
||||
"*": {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.ClusterReplicaSetPreferences{
|
||||
"*": {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.ClusterReplicaSetPreferences{
|
||||
"*": {Weight: 1}},
|
||||
500000, []string{"A", "B"},
|
||||
map[string]int64{"A": 300000},
|
||||
map[string]int64{"A": 300000, "B": 200000})
|
||||
|
||||
doCheckWithExisting(t, map[string]fedapi.ClusterReplicaSetPreferences{
|
||||
"*": {Weight: 1}},
|
||||
50, []string{"A", "B"},
|
||||
map[string]int64{"A": 10},
|
||||
map[string]int64{"A": 25, "B": 25})
|
||||
|
||||
doCheckWithExisting(t, map[string]fedapi.ClusterReplicaSetPreferences{
|
||||
"*": {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.ClusterReplicaSetPreferences{
|
||||
"*": {Weight: 1}},
|
||||
1, []string{"A", "B"},
|
||||
map[string]int64{"A": 30},
|
||||
map[string]int64{"A": 1, "B": 0})
|
||||
|
||||
doCheckWithExisting(t, map[string]fedapi.ClusterReplicaSetPreferences{
|
||||
"*": {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.ClusterReplicaSetPreferences{
|
||||
"*": {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.ClusterReplicaSetPreferences{
|
||||
"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.ClusterReplicaSetPreferences{
|
||||
"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.ClusterReplicaSetPreferences{
|
||||
"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.ClusterReplicaSetPreferences{
|
||||
"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.ClusterReplicaSetPreferences{
|
||||
"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.ClusterReplicaSetPreferences{
|
||||
"*": {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.ClusterReplicaSetPreferences{
|
||||
"*": {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.ClusterReplicaSetPreferences{
|
||||
"*": {MinReplicas: 2, Weight: 0}},
|
||||
50, []string{"A", "B", "C"},
|
||||
map[string]int64{"A": 2, "B": 2, "C": 2})
|
||||
|
||||
doCheck(t, map[string]fedapi.ClusterReplicaSetPreferences{
|
||||
"*": {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.ClusterReplicaSetPreferences{
|
||||
"*": {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.ClusterReplicaSetPreferences{
|
||||
"*": {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.ClusterReplicaSetPreferences{
|
||||
"*": {Weight: 1, MaxReplicas: pint(2)}},
|
||||
50, []string{"A", "B", "C"},
|
||||
map[string]int64{"A": 2, "B": 2, "C": 2})
|
||||
|
||||
doCheck(t, map[string]fedapi.ClusterReplicaSetPreferences{
|
||||
"*": {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.ClusterReplicaSetPreferences{
|
||||
"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.ClusterReplicaSetPreferences{
|
||||
"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.ClusterReplicaSetPreferences{
|
||||
"A": {Weight: 10000},
|
||||
"B": {Weight: 1}},
|
||||
50, []string{"B", "C"},
|
||||
map[string]int64{"B": 50, "C": 0})
|
||||
|
||||
doCheck(t, map[string]fedapi.ClusterReplicaSetPreferences{
|
||||
"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.ClusterReplicaSetPreferences{
|
||||
"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.ClusterReplicaSetPreferences{
|
||||
"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.ClusterReplicaSetPreferences{
|
||||
"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})
|
||||
}
|
||||
37
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/podanalyzer/BUILD
generated
vendored
Normal file
37
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/podanalyzer/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["pod_helper.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//federation/pkg/federation-controller/util:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/apis/meta/v1:go_default_library",
|
||||
"//pkg/labels:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["pod_helper_test.go"],
|
||||
library = "go_default_library",
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//federation/pkg/federation-controller/util:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/apis/extensions/v1beta1:go_default_library",
|
||||
"//pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor:github.com/stretchr/testify/assert",
|
||||
],
|
||||
)
|
||||
82
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/podanalyzer/pod_helper.go
generated
vendored
Normal file
82
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/podanalyzer/pod_helper.go
generated
vendored
Normal file
|
|
@ -0,0 +1,82 @@
|
|||
/*
|
||||
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 (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"k8s.io/kubernetes/federation/pkg/federation-controller/util"
|
||||
api_v1 "k8s.io/kubernetes/pkg/api/v1"
|
||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/labels"
|
||||
)
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
// A function that 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 AnalysePods(selectorv1 *metav1.LabelSelector, allPods []util.FederatedObject, currentTime time.Time) (map[string]PodAnalysisResult, error) {
|
||||
selector, err := metav1.LabelSelectorAsSelector(selectorv1)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("invalid selector: %v", err)
|
||||
}
|
||||
result := make(map[string]PodAnalysisResult)
|
||||
|
||||
for _, fedObject := range allPods {
|
||||
pod, isPod := fedObject.Object.(*api_v1.Pod)
|
||||
if !isPod {
|
||||
return nil, fmt.Errorf("invalid arg content - not a *pod")
|
||||
}
|
||||
if !selector.Empty() && selector.Matches(labels.Set(pod.Labels)) {
|
||||
status := result[fedObject.ClusterName]
|
||||
status.Total++
|
||||
for _, condition := range pod.Status.Conditions {
|
||||
if pod.Status.Phase == api_v1.PodRunning {
|
||||
if condition.Type == api_v1.PodReady {
|
||||
status.RunningAndReady++
|
||||
}
|
||||
} else {
|
||||
if condition.Type == api_v1.PodScheduled &&
|
||||
condition.Status == api_v1.ConditionFalse &&
|
||||
condition.Reason == "Unschedulable" &&
|
||||
condition.LastTransitionTime.Add(UnschedulableThreshold).Before(currentTime) {
|
||||
|
||||
status.Unschedulable++
|
||||
}
|
||||
}
|
||||
}
|
||||
result[fedObject.ClusterName] = status
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
119
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/podanalyzer/pod_helper_test.go
generated
vendored
Normal file
119
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/podanalyzer/pod_helper_test.go
generated
vendored
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
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"
|
||||
|
||||
"k8s.io/kubernetes/federation/pkg/federation-controller/util"
|
||||
api_v1 "k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/apis/extensions/v1beta1"
|
||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestAnalyze(t *testing.T) {
|
||||
now := time.Now()
|
||||
replicaSet := newReplicaSet(map[string]string{"A": "B"})
|
||||
replicaSet2 := newReplicaSet(map[string]string{"C": "D"})
|
||||
podRunning := newPod("p1", replicaSet,
|
||||
api_v1.PodStatus{
|
||||
Phase: api_v1.PodRunning,
|
||||
Conditions: []api_v1.PodCondition{
|
||||
{
|
||||
Type: api_v1.PodReady,
|
||||
Status: api_v1.ConditionTrue,
|
||||
},
|
||||
},
|
||||
})
|
||||
podUnschedulable := newPod("pU", replicaSet,
|
||||
api_v1.PodStatus{
|
||||
Phase: api_v1.PodPending,
|
||||
Conditions: []api_v1.PodCondition{
|
||||
{
|
||||
Type: api_v1.PodScheduled,
|
||||
Status: api_v1.ConditionFalse,
|
||||
Reason: "Unschedulable",
|
||||
LastTransitionTime: metav1.Time{Time: now.Add(-10 * time.Minute)},
|
||||
},
|
||||
},
|
||||
})
|
||||
podOther := newPod("pO", replicaSet,
|
||||
api_v1.PodStatus{
|
||||
Phase: api_v1.PodPending,
|
||||
Conditions: []api_v1.PodCondition{},
|
||||
})
|
||||
podOtherRS := newPod("pO", replicaSet2,
|
||||
api_v1.PodStatus{
|
||||
Phase: api_v1.PodPending,
|
||||
Conditions: []api_v1.PodCondition{},
|
||||
})
|
||||
|
||||
federatedObjects := []util.FederatedObject{
|
||||
{ClusterName: "c1", Object: podRunning},
|
||||
{ClusterName: "c1", Object: podRunning},
|
||||
{ClusterName: "c1", Object: podRunning},
|
||||
{ClusterName: "c1", Object: podUnschedulable},
|
||||
{ClusterName: "c1", Object: podUnschedulable},
|
||||
{ClusterName: "c2", Object: podOther},
|
||||
{ClusterName: "c2", Object: podOtherRS},
|
||||
}
|
||||
|
||||
raport, err := AnalysePods(replicaSet.Spec.Selector, federatedObjects, now)
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, 2, len(raport))
|
||||
c1Raport := raport["c1"]
|
||||
c2Raport := raport["c2"]
|
||||
assert.Equal(t, PodAnalysisResult{
|
||||
Total: 5,
|
||||
RunningAndReady: 3,
|
||||
Unschedulable: 2,
|
||||
}, c1Raport)
|
||||
assert.Equal(t, PodAnalysisResult{
|
||||
Total: 1,
|
||||
RunningAndReady: 0,
|
||||
Unschedulable: 0,
|
||||
}, c2Raport)
|
||||
}
|
||||
|
||||
func newReplicaSet(selectorMap map[string]string) *v1beta1.ReplicaSet {
|
||||
replicas := int32(3)
|
||||
rs := &v1beta1.ReplicaSet{
|
||||
ObjectMeta: api_v1.ObjectMeta{
|
||||
Name: "foobar",
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v1beta1.ReplicaSetSpec{
|
||||
Replicas: &replicas,
|
||||
Selector: &metav1.LabelSelector{MatchLabels: selectorMap},
|
||||
},
|
||||
}
|
||||
return rs
|
||||
}
|
||||
|
||||
func newPod(name string, rs *v1beta1.ReplicaSet, status api_v1.PodStatus) *api_v1.Pod {
|
||||
return &api_v1.Pod{
|
||||
ObjectMeta: api_v1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: rs.Namespace,
|
||||
Labels: rs.Spec.Selector.MatchLabels,
|
||||
},
|
||||
Status: status,
|
||||
}
|
||||
}
|
||||
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/kubernetes/pkg/api/v1"
|
||||
)
|
||||
|
||||
// Checks if cluster-independent, user provided data in two given Secrets are eqaul. 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)
|
||||
}
|
||||
28
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/test/BUILD
generated
vendored
Normal file
28
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/test/BUILD
generated
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_binary",
|
||||
"go_library",
|
||||
"go_test",
|
||||
"cgo_library",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["test_helper.go"],
|
||||
tags = ["automanaged"],
|
||||
deps = [
|
||||
"//federation/apis/federation/v1beta1:go_default_library",
|
||||
"//federation/pkg/federation-controller/util:go_default_library",
|
||||
"//pkg/api:go_default_library",
|
||||
"//pkg/api/v1:go_default_library",
|
||||
"//pkg/client/testing/core:go_default_library",
|
||||
"//pkg/runtime:go_default_library",
|
||||
"//pkg/util/wait:go_default_library",
|
||||
"//pkg/watch:go_default_library",
|
||||
"//vendor:github.com/golang/glog",
|
||||
],
|
||||
)
|
||||
324
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/test/test_helper.go
generated
vendored
Normal file
324
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/test/test_helper.go
generated
vendored
Normal file
|
|
@ -0,0 +1,324 @@
|
|||
/*
|
||||
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"
|
||||
"time"
|
||||
|
||||
federationapi "k8s.io/kubernetes/federation/apis/federation/v1beta1"
|
||||
"k8s.io/kubernetes/federation/pkg/federation-controller/util"
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
apiv1 "k8s.io/kubernetes/pkg/api/v1"
|
||||
"k8s.io/kubernetes/pkg/client/testing/core"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/util/wait"
|
||||
"k8s.io/kubernetes/pkg/watch"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// A structure that distributes eventes 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)
|
||||
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()),
|
||||
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
|
||||
})
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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()
|
||||
// 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 updated. Writing to channel: %v", obj)
|
||||
watcher.Modify(obj)
|
||||
objChan <- obj
|
||||
}
|
||||
return true, originalObj, nil
|
||||
})
|
||||
return objChan
|
||||
}
|
||||
|
||||
// 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 apiv1.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.Namespace, b.Namespace)
|
||||
}
|
||||
if !reflect.DeepEqual(a.Labels, b.Labels) && (len(a.Labels) != 0 || len(b.Labels) != 0) {
|
||||
return fmt.Errorf("Labels are different expected:%v observerd:%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 observerd:%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: apiv1.ObjectMeta{
|
||||
Name: name,
|
||||
Annotations: map[string]string{},
|
||||
},
|
||||
Status: federationapi.ClusterStatus{
|
||||
Conditions: []federationapi.ClusterCondition{
|
||||
{Type: federationapi.ClusterReady, Status: readyStatus},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
40
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/versionize_listoptions.go
generated
vendored
Normal file
40
vendor/k8s.io/kubernetes/federation/pkg/federation-controller/util/versionize_listoptions.go
generated
vendored
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
/*
|
||||
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 (
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
)
|
||||
|
||||
// TODO: remove this when Reflector takes an interface rather than a particular ListOptions as input parameter.
|
||||
func VersionizeV1ListOptions(in api.ListOptions) (out v1.ListOptions) {
|
||||
if in.LabelSelector != nil {
|
||||
out.LabelSelector = in.LabelSelector.String()
|
||||
} else {
|
||||
out.LabelSelector = ""
|
||||
}
|
||||
if in.FieldSelector != nil {
|
||||
out.FieldSelector = in.FieldSelector.String()
|
||||
} else {
|
||||
out.FieldSelector = ""
|
||||
}
|
||||
out.Watch = in.Watch
|
||||
out.ResourceVersion = in.ResourceVersion
|
||||
out.TimeoutSeconds = in.TimeoutSeconds
|
||||
return out
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue