github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/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: "dirty",
    47  			})
    48  	}
    49  
    50  	return fb
    51  }
    52  
    53  func (fb *fakeBoskos) Acquire(rtype string, state string, dest string) (string, 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.Name, nil
    63  		}
    64  	}
    65  
    66  	return "", nil
    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 = 0
   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 != "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  
   139  	for {
   140  		select {
   141  		case <-timeout:
   142  			return totalClean, errors.New("should not timedout")
   143  		default:
   144  			if proj, err := fb.Acquire(res, "dirty", "cleaning"); err != nil {
   145  				return totalClean, fmt.Errorf("acquire failed with %v", err)
   146  			} else if proj == "" {
   147  				return totalClean, errors.New("not expect to run out of resources")
   148  			} else {
   149  				if totalClean > poolSize+bufferSize+1 {
   150  					// poolSize in janitor, bufferSize more in janitor pool, 1 more hanging and will exit the loop
   151  					return totalClean, errors.New("should not acquire more than 12 projects")
   152  				}
   153  				boom := time.After(50 * time.Millisecond)
   154  				select {
   155  				case buffer <- proj: // normal case
   156  					totalClean++
   157  				case <-boom:
   158  					return totalClean, nil
   159  				}
   160  			}
   161  		}
   162  	}
   163  }
   164  
   165  func TestMalfunctionJanitor(t *testing.T) {
   166  
   167  	stuck := make(chan string, 1)
   168  	fakeClean := func(p string) error {
   169  		<-stuck
   170  		return nil
   171  	}
   172  
   173  	fb := CreateFakeBoskos(200, []string{"t"})
   174  
   175  	buffer := setup(fb, poolSize, bufferSize, fakeClean)
   176  
   177  	if totalClean, err := FakeRun(fb, buffer, "t"); err != nil {
   178  		t.Fatalf("run failed unexpectedly : %v", err)
   179  	} else if totalClean != poolSize+1 {
   180  		t.Errorf("expect to clean %d from fake boskos, got %d", poolSize+1, totalClean)
   181  	}
   182  }