Skip to content

Commit ce8122e

Browse files
committed
feat: add vm_restart tool for KubeVirt VirtualMachines
Implement functionality to restart VirtualMachines by performing a stop-start sequence. This commit includes: - pkg/kubevirt/vm.go: Add RestartVM() function - Restarts a VM by setting runStrategy to Halted then Always - Performs two updates: stop then start - Non-idempotent: always executes the restart sequence - Ensures VM cycles through complete stop/start for clean restart - pkg/toolsets/kubevirt/vm/restart/tool.go: MCP tool implementation - Input validation for namespace and name parameters - Proper error handling for both stop and start phases - Returns updated VM YAML showing final state The restart operation ensures VMs cycle through a full stop/start sequence rather than just signaling a restart, providing more reliable VM reinitialization. Code was assisted by Cursor AI Signed-off-by: Karel Simon <ksimon@redhat.com>
1 parent 655a75f commit ce8122e

File tree

2 files changed

+117
-0
lines changed

2 files changed

+117
-0
lines changed

pkg/kubevirt/vm.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,3 +127,34 @@ func StopVM(ctx context.Context, dynamicClient dynamic.Interface, namespace, nam
127127

128128
return updatedVM, true, nil
129129
}
130+
131+
// RestartVM restarts a VirtualMachine by temporarily setting runStrategy to Halted then back to Always
132+
func RestartVM(ctx context.Context, dynamicClient dynamic.Interface, namespace, name string) (*unstructured.Unstructured, error) {
133+
// Get the current VirtualMachine
134+
vm, err := GetVirtualMachine(ctx, dynamicClient, namespace, name)
135+
if err != nil {
136+
return nil, fmt.Errorf("failed to get VirtualMachine: %w", err)
137+
}
138+
139+
// Stop the VM first
140+
if err := SetVMRunStrategy(vm, RunStrategyHalted); err != nil {
141+
return nil, fmt.Errorf("failed to set runStrategy to Halted: %w", err)
142+
}
143+
144+
vm, err = UpdateVirtualMachine(ctx, dynamicClient, vm)
145+
if err != nil {
146+
return nil, fmt.Errorf("failed to stop VirtualMachine: %w", err)
147+
}
148+
149+
// Start the VM again
150+
if err := SetVMRunStrategy(vm, RunStrategyAlways); err != nil {
151+
return nil, fmt.Errorf("failed to set runStrategy to Always: %w", err)
152+
}
153+
154+
updatedVM, err := UpdateVirtualMachine(ctx, dynamicClient, vm)
155+
if err != nil {
156+
return nil, fmt.Errorf("failed to start VirtualMachine: %w", err)
157+
}
158+
159+
return updatedVM, nil
160+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
package restart
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/containers/kubernetes-mcp-server/pkg/api"
7+
"github.com/containers/kubernetes-mcp-server/pkg/kubevirt"
8+
"github.com/containers/kubernetes-mcp-server/pkg/output"
9+
"github.com/google/jsonschema-go/jsonschema"
10+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
11+
"k8s.io/utils/ptr"
12+
)
13+
14+
func Tools() []api.ServerTool {
15+
return []api.ServerTool{
16+
{
17+
Tool: api.Tool{
18+
Name: "vm_restart",
19+
Description: "Restart a VirtualMachine using the /restart subresource API",
20+
InputSchema: &jsonschema.Schema{
21+
Type: "object",
22+
Properties: map[string]*jsonschema.Schema{
23+
"namespace": {
24+
Type: "string",
25+
Description: "The namespace of the virtual machine",
26+
},
27+
"name": {
28+
Type: "string",
29+
Description: "The name of the virtual machine to restart",
30+
},
31+
},
32+
Required: []string{"namespace", "name"},
33+
},
34+
Annotations: api.ToolAnnotations{
35+
Title: "Virtual Machine: Restart",
36+
ReadOnlyHint: ptr.To(false),
37+
DestructiveHint: ptr.To(false),
38+
IdempotentHint: ptr.To(false),
39+
OpenWorldHint: ptr.To(false),
40+
},
41+
},
42+
Handler: restart,
43+
},
44+
}
45+
}
46+
47+
func restart(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
48+
// Parse input parameters
49+
namespace, err := getRequiredString(params, "namespace")
50+
if err != nil {
51+
return api.NewToolCallResult("", err), nil
52+
}
53+
54+
name, err := getRequiredString(params, "name")
55+
if err != nil {
56+
return api.NewToolCallResult("", err), nil
57+
}
58+
59+
// Restart the VirtualMachine using kubevirt helper
60+
dynamicClient := params.AccessControlClientset().DynamicClient()
61+
vm, err := kubevirt.RestartVM(params.Context, dynamicClient, namespace, name)
62+
if err != nil {
63+
return api.NewToolCallResult("", err), nil
64+
}
65+
66+
// Format the output
67+
marshalledYaml, err := output.MarshalYaml([]*unstructured.Unstructured{vm})
68+
if err != nil {
69+
return api.NewToolCallResult("", fmt.Errorf("failed to marshal VirtualMachine: %w", err)), nil
70+
}
71+
72+
return api.NewToolCallResult("# VirtualMachine restarted successfully\n"+marshalledYaml, nil), nil
73+
}
74+
75+
func getRequiredString(params api.ToolHandlerParams, key string) (string, error) {
76+
args := params.GetArguments()
77+
val, ok := args[key]
78+
if !ok {
79+
return "", fmt.Errorf("%s parameter required", key)
80+
}
81+
str, ok := val.(string)
82+
if !ok {
83+
return "", fmt.Errorf("%s parameter must be a string", key)
84+
}
85+
return str, nil
86+
}

0 commit comments

Comments
 (0)