github.com/shashidharatd/test-infra@v0.0.0-20171006011030-71304e1ca560/boskos/ranch/ranch.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 ranch
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"os"
    24  	"sync"
    25  	"time"
    26  
    27  	"github.com/sirupsen/logrus"
    28  	"k8s.io/test-infra/boskos/common"
    29  )
    30  
    31  // Ranch is the place which all of the Resource objects lives.
    32  type Ranch struct {
    33  	Resources   []common.Resource
    34  	lock        sync.RWMutex
    35  	storagePath string
    36  }
    37  
    38  // Public errors:
    39  
    40  // OwnerNotMatch will be returned if request owner does not match current owner for target resource.
    41  type OwnerNotMatch struct {
    42  	request string
    43  	owner   string
    44  }
    45  
    46  func (o OwnerNotMatch) Error() string {
    47  	return fmt.Sprintf("OwnerNotMatch - request by %s, currently owned by %s", o.request, o.owner)
    48  }
    49  
    50  // ResourceNotFound will be returned if requested resource does not exist.
    51  type ResourceNotFound struct {
    52  	name string
    53  }
    54  
    55  func (r ResourceNotFound) Error() string {
    56  	return fmt.Sprintf("Resource %s not exist", r.name)
    57  }
    58  
    59  // StateNotMatch will be returned if requested state does not match current state for target resource.
    60  type StateNotMatch struct {
    61  	expect  string
    62  	current string
    63  }
    64  
    65  func (s StateNotMatch) Error() string {
    66  	return fmt.Sprintf("StateNotMatch - expect %v, current %v", s.expect, s.current)
    67  }
    68  
    69  // NewRanch creates a new Ranch object.
    70  // In: config - path to resource file
    71  //     storage - path to where to save/restore the state data
    72  // Out: A Ranch object, loaded from config/storage, or error
    73  func NewRanch(config string, storage string) (*Ranch, error) {
    74  
    75  	newRanch := &Ranch{
    76  		storagePath: storage,
    77  	}
    78  
    79  	if storage != "" {
    80  		buf, err := ioutil.ReadFile(storage)
    81  		if err == nil {
    82  			logrus.Infof("Current state: %v.", buf)
    83  			err = json.Unmarshal(buf, newRanch)
    84  			if err != nil {
    85  				return nil, err
    86  			}
    87  		} else if !os.IsNotExist(err) {
    88  			return nil, err
    89  		}
    90  	}
    91  
    92  	if err := newRanch.SyncConfig(config); err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	newRanch.LogStatus()
    97  
    98  	return newRanch, nil
    99  }
   100  
   101  // Acquire checks out a type of resource in certain state without an owner,
   102  // and move the checked out resource to the end of the resource list.
   103  // In: rtype - name of the target resource
   104  //     state - current state of the requested resource
   105  //     dest - destination state of the requested resource
   106  //     owner - requester of the resource
   107  // Out: A valid Resource object on success, or
   108  //      ResourceNotFound error if target type resource does not exist in target state.
   109  func (r *Ranch) Acquire(rtype string, state string, dest string, owner string) (*common.Resource, error) {
   110  	r.lock.Lock()
   111  	defer r.lock.Unlock()
   112  
   113  	for idx := range r.Resources {
   114  		res := r.Resources[idx]
   115  		if rtype == res.Type && state == res.State && res.Owner == "" {
   116  			res.LastUpdate = time.Now()
   117  			res.Owner = owner
   118  			res.State = dest
   119  
   120  			copy(r.Resources[idx:], r.Resources[idx+1:])
   121  			r.Resources[len(r.Resources)-1] = res
   122  			return &res, nil
   123  		}
   124  	}
   125  
   126  	return nil, &ResourceNotFound{rtype}
   127  }
   128  
   129  // Release unsets owner for target resource and move it to a new state.
   130  // In: name - name of the target resource
   131  //     dest - destination state of the resource
   132  //     owner - owner of the resource
   133  // Out: nil on success, or
   134  //      OwnerNotMatch error if owner does not match current owner of the resource, or
   135  //      ResourceNotFound error if target named resource does not exist.
   136  func (r *Ranch) Release(name string, dest string, owner string) error {
   137  	r.lock.Lock()
   138  	defer r.lock.Unlock()
   139  
   140  	for idx := range r.Resources {
   141  		res := &r.Resources[idx]
   142  		if name == res.Name {
   143  			if owner != res.Owner {
   144  				return &OwnerNotMatch{res.Owner, owner}
   145  			}
   146  			res.LastUpdate = time.Now()
   147  			res.Owner = ""
   148  			res.State = dest
   149  			return nil
   150  		}
   151  	}
   152  
   153  	return &ResourceNotFound{name}
   154  }
   155  
   156  // Update updates the timestamp of a target resource.
   157  // In: name - name of the target resource
   158  //     state - current state of the resource
   159  //     owner - current owner of the resource
   160  // Out: nil on success, or
   161  //      OwnerNotMatch error if owner does not match current owner of the resource, or
   162  //      ResourceNotFound error if target named resource does not exist, or
   163  //      StateNotMatch error if state does not match current state of the resource.
   164  func (r *Ranch) Update(name string, owner string, state string) error {
   165  	r.lock.Lock()
   166  	defer r.lock.Unlock()
   167  
   168  	for idx := range r.Resources {
   169  		res := &r.Resources[idx]
   170  		if name == res.Name {
   171  			if owner != res.Owner {
   172  				return &OwnerNotMatch{res.Owner, owner}
   173  			}
   174  
   175  			if state != res.State {
   176  				return &StateNotMatch{res.State, state}
   177  			}
   178  			res.LastUpdate = time.Now()
   179  			return nil
   180  		}
   181  	}
   182  
   183  	return &ResourceNotFound{name}
   184  }
   185  
   186  // Reset unstucks a type of stale resource to a new state.
   187  // In: rtype - type of the resource
   188  //     state - current state of the resource
   189  //     expire - duration before resource's last update
   190  //     dest - destination state of expired resources
   191  // Out: map of resource name - resource owner.
   192  func (r *Ranch) Reset(rtype string, state string, expire time.Duration, dest string) map[string]string {
   193  	r.lock.Lock()
   194  	defer r.lock.Unlock()
   195  
   196  	ret := make(map[string]string)
   197  
   198  	for idx := range r.Resources {
   199  		res := &r.Resources[idx]
   200  		if rtype == res.Type && state == res.State && res.Owner != "" {
   201  			if time.Now().Sub(res.LastUpdate) > expire {
   202  				res.LastUpdate = time.Now()
   203  				ret[res.Name] = res.Owner
   204  				res.Owner = ""
   205  				res.State = dest
   206  			}
   207  		}
   208  	}
   209  
   210  	return ret
   211  }
   212  
   213  // LogStatus outputs current status of all resources
   214  func (r *Ranch) LogStatus() {
   215  	r.lock.RLock()
   216  	defer r.lock.RUnlock()
   217  
   218  	resJSON, err := json.Marshal(r.Resources)
   219  	if err != nil {
   220  		logrus.WithError(err).Errorf("Fail to marshal Resources. %v", r.Resources)
   221  	}
   222  	logrus.Infof("Current Resources : %v", string(resJSON))
   223  }
   224  
   225  // ResourceEntry is resource config format defined from resources.json
   226  type ResourceEntry struct {
   227  	Type  string   `json:"type"`
   228  	State string   `json:"state"`
   229  	Names []string `json:"names"`
   230  }
   231  
   232  // SyncConfig updates resource list from a file
   233  func (r *Ranch) SyncConfig(config string) error {
   234  	r.lock.Lock()
   235  	defer r.lock.Unlock()
   236  
   237  	data, err := r.ParseConfig(config)
   238  	if err != nil {
   239  		return err
   240  	}
   241  
   242  	r.syncConfigHelper(data)
   243  	return nil
   244  }
   245  
   246  // ParseConfig reads in configPath and returns a list of resource objects
   247  // on success.
   248  func (r *Ranch) ParseConfig(configPath string) ([]common.Resource, error) {
   249  	file, err := ioutil.ReadFile(configPath)
   250  	if err != nil {
   251  		return nil, err
   252  	}
   253  
   254  	data := []ResourceEntry{}
   255  	err = json.Unmarshal(file, &data)
   256  	if err != nil {
   257  		return nil, err
   258  	}
   259  
   260  	var resources []common.Resource
   261  	for _, res := range data {
   262  		for _, name := range res.Names {
   263  			resources = append(resources, common.Resource{
   264  				Type:  res.Type,
   265  				State: res.State,
   266  				Name:  name,
   267  			})
   268  		}
   269  	}
   270  	return resources, nil
   271  }
   272  
   273  // Boskos resource config will be updated every 10 mins.
   274  // It will append newly added resources to ranch.Resources,
   275  // And try to remove newly deleted resources from ranch.Resources.
   276  // If the newly deleted resource is currently held by a user, the deletion will
   277  // yield to next update cycle.
   278  func (r *Ranch) syncConfigHelper(data []common.Resource) {
   279  	// delete non-exist resource
   280  	valid := 0
   281  	for _, res := range r.Resources {
   282  		// If currently busy, yield deletion to later cycles.
   283  		if res.Owner != "" {
   284  			r.Resources[valid] = res
   285  			valid++
   286  			continue
   287  		}
   288  
   289  		for _, newRes := range data {
   290  			if res.Name == newRes.Name {
   291  				r.Resources[valid] = res
   292  				valid++
   293  				break
   294  			}
   295  		}
   296  	}
   297  	r.Resources = r.Resources[:valid]
   298  
   299  	// add new resource
   300  	for _, p := range data {
   301  		found := false
   302  		for _, exist := range r.Resources {
   303  			if p.Name == exist.Name {
   304  				found = true
   305  				break
   306  			}
   307  		}
   308  
   309  		if !found {
   310  			if p.State == "" {
   311  				p.State = "free"
   312  			}
   313  			r.Resources = append(r.Resources, p)
   314  		}
   315  	}
   316  }
   317  
   318  // SaveState saves current server state in json format
   319  func (r *Ranch) SaveState() {
   320  	if r.storagePath == "" {
   321  		return
   322  	}
   323  
   324  	r.lock.RLock()
   325  	defer r.lock.RUnlock()
   326  
   327  	// If fail to save data, fatal and restart the server
   328  	if buf, err := json.Marshal(r); err != nil {
   329  		logrus.WithError(err).Fatal("Error marshal ranch")
   330  	} else if err = ioutil.WriteFile(r.storagePath+".tmp", buf, 0644); err != nil {
   331  		logrus.WithError(err).Fatal("Error write file")
   332  	} else if err = os.Rename(r.storagePath+".tmp", r.storagePath); err != nil {
   333  		logrus.WithError(err).Fatal("Error rename file")
   334  	}
   335  }
   336  
   337  // Metric returns a metric object with metrics filled in
   338  func (r *Ranch) Metric(rtype string) (common.Metric, error) {
   339  	metric := common.Metric{
   340  		Type:    rtype,
   341  		Current: map[string]int{},
   342  		Owners:  map[string]int{},
   343  	}
   344  
   345  	for _, res := range r.Resources {
   346  		if res.Type != rtype {
   347  			continue
   348  		}
   349  
   350  		if _, ok := metric.Current[res.State]; !ok {
   351  			metric.Current[res.State] = 0
   352  		}
   353  
   354  		if _, ok := metric.Owners[res.Owner]; !ok {
   355  			metric.Owners[res.Owner] = 0
   356  		}
   357  
   358  		metric.Current[res.State]++
   359  		metric.Owners[res.Owner]++
   360  	}
   361  
   362  	if len(metric.Current) == 0 && len(metric.Owners) == 0 {
   363  		return metric, &ResourceNotFound{rtype}
   364  	}
   365  
   366  	return metric, nil
   367  }