Skip to content

Commit e0e6a39

Browse files
committed
fix: refactor console and vnc for better consistency
Signed-off-by: Daniil Antoshin <[email protected]> + Signed-off-by: Daniil Antoshin <[email protected]>
1 parent 111e2ee commit e0e6a39

File tree

5 files changed

+227
-233
lines changed

5 files changed

+227
-233
lines changed

src/cli/go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ require (
1919
k8s.io/client-go v0.32.2
2020
k8s.io/component-base v0.29.3
2121
k8s.io/klog/v2 v2.130.1
22-
k8s.io/utils v0.0.0-20241210054802-24370beab758
2322
)
2423

2524
require github.com/golang/protobuf v1.5.4 // indirect
@@ -69,6 +68,7 @@ require (
6968
k8s.io/api v0.32.2 // indirect
7069
k8s.io/apiextensions-apiserver v0.29.3 // indirect
7170
k8s.io/kube-openapi v0.0.0-20241105132330-32ad38e42d3f // indirect
71+
k8s.io/utils v0.0.0-20241210054802-24370beab758 // indirect
7272
kubevirt.io/api v1.2.0 // indirect
7373
kubevirt.io/containerized-data-importer-api v1.57.0-alpha1 // indirect
7474
kubevirt.io/controller-lifecycle-operator-sdk/api v0.0.0-20220329064328-f3cc58c6ed90 // indirect

src/cli/internal/cmd/console/console.go

Lines changed: 118 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -20,13 +20,17 @@ Initially copied from https://github.com/kubevirt/kubevirt/blob/main/pkg/virtctl
2020
package console
2121

2222
import (
23+
"context"
2324
"errors"
25+
"fmt"
2426
"io"
27+
"os"
2528
"strings"
2629
"time"
2730

2831
"github.com/gorilla/websocket"
2932
"github.com/spf13/cobra"
33+
"golang.org/x/term"
3034

3135
"github.com/deckhouse/virtualization/api/client/kubeclient"
3236

@@ -79,64 +83,138 @@ func (c *Console) Run(cmd *cobra.Command, args []string) error {
7983
}
8084

8185
for {
82-
err := connect(name, namespace, client, c.timeout)
83-
if err == nil {
84-
continue
85-
}
86-
87-
if errors.Is(err, util.ErrorInterrupt) || strings.Contains(err.Error(), "not found") {
88-
return err
89-
}
90-
91-
var e *websocket.CloseError
92-
if errors.As(err, &e) {
93-
switch e.Code {
94-
case websocket.CloseGoingAway:
95-
cmd.Printf("\nYou were disconnected from the console. This has one of the following reasons:" +
96-
"\n - another user connected to the console of the target vm\n")
86+
select {
87+
case <-cmd.Context().Done():
88+
return nil
89+
default:
90+
cmd.Printf("Connecting to %s via console...\n", name)
91+
92+
err := connect(cmd.Context(), name, namespace, client, c.timeout)
93+
if err != nil {
94+
if strings.Contains(err.Error(), "not found") {
95+
return err
96+
}
97+
98+
var e *websocket.CloseError
99+
if errors.As(err, &e) {
100+
switch e.Code {
101+
case websocket.CloseGoingAway:
102+
cmd.Printf(util.CloseGoingAwayMessage)
103+
return nil
104+
case websocket.CloseAbnormalClosure:
105+
cmd.Printf(util.CloseAbnormalClosureMessage)
106+
}
107+
} else {
108+
cmd.Printf("%s\n", err)
109+
}
110+
111+
time.Sleep(time.Second)
112+
} else {
97113
return nil
98-
case websocket.CloseAbnormalClosure:
99-
cmd.Printf("\nYou were disconnected from the console. This has one of the following reasons:" +
100-
"\n - network issues" +
101-
"\n - machine restart\n")
102114
}
103-
} else {
104-
cmd.Printf("%s\n", err)
105115
}
106-
107-
time.Sleep(time.Second)
108116
}
109117
}
110118

111-
func connect(name string, namespace string, virtCli kubeclient.Client, timeout int) error {
119+
func connect(ctx context.Context, name string, namespace string, virtCli kubeclient.Client, timeout int) error {
120+
// in -> stdinWriter | stdinReader -> console
121+
// out <- stdoutReader | stdoutWriter <- console
112122
stdinReader, stdinWriter := io.Pipe()
113123
stdoutReader, stdoutWriter := io.Pipe()
114124

115-
// in -> stdinWriter | stdinReader -> console
116-
// out <- stdoutReader | stdoutWriter <- console
117-
// Wait until the virtual machine is in running phase, user interrupt or timeout
118-
resChan := make(chan error)
119-
runningChan := make(chan error)
125+
doneChan := make(chan struct{}, 1)
120126

121-
go func() {
122-
con, err := virtCli.VirtualMachines(namespace).SerialConsole(name, &kubeclient.SerialConsoleOptions{ConnectionTimeout: time.Duration(timeout) * time.Minute})
123-
runningChan <- err
127+
k8sResErr := make(chan error)
128+
writeStopErr := make(chan error)
129+
readStopErr := make(chan error)
124130

125-
if err != nil {
126-
return
127-
}
131+
console, err := virtCli.VirtualMachines(namespace).SerialConsole(name, &kubeclient.SerialConsoleOptions{ConnectionTimeout: time.Duration(timeout) * time.Minute})
132+
if err != nil {
133+
return fmt.Errorf("can't access VM %s: %s", name, err.Error())
134+
}
128135

129-
resChan <- con.Stream(kubeclient.StreamOptions{
136+
go func() {
137+
err := console.Stream(kubeclient.StreamOptions{
130138
In: stdinReader,
131139
Out: stdoutWriter,
132140
})
141+
if err != nil {
142+
k8sResErr <- err
143+
}
133144
}()
134145

135-
err := <-runningChan
136-
if err != nil {
137-
return err
146+
if term.IsTerminal(int(os.Stdin.Fd())) {
147+
state, err := term.MakeRaw(int(os.Stdin.Fd()))
148+
if err != nil {
149+
return fmt.Errorf("make raw terminal failed: %w", err)
150+
}
151+
defer term.Restore(int(os.Stdin.Fd()), state)
152+
}
153+
154+
fmt.Fprintf(os.Stderr, "Successfully connected to %s console. The escape sequence is ^]\n", name)
155+
156+
out := os.Stdout
157+
go func() {
158+
_, err := io.Copy(out, stdoutReader)
159+
if err != nil {
160+
readStopErr <- err
161+
}
162+
}()
163+
164+
stdinCh := make(chan []byte)
165+
go func() {
166+
in := os.Stdin
167+
buf := make([]byte, 1024)
168+
for {
169+
// reading from stdin
170+
n, err := in.Read(buf)
171+
if err != nil {
172+
if err != io.EOF || n == 0 {
173+
return
174+
}
175+
176+
readStopErr <- err
177+
}
178+
179+
// the escape sequence
180+
if buf[0] == 29 {
181+
doneChan <- struct{}{}
182+
return
183+
}
184+
185+
stdinCh <- buf[0:n]
186+
}
187+
}()
188+
189+
go func() {
190+
_, err := stdinWriter.Write([]byte("\r"))
191+
if err != nil {
192+
if err == io.EOF {
193+
return
194+
}
195+
196+
writeStopErr <- err
197+
}
198+
199+
for b := range stdinCh {
200+
_, err = stdinWriter.Write(b)
201+
if err != nil {
202+
if err == io.EOF {
203+
return
204+
}
205+
206+
writeStopErr <- err
207+
}
208+
}
209+
}()
210+
211+
select {
212+
case <-ctx.Done():
213+
case <-doneChan:
214+
case err = <-k8sResErr:
215+
case err = <-writeStopErr:
216+
case err = <-readStopErr:
138217
}
139218

140-
err = util.AttachConsole(stdinReader, stdoutReader, stdinWriter, stdoutWriter, name, resChan)
141219
return err
142220
}

0 commit comments

Comments
 (0)