github.com/munnerz/test-infra@v0.0.0-20190108210205-ce3d181dc989/boskos/common/common.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 common 18 19 import ( 20 "encoding/json" 21 "errors" 22 "fmt" 23 "strings" 24 "sync" 25 "time" 26 27 "sigs.k8s.io/yaml" 28 ) 29 30 const ( 31 // Busy state defines a resource being used. 32 Busy = "busy" 33 // Dirty state defines a resource that needs cleaning 34 Dirty = "dirty" 35 // Free state defines a resource that is usable 36 Free = "free" 37 // Cleaning state defines a resource being cleaned 38 Cleaning = "cleaning" 39 // Leased state defines a resource being leased in order to make a new resource 40 Leased = "leased" 41 // Other is used to agglomerate unspecified states for metrics reporting 42 Other = "other" 43 ) 44 45 // UserData is a map of Name to user defined interface, serialized into a string 46 type UserData struct { 47 sync.Map 48 } 49 50 // UserDataMap is the standard Map version of UserMap, it is used to ease UserMap creation. 51 type UserDataMap map[string]string 52 53 // LeasedResources is a list of resources name that used in order to create another resource by Mason 54 type LeasedResources []string 55 56 // Item interfaces for resources and configs 57 type Item interface { 58 GetName() string 59 } 60 61 // Resource abstracts any resource type that can be tracked by boskos 62 type Resource struct { 63 Type string `json:"type"` 64 Name string `json:"name"` 65 State string `json:"state"` 66 Owner string `json:"owner"` 67 LastUpdate time.Time `json:"lastupdate"` 68 // Customized UserData 69 UserData *UserData `json:"userdata"` 70 } 71 72 // ResourceEntry is resource config format defined from config.yaml 73 type ResourceEntry struct { 74 Type string `json:"type"` 75 State string `json:"state"` 76 Names []string `json:"names,flow"` 77 } 78 79 // BoskosConfig defines config used by boskos server 80 type BoskosConfig struct { 81 Resources []ResourceEntry `json:"resources,flow"` 82 } 83 84 // Metric contains analytics about a specific resource type 85 type Metric struct { 86 Type string `json:"type"` 87 Current map[string]int `json:"current"` 88 Owners map[string]int `json:"owner"` 89 // TODO: implements state transition metrics 90 } 91 92 // NewResource creates a new Boskos Resource. 93 func NewResource(name, rtype, state, owner string, t time.Time) Resource { 94 return Resource{ 95 Name: name, 96 Type: rtype, 97 State: state, 98 Owner: owner, 99 LastUpdate: t, 100 UserData: &UserData{}, 101 } 102 } 103 104 // NewResourcesFromConfig parse the a ResourceEntry into a list of resources 105 func NewResourcesFromConfig(e ResourceEntry) []Resource { 106 var resources []Resource 107 for _, name := range e.Names { 108 resources = append(resources, NewResource(name, e.Type, e.State, "", time.Time{})) 109 } 110 return resources 111 } 112 113 // UserDataFromMap returns a UserData from a map 114 func UserDataFromMap(m UserDataMap) *UserData { 115 ud := &UserData{} 116 for k, v := range m { 117 ud.Store(k, v) 118 } 119 return ud 120 } 121 122 // UserDataNotFound will be returned if requested resource does not exist. 123 type UserDataNotFound struct { 124 ID string 125 } 126 127 func (ud *UserDataNotFound) Error() string { 128 return fmt.Sprintf("user data ID %s does not exist", ud.ID) 129 } 130 131 // ResourceByUpdateTime helps sorting resources by update time 132 type ResourceByUpdateTime []Resource 133 134 func (ut ResourceByUpdateTime) Len() int { return len(ut) } 135 func (ut ResourceByUpdateTime) Swap(i, j int) { ut[i], ut[j] = ut[j], ut[i] } 136 func (ut ResourceByUpdateTime) Less(i, j int) bool { return ut[i].LastUpdate.Before(ut[j].LastUpdate) } 137 138 // ResourceByName helps sorting resources by name 139 type ResourceByName []Resource 140 141 func (ut ResourceByName) Len() int { return len(ut) } 142 func (ut ResourceByName) Swap(i, j int) { ut[i], ut[j] = ut[j], ut[i] } 143 func (ut ResourceByName) Less(i, j int) bool { return ut[i].GetName() < ut[j].GetName() } 144 145 // CommaSeparatedStrings is used to parse comma separated string flag into a list of strings 146 type CommaSeparatedStrings []string 147 148 func (r *CommaSeparatedStrings) String() string { 149 return fmt.Sprint(*r) 150 } 151 152 // Set parses the flag value into a CommaSeparatedStrings 153 func (r *CommaSeparatedStrings) Set(value string) error { 154 if len(*r) > 0 { 155 return errors.New("resTypes flag already set") 156 } 157 for _, rtype := range strings.Split(value, ",") { 158 *r = append(*r, rtype) 159 } 160 return nil 161 } 162 163 // GetName implements the Item interface used for storage 164 func (res Resource) GetName() string { return res.Name } 165 166 // UnmarshalJSON implements JSON Unmarshaler interface 167 func (ud *UserData) UnmarshalJSON(data []byte) error { 168 tmpMap := UserDataMap{} 169 if err := json.Unmarshal(data, &tmpMap); err != nil { 170 return err 171 } 172 ud.FromMap(tmpMap) 173 return nil 174 } 175 176 // MarshalJSON implements JSON Marshaler interface 177 func (ud *UserData) MarshalJSON() ([]byte, error) { 178 return json.Marshal(ud.ToMap()) 179 } 180 181 // Extract unmarshalls a string a given struct if it exists 182 func (ud *UserData) Extract(id string, out interface{}) error { 183 content, ok := ud.Load(id) 184 if !ok { 185 return &UserDataNotFound{id} 186 } 187 return yaml.Unmarshal([]byte(content.(string)), out) 188 } 189 190 // User Data are used to store custom information mainly by Mason and Masonable implementation. 191 // Mason used a LeasedResource keys to store information about other resources that used to 192 // create the given resource. 193 194 // Set marshalls a struct to a string into the UserData 195 func (ud *UserData) Set(id string, in interface{}) error { 196 b, err := yaml.Marshal(in) 197 if err != nil { 198 return err 199 } 200 ud.Store(id, string(b)) 201 return nil 202 } 203 204 // Update updates existing UserData with new UserData. 205 // If a key as an empty string, the key will be deleted 206 func (ud *UserData) Update(new *UserData) { 207 if new == nil { 208 return 209 } 210 new.Range(func(key, value interface{}) bool { 211 if value.(string) != "" { 212 ud.Store(key, value) 213 } else { 214 ud.Delete(key) 215 } 216 return true 217 }) 218 } 219 220 // ToMap converts a UserData to UserDataMap 221 func (ud *UserData) ToMap() UserDataMap { 222 m := UserDataMap{} 223 ud.Range(func(key, value interface{}) bool { 224 m[key.(string)] = value.(string) 225 return true 226 }) 227 return m 228 } 229 230 // FromMap feels updates user data from a map 231 func (ud *UserData) FromMap(m UserDataMap) { 232 for key, value := range m { 233 ud.Store(key, value) 234 } 235 } 236 237 // ItemToResource casts a Item back to a Resource 238 func ItemToResource(i Item) (Resource, error) { 239 res, ok := i.(Resource) 240 if !ok { 241 return Resource{}, fmt.Errorf("cannot construct Resource from received object %v", i) 242 } 243 return res, nil 244 }