Skip to content

Commit f656171

Browse files
committed
feat: add delete action in VM lifecycle
Add a new action to delete the VM during the lifecycle of it. Signed-off-by: Ben Oukhanov <[email protected]>
1 parent 4a0914e commit f656171

File tree

6 files changed

+84
-9
lines changed

6 files changed

+84
-9
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -524,8 +524,8 @@ In case multi-cluster support is enabled (default) and you have access to multip
524524
- `storage` (`string`) - Optional storage size for the VM's root disk when using DataSources (e.g., '30Gi', '50Gi', '100Gi'). Defaults to 30Gi. Ignored when using container disks.
525525
- `workload` (`string`) - The workload for the VM. Accepts OS names (e.g., 'fedora' (default), 'ubuntu', 'centos', 'centos-stream', 'debian', 'rhel', 'opensuse', 'opensuse-tumbleweed', 'opensuse-leap') or full container disk image URLs
526526

527-
- **vm_lifecycle** - Manage VirtualMachine lifecycle: start, stop, or restart a VM
528-
- `action` (`string`) **(required)** - The lifecycle action to perform: 'start' (changes runStrategy to Always), 'stop' (changes runStrategy to Halted), or 'restart' (stops then starts the VM)
527+
- **vm_lifecycle** - Manage VirtualMachine lifecycle: start, stop, restart, or delete a VM
528+
- `action` (`string`) **(required)** - The lifecycle action to perform: 'start' (changes runStrategy to Always), 'stop' (changes runStrategy to Halted), or 'restart' (stops then starts the VM), or 'delete' (deletes the VM)
529529
- `name` (`string`) **(required)** - The name of the virtual machine
530530
- `namespace` (`string`) **(required)** - The namespace of the virtual machine
531531

pkg/kubevirt/vm.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ func UpdateVirtualMachine(ctx context.Context, client dynamic.Interface, vm *uns
6262
Update(ctx, vm, metav1.UpdateOptions{})
6363
}
6464

65+
// DeleteVirtualMachine deletes a VirtualMachine by namespace and name
66+
func DeleteVirtualMachine(ctx context.Context, client dynamic.Interface, namespace, name string) error {
67+
return client.Resource(VirtualMachineGVR).
68+
Namespace(namespace).
69+
Delete(ctx, name, metav1.DeleteOptions{})
70+
}
71+
6572
// StartVM starts a VirtualMachine by updating its runStrategy to Always
6673
// Returns the updated VM and true if the VM was started, false if it was already running
6774
func StartVM(ctx context.Context, dynamicClient dynamic.Interface, namespace, name string) (*unstructured.Unstructured, bool, error) {

pkg/kubevirt/vm_test.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -327,3 +327,41 @@ func TestRestartVMNotFound(t *testing.T) {
327327
t.Errorf("Error = %v, want to contain 'failed to get VirtualMachine'", err)
328328
}
329329
}
330+
331+
func TestDeleteVM(t *testing.T) {
332+
scheme := runtime.NewScheme()
333+
initialVM := createTestVM("test-vm", "default", RunStrategyAlways)
334+
client := fake.NewSimpleDynamicClient(scheme, initialVM)
335+
ctx := context.Background()
336+
337+
err := DeleteVirtualMachine(ctx, client, "default", "test-vm")
338+
if err != nil {
339+
t.Errorf("Unexpected error during deletion: %v", err)
340+
return
341+
}
342+
343+
// Verify the VM is deleted
344+
_, err = GetVirtualMachine(ctx, client, "default", "test-vm")
345+
if err == nil {
346+
t.Errorf("Expected error when getting deleted VM, got nil")
347+
return
348+
}
349+
if !strings.Contains(err.Error(), "not found") {
350+
t.Errorf("Error = %v, want to contain 'not found'", err)
351+
}
352+
}
353+
354+
func TestDeleteVMNotFound(t *testing.T) {
355+
scheme := runtime.NewScheme()
356+
client := fake.NewSimpleDynamicClient(scheme)
357+
ctx := context.Background()
358+
359+
err := DeleteVirtualMachine(ctx, client, "default", "non-existent-vm")
360+
if err == nil {
361+
t.Errorf("Expected error for non-existent VM deletion, got nil")
362+
return
363+
}
364+
if !strings.Contains(err.Error(), "not found") {
365+
t.Errorf("Error = %v, want to contain 'not found'", err)
366+
}
367+
}

pkg/mcp/kubevirt_test.go

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -651,8 +651,30 @@ func (s *KubevirtSuite) TestVMLifecycle() {
651651
})
652652
})
653653

