Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
/test
/logs
/hack/tools/bin
/pkg/simulatedprovider/bin

*.html
.vscode
Expand Down
3 changes: 3 additions & 0 deletions pkg/simulatedprovider/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Default command output directory
/gen
/bin
6 changes: 5 additions & 1 deletion pkg/simulatedprovider/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ KWOK_VERSION ?= v0.8.0
$(KWOKCTL): $(TOOLS_BIN_DIR)
GOBIN=$(abspath $(TOOLS_BIN_DIR)) go install sigs.k8s.io/kwok/cmd/{kwok,kwokctl}@$(KWOK_VERSION)


.PHONY: build
build:
@mkdir -p bin
Expand Down Expand Up @@ -47,3 +46,8 @@ stop-simulated-cluster: $(KWOKCTL)
.PHONY: test-integration
test-integration:
@(cd test/integration/controller; ginkgo -v --show-node-events --poll-progress-after=300s --poll-progress-interval=60s)

.PHONY: build-clustersim
build-clustersim:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we please also duplicate this target (only this target in top level MCM Makefile)

@mkdir -p bin

@elankath elankath Oct 6, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

a bin directory for binaries should not go inside a pkg subdirectory in Go. You can use top level project bin directory for all binaries. There is no need to have gen/bin which is even more confusing as we now have 3 different build dirs. DOn't use cluster data directory to determine binary directory. Binaries are always at fixed location and constant.

go build -v -o bin/clustersim ./cmd/clustersim
5 changes: 5 additions & 0 deletions pkg/simulatedprovider/cmd/clustersim/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Default command output directory
/gen

# Command binary
clustersim
130 changes: 130 additions & 0 deletions pkg/simulatedprovider/cmd/clustersim/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
## Clustersim

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Please add pre-requisites for clustersim tool.

Error: kwok: failed to create cluster "aw1": exit status 1: exit status 1: {"time":"2026-10-06T13:51:50.793557+05:30","level":"INFO","source":{"function":"sigs.k8s.io/kwok/pkg/kwokctl/cmd/create/cluster.runE","file":"/Users/I034796/go/pkg/mod/sigs.k8s.io/kwok@v0.5.0/pkg/kwokctl/cmd/create/cluster/cluster.go","line":266},"msg":"Cluster is creating","cluster":"aw1"}
{"time":"2026-10-06T13:51:51.18667+05:30","level":"INFO","source":{"function":"sigs.k8s.io/kwok/pkg/utils/file.getCacheOrDownload","file":"/Users/I034796/go/pkg/mod/sigs.k8s.io/kwok@v0.5.0/pkg/utils/file/download.go","line":145},"msg":"Download","cluster":"aw1","uri":"https://dl.k8s.io/release/v1.35.0/bin/darwin/arm64/kube-apiserver"}
{"time":"2026-10-06T13:51:53.40609+05:30","level":"ERROR","source":{"function":"sigs.k8s.io/kwok/pkg/kwokctl/cmd/create/cluster.runE","file":"/Users/I034796/go/pkg/mod/sigs.k8s.io/kwok@v0.5.0/pkg/kwokctl/cmd/create/cluster/cluster.go","line":269},"msg":"Failed to setup config","cluster":"aw1","err":"https://dl.k8s.io/release/v1.35.0/bin/darwin/arm64/kube-apiserver: 404 Not Found"}
{"time":"2026-10-06T13:51:53.415484+05:30","level":"INFO","source":{"function":"sigs.k8s.io/kwok/pkg/kwokctl/cmd/create/cluster.runE.func2","file":"/Users/I034796/go/pkg/mod/sigs.k8s.io/kwok@v0.5.0/pkg/kwokctl/cmd/create/cluster/cluster.go","line":247},"msg":"Cluster is cleaned up","cluster":"aw1"}
{"time":"2026-10-06T13:51:53.415536+05:30","level":"ERROR","source":{"function":"main.main","file":"/Users/I034796/go/pkg/mod/sigs.k8s.io/kwok@v0.5.0/cmd/kwokctl/main.go","line":54},"msg":"Execute exit","err":"https://dl.k8s.io/release/v1.35.0/bin/darwin/arm64/kube-apiserver: 404 Not Found"}

