From b49a1ce1a41f492ce1dde6b390321ff2024d43f8 Mon Sep 17 00:00:00 2001 From: Michelle Au Date: Mon, 13 Nov 2017 21:18:00 -0800 Subject: [PATCH] Cache for pod bindings --- .../scheduler_binder_cache.go | 87 ++++++++++++++ .../scheduler_binder_cache_test.go | 112 ++++++++++++++++++ 2 files changed, 199 insertions(+) create mode 100644 pkg/controller/volume/persistentvolume/scheduler_binder_cache.go create mode 100644 pkg/controller/volume/persistentvolume/scheduler_binder_cache_test.go diff --git a/pkg/controller/volume/persistentvolume/scheduler_binder_cache.go b/pkg/controller/volume/persistentvolume/scheduler_binder_cache.go new file mode 100644 index 00000000000..8a0a7796085 --- /dev/null +++ b/pkg/controller/volume/persistentvolume/scheduler_binder_cache.go @@ -0,0 +1,87 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package persistentvolume + +import ( + "sync" + + "k8s.io/api/core/v1" +) + +// podBindingCache stores PV binding decisions per pod per node. +// Pod entries are removed when the Pod is deleted or updated to +// no longer be schedulable. +type PodBindingCache interface { + // UpdateBindings will update the cache with the given bindings for the + // pod and node. + UpdateBindings(pod *v1.Pod, node string, bindings []*bindingInfo) + + // DeleteBindings will remove all cached bindings for the given pod. + DeleteBindings(pod *v1.Pod) + + // GetBindings will return the cached bindings for the given pod and node. + GetBindings(pod *v1.Pod, node string) []*bindingInfo +} + +type podBindingCache struct { + mutex sync.Mutex + + // Key = pod name + // Value = nodeBindings + bindings map[string]nodeBindings +} + +// Key = nodeName +// Value = array of bindingInfo +type nodeBindings map[string][]*bindingInfo + +func NewPodBindingCache() PodBindingCache { + return &podBindingCache{bindings: map[string]nodeBindings{}} +} + +func (c *podBindingCache) DeleteBindings(pod *v1.Pod) { + c.mutex.Lock() + defer c.mutex.Unlock() + + podName := getPodName(pod) + delete(c.bindings, podName) +} + +func (c *podBindingCache) UpdateBindings(pod *v1.Pod, node string, bindings []*bindingInfo) { + c.mutex.Lock() + defer c.mutex.Unlock() + + podName := getPodName(pod) + nodeBinding, ok := c.bindings[podName] + if !ok { + nodeBinding = nodeBindings{} + c.bindings[podName] = nodeBinding + } + nodeBinding[node] = bindings +} + +func (c *podBindingCache) GetBindings(pod *v1.Pod, node string) []*bindingInfo { + c.mutex.Lock() + defer c.mutex.Unlock() + + podName := getPodName(pod) + nodeBindings, ok := c.bindings[podName] + if !ok { + return nil + } + return nodeBindings[node] +} diff --git a/pkg/controller/volume/persistentvolume/scheduler_binder_cache_test.go b/pkg/controller/volume/persistentvolume/scheduler_binder_cache_test.go new file mode 100644 index 00000000000..c73cea970d0 --- /dev/null +++ b/pkg/controller/volume/persistentvolume/scheduler_binder_cache_test.go @@ -0,0 +1,112 @@ +/* +Copyright 2017 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package persistentvolume + +import ( + "reflect" + "testing" + + "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +func TestUpdateGetBindings(t *testing.T) { + scenarios := map[string]struct { + updateBindings []*bindingInfo + updatePod string + updateNode string + + getBindings []*bindingInfo + getPod string + getNode string + }{ + "no-pod": { + getPod: "pod1", + getNode: "node1", + }, + "no-node": { + updatePod: "pod1", + updateNode: "node1", + updateBindings: []*bindingInfo{}, + getPod: "pod1", + getNode: "node2", + }, + "binding-exists": { + updatePod: "pod1", + updateNode: "node1", + updateBindings: []*bindingInfo{{pvc: &v1.PersistentVolumeClaim{ObjectMeta: metav1.ObjectMeta{Name: "pvc1"}}}}, + getPod: "pod1", + getNode: "node1", + getBindings: []*bindingInfo{{pvc: &v1.PersistentVolumeClaim{ObjectMeta: metav1.ObjectMeta{Name: "pvc1"}}}}, + }, + } + + for name, scenario := range scenarios { + cache := NewPodBindingCache() + + // Perform updates + updatePod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: scenario.updatePod, Namespace: "ns"}} + cache.UpdateBindings(updatePod, scenario.updateNode, scenario.updateBindings) + + // Verify updated bindings + bindings := cache.GetBindings(updatePod, scenario.updateNode) + if !reflect.DeepEqual(bindings, scenario.updateBindings) { + t.Errorf("Test %v failed: returned bindings after update different. Got %+v, expected %+v", name, bindings, scenario.updateBindings) + } + + // Get bindings + getPod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: scenario.getPod, Namespace: "ns"}} + bindings = cache.GetBindings(getPod, scenario.getNode) + if !reflect.DeepEqual(bindings, scenario.getBindings) { + t.Errorf("Test %v failed: unexpected bindings returned. Got %+v, expected %+v", name, bindings, scenario.updateBindings) + } + } +} + +func TestDeleteBindings(t *testing.T) { + initialBindings := []*bindingInfo{{pvc: &v1.PersistentVolumeClaim{ObjectMeta: metav1.ObjectMeta{Name: "pvc1"}}}} + cache := NewPodBindingCache() + + pod := &v1.Pod{ObjectMeta: metav1.ObjectMeta{Name: "pod1", Namespace: "ns"}} + + // Get nil bindings + bindings := cache.GetBindings(pod, "node1") + if bindings != nil { + t.Errorf("Test failed: expected inital nil bindings, got %+v", bindings) + } + + // Delete nothing + cache.DeleteBindings(pod) + + // Perform updates + cache.UpdateBindings(pod, "node1", initialBindings) + + // Get bindings + bindings = cache.GetBindings(pod, "node1") + if !reflect.DeepEqual(bindings, initialBindings) { + t.Errorf("Test failed: expected bindings %+v, got %+v", initialBindings, bindings) + } + + // Delete + cache.DeleteBindings(pod) + + // Get bindings + bindings = cache.GetBindings(pod, "node1") + if bindings != nil { + t.Errorf("Test failed: expected nil bindings, got %+v", bindings) + } +}