github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/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  	"strings"
    23  	"sync"
    24  	"time"
    25  
    26  	"github.com/sirupsen/logrus"
    27  
    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  	Storage       *Storage
    34  	resourcesLock sync.RWMutex
    35  	// For testing
    36  	UpdateTime func() time.Time
    37  }
    38  
    39  func updateTime() time.Time {
    40  	return time.Now()
    41  }
    42  
    43  // Public errors:
    44  
    45  // ResourceNotFound will be returned if requested resource does not exist.
    46  type ResourceNotFound struct {
    47  	name string
    48  }
    49  
    50  func (r ResourceNotFound) Error() string {
    51  	return fmt.Sprintf("Resource %s not exist", r.name)
    52  }
    53  
    54  // OwnerNotMatch will be returned if request owner does not match current owner for target resource.
    55  type OwnerNotMatch struct {
    56  	request string
    57  	owner   string
    58  }
    59  
    60  func (o OwnerNotMatch) Error() string {
    61  	return fmt.Sprintf("OwnerNotMatch - request by %s, currently owned by %s", o.request, o.owner)
    62  }
    63  
    64  // StateNotMatch will be returned if requested state does not match current state for target resource.
    65  type StateNotMatch struct {
    66  	expect  string
    67  	current string
    68  }
    69  
    70  func (s StateNotMatch) Error() string {
    71  	return fmt.Sprintf("StateNotMatch - expect %v, current %v", s.expect, s.current)
    72  }
    73  
    74  // NewRanch creates a new Ranch object.
    75  // In: config - path to resource file
    76  //     storage - path to where to save/restore the state data
    77  // Out: A Ranch object, loaded from config/storage, or error
    78  func NewRanch(config string, s *Storage) (*Ranch, error) {
    79  	newRanch := &Ranch{
    80  		Storage:    s,
    81  		UpdateTime: updateTime,
    82  	}
    83  	if config != "" {
    84  		if err := newRanch.SyncConfig(config); err != nil {
    85  			return nil, err
    86  		}
    87  	}
    88  	newRanch.LogStatus()
    89  	return newRanch, nil
    90  }
    91  
    92  // Acquire checks out a type of resource in certain state without an owner,
    93  // and move the checked out resource to the end of the resource list.
    94  // In: rtype - name of the target resource
    95  //     state - current state of the requested resource
    96  //     dest - destination state of the requested resource
    97  //     owner - requester of the resource
    98  // Out: A valid Resource object on success, or
    99  //      ResourceNotFound error if target type resource does not exist in target state.
   100  func (r *Ranch) Acquire(rType, state, dest, owner string) (*common.Resource, error) {
   101  	r.resourcesLock.Lock()
   102  	defer r.resourcesLock.Unlock()
   103  
   104  	resources, err := r.Storage.GetResources()
   105  	if err != nil {
   106  		logrus.WithError(err).Errorf("could not get resources")
   107  		return nil, &ResourceNotFound{rType}
   108  	}
   109  
   110  	for idx := range resources {
   111  		res := resources[idx]
   112  		if rType == res.Type && state == res.State && res.Owner == "" {
   113  			res.LastUpdate = r.UpdateTime()
   114  			res.Owner = owner
   115  			res.State = dest
   116  			if err := r.Storage.UpdateResource(res); err != nil {
   117  				logrus.WithError(err).Errorf("could not update resource %s", res.Name)
   118  				return nil, err
   119  			}
   120  			return &res, nil
   121  		}
   122  	}
   123  	return nil, &ResourceNotFound{rType}
   124  }
   125  
   126  // AcquireByState checks out resources of a given type without an owner,
   127  // that matches a list of resources names.
   128  // In: state - current state of the requested resource
   129  //     dest - destination state of the requested resource
   130  //     owner - requester of the resource
   131  //     names - names of resource to acquire
   132  // Out: A valid list of Resource object on success, or
   133  //      ResourceNotFound error if target type resource does not exist in target state.
   134  func (r *Ranch) AcquireByState(state, dest, owner string, names []string) ([]common.Resource, error) {
   135  	r.resourcesLock.Lock()
   136  	defer r.resourcesLock.Unlock()
   137  
   138  	if names == nil {
   139  		return nil, fmt.Errorf("must provide names of expected resources")
   140  	}
   141  
   142  	rNames := map[string]bool{}
   143  	for _, t := range names {
   144  		rNames[t] = true
   145  	}
   146  
   147  	allResources, err := r.Storage.GetResources()
   148  	if err != nil {
   149  		logrus.WithError(err).Errorf("could not get resources")
   150  		return nil, &ResourceNotFound{state}
   151  	}
   152  
   153  	var resources []common.Resource
   154  
   155  	for idx := range allResources {
   156  		res := allResources[idx]
   157  		if state == res.State {
   158  			if res.Owner != "" {
   159  				continue
   160  			}
   161  			if rNames[res.Name] {
   162  				res.LastUpdate = r.UpdateTime()
   163  				res.Owner = owner
   164  				res.State = dest
   165  				if err := r.Storage.UpdateResource(res); err != nil {
   166  					logrus.WithError(err).Errorf("could not update resource %s", res.Name)
   167  					return nil, err
   168  				}
   169  				resources = append(resources, res)
   170  				delete(rNames, res.Name)
   171  			}
   172  		}
   173  	}
   174  	if len(rNames) != 0 {
   175  		var missingResources []string
   176  		for n := range rNames {
   177  			missingResources = append(missingResources, n)
   178  		}
   179  		err := &ResourceNotFound{state}
   180  		logrus.WithError(err).Errorf("could not find required resources %s", strings.Join(missingResources, ", "))
   181  		return resources, err
   182  	}
   183  	return resources, nil
   184  }
   185  
   186  // Release unsets owner for target resource and move it to a new state.
   187  // In: name - name of the target resource
   188  //     dest - destination state of the resource
   189  //     owner - owner of the resource
   190  // Out: nil on success, or
   191  //      OwnerNotMatch error if owner does not match current owner of the resource, or
   192  //      ResourceNotFound error if target named resource does not exist.
   193  func (r *Ranch) Release(name, dest, owner string) error {
   194  	r.resourcesLock.Lock()
   195  	defer r.resourcesLock.Unlock()
   196  
   197  	res, err := r.Storage.GetResource(name)
   198  	if err != nil {
   199  		logrus.WithError(err).Errorf("unable to release resource %s", name)
   200  		return &ResourceNotFound{name}
   201  	}
   202  	if owner != res.Owner {
   203  		return &OwnerNotMatch{res.Owner, owner}
   204  	}
   205  	res.LastUpdate = r.UpdateTime()
   206  	res.Owner = ""
   207  	res.State = dest
   208  	if err := r.Storage.UpdateResource(res); err != nil {
   209  		logrus.WithError(err).Errorf("could not update resource %s", res.Name)
   210  		return err
   211  	}
   212  	return nil
   213  }
   214  
   215  // Update updates the timestamp of a target resource.
   216  // In: name  - name of the target resource
   217  //     state - current state of the resource
   218  //     owner - current owner of the resource
   219  // 	   info  - information on how to use the resource
   220  // Out: nil on success, or
   221  //      OwnerNotMatch error if owner does not match current owner of the resource, or
   222  //      ResourceNotFound error if target named resource does not exist, or
   223  //      StateNotMatch error if state does not match current state of the resource.
   224  func (r *Ranch) Update(name, owner, state string, ud *common.UserData) error {
   225  	r.resourcesLock.Lock()
   226  	defer r.resourcesLock.Unlock()
   227  
   228  	res, err := r.Storage.GetResource(name)
   229  	if err != nil {
   230  		logrus.WithError(err).Errorf("could not find resource %s for update", name)
   231  		return &ResourceNotFound{name}
   232  	}
   233  	if owner != res.Owner {
   234  		return &OwnerNotMatch{owner, res.Owner}
   235  	}
   236  	if state != res.State {
   237  		return &StateNotMatch{res.State, state}
   238  	}
   239  	if res.UserData == nil {
   240  		res.UserData = &common.UserData{}
   241  	}
   242  	res.UserData.Update(ud)
   243  	res.LastUpdate = r.UpdateTime()
   244  	if err := r.Storage.UpdateResource(res); err != nil {
   245  		logrus.WithError(err).Errorf("could not update resource %s", res.Name)
   246  		return err
   247  	}
   248  	return nil
   249  }
   250  
   251  // Reset unstucks a type of stale resource to a new state.
   252  // In: rtype - type of the resource
   253  //     state - current state of the resource
   254  //     expire - duration before resource's last update
   255  //     dest - destination state of expired resources
   256  // Out: map of resource name - resource owner.
   257  func (r *Ranch) Reset(rtype, state string, expire time.Duration, dest string) (map[string]string, error) {
   258  	r.resourcesLock.Lock()
   259  	defer r.resourcesLock.Unlock()
   260  
   261  	ret := make(map[string]string)
   262  
   263  	resources, err := r.Storage.GetResources()
   264  	if err != nil {
   265  		logrus.WithError(err).Errorf("cannot find resources")
   266  		return nil, err
   267  	}
   268  
   269  	for idx := range resources {
   270  		res := resources[idx]
   271  		if rtype == res.Type && state == res.State && res.Owner != "" {
   272  			if time.Since(res.LastUpdate) > expire {
   273  				res.LastUpdate = r.UpdateTime()
   274  				ret[res.Name] = res.Owner
   275  				res.Owner = ""
   276  				res.State = dest
   277  				if err := r.Storage.UpdateResource(res); err != nil {
   278  					logrus.WithError(err).Errorf("could not update resource %s", res.Name)
   279  					return ret, err
   280  				}
   281  			}
   282  		}
   283  	}
   284  	return ret, nil
   285  }
   286  
   287  // LogStatus outputs current status of all resources
   288  func (r *Ranch) LogStatus() {
   289  	resources, err := r.Storage.GetResources()
   290  
   291  	if err != nil {
   292  		return
   293  	}
   294  
   295  	resJSON, err := json.Marshal(resources)
   296  	if err != nil {
   297  		logrus.WithError(err).Errorf("Fail to marshal Resources. %v", resources)
   298  	}
   299  	logrus.Infof("Current Resources : %v", string(resJSON))
   300  }
   301  
   302  // SyncConfig updates resource list from a file
   303  func (r *Ranch) SyncConfig(config string) error {
   304  	resources, err := ParseConfig(config)
   305  	if err != nil {
   306  		return err
   307  	}
   308  	if err := r.Storage.SyncResources(resources); err != nil {
   309  		return err
   310  	}
   311  	return nil
   312  }
   313  
   314  // Metric returns a metric object with metrics filled in
   315  func (r *Ranch) Metric(rtype string) (common.Metric, error) {
   316  	metric := common.Metric{
   317  		Type:    rtype,
   318  		Current: map[string]int{},
   319  		Owners:  map[string]int{},
   320  	}
   321  
   322  	resources, err := r.Storage.GetResources()
   323  	if err != nil {
   324  		logrus.WithError(err).Error("cannot find resources")
   325  		return metric, &ResourceNotFound{rtype}
   326  	}
   327  
   328  	for _, res := range resources {
   329  		if res.Type != rtype {
   330  			continue
   331  		}
   332  
   333  		if _, ok := metric.Current[res.State]; !ok {
   334  			metric.Current[res.State] = 0
   335  		}
   336  
   337  		if _, ok := metric.Owners[res.Owner]; !ok {
   338  			metric.Owners[res.Owner] = 0
   339  		}
   340  
   341  		metric.Current[res.State]++
   342  		metric.Owners[res.Owner]++
   343  	}
   344  
   345  	if len(metric.Current) == 0 && len(metric.Owners) == 0 {
   346  		return metric, &ResourceNotFound{rtype}
   347  	}
   348  
   349  	return metric, nil
   350  }