diff --git a/pkg/scheduler/framework/plugins/examples/multipoint/multipoint.go b/pkg/scheduler/framework/plugins/examples/multipoint/multipoint.go index d33f221f412..fb52d2b6b27 100644 --- a/pkg/scheduler/framework/plugins/examples/multipoint/multipoint.go +++ b/pkg/scheduler/framework/plugins/examples/multipoint/multipoint.go @@ -37,6 +37,17 @@ func (mc CommunicatingPlugin) Name() string { return Name } +type contextData struct { + data string +} + +func (f *contextData) Clone() framework.ContextData { + copy := &contextData{ + data: f.data, + } + return copy +} + // Reserve is the functions invoked by the framework at "reserve" extension point. func (mc CommunicatingPlugin) Reserve(pc *framework.PluginContext, pod *v1.Pod, nodeName string) *framework.Status { if pod == nil { @@ -44,7 +55,7 @@ func (mc CommunicatingPlugin) Reserve(pc *framework.PluginContext, pod *v1.Pod, } if pod.Name == "my-test-pod" { pc.Lock() - pc.Write(framework.ContextKey(pod.Name), "never bind") + pc.Write(framework.ContextKey(pod.Name), &contextData{data: "never bind"}) pc.Unlock() } return nil @@ -57,8 +68,10 @@ func (mc CommunicatingPlugin) PreBind(pc *framework.PluginContext, pod *v1.Pod, } pc.RLock() defer pc.RUnlock() - if v, e := pc.Read(framework.ContextKey(pod.Name)); e == nil && v == "never bind" { - return framework.NewStatus(framework.Unschedulable, "pod is not permitted") + if v, e := pc.Read(framework.ContextKey(pod.Name)); e == nil { + if value, ok := v.(*contextData); ok && value.data == "never bind" { + return framework.NewStatus(framework.Unschedulable, "pod is not permitted") + } } return nil } diff --git a/pkg/scheduler/framework/v1alpha1/BUILD b/pkg/scheduler/framework/v1alpha1/BUILD index 6554c53dd63..4a3e532426a 100644 --- a/pkg/scheduler/framework/v1alpha1/BUILD +++ b/pkg/scheduler/framework/v1alpha1/BUILD @@ -43,6 +43,7 @@ filegroup( go_test( name = "go_default_test", srcs = [ + "context_test.go", "framework_test.go", "interface_test.go", "registry_test.go", diff --git a/pkg/scheduler/framework/v1alpha1/context.go b/pkg/scheduler/framework/v1alpha1/context.go index 0dfe6a47335..80667dd70cc 100644 --- a/pkg/scheduler/framework/v1alpha1/context.go +++ b/pkg/scheduler/framework/v1alpha1/context.go @@ -27,7 +27,12 @@ const ( ) // ContextData is a generic type for arbitrary data stored in PluginContext. -type ContextData interface{} +type ContextData interface { + // Clone is an interface to make a copy of ContextData. For performance reasons, + // clone should make shallow copies for members (e.g., slices or maps) that are not + // impacted by PreFilter's optional AddPod/RemovePod methods. + Clone() ContextData +} // ContextKey is the type of keys stored in PluginContext. type ContextKey string @@ -48,6 +53,15 @@ func NewPluginContext() *PluginContext { } } +// Clone creates a copy of PluginContext and returns its pointer. +func (c *PluginContext) Clone() *PluginContext { + copy := NewPluginContext() + for k, v := range c.storage { + copy.Write(k, v.Clone()) + } + return copy +} + // Read retrieves data with the given "key" from PluginContext. If the key is not // present an error is returned. // This function is not thread safe. In multi-threaded code, lock should be diff --git a/pkg/scheduler/framework/v1alpha1/context_test.go b/pkg/scheduler/framework/v1alpha1/context_test.go new file mode 100644 index 00000000000..0ddac49ab81 --- /dev/null +++ b/pkg/scheduler/framework/v1alpha1/context_test.go @@ -0,0 +1,66 @@ +/* +Copyright 2019 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 v1alpha1 + +import ( + "testing" +) + +type fakeData struct { + data string +} + +func (f *fakeData) Clone() ContextData { + copy := &fakeData{ + data: f.data, + } + return copy +} + +func TestPluginContextClone(t *testing.T) { + var key ContextKey = "key" + data1 := "value1" + data2 := "value2" + + pc := NewPluginContext() + originalValue := &fakeData{ + data: data1, + } + pc.Write(key, originalValue) + pcCopy := pc.Clone() + + valueCopy, err := pcCopy.Read(key) + if err != nil { + t.Errorf("failed to read copied value: %v", err) + } + if v, ok := valueCopy.(*fakeData); ok && v.data != data1 { + t.Errorf("clone failed, got %q, expected %q", v.data, data1) + } + + originalValue.data = data2 + original, err := pc.Read(key) + if err != nil { + t.Errorf("failed to read original value: %v", err) + } + if v, ok := original.(*fakeData); ok && v.data != data2 { + t.Errorf("original value should change, got %q, expected %q", v.data, data2) + } + + if v, ok := valueCopy.(*fakeData); ok && v.data != data1 { + t.Errorf("cloned copy should not change, got %q, expected %q", v.data, data1) + } +}