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 }