654+
s.Run("vm_lifecycle action=delete on running VM", func() {
655+
toolResult, err := s.CallTool("vm_lifecycle", map[string]interface{}{
656+
"name": "test-vm-lifecycle",
657+
"namespace": "default",
658+
"action": "delete",
659+
})
660+
s.Run("no error", func() {
661+
s.Nilf(err, "call tool failed %v", err)
662+
s.Falsef(toolResult.IsError, "call tool failed")
663+
})
664+
var decodedResult []unstructured.Unstructured
665+
err = yaml.Unmarshal([]byte(toolResult.Content[0].(mcp.TextContent).Text), &decodedResult)
666+
s.Run("returns yaml content", func() {
667+
s.Nilf(err, "invalid tool result content %v", err)
668+
s.Truef(strings.HasPrefix(toolResult.Content[0].(mcp.TextContent).Text, "# VirtualMachine deleted successfully"),
669+
"Expected success message, got %v", toolResult.Content[0].(mcp.TextContent).Text)
670+
s.Require().Lenf(decodedResult, 1, "invalid resource count, expected 1, got %v", len(decodedResult))
671+
s.Equal("test-vm-lifecycle", decodedResult[0].GetName(), "invalid resource name")
672+
s.Equal("default", decodedResult[0].GetNamespace(), "invalid resource namespace")
673+
})
674+
})
675+
654676
s.Run("vm_lifecycle on non-existent VM", func() {
655-
for _, action := range []string{"start", "stop", "restart"} {
677+
for _, action := range []string{"start", "stop", "restart", "delete"} {
656678
s.Run("action="+action, func() {
657679
toolResult, err := s.CallTool("vm_lifecycle", map[string]interface{}{
658680
"name": "non-existent-vm",

pkg/mcp/testdata/toolsets-kubevirt-tools.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,16 +84,17 @@
8484
"destructiveHint": true,
8585
"openWorldHint": false
8686
},
87-
"description": "Manage VirtualMachine lifecycle: start, stop, or restart a VM",
87+
"description": "Manage VirtualMachine lifecycle: start, stop, restart, or delete a VM",
8888
"inputSchema": {
8989
"type": "object",
9090
"properties": {
9191
"action": {
92-
"description": "The lifecycle action to perform: 'start' (changes runStrategy to Always), 'stop' (changes runStrategy to Halted), or 'restart' (stops then starts the VM)",
92+
"description": "The lifecycle action to perform: 'start' (changes runStrategy to Always), 'stop' (changes runStrategy to Halted), or 'restart' (stops then starts the VM), or 'delete' (deletes the VM)",
9393
"enum": [
9494
"start",
9595
"stop",
96-
"restart"
96+
"restart",
97+
"delete"
9798
],
9899
"type": "string"
99100
},

pkg/toolsets/kubevirt/vm/lifecycle/tool.go

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,15 @@ const (
1818
ActionStart Action = "start"
1919
ActionStop Action = "stop"
2020
ActionRestart Action = "restart"
21+
ActionDelete Action = "delete"
2122
)
2223

2324
func Tools() []api.ServerTool {
2425
return []api.ServerTool{
2526
{
2627
Tool: api.Tool{
2728
Name: "vm_lifecycle",
28-
Description: "Manage VirtualMachine lifecycle: start, stop, or restart a VM",
29+
Description: "Manage VirtualMachine lifecycle: start, stop, restart, or delete a VM",
2930
InputSchema: &jsonschema.Schema{
3031
Type: "object",
3132
Properties: map[string]*jsonschema.Schema{
@@ -39,8 +40,8 @@ func Tools() []api.ServerTool {
3940
},
4041
"action": {
4142
Type: "string",
42-
Enum: []any{string(ActionStart), string(ActionStop), string(ActionRestart)},
43-
Description: "The lifecycle action to perform: 'start' (changes runStrategy to Always), 'stop' (changes runStrategy to Halted), or 'restart' (stops then starts the VM)",
43+
Enum: []any{string(ActionStart), string(ActionStop), string(ActionRestart), string(ActionDelete)},
44+
Description: "The lifecycle action to perform: 'start' (changes runStrategy to Always), 'stop' (changes runStrategy to Halted), 'restart' (stops then starts the VM), or 'delete' (deletes the VM)",
4445
},
4546
},
4647
Required: []string{"namespace", "name", "action"},
@@ -111,6 +112,12 @@ func lifecycle(params api.ToolHandlerParams) (*api.ToolCallResult, error) {
111112
return api.NewToolCallResult("", err), nil
112113
}
113114
message = "# VirtualMachine restarted successfully\n"
115+
case ActionDelete:
116+
err = kubevirt.DeleteVirtualMachine(params.Context, dynamicClient, namespace, name)
117+
if err != nil {
118+
return api.NewToolCallResult("", err), nil
119+
}
120+
message = "# VirtualMachine deleted successfully\n"
114121

115122
default:
116123
return api.NewToolCallResult("", fmt.Errorf("invalid action '%s': must be one of 'start', 'stop', 'restart'", action)), nil

0 commit comments

Comments
 (0)