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  }