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 }