github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/boskos/janitor/janitor_test.go (about) 1 /* 2 Copyright 2017 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package main 18 19 import ( 20 "errors" 21 "fmt" 22 "math/rand" 23 "sync" 24 "sync/atomic" 25 "testing" 26 "time" 27 28 "k8s.io/test-infra/boskos/common" 29 ) 30 31 type fakeBoskos struct { 32 lock sync.Mutex 33 wg sync.WaitGroup 34 resources []common.Resource 35 } 36 37 // Create a fake client 38 func createFakeBoskos(resources int, types []string) *fakeBoskos { 39 fb := &fakeBoskos{} 40 r := rand.New(rand.NewSource(99)) 41 for i := 0; i < resources; i++ { 42 fb.resources = append(fb.resources, 43 common.Resource{ 44 Name: fmt.Sprintf("res-%d", i), 45 Type: types[r.Intn(len(types))], 46 State: common.Dirty, 47 }) 48 } 49 50 return fb 51 } 52 53 func (fb *fakeBoskos) Acquire(rtype string, state string, dest string) (*common.Resource, error) { 54 fb.lock.Lock() 55 defer fb.lock.Unlock() 56 57 for idx := range fb.resources { 58 r := &fb.resources[idx] 59 if r.State == state { 60 r.State = dest 61 fb.wg.Add(1) 62 return r, nil 63 } 64 } 65 66 return nil, fmt.Errorf("could not find resource of type %s", rtype) 67 } 68 69 func (fb *fakeBoskos) ReleaseOne(name string, dest string) error { 70 fb.lock.Lock() 71 defer fb.lock.Unlock() 72 73 for idx := range fb.resources { 74 r := &fb.resources[idx] 75 if r.Name == name { 76 r.State = dest 77 fb.wg.Done() 78 return nil 79 } 80 } 81 82 return fmt.Errorf("no resource %v", name) 83 } 84 85 // waitTimeout waits for the waitgroup for the specified max timeout. 86 // Returns true if waiting timed out. 87 func waitTimeout(wg *sync.WaitGroup, timeout time.Duration) bool { 88 c := make(chan struct{}) 89 go func() { 90 defer close(c) 91 wg.Wait() 92 }() 93 select { 94 case <-c: 95 return false // completed normally 96 case <-time.After(timeout): 97 return true // timed out 98 } 99 } 100 101 func TestNormal(t *testing.T) { 102 var totalClean int32 103 104 fakeClean := func(p string) error { 105 atomic.AddInt32(&totalClean, 1) 106 return nil 107 } 108 109 types := []string{"a", "b", "c", "d"} 110 fb := createFakeBoskos(1000, types) 111 112 buffer := setup(fb, poolSize, bufferSize, fakeClean) 113 totalAcquire := run(fb, buffer, []string{"t"}) 114 115 if totalAcquire != len(fb.resources) { 116 t.Errorf("expect to acquire all resources(%d) from fake boskos, got %d", len(fb.resources), totalAcquire) 117 } 118 119 if waitTimeout(&fb.wg, time.Second) { 120 t.Fatal("expect janitor to finish!") 121 } 122 123 if int(totalClean) != len(fb.resources) { 124 t.Errorf("expect to clean all resources(%d) from fake boskos, got %d", len(fb.resources), totalClean) 125 } 126 127 for _, r := range fb.resources { 128 if r.State != common.Free { 129 t.Errorf("resource %v, expect state free, got state %v", r.Name, r.State) 130 } 131 } 132 } 133 134 func FakeRun(fb *fakeBoskos, buffer chan string, res string) (int, error) { 135 timeout := time.NewTimer(5 * time.Second).C 136 137 totalClean := 0 138 maxAcquire := poolSize + bufferSize + 1 139 140 for { 141 select { 142 case <-timeout: 143 return totalClean, errors.New("should not timedout") 144 default: 145 if projRes, err := fb.Acquire(res, common.Dirty, common.Cleaning); err != nil { 146 return totalClean, fmt.Errorf("acquire failed with %v", err) 147 } else if projRes.Name == "" { 148 return totalClean, errors.New("not expect to run out of resources") 149 } else { 150 if totalClean > maxAcquire { 151 // poolSize in janitor, bufferSize more in janitor pool, 1 more hanging and will exit the loop 152 return totalClean, fmt.Errorf("should not acquire more than %d projects", maxAcquire) 153 } 154 boom := time.After(50 * time.Millisecond) 155 select { 156 case buffer <- projRes.Name: // normal case 157 totalClean++ 158 case <-boom: 159 return totalClean, nil 160 } 161 } 162 } 163 } 164 } 165 166 func TestMalfunctionJanitor(t *testing.T) { 167 168 stuck := make(chan string, 1) 169 fakeClean := func(p string) error { 170 <-stuck 171 return nil 172 } 173 174 fb := createFakeBoskos(200, []string{"t"}) 175 176 buffer := setup(fb, poolSize, bufferSize, fakeClean) 177 178 if totalClean, err := FakeRun(fb, buffer, "t"); err != nil { 179 t.Fatalf("run failed unexpectedly : %v", err) 180 } else if totalClean != poolSize+1 { 181 t.Errorf("expect to clean %d from fake boskos, got %d", poolSize+1, totalClean) 182 } 183 }