Error: kwok: failed to create cluster "aw1": exit status 1: exit status 1: {"time":"2026-10-06T13:51:50.793557+05:30","level":"INFO","source":{"function":"sigs.k8s.io/kwok/pkg/kwokctl/cmd/create/cluster.runE","file":"/Users/I034796/go/pkg/mod/sigs.k8s.io/kwok@v0.5.0/pkg/kwokctl/cmd/create/cluster/cluster.go","line":266},"msg":"Cluster is creating","cluster":"aw1"}
{"time":"2026-10-06T13:51:51.18667+05:30","level":"INFO","source":{"function":"sigs.k8s.io/kwok/pkg/utils/file.getCacheOrDownload","file":"/Users/I034796/go/pkg/mod/sigs.k8s.io/kwok@v0.5.0/pkg/utils/file/download.go","line":145},"msg":"Download","cluster":"aw1","uri":"https://dl.k8s.io/release/v1.35.0/bin/darwin/arm64/kube-apiserver"}
{"time":"2026-10-06T13:51:53.40609+05:30","level":"ERROR","source":{"function":"sigs.k8s.io/kwok/pkg/kwokctl/cmd/create/cluster.runE","file":"/Users/I034796/go/pkg/mod/sigs.k8s.io/kwok@v0.5.0/pkg/kwokctl/cmd/create/cluster/cluster.go","line":269},"msg":"Failed to setup config","cluster":"aw1","err":"https://dl.k8s.io/release/v1.35.0/bin/darwin/arm64/kube-apiserver: 404 Not Found"}
{"time":"2026-10-06T13:51:53.415484+05:30","level":"INFO","source":{"function":"sigs.k8s.io/kwok/pkg/kwokctl/cmd/create/cluster.runE.func2","file":"/Users/I034796/go/pkg/mod/sigs.k8s.io/kwok@v0.5.0/pkg/kwokctl/cmd/create/cluster/cluster.go","line":247},"msg":"Cluster is cleaned up","cluster":"aw1"}
{"time":"2026-10-06T13:51:53.415536+05:30","level":"ERROR","source":{"function":"main.main","file":"/Users/I034796/go/pkg/mod/sigs.k8s.io/kwok@v0.5.0/cmd/kwokctl/main.go","line":54},"msg":"Execute exit","err":"https://dl.k8s.io/release/v1.35.0/bin/darwin/arm64/kube-apiserver: 404 Not Found"}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

This is because of old kwok version present on the system: 0.5.0

I'll add a note in the README to ensure that the user cleans it up and checks that the version is atleast 0.8.0.

It is a tool that can be used for manual MCM testing, allowing a developer to create an environment where they can deploy custom `MachineClass`es and `MachineDeployment`s in a virtual environment running both the MCM and the simulated provider processes.

The command line program contains a bunch of helper sub-commands allowing one to set-up the cluster in numerous ways before they actually run any processes.

### `help`

```
./bin/clustersim -h
Manages and sets up virtual cluster running simulated Machine Controller Manager.

Usage:
clustersim {--clusters name}... command [--dir /tmp]
clustersim [command]

Examples:
clustersim --clusters "test" setup

Available Commands:
build Builds the specified components (machine-controller-manager, simulated provider).
completion Generate the autocompletion script for the specified shell
copyshoot Fetches MachineDeployments and MachineClasses from specified shoot.
gencluster Generates MachineClasses and MachineDeployments with the specified parameters.
help Help about any command
setup Generates the launch configuration for the cluster components.
start Creates kwok cluster and starts specified components using their launch config.
stop Stops specified components using their pid and destroys the kwok cluster.

Flags:
--clusters strings comma separated list of cluster(s) to target (required)
--dir string optional flag to specify the directory for clusters data (default "./gen")
-h, --help help for clustersim
```

The main command takes two flags, one optional and one required.
- The flag to specify clusters' data directory is optional, and if unspecified falls back to `./gen` directory.
- The flag to list out the cluster names is required by all subcommands except the `build` command.

