Skip to content

Commit c9804bf

Browse files
authored
feat(kubevirt): add VM lifecycle management tools (start, stop, restart) (#533)
* feat(kubevirt): add VM lifecycle tools into single vm_lifecycle tool Add the vm_start, vm_stop, and vm_restart tools into a single vm_lifecycle tool with an 'action' parameter. The new vm_lifecycle tool accepts: - namespace: The namespace of the virtual machine - name: The name of the virtual machine - action: The lifecycle action ('start', 'stop', or 'restart') - start: changes runStrategy to Always - stop: changes runStrategy to Halted - restart: stops then starts the VM Code was assisted by Cursor AI. Signed-off-by: Karel Simon <[email protected]> * test(kubevirt): add tests for vm_lifecycle tool Add tests for the vm_lifecycle tool Code was assisted by Cursor AI. Signed-off-by: Karel Simon <[email protected]> * feat: adjust README.md file Code was assisted by Cursor AI. Signed-off-by: Karel Simon <[email protected]> * feat: change idempotent and desctructive hint The destructive hint is changed to true, since restart might cause unexpected troubles inside VM. Idempotent hint is changed to false, because the restart action might cause additional effects on rest of the workload. Signed-off-by: Karel Simon <[email protected]> --------- Signed-off-by: Karel Simon <[email protected]>
1 parent abbb661 commit c9804bf

File tree

7 files changed

+886
-0
lines changed

7 files changed

+886
-0
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,11 @@ In case multi-cluster support is enabled (default) and you have access to multip
521521
- `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.
522522
- `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
523523

524+
- **vm_lifecycle** - Manage VirtualMachine lifecycle: start, stop, or restart a VM
525+
- `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)
526+
- `name` (`string`) **(required)** - The name of the virtual machine
527+
- `namespace` (`string`) **(required)** - The namespace of the virtual machine
528+
524529
</details>
525530

526531

pkg/kubevirt/vm.go

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package kubevirt
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
8+
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
9+
"k8s.io/apimachinery/pkg/runtime/schema"
10+
"k8s.io/client-go/dynamic"
11+
)
12+
13+
// RunStrategy represents the run strategy for a VirtualMachine
14+
type RunStrategy string
15+
16+
const (
17+
RunStrategyAlways RunStrategy = "Always"
18+
RunStrategyHalted RunStrategy = "Halted"
19+
)
20+
21+
var (
22+
// VirtualMachineGVK is the GroupVersionKind for VirtualMachine resources
23+
VirtualMachineGVK = schema.GroupVersionKind{
24+
Group: "kubevirt.io",
25+
Version: "v1",
26+
Kind: "VirtualMachine",
27+
}
28+
29+
// VirtualMachineGVR is the GroupVersionResource for VirtualMachine resources
30+
VirtualMachineGVR = schema.GroupVersionResource{
31+
Group: "kubevirt.io",
32+
Version: "v1",
33+
Resource: "virtualmachines",
34+
}
35+
)
36+
37+
// GetVirtualMachine retrieves a VirtualMachine by namespace and name
38+
func GetVirtualMachine(ctx context.Context, client dynamic.Interface, namespace, name string) (*unstructured.Unstructured, error) {
39+
return client.Resource(VirtualMachineGVR).Namespace(namespace).Get(ctx, name, metav1.GetOptions{})
40+
}
41+
42+
// GetVMRunStrategy retrieves the current runStrategy from a VirtualMachine
43+
// Returns the strategy, whether it was found, and any error
44+
func GetVMRunStrategy(vm *unstructured.Unstructured) (RunStrategy, bool, error) {
45+
strategy, found, err := unstructured.NestedString(vm.Object, "spec", "runStrategy")
46+
if err != nil {
47+
return "", false, fmt.Errorf("failed to read runStrategy: %w", err)
48+
}
49+
50+
return RunStrategy(strategy), found, nil
51+
}
52+
53+
// SetVMRunStrategy sets the runStrategy on a VirtualMachine
54+
func SetVMRunStrategy(vm *unstructured.Unstructured, strategy RunStrategy) error {
55+
return unstructured.SetNestedField(vm.Object, string(strategy), "spec", "runStrategy")
56+
}
57+
58+
// UpdateVirtualMachine updates a VirtualMachine in the cluster
59+
func UpdateVirtualMachine(ctx context.Context, client dynamic.Interface, vm *unstructured.Unstructured) (*unstructured.Unstructured, error) {
60+
return client.Resource(VirtualMachineGVR).
61+
Namespace(vm.GetNamespace()).
62+
Update(ctx, vm, metav1.UpdateOptions{})
63+
}
64+
65+
// StartVM starts a VirtualMachine by updating its runStrategy to Always
66+
// Returns the updated VM and true if the VM was started, false if it was already running
67+
func StartVM(ctx context.Context, dynamicClient dynamic.Interface, namespace, name string) (*unstructured.Unstructured, bool, error) {
68+
// Get the current VirtualMachine
69+
vm, err := GetVirtualMachine(ctx, dynamicClient, namespace, name)
70+
if err != nil {
71+
return nil, false, fmt.Errorf("failed to get VirtualMachine: %w", err)
72+
}
73+
74+
currentStrategy, found, err := GetVMRunStrategy(vm)
75+
if err != nil {
76+
return nil, false, err
77+
}
78+
79+
// Check if already running
80+
if found && currentStrategy == RunStrategyAlways {
81+
return vm, false, nil
82+
}
83+
84+
// Update runStrategy to Always
85+
if err := SetVMRunStrategy(vm, RunStrategyAlways); err != nil {
86+
return nil, false, fmt.Errorf("failed to set runStrategy: %w", err)
87+
}
88+
89+
// Update the VM in the cluster
90+
updatedVM, err := UpdateVirtualMachine(ctx, dynamicClient, vm)
91+
if err != nil {
92+
return nil, false, fmt.Errorf("failed to start VirtualMachine: %w", err)
93+
}
94+
95+
return updatedVM, true, nil
96+
}
97+
98+
// StopVM stops a VirtualMachine by updating its runStrategy to Halted
99+
// Returns the updated VM and true if the VM was stopped, false if it was already stopped
100+
func StopVM(ctx context.Context, dynamicClient dynamic.Interface, namespace, name string) (*unstructured.Unstructured, bool, error) {
101+
// Get the current VirtualMachine
102+
vm, err := GetVirtualMachine(ctx, dynamicClient, namespace, name)
103+
if err != nil {
104+
return nil, false, fmt.Errorf("failed to get VirtualMachine: %w", err)
105+
}
106+
107+
currentStrategy, found, err := GetVMRunStrategy(vm)
108+
if err != nil {
109+
return nil, false, err
110+
}
111+
112+
// Check if already stopped
113+
if found && currentStrategy == RunStrategyHalted {
114+
return vm, false, nil
115+
}
116+
117+
// Update runStrategy to Halted
118+
if err := SetVMRunStrategy(vm, RunStrategyHalted); err != nil {
119+
return nil, false, fmt.Errorf("failed to set runStrategy: %w", err)
120+
}
121+
122+
// Update the VM in the cluster
123+
updatedVM, err := UpdateVirtualMachine(ctx, dynamicClient, vm)
124+
if err != nil {
125+
return nil, false, fmt.Errorf("failed to stop VirtualMachine: %w", err)
126+
}
127+
128+
return updatedVM, true, nil
129+
}
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+
}

0 commit comments

Comments
 (0)