api/types/load_traffic.go (190 lines of code) (raw):
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
package types
import "fmt"
// ContentType represents the format of response.
type ContentType string
const (
// ContentTypeJSON means the format is json.
ContentTypeJSON ContentType = "json"
// ContentTypeProtobuffer means the format is protobuf.
ContentTypeProtobuffer = "protobuf"
)
// Validate returns error if ContentType is not supported.
func (ct ContentType) Validate() error {
switch ct {
case ContentTypeJSON, ContentTypeProtobuffer:
return nil
default:
return fmt.Errorf("unsupported content type %s", ct)
}
}
// LoadProfile defines how to create load traffic from one host to kube-apiserver.
type LoadProfile struct {
// Version defines the version of this object.
Version int `json:"version" yaml:"version"`
// Description is a string value to describe this object.
Description string `json:"description,omitempty" yaml:"description"`
// Spec defines behavior of load profile.
Spec LoadProfileSpec `json:"spec" yaml:"spec"`
}
// LoadProfileSpec defines the load traffic for traget resource.
type LoadProfileSpec struct {
// Rate defines the maximum requests per second (zero is no limit).
Rate float64 `json:"rate" yaml:"rate"`
// Total defines the total number of requests.
Total int `json:"total" yaml:"total"`
// Conns defines total number of long connections used for traffic.
Conns int `json:"conns" yaml:"conns"`
// Client defines total number of HTTP clients.
Client int `json:"client" yaml:"client"`
// ContentType defines response's content type.
ContentType ContentType `json:"contentType" yaml:"contentType"`
// DisableHTTP2 means client will use HTTP/1.1 protocol if it's true.
DisableHTTP2 bool `json:"disableHTTP2" yaml:"disableHTTP2"`
// MaxRetries makes the request use the given integer as a ceiling of
// retrying upon receiving "Retry-After" headers and 429 status-code
// in the response (<= 0 means no retry).
MaxRetries int `json:"maxRetries" yaml:"maxRetries"`
// Requests defines the different kinds of requests with weights.
// The executor should randomly pick by weight.
Requests []*WeightedRequest
}
// KubeGroupVersionResource identifies the resource URI.
type KubeGroupVersionResource struct {
// Group is the name about a collection of related functionality.
Group string `json:"group" yaml:"group"`
// Version is a version of that group.
Version string `json:"version" yaml:"version"`
// Resource is a type in that versioned group APIs.
Resource string `json:"resource" yaml:"resource"`
}
// WeightedRequest represents request with weight.
// Only one of request types may be specified.
type WeightedRequest struct {
// Shares defines weight in the same group.
Shares int `json:"shares" yaml:"shares"`
// StaleList means this list request with zero resource version.
StaleList *RequestList `json:"staleList,omitempty" yaml:"staleList,omitempty"`
// QuorumList means this list request without kube-apiserver cache.
QuorumList *RequestList `json:"quorumList,omitempty" yaml:"quorumList,omitempty"`
// WatchList lists objects with the watch list feature, a.k.a streaming list.
WatchList *RequestWatchList `json:"watchList,omitempty" yaml:"watchList,omitempty"`
// StaleGet means this get request with zero resource version.
StaleGet *RequestGet `json:"staleGet,omitempty" yaml:"staleGet,omitempty"`
// QuorumGet means this get request without kube-apiserver cache.
QuorumGet *RequestGet `json:"quorumGet,omitempty" yaml:"quorumGet,omitempty"`
// Put means this is mutating request.
Put *RequestPut `json:"put,omitempty" yaml:"put,omitempty"`
// GetPodLog means this is to get log from target pod.
GetPodLog *RequestGetPodLog `json:"getPodLog,omitempty" yaml:"getPodLog,omitempty"`
}
// RequestGet defines GET request for target object.
type RequestGet struct {
// KubeGroupVersionResource identifies the resource URI.
KubeGroupVersionResource `yaml:",inline"`
// Namespace is object's namespace.
Namespace string `json:"namespace" yaml:"namespace"`
// Name is object's name.
Name string `json:"name" yaml:"name"`
}
// RequestList defines LIST request for target objects.
type RequestList struct {
// KubeGroupVersionResource identifies the resource URI.
KubeGroupVersionResource `yaml:",inline"`
// Namespace is object's namespace.
Namespace string `json:"namespace" yaml:"namespace"`
// Limit defines the page size.
Limit int `json:"limit" yaml:"limit"`
// Selector defines how to identify a set of objects.
Selector string `json:"seletor" yaml:"seletor"`
// FieldSelector defines how to identify a set of objects with field selector.
FieldSelector string `json:"fieldSelector" yaml:"fieldSelector"`
}
type RequestWatchList struct {
// KubeGroupVersionResource identifies the resource URI.
KubeGroupVersionResource `yaml:",inline"`
// Namespace is object's namespace.
Namespace string `json:"namespace" yaml:"namespace"`
// Selector defines how to identify a set of objects.
Selector string `json:"selector" yaml:"selector"`
// FieldSelector defines how to identify a set of objects with field selector.
FieldSelector string `json:"fieldSelector" yaml:"fieldSelector"`
}
// RequestPut defines PUT request for target resource type.
type RequestPut struct {
// KubeGroupVersionResource identifies the resource URI.
//
// NOTE: Currently, it should be configmap or secrets because we can
// generate random bytes as blob for it. However, for the pod resource,
// we need to ensure a lot of things are ready, for instance, volumes,
// resource capacity. It's not easy to generate it randomly. Maybe we
// can introduce pod template in the future.
KubeGroupVersionResource `yaml:",inline"`
// Namespace is object's namespace.
Namespace string `json:"namespace" yaml:"namespace"`
// Name is object's prefix name.
Name string `json:"name" yaml:"name"`
// KeySpaceSize is used to generate random number as name's suffix.
KeySpaceSize int `json:"keySpaceSize" yaml:"keySpaceSize"`
// ValueSize is the object's size in bytes.
ValueSize int `json:"valueSize" yaml:"valueSize"`
}
// RequestGetPodLog defines GetLog request for target pod.
type RequestGetPodLog struct {
// Namespace is pod's namespace.
Namespace string `json:"namespace" yaml:"namespace"`
// Name is pod's name.
Name string `json:"name" yaml:"name"`
// Container is target for stream logs. If empty, it's only valid
// when there is only one container.
Container string `json:"container" yaml:"container"`
// TailLines is the number of lines from the end of the logs to show,
// if set.
TailLines *int64 `json:"tailLines" yaml:"tailLines"`
// LimitBytes is the number of bytes to read from the server before
// terminating the log output, if set.
LimitBytes *int64 `json:"limitBytes" yaml:"limitBytes"`
}
// Validate verifies fields of LoadProfile.
func (lp LoadProfile) Validate() error {
if lp.Version != 1 {
return fmt.Errorf("version should be 1")
}
return lp.Spec.Validate()
}
// Validate verifies fields of LoadProfileSpec.
func (spec LoadProfileSpec) Validate() error {
if spec.Conns <= 0 {
return fmt.Errorf("conns requires > 0: %v", spec.Conns)
}
if spec.Rate < 0 {
return fmt.Errorf("rate requires >= 0: %v", spec.Rate)
}
if spec.Total <= 0 {
return fmt.Errorf("total requires > 0: %v", spec.Total)
}
if spec.Client <= 0 {
return fmt.Errorf("client requires > 0: %v", spec.Client)
}
err := spec.ContentType.Validate()
if err != nil {
return err
}
for idx, req := range spec.Requests {
if err := req.Validate(); err != nil {
return fmt.Errorf("idx: %v request: %v", idx, err)
}
}
return nil
}
// Validate verifies fields of WeightedRequest.
func (r WeightedRequest) Validate() error {
if r.Shares < 0 {
return fmt.Errorf("shares(%v) requires >= 0", r.Shares)
}
switch {
case r.StaleList != nil:
return r.StaleList.Validate(true)
case r.QuorumList != nil:
return r.QuorumList.Validate(false)
case r.WatchList != nil:
return r.WatchList.Validate()
case r.StaleGet != nil:
return r.StaleGet.Validate()
case r.QuorumGet != nil:
return r.QuorumGet.Validate()
case r.Put != nil:
return r.Put.Validate()
case r.GetPodLog != nil:
return r.GetPodLog.Validate()
default:
return fmt.Errorf("empty request value")
}
}
// RequestList validates RequestList type.
func (r *RequestList) Validate(stale bool) error {
if err := r.KubeGroupVersionResource.Validate(); err != nil {
return fmt.Errorf("kube metadata: %v", err)
}
if r.Limit < 0 {
return fmt.Errorf("limit must >= 0")
}
if stale && r.Limit != 0 {
return fmt.Errorf("stale list doesn't support pagination option: https://github.com/kubernetes/kubernetes/issues/108003")
}
return nil
}
func (r *RequestWatchList) Validate() error {
if err := r.KubeGroupVersionResource.Validate(); err != nil {
return fmt.Errorf("kube metadata: %v", err)
}
return nil
}
// Validate validates RequestGet type.
func (r *RequestGet) Validate() error {
if err := r.KubeGroupVersionResource.Validate(); err != nil {
return fmt.Errorf("kube metadata: %v", err)
}
if r.Name == "" {
return fmt.Errorf("name is required")
}
return nil
}
// Validate validates RequestPut type.
func (r *RequestPut) Validate() error {
if err := r.KubeGroupVersionResource.Validate(); err != nil {
return fmt.Errorf("kube metadata: %v", err)
}
// TODO: check resource type
if r.Name == "" {
return fmt.Errorf("name pattern is required")
}
if r.KeySpaceSize <= 0 {
return fmt.Errorf("keySpaceSize must > 0")
}
if r.ValueSize <= 0 {
return fmt.Errorf("valueSize must > 0")
}
return nil
}
// Validate validates RequestGetPodLog type.
func (r *RequestGetPodLog) Validate() error {
if r.Namespace == "" {
return fmt.Errorf("namespace is required")
}
if r.Name == "" {
return fmt.Errorf("name is required")
}
return nil
}
// Validate validates KubeGroupVersionResource.
func (m *KubeGroupVersionResource) Validate() error {
if m.Version == "" {
return fmt.Errorf("version is required")
}
if m.Resource == "" {
return fmt.Errorf("resource is required")
}
return nil
}