github.com/abayer/test-infra@v0.0.5/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  }