Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 57 additions & 1 deletion api/types/load_traffic.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,11 @@

package types

import "fmt"
import (
"encoding/json"
"fmt"
"strings"
)

// ContentType represents the format of response.
type ContentType string
Expand Down Expand Up @@ -87,6 +91,8 @@
QuorumGet *RequestGet `json:"quorumGet,omitempty" yaml:"quorumGet,omitempty"`
// Put means this is mutating request.
Put *RequestPut `json:"put,omitempty" yaml:"put,omitempty"`
// Patch means this is mutating request to update resource.
Patch *RequestPatch `json:"patch,omitempty" yaml:"patch,omitempty"`
// GetPodLog means this is to get log from target pod.
GetPodLog *RequestGetPodLog `json:"getPodLog,omitempty" yaml:"getPodLog,omitempty"`
}
Expand Down Expand Up @@ -146,6 +152,19 @@
ValueSize int `json:"valueSize" yaml:"valueSize"`
}

// RequestPatch defines PATCH request for target resource type.
type RequestPatch struct {
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"`
// PatchType is the type of patch, e.g. "json", "merge", "strategic-merge".
PatchType string `json:"patchType" yaml:"patchType"`
// Body is the request body, for fields to be changed.
Body string `json:"body" yaml:"body"`
}

// RequestGetPodLog defines GetLog request for target pod.
type RequestGetPodLog struct {
// Namespace is pod's namespace.
Expand Down Expand Up @@ -221,6 +240,8 @@
return r.QuorumGet.Validate()
case r.Put != nil:
return r.Put.Validate()
case r.Patch != nil:
return r.Patch.Validate()
case r.GetPodLog != nil:
return r.GetPodLog.Validate()
default:
Expand Down Expand Up @@ -304,3 +325,38 @@
}
return nil
}

var PatchTypeMapping = map[string]string{
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion adding function instead of exported var.

func GetCliJSONPatchType(name string) (string, bool) {
        switch name {
        case "json":
             return "application/json-patch+json", false
        ...
}

You can handle it in the following-up.

"json": "application/json-patch+json", // RFC 6902 JSON Patch

Check failure on line 330 in api/types/load_traffic.go

View workflow job for this annotation

GitHub Actions / linter

File is not properly formatted (gofmt)
"merge": "application/merge-patch+json", // RFC 7396 JSON Merge Patch
"strategic-merge": "application/strategic-merge-patch+json", // Kubernetes Strategic Merge
}

// Validate validates RequestPatch type.
func (r *RequestPatch) 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")
}
if r.Body == "" {
return fmt.Errorf("body is required")
}

// Validate patch type
_, ok := PatchTypeMapping[r.PatchType]
if !ok {
return fmt.Errorf("unknown patch type: %s (valid types: json, merge, strategic-merge)", r.PatchType)
}

// Validate JSON body and trim it
trimmed := strings.TrimSpace(r.Body)
if !json.Valid([]byte(trimmed)) {
return fmt.Errorf("invalid JSON in patch body: %q", r.Body)
}

Comment on lines +347 to +358
Copy link
Contributor Author

@vittoriasalim vittoriasalim Jul 21, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed #187 (comment) #187 (comment)

Move validation to Request Patch Validate()
Error handling for incorrect patch types. If an invalid patch type is provided, the system will throw following error:

kperf: idx: 0 request: unknown patch type: strategic-mergd (valid types: json, merge, strategic-merge)

r.Body = trimmed // Store the trimmed body

return nil
}
55 changes: 55 additions & 0 deletions request/random.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
apitypes "k8s.io/apimachinery/pkg/types"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
)
Expand Down Expand Up @@ -56,6 +57,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.Patch != nil:
builder = newRequestPatchBuilder(r.Patch, "", spec.MaxRetries)
default:
return nil, fmt.Errorf("not implement for PUT yet")
}
Expand Down Expand Up @@ -354,6 +357,58 @@ func (b *requestGetPodLogBuilder) Build(cli rest.Interface) Requester {
}
}

type requestPatchBuilder struct {
version schema.GroupVersion
resource string
resourceVersion string
namespace string
name string
patchType apitypes.PatchType
body interface{}
maxRetries int
}

func newRequestPatchBuilder(src *types.RequestPatch, resourceVersion string, maxRetries int) *requestPatchBuilder {

return &requestPatchBuilder{
version: schema.GroupVersion{
Group: src.Group,
Version: src.Version,
},
resource: src.Resource,
resourceVersion: resourceVersion,
namespace: src.Namespace,
name: src.Name,
patchType: apitypes.PatchType(types.PatchTypeMapping[src.PatchType]),
body: []byte(src.Body),
maxRetries: maxRetries,
}
}

// Build implements RequestBuilder.Build.
func (b *requestPatchBuilder) 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, b.name)

return &DiscardRequester{
BaseRequester: BaseRequester{
method: "PATCH",
req: cli.Patch(b.patchType).AbsPath(comps...).
Body(b.body).
MaxRetries(b.maxRetries),
},
}
}

func toPtr[T any](v T) *T {
return &v
}
Loading