diff --git a/api/types/load_traffic.go b/api/types/load_traffic.go index 009fdef9..d874cafd 100644 --- a/api/types/load_traffic.go +++ b/api/types/load_traffic.go @@ -87,6 +87,8 @@ type WeightedRequest struct { QuorumGet *RequestGet `json:"quorumGet,omitempty" yaml:"quorumGet,omitempty"` // Put means this is mutating request. Put *RequestPut `json:"put,omitempty" yaml:"put,omitempty"` + // Post means this is resource creation request. + Post *RequestPost `json:"post,omitempty" yaml:"post,omitempty"` // GetPodLog means this is to get log from target pod. GetPodLog *RequestGetPodLog `json:"getPodLog,omitempty" yaml:"getPodLog,omitempty"` } @@ -146,6 +148,17 @@ type RequestPut struct { ValueSize int `json:"valueSize" yaml:"valueSize"` } +// RequestPost defines POST request for resource creation +type RequestPost struct { + // KubeGroupVersionResource identifies the resource URI. + KubeGroupVersionResource `yaml:",inline"` + // Namespace is object's namespace. + Namespace string `json:"namespace" yaml:"namespace"` + // Body is the request body, for resource creation. + // TODO (vittoria): replace with resource template ...e.g workload/deployments/.../deployments.tpl + Body string `json:"body,omitempty" yaml:"body,omitempty"` +} + // RequestGetPodLog defines GetLog request for target pod. type RequestGetPodLog struct { // Namespace is pod's namespace. @@ -221,6 +234,8 @@ func (r WeightedRequest) Validate() error { return r.QuorumGet.Validate() case r.Put != nil: return r.Put.Validate() + case r.Post != nil: + return r.Post.Validate() case r.GetPodLog != nil: return r.GetPodLog.Validate() default: @@ -282,6 +297,17 @@ func (r *RequestPut) Validate() error { return nil } +// Validate validates RequestPost type. +func (r *RequestPost) Validate() error { + if err := r.KubeGroupVersionResource.Validate(); err != nil { + return fmt.Errorf("kube metadata: %v", err) + } + if r.Body == "" { + return fmt.Errorf("body is required") + } + return nil +} + // Validate validates RequestGetPodLog type. func (r *RequestGetPodLog) Validate() error { if r.Namespace == "" { diff --git a/request/random.go b/request/random.go index 1cd29e18..27093876 100644 --- a/request/random.go +++ b/request/random.go @@ -6,8 +6,10 @@ package request import ( "context" "crypto/rand" + "encoding/json" "fmt" "math/big" + "strings" "sync" "github.com/Azure/kperf/api/types" @@ -56,6 +58,8 @@ func NewWeightedRandomRequests(spec *types.LoadProfileSpec) (*WeightedRandomRequ builder = newRequestGetBuilder(r.QuorumGet, "", spec.MaxRetries) case r.GetPodLog != nil: builder = newRequestGetPodLogBuilder(r.GetPodLog, spec.MaxRetries) + case r.Post != nil: + builder = newRequestPOSTBuilder(r.Post, "", spec.MaxRetries) default: return nil, fmt.Errorf("not implement for PUT yet") } @@ -354,6 +358,63 @@ func (b *requestGetPodLogBuilder) Build(cli rest.Interface) Requester { } } +type requestPOSTBuilder struct { + version schema.GroupVersion + resource string + resourceVersion string + namespace string + body interface{} // TODO (vittoria): replace with resource template + maxRetries int +} + +func newRequestPOSTBuilder(src *types.RequestPost, resourceVersion string, maxRetries int) *requestPOSTBuilder { + // TODO (vittoria): load with resource template + var body interface{} + + trimmed := strings.TrimSpace(src.Body) + if json.Valid([]byte(trimmed)) { + body = []byte(trimmed) // send raw JSON + } else { + body = trimmed // fallback to raw string + } + + return &requestPOSTBuilder{ + version: schema.GroupVersion{ + Group: src.Group, + Version: src.Version, + }, + resource: src.Resource, + resourceVersion: resourceVersion, + namespace: src.Namespace, + body: body, + maxRetries: maxRetries, + } +} + +// Build implements RequestBuilder.Build. +func (b *requestPOSTBuilder) Build(cli rest.Interface) Requester { + // https://kubernetes.io/docs/reference/using-api/#api-groups + comps := make([]string, 0, 5) + if b.version.Group == "" { + comps = append(comps, "api", b.version.Version) + } else { + comps = append(comps, "apis", b.version.Group, b.version.Version) + } + if b.namespace != "" { + comps = append(comps, "namespaces", b.namespace) + } + comps = append(comps, b.resource) + + return &DiscardRequester{ + BaseRequester: BaseRequester{ + method: "POST", + req: cli.Post().AbsPath(comps...). + Body(b.body). + MaxRetries(b.maxRetries), + }, + } +} + func toPtr[T any](v T) *T { return &v }