Skip to content
Merged
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
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -273,7 +273,7 @@ and only needed for the project-specific scenarios noted.
| [Istio](https://istio.io) | `kiali` | 5 |
| [Kiali](https://kiali.io) | `kiali` | 16 |
| [Kubernetes](https://kubernetes.io) | - | 32 |
| [KubeVirt](https://kubevirt.io) | `kubevirt`, `tekton` | 24 |
| [KubeVirt](https://kubevirt.io) | `kubevirt`, `tekton` | 26 |
| [NetObserv](https://netobserv.io) | `netobserv` | 4 |
| [Tekton](https://tekton.dev) | `tekton` | 9 |

Expand Down Expand Up @@ -561,8 +561,8 @@ In case multi-cluster support is enabled (default) and you have access to multip
- `name` (`string`) **(required)** - The name of the virtual machine
- `namespace` (`string`) **(required)** - The namespace of the virtual machine

- **vm_lifecycle** - Manage KubeVirt VirtualMachine lifecycle: start, stop, or restart a VM
- `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)
- **vm_lifecycle** - Manage KubeVirt VirtualMachine lifecycle: start, stop, restart, pause, or unpause a VM
- `action` (`string`) **(required)** - The lifecycle action to perform: 'start' (changes runStrategy to Always), 'stop' (changes runStrategy to Halted), 'restart' (stops then starts the VM), 'pause' (suspends the running VMI in-place), or 'unpause' (resumes a paused VMI)
- `name` (`string`) **(required)** - The name of the virtual machine
- `namespace` (`string`) **(required)** - The namespace of the virtual machine

Expand Down
78 changes: 78 additions & 0 deletions evals/tasks/kubevirt/pause-vm/task.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
kind: Task
apiVersion: mcpchecker/v1alpha2
metadata:
labels:
suite: kubevirt
project: kubevirt
requires: kubevirt
annotations:
project-name: "KubeVirt"
project-url: "https://kubevirt.io"
name: "pause-vm"
difficulty: easy
spec:
requires:
- extension: kubernetes
as: k8s
setup:
- k8s.delete:
apiVersion: v1
kind: Namespace
metadata:
name: vm-test-pause
ignoreNotFound: true
- k8s.create:
apiVersion: v1
kind: Namespace
metadata:
name: vm-test-pause
- k8s.create:
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
name: test-vm
namespace: vm-test-pause
spec:
runStrategy: Always
template:
spec:
domain:
devices:
disks:
- name: containerdisk
disk:
bus: virtio
memory:
guest: 2Gi
terminationGracePeriodSeconds: 0
volumes:
- name: containerdisk
containerDisk:
image: quay.io/containerdisks/fedora:latest
- k8s.wait:
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
name: test-vm
namespace: vm-test-pause
condition: Ready
timeout: 300s
verify:
- k8s.wait:
apiVersion: kubevirt.io/v1
kind: VirtualMachineInstance
metadata:
name: test-vm
namespace: vm-test-pause
condition: Paused
timeout: 120s
cleanup:
- k8s.delete:
apiVersion: v1
kind: Namespace
metadata:
name: vm-test-pause
ignoreNotFound: true
prompt:
inline: |
Please pause the running virtual machine named test-vm in the vm-test-pause namespace.
90 changes: 90 additions & 0 deletions evals/tasks/kubevirt/unpause-vm/task.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
kind: Task
apiVersion: mcpchecker/v1alpha2
metadata:
labels:
suite: kubevirt
project: kubevirt
requires: kubevirt
annotations:
project-name: "KubeVirt"
project-url: "https://kubevirt.io"
name: "unpause-vm"
difficulty: easy
spec:
requires:
- extension: kubernetes
as: k8s
setup:
- k8s.delete:
apiVersion: v1
kind: Namespace
metadata:
name: vm-test-unpause
ignoreNotFound: true
- k8s.create:
apiVersion: v1
kind: Namespace
metadata:
name: vm-test-unpause
- k8s.create:
apiVersion: kubevirt.io/v1
kind: VirtualMachine
metadata:
name: test-vm
namespace: vm-test-unpause
spec:
runStrategy: Always
startStrategy: Paused
template:
spec:
domain:
devices:
disks:
- name: containerdisk
disk:
bus: virtio
memory:
guest: 2Gi
terminationGracePeriodSeconds: 0
volumes:
- name: containerdisk
containerDisk:
image: quay.io/containerdisks/fedora:latest
- k8s.wait:
apiVersion: kubevirt.io/v1
kind: VirtualMachineInstance
metadata:
name: test-vm
namespace: vm-test-unpause
condition: Paused
timeout: 60s
verify:
- k8s.wait:
apiVersion: kubevirt.io/v1
kind: VirtualMachineInstance
metadata:
name: test-vm
namespace: vm-test-unpause
condition: Ready
timeout: 120s
- script:
inline: |-
#!/usr/bin/env bash
# The Paused condition is removed entirely when unpaused so
# k8s.wait cannot be used. Verify the condition is absent.
PAUSED=$(kubectl get vmi test-vm -n vm-test-unpause -o jsonpath='{.status.conditions[?(@.type=="Paused")].status}')
if [[ "$PAUSED" == "True" ]]; then
echo "✗ VirtualMachineInstance still has Paused=True condition"
exit 1
fi
echo "✓ VirtualMachineInstance is unpaused"
cleanup:
- k8s.delete:
apiVersion: v1
kind: Namespace
metadata:
name: vm-test-unpause
ignoreNotFound: true
prompt:
inline: |
The virtual machine named test-vm in the vm-test-unpause namespace is currently paused. Please unpause it so it resumes running.
13 changes: 1 addition & 12 deletions pkg/kubevirt/guestagent.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import (
"fmt"

"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/rest"
)

Expand All @@ -31,21 +30,11 @@ type AllGuestInfo struct {

// getVMISubresource retrieves a VMI subresource using the REST client
func getVMISubresource(ctx context.Context, restConfig *rest.Config, namespace, vmiName, subresource string) (map[string]any, error) {
// Create a copy to avoid mutating the original config
config := rest.CopyConfig(restConfig)

// Create a REST client configured for the subresources.kubevirt.io API group
gv := schema.GroupVersion{Group: "subresources.kubevirt.io", Version: "v1"}
config.GroupVersion = &gv
config.APIPath = "/apis"
config.NegotiatedSerializer = subresourcesCodec.WithoutConversion()

restClient, err := rest.RESTClientFor(config)
restClient, err := newSubresourceClient(restConfig)
if err != nil {
return nil, fmt.Errorf("failed to create REST client for subresources: %w", err)
}

// Make the request using SubResource() to properly construct the URL
result := &unstructured.Unstructured{}
err = restClient.Get().
Namespace(namespace).
Expand Down
51 changes: 51 additions & 0 deletions pkg/kubevirt/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ import (

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/rest"
)

// RunStrategy represents the run strategy for a VirtualMachine
Expand Down Expand Up @@ -144,6 +146,55 @@ func CloneVM(ctx context.Context, dynamicClient dynamic.Interface, namespace, so
return result, nil
}

// newSubresourceClient creates a REST client for the KubeVirt subresources API group
func newSubresourceClient(restConfig *rest.Config) (rest.Interface, error) {
Comment thread
lyarwood marked this conversation as resolved.
cfg := rest.CopyConfig(restConfig)
cfg.GroupVersion = &schema.GroupVersion{Group: "subresources.kubevirt.io", Version: "v1"}
cfg.APIPath = "/apis"
cfg.NegotiatedSerializer = subresourcesCodec.WithoutConversion()
return rest.RESTClientFor(cfg)
}

// PauseVM pauses a running VirtualMachineInstance via the KubeVirt subresource API
// and returns the parent VirtualMachine
func PauseVM(ctx context.Context, dynamicClient dynamic.Interface, restConfig *rest.Config, namespace, name string) (*unstructured.Unstructured, error) {
Comment thread
lyarwood marked this conversation as resolved.
Comment thread
lyarwood marked this conversation as resolved.
client, err := newSubresourceClient(restConfig)
if err != nil {
return nil, fmt.Errorf("failed to create subresource client: %w", err)
}
result := client.Put().
Namespace(namespace).
Resource("virtualmachineinstances").
Name(name).
SubResource("pause").
Comment thread
lyarwood marked this conversation as resolved.
Body([]byte("{}")).
Do(ctx)
if err := result.Error(); err != nil {
return nil, fmt.Errorf("failed to pause VirtualMachineInstance: %w", err)
}
return GetVirtualMachine(ctx, dynamicClient, namespace, name)
}

// UnpauseVM unpauses a paused VirtualMachineInstance via the KubeVirt subresource API
// and returns the parent VirtualMachine
func UnpauseVM(ctx context.Context, dynamicClient dynamic.Interface, restConfig *rest.Config, namespace, name string) (*unstructured.Unstructured, error) {
Comment thread
lyarwood marked this conversation as resolved.
client, err := newSubresourceClient(restConfig)
if err != nil {
return nil, fmt.Errorf("failed to create subresource client: %w", err)
}
result := client.Put().
Namespace(namespace).
Resource("virtualmachineinstances").
Name(name).
SubResource("unpause").
Body([]byte("{}")).
Do(ctx)
if err := result.Error(); err != nil {
return nil, fmt.Errorf("failed to unpause VirtualMachineInstance: %w", err)
}
return GetVirtualMachine(ctx, dynamicClient, namespace, name)
}

// RestartVM restarts a VirtualMachine by temporarily setting runStrategy to Halted then back to Always
func RestartVM(ctx context.Context, dynamicClient dynamic.Interface, namespace, name string) (*unstructured.Unstructured, error) {
// Get the current VirtualMachine
Expand Down
Loading
Loading