Skip to content

Commit

Permalink
init layout of codebase
Browse files Browse the repository at this point in the history
Signed-off-by: Wei Fu <weifu@microsoft.com>
  • Loading branch information
fuweid committed Nov 30, 2023
1 parent fc3bbb0 commit 0fee410
Show file tree
Hide file tree
Showing 12 changed files with 464 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

# Output of the go coverage tool, specifically when used with LiteIDE
*.out
bin/

# Dependency directories (remove the comment below to include it)
# vendor/
Expand Down
14 changes: 14 additions & 0 deletions .golangci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
linters:
enable:
- gofmt
- goimports
- gosec
- ineffassign
- misspell
- nolintlint
- revive
- staticcheck
- unconvert
- unused
- vet
- errcheck
29 changes: 29 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
COMMANDS=kperf

BINARIES=$(addprefix bin/,$(COMMANDS))

# default recipe is build
.DEFAULT_GOAL := build

# Always build
ALWAYS:

bin/%: cmd/% ALWAYS
@go build -o $@ ./$<

build: $(BINARIES) ## build binaries
@echo "$@"

test: ## run test
@go test -v ./...

lint: ## run lint
@golangci-lint run --config .golangci.yml

.PHONY: clean
clean: ## clean up binaries
@rm -f $(BINARIES)

.PHONY: help
help: ## this help
@awk 'BEGIN {FS = ":.*?## "} /^[a-zA-Z_-]+:.*?## / {printf "\033[36m%-36s\033[0m%s\n", $$1, $$2}' $(MAKEFILE_LIST)
91 changes: 91 additions & 0 deletions api/types/load_traffic.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package types

// 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 int `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"`
// Requests defines the different kinds of requests with weights.
// The executor should randomly pick by weight.
Requests []*WeightedRequest
}

// KubeTypeMeta represents metadata of kubernetes object.
type KubeTypeMeta struct {
// Kind is a string value representing the REST resource the object represents.
Kind string `json:"kind" yaml:"kind"`
// APIVersion defines the versioned schema of the representation of an object.
APIVersion string `json:"apiVersion" yaml:"apiVersion"`
}

// 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" yaml:"staleList"`
// QuorumList means this list request without kube-apiserver cache.
QuorumList *RequestList `json:"quorumList" yaml:"quorumList"`
// StaleGet means this get request with zero resource version.
StaleGet *RequestGet `json:"staleGet" yaml:"staleGet"`
// QuorumGet means this get request without kube-apiserver cache.
QuorumGet *RequestGet `json:"quorumGet" yaml:"quorumGet"`
// Put means this is mutating request.
Put *RequestPut `json:"put" yaml:"put"`
}

// RequestGet defines GET request for target object.
type RequestGet struct {
// KubeTypeMeta represents object's resource type.
KubeTypeMeta `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 {
// KubeTypeMeta represents object's resource type.
KubeTypeMeta `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"`
}

// RequestPut defines PUT request for target resource type.
type RequestPut struct {
// KubeTypeMeta represents object's resource type.
//
// 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.
KubeTypeMeta `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"`
}
94 changes: 94 additions & 0 deletions api/types/load_traffic_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
package types

import (
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/yaml.v2"
)

func TestLoadProfileUnmarshalFromYAML(t *testing.T) {
in := `
version: 1
description: test
spec:
rate: 100
total: 10000
conns: 2
requests:
- staleGet:
kind: pods
apiVersion: v1
namespace: default
name: x1
shares: 100
- quorumGet:
kind: configmap
apiVersion: v1
namespace: default
name: x2
shares: 150
- staleList:
kind: pods
apiVersion: v1
namespace: default
limit: 10000
seletor: app=x2
shares: 200
- quorumList:
kind: configmap
apiVersion: v1
namespace: default
limit: 10000
seletor: app=x3
shares: 400
- put:
kind: configmap
apiVersion: v1
namespace: kperf
name: kperf-
keySpaceSize: 1000
valueSize: 1024
shares: 1000
`

target := LoadProfile{}
require.NoError(t, yaml.Unmarshal([]byte(in), &target))
assert.Equal(t, 1, target.Version)
assert.Equal(t, "test", target.Description)
assert.Equal(t, 100, target.Spec.Rate)
assert.Equal(t, 10000, target.Spec.Total)
assert.Equal(t, 2, target.Spec.Conns)
assert.Len(t, target.Spec.Requests, 5)

assert.Equal(t, 100, target.Spec.Requests[0].Shares)
assert.NotNil(t, target.Spec.Requests[0].StaleGet)
assert.Equal(t, "pods", target.Spec.Requests[0].StaleGet.Kind)
assert.Equal(t, "v1", target.Spec.Requests[0].StaleGet.APIVersion)
assert.Equal(t, "default", target.Spec.Requests[0].StaleGet.Namespace)
assert.Equal(t, "x1", target.Spec.Requests[0].StaleGet.Name)

assert.NotNil(t, target.Spec.Requests[1].QuorumGet)
assert.Equal(t, 150, target.Spec.Requests[1].Shares)

assert.Equal(t, 200, target.Spec.Requests[2].Shares)
assert.NotNil(t, target.Spec.Requests[2].StaleList)
assert.Equal(t, "pods", target.Spec.Requests[2].StaleList.Kind)
assert.Equal(t, "v1", target.Spec.Requests[2].StaleList.APIVersion)
assert.Equal(t, "default", target.Spec.Requests[2].StaleList.Namespace)
assert.Equal(t, 10000, target.Spec.Requests[2].StaleList.Limit)
assert.Equal(t, "app=x2", target.Spec.Requests[2].StaleList.Selector)

assert.NotNil(t, target.Spec.Requests[3].QuorumList)
assert.Equal(t, 400, target.Spec.Requests[3].Shares)

assert.Equal(t, 1000, target.Spec.Requests[4].Shares)
assert.NotNil(t, target.Spec.Requests[4].Put)
assert.Equal(t, "configmap", target.Spec.Requests[4].Put.Kind)
assert.Equal(t, "v1", target.Spec.Requests[4].Put.APIVersion)
assert.Equal(t, "kperf", target.Spec.Requests[4].Put.Namespace)
assert.Equal(t, "kperf-", target.Spec.Requests[4].Put.Name)
assert.Equal(t, 1000, target.Spec.Requests[4].Put.KeySpaceSize)
assert.Equal(t, 1024, target.Spec.Requests[4].Put.ValueSize)
}
113 changes: 113 additions & 0 deletions cmd/kperf/commands/multirunners/runner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package multirunners

