github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/stream/listener.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  	"fmt"
    21  	"io"
    22  	"log"
    23  	"net"
    24  	"os"
    25  	"os/user"
    26  	"strconv"
    27  	"sync"
    28  	"time"
    29  
    30  	"golang.org/x/sync/syncmap"
    31  
    32  	"github.com/golang/glog"
    33  )
    34  
    35  // UnixServer listens for connections from qemu instances and sends its
    36  // stdout to registered channels.
    37  type UnixServer struct {
    38  	SocketPath      string
    39  	closeCh         chan bool
    40  	listenDone      chan bool
    41  	deadlineSeconds int
    42  	UnixConnections *syncmap.Map
    43  
    44  	outputReaders    map[string][]chan []byte
    45  	outputReadersMux sync.Mutex
    46  
    47  	workersWG sync.WaitGroup
    48  }
    49  
    50  // NewUnixServer creates new UnixServer. Requires socketPath on which it will listen
    51  // and kubernetesDir where logs will be written
    52  func NewUnixServer(socketPath string) *UnixServer {
    53  	u := UnixServer{
    54  		SocketPath:      socketPath,
    55  		deadlineSeconds: 5,
    56  	}
    57  	u.UnixConnections = new(syncmap.Map)
    58  	u.outputReaders = map[string][]chan []byte{}
    59  	u.closeCh = make(chan bool)
    60  	u.listenDone = make(chan bool)
    61  	return &u
    62  }
    63  
    64  // Listen starts listening for connections from qemus
    65  func (u *UnixServer) Listen() {
    66  	glog.V(1).Info("UnixSocket Listener started")
    67  	defer func() {
    68  		u.listenDone <- true
    69  	}()
    70  
    71  	l, err := net.ListenUnix("unix", &net.UnixAddr{Name: u.SocketPath, Net: "unix"})
    72  	if err != nil {
    73  		glog.Error("listen error:", err)
    74  		return
    75  	}
    76  
    77  	err = fixOwner(u.SocketPath)
    78  	if err != nil {
    79  		glog.Error("%v", err)
    80  		return
    81  	}
    82  	defer func() {
    83  		l.Close()
    84  		u.cleanup()
    85  	}()
    86  
    87  	for {
    88  		select {
    89  		case <-u.closeCh:
    90  			log.Println("stopping listening on", l.Addr())
    91  			return
    92  		default:
    93  		}
    94  
    95  		l.SetDeadline(time.Now().Add(time.Duration(u.deadlineSeconds) * time.Second))
    96  		conn, err := l.AcceptUnix()
    97  		if err != nil {
    98  			if opErr, ok := err.(*net.OpError); ok && opErr.Timeout() {
    99  				continue
   100  			}
   101  			glog.Warningf("accept error:", err)
   102  			continue
   103  		}
   104  
   105  		pid, err := getPidFromConnection(conn)
   106  		if err != nil {
   107  			glog.Warningf("couldn't get pid from connection: %v", err)
   108  			continue
   109  		}
   110  
   111  		podEnv, err := getProcessEnvironment(pid)
   112  		if err != nil {
   113  			glog.Warningf("couldn't get pod information from pid: %v", err)
   114  			continue
   115  		}
   116  		logPath := podEnv["VIRTLET_CONTAINER_LOG_PATH"]
   117  		containerID := podEnv["VIRTLET_CONTAINER_ID"]
   118  
   119  		oldConn, ok := u.UnixConnections.Load(containerID)
   120  		if ok {
   121  			glog.Warningf("closing old unix connection for vm: %s", containerID)
   122  			go oldConn.(*net.UnixConn).Close()
   123  		}
   124  		u.UnixConnections.Store(containerID, conn)
   125  
   126  		logChan := make(chan []byte)
   127  		u.AddOutputReader(containerID, logChan)
   128  		u.workersWG.Add(1)
   129  		go u.reader(containerID, &u.workersWG)
   130  
   131  		u.workersWG.Add(1)
   132  		go NewLogWriter(logChan, logPath, &u.workersWG)
   133  	}
   134  }
   135  
   136  func (u *UnixServer) reader(containerID string, wg *sync.WaitGroup) {
   137  	defer wg.Done()
   138  	glog.V(1).Infoln("Spawned new stream reader for container", containerID)
   139  	connObj, ok := u.UnixConnections.Load(containerID)
   140  	if !ok {
   141  		glog.Error("can not load unix connection")
   142  		return
   143  	}
   144  	conn := connObj.(*net.UnixConn)
   145  
   146  	buf := make([]byte, 4096)
   147  	for {
   148  		n, err := conn.Read(buf)
   149  		if err != nil {
   150  			if err != io.EOF {
   151  				glog.V(1).Infoln("error reading data:", err)
   152  			}
   153  			break
   154  		}
   155  		bufCopy := make([]byte, n)
   156  		copy(bufCopy, buf)
   157  		u.broadcast(containerID, bufCopy)
   158  	}
   159  	conn.Close()
   160  	u.UnixConnections.Delete(containerID)
   161  
   162  	// Closing all channels
   163  	u.outputReadersMux.Lock()
   164  	outputReaders, ok := u.outputReaders[containerID]
   165  	if ok == false {
   166  		outputReaders = []chan []byte{}
   167  	}
   168  	for _, reader := range outputReaders {
   169  		close(reader)
   170  	}
   171  	delete(u.outputReaders, containerID)
   172  	u.outputReadersMux.Unlock()
   173  
   174  	glog.V(1).Infof("Stream reader for container '%s' stopped gracefully", containerID)
   175  }
   176  
   177  // Stop stops listening and waits for all writers to finish
   178  func (u *UnixServer) Stop() {
   179  	close(u.closeCh)
   180  	<-u.listenDone
   181  	u.workersWG.Wait()
   182  	glog.V(1).Info("UnixSocket Listener stopped")
   183  }
   184  
   185  func (u *UnixServer) cleanup() {
   186  	os.Remove(u.SocketPath)
   187  	u.UnixConnections.Range(func(key, conObj interface{}) bool {
   188  		conn := conObj.(*net.UnixConn)
   189  		conn.Close()
   190  		return true
   191  	})
   192  }
   193  
   194  // AddOutputReader adds a new channel for containerID to send stdout
   195  func (u *UnixServer) AddOutputReader(containerID string, newChan chan []byte) {
   196  	u.outputReadersMux.Lock()
   197  	defer u.outputReadersMux.Unlock()
   198  
   199  	outputReaders, ok := u.outputReaders[containerID]
   200  	if ok == false {
   201  		outputReaders = []chan []byte{}
   202  	}
   203  	outputReaders = append(outputReaders, newChan)
   204  	u.outputReaders[containerID] = outputReaders
   205  }
   206  
   207  // RemoveOutputReader removes a channel for containerID
   208  func (u *UnixServer) RemoveOutputReader(containerID string, readerChan chan []byte) {
   209  	u.outputReadersMux.Lock()
   210  	defer u.outputReadersMux.Unlock()
   211  
   212  	outputReaders, ok := u.outputReaders[containerID]
   213  	if ok == false {
   214  		outputReaders = []chan []byte{}
   215  	}
   216  	i := readerIndex(outputReaders, readerChan)
   217  	if i != -1 {
   218  		outputReaders = append(outputReaders[:i], outputReaders[i+1:]...)
   219  		u.outputReaders[containerID] = outputReaders
   220  	}
   221  }
   222  
   223  func (u *UnixServer) broadcast(containerID string, buf []byte) {
   224  	u.outputReadersMux.Lock()
   225  	outputReaders, ok := u.outputReaders[containerID]
   226  	if ok == false {
   227  		outputReaders = []chan []byte{}
   228  	}
   229  	for _, reader := range outputReaders {
   230  		reader <- buf
   231  	}
   232  	u.outputReadersMux.Unlock()
   233  }
   234  
   235  func readerIndex(readers []chan []byte, r chan []byte) int {
   236  	for i, v := range readers {
   237  		if v == r {
   238  			return i
   239  		}
   240  	}
   241  	return -1
   242  }
   243  
   244  func fixOwner(path string) error {
   245  	// Qemu is run as a libvirt-qemu user. It needs acces to a socket file for serial access.
   246  	// Libvirt sets correct rights for it when vm is started but when virtlet pod restarts the file
   247  	// is recreated with root/root set as owner/group so changing user manualy
   248  	// FIXME: Is it better way to do it?
   249  	user, err := user.Lookup("libvirt-qemu")
   250  	if err != nil {
   251  		return fmt.Errorf("Error when looking up libvirt-qemu user: %v", err)
   252  	}
   253  	uid, err := strconv.Atoi(user.Uid)
   254  	if err != nil {
   255  		return fmt.Errorf("Error when converting Uid to int: %v", err)
   256  	}
   257  	if err := os.Chown(path, uid, 0); err != nil {
   258  		return fmt.Errorf("chown error: %v", err)
   259  	}
   260  	return nil
   261  }