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 }