From 24dfaabedb4475ce7c94b871d9287071528ec0a8 Mon Sep 17 00:00:00 2001 From: vittoriasalim Date: Mon, 14 Jul 2025 14:27:58 +1000 Subject: [PATCH 1/4] adding POST operation to kperf --- api/types/load_traffic.go | 24 +++++++ .../manifests/loadprofile/test_mutation.yaml | 33 ++++++++++ request/random.go | 66 +++++++++++++++++++ 3 files changed, 123 insertions(+) create mode 100644 contrib/internal/manifests/loadprofile/test_mutation.yaml diff --git a/api/types/load_traffic.go b/api/types/load_traffic.go index 009fdef9..4a2344a9 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"` } @@ -145,6 +147,15 @@ type RequestPut struct { // ValueSize is the object's size in bytes. 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. + Body string `json:"body,omitempty" yaml:"body,omitempty"` +} // RequestGetPodLog defines GetLog request for target pod. type RequestGetPodLog struct { @@ -221,6 +232,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 +295,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/contrib/internal/manifests/loadprofile/test_mutation.yaml b/contrib/internal/manifests/loadprofile/test_mutation.yaml new file mode 100644 index 00000000..c7397087 --- /dev/null +++ b/contrib/internal/manifests/loadprofile/test_mutation.yaml @@ -0,0 +1,33 @@ +version: 1 +description: "test=mutation" +spec: + rate: 10 + total: 1 + conns: 10 + client: 10 + contentType: json + disableHTTP2: false + maxRetries: 0 + requests: + - post: + version: v1 + resource: pods + namespace: kperf + body: | + { + "apiVersion": "v1", + "kind": "Pod", + "metadata": { + "name": "test-mutation" + }, + "spec": { + "containers": [ + { + "name": "pause", + "image": "k8s.gcr.io/pause:3.1" + } + ] + } + } + shares: 1000 + diff --git a/request/random.go b/request/random.go index 1cd29e18..9a5c870c 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,68 @@ func (b *requestGetPodLogBuilder) Build(cli rest.Interface) Requester { } } +type requestPOSTBuilder struct { + version schema.GroupVersion + resource string + resourceVersion string + namespace string + body interface{} + maxRetries int +} + +func newRequestPOSTBuilder(src *types.RequestPost, resourceVersion string, maxRetries int) *requestPOSTBuilder { + + var body interface{} + + // Check if Body field is specified + if src.Body != "" { + trimmed := strings.TrimSpace(src.Body) + if json.Valid([]byte(trimmed)) { + body = []byte(trimmed) // send raw JSON + } else { + body = trimmed // fallback to raw string + } + } else { + // Use the entire request as body data (current behavior for backward compatibility) + body = src + } + 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 } From 7b1873a59563610344704cf83fb6e8d57ff0d0c0 Mon Sep 17 00:00:00 2001 From: vittoriasalim Date: Mon, 14 Jul 2025 14:40:30 +1000 Subject: [PATCH 2/4] remove mutation testing scenario --- .../manifests/loadprofile/test_mutation.yaml | 33 ------------------- 1 file changed, 33 deletions(-) delete mode 100644 contrib/internal/manifests/loadprofile/test_mutation.yaml diff --git a/contrib/internal/manifests/loadprofile/test_mutation.yaml b/contrib/internal/manifests/loadprofile/test_mutation.yaml deleted file mode 100644 index c7397087..00000000 --- a/contrib/internal/manifests/loadprofile/test_mutation.yaml +++ /dev/null @@ -1,33 +0,0 @@ -version: 1 -description: "test=mutation" -spec: - rate: 10 - total: 1 - conns: 10 - client: 10 - contentType: json - disableHTTP2: false - maxRetries: 0 - requests: - - post: - version: v1 - resource: pods - namespace: kperf - body: | - { - "apiVersion": "v1", - "kind": "Pod", - "metadata": { - "name": "test-mutation" - }, - "spec": { - "containers": [ - { - "name": "pause", - "image": "k8s.gcr.io/pause:3.1" - } - ] - } - } - shares: 1000 - From 12a7a737c21f819cb7e55d728e3a097ff0b27b5d Mon Sep 17 00:00:00 2001 From: vittoriasalim Date: Mon, 14 Jul 2025 14:47:17 +1000 Subject: [PATCH 3/4] fix linter --- api/types/load_traffic.go | 1 + request/random.go | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/api/types/load_traffic.go b/api/types/load_traffic.go index 4a2344a9..ec4acec4 100644 --- a/api/types/load_traffic.go +++ b/api/types/load_traffic.go @@ -147,6 +147,7 @@ type RequestPut struct { // ValueSize is the object's size in bytes. ValueSize int `json:"valueSize" yaml:"valueSize"` } + // RequestPost defines POST request for resource creation type RequestPost struct { // KubeGroupVersionResource identifies the resource URI. diff --git a/request/random.go b/request/random.go index 9a5c870c..74e1952b 100644 --- a/request/random.go +++ b/request/random.go @@ -380,9 +380,9 @@ func newRequestPOSTBuilder(src *types.RequestPost, resourceVersion string, maxRe body = trimmed // fallback to raw string } } else { - // Use the entire request as body data (current behavior for backward compatibility) - body = src - } + // Use the entire request as body data (current behavior for backward compatibility) + body = src + } return &requestPOSTBuilder{ version: schema.GroupVersion{ Group: src.Group, From 0743d3f060618868bffa5bcbc8b2d6ed2f030021 Mon Sep 17 00:00:00 2001 From: vittoriasalim Date: Wed, 16 Jul 2025 12:19:54 +1000 Subject: [PATCH 4/4] added TODO --- api/types/load_traffic.go | 1 + request/random.go | 19 +++++++------------ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/api/types/load_traffic.go b/api/types/load_traffic.go index ec4acec4..d874cafd 100644 --- a/api/types/load_traffic.go +++ b/api/types/load_traffic.go @@ -155,6 +155,7 @@ type RequestPost struct { // 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"` } diff --git a/request/random.go b/request/random.go index 74e1952b..27093876 100644 --- a/request/random.go +++ b/request/random.go @@ -363,26 +363,21 @@ type requestPOSTBuilder struct { resource string resourceVersion string namespace string - body interface{} + 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{} - // Check if Body field is specified - if src.Body != "" { - trimmed := strings.TrimSpace(src.Body) - if json.Valid([]byte(trimmed)) { - body = []byte(trimmed) // send raw JSON - } else { - body = trimmed // fallback to raw string - } + trimmed := strings.TrimSpace(src.Body) + if json.Valid([]byte(trimmed)) { + body = []byte(trimmed) // send raw JSON } else { - // Use the entire request as body data (current behavior for backward compatibility) - body = src + body = trimmed // fallback to raw string } + return &requestPOSTBuilder{ version: schema.GroupVersion{ Group: src.Group,