diff --git a/pkg/kubelet/cm/BUILD b/pkg/kubelet/cm/BUILD index 804c455b15..c2e456aa10 100644 --- a/pkg/kubelet/cm/BUILD +++ b/pkg/kubelet/cm/BUILD @@ -166,6 +166,7 @@ filegroup( "//pkg/kubelet/cm/cpumanager:all-srcs", "//pkg/kubelet/cm/cpuset:all-srcs", "//pkg/kubelet/cm/devicemanager:all-srcs", + "//pkg/kubelet/cm/topologymanager/socketmask:all-srcs", "//pkg/kubelet/cm/util:all-srcs", ], tags = ["automanaged"], diff --git a/pkg/kubelet/cm/topologymanager/socketmask/BUILD b/pkg/kubelet/cm/topologymanager/socketmask/BUILD new file mode 100644 index 0000000000..25bd381e86 --- /dev/null +++ b/pkg/kubelet/cm/topologymanager/socketmask/BUILD @@ -0,0 +1,28 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = ["socketmask.go"], + importpath = "k8s.io/kubernetes/pkg/kubelet/cm/topologymanager/socketmask", + visibility = ["//visibility:public"], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) + +go_test( + name = "go_default_test", + srcs = ["socketmask_test.go"], + embed = [":go_default_library"], +) diff --git a/pkg/kubelet/cm/topologymanager/socketmask/socketmask.go b/pkg/kubelet/cm/topologymanager/socketmask/socketmask.go new file mode 100644 index 0000000000..8e74ce0291 --- /dev/null +++ b/pkg/kubelet/cm/topologymanager/socketmask/socketmask.go @@ -0,0 +1,152 @@ +/* +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 socketmask + +import ( + "fmt" +) + +//SocketMask interface allows hint providers to create SocketMasks for TopologyHints +type SocketMask interface { + Add(sockets ...int) error + Remove(sockets ...int) error + And(masks ...SocketMask) + Or(masks ...SocketMask) + Clear() + Fill() + IsEqual(mask SocketMask) bool + IsEmpty() bool + IsSet(socket int) bool + String() string + Count() int + GetSockets() []int +} + +type socketMask uint64 + +//NewSocketMask creates a new SocketMask +func NewSocketMask(sockets ...int) (SocketMask, error) { + s := socketMask(0) + err := (&s).Add(sockets...) + if err != nil { + return nil, err + } + return &s, nil +} + +//Add adds the sockets with topology affinity to the SocketMask +func (s *socketMask) Add(sockets ...int) error { + mask := *s + for _, i := range sockets { + if i < 0 || i >= 64 { + return fmt.Errorf("socket number must be in range 0-63") + } + mask |= 1 << uint64(i) + } + *s = mask + return nil +} + +//Remove removes specified sockets from SocketMask +func (s *socketMask) Remove(sockets ...int) error { + mask := *s + for _, i := range sockets { + if i < 0 || i >= 64 { + return fmt.Errorf("socket number must be in range 0-63") + } + mask &^= 1 << uint64(i) + } + *s = mask + return nil +} + +//And performs and operation on all bits in masks +func (s *socketMask) And(masks ...SocketMask) { + for _, m := range masks { + *s &= *m.(*socketMask) + } +} + +//Or performs or operation on all bits in masks +func (s *socketMask) Or(masks ...SocketMask) { + for _, m := range masks { + *s |= *m.(*socketMask) + } +} + +//Clear resets all bits in mask to zero +func (s *socketMask) Clear() { + *s = 0 +} + +//Fill sets all bits in mask to one +func (s *socketMask) Fill() { + *s = socketMask(^uint64(0)) +} + +//IsEmpty checks mask to see if all bits are zero +func (s *socketMask) IsEmpty() bool { + return *s == 0 +} + +//IsSet checks socket in mask to see if bit is set to one +func (s *socketMask) IsSet(socket int) bool { + if socket < 0 || socket >= 64 { + return false + } + return (*s & (1 << uint64(socket))) > 0 +} + +//IsEqual checks if masks are equal +func (s *socketMask) IsEqual(mask SocketMask) bool { + return *s == *mask.(*socketMask) +} + +//String converts mask to string +func (s *socketMask) String() string { + str := "" + for i := uint64(0); i < 64; i++ { + if (*s & (1 << i)) > 0 { + str += "1" + } else { + str += "0" + } + } + return str +} + +//Count counts number of bits in mask set to one +func (s *socketMask) Count() int { + count := 0 + for i := uint64(0); i < 64; i++ { + if (*s & (1 << i)) > 0 { + count++ + } + } + return count +} + +//GetSockets returns each socket number with bits set to one +func (s *socketMask) GetSockets() []int { + var sockets []int + for i := uint64(0); i < 64; i++ { + if (*s & (1 << i)) > 0 { + sockets = append(sockets, int(i)) + } + } + return sockets +} diff --git a/pkg/kubelet/cm/topologymanager/socketmask/socketmask_test.go b/pkg/kubelet/cm/topologymanager/socketmask/socketmask_test.go new file mode 100644 index 0000000000..01df68999c --- /dev/null +++ b/pkg/kubelet/cm/topologymanager/socketmask/socketmask_test.go @@ -0,0 +1,290 @@ +/* +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 socketmask + +import ( + "reflect" + "testing" +) + +func TestNewSocketMask(t *testing.T) { + tcases := []struct { + name string + socket int + expectedMask string + }{ + { + name: "New SocketMask with socket 0 set", + socket: int(0), + expectedMask: "1000000000000000000000000000000000000000000000000000000000000000", + }, + } + for _, tc := range tcases { + sm, _ := NewSocketMask(0) + if sm.String() != tc.expectedMask { + t.Errorf("Expected mask to be %v, got %v", tc.expectedMask, sm) + } + } +} + +func TestAdd(t *testing.T) { + tcases := []struct { + name string + firstSocket int + secondSocket int + expectedMask string + }{ + { + name: "Reset bit 1 SocketMask to 0", + firstSocket: 0, + secondSocket: 1, + expectedMask: "1100000000000000000000000000000000000000000000000000000000000000", + }, + } + for _, tc := range tcases { + mask, _ := NewSocketMask() + mask.Add(tc.firstSocket, tc.secondSocket) + if mask.String() != tc.expectedMask { + t.Errorf("Expected mask to be %v, got %v", tc.expectedMask, mask) + } + } +} + +func TestRemove(t *testing.T) { + tcases := []struct { + name string + firstSocketSet int + secondSocketSet int + firstSocketRemove int + expectedMask string + }{ + { + name: "Reset bit 1 SocketMask to 0", + firstSocketSet: 0, + secondSocketSet: 1, + firstSocketRemove: 0, + expectedMask: "0100000000000000000000000000000000000000000000000000000000000000", + }, + } + for _, tc := range tcases { + mask, _ := NewSocketMask(tc.firstSocketSet, tc.secondSocketSet) + mask.Remove(tc.firstSocketRemove) + if mask.String() != tc.expectedMask { + t.Errorf("Expected mask to be %v, got %v", tc.expectedMask, mask) + } + } +} + +func TestAnd(t *testing.T) { + tcases := []struct { + name string + firstMaskBit int + secondMaskBit int + andMask string + }{ + { + name: "And socket masks", + firstMaskBit: 0, + secondMaskBit: 0, + andMask: "1000000000000000000000000000000000000000000000000000000000000000", + }, + } + for _, tc := range tcases { + firstMask, _ := NewSocketMask(tc.firstMaskBit) + secondMask, _ := NewSocketMask(tc.secondMaskBit) + firstMask.And(secondMask) + if firstMask.String() != string(tc.andMask) { + t.Errorf("Expected mask to be %v, got %v", tc.andMask, firstMask) + } + } +} + +func TestOr(t *testing.T) { + tcases := []struct { + name string + firstMaskBit int + secondMaskBit int + orMask string + }{ + { + name: "Or socket masks", + firstMaskBit: int(0), + secondMaskBit: int(1), + orMask: "1100000000000000000000000000000000000000000000000000000000000000", + }, + } + for _, tc := range tcases { + firstMask, _ := NewSocketMask(tc.firstMaskBit) + secondMask, _ := NewSocketMask(tc.secondMaskBit) + firstMask.Or(secondMask) + if firstMask.String() != string(tc.orMask) { + t.Errorf("Expected mask to be %v, got %v", tc.orMask, firstMask) + } + } +} + +func TestClear(t *testing.T) { + tcases := []struct { + name string + firstBit int + secondBit int + clearedMask string + }{ + { + name: "Clear socket masks", + firstBit: int(0), + secondBit: int(1), + clearedMask: "0000000000000000000000000000000000000000000000000000000000000000", + }, + } + for _, tc := range tcases { + mask, _ := NewSocketMask(tc.firstBit, tc.secondBit) + mask.Clear() + if mask.String() != string(tc.clearedMask) { + t.Errorf("Expected mask to be %v, got %v", tc.clearedMask, mask) + } + } +} + +func TestFill(t *testing.T) { + tcases := []struct { + name string + filledMask string + }{ + { + name: "Fill socket masks", + filledMask: "1111111111111111111111111111111111111111111111111111111111111111", + }, + } + for _, tc := range tcases { + mask, _ := NewSocketMask() + mask.Fill() + if mask.String() != string(tc.filledMask) { + t.Errorf("Expected mask to be %v, got %v", tc.filledMask, mask) + } + } +} + +func TestIsEmpty(t *testing.T) { + tcases := []struct { + name string + maskBit int + expectedEmpty bool + }{ + { + name: "Check if mask is empty", + maskBit: int(0), + expectedEmpty: false, + }, + } + for _, tc := range tcases { + mask, _ := NewSocketMask(tc.maskBit) + empty := mask.IsEmpty() + if empty { + t.Errorf("Expected value to be %v, got %v", tc.expectedEmpty, empty) + } + } +} + +func TestIsSet(t *testing.T) { + tcases := []struct { + name string + maskBit int + expectedSet bool + }{ + { + name: "Check if mask bit is set", + maskBit: int(0), + expectedSet: true, + }, + } + for _, tc := range tcases { + mask, _ := NewSocketMask(tc.maskBit) + set := mask.IsSet(tc.maskBit) + if !set { + t.Errorf("Expected value to be %v, got %v", tc.expectedSet, set) + } + } +} + +func TestIsEqual(t *testing.T) { + tcases := []struct { + name string + firstMaskBit int + secondMaskBit int + isEqual bool + }{ + { + name: "And socket masks", + firstMaskBit: int(0), + secondMaskBit: int(0), + isEqual: true, + }, + } + for _, tc := range tcases { + firstMask, _ := NewSocketMask(tc.firstMaskBit) + secondMask, _ := NewSocketMask(tc.secondMaskBit) + isEqual := firstMask.IsEqual(secondMask) + if !isEqual { + t.Errorf("Expected mask to be %v, got %v", tc.isEqual, isEqual) + } + } +} + +func TestCount(t *testing.T) { + tcases := []struct { + name string + maskBit int + expectedCount int + }{ + { + name: "Count number of bits set in full mask", + maskBit: 0, + expectedCount: 1, + }, + } + for _, tc := range tcases { + mask, _ := NewSocketMask(tc.maskBit) + count := mask.Count() + if count != tc.expectedCount { + t.Errorf("Expected value to be %v, got %v", tc.expectedCount, count) + } + } +} + +func TestGetSockets(t *testing.T) { + tcases := []struct { + name string + firstSocket int + secondSocket int + expectedSockets []int + }{ + { + name: "Get number of each socket which has been set", + firstSocket: 0, + secondSocket: 1, + expectedSockets: []int{0, 1}, + }, + } + for _, tc := range tcases { + mask, _ := NewSocketMask(tc.firstSocket, tc.secondSocket) + sockets := mask.GetSockets() + if !reflect.DeepEqual(sockets, tc.expectedSockets) { + t.Errorf("Expected value to be %v, got %v", tc.expectedSockets, sockets) + } + } +}