### `build`

Builds the specified components, fallsback to building both `machine-controller-manager` and the `simulated-provider` if unspecifed. Requires a mandatory flag `source-dir` pointing to the `machine-controller-manager` source root directory.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be nice if source-dir had a default of ../../../../ and users can override if they intend on using a different mcm

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

The only problem is this depends on the path from where the binary is being invoked, if its from the clustersim directory the above works but anywhere else it'll just be incorrect. This was the reason why I turned it into a flag (initially I was automatically computing the root path relative to the file)

Do you still think we should have the default?


```
./bin/clustersim build -h
Builds the specified components (machine-controller-manager, simulated provider).

Usage:
clustersim build {--source-dir ..} [--components mcm]...

Examples:
clustersim build --source-dir ../../../../ --components "mcm,mc"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It is not possible to build the ca tried

Error: passed invalid component "ca", supported ones are [mcm mc]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I had mentioned in the proposal that I didn't wish to add CA support for now. Wanted to keep the current task's scope restricted to just adding IT support and basic manual testing. This can be handled later unless you feel strongly that it should also be done now.

(I'd also mentioned skipping copyshoot and gendata for current implementation but since it was somewhat necessary for testing with gardener data, I added those subcommands)


Flags:
-c, --components strings comma separated list of components to build (default [mcm,mc])

@elankath elankath Oct 6, 2026 •

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I suggest making a command build with different source directories passed --source-mcm and --source-ca. Since source directory can be different for each. Or ask user to specify env variables if this is too much work. I don't think building MC is necessary since it only builds the current simulated provider - which it should do by default for any build invocation using the --source-mcm directory and put built binaries into $source-mcm/bin.

-h, --help help for build
--source-dir string machine-controller-manager source root directory path (required)
```

### `setup`

Generates the launch configuration for the cluster components. **Requires** the `mcc.yaml` and the `mcd.yaml` files to already be present in the specified clusters' directories. These can be manually fetched by targeting a cluster or be constructed via the `gencluster` subcommand.

The launch configuration consists of the path for the components binaries and their corresponding launch flags.

### `copyshoot`

Rather than manually fetching the data for a cluster for `setup`, `clustersim` also provides a gardener shoot specific `copyshoot` subcommand which can fetch the `MachineClass`es and `MachineDeployment`s of a live cluster in an automated fashion provided the user has the required permission to access the specified shoot's data.

It can also fetch the launch flags for the components from the actual cluster as well, and sanitizes the flags for the virtual cluster's usage.

```
./bin/clustersim copyshoot -h
Fetches MachineDeployments and MachineClasses from specified shoot.

Usage:
clustersim copyshoot {--clusters name} {-l landscape} {-p project} {-s shoot} [-f true|false]

Flags:
-f, --fetch-config fetch component configuration from cluster (default true)
-h, --help help for copyshoot
-l, --landscape string gardener landscape name (required)
-p, --project string gardener project name (required)
-s, --shoot string gardener shoot name (required)
```

### `gencluster`

In case a developer needs to quickly generate some dummy data for testing, a `gencluster` helper command is provided, which takes specified `zones` and `instances` and constructs MCCs and MCDs for their cross-product. Additionally it generates the required launch configuration as well.

```
./bin/clustersim gencluster -h
Generates MachineClasses and MachineDeployments with the specified parameters.

Usage:
clustersim gencluster {--clusters name} {--zones zone}... {--instances inst}...

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I suggest supporting multiple --instance fields each with a spec like --instance m5.xlarge:amd64:2:0:8Gi . Order is name:arch:cpu:gpu:mem. Or more verbose like --instance name=m5.xlarge,cpu=2,mem=8Gi,arch=amd64, but I think this will give problem with cobra.


Flags:
-h, --help help for gencluster
-i, --instances strings comma separated list of instances to use (required)
-z, --zones strings comma separated list of zones to use (required)
```

It utilizes the templates present in [./templates](./templates) in order to construct the required mcc and mcd. And uses the data present in [./templates/instances.json](./templates/instances.json) to get the required `arch` and the `capacity` for the specified instances.

### `start`