import (
"fmt"

"github.com/urfave/cli"
)

// Command represents multirunners sub-command.
//
// Subcommand multirunners is to deploy multiple runners as kubernetes jobs.
// Since one runner could run out of networking bandwidth on one host, the
// multirunners deploys runners on different hosts and reduces the impact of
// limited networking resource.
//
// Command line interface:
//
// kperf mrunners run --help
//
// Options:
//
// --kubeconfig PATH (default: empty_string, use token if it's empty)
// --namespace STRING (default: empty_string, required)
// --runner-image STRING (default: empty_string, required)
// --runners []STRING (default: empty, required)
// --wait BOOLEAN (default: false)
//
// Details:
//
// The --runners format is defined by URI.
//
// - file:///abs_path?numbers=10
// - configmap:///namespace/name?numbers=2
// - ...
//
// The schema:://PATH is used to get runner's configuration. It can be local
// path or stored as configmap in target kubernetes cluster. The query part is
// to define what the job looks like. Currently, that command just requires
// the number of pods in that job. At the beginning, we just need to file://.
// The number of runners defines the number of jobs.
//
// All the jobs are referenced by one configmap (ownerReference). The configmap
// name will be output to stdout. The name is progress tracker ID. By default,
// there is only one progress tracker ID in one namespace.
//
// kperf mrunners wait --help
//
// Args:
//
// 0: namespace (STRING)
//
// Options:
//
// --kubeconfig PATH (default: empty_string, use token if it's empty)
//
// Wait it to wait until jobs finish.
//
// kperf mrunners result --help
//
// Args:
//
// 0: namespace (STRING)
//
// Options:
//
// --kubeconfig PATH (default: empty_string, use token if it's empty)
//
// Result retrieves the result for jobs. If jobs is still running, that command
// will fail.
var Command = cli.Command{
Name: "multirunners",
ShortName: "mrunners",
Usage: "packages runner as job and deploy runners into kubernetes",
Subcommands: []cli.Command{
runCommand,
waitCommand,
resultCommand,
},
}

var runCommand = cli.Command{
Name: "run",
Flags: []cli.Flag{},
Action: func(cliCtx *cli.Context) error {
// 1. Parse options
// 2. Deploy jobs for --runners
// 3. Wait
return fmt.Errorf("run - not implemented")
},
}

var waitCommand = cli.Command{
Name: "wait",
Usage: "wait until jobs finish",
Flags: []cli.Flag{},
Action: func(cliCtx *cli.Context) error {
// 1. Check the progress tracker name
// 2. Wait for the jobs
return fmt.Errorf("wait - not implemented")
},
}

var resultCommand = cli.Command{
Name: "result",
Usage: "show the result",
Flags: []cli.Flag{},
Action: func(cliCtx *cli.Context) error {
// 1. Check the progress tracker name
// 2. Ensure the jobs finished
// 3. Output the result
return fmt.Errorf("result - not implemented")
},
}
20 changes: 20 additions & 0 deletions cmd/kperf/commands/root.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package commands

import (
"github.com/Azure/kperf/cmd/kperf/commands/multirunners"
"github.com/Azure/kperf/cmd/kperf/commands/runner"

"github.com/urfave/cli"
)

// App returns kperf application.
func App() *cli.App {
return &cli.App{
Name: "kperf",
// TODO: add more fields
Commands: []cli.Command{
runner.Command,
multirunners.Command,
},
}
}
Loading

0 comments on commit 0fee410

Please sign in to comment.