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  }