github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/stream/streamer.go (about)

     1  /*
     2  Copyright 2017 Mirantis
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package stream
    18  
    19  import (
    20  	"bytes"
    21  	"fmt"
    22  	"io"
    23  	"net"
    24  	"os/exec"
    25  
    26  	"github.com/Mirantis/virtlet/pkg/cni"
    27  	"github.com/docker/docker/pkg/pools"
    28  	"github.com/golang/glog"
    29  
    30  	"k8s.io/client-go/tools/remotecommand"
    31  
    32  	kubeapi "k8s.io/kubernetes/pkg/kubelet/apis/cri/runtime/v1alpha2"
    33  	kubecontainer "k8s.io/kubernetes/pkg/kubelet/container"
    34  )
    35  
    36  // GetAttach returns attach stream request
    37  func (s *Server) GetAttach(req *kubeapi.AttachRequest) (*kubeapi.AttachResponse, error) {
    38  	return s.streamServer.GetAttach(req)
    39  }
    40  
    41  // GetPortForward returns pofrforward stream request
    42  func (s *Server) GetPortForward(req *kubeapi.PortForwardRequest) (*kubeapi.PortForwardResponse, error) {
    43  	return s.streamServer.GetPortForward(req)
    44  }
    45  
    46  // Attach endpoint for streaming.Runtime
    47  func (s *Server) Attach(containerID string, inputStream io.Reader, outputStream, errorStream io.WriteCloser, tty bool, resize <-chan remotecommand.TerminalSize) error {
    48  	glog.V(1).Infoln("New Attach request", containerID)
    49  	c, ok := s.unixServer.UnixConnections.Load(containerID)
    50  	if ok == false {
    51  		return fmt.Errorf("could not find vm %q", containerID)
    52  	}
    53  	conn := c.(*net.UnixConn)
    54  
    55  	outChan := make(chan []byte)
    56  	s.unixServer.AddOutputReader(containerID, outChan)
    57  
    58  	kubecontainer.HandleResizing(resize, func(size remotecommand.TerminalSize) {
    59  		glog.Infof("Got a resize event: %+v", size)
    60  	})
    61  
    62  	receiveStdout := make(chan error)
    63  	if outputStream != nil {
    64  		go func() {
    65  			for data := range outChan {
    66  				outputStream.Write(data)
    67  			}
    68  		}()
    69  	}
    70  
    71  	stdinDone := make(chan error)
    72  	go func() {
    73  		var err error
    74  		if inputStream != nil {
    75  			_, err = CopyDetachable(conn, inputStream, nil)
    76  			if err != nil {
    77  				glog.V(1).Info("Attach coppy error: %v", err)
    78  			}
    79  			stdinDone <- err
    80  		}
    81  	}()
    82  
    83  	var err error
    84  	select {
    85  	case err = <-receiveStdout:
    86  	case err = <-stdinDone:
    87  	}
    88  	s.unixServer.RemoveOutputReader(containerID, outChan)
    89  	glog.V(1).Infoln("Attach request finished", containerID)
    90  	return err
    91  }
    92  
    93  // PortForward endpoint for streaming.Runtime
    94  func (s *Server) PortForward(podSandboxID string, port int32, stream io.ReadWriteCloser) error {
    95  	// implementation based on https://github.com/kubernetes-incubator/cri-o/blob/master/server/container_portforward.go
    96  	glog.V(1).Infoln("New PortForward request", podSandboxID)
    97  
    98  	socatPath, lookupErr := exec.LookPath("socat")
    99  	if lookupErr != nil {
   100  		return fmt.Errorf("unable to do port forwarding: socat not found")
   101  	}
   102  
   103  	ip, err := s.getPodSandboxIP(podSandboxID)
   104  	if err != nil {
   105  		return fmt.Errorf("unable to do port forwarding: %v", err)
   106  	}
   107  
   108  	args := []string{"-", fmt.Sprintf("TCP4:%s:%d", ip, port)}
   109  
   110  	command := exec.Command(socatPath, args...)
   111  	command.Stdout = stream
   112  
   113  	stderr := new(bytes.Buffer)
   114  	command.Stderr = stderr
   115  
   116  	// If we use Stdin, command.Run() won't return until the goroutine that's copying
   117  	// from stream finishes. Unfortunately, if you have a client like telnet connected
   118  	// via port forwarding, as long as the user's telnet client is connected to the user's
   119  	// local listener that port forwarding sets up, the telnet session never exits. This
   120  	// means that even if socat has finished running, command.Run() won't ever return
   121  	// (because the client still has the connection and stream open).
   122  	//
   123  	// The work around is to use StdinPipe(), as Wait() (called by Run()) closes the pipe
   124  	// when the command (socat) exits.
   125  	inPipe, err := command.StdinPipe()
   126  	if err != nil {
   127  		return fmt.Errorf("unable to do port forwarding: error creating stdin pipe: %v", err)
   128  	}
   129  	go func() {
   130  		pools.Copy(inPipe, stream)
   131  		inPipe.Close()
   132  	}()
   133  
   134  	if err := command.Run(); err != nil {
   135  		return fmt.Errorf("%v: %s", err, stderr.String())
   136  	}
   137  
   138  	return nil
   139  }
   140  
   141  func (s *Server) getPodSandboxIP(sandboxID string) (string, error) {
   142  	sandbox := s.metadataStore.PodSandbox(sandboxID)
   143  	sandboxInfo, err := sandbox.Retrieve()
   144  	if err != nil {
   145  		glog.Errorf("Error when getting pod sandbox %q: %v", sandboxID, err)
   146  		return "", err
   147  	}
   148  	if sandboxInfo == nil {
   149  		glog.Errorf("Missing metadata for pod sandbox %q", sandboxID)
   150  		return "", fmt.Errorf("missing metadata for pod sandbox %q", sandboxID)
   151  	}
   152  
   153  	if sandboxInfo.ContainerSideNetwork == nil {
   154  		return "", fmt.Errorf("ContainerSideNetwork missing in PodSandboxInfo returned from medatada store")
   155  	}
   156  	ip := cni.GetPodIP(sandboxInfo.ContainerSideNetwork.Result)
   157  	if ip != "" {
   158  		return ip, nil
   159  	}
   160  	return "", fmt.Errorf("Couldn't get IP address for for PodSandbox: %s", sandboxID)
   161  }