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  }