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  }