1
19
20 package kubecli
21
22 import (
23 "context"
24 "fmt"
25 "io"
26 "net/http"
27 "path"
28 "time"
29
30 "github.com/gorilla/websocket"
31 . "github.com/onsi/ginkgo/v2"
32 . "github.com/onsi/gomega"
33 "github.com/onsi/gomega/ghttp"
34 k8sv1 "k8s.io/api/core/v1"
35 "k8s.io/apimachinery/pkg/api/errors"
36 k8smetav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
37 "k8s.io/apimachinery/pkg/runtime/schema"
38
39 v1 "kubevirt.io/api/core/v1"
40 "kubevirt.io/client-go/api"
41 kvcorev1 "kubevirt.io/client-go/generated/kubevirt/clientset/versioned/typed/core/v1"
42 )
43
44 var _ = Describe("Kubevirt VirtualMachineInstance Client", func() {
45
46 var upgrader websocket.Upgrader
47 var server *ghttp.Server
48 basePath := "/apis/kubevirt.io/v1/namespaces/default/virtualmachineinstances"
49 vmiPath := path.Join(basePath, "testvm")
50 subVMIPath := "/apis/subresources.kubevirt.io/v1/namespaces/default/virtualmachineinstances/testvm"
51 proxyPath := "/proxy/path"
52
53 BeforeEach(func() {
54 server = ghttp.NewServer()
55 })
56
57 DescribeTable("should fetch a VirtualMachineInstance", func(proxyPath string) {
58 client, err := GetKubevirtClientFromFlags(server.URL()+proxyPath, "")
59 Expect(err).ToNot(HaveOccurred())
60
61 vmi := api.NewMinimalVMI("testvm")
62 server.AppendHandlers(ghttp.CombineHandlers(
63 ghttp.VerifyRequest("GET", path.Join(proxyPath, vmiPath)),
64 ghttp.RespondWithJSONEncoded(http.StatusOK, vmi),
65 ))
66 fetchedVMI, err := client.VirtualMachineInstance(k8sv1.NamespaceDefault).Get(context.Background(), "testvm", k8smetav1.GetOptions{})
67
68 Expect(server.ReceivedRequests()).To(HaveLen(1))
69 Expect(err).ToNot(HaveOccurred())
70 Expect(fetchedVMI).To(Equal(vmi))
71 },
72 Entry("with regular server URL", ""),
73 Entry("with proxied server URL", proxyPath),
74 )
75
76 DescribeTable("should detect non existent VMIs", func(proxyPath string) {
77 client, err := GetKubevirtClientFromFlags(server.URL()+proxyPath, "")
78 Expect(err).ToNot(HaveOccurred())
79
80 server.AppendHandlers(ghttp.CombineHandlers(
81 ghttp.VerifyRequest("GET", path.Join(proxyPath, vmiPath)),
82 ghttp.RespondWithJSONEncoded(http.StatusNotFound, errors.NewNotFound(schema.GroupResource{}, "testvm")),
83 ))
84 _, err = client.VirtualMachineInstance(k8sv1.NamespaceDefault).Get(context.Background(), "testvm", k8smetav1.GetOptions{})
85
86 Expect(server.ReceivedRequests()).To(HaveLen(1))
87 Expect(err).To(HaveOccurred())
88 Expect(errors.IsNotFound(err)).To(BeTrue())
89 },
90 Entry("with regular server URL", ""),
91 Entry("with proxied server URL", proxyPath),
92 )
93
94 DescribeTable("should fetch a VirtualMachineInstance list", func(proxyPath string) {
95 client, err := GetKubevirtClientFromFlags(server.URL()+proxyPath, "")
96 Expect(err).ToNot(HaveOccurred())
97
98 vmi := api.NewMinimalVMI("testvm")
99 server.AppendHandlers(ghttp.CombineHandlers(
100 ghttp.VerifyRequest("GET", path.Join(proxyPath, basePath)),
101 ghttp.RespondWithJSONEncoded(http.StatusOK, NewVMIList(*vmi)),
102 ))
103 fetchedVMIList, err := client.VirtualMachineInstance(k8sv1.NamespaceDefault).List(context.Background(), k8smetav1.ListOptions{})
104 apiVersion, kind := v1.VirtualMachineInstanceGroupVersionKind.ToAPIVersionAndKind()
105
106 Expect(server.ReceivedRequests()).To(HaveLen(1))
107 Expect(err).ToNot(HaveOccurred())
108 Expect(fetchedVMIList.Items).To(HaveLen(1))
109 Expect(fetchedVMIList.Items[0].APIVersion).To(Equal(apiVersion))
110 Expect(fetchedVMIList.Items[0].Kind).To(Equal(kind))
111 Expect(fetchedVMIList.Items[0]).To(Equal(*vmi))
112 },
113 Entry("with regular server URL", ""),
114 Entry("with proxied server URL", proxyPath),
115 )
116
117 DescribeTable("should create a VirtualMachineInstance", func(proxyPath string) {
118 client, err := GetKubevirtClientFromFlags(server.URL()+proxyPath, "")
119 Expect(err).ToNot(HaveOccurred())
120
121 vmi := api.NewMinimalVMI("testvm")
122 server.AppendHandlers(ghttp.CombineHandlers(
123 ghttp.VerifyRequest("POST", path.Join(proxyPath, basePath)),
124 ghttp.RespondWithJSONEncoded(http.StatusCreated, vmi),
125 ))
126 createdVMI, err := client.VirtualMachineInstance(k8sv1.NamespaceDefault).Create(context.Background(), vmi, k8smetav1.CreateOptions{})
127
128 Expect(server.ReceivedRequests()).To(HaveLen(1))
129 Expect(err).ToNot(HaveOccurred())
130 Expect(createdVMI).To(Equal(vmi))
131 },
132 Entry("with regular server URL", ""),
133 Entry("with proxied server URL", proxyPath),
134 )
135
136 DescribeTable("should update a VirtualMachineInstance", func(proxyPath string) {
137 client, err := GetKubevirtClientFromFlags(server.URL()+proxyPath, "")
138 Expect(err).ToNot(HaveOccurred())
139
140 vmi := api.NewMinimalVMI("testvm")
141 server.AppendHandlers(ghttp.CombineHandlers(
142 ghttp.VerifyRequest("PUT", path.Join(proxyPath, vmiPath)),
143 ghttp.RespondWithJSONEncoded(http.StatusOK, vmi),
144 ))
145 updatedVMI, err := client.VirtualMachineInstance(k8sv1.NamespaceDefault).Update(context.Background(), vmi, k8smetav1.UpdateOptions{})
146
147 Expect(server.ReceivedRequests()).To(HaveLen(1))
148 Expect(err).ToNot(HaveOccurred())
149 Expect(updatedVMI).To(Equal(vmi))
150 },
151 Entry("with regular server URL", ""),
152 Entry("with proxied server URL", proxyPath),
153 )
154
155 DescribeTable("should delete a VirtualMachineInstance", func(proxyPath string) {
156 client, err := GetKubevirtClientFromFlags(server.URL()+proxyPath, "")
157 Expect(err).ToNot(HaveOccurred())
158
159 server.AppendHandlers(ghttp.CombineHandlers(
160 ghttp.VerifyRequest("DELETE", path.Join(proxyPath, vmiPath)),
161 ghttp.RespondWithJSONEncoded(http.StatusOK, nil),
162 ))
163 err = client.VirtualMachineInstance(k8sv1.NamespaceDefault).Delete(context.Background(), "testvm", k8smetav1.DeleteOptions{})
164
165 Expect(server.ReceivedRequests()).To(HaveLen(1))
166 Expect(err).ToNot(HaveOccurred())
167 },
168 Entry("with regular server URL", ""),
169 Entry("with proxied server URL", proxyPath),
170 )
171
172 DescribeTable("should allow to connect a stream to a VM", func(proxyPath string) {
173 client, err := GetKubevirtClientFromFlags(server.URL()+proxyPath, "")
174 Expect(err).ToNot(HaveOccurred())
175
176 vncPath := "/apis/subresources.kubevirt.io/v1/namespaces/default/virtualmachineinstances/testvm/vnc"
177
178 server.AppendHandlers(ghttp.CombineHandlers(
179 ghttp.VerifyRequest("GET", path.Join(proxyPath, vncPath)),
180 func(w http.ResponseWriter, r *http.Request) {
181 _, err := upgrader.Upgrade(w, r, nil)
182 if err != nil {
183 return
184 }
185 },
186 ))
187 _, err = client.VirtualMachineInstance(k8sv1.NamespaceDefault).VNC("testvm")
188 Expect(err).ToNot(HaveOccurred())
189 },
190 Entry("with regular server URL", ""),
191 Entry("with proxied server URL", proxyPath),
192 )
193
194 DescribeTable("should handle a failure connecting to the VM", func(proxyPath string) {
195 client, err := GetKubevirtClientFromFlags(server.URL()+proxyPath, "")
196 Expect(err).ToNot(HaveOccurred())
197
198 vncPath := "/apis/subresources.kubevirt.io/v1/namespaces/default/virtualmachineinstances/testvm/vnc"
199
200 server.AppendHandlers(ghttp.CombineHandlers(
201 ghttp.VerifyRequest("GET", path.Join(proxyPath, vncPath)),
202 func(w http.ResponseWriter, r *http.Request) {
203 return
204 },
205 ))
206 _, err = client.VirtualMachineInstance(k8sv1.NamespaceDefault).VNC("testvm")
207 Expect(err).To(HaveOccurred())
208 },
209 Entry("with regular server URL", ""),
210 Entry("with proxied server URL", proxyPath),
211 )
212
213 DescribeTable("should exchange data with the VM", func(proxyPath string) {
214 client, err := GetKubevirtClientFromFlags(server.URL()+proxyPath, "")
215 Expect(err).ToNot(HaveOccurred())
216
217 vncPath := path.Join(subVMIPath, "vnc")
218
219 server.AppendHandlers(ghttp.CombineHandlers(
220 ghttp.VerifyRequest("GET", path.Join(proxyPath, vncPath)),
221 func(w http.ResponseWriter, r *http.Request) {
222 c, err := upgrader.Upgrade(w, r, nil)
223 if err != nil {
224 panic("server upgrader failed")
225 }
226 defer c.Close()
227
228 for {
229 mt, message, err := c.ReadMessage()
230 if err != nil {
231 io.WriteString(GinkgoWriter, fmt.Sprintf("server read failed: %v\n", err))
232 break
233 }
234
235 err = c.WriteMessage(mt, message)
236 if err != nil {
237 io.WriteString(GinkgoWriter, fmt.Sprintf("server write failed: %v\n", err))
238 break
239 }
240 }
241 },
242 ))
243
244 By("establishing connection")
245
246 vnc, err := client.VirtualMachineInstance(k8sv1.NamespaceDefault).VNC("testvm")
247 Expect(err).ToNot(HaveOccurred())
248
249 By("wiring the pipes")
250
251 pipeInReader, pipeInWriter := io.Pipe()
252 pipeOutReader, pipeOutWriter := io.Pipe()
253
254 go func() {
255 vnc.Stream(kvcorev1.StreamOptions{
256 In: pipeInReader,
257 Out: pipeOutWriter,
258 })
259 }()
260
261 By("sending data around")
262 msg := "hello, vnc!"
263 bufIn := make([]byte, 64)
264 copy(bufIn[:], msg)
265
266 _, err = pipeInWriter.Write(bufIn)
267 Expect(err).ToNot(HaveOccurred())
268
269 By("reading back data")
270 bufOut := make([]byte, 64)
271 _, err = pipeOutReader.Read(bufOut)
272 Expect(err).ToNot(HaveOccurred())
273
274 By("checking the result")
275 Expect(bufOut).To(Equal(bufIn))
276 },
277 Entry("with regular server URL", ""),
278 Entry("with proxied server URL", proxyPath),
279 )
280
281 DescribeTable("should pause a VirtualMachineInstance", func(proxyPath string) {
282 client, err := GetKubevirtClientFromFlags(server.URL()+proxyPath, "")
283 Expect(err).ToNot(HaveOccurred())
284
285 server.AppendHandlers(ghttp.CombineHandlers(
286 ghttp.VerifyRequest("PUT", path.Join(proxyPath, subVMIPath, "pause")),
287 ghttp.RespondWithJSONEncoded(http.StatusOK, nil),
288 ))
289 err = client.VirtualMachineInstance(k8sv1.NamespaceDefault).Pause(context.Background(), "testvm", &v1.PauseOptions{})
290
291 Expect(server.ReceivedRequests()).To(HaveLen(1))
292 Expect(err).ToNot(HaveOccurred())
293 },
294 Entry("with regular server URL", ""),
295 Entry("with proxied server URL", proxyPath),
296 )
297
298 DescribeTable("should unpause a VirtualMachineInstance", func(proxyPath string) {
299 client, err := GetKubevirtClientFromFlags(server.URL()+proxyPath, "")
300 Expect(err).ToNot(HaveOccurred())
301
302 server.AppendHandlers(ghttp.CombineHandlers(
303 ghttp.VerifyRequest("PUT", path.Join(proxyPath, subVMIPath, "unpause")),
304 ghttp.RespondWithJSONEncoded(http.StatusOK, nil),
305 ))
306 err = client.VirtualMachineInstance(k8sv1.NamespaceDefault).Unpause(context.Background(), "testvm", &v1.UnpauseOptions{})
307
308 Expect(server.ReceivedRequests()).To(HaveLen(1))
309 Expect(err).ToNot(HaveOccurred())
310 },
311 Entry("with regular server URL", ""),
312 Entry("with proxied server URL", proxyPath),
313 )
314
315 DescribeTable("should freeze a VirtualMachineInstance", func(proxyPath string) {
316 client, err := GetKubevirtClientFromFlags(server.URL()+proxyPath, "")
317 Expect(err).ToNot(HaveOccurred())
318
319 server.AppendHandlers(ghttp.CombineHandlers(
320 ghttp.VerifyRequest("PUT", path.Join(proxyPath, subVMIPath, "freeze")),
321 ghttp.RespondWithJSONEncoded(http.StatusOK, nil),
322 ))
323 err = client.VirtualMachineInstance(k8sv1.NamespaceDefault).Freeze(context.Background(), "testvm", 0*time.Second)
324
325 Expect(server.ReceivedRequests()).To(HaveLen(1))
326 Expect(err).ToNot(HaveOccurred())
327 },
328 Entry("with regular server URL", ""),
329 Entry("with proxied server URL", proxyPath),
330 )
331
332 DescribeTable("should unfreeze a VirtualMachineInstance", func(proxyPath string) {
333 client, err := GetKubevirtClientFromFlags(server.URL()+proxyPath, "")
334 Expect(err).ToNot(HaveOccurred())
335
336 server.AppendHandlers(ghttp.CombineHandlers(
337 ghttp.VerifyRequest("PUT", path.Join(proxyPath, subVMIPath, "unfreeze")),
338 ghttp.RespondWithJSONEncoded(http.StatusOK, nil),
339 ))
340 err = client.VirtualMachineInstance(k8sv1.NamespaceDefault).Unfreeze(context.Background(), "testvm")
341
342 Expect(server.ReceivedRequests()).To(HaveLen(1))
343 Expect(err).ToNot(HaveOccurred())
344 },
345 Entry("with regular server URL", ""),
346 Entry("with proxied server URL", proxyPath),
347 )
348
349 DescribeTable("should soft reboot a VirtualMachineInstance", func(proxyPath string) {
350 client, err := GetKubevirtClientFromFlags(server.URL()+proxyPath, "")
351 Expect(err).ToNot(HaveOccurred())
352
353 server.AppendHandlers(ghttp.CombineHandlers(
354 ghttp.VerifyRequest("PUT", path.Join(proxyPath, subVMIPath, "softreboot")),
355 ghttp.RespondWithJSONEncoded(http.StatusOK, nil),
356 ))
357 err = client.VirtualMachineInstance(k8sv1.NamespaceDefault).SoftReboot(context.Background(), "testvm")
358
359 Expect(server.ReceivedRequests()).To(HaveLen(1))
360 Expect(err).ToNot(HaveOccurred())
361 },
362 Entry("with regular server URL", ""),
363 Entry("with proxied server URL", proxyPath),
364 )
365
366 DescribeTable("should fetch GuestOSInfo from VirtualMachineInstance via subresource", func(proxyPath string) {
367 client, err := GetKubevirtClientFromFlags(server.URL()+proxyPath, "")
368 Expect(err).ToNot(HaveOccurred())
369
370 osInfo := v1.VirtualMachineInstanceGuestAgentInfo{
371 GAVersion: "4.1.1",
372 }
373
374 server.AppendHandlers(ghttp.CombineHandlers(
375 ghttp.VerifyRequest("GET", path.Join(proxyPath, subVMIPath, "guestosinfo")),
376 ghttp.RespondWithJSONEncoded(http.StatusOK, osInfo),
377 ))
378 fetchedInfo, err := client.VirtualMachineInstance(k8sv1.NamespaceDefault).GuestOsInfo(context.Background(), "testvm")
379
380 Expect(err).ToNot(HaveOccurred(), "should fetch info normally")
381 Expect(fetchedInfo).To(Equal(osInfo), "fetched info should be the same as passed in")
382 },
383 Entry("with regular server URL", ""),
384 Entry("with proxied server URL", proxyPath),
385 )
386
387 DescribeTable("should fetch UserList from VirtualMachineInstance via subresource", func(proxyPath string) {
388 client, err := GetKubevirtClientFromFlags(server.URL()+proxyPath, "")
389 Expect(err).ToNot(HaveOccurred())
390
391 userList := v1.VirtualMachineInstanceGuestOSUserList{
392 Items: []v1.VirtualMachineInstanceGuestOSUser{
393 {UserName: "testUser"},
394 },
395 }
396 server.AppendHandlers(ghttp.CombineHandlers(
397 ghttp.VerifyRequest("GET", path.Join(proxyPath, subVMIPath, "userlist")),
398 ghttp.RespondWithJSONEncoded(http.StatusOK, userList),
399 ))
400 fetchedInfo, err := client.VirtualMachineInstance(k8sv1.NamespaceDefault).UserList(context.Background(), "testvm")
401
402 Expect(err).ToNot(HaveOccurred(), "should fetch info normally")
403 Expect(fetchedInfo).To(Equal(userList), "fetched info should be the same as passed in")
404 },
405 Entry("with regular server URL", ""),
406 Entry("with proxied server URL", proxyPath),
407 )
408
409 DescribeTable("should fetch FilesystemList from VirtualMachineInstance via subresource", func(proxyPath string) {
410 client, err := GetKubevirtClientFromFlags(server.URL()+proxyPath, "")
411 Expect(err).ToNot(HaveOccurred())
412
413 fileSystemList := v1.VirtualMachineInstanceFileSystemList{
414 Items: []v1.VirtualMachineInstanceFileSystem{
415 {
416 DiskName: "main",
417 MountPoint: "/",
418 },
419 },
420 }
421
422 server.AppendHandlers(ghttp.CombineHandlers(
423 ghttp.VerifyRequest("GET", path.Join(proxyPath, subVMIPath, "filesystemlist")),
424 ghttp.RespondWithJSONEncoded(http.StatusOK, fileSystemList),
425 ))
426 fetchedInfo, err := client.VirtualMachineInstance(k8sv1.NamespaceDefault).FilesystemList(context.Background(), "testvm")
427
428 Expect(err).ToNot(HaveOccurred(), "should fetch info normally")
429 Expect(fetchedInfo).To(Equal(fileSystemList), "fetched info should be the same as passed in")
430 },
431 Entry("with regular server URL", ""),
432 Entry("with proxied server URL", proxyPath),
433 )
434
435 DescribeTable("should fetch SEV platform info via subresource", func(proxyPath string) {
436 client, err := GetKubevirtClientFromFlags(server.URL()+proxyPath, "")
437 Expect(err).ToNot(HaveOccurred())
438
439 sevPlatformIfo := v1.SEVPlatformInfo{
440 PDH: "AAABBB",
441 CertChain: "CCCDDD",
442 }
443
444 server.AppendHandlers(ghttp.CombineHandlers(
445 ghttp.VerifyRequest("GET", path.Join(proxyPath, subVMIPath, "sev/fetchcertchain")),
446 ghttp.RespondWithJSONEncoded(http.StatusOK, sevPlatformIfo),
447 ))
448 fetchedInfo, err := client.VirtualMachineInstance(k8sv1.NamespaceDefault).SEVFetchCertChain(context.Background(), "testvm")
449
450 Expect(err).ToNot(HaveOccurred(), "should fetch info normally")
451 Expect(fetchedInfo).To(Equal(sevPlatformIfo), "fetched info should be the same as passed in")
452 },
453 Entry("with regular server URL", ""),
454 Entry("with proxied server URL", proxyPath),
455 )
456
457 DescribeTable("should query SEV launch measurement info via subresource", func(proxyPath string) {
458 client, err := GetKubevirtClientFromFlags(server.URL()+proxyPath, "")
459 Expect(err).ToNot(HaveOccurred())
460
461 sevMeasurementInfo := v1.SEVMeasurementInfo{
462 Measurement: "AAABBB",
463 APIMajor: 1,
464 APIMinor: 2,
465 BuildID: 0xFFEE,
466 Policy: 0xFF,
467 }
468
469 server.AppendHandlers(ghttp.CombineHandlers(
470 ghttp.VerifyRequest("GET", path.Join(proxyPath, subVMIPath, "sev/querylaunchmeasurement")),
471 ghttp.RespondWithJSONEncoded(http.StatusOK, sevMeasurementInfo),
472 ))
473 fetchedInfo, err := client.VirtualMachineInstance(k8sv1.NamespaceDefault).SEVQueryLaunchMeasurement(context.Background(), "testvm")
474
475 Expect(err).ToNot(HaveOccurred(), "should fetch info normally")
476 Expect(fetchedInfo).To(Equal(sevMeasurementInfo), "fetched info should be the same as passed in")
477 },
478 Entry("with regular server URL", ""),
479 Entry("with proxied server URL", proxyPath),
480 )
481
482 DescribeTable("should setup SEV session for a VirtualMachineInstance", func(proxyPath string) {
483 client, err := GetKubevirtClientFromFlags(server.URL()+proxyPath, "")
484 Expect(err).ToNot(HaveOccurred())
485
486 server.AppendHandlers(ghttp.CombineHandlers(
487 ghttp.VerifyRequest("PUT", path.Join(proxyPath, subVMIPath, "sev/setupsession")),
488 ghttp.RespondWithJSONEncoded(http.StatusOK, nil),
489 ))
490 err = client.VirtualMachineInstance(k8sv1.NamespaceDefault).SEVSetupSession(context.Background(), "testvm", &v1.SEVSessionOptions{})
491
492 Expect(server.ReceivedRequests()).To(HaveLen(1))
493 Expect(err).ToNot(HaveOccurred())
494 },
495 Entry("with regular server URL", ""),
496 Entry("with proxied server URL", proxyPath),
497 )
498
499 DescribeTable("should inject SEV launch secret into a VirtualMachineInstance", func(proxyPath string) {
500 client, err := GetKubevirtClientFromFlags(server.URL()+proxyPath, "")
501 Expect(err).ToNot(HaveOccurred())
502
503 server.AppendHandlers(ghttp.CombineHandlers(
504 ghttp.VerifyRequest("PUT", path.Join(proxyPath, subVMIPath, "sev/injectlaunchsecret")),
505 ghttp.RespondWithJSONEncoded(http.StatusOK, nil),
506 ))
507 err = client.VirtualMachineInstance(k8sv1.NamespaceDefault).SEVInjectLaunchSecret(context.Background(), "testvm", &v1.SEVSecretOptions{})
508
509 Expect(server.ReceivedRequests()).To(HaveLen(1))
510 Expect(err).ToNot(HaveOccurred())
511 },
512 Entry("with regular server URL", ""),
513 Entry("with proxied server URL", proxyPath),
514 )
515
516 AfterEach(func() {
517 server.Close()
518 })
519 })
520
521 func NewVMIList(vms ...v1.VirtualMachineInstance) *v1.VirtualMachineInstanceList {
522 return &v1.VirtualMachineInstanceList{TypeMeta: k8smetav1.TypeMeta{APIVersion: v1.GroupVersion.String(), Kind: "VirtualMachineInstanceList"}, Items: vms}
523 }
524
View as plain text