123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535 |
- /*
- 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 state
- import (
- "bytes"
- "fmt"
- "io"
- "io/ioutil"
- "os"
- "path"
- "reflect"
- "strings"
- "testing"
- "k8s.io/kubernetes/pkg/kubelet/cm/cpumanager/containermap"
- "k8s.io/kubernetes/pkg/kubelet/cm/cpuset"
- )
- func writeToStateFile(statefile string, content string) {
- ioutil.WriteFile(statefile, []byte(content), 0644)
- }
- // AssertStateEqual marks provided test as failed if provided states differ
- func AssertStateEqual(t *testing.T, sf State, sm State) {
- cpusetSf := sf.GetDefaultCPUSet()
- cpusetSm := sm.GetDefaultCPUSet()
- if !cpusetSf.Equals(cpusetSm) {
- t.Errorf("State CPUSet mismatch. Have %v, want %v", cpusetSf, cpusetSm)
- }
- cpuassignmentSf := sf.GetCPUAssignments()
- cpuassignmentSm := sm.GetCPUAssignments()
- if !reflect.DeepEqual(cpuassignmentSf, cpuassignmentSm) {
- t.Errorf("State CPU assignments mismatch. Have %s, want %s", cpuassignmentSf, cpuassignmentSm)
- }
- }
- func stderrCapture(t *testing.T, f func() State) (bytes.Buffer, State) {
- stderr := os.Stderr
- readBuffer, writeBuffer, err := os.Pipe()
- if err != nil {
- t.Errorf("cannot create pipe: %v", err.Error())
- }
- os.Stderr = writeBuffer
- var outputBuffer bytes.Buffer
- state := f()
- writeBuffer.Close()
- io.Copy(&outputBuffer, readBuffer)
- os.Stderr = stderr
- return outputBuffer, state
- }
- func TestFileStateTryRestore(t *testing.T) {
- testCases := []struct {
- description string
- stateFileContent string
- policyName string
- initialContainers containermap.ContainerMap
- expErr string
- expectedState *stateMemory
- }{
- {
- "Invalid JSON - one byte file",
- "\n",
- "none",
- containermap.ContainerMap{},
- "[cpumanager] state file: unable to restore state from disk (unexpected end of JSON input)",
- &stateMemory{},
- },
- {
- "Invalid JSON - invalid content",
- "{",
- "none",
- containermap.ContainerMap{},
- "[cpumanager] state file: unable to restore state from disk (unexpected end of JSON input)",
- &stateMemory{},
- },
- {
- "Try restore defaultCPUSet only",
- `{"policyName": "none", "defaultCpuSet": "4-6"}`,
- "none",
- containermap.ContainerMap{},
- "",
- &stateMemory{
- assignments: ContainerCPUAssignments{},
- defaultCPUSet: cpuset.NewCPUSet(4, 5, 6),
- },
- },
- {
- "Try restore defaultCPUSet only - invalid name",
- `{"policyName": "none", "defaultCpuSet" "4-6"}`,
- "none",
- containermap.ContainerMap{},
- `[cpumanager] state file: unable to restore state from disk (invalid character '"' after object key)`,
- &stateMemory{},
- },
- {
- "Try restore assignments only",
- `{
- "policyName": "none",
- "entries": {
- "pod": {
- "container1": "4-6",
- "container2": "1-3"
- }
- }
- }`,
- "none",
- containermap.ContainerMap{},
- "",
- &stateMemory{
- assignments: ContainerCPUAssignments{
- "pod": map[string]cpuset.CPUSet{
- "container1": cpuset.NewCPUSet(4, 5, 6),
- "container2": cpuset.NewCPUSet(1, 2, 3),
- },
- },
- defaultCPUSet: cpuset.NewCPUSet(),
- },
- },
- {
- "Try restore invalid policy name",
- `{
- "policyName": "A",
- "defaultCpuSet": "0-7",
- "entries": {}
- }`,
- "B",
- containermap.ContainerMap{},
- `[cpumanager] state file: unable to restore state from disk (policy configured "B" != policy from state file "A")`,
- &stateMemory{},
- },
- {
- "Try restore invalid assignments",
- `{"entries": }`,
- "none",
- containermap.ContainerMap{},
- "[cpumanager] state file: unable to restore state from disk (invalid character '}' looking for beginning of value)",
- &stateMemory{},
- },
- {
- "Try restore valid file",
- `{
- "policyName": "none",
- "defaultCpuSet": "23-24",
- "entries": {
- "pod": {
- "container1": "4-6",
- "container2": "1-3"
- }
- }
- }`,
- "none",
- containermap.ContainerMap{},
- "",
- &stateMemory{
- assignments: ContainerCPUAssignments{
- "pod": map[string]cpuset.CPUSet{
- "container1": cpuset.NewCPUSet(4, 5, 6),
- "container2": cpuset.NewCPUSet(1, 2, 3),
- },
- },
- defaultCPUSet: cpuset.NewCPUSet(23, 24),
- },
- },
- {
- "Try restore un-parsable defaultCPUSet ",
- `{
- "policyName": "none",
- "defaultCpuSet": "2-sd"
- }`,
- "none",
- containermap.ContainerMap{},
- `[cpumanager] state file: unable to restore state from disk (strconv.Atoi: parsing "sd": invalid syntax)`,
- &stateMemory{},
- },
- {
- "Try restore un-parsable assignments",
- `{
- "policyName": "none",
- "defaultCpuSet": "23-24",
- "entries": {
- "pod": {
- "container1": "p-6",
- "container2": "1-3"
- }
- }
- }`,
- "none",
- containermap.ContainerMap{},
- `[cpumanager] state file: unable to restore state from disk (strconv.Atoi: parsing "p": invalid syntax)`,
- &stateMemory{},
- },
- {
- "tryRestoreState creates empty state file",
- "",
- "none",
- containermap.ContainerMap{},
- "",
- &stateMemory{
- assignments: ContainerCPUAssignments{},
- defaultCPUSet: cpuset.NewCPUSet(),
- },
- },
- {
- "Try restore with migration",
- `{
- "policyName": "none",
- "defaultCpuSet": "23-24",
- "entries": {
- "containerID1": "4-6",
- "containerID2": "1-3"
- }
- }`,
- "none",
- func() containermap.ContainerMap {
- cm := containermap.NewContainerMap()
- cm.Add("pod", "container1", "containerID1")
- cm.Add("pod", "container2", "containerID2")
- return cm
- }(),
- "",
- &stateMemory{
- assignments: ContainerCPUAssignments{
- "pod": map[string]cpuset.CPUSet{
- "container1": cpuset.NewCPUSet(4, 5, 6),
- "container2": cpuset.NewCPUSet(1, 2, 3),
- },
- },
- defaultCPUSet: cpuset.NewCPUSet(23, 24),
- },
- },
- }
- for idx, tc := range testCases {
- t.Run(tc.description, func(t *testing.T) {
- sfilePath, err := ioutil.TempFile("/tmp", fmt.Sprintf("cpumanager_state_file_test_%d", idx))
- if err != nil {
- t.Errorf("cannot create temporary file: %q", err.Error())
- }
- // Don't create state file, let tryRestoreState figure out that is should create
- if tc.stateFileContent != "" {
- writeToStateFile(sfilePath.Name(), tc.stateFileContent)
- }
- // Always remove file - regardless of who created
- defer os.Remove(sfilePath.Name())
- logData, fileState := stderrCapture(t, func() State {
- newFileState, _ := NewFileState(sfilePath.Name(), tc.policyName, tc.initialContainers)
- return newFileState
- })
- if tc.expErr != "" {
- if logData.String() != "" {
- if !strings.Contains(logData.String(), tc.expErr) {
- t.Errorf("tryRestoreState() error = %v, wantErr %v", logData.String(), tc.expErr)
- return
- }
- } else {
- t.Errorf("tryRestoreState() error = nil, wantErr %v", tc.expErr)
- return
- }
- }
- if fileState == nil {
- return
- }
- AssertStateEqual(t, fileState, tc.expectedState)
- })
- }
- }
- func TestFileStateTryRestoreError(t *testing.T) {
- testCases := []struct {
- description string
- expErr error
- }{
- {
- " create file error",
- fmt.Errorf("[cpumanager] state file not written"),
- },
- }
- for _, testCase := range testCases {
- t.Run(testCase.description, func(t *testing.T) {
- sfilePath := path.Join("/invalid_path/to_some_dir", "cpumanager_state_file_test")
- _, err := NewFileState(sfilePath, "static", nil)
- if !reflect.DeepEqual(err, testCase.expErr) {
- t.Errorf("unexpected error, expected: %s, got: %s", testCase.expErr, err)
- }
- })
- }
- }
- func TestUpdateStateFile(t *testing.T) {
- testCases := []struct {
- description string
- expErr string
- expectedState *stateMemory
- }{
- {
- "Save empty state",
- "",
- &stateMemory{
- assignments: ContainerCPUAssignments{},
- defaultCPUSet: cpuset.NewCPUSet(),
- },
- },
- {
- "Save defaultCPUSet only",
- "",
- &stateMemory{
- assignments: ContainerCPUAssignments{},
- defaultCPUSet: cpuset.NewCPUSet(1, 6),
- },
- },
- {
- "Save assignments only",
- "",
- &stateMemory{
- assignments: ContainerCPUAssignments{
- "pod": map[string]cpuset.CPUSet{
- "container1": cpuset.NewCPUSet(4, 5, 6),
- "container2": cpuset.NewCPUSet(1, 2, 3),
- },
- },
- defaultCPUSet: cpuset.NewCPUSet(),
- },
- },
- }
- for idx, tc := range testCases {
- t.Run(tc.description, func(t *testing.T) {
- sfilePath, err := ioutil.TempFile("/tmp", fmt.Sprintf("cpumanager_state_file_test_%d", idx))
- defer os.Remove(sfilePath.Name())
- if err != nil {
- t.Errorf("cannot create temporary file: %q", err.Error())
- }
- fileState := stateFile{
- stateFilePath: sfilePath.Name(),
- policyName: "static",
- cache: NewMemoryState(),
- }
- fileState.SetDefaultCPUSet(tc.expectedState.defaultCPUSet)
- fileState.SetCPUAssignments(tc.expectedState.assignments)
- logData, _ := stderrCapture(t, func() State {
- fileState.storeState()
- return &stateFile{}
- })
- errMsg := logData.String()
- if tc.expErr != "" {
- if errMsg != "" {
- if errMsg != tc.expErr {
- t.Errorf("UpdateStateFile() error = %v, wantErr %v", errMsg, tc.expErr)
- return
- }
- } else {
- t.Errorf("UpdateStateFile() error = nil, wantErr %v", tc.expErr)
- return
- }
- } else {
- if errMsg != "" {
- t.Errorf("UpdateStateFile() error = %v, wantErr nil", errMsg)
- return
- }
- }
- newFileState, err := NewFileState(sfilePath.Name(), "static", nil)
- if err != nil {
- t.Errorf("NewFileState() error: %v", err)
- return
- }
- AssertStateEqual(t, newFileState, tc.expectedState)
- })
- }
- }
- func TestHelpersStateFile(t *testing.T) {
- testCases := []struct {
- description string
- defaultCPUset cpuset.CPUSet
- assignments map[string]map[string]cpuset.CPUSet
- }{
- {
- description: "one container",
- defaultCPUset: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7, 8),
- assignments: map[string]map[string]cpuset.CPUSet{
- "pod": {
- "c1": cpuset.NewCPUSet(0, 1),
- },
- },
- },
- {
- description: "two containers",
- defaultCPUset: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7, 8),
- assignments: map[string]map[string]cpuset.CPUSet{
- "pod": {
- "c1": cpuset.NewCPUSet(0, 1),
- "c2": cpuset.NewCPUSet(2, 3, 4, 5),
- },
- },
- },
- {
- description: "container with more cpus than is possible",
- defaultCPUset: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7, 8),
- assignments: map[string]map[string]cpuset.CPUSet{
- "pod": {
- "c1": cpuset.NewCPUSet(0, 10),
- },
- },
- },
- {
- description: "container without assigned cpus",
- defaultCPUset: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7, 8),
- assignments: map[string]map[string]cpuset.CPUSet{
- "pod": {
- "c1": cpuset.NewCPUSet(),
- },
- },
- },
- }
- for _, tc := range testCases {
- t.Run(tc.description, func(t *testing.T) {
- sfFile, err := ioutil.TempFile("/tmp", "testHelpersStateFile")
- defer os.Remove(sfFile.Name())
- if err != nil {
- t.Errorf("cannot create temporary test file: %q", err.Error())
- }
- state, err := NewFileState(sfFile.Name(), "static", nil)
- if err != nil {
- t.Errorf("new file state error: %v", err)
- return
- }
- state.SetDefaultCPUSet(tc.defaultCPUset)
- for podUID := range tc.assignments {
- for containerName, containerCPUs := range tc.assignments[podUID] {
- state.SetCPUSet(podUID, containerName, containerCPUs)
- if cpus, _ := state.GetCPUSet(podUID, containerName); !cpus.Equals(containerCPUs) {
- t.Errorf("state is inconsistent. Wants = %q Have = %q", containerCPUs, cpus)
- }
- state.Delete(podUID, containerName)
- if cpus := state.GetCPUSetOrDefault(podUID, containerName); !cpus.Equals(tc.defaultCPUset) {
- t.Error("deleted container still existing in state")
- }
- }
- }
- })
- }
- }
- func TestClearStateStateFile(t *testing.T) {
- testCases := []struct {
- description string
- defaultCPUset cpuset.CPUSet
- assignments map[string]map[string]cpuset.CPUSet
- }{
- {
- description: "valid file",
- defaultCPUset: cpuset.NewCPUSet(0, 1, 2, 3, 4, 5, 6, 7, 8),
- assignments: map[string]map[string]cpuset.CPUSet{
- "pod": {
- "c1": cpuset.NewCPUSet(0, 1),
- "c2": cpuset.NewCPUSet(2, 3),
- "c3": cpuset.NewCPUSet(4, 5),
- },
- },
- },
- }
- for _, testCase := range testCases {
- t.Run(testCase.description, func(t *testing.T) {
- sfFile, err := ioutil.TempFile("/tmp", "testHelpersStateFile")
- defer os.Remove(sfFile.Name())
- if err != nil {
- t.Errorf("cannot create temporary test file: %q", err.Error())
- }
- state, err := NewFileState(sfFile.Name(), "static", nil)
- if err != nil {
- t.Errorf("new file state error: %v", err)
- return
- }
- state.SetDefaultCPUSet(testCase.defaultCPUset)
- for podUID := range testCase.assignments {
- for containerName, containerCPUs := range testCase.assignments[podUID] {
- state.SetCPUSet(podUID, containerName, containerCPUs)
- }
- }
- state.ClearState()
- if !cpuset.NewCPUSet().Equals(state.GetDefaultCPUSet()) {
- t.Error("cleared state shouldn't has got information about available cpuset")
- }
- for podUID := range testCase.assignments {
- for containerName := range testCase.assignments[podUID] {
- if !cpuset.NewCPUSet().Equals(state.GetCPUSetOrDefault(podUID, containerName)) {
- t.Error("cleared state shouldn't has got information about containers")
- }
- }
- }
- })
- }
- }
|