github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/worker/uniter/runlistener.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // The run listener is a worker go-routine that listens on either a unix
     5  // socket or a tcp connection for juju-run commands.
     6  
     7  package uniter
     8  
     9  import (
    10  	"net"
    11  	"net/rpc"
    12  	"os"
    13  	"sync"
    14  
    15  	"github.com/juju/utils/exec"
    16  )
    17  
    18  const JujuRunEndpoint = "JujuRunServer.RunCommands"
    19  
    20  // A CommandRunner is something that will actually execute the commands and
    21  // return the results of that execution in the exec.ExecResponse (which
    22  // contains stdout, stderr, and return code).
    23  type CommandRunner interface {
    24  	RunCommands(commands string) (results *exec.ExecResponse, err error)
    25  }
    26  
    27  // RunListener is responsible for listening on the network connection and
    28  // seting up the rpc server on that net connection. Also starts the go routine
    29  // that listens and hands off the work.
    30  type RunListener struct {
    31  	listener net.Listener
    32  	server   *rpc.Server
    33  	closed   chan struct{}
    34  	closing  chan struct{}
    35  	wg       sync.WaitGroup
    36  }
    37  
    38  // The JujuRunServer is the entity that has the methods that are called over
    39  // the rpc connection.
    40  type JujuRunServer struct {
    41  	runner CommandRunner
    42  }
    43  
    44  // RunCommands delegates the actual running to the runner and populates the
    45  // response structure.
    46  func (r *JujuRunServer) RunCommands(commands string, result *exec.ExecResponse) error {
    47  	logger.Debugf("RunCommands: %q", commands)
    48  	runResult, err := r.runner.RunCommands(commands)
    49  	*result = *runResult
    50  	return err
    51  }
    52  
    53  // NewRunListener returns a new RunListener that is listening on given
    54  // unix socket path passed in. If a valid RunListener is returned, is
    55  // has the go routine running, and should be closed by the creator
    56  // when they are done with it.
    57  func NewRunListener(runner CommandRunner, socketPath string) (*RunListener, error) {
    58  	server := rpc.NewServer()
    59  	if err := server.Register(&JujuRunServer{runner}); err != nil {
    60  		return nil, err
    61  	}
    62  	// In case the unix socket is present, delete it.
    63  	if err := os.Remove(socketPath); err != nil {
    64  		logger.Tracef("ignoring error on removing %q: %v", socketPath, err)
    65  	}
    66  	listener, err := net.Listen("unix", socketPath)
    67  	if err != nil {
    68  		logger.Errorf("failed to listen on unix:%s: %v", socketPath, err)
    69  		return nil, err
    70  	}
    71  	runListener := &RunListener{
    72  		listener: listener,
    73  		server:   server,
    74  		closed:   make(chan struct{}),
    75  		closing:  make(chan struct{}),
    76  	}
    77  	go runListener.Run()
    78  	return runListener, nil
    79  }
    80  
    81  // Run accepts new connections until it encounters an error, or until Close is
    82  // called, and then blocks until all existing connections have been closed.
    83  func (s *RunListener) Run() (err error) {
    84  	logger.Debugf("juju-run listener running")
    85  	var conn net.Conn
    86  	for {
    87  		conn, err = s.listener.Accept()
    88  		if err != nil {
    89  			break
    90  		}
    91  		s.wg.Add(1)
    92  		go func(conn net.Conn) {
    93  			s.server.ServeConn(conn)
    94  			s.wg.Done()
    95  		}(conn)
    96  	}
    97  	logger.Debugf("juju-run listener stopping")
    98  	select {
    99  	case <-s.closing:
   100  		// Someone has called Close(), so it is overwhelmingly likely that
   101  		// the error from Accept is a direct result of the Listener being
   102  		// closed, and can therefore be safely ignored.
   103  		err = nil
   104  	default:
   105  	}
   106  	s.wg.Wait()
   107  	close(s.closed)
   108  	return
   109  }
   110  
   111  // Close immediately stops accepting connections, and blocks until all existing
   112  // connections have been closed.
   113  func (s *RunListener) Close() {
   114  	close(s.closing)
   115  	s.listener.Close()
   116  	<-s.closed
   117  	logger.Debugf("juju-run listener stopped")
   118  }