github.com/maxgio92/test-infra@v0.1.0/kubetest/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 // Cleaning state defines a resource being cleaned 34 Cleaning = "cleaning" 35 // Dirty state defines a resource that needs cleaning 36 Dirty = "dirty" 37 // Free state defines a resource that is usable 38 Free = "free" 39 // Leased state defines a resource being leased in order to make a new resource 40 Leased = "leased" 41 // ToBeDeleted is used for resources about to be deleted, they will be verified by a cleaner which mark them as tombstone 42 ToBeDeleted = "toBeDeleted" 43 // Tombstone is the state in which a resource can safely be deleted 44 Tombstone = "tombstone" 45 // Other is used to agglomerate unspecified states for metrics reporting 46 Other = "other" 47 ) 48 49 var ( 50 // KnownStates is the set of all known states, excluding "other". 51 KnownStates = []string{ 52 Busy, 53 Cleaning, 54 Dirty, 55 Free, 56 Leased, 57 ToBeDeleted, 58 Tombstone, 59 } 60 ) 61 62 // UserData is a map of Name to user defined interface, serialized into a string 63 type UserData struct { 64 sync.Map 65 } 66 67 // UserDataMap is the standard Map version of UserMap, it is used to ease UserMap creation. 68 type UserDataMap map[string]string 69 70 // LeasedResources is a list of resources name that used in order to create another resource by Mason 71 type LeasedResources []string 72 73 // Duration is a wrapper around time.Duration that parses times in either 74 // 'integer number of nanoseconds' or 'duration string' formats and serializes 75 // to 'duration string' format. 76 type Duration struct { 77 *time.Duration 78 } 79 80 // UnmarshalJSON implement the JSON Unmarshaler interface in order to be able parse string to time.Duration. 81 func (d *Duration) UnmarshalJSON(b []byte) error { 82 if err := json.Unmarshal(b, &d.Duration); err == nil { 83 // b was an integer number of nanoseconds. 84 return nil 85 } 86 // b was not an integer. Assume that it is a duration string. 87 88 var str string 89 err := json.Unmarshal(b, &str) 90 if err != nil { 91 return err 92 } 93 94 pd, err := time.ParseDuration(str) 95 if err != nil { 96 return err 97 } 98 d.Duration = &pd 99 return nil 100 } 101 102 // Resource abstracts any resource type that can be tracked by boskos 103 type Resource struct { 104 Type string `json:"type"` 105 Name string `json:"name"` 106 State string `json:"state"` 107 Owner string `json:"owner"` 108 LastUpdate time.Time `json:"lastupdate"` 109 // Customized UserData 110 UserData *UserData `json:"userdata"` 111 // Used to clean up dynamic resources 112 ExpirationDate *time.Time `json:"expiration-date,omitempty"` 113 } 114 115 // ResourceEntry is resource config format defined from config.yaml 116 type ResourceEntry struct { 117 Type string `json:"type"` 118 State string `json:"state"` 119 Names []string `json:"names"` 120 MaxCount int `json:"max-count,omitempty"` 121 MinCount int `json:"min-count,omitempty"` 122 LifeSpan *Duration `json:"lifespan,omitempty"` 123 Config ConfigType `json:"config,omitempty"` 124 Needs ResourceNeeds `json:"needs,omitempty"` 125 } 126 127 func (re *ResourceEntry) IsDRLC() bool { 128 return len(re.Names) == 0 129 } 130 131 // BoskosConfig defines config used by boskos server 132 type BoskosConfig struct { 133 Resources []ResourceEntry `json:"resources"` 134 } 135 136 // Metric contains analytics about a specific resource type 137 type Metric struct { 138 Type string `json:"type"` 139 Current map[string]int `json:"current"` 140 Owners map[string]int `json:"owner"` 141 // TODO: implements state transition metrics 142 } 143 144 // NewMetric returns a new Metric struct. 145 func NewMetric(rtype string) Metric { 146 return Metric{ 147 Type: rtype, 148 Current: map[string]int{}, 149 Owners: map[string]int{}, 150 } 151 } 152 153 // NewResource creates a new Boskos Resource. 154 func NewResource(name, rtype, state, owner string, t time.Time) Resource { 155 // If no state defined, mark as Free 156 if state == "" { 157 state = Free 158 } 159 return Resource{ 160 Name: name, 161 Type: rtype, 162 State: state, 163 Owner: owner, 164 LastUpdate: t, 165 } 166 } 167 168 // NewResourcesFromConfig parse the a ResourceEntry into a list of resources 169 func NewResourcesFromConfig(e ResourceEntry) []Resource { 170 var resources []Resource 171 for _, name := range e.Names { 172 resources = append(resources, NewResource(name, e.Type, e.State, "", time.Time{})) 173 } 174 return resources 175 } 176 177 // UserDataFromMap returns a UserData from a map 178 func UserDataFromMap(m UserDataMap) *UserData { 179 ud := &UserData{} 180 for k, v := range m { 181 ud.Store(k, v) 182 } 183 return ud 184 } 185 186 // UserDataNotFound will be returned if requested resource does not exist. 187 type UserDataNotFound struct { 188 ID string 189 } 190 191 func (ud *UserDataNotFound) Error() string { 192 return fmt.Sprintf("user data ID %s does not exist", ud.ID) 193 } 194 195 // ResourceByName helps sorting resources by name 196 type ResourceByName []Resource 197 198 func (ut ResourceByName) Len() int { return len(ut) } 199 func (ut ResourceByName) Swap(i, j int) { ut[i], ut[j] = ut[j], ut[i] } 200 func (ut ResourceByName) Less(i, j int) bool { return ut[i].Name < ut[j].Name } 201 202 // CommaSeparatedStrings is used to parse comma separated string flag into a list of strings 203 type CommaSeparatedStrings []string 204 205 func (r *CommaSeparatedStrings) String() string { 206 return fmt.Sprint(*r) 207 } 208 209 // Set parses the flag value into a CommaSeparatedStrings 210 func (r *CommaSeparatedStrings) Set(value string) error { 211 if len(*r) > 0 { 212 return errors.New("resTypes flag already set") 213 } 214 for _, rtype := range strings.Split(value, ",") { 215 *r = append(*r, rtype) 216 } 217 return nil 218 } 219 220 func (r *CommaSeparatedStrings) Type() string { 221 return "commaSeparatedStrings" 222 } 223 224 // UnmarshalJSON implements JSON Unmarshaler interface 225 func (ud *UserData) UnmarshalJSON(data []byte) error { 226 tmpMap := UserDataMap{} 227 if err := json.Unmarshal(data, &tmpMap); err != nil { 228 return err 229 } 230 ud.FromMap(tmpMap) 231 return nil 232 } 233 234 // MarshalJSON implements JSON Marshaler interface 235 func (ud *UserData) MarshalJSON() ([]byte, error) { 236 return json.Marshal(ud.ToMap()) 237 } 238 239 // Extract unmarshalls a string a given struct if it exists 240 func (ud *UserData) Extract(id string, out interface{}) error { 241 content, ok := ud.Load(id) 242 if !ok { 243 return &UserDataNotFound{id} 244 } 245 return yaml.Unmarshal([]byte(content.(string)), out) 246 } 247 248 // User Data are used to store custom information mainly by Mason and Masonable implementation. 249 // Mason used a LeasedResource keys to store information about other resources that used to 250 // create the given resource. 251 252 // Set marshalls a struct to a string into the UserData 253 func (ud *UserData) Set(id string, in interface{}) error { 254 b, err := yaml.Marshal(in) 255 if err != nil { 256 return err 257 } 258 ud.Store(id, string(b)) 259 return nil 260 } 261 262 // Update updates existing UserData with new UserData. 263 // If a key as an empty string, the key will be deleted 264 func (ud *UserData) Update(new *UserData) *UserData { 265 if new == nil { 266 return ud 267 } 268 new.Range(func(key, value interface{}) bool { 269 if value.(string) != "" { 270 ud.Store(key, value) 271 } else { 272 ud.Delete(key) 273 } 274 return true 275 }) 276 return ud 277 } 278 279 // ToMap converts a UserData to UserDataMap 280 func (ud *UserData) ToMap() UserDataMap { 281 if ud == nil { 282 return nil 283 } 284 m := UserDataMap{} 285 ud.Range(func(key, value interface{}) bool { 286 m[key.(string)] = value.(string) 287 return true 288 }) 289 return m 290 } 291 292 // FromMap feels updates user data from a map 293 func (ud *UserData) FromMap(m UserDataMap) { 294 for key, value := range m { 295 ud.Store(key, value) 296 } 297 } 298 299 func ResourceTypeNotFoundMessage(rType string) string { 300 return fmt.Sprintf("resource type %q does not exist", rType) 301 }