Skip to content
This repository was archived by the owner on Nov 4, 2024. It is now read-only.

Commit 43e5698

Browse files
committed
feat(CNV-48461): use nbdkit to download disk img
Use nbdkit to download raw disk img and qemu-img to convert it to qcow2 format directly from the remote server. - Remove virt-sysprep - Remove virtctl - Remove gzip Also add emptyDir volume to store the converted disk qcow2 image. Signed-off-by: Ben Oukhanov <[email protected]>
1 parent 37834ce commit 43e5698

File tree

7 files changed

+69
-120
lines changed

7 files changed

+69
-120
lines changed

Dockerfile

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,7 @@ RUN CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -o kubevirt-disk-uploader .
88

99
FROM quay.io/fedora/fedora-minimal:39
1010

11-
RUN cd /usr/bin && \
12-
curl -L https://github.com/kubevirt/kubevirt/releases/download/v1.0.0/virtctl-v1.0.0-linux-amd64 --output virtctl && \
13-
chmod +x virtctl && \
14-
microdnf install -y qemu-img libguestfs-tools-c && \
15-
microdnf clean all -y
11+
RUN microdnf install -y nbdkit qemu-img && microdnf clean all -y
1612
COPY --from=builder /app/kubevirt-disk-uploader /usr/local/bin/kubevirt-disk-uploader
1713

1814
ENTRYPOINT ["/usr/local/bin/kubevirt-disk-uploader"]

examples/kubevirt-disk-uploader-tekton.yaml

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -162,9 +162,6 @@ spec:
162162
- name: IMAGE_DESTINATION
163163
description: Destination of the image in container registry
164164
type: string
165-
- name: ENABLE_VIRT_SYSPREP
166-
description: Enable or disable preparation of disk image
167-
type: string
168165
- name: PUSH_TIMEOUT
169166
description: ContainerDisk push timeout in minutes
170167
type: string
@@ -194,8 +191,6 @@ spec:
194191
- $(params.VOLUME_NAME)
195192
- "--imagedestination"
196193
- $(params.IMAGE_DESTINATION)
197-
- "--enablevirtsysprep"
198-
- $(params.ENABLE_VIRT_SYSPREP)
199194
- "--pushtimeout"
200195
- $(params.PUSH_TIMEOUT)
201196
computeResources:
@@ -219,9 +214,6 @@ spec:
219214
- name: IMAGE_DESTINATION
220215
description: "Destination of the image in container registry"
221216
type: string
222-
- name: ENABLE_VIRT_SYSPREP
223-
description: "Enable or disable preparation of disk image"
224-
type: string
225217
- name: PUSH_TIMEOUT
226218
description: "ContainerDisk push timeout in minutes"
227219
type: string
@@ -241,8 +233,6 @@ spec:
241233
value: "$(params.VOLUME_NAME)"
242234
- name: IMAGE_DESTINATION
243235
value: "$(params.IMAGE_DESTINATION)"
244-
- name: ENABLE_VIRT_SYSPREP
245-
value: "$(params.ENABLE_VIRT_SYSPREP)"
246236
- name: PUSH_TIMEOUT
247237
value: "$(params.PUSH_TIMEOUT)"
248238
- name: deploy-example-vm-exported
@@ -265,8 +255,6 @@ spec:
265255
value: datavolumedisk
266256
- name: IMAGE_DESTINATION
267257
value: quay.io/boukhano/example-vm-tekton-exported:latest
268-
- name: ENABLE_VIRT_SYSPREP
269-
value: true
270258
- name: PUSH_TIMEOUT
271259
value: 120
272260
taskRunTemplate:

kubevirt-disk-uploader.yaml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -54,12 +54,18 @@ spec:
5454
fieldRef:
5555
fieldPath: metadata.namespace
5656
command: ["/usr/local/bin/kubevirt-disk-uploader"]
57-
# args: ["--vmname", "example-vm", "--volumename", "datavolumedisk", "--imagedestination", "quay.io/boukhano/example-vm-exported:latest", "--enablevirtsysprep", "false", "--pushtimeout", "120"]
57+
# args: ["--vmname", "example-vm", "--volumename", "datavolumedisk", "--imagedestination", "quay.io/boukhano/example-vm-exported:latest", "--pushtimeout", "120"]
5858
resources:
5959
requests:
6060
memory: 3Gi
6161
limits:
6262
memory: 5Gi
63+
volumeMounts:
64+
- name: disk
65+
mountPath: /tmp
66+
volumes:
67+
- name: disk
68+
emptyDir: {}
6369
restartPolicy: Never
6470
---
6571
apiVersion: v1

