2016-11-22 16:49:54 +00:00
|
|
|
package server
|
|
|
|
|
|
|
|
import (
|
2017-04-26 20:58:03 +00:00
|
|
|
"bytes"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"os/exec"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/Sirupsen/logrus"
|
2017-06-10 16:33:35 +00:00
|
|
|
"github.com/docker/docker/pkg/pools"
|
2017-04-26 20:58:03 +00:00
|
|
|
"github.com/kubernetes-incubator/cri-o/oci"
|
2016-11-22 16:49:54 +00:00
|
|
|
"golang.org/x/net/context"
|
|
|
|
pb "k8s.io/kubernetes/pkg/kubelet/api/v1alpha1/runtime"
|
|
|
|
)
|
|
|
|
|
|
|
|
// PortForward prepares a streaming endpoint to forward ports from a PodSandbox.
|
|
|
|
func (s *Server) PortForward(ctx context.Context, req *pb.PortForwardRequest) (*pb.PortForwardResponse, error) {
|
2017-04-26 20:58:03 +00:00
|
|
|
logrus.Debugf("PortForwardRequest %+v", req)
|
|
|
|
|
|
|
|
resp, err := s.GetPortForward(req)
|
|
|
|
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("unable to prepare portforward endpoint")
|
|
|
|
}
|
|
|
|
|
|
|
|
return resp, nil
|
|
|
|
}
|
|
|
|
|
server: fix PortForward panic
During "Port forwarding" e2e tests, the following panic happened:
```
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x64981d]
goroutine 52788 [running]:
panic(0x1830ee0, 0xc4200100c0)
/usr/lib/golang/src/runtime/panic.go:500 +0x1a1
github.com/kubernetes-incubator/cri-o/oci.(*Runtime).UpdateStatus(0xc4202afc00,
0x0, 0x0, 0x0)
/home/amurdaca/go/src/github.com/kubernetes-incubator/cri-o/oci/oci.go:549
+0x7d
github.com/kubernetes-incubator/cri-o/server.streamService.PortForward(0xc42026e000,
0x0, 0x0, 0x0, 0x0, 0xc420d9af40, 0x40, 0xc400000050, 0x7fe660659a28,
0xc4201cd0e0, ...)
```
The issue is `streamService.PortForward` assumed the first argument to
be the sandbox's infra container ID, thus trying to get it from memory
store using `.state.containers.Get`. Since that ID is of the sandbox
itself, it fails to get the container object from memory and panics in
`UpdateStatus`.
Fix it by looking for the sandbox's infra container ID starting from a
sandbox ID.
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
2017-05-28 15:22:46 +00:00
|
|
|
func (ss streamService) PortForward(podSandboxID string, port int32, stream io.ReadWriteCloser) error {
|
|
|
|
c := ss.runtimeServer.GetSandboxContainer(podSandboxID)
|
2017-04-26 20:58:03 +00:00
|
|
|
|
2017-05-29 11:16:27 +00:00
|
|
|
if c == nil {
|
|
|
|
return fmt.Errorf("could not find container for sandbox %q", podSandboxID)
|
|
|
|
}
|
|
|
|
|
2017-04-26 20:58:03 +00:00
|
|
|
if err := ss.runtimeServer.runtime.UpdateStatus(c); err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
cState := ss.runtimeServer.runtime.ContainerStatus(c)
|
|
|
|
if !(cState.Status == oci.ContainerStateRunning || cState.Status == oci.ContainerStateCreated) {
|
|
|
|
return fmt.Errorf("container is not created or running")
|
|
|
|
}
|
|
|
|
|
|
|
|
containerPid := cState.Pid
|
|
|
|
socatPath, lookupErr := exec.LookPath("socat")
|
|
|
|
if lookupErr != nil {
|
|
|
|
return fmt.Errorf("unable to do port forwarding: socat not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
args := []string{"-t", fmt.Sprintf("%d", containerPid), "-n", socatPath, "-", fmt.Sprintf("TCP4:localhost:%d", port)}
|
|
|
|
|
|
|
|
nsenterPath, lookupErr := exec.LookPath("nsenter")
|
|
|
|
if lookupErr != nil {
|
|
|
|
return fmt.Errorf("unable to do port forwarding: nsenter not found")
|
|
|
|
}
|
|
|
|
|
|
|
|
commandString := fmt.Sprintf("%s %s", nsenterPath, strings.Join(args, " "))
|
|
|
|
logrus.Debugf("executing port forwarding command: %s", commandString)
|
|
|
|
|
|
|
|
command := exec.Command(nsenterPath, args...)
|
|
|
|
command.Stdout = stream
|
|
|
|
|
|
|
|
stderr := new(bytes.Buffer)
|
|
|
|
command.Stderr = stderr
|
|
|
|
|
|
|
|
// If we use Stdin, command.Run() won't return until the goroutine that's copying
|
|
|
|
// from stream finishes. Unfortunately, if you have a client like telnet connected
|
|
|
|
// via port forwarding, as long as the user's telnet client is connected to the user's
|
|
|
|
// local listener that port forwarding sets up, the telnet session never exits. This
|
|
|
|
// means that even if socat has finished running, command.Run() won't ever return
|
|
|
|
// (because the client still has the connection and stream open).
|
|
|
|
//
|
|
|
|
// The work around is to use StdinPipe(), as Wait() (called by Run()) closes the pipe
|
|
|
|
// when the command (socat) exits.
|
|
|
|
inPipe, err := command.StdinPipe()
|
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("unable to do port forwarding: error creating stdin pipe: %v", err)
|
|
|
|
}
|
|
|
|
go func() {
|
2017-06-10 16:33:35 +00:00
|
|
|
pools.Copy(inPipe, stream)
|
2017-04-26 20:58:03 +00:00
|
|
|
inPipe.Close()
|
|
|
|
}()
|
|
|
|
|
|
|
|
if err := command.Run(); err != nil {
|
|
|
|
return fmt.Errorf("%v: %s", err, stderr.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
return nil
|
2016-11-22 16:49:54 +00:00
|
|
|
}
|