github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/boskos/ranch/storage.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  	"io/ioutil"
    22  	"os"
    23  	"sort"
    24  	"sync"
    25  
    26  	"github.com/hashicorp/go-multierror"
    27  	"github.com/sirupsen/logrus"
    28  	"sigs.k8s.io/yaml"
    29  
    30  	"k8s.io/test-infra/boskos/common"
    31  	"k8s.io/test-infra/boskos/storage"
    32  )
    33  
    34  // Storage is used to decouple ranch functionality with the resource persistence layer
    35  type Storage struct {
    36  	resources     storage.PersistenceLayer
    37  	resourcesLock sync.RWMutex
    38  }
    39  
    40  // NewStorage instantiates a new Storage with a PersistenceLayer implementation
    41  // If storage string is not empty, it will read resource data from the file
    42  func NewStorage(r storage.PersistenceLayer, storage string) (*Storage, error) {
    43  	s := &Storage{
    44  		resources: r,
    45  	}
    46  
    47  	if storage != "" {
    48  		var data struct {
    49  			Resources []common.Resource
    50  		}
    51  		buf, err := ioutil.ReadFile(storage)
    52  		if err == nil {
    53  			logrus.Infof("Current state: %s.", string(buf))
    54  			err = json.Unmarshal(buf, &data)
    55  			if err != nil {
    56  				return nil, err
    57  			}
    58  		} else if !os.IsNotExist(err) {
    59  			return nil, err
    60  		}
    61  
    62  		logrus.Info("Before adding resource loop")
    63  		for _, res := range data.Resources {
    64  			if err := s.AddResource(res); err != nil {
    65  				logrus.WithError(err).Errorf("Failed Adding Resources: %s - %s.", res.Name, res.State)
    66  			}
    67  			logrus.Infof("Successfully Added Resources: %s - %s.", res.Name, res.State)
    68  		}
    69  	}
    70  	return s, nil
    71  }
    72  
    73  // AddResource adds a new resource
    74  func (s *Storage) AddResource(resource common.Resource) error {
    75  	return s.resources.Add(resource)
    76  }
    77  
    78  // DeleteResource deletes a resource if it exists, errors otherwise
    79  func (s *Storage) DeleteResource(name string) error {
    80  	return s.resources.Delete(name)
    81  }
    82  
    83  // UpdateResource updates a resource if it exists, errors otherwise
    84  func (s *Storage) UpdateResource(resource common.Resource) error {
    85  	return s.resources.Update(resource)
    86  }
    87  
    88  // GetResource gets an existing resource, errors otherwise
    89  func (s *Storage) GetResource(name string) (common.Resource, error) {
    90  	i, err := s.resources.Get(name)
    91  	if err != nil {
    92  		return common.Resource{}, err
    93  	}
    94  	var res common.Resource
    95  	res, err = common.ItemToResource(i)
    96  	if err != nil {
    97  		return common.Resource{}, err
    98  	}
    99  	return res, nil
   100  }
   101  
   102  // GetResources list all resources
   103  func (s *Storage) GetResources() ([]common.Resource, error) {
   104  	var resources []common.Resource
   105  	items, err := s.resources.List()
   106  	if err != nil {
   107  		return resources, err
   108  	}
   109  	for _, i := range items {
   110  		var res common.Resource
   111  		res, err = common.ItemToResource(i)
   112  		if err != nil {
   113  			return nil, err
   114  		}
   115  		resources = append(resources, res)
   116  	}
   117  	sort.Stable(common.ResourceByUpdateTime(resources))
   118  	return resources, nil
   119  }
   120  
   121  // SyncResources will update resources every 10 mins.
   122  // It will append newly added resources to ranch.Resources,
   123  // And try to remove newly deleted resources from ranch.Resources.
   124  // If the newly deleted resource is currently held by a user, the deletion will
   125  // yield to next update cycle.
   126  func (s *Storage) SyncResources(data []common.Resource) error {
   127  	s.resourcesLock.Lock()
   128  	defer s.resourcesLock.Unlock()
   129  
   130  	resources, err := s.GetResources()
   131  	if err != nil {
   132  		logrus.WithError(err).Error("cannot find resources")
   133  		return err
   134  	}
   135  
   136  	var finalError error
   137  
   138  	// delete non-exist resource
   139  	valid := 0
   140  	for _, res := range resources {
   141  		// If currently busy, yield deletion to later cycles.
   142  		if res.Owner != "" {
   143  			resources[valid] = res
   144  			valid++
   145  			continue
   146  		}
   147  		toDelete := true
   148  		for _, newRes := range data {
   149  			if res.Name == newRes.Name {
   150  				resources[valid] = res
   151  				valid++
   152  				toDelete = false
   153  				break
   154  			}
   155  		}
   156  		if toDelete {
   157  			logrus.Infof("Deleting resource %s", res.Name)
   158  			if err := s.DeleteResource(res.Name); err != nil {
   159  				finalError = multierror.Append(finalError, err)
   160  				logrus.WithError(err).Errorf("unable to delete resource %s", res.Name)
   161  			}
   162  		}
   163  	}
   164  	resources = resources[:valid]
   165  
   166  	// add new resource
   167  	for _, p := range data {
   168  		found := false
   169  		for idx := range resources {
   170  			exist := resources[idx]
   171  			if p.Name == exist.Name {
   172  				found = true
   173  				logrus.Infof("Keeping resource %s", p.Name)
   174  				break
   175  			}
   176  		}
   177  
   178  		if !found {
   179  			if p.State == "" {
   180  				p.State = common.Free
   181  			}
   182  			logrus.Infof("Adding resource %s", p.Name)
   183  			resources = append(resources, p)
   184  			if err := s.AddResource(p); err != nil {
   185  				logrus.WithError(err).Errorf("unable to add resource %s", p.Name)
   186  				finalError = multierror.Append(finalError, err)
   187  			}
   188  		}
   189  	}
   190  	return finalError
   191  }
   192  
   193  // ParseConfig reads in configPath and returns a list of resource objects
   194  // on success.
   195  func ParseConfig(configPath string) ([]common.Resource, error) {
   196  	file, err := ioutil.ReadFile(configPath)
   197  	if err != nil {
   198  		return nil, err
   199  	}
   200  
   201  	var data common.BoskosConfig
   202  	err = yaml.Unmarshal(file, &data)
   203  	if err != nil {
   204  		return nil, err
   205  	}
   206  
   207  	var resources []common.Resource
   208  	for _, entry := range data.Resources {
   209  		resources = append(resources, common.NewResourcesFromConfig(entry)...)
   210  	}
   211  	return resources, nil
   212  }