-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
VNC screenshot support on the API and on virtctl #8465
Conversation
/cc @0xFelix regarding to the virtctl changes. Would be great if you could to a review. |
/test pull-kubevirt-goveralls |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice feature. Looks good for me in general, I just left a couple of minor comments inline.
pkg/virt-api/rest/vnc.go
Outdated
rects := msg.(*vnc.FramebufferUpdateMessage).Rectangles | ||
|
||
w := int(rects[0].Width) | ||
h := int(rects[0].Height) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it worth checking len(rects) > 0
before doing rect[0]
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
pkg/virt-api/rest/vnc.go
Outdated
_ = c.PointerEvent(0, 0, 0) | ||
_ = c.PointerEvent(0, 1, 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like a side effect (potentially unwanted). Maybe better to make it optional somehow (e.g. via an additional argument)?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a --move-cursor
arguement and a moveCursor
query parameter. Defaults to not moving.
if _, err := os.Stdout.Write(screenshot); err != nil { | ||
return fmt.Errorf("failed to write image to stdout: %v", err) | ||
} | ||
} else if err := os.WriteFile(fileName, screenshot, os.ModePerm); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
https://pkg.go.dev/io/fs#ModePerm:
ModePerm FileMode = 0777 // Unix permission bits
0777
seems too permissive. Maybe 0600
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A common default would be 0644
IMO. But actually os.ModePerm
should be alright, if os.WriteFile
respects umask
(which it should, see here).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, still let me go with 0644, it will still make prper use of umask and is much more appropriate. Thanks.
// setup connection with VM | ||
screenshot, err := virtCli.VirtualMachineInstance(namespace).Screenshot(vmi) | ||
if err != nil { | ||
return fmt.Errorf("Can't access VMI %s: %v: %s", vmi, err, string(screenshot)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think if you just convert []byte
as string(screenshot)
it will print garbage and potentially unprintable or special characters. I would personally omit this part, but alternatively it can be encoded to e.g. base64
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The main issue is that the error is very unspecific and the body contains the actual error (a general problem in our webhooks I think). Maybe I can find a solution to add it to the actual error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So maybe I misunderstood it, but I got the impression that screenshot
will contain only the PNG bits, no?
Or do you mean that in the error case, it will include the rest response?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
yes, if there is a server error the err.Error() string just contains .e.g 500 internal error
, and the actual reason for the error is in the body. That does not look right, due to potential issues, like you describe, but it is in virtctl at this moment the only way to print the actual reason for the error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And depending on when it fails, it could indeed add some png parts already, resulting to ugly output. I will try to see if I can fix this the right way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Fixed by properly retrieving the error from the body in client-go
which has the code to properly detect if a StatusError is sent in the body instead of the actual requested payload.
cmd := clientcmd.NewVirtctlCommand("vnc", "screenshot", "--namespace", vmi.Namespace, "--file", filePath, vmi.Name) | ||
Expect(cmd.Execute()).To(Succeed()) | ||
|
||
f, err := os.Open(filePath) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Probably defer f.close()
is needed here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yep. 👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
Expect(img.Bounds().Size().X).To(BeNumerically("==", 720)) | ||
Expect(img.Bounds().Size().Y).To(BeNumerically("==", 400)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just to confirm: the framebuffer size 720x400 is specific to that particular VM image that is run in the test, right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is the default resolution of the qemu boot screen. Since the VM we start has no actual VM image, it should be stable to use, because there boots no OS which can change it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks quite good! Added some comments.
pkg/virt-api/rest/vnc.go
Outdated
activeConnectionMetric := apimetrics.NewActiveVNCConnection(request.PathParameter("namespace"), request.PathParameter("name")) | ||
defer activeConnectionMetric.Dec() | ||
|
||
ch := make(chan vnc.ServerMessage) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put this above line 68?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
pkg/virt-api/rest/vnc.go
Outdated
|
||
// Then send a buffer update request | ||
err = c.FramebufferUpdateRequest(false, 0, 0, c.FrameBufferWidth, c.FrameBufferHeight) | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove new line?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
return c.Run(cmd, args) | ||
}, | ||
} | ||
cmd.Flags().StringVarP(&fileName, "file", "f", "", "where to store the VNC screenshot in PNG format. User '-' for stdout") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think output
or o
would make it more clear that this is the output of the command. The flag file
is often used as an input.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to keep f
and file
because o
is prominently used in kubectl
for the output format (-o wide
, ...), so I would it expect to be used for the image type if we would for instance start supporting different image formats (but no plans to do so right now).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ok, then let's keep it.
"kubevirt.io/kubevirt/pkg/virtctl/templates" | ||
) | ||
|
||
var fileName string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can't fileName
be part of the Screenshot
struct instead of a global variable?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely. Done.
return err | ||
} | ||
|
||
vmi := args[0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Put this above line 63?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
if _, err := os.Stdout.Write(screenshot); err != nil { | ||
return fmt.Errorf("failed to write image to stdout: %v", err) | ||
} | ||
} else if err := os.WriteFile(fileName, screenshot, os.ModePerm); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A common default would be 0644
IMO. But actually os.ModePerm
should be alright, if os.WriteFile
respects umask
(which it should, see here).
@@ -131,3 +131,8 @@ func convert(err error) error { | |||
} | |||
return err | |||
} | |||
|
|||
type readWriteCloser struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this actually used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. Removed. 👍
@@ -53,3 +53,10 @@ func (c *wsConn) SetDeadline(t time.Time) error { | |||
} | |||
return c.Conn.SetReadDeadline(t) | |||
} | |||
|
|||
func NewWebsocketStreamer(conn *websocket.Conn, done chan struct{}) *wsStreamer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is this actually used?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, in rest/vnc.go
Allow virt-api websocket dialers to return the virt-handler websocket connection itself or the underlying connection based on the need. Signed-off-by: Roman Mohr <rmohr@google.com>
Make use of the more flexible virt-handler dialers to directly get access to the websocket connection and use the websocket stream functionality to connect to vnc-go. Signed-off-by: Roman Mohr <rmohr@google.com>
Signed-off-by: Roman Mohr <rmohr@google.com>
Signed-off-by: Roman Mohr <rmohr@google.com>
Signed-off-by: Roman Mohr <rmohr@google.com>
Support saving VNC screenshots in PNG format to files: virctl vnc screenshot testvmi -f screenshot.png Support piping VNC screenshots in PNG format: virtctl vnc screenshot testvmi -f - | display Signed-off-by: Roman Mohr <rmohr@google.com>
Start a VM without a boot disk and take a screenshot. Due to not having a boot disk, the default resolution is known and we can check in the e2e test for it. Signed-off-by: Roman Mohr <rmohr@google.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the reviews! PTAL.
return c.Run(cmd, args) | ||
}, | ||
} | ||
cmd.Flags().StringVarP(&fileName, "file", "f", "", "where to store the VNC screenshot in PNG format. User '-' for stdout") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would prefer to keep f
and file
because o
is prominently used in kubectl
for the output format (-o wide
, ...), so I would it expect to be used for the image type if we would for instance start supporting different image formats (but no plans to do so right now).
"kubevirt.io/kubevirt/pkg/virtctl/templates" | ||
) | ||
|
||
var fileName string |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Definitely. Done.
return err | ||
} | ||
|
||
vmi := args[0] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
// setup connection with VM | ||
screenshot, err := virtCli.VirtualMachineInstance(namespace).Screenshot(vmi) | ||
if err != nil { | ||
return fmt.Errorf("Can't access VMI %s: %v: %s", vmi, err, string(screenshot)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done. Fixed by properly retrieving the error from the body in client-go
which has the code to properly detect if a StatusError is sent in the body instead of the actual requested payload.
if _, err := os.Stdout.Write(screenshot); err != nil { | ||
return fmt.Errorf("failed to write image to stdout: %v", err) | ||
} | ||
} else if err := os.WriteFile(fileName, screenshot, os.ModePerm); err != nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, still let me go with 0644, it will still make prper use of umask and is much more appropriate. Thanks.
pkg/virt-api/rest/vnc.go
Outdated
rects := msg.(*vnc.FramebufferUpdateMessage).Rectangles | ||
|
||
w := int(rects[0].Width) | ||
h := int(rects[0].Height) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
@@ -131,3 +131,8 @@ func convert(err error) error { | |||
} | |||
return err | |||
} | |||
|
|||
type readWriteCloser struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No. Removed. 👍
@@ -53,3 +53,10 @@ func (c *wsConn) SetDeadline(t time.Time) error { | |||
} | |||
return c.Conn.SetReadDeadline(t) | |||
} | |||
|
|||
func NewWebsocketStreamer(conn *websocket.Conn, done chan struct{}) *wsStreamer { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, in rest/vnc.go
pkg/virt-api/rest/vnc.go
Outdated
_ = c.PointerEvent(0, 0, 0) | ||
_ = c.PointerEvent(0, 1, 1) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a --move-cursor
arguement and a moveCursor
query parameter. Defaults to not moving.
pkg/virt-api/rest/vnc.go
Outdated
activeConnectionMetric := apimetrics.NewActiveVNCConnection(request.PathParameter("namespace"), request.PathParameter("name")) | ||
defer activeConnectionMetric.Dec() | ||
|
||
ch := make(chan vnc.ServerMessage) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the reviews! PTAL.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good 👍
Looks good to me! /lgtm |
/retest |
thanks everyone for the reviews! /approve |
[APPROVALNOTIFIER] This PR is APPROVED This pull-request has been approved by: rmohr The full list of commands accepted by this bot can be found here. The pull request process is described here
Needs approval from an approver in each of these files:
Approvers can indicate their approval by writing |
/retest-required |
What this PR does / why we need it:
Add a
GET
subresource endpointvirtualmachineinstances/<name>/vnc/screenshot
which allows taking VNC screenshots in PNG format in a server-side fashion.virtctl support is also included:
Support saving VNC screenshots in PNG format to files:
Support piping VNC screenshots in PNG format:
Support waking up the VNC screen when e.g. a screensaver hides the actual screen:
This allows addressing the following use-cases:
vnc
andconsole
.Which issue(s) this PR fixes (optional, in
fixes #<issue number>(, fixes #<issue_number>, ...)
format, will close the issue(s) when PR gets merged):Fixes #
Special notes for your reviewer:
Release note: