github.com/yrj2011/jx-test-infra@v0.0.0-20190529031832-7a2065ee98eb/boskos/client/client.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 client 18 19 import ( 20 "bytes" 21 "encoding/json" 22 "fmt" 23 "io" 24 "io/ioutil" 25 "net/http" 26 "strings" 27 "time" 28 29 "sync" 30 31 "github.com/hashicorp/go-multierror" 32 "github.com/sirupsen/logrus" 33 "k8s.io/test-infra/boskos/common" 34 "k8s.io/test-infra/boskos/storage" 35 ) 36 37 // Client defines the public Boskos client object 38 type Client struct { 39 owner string 40 url string 41 lock sync.Mutex 42 43 storage storage.PersistenceLayer 44 } 45 46 // NewClient creates a boskos client, with boskos url and owner of the client. 47 func NewClient(owner string, url string) *Client { 48 49 client := &Client{ 50 url: url, 51 owner: owner, 52 storage: storage.NewMemoryStorage(), 53 } 54 55 return client 56 } 57 58 // public method 59 60 // Acquire asks boskos for a resource of certain type in certain state, and set the resource to dest state. 61 // Returns the resource on success. 62 func (c *Client) Acquire(rtype, state, dest string) (*common.Resource, error) { 63 r, err := c.acquire(rtype, state, dest) 64 if err != nil { 65 return nil, err 66 } 67 c.lock.Lock() 68 defer c.lock.Unlock() 69 if r != nil { 70 c.storage.Add(*r) 71 } 72 73 return r, nil 74 } 75 76 // AcquireByState asks boskos for a resources of certain type, and set the resource to dest state. 77 // Returns a list of resources on success. 78 func (c *Client) AcquireByState(state, dest string, names []string) ([]common.Resource, error) { 79 resources, err := c.acquireByState(state, dest, names) 80 if err != nil { 81 return nil, err 82 } 83 c.lock.Lock() 84 defer c.lock.Unlock() 85 for _, r := range resources { 86 c.storage.Add(r) 87 } 88 return resources, nil 89 } 90 91 // ReleaseAll returns all resources hold by the client back to boskos and set them to dest state. 92 func (c *Client) ReleaseAll(dest string) error { 93 c.lock.Lock() 94 defer c.lock.Unlock() 95 resources, err := c.storage.List() 96 if err != nil { 97 return err 98 } 99 if len(resources) == 0 { 100 return fmt.Errorf("no holding resource") 101 } 102 var allErrors error 103 for _, r := range resources { 104 c.storage.Delete(r.GetName()) 105 err := c.release(r.GetName(), dest) 106 if err != nil { 107 allErrors = multierror.Append(allErrors, err) 108 } 109 } 110 return allErrors 111 } 112 113 // ReleaseOne returns one of owned resources back to boskos and set it to dest state. 114 func (c *Client) ReleaseOne(name, dest string) error { 115 c.lock.Lock() 116 defer c.lock.Unlock() 117 118 if _, err := c.storage.Get(name); err != nil { 119 return fmt.Errorf("no resource name %v", name) 120 } 121 c.storage.Delete(name) 122 if err := c.release(name, dest); err != nil { 123 return err 124 } 125 return nil 126 } 127 128 // UpdateAll signals update for all resources hold by the client. 129 func (c *Client) UpdateAll(state string) error { 130 c.lock.Lock() 131 defer c.lock.Unlock() 132 133 resources, err := c.storage.List() 134 if err != nil { 135 return err 136 } 137 if len(resources) == 0 { 138 return fmt.Errorf("no holding resource") 139 } 140 var allErrors error 141 for _, r := range resources { 142 if err := c.update(r.GetName(), state, nil); err != nil { 143 allErrors = multierror.Append(allErrors, err) 144 continue 145 } 146 if err := c.updateLocalResource(r, state, nil); err != nil { 147 allErrors = multierror.Append(allErrors, err) 148 } 149 } 150 return allErrors 151 } 152 153 // SyncAll signals update for all resources hold by the client. 154 func (c *Client) SyncAll() error { 155 c.lock.Lock() 156 defer c.lock.Unlock() 157 158 resources, err := c.storage.List() 159 if err != nil { 160 return err 161 } 162 if len(resources) == 0 { 163 logrus.Info("no resource to sync") 164 return nil 165 } 166 var allErrors error 167 for _, i := range resources { 168 r, err := common.ItemToResource(i) 169 if err != nil { 170 allErrors = multierror.Append(allErrors, err) 171 continue 172 } 173 if err := c.update(r.Name, r.State, nil); err != nil { 174 allErrors = multierror.Append(allErrors, err) 175 continue 176 } 177 if err := c.storage.Update(r); err != nil { 178 allErrors = multierror.Append(allErrors, err) 179 } 180 } 181 return allErrors 182 } 183 184 // UpdateOne signals update for one of the resources hold by the client. 185 func (c *Client) UpdateOne(name, state string, userData *common.UserData) error { 186 c.lock.Lock() 187 defer c.lock.Unlock() 188 189 r, err := c.storage.Get(name) 190 if err != nil { 191 return fmt.Errorf("no resource name %v", name) 192 } 193 if err := c.update(r.GetName(), state, userData); err != nil { 194 return err 195 } 196 return c.updateLocalResource(r, state, userData) 197 } 198 199 // Reset will scan all boskos resources of type, in state, last updated before expire, and set them to dest state. 200 // Returns a map of {resourceName:owner} for further actions. 201 func (c *Client) Reset(rtype, state string, expire time.Duration, dest string) (map[string]string, error) { 202 return c.reset(rtype, state, expire, dest) 203 } 204 205 // Metric will query current metric for target resource type. 206 // Return a common.Metric object on success. 207 func (c *Client) Metric(rtype string) (common.Metric, error) { 208 return c.metric(rtype) 209 } 210 211 // HasResource tells if current client holds any resources 212 func (c *Client) HasResource() bool { 213 resources, _ := c.storage.List() 214 return len(resources) > 0 215 } 216 217 // private methods 218 219 func (c *Client) updateLocalResource(i common.Item, state string, data *common.UserData) error { 220 res, err := common.ItemToResource(i) 221 if err != nil { 222 return err 223 } 224 res.State = state 225 if res.UserData == nil { 226 res.UserData = data 227 } else { 228 res.UserData.Update(data) 229 } 230 231 return c.storage.Update(res) 232 } 233 234 func (c *Client) acquire(rtype, state, dest string) (*common.Resource, error) { 235 resp, err := http.Post(fmt.Sprintf("%v/acquire?type=%v&state=%v&dest=%v&owner=%v", 236 c.url, rtype, state, dest, c.owner), "", nil) 237 if err != nil { 238 return nil, err 239 } 240 defer resp.Body.Close() 241 242 if resp.StatusCode == http.StatusOK { 243 body, err := ioutil.ReadAll(resp.Body) 244 if err != nil { 245 return nil, err 246 } 247 248 res := common.Resource{} 249 err = json.Unmarshal(body, &res) 250 if err != nil { 251 return nil, err 252 } 253 if res.Name == "" { 254 return nil, fmt.Errorf("unable to parse resource") 255 } 256 return &res, nil 257 } else if resp.StatusCode == http.StatusNotFound { 258 return nil, fmt.Errorf("resource not found") 259 } 260 return nil, fmt.Errorf("status %s, status code %v", resp.Status, resp.StatusCode) 261 } 262 263 func (c *Client) acquireByState(state, dest string, names []string) ([]common.Resource, error) { 264 resp, err := http.Post(fmt.Sprintf("%v/acquirebystate?state=%v&dest=%v&owner=%v&names=%v", 265 c.url, state, dest, c.owner, strings.Join(names, ",")), "", nil) 266 if err != nil { 267 return nil, err 268 } 269 defer resp.Body.Close() 270 271 switch resp.StatusCode { 272 case http.StatusOK: 273 var resources []common.Resource 274 if err := json.NewDecoder(resp.Body).Decode(&resources); err != nil { 275 return nil, err 276 } 277 return resources, nil 278 case http.StatusUnauthorized: 279 return nil, fmt.Errorf("resources already used by another user") 280 281 case http.StatusNotFound: 282 return nil, fmt.Errorf("resources not found") 283 } 284 return nil, fmt.Errorf("status %s, status code %v", resp.Status, resp.StatusCode) 285 } 286 287 func (c *Client) release(name, dest string) error { 288 resp, err := http.Post(fmt.Sprintf("%v/release?name=%v&dest=%v&owner=%v", 289 c.url, name, dest, c.owner), "", nil) 290 if err != nil { 291 return err 292 } 293 defer resp.Body.Close() 294 295 if resp.StatusCode != http.StatusOK { 296 return fmt.Errorf("status %s, statusCode %v releasing %s", resp.Status, resp.StatusCode, name) 297 } 298 return nil 299 } 300 301 func (c *Client) update(name, state string, userData *common.UserData) error { 302 var body io.Reader 303 if userData != nil { 304 b := new(bytes.Buffer) 305 err := json.NewEncoder(b).Encode(userData) 306 if err != nil { 307 return err 308 } 309 body = b 310 } 311 resp, err := http.Post(fmt.Sprintf("%v/update?name=%v&owner=%v&state=%v", 312 c.url, name, c.owner, state), "application/json", body) 313 if err != nil { 314 return err 315 } 316 defer resp.Body.Close() 317 318 if resp.StatusCode != http.StatusOK { 319 return fmt.Errorf("status %s, status code %v updating %s", resp.Status, resp.StatusCode, name) 320 } 321 return nil 322 } 323 324 func (c *Client) reset(rtype, state string, expire time.Duration, dest string) (map[string]string, error) { 325 rmap := make(map[string]string) 326 resp, err := http.Post(fmt.Sprintf("%v/reset?type=%v&state=%v&expire=%v&dest=%v", 327 c.url, rtype, state, expire.String(), dest), "", nil) 328 if err != nil { 329 return rmap, err 330 } 331 defer resp.Body.Close() 332 333 if resp.StatusCode == http.StatusOK { 334 body, err := ioutil.ReadAll(resp.Body) 335 if err != nil { 336 return rmap, err 337 } 338 339 err = json.Unmarshal(body, &rmap) 340 return rmap, err 341 } 342 343 return rmap, fmt.Errorf("status %s, status code %v", resp.Status, resp.StatusCode) 344 } 345 346 func (c *Client) metric(rtype string) (common.Metric, error) { 347 var metric common.Metric 348 resp, err := http.Get(fmt.Sprintf("%v/metric?type=%v", c.url, rtype)) 349 if err != nil { 350 return metric, err 351 } 352 defer resp.Body.Close() 353 354 if resp.StatusCode != http.StatusOK { 355 return metric, fmt.Errorf("status %s, status code %v", resp.Status, resp.StatusCode) 356 } 357 358 body, err := ioutil.ReadAll(resp.Body) 359 if err != nil { 360 return metric, err 361 } 362 363 err = json.Unmarshal(body, &metric) 364 return metric, err 365 }