This runs the actual virtual cluster and deploys the MCC and MCD belonging to the clusters, it also creates two files per component that it runs (mcm, machine-controller):
- `<process>.pid`: denoting the PID for the component, used by the `stop` command to kill the process for the specified cluster.
- `<process>.log`: consisting of the components' log file.

### `stop`

Kills the specified clusters' specified components and destroys the virtual cluster.

```
./bin/clustersim stop -h
Stops specified components using their pid and destroys the kwok cluster.

Usage:
clustersim stop {--clusters name}... [--components mcm]... [--killall true|false]

Examples:
clustersim --clusters "test" stop

Flags:
-c, --components strings comma separated list of components to stop (default [mcm,mc])
-h, --help help for stop
--killall stop all components and destroy all clusters (default true)
```
114 changes: 114 additions & 0 deletions pkg/simulatedprovider/cmd/clustersim/build.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
package main

import (
"bytes"
"context"
"fmt"
"os"
"os/exec"
"path/filepath"
"slices"

"github.com/spf13/cobra"
)

type BuildFlags struct {
SourceDir string
Components []string
}

var buildFlags BuildFlags

const (
mcmCmdFile = "cmd/machine-controller-manager/controller_manager.go"
providerCmdFile = "cmd/machine-controller/main.go"
)

var buildCmd = &cobra.Command{
Use: "build {--source-dir ..} [--components mcm]...",
Short: "Builds the specified components (machine-controller-manager, simulated provider).",
Example: `clustersim build --source-dir ../../../../ --components "mcm,mc"`,
DisableFlagsInUseLine: true,
PreRunE: func(cmd *cobra.Command, _ []string) error {
if buildFlags.SourceDir == "" {
return fmt.Errorf("required flag --source-dir not passed")
}

if err := validateComponents(buildFlags.Components); err != nil {
return err
}

binDir := filepath.Join(getClustersDir(cmd), "bin")
return os.MkdirAll(binDir, 0750)
},
RunE: func(cmd *cobra.Command, _ []string) error {
cmd.SilenceUsage = true

ctx := cmd.Context()
binDir := filepath.Join(getClustersDir(cmd), "bin")

if slices.Contains(buildFlags.Components, componentMCM) {
outputPath := filepath.Join(binDir, binaryNameMCM)
err := goBuild(ctx, buildFlags.SourceDir, mcmCmdFile, outputPath)
if err != nil {
return err
}
}

if slices.Contains(buildFlags.Components, componentMC) {
providerPkgPath, err := getProviderRoot(buildFlags.SourceDir)
if err != nil {
return err
}

outputPath := filepath.Join(binDir, binaryNameMC)
err = goBuild(ctx, providerPkgPath, providerCmdFile, outputPath)
if err != nil {
return err
}
}
return nil
},
}

func init() {
buildCmd.PersistentFlags().StringSliceVarP(
&buildFlags.Components, "components", "c", allComponents,
"comma separated list of components to build",
)
buildCmd.PersistentFlags().StringVar(
&buildFlags.SourceDir, "source-dir", "",
"machine-controller-manager source root directory path (required)",
)
}

// goBuild invokes `go build -C dir -o binPath -v -buildvcs=true file`
func goBuild(ctx context.Context, dir, file, binPath string) error {
binPathAbs, err := filepath.Abs(binPath)
if err != nil {
return fmt.Errorf("cannot get absolute path for path %q: %w", binPath, err)
}

fmt.Printf("Building %q\n", binPath)
cmd := exec.CommandContext(
ctx, "go", "build", "-C", dir, "-o", binPathAbs, "-v", "-buildvcs=true", file,
)
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr

err = cmd.Run()
if err != nil {
return err
}

if finfo, err := os.Stat(binPath); err != nil && finfo.IsDir() {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

error here: if err is not nil then finfo will be nil and you will get a panic.

return fmt.Errorf("did not find binary installed at expected path %q", binPath)
}

return nil
}

func getProviderRoot(sourceDir string) (string, error) {
return filepath.Abs(filepath.Join(sourceDir, "pkg", "simulatedprovider"))
}
Loading
Loading