github.com/kaixiang/packer@v0.5.2-0.20140114230416-1f5786b0d7f1/packer/rpc/communicator.go (about) 1 package rpc 2 3 import ( 4 "encoding/gob" 5 "github.com/mitchellh/packer/packer" 6 "io" 7 "log" 8 "net/rpc" 9 ) 10 11 // An implementation of packer.Communicator where the communicator is actually 12 // executed over an RPC connection. 13 type communicator struct { 14 client *rpc.Client 15 mux *MuxConn 16 } 17 18 // CommunicatorServer wraps a packer.Communicator implementation and makes 19 // it exportable as part of a Golang RPC server. 20 type CommunicatorServer struct { 21 c packer.Communicator 22 mux *MuxConn 23 } 24 25 type CommandFinished struct { 26 ExitStatus int 27 } 28 29 type CommunicatorStartArgs struct { 30 Command string 31 StdinStreamId uint32 32 StdoutStreamId uint32 33 StderrStreamId uint32 34 ResponseStreamId uint32 35 } 36 37 type CommunicatorDownloadArgs struct { 38 Path string 39 WriterStreamId uint32 40 } 41 42 type CommunicatorUploadArgs struct { 43 Path string 44 ReaderStreamId uint32 45 } 46 47 type CommunicatorUploadDirArgs struct { 48 Dst string 49 Src string 50 Exclude []string 51 } 52 53 func Communicator(client *rpc.Client) *communicator { 54 return &communicator{client: client} 55 } 56 57 func (c *communicator) Start(cmd *packer.RemoteCmd) (err error) { 58 var args CommunicatorStartArgs 59 args.Command = cmd.Command 60 61 if cmd.Stdin != nil { 62 args.StdinStreamId = c.mux.NextId() 63 go serveSingleCopy("stdin", c.mux, args.StdinStreamId, nil, cmd.Stdin) 64 } 65 66 if cmd.Stdout != nil { 67 args.StdoutStreamId = c.mux.NextId() 68 go serveSingleCopy("stdout", c.mux, args.StdoutStreamId, cmd.Stdout, nil) 69 } 70 71 if cmd.Stderr != nil { 72 args.StderrStreamId = c.mux.NextId() 73 go serveSingleCopy("stderr", c.mux, args.StderrStreamId, cmd.Stderr, nil) 74 } 75 76 responseStreamId := c.mux.NextId() 77 args.ResponseStreamId = responseStreamId 78 79 go func() { 80 conn, err := c.mux.Accept(responseStreamId) 81 if err != nil { 82 log.Printf("[ERR] Error accepting response stream %d: %s", 83 responseStreamId, err) 84 cmd.SetExited(123) 85 return 86 } 87 defer conn.Close() 88 89 var finished CommandFinished 90 decoder := gob.NewDecoder(conn) 91 if err := decoder.Decode(&finished); err != nil { 92 log.Printf("[ERR] Error decoding response stream %d: %s", 93 responseStreamId, err) 94 cmd.SetExited(123) 95 return 96 } 97 98 log.Printf("[INFO] RPC client: Communicator ended with: %d", finished.ExitStatus) 99 cmd.SetExited(finished.ExitStatus) 100 }() 101 102 err = c.client.Call("Communicator.Start", &args, new(interface{})) 103 return 104 } 105 106 func (c *communicator) Upload(path string, r io.Reader) (err error) { 107 // Pipe the reader through to the connection 108 streamId := c.mux.NextId() 109 go serveSingleCopy("uploadData", c.mux, streamId, nil, r) 110 111 args := CommunicatorUploadArgs{ 112 Path: path, 113 ReaderStreamId: streamId, 114 } 115 116 err = c.client.Call("Communicator.Upload", &args, new(interface{})) 117 return 118 } 119 120 func (c *communicator) UploadDir(dst string, src string, exclude []string) error { 121 args := &CommunicatorUploadDirArgs{ 122 Dst: dst, 123 Src: src, 124 Exclude: exclude, 125 } 126 127 var reply error 128 err := c.client.Call("Communicator.UploadDir", args, &reply) 129 if err == nil { 130 err = reply 131 } 132 133 return err 134 } 135 136 func (c *communicator) Download(path string, w io.Writer) (err error) { 137 // Serve a single connection and a single copy 138 streamId := c.mux.NextId() 139 go serveSingleCopy("downloadWriter", c.mux, streamId, w, nil) 140 141 args := CommunicatorDownloadArgs{ 142 Path: path, 143 WriterStreamId: streamId, 144 } 145 146 err = c.client.Call("Communicator.Download", &args, new(interface{})) 147 return 148 } 149 150 func (c *CommunicatorServer) Start(args *CommunicatorStartArgs, reply *interface{}) error { 151 // Build the RemoteCmd on this side so that it all pipes over 152 // to the remote side. 153 var cmd packer.RemoteCmd 154 cmd.Command = args.Command 155 156 // Create a channel to signal we're done so that we can close 157 // our stdin/stdout/stderr streams 158 toClose := make([]io.Closer, 0) 159 doneCh := make(chan struct{}) 160 go func() { 161 <-doneCh 162 for _, conn := range toClose { 163 defer conn.Close() 164 } 165 }() 166 167 if args.StdinStreamId > 0 { 168 conn, err := c.mux.Dial(args.StdinStreamId) 169 if err != nil { 170 close(doneCh) 171 return NewBasicError(err) 172 } 173 174 toClose = append(toClose, conn) 175 cmd.Stdin = conn 176 } 177 178 if args.StdoutStreamId > 0 { 179 conn, err := c.mux.Dial(args.StdoutStreamId) 180 if err != nil { 181 close(doneCh) 182 return NewBasicError(err) 183 } 184 185 toClose = append(toClose, conn) 186 cmd.Stdout = conn 187 } 188 189 if args.StderrStreamId > 0 { 190 conn, err := c.mux.Dial(args.StderrStreamId) 191 if err != nil { 192 close(doneCh) 193 return NewBasicError(err) 194 } 195 196 toClose = append(toClose, conn) 197 cmd.Stderr = conn 198 } 199 200 // Connect to the response address so we can write our result to it 201 // when ready. 202 responseC, err := c.mux.Dial(args.ResponseStreamId) 203 if err != nil { 204 close(doneCh) 205 return NewBasicError(err) 206 } 207 responseWriter := gob.NewEncoder(responseC) 208 209 // Start the actual command 210 err = c.c.Start(&cmd) 211 if err != nil { 212 close(doneCh) 213 return NewBasicError(err) 214 } 215 216 // Start a goroutine to spin and wait for the process to actual 217 // exit. When it does, report it back to caller... 218 go func() { 219 defer close(doneCh) 220 defer responseC.Close() 221 cmd.Wait() 222 log.Printf("[INFO] RPC endpoint: Communicator ended with: %d", cmd.ExitStatus) 223 responseWriter.Encode(&CommandFinished{cmd.ExitStatus}) 224 }() 225 226 return nil 227 } 228 229 func (c *CommunicatorServer) Upload(args *CommunicatorUploadArgs, reply *interface{}) (err error) { 230 readerC, err := c.mux.Dial(args.ReaderStreamId) 231 if err != nil { 232 return 233 } 234 defer readerC.Close() 235 236 err = c.c.Upload(args.Path, readerC) 237 return 238 } 239 240 func (c *CommunicatorServer) UploadDir(args *CommunicatorUploadDirArgs, reply *error) error { 241 return c.c.UploadDir(args.Dst, args.Src, args.Exclude) 242 } 243 244 func (c *CommunicatorServer) Download(args *CommunicatorDownloadArgs, reply *interface{}) (err error) { 245 writerC, err := c.mux.Dial(args.WriterStreamId) 246 if err != nil { 247 return 248 } 249 defer writerC.Close() 250 251 err = c.c.Download(args.Path, writerC) 252 return 253 } 254 255 func serveSingleCopy(name string, mux *MuxConn, id uint32, dst io.Writer, src io.Reader) { 256 conn, err := mux.Accept(id) 257 if err != nil { 258 log.Printf("[ERR] '%s' accept error: %s", name, err) 259 return 260 } 261 262 // Be sure to close the connection after we're done copying so 263 // that an EOF will successfully be sent to the remote side 264 defer conn.Close() 265 266 // The connection is the destination/source that is nil 267 if dst == nil { 268 dst = conn 269 } else { 270 src = conn 271 } 272 273 written, err := io.Copy(dst, src) 274 log.Printf("[INFO] %d bytes written for '%s'", written, name) 275 if err != nil { 276 log.Printf("[ERR] '%s' copy error: %s", name, err) 277 } 278 }