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 }