main.go

Lines changed: 61 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,11 @@
11
package main
22

33
import (
4-
"compress/gzip"
54
"context"
65
"fmt"
7-
"io"
86
"log"
97
"os"
108
"os/exec"
11-
"strconv"
129
"time"
1310

1411
"github.com/google/go-containerregistry/pkg/authn"
@@ -21,35 +18,24 @@ import (
2118
cobra "github.com/spf13/cobra"
2219
corev1 "k8s.io/api/core/v1"
2320
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
21+
"k8s.io/apimachinery/pkg/util/wait"
2422
kvcorev1 "kubevirt.io/api/core/v1"
2523
v1beta1 "kubevirt.io/api/export/v1beta1"
2624
kubecli "kubevirt.io/client-go/kubecli"
2725
tar "kubevirt.io/containerdisks/pkg/build"
2826
)
2927

3028
const (
29+
pollInterval = 15 * time.Second
30+
pollTimeout = 3600 * time.Second
3131
diskPath string = "./tmp/disk.img.gz"
3232
diskPathDecompressed string = "./tmp/disk.img"
3333
diskPathConverted string = "./tmp/disk.qcow2"
3434
)
3535

36-
func applyVirtualMachineExport(vmNamespace, vmName string) error {
36+
func applyVirtualMachineExport(client kubecli.KubevirtClient, vmNamespace, vmName string) error {
3737
log.Println("Applying VirtualMachineExport object...")
3838

39-
client, err := kubecli.GetKubevirtClient()
40-
if err != nil {
41-
return err
42-
}
43-
44-
env := os.Getenv("VM_NAMESPACE")
45-
if env != "" {
46-
vmNamespace = env
47-
}
48-
49-
if vmNamespace == "" {
50-
return fmt.Errorf("VM namespace is not defined. Set VM_NAMESPACE or parameter.")
51-
}
52-
5339
vmExport := &v1beta1.VirtualMachineExport{
5440
ObjectMeta: metav1.ObjectMeta{
5541
Name: vmName,
@@ -64,68 +50,69 @@ func applyVirtualMachineExport(vmNamespace, vmName string) error {
6450
},
6551
}
6652

67-
_, err = client.VirtualMachineExport(vmNamespace).Create(context.Background(), vmExport, metav1.CreateOptions{})
53+
_, err := client.VirtualMachineExport(vmNamespace).Create(context.Background(), vmExport, metav1.CreateOptions{})
6854
return err
6955
}
7056

71-
func downloadVirtualMachineDiskImage(vmName, volumeName string) error {
72-
log.Printf("Downloading disk image from %s Virtual Machine...\n", vmName)
73-
74-
cmd := exec.Command("usr/bin/virtctl", "vmexport", "download", vmName, "--vm", vmName, "--volume", volumeName, "--output", diskPath)
75-
cmd.Stdout = os.Stdout
76-
cmd.Stderr = os.Stderr
57+
func getRawDiskUrlFromVirtualMachineExport(client kubecli.KubevirtClient, vmNamespace, vmName, volumeName string) (string, error) {
58+
log.Println("Waiting for VirtualMachineExport to be ready...")
7759

78-
if err := cmd.Run(); err != nil {
79-
return err
60+
vmExport, err := getVirtualMachineExportOnceReady(client, vmNamespace, vmName)
61+
if err != nil {
62+
return "", err
8063
}
8164

82-
if fileInfo, err := os.Stat(diskPath); err != nil || fileInfo.Size() == 0 {
83-
return fmt.Errorf("File does not exist or is empty.")
65+
if vmExport.Status.Links == nil && vmExport.Status.Links.Internal == nil {
66+
return "", fmt.Errorf("No links found in VirtualMachineExport status.")
8467
}
8568

86-
log.Println("Download completed successfully.")
87-
return nil
88-
}
89-
90-
func decompressVirtualMachineDiskImage() error {
91-
log.Println("Decompressing downloaded disk image...")
69+
for _, volume := range vmExport.Status.Links.Internal.Volumes {
70+
if volumeName != volume.Name {
71+
continue
72+
}
9273

93-
diskImg, err := os.Open(diskPath)
94-
if err != nil {
95-
return err
74+
for _, format := range volume.Formats {
75+
if format.Format == v1beta1.KubeVirtRaw {
76+
return format.Url, nil
77+
}
78+
}
9679
}
97-
defer diskImg.Close()
80+
return "", fmt.Errorf("Could not get raw disk URL from the VirtualMachineExport object.")
81+
}
9882

99-
gzipReader, err := gzip.NewReader(diskImg)
100-
if err != nil {
101-
return err
102-
}
103-
defer gzipReader.Close()
83+
func getVirtualMachineExportOnceReady(client kubecli.KubevirtClient, vmNamespace, vmName string) (*v1beta1.VirtualMachineExport, error) {
84+
var vmExport *v1beta1.VirtualMachineExport
10485

105-
newDiskImg, err := os.Create(diskPathDecompressed)
106-
if err != nil {
107-
return err
108-
}
109-
defer newDiskImg.Close()
86+
poller := func(ctx context.Context) (bool, error) {
87+
vmExport, err := client.VirtualMachineExport(vmNamespace).Get(ctx, vmName, metav1.GetOptions{})
88+
if err != nil {
89+
return false, err
90+
}
11091

111-
_, err = io.Copy(newDiskImg, gzipReader)
112-
if err != nil {
113-
return err
92+
if vmExport.Status.Phase == v1beta1.Ready {
93+
return true, nil
94+
}
95+
return false, nil
11496
}
11597

116-
err = os.Chmod(diskPathDecompressed, 0666) // Grants read and write permission to everyone.
98+
err := wait.PollUntilContextTimeout(context.Background(), pollInterval, pollTimeout, true, poller)
11799
if err != nil {
118-
return err
100+
return nil, fmt.Errorf("Failed to wait for VirtualMachineExport to be ready: %v", err)
119101
}
120-
121-
log.Println("Decompression completed successfully.")
122-
return nil
102+
return vmExport, nil
123103
}
124104

125-
func convertRawDiskImageToQcow2() error {
105+
func convertRawDiskImageToQcow2(rawDiskUrl string) error {
126106
log.Println("Converting raw disk image to qcow2 format...")
127107

128-
cmd := exec.Command("qemu-img", "convert", "-f", "raw", "-O", "qcow2", diskPathDecompressed, diskPathConverted)
108+
cmd := exec.Command(
109+
"nbdkit",
110+
"-r",
111+
"curl",
112+
rawDiskUrl,
113+
"--run",
114+
fmt.Sprintf("qemu-img convert \"$uri\" -O qcow2 %s", diskPathConverted),
115+
)
129116
cmd.Stdout = os.Stdout
130117
cmd.Stderr = os.Stderr
131118

@@ -141,29 +128,6 @@ func convertRawDiskImageToQcow2() error {
141128
return nil
142129
}
143130

144-
func prepareVirtualMachineDiskImage(enableVirtSysprep string) error {
145-
enabled, err := strconv.ParseBool(enableVirtSysprep)
146-
if err != nil {
147-
return err
148-
}
149-
150-
if !enabled {
151-
log.Println("Skipping disk image preparation.")
152-
return nil
153-
}
154-
155-
os.Setenv("LIBGUESTFS_BACKEND", "direct")
156-
cmd := exec.Command("virt-sysprep", "--format", "qcow2", "-a", diskPathConverted)
157-
cmd.Stdout = os.Stdout
158-
cmd.Stderr = os.Stderr
159-
160-
if err := cmd.Run(); err != nil {
161-
return err
162-
}
163-
164-
return nil
165-
}
166-
167131
func buildContainerDisk(diskPath string) (v1.Image, error) {
168132
layer, err := tarball.LayerFromOpener(tar.StreamLayerOpener(diskPath))
169133
if err != nil {
@@ -202,24 +166,31 @@ func pushContainerDisk(image v1.Image, imageDestination string, pushTimeout int)
202166
return nil
203167
}
204168

205-
func run(vmNamespace, vmName, volumeName, imageDestination, enableVirtSysprep string, pushTimeout int) error {
206-
if err := applyVirtualMachineExport(vmNamespace, vmName); err != nil {
169+
func run(vmNamespace, vmName, volumeName, imageDestination string, pushTimeout int) error {
170+
client, err := kubecli.GetKubevirtClient()
171+
if err != nil {
207172
return err
208173
}
209174

210-
if err := downloadVirtualMachineDiskImage(vmName, volumeName); err != nil {
211-
return err
175+
env := os.Getenv("VM_NAMESPACE")
176+
if env != "" {
177+
vmNamespace = env
178+
}
179+
180+
if vmNamespace == "" {
181+
return fmt.Errorf("VM namespace is not defined. Set VM_NAMESPACE or parameter.")
212182
}
213183

214-
if err := decompressVirtualMachineDiskImage(); err != nil {
184+
if err := applyVirtualMachineExport(client, vmNamespace, vmName); err != nil {
215185
return err
216186
}
217187

218-
if err := convertRawDiskImageToQcow2(); err != nil {
188+
rawDiskUrl, err := getRawDiskUrlFromVirtualMachineExport(client, vmNamespace, vmName, volumeName)
189+
if err != nil {
219190
return err
220191
}
221192

222-
if err := prepareVirtualMachineDiskImage(enableVirtSysprep); err != nil {
193+
if err := convertRawDiskImageToQcow2(rawDiskUrl); err != nil {
223194
return err
224195
}
225196

@@ -236,7 +207,6 @@ func main() {
236207
var vmName string
237208
var volumeName string
238209
var imageDestination string
239-
var enableVirtSysprep string
240210
var pushTimeout int
241211

242212
var command = &cobra.Command{
@@ -245,7 +215,7 @@ func main() {
245215
Run: func(cmd *cobra.Command, args []string) {
246216
log.Println("Extracts disk and uploads it to a container registry...")
247217

248-
if err := run(vmNamespace, vmName, volumeName, imageDestination, enableVirtSysprep, pushTimeout); err != nil {
218+
if err := run(vmNamespace, vmName, volumeName, imageDestination, pushTimeout); err != nil {
249219
log.Panicln(err)
250220
}
251221

@@ -257,7 +227,6 @@ func main() {
257227
command.Flags().StringVar(&vmName, "vmname", "", "name of the virtual machine")
258228
command.Flags().StringVar(&volumeName, "volumename", "", "volume name of the virtual machine")
259229
command.Flags().StringVar(&imageDestination, "imagedestination", "", "destination of the image in container registry")
260-
command.Flags().StringVar(&enableVirtSysprep, "enablevirtsysprep", "false", "enable or disable virt-sysprep")
261230
command.Flags().IntVar(&pushTimeout, "pushtimeout", 60, "containerdisk push timeout in minutes")
262231
command.MarkFlagRequired("vmname")
263232
command.MarkFlagRequired("volumename")

tasks/kubevirt-disk-uploader/0.5.0/README.md

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@ kubectl apply -f https://raw.githubusercontent.com/codingben/kubevirt-disk-uploa
1717
- **VM_NAME**: The name of the virtual machine
1818
- **VOLUME_NAME**: The volume name of the virtual machine
1919
- **IMAGE_DESTINATION**: Destination of the image in container registry
20-
- **ENABLE_VIRT_SYSPREP**: Enable or disable preparation of disk image
2120
- **PUSH_TIMEOUT**: ContainerDisk push timeout in minutes
2221

2322
# Usage
@@ -55,8 +54,6 @@ spec:
5554
value: <VOLUME_NAME_VALUE>
5655
- name: IMAGE_DESTINATION
5756
value: <IMAGE_DESTINATION_VALUE>
58-
- name: ENABLE_VIRT_SYSPREP
59-
value: <ENABLE_VIRT_SYSPREP_VALUE>
6057
- name: PUSH_TIMEOUT
6158
value: <PUSH_TIMEOUT>
6259
```

tasks/kubevirt-disk-uploader/0.5.0/kubevirt-disk-uploader.yaml

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,6 @@ spec:
2525
- name: IMAGE_DESTINATION
2626
description: Destination of the image in container registry
2727
type: string
28-
- name: ENABLE_VIRT_SYSPREP
29-
description: Enable or disable preparation of disk image
30-
type: string
3128
- name: PUSH_TIMEOUT
3229
description: ContainerDisk push timeout in minutes
3330
type: string
@@ -57,8 +54,6 @@ spec:
5754
- $(params.VOLUME_NAME)
5855
- "--imagedestination"
5956
- $(params.IMAGE_DESTINATION)
60-
- "--enablevirtsysprep"
61-
- $(params.ENABLE_VIRT_SYSPREP)
6257
- "--pushtimeout"
6358
- $(params.PUSH_TIMEOUT)
6459
computeResources:

tasks/kubevirt-disk-uploader/0.5.0/tests/kubevirt-disk-uploader-task-run.yaml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,5 @@ spec:
1212
value: datavolumedisk
1313
- name: IMAGE_DESTINATION
1414
value: quay.io/boukhano/example-vm-tekton-exported:latest
15-
- name: ENABLE_VIRT_SYSPREP
16-
value: false
1715
- name: PUSH_TIMEOUT
1816
value: 120

0 commit comments

Comments
 (0)