mirror of
				https://github.com/optim-enterprises-bv/kubernetes.git
				synced 2025-11-04 04:08:16 +00:00 
			
		
		
		
	The `recorder.PastEventf` method wasn't actually working as advertised. It was supposed to accept a timestamp, which would be used when generating the event. However, as the [source code](https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/client-go/tools/record/event.go#L316) shows, this `timestamp` was never actually used. In other words, `PastEventf` is identical to `Eventf`. We have two options: one would be to fix `PastEventf` so that it works as advertised. The other would be to delete `PastEventf` and only support `Eventf`. Ultimately, I could only find one use of `PastEventf` in the code base, so I propose we just delete `PastEventf` and convert all uses to `Eventf`.
		
			
				
	
	
		
			515 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			515 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
/*
 | 
						|
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 (
 | 
						|
	"encoding/json"
 | 
						|
	"errors"
 | 
						|
	"fmt"
 | 
						|
	"reflect"
 | 
						|
	"sync"
 | 
						|
	"testing"
 | 
						|
	"time"
 | 
						|
 | 
						|
	apierrors "k8s.io/apimachinery/pkg/api/errors"
 | 
						|
	"k8s.io/apimachinery/pkg/api/resource"
 | 
						|
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | 
						|
	"k8s.io/apimachinery/pkg/runtime"
 | 
						|
	"k8s.io/apimachinery/pkg/types"
 | 
						|
	"k8s.io/apimachinery/pkg/util/sets"
 | 
						|
	"k8s.io/apimachinery/pkg/util/strategicpatch"
 | 
						|
	"k8s.io/apimachinery/pkg/watch"
 | 
						|
 | 
						|
	"k8s.io/apimachinery/pkg/util/clock"
 | 
						|
	ref "k8s.io/client-go/tools/reference"
 | 
						|
 | 
						|
	"k8s.io/api/core/v1"
 | 
						|
	"k8s.io/client-go/kubernetes/fake"
 | 
						|
	v1core "k8s.io/client-go/kubernetes/typed/core/v1"
 | 
						|
	"k8s.io/client-go/tools/cache"
 | 
						|
	"k8s.io/kubernetes/pkg/api/legacyscheme"
 | 
						|
	api "k8s.io/kubernetes/pkg/apis/core"
 | 
						|
	utilnode "k8s.io/kubernetes/pkg/util/node"
 | 
						|
 | 
						|
	jsonpatch "github.com/evanphx/json-patch"
 | 
						|
	"k8s.io/klog"
 | 
						|
)
 | 
						|
 | 
						|
var (
 | 
						|
	keyFunc = cache.DeletionHandlingMetaNamespaceKeyFunc
 | 
						|
)
 | 
						|
 | 
						|
// FakeNodeHandler is a fake implementation of NodesInterface and NodeInterface. It
 | 
						|
// allows test cases to have fine-grained control over mock behaviors. We also need
 | 
						|
// PodsInterface and PodInterface to test list & delete pods, which is implemented in
 | 
						|
// the embedded client.Fake field.
 | 
						|
type FakeNodeHandler struct {
 | 
						|
	*fake.Clientset
 | 
						|
 | 
						|
	// Input: Hooks determine if request is valid or not
 | 
						|
	CreateHook func(*FakeNodeHandler, *v1.Node) bool
 | 
						|
	Existing   []*v1.Node
 | 
						|
 | 
						|
	// Output
 | 
						|
	CreatedNodes        []*v1.Node
 | 
						|
	DeletedNodes        []*v1.Node
 | 
						|
	UpdatedNodes        []*v1.Node
 | 
						|
	UpdatedNodeStatuses []*v1.Node
 | 
						|
	RequestCount        int
 | 
						|
 | 
						|
	// Synchronization
 | 
						|
	lock           sync.Mutex
 | 
						|
	DeleteWaitChan chan struct{}
 | 
						|
	PatchWaitChan  chan struct{}
 | 
						|
}
 | 
						|
 | 
						|
// FakeLegacyHandler is a fake implementation of CoreV1Interface.
 | 
						|
type FakeLegacyHandler struct {
 | 
						|
	v1core.CoreV1Interface
 | 
						|
	n *FakeNodeHandler
 | 
						|
}
 | 
						|
 | 
						|
// GetUpdatedNodesCopy returns a slice of Nodes with updates applied.
 | 
						|
func (m *FakeNodeHandler) GetUpdatedNodesCopy() []*v1.Node {
 | 
						|
	m.lock.Lock()
 | 
						|
	defer m.lock.Unlock()
 | 
						|
	updatedNodesCopy := make([]*v1.Node, len(m.UpdatedNodes), len(m.UpdatedNodes))
 | 
						|
	for i, ptr := range m.UpdatedNodes {
 | 
						|
		updatedNodesCopy[i] = ptr
 | 
						|
	}
 | 
						|
	return updatedNodesCopy
 | 
						|
}
 | 
						|
 | 
						|
// Core returns fake CoreInterface.
 | 
						|
func (m *FakeNodeHandler) Core() v1core.CoreV1Interface {
 | 
						|
	return &FakeLegacyHandler{m.Clientset.CoreV1(), m}
 | 
						|
}
 | 
						|
 | 
						|
// CoreV1 returns fake CoreV1Interface
 | 
						|
func (m *FakeNodeHandler) CoreV1() v1core.CoreV1Interface {
 | 
						|
	return &FakeLegacyHandler{m.Clientset.CoreV1(), m}
 | 
						|
}
 | 
						|
 | 
						|
// Nodes return fake NodeInterfaces.
 | 
						|
func (m *FakeLegacyHandler) Nodes() v1core.NodeInterface {
 | 
						|
	return m.n
 | 
						|
}
 | 
						|
 | 
						|
// Create adds a new Node to the fake store.
 | 
						|
func (m *FakeNodeHandler) Create(node *v1.Node) (*v1.Node, error) {
 | 
						|
	m.lock.Lock()
 | 
						|
	defer func() {
 | 
						|
		m.RequestCount++
 | 
						|
		m.lock.Unlock()
 | 
						|
	}()
 | 
						|
	for _, n := range m.Existing {
 | 
						|
		if n.Name == node.Name {
 | 
						|
			return nil, apierrors.NewAlreadyExists(api.Resource("nodes"), node.Name)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if m.CreateHook == nil || m.CreateHook(m, node) {
 | 
						|
		nodeCopy := *node
 | 
						|
		m.CreatedNodes = append(m.CreatedNodes, &nodeCopy)
 | 
						|
		return node, nil
 | 
						|
	}
 | 
						|
	return nil, errors.New("create error")
 | 
						|
}
 | 
						|
 | 
						|
// Get returns a Node from the fake store.
 | 
						|
func (m *FakeNodeHandler) Get(name string, opts metav1.GetOptions) (*v1.Node, error) {
 | 
						|
	m.lock.Lock()
 | 
						|
	defer func() {
 | 
						|
		m.RequestCount++
 | 
						|
		m.lock.Unlock()
 | 
						|
	}()
 | 
						|
	for i := range m.UpdatedNodes {
 | 
						|
		if m.UpdatedNodes[i].Name == name {
 | 
						|
			nodeCopy := *m.UpdatedNodes[i]
 | 
						|
			return &nodeCopy, nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	for i := range m.Existing {
 | 
						|
		if m.Existing[i].Name == name {
 | 
						|
			nodeCopy := *m.Existing[i]
 | 
						|
			return &nodeCopy, nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return nil, nil
 | 
						|
}
 | 
						|
 | 
						|
// List returns a list of Nodes from the fake store.
 | 
						|
func (m *FakeNodeHandler) List(opts metav1.ListOptions) (*v1.NodeList, error) {
 | 
						|
	m.lock.Lock()
 | 
						|
	defer func() {
 | 
						|
		m.RequestCount++
 | 
						|
		m.lock.Unlock()
 | 
						|
	}()
 | 
						|
	var nodes []*v1.Node
 | 
						|
	for i := 0; i < len(m.UpdatedNodes); i++ {
 | 
						|
		if !contains(m.UpdatedNodes[i], m.DeletedNodes) {
 | 
						|
			nodes = append(nodes, m.UpdatedNodes[i])
 | 
						|
		}
 | 
						|
	}
 | 
						|
	for i := 0; i < len(m.Existing); i++ {
 | 
						|
		if !contains(m.Existing[i], m.DeletedNodes) && !contains(m.Existing[i], nodes) {
 | 
						|
			nodes = append(nodes, m.Existing[i])
 | 
						|
		}
 | 
						|
	}
 | 
						|
	for i := 0; i < len(m.CreatedNodes); i++ {
 | 
						|
		if !contains(m.CreatedNodes[i], m.DeletedNodes) && !contains(m.CreatedNodes[i], nodes) {
 | 
						|
			nodes = append(nodes, m.CreatedNodes[i])
 | 
						|
		}
 | 
						|
	}
 | 
						|
	nodeList := &v1.NodeList{}
 | 
						|
	for _, node := range nodes {
 | 
						|
		nodeList.Items = append(nodeList.Items, *node)
 | 
						|
	}
 | 
						|
	return nodeList, nil
 | 
						|
}
 | 
						|
 | 
						|
// Delete deletes a Node from the fake store.
 | 
						|
func (m *FakeNodeHandler) Delete(id string, opt *metav1.DeleteOptions) error {
 | 
						|
	m.lock.Lock()
 | 
						|
	defer func() {
 | 
						|
		m.RequestCount++
 | 
						|
		if m.DeleteWaitChan != nil {
 | 
						|
			m.DeleteWaitChan <- struct{}{}
 | 
						|
		}
 | 
						|
		m.lock.Unlock()
 | 
						|
	}()
 | 
						|
	m.DeletedNodes = append(m.DeletedNodes, NewNode(id))
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// DeleteCollection deletes a collection of Nodes from the fake store.
 | 
						|
func (m *FakeNodeHandler) DeleteCollection(opt *metav1.DeleteOptions, listOpts metav1.ListOptions) error {
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// Update updates a Node in the fake store.
 | 
						|
func (m *FakeNodeHandler) Update(node *v1.Node) (*v1.Node, error) {
 | 
						|
	m.lock.Lock()
 | 
						|
	defer func() {
 | 
						|
		m.RequestCount++
 | 
						|
		m.lock.Unlock()
 | 
						|
	}()
 | 
						|
 | 
						|
	nodeCopy := *node
 | 
						|
	for i, updateNode := range m.UpdatedNodes {
 | 
						|
		if updateNode.Name == nodeCopy.Name {
 | 
						|
			m.UpdatedNodes[i] = &nodeCopy
 | 
						|
			return node, nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	m.UpdatedNodes = append(m.UpdatedNodes, &nodeCopy)
 | 
						|
	return node, nil
 | 
						|
}
 | 
						|
 | 
						|
// UpdateStatus updates a status of a Node in the fake store.
 | 
						|
func (m *FakeNodeHandler) UpdateStatus(node *v1.Node) (*v1.Node, error) {
 | 
						|
	m.lock.Lock()
 | 
						|
	defer func() {
 | 
						|
		m.RequestCount++
 | 
						|
		m.lock.Unlock()
 | 
						|
	}()
 | 
						|
 | 
						|
	var origNodeCopy v1.Node
 | 
						|
	found := false
 | 
						|
	for i := range m.Existing {
 | 
						|
		if m.Existing[i].Name == node.Name {
 | 
						|
			origNodeCopy = *m.Existing[i]
 | 
						|
			found = true
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
	updatedNodeIndex := -1
 | 
						|
	for i := range m.UpdatedNodes {
 | 
						|
		if m.UpdatedNodes[i].Name == node.Name {
 | 
						|
			origNodeCopy = *m.UpdatedNodes[i]
 | 
						|
			updatedNodeIndex = i
 | 
						|
			found = true
 | 
						|
			break
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if !found {
 | 
						|
		return nil, fmt.Errorf("not found node %v", node)
 | 
						|
	}
 | 
						|
 | 
						|
	origNodeCopy.Status = node.Status
 | 
						|
	if updatedNodeIndex < 0 {
 | 
						|
		m.UpdatedNodes = append(m.UpdatedNodes, &origNodeCopy)
 | 
						|
	} else {
 | 
						|
		m.UpdatedNodes[updatedNodeIndex] = &origNodeCopy
 | 
						|
	}
 | 
						|
 | 
						|
	nodeCopy := *node
 | 
						|
	m.UpdatedNodeStatuses = append(m.UpdatedNodeStatuses, &nodeCopy)
 | 
						|
	return node, nil
 | 
						|
}
 | 
						|
 | 
						|
// PatchStatus patches a status of a Node in the fake store.
 | 
						|
func (m *FakeNodeHandler) PatchStatus(nodeName string, data []byte) (*v1.Node, error) {
 | 
						|
	m.RequestCount++
 | 
						|
	return m.Patch(nodeName, types.StrategicMergePatchType, data, "status")
 | 
						|
}
 | 
						|
 | 
						|
// Watch watches Nodes in a fake store.
 | 
						|
func (m *FakeNodeHandler) Watch(opts metav1.ListOptions) (watch.Interface, error) {
 | 
						|
	return watch.NewFake(), nil
 | 
						|
}
 | 
						|
 | 
						|
// Patch patches a Node in the fake store.
 | 
						|
func (m *FakeNodeHandler) Patch(name string, pt types.PatchType, data []byte, subresources ...string) (*v1.Node, error) {
 | 
						|
	m.lock.Lock()
 | 
						|
	defer func() {
 | 
						|
		m.RequestCount++
 | 
						|
		if m.PatchWaitChan != nil {
 | 
						|
			m.PatchWaitChan <- struct{}{}
 | 
						|
		}
 | 
						|
		m.lock.Unlock()
 | 
						|
	}()
 | 
						|
	var nodeCopy v1.Node
 | 
						|
	for i := range m.Existing {
 | 
						|
		if m.Existing[i].Name == name {
 | 
						|
			nodeCopy = *m.Existing[i]
 | 
						|
		}
 | 
						|
	}
 | 
						|
	updatedNodeIndex := -1
 | 
						|
	for i := range m.UpdatedNodes {
 | 
						|
		if m.UpdatedNodes[i].Name == name {
 | 
						|
			nodeCopy = *m.UpdatedNodes[i]
 | 
						|
			updatedNodeIndex = i
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	originalObjJS, err := json.Marshal(nodeCopy)
 | 
						|
	if err != nil {
 | 
						|
		klog.Errorf("Failed to marshal %v", nodeCopy)
 | 
						|
		return nil, nil
 | 
						|
	}
 | 
						|
	var originalNode v1.Node
 | 
						|
	if err = json.Unmarshal(originalObjJS, &originalNode); err != nil {
 | 
						|
		klog.Errorf("Failed to unmarshal original object: %v", err)
 | 
						|
		return nil, nil
 | 
						|
	}
 | 
						|
 | 
						|
	var patchedObjJS []byte
 | 
						|
	switch pt {
 | 
						|
	case types.JSONPatchType:
 | 
						|
		patchObj, err := jsonpatch.DecodePatch(data)
 | 
						|
		if err != nil {
 | 
						|
			klog.Error(err.Error())
 | 
						|
			return nil, nil
 | 
						|
		}
 | 
						|
		if patchedObjJS, err = patchObj.Apply(originalObjJS); err != nil {
 | 
						|
			klog.Error(err.Error())
 | 
						|
			return nil, nil
 | 
						|
		}
 | 
						|
	case types.MergePatchType:
 | 
						|
		if patchedObjJS, err = jsonpatch.MergePatch(originalObjJS, data); err != nil {
 | 
						|
			klog.Error(err.Error())
 | 
						|
			return nil, nil
 | 
						|
		}
 | 
						|
	case types.StrategicMergePatchType:
 | 
						|
		if patchedObjJS, err = strategicpatch.StrategicMergePatch(originalObjJS, data, originalNode); err != nil {
 | 
						|
			klog.Error(err.Error())
 | 
						|
			return nil, nil
 | 
						|
		}
 | 
						|
	default:
 | 
						|
		klog.Errorf("unknown Content-Type header for patch: %v", pt)
 | 
						|
		return nil, nil
 | 
						|
	}
 | 
						|
 | 
						|
	var updatedNode v1.Node
 | 
						|
	if err = json.Unmarshal(patchedObjJS, &updatedNode); err != nil {
 | 
						|
		klog.Errorf("Failed to unmarshal patched object: %v", err)
 | 
						|
		return nil, nil
 | 
						|
	}
 | 
						|
 | 
						|
	if updatedNodeIndex < 0 {
 | 
						|
		m.UpdatedNodes = append(m.UpdatedNodes, &updatedNode)
 | 
						|
	} else {
 | 
						|
		m.UpdatedNodes[updatedNodeIndex] = &updatedNode
 | 
						|
	}
 | 
						|
 | 
						|
	return &updatedNode, nil
 | 
						|
}
 | 
						|
 | 
						|
// FakeRecorder is used as a fake during testing.
 | 
						|
type FakeRecorder struct {
 | 
						|
	sync.Mutex
 | 
						|
	source v1.EventSource
 | 
						|
	Events []*v1.Event
 | 
						|
	clock  clock.Clock
 | 
						|
}
 | 
						|
 | 
						|
// Event emits a fake event to the fake recorder
 | 
						|
func (f *FakeRecorder) Event(obj runtime.Object, eventtype, reason, message string) {
 | 
						|
	f.generateEvent(obj, metav1.Now(), eventtype, reason, message)
 | 
						|
}
 | 
						|
 | 
						|
// Eventf emits a fake formatted event to the fake recorder
 | 
						|
func (f *FakeRecorder) Eventf(obj runtime.Object, eventtype, reason, messageFmt string, args ...interface{}) {
 | 
						|
	f.Event(obj, eventtype, reason, fmt.Sprintf(messageFmt, args...))
 | 
						|
}
 | 
						|
 | 
						|
// AnnotatedEventf emits a fake formatted event to the fake recorder
 | 
						|
func (f *FakeRecorder) AnnotatedEventf(obj runtime.Object, annotations map[string]string, eventtype, reason, messageFmt string, args ...interface{}) {
 | 
						|
	f.Eventf(obj, eventtype, reason, messageFmt, args...)
 | 
						|
}
 | 
						|
 | 
						|
func (f *FakeRecorder) generateEvent(obj runtime.Object, timestamp metav1.Time, eventtype, reason, message string) {
 | 
						|
	f.Lock()
 | 
						|
	defer f.Unlock()
 | 
						|
	ref, err := ref.GetReference(legacyscheme.Scheme, obj)
 | 
						|
	if err != nil {
 | 
						|
		klog.Errorf("Encountered error while getting reference: %v", err)
 | 
						|
		return
 | 
						|
	}
 | 
						|
	event := f.makeEvent(ref, eventtype, reason, message)
 | 
						|
	event.Source = f.source
 | 
						|
	if f.Events != nil {
 | 
						|
		f.Events = append(f.Events, event)
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (f *FakeRecorder) makeEvent(ref *v1.ObjectReference, eventtype, reason, message string) *v1.Event {
 | 
						|
	t := metav1.Time{Time: f.clock.Now()}
 | 
						|
	namespace := ref.Namespace
 | 
						|
	if namespace == "" {
 | 
						|
		namespace = metav1.NamespaceDefault
 | 
						|
	}
 | 
						|
 | 
						|
	clientref := v1.ObjectReference{
 | 
						|
		Kind:            ref.Kind,
 | 
						|
		Namespace:       ref.Namespace,
 | 
						|
		Name:            ref.Name,
 | 
						|
		UID:             ref.UID,
 | 
						|
		APIVersion:      ref.APIVersion,
 | 
						|
		ResourceVersion: ref.ResourceVersion,
 | 
						|
		FieldPath:       ref.FieldPath,
 | 
						|
	}
 | 
						|
 | 
						|
	return &v1.Event{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Name:      fmt.Sprintf("%v.%x", ref.Name, t.UnixNano()),
 | 
						|
			Namespace: namespace,
 | 
						|
		},
 | 
						|
		InvolvedObject: clientref,
 | 
						|
		Reason:         reason,
 | 
						|
		Message:        message,
 | 
						|
		FirstTimestamp: t,
 | 
						|
		LastTimestamp:  t,
 | 
						|
		Count:          1,
 | 
						|
		Type:           eventtype,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// NewFakeRecorder returns a pointer to a newly constructed FakeRecorder.
 | 
						|
func NewFakeRecorder() *FakeRecorder {
 | 
						|
	return &FakeRecorder{
 | 
						|
		source: v1.EventSource{Component: "nodeControllerTest"},
 | 
						|
		Events: []*v1.Event{},
 | 
						|
		clock:  clock.NewFakeClock(time.Now()),
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// NewNode is a helper function for creating Nodes for testing.
 | 
						|
func NewNode(name string) *v1.Node {
 | 
						|
	return &v1.Node{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{Name: name},
 | 
						|
		Status: v1.NodeStatus{
 | 
						|
			Capacity: v1.ResourceList{
 | 
						|
				v1.ResourceName(v1.ResourceCPU):    resource.MustParse("10"),
 | 
						|
				v1.ResourceName(v1.ResourceMemory): resource.MustParse("10G"),
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// NewPod is a helper function for creating Pods for testing.
 | 
						|
func NewPod(name, host string) *v1.Pod {
 | 
						|
	pod := &v1.Pod{
 | 
						|
		ObjectMeta: metav1.ObjectMeta{
 | 
						|
			Namespace: "default",
 | 
						|
			Name:      name,
 | 
						|
		},
 | 
						|
		Spec: v1.PodSpec{
 | 
						|
			NodeName: host,
 | 
						|
		},
 | 
						|
		Status: v1.PodStatus{
 | 
						|
			Conditions: []v1.PodCondition{
 | 
						|
				{
 | 
						|
					Type:   v1.PodReady,
 | 
						|
					Status: v1.ConditionTrue,
 | 
						|
				},
 | 
						|
			},
 | 
						|
		},
 | 
						|
	}
 | 
						|
 | 
						|
	return pod
 | 
						|
}
 | 
						|
 | 
						|
func contains(node *v1.Node, nodes []*v1.Node) bool {
 | 
						|
	for i := 0; i < len(nodes); i++ {
 | 
						|
		if node.Name == nodes[i].Name {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
// GetZones returns list of zones for all Nodes stored in FakeNodeHandler
 | 
						|
func GetZones(nodeHandler *FakeNodeHandler) []string {
 | 
						|
	nodes, _ := nodeHandler.List(metav1.ListOptions{})
 | 
						|
	zones := sets.NewString()
 | 
						|
	for _, node := range nodes.Items {
 | 
						|
		zones.Insert(utilnode.GetZoneKey(&node))
 | 
						|
	}
 | 
						|
	return zones.List()
 | 
						|
}
 | 
						|
 | 
						|
// CreateZoneID returns a single zoneID for a given region and zone.
 | 
						|
func CreateZoneID(region, zone string) string {
 | 
						|
	return region + ":\x00:" + zone
 | 
						|
}
 | 
						|
 | 
						|
// GetKey is a helper function used by controllers unit tests to get the
 | 
						|
// key for a given kubernetes resource.
 | 
						|
func GetKey(obj interface{}, t *testing.T) string {
 | 
						|
	tombstone, ok := obj.(cache.DeletedFinalStateUnknown)
 | 
						|
	if ok {
 | 
						|
		// if tombstone , try getting the value from tombstone.Obj
 | 
						|
		obj = tombstone.Obj
 | 
						|
	}
 | 
						|
	val := reflect.ValueOf(obj).Elem()
 | 
						|
	name := val.FieldByName("Name").String()
 | 
						|
	kind := val.FieldByName("Kind").String()
 | 
						|
	// Note kind is not always set in the tests, so ignoring that for now
 | 
						|
	if len(name) == 0 || len(kind) == 0 {
 | 
						|
		t.Errorf("Unexpected object %v", obj)
 | 
						|
	}
 | 
						|
 | 
						|
	key, err := keyFunc(obj)
 | 
						|
	if err != nil {
 | 
						|
		t.Errorf("Unexpected error getting key for %v %v: %v", kind, name, err)
 | 
						|
		return ""
 | 
						|
	}
 | 
						|
	return key
 | 
						|
}
 |