github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/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  	"sync"
    13  
    14  	"github.com/juju/errors"
    15  	"github.com/juju/utils/exec"
    16  
    17  	"github.com/juju/juju/juju/sockets"
    18  )
    19  
    20  const JujuRunEndpoint = "JujuRunServer.RunCommands"
    21  
    22  // RunCommandsArgs stores the arguments for a RunCommands call.
    23  type RunCommandsArgs struct {
    24  	// Commands is the arbitrary commands to execute on the unit
    25  	Commands string
    26  	// RelationId is the relation context to execute the commands in.
    27  	RelationId int
    28  	// RemoteUnitName is the remote unit for the relation context.
    29  	RemoteUnitName string
    30  	// ForceRemoteUnit skips relation membership and existence validation.
    31  	ForceRemoteUnit bool
    32  }
    33  
    34  // A CommandRunner is something that will actually execute the commands and
    35  // return the results of that execution in the exec.ExecResponse (which
    36  // contains stdout, stderr, and return code).
    37  type CommandRunner interface {
    38  	RunCommands(RunCommandsArgs RunCommandsArgs) (results *exec.ExecResponse, err error)
    39  }
    40  
    41  // RunListener is responsible for listening on the network connection and
    42  // seting up the rpc server on that net connection. Also starts the go routine
    43  // that listens and hands off the work.
    44  type RunListener struct {
    45  	listener net.Listener
    46  	server   *rpc.Server
    47  	closed   chan struct{}
    48  	closing  chan struct{}
    49  	wg       sync.WaitGroup
    50  }
    51  
    52  // The JujuRunServer is the entity that has the methods that are called over
    53  // the rpc connection.
    54  type JujuRunServer struct {
    55  	runner CommandRunner
    56  }
    57  
    58  // RunCommands delegates the actual running to the runner and populates the
    59  // response structure.
    60  func (r *JujuRunServer) RunCommands(args RunCommandsArgs, result *exec.ExecResponse) error {
    61  	logger.Debugf("RunCommands: %+v", args)
    62  	runResult, err := r.runner.RunCommands(args)
    63  	if err != nil {
    64  		return errors.Annotate(err, "r.runner.RunCommands")
    65  	}
    66  	*result = *runResult
    67  	return err
    68  }
    69  
    70  // NewRunListener returns a new RunListener that is listening on given
    71  // socket or named pipe passed in. If a valid RunListener is returned, is
    72  // has the go routine running, and should be closed by the creator
    73  // when they are done with it.
    74  func NewRunListener(runner CommandRunner, socketPath string) (*RunListener, error) {
    75  	server := rpc.NewServer()
    76  	if err := server.Register(&JujuRunServer{runner}); err != nil {
    77  		return nil, errors.Trace(err)
    78  	}
    79  	listener, err := sockets.Listen(socketPath)
    80  	if err != nil {
    81  		return nil, errors.Trace(err)
    82  	}
    83  	runListener := &RunListener{
    84  		listener: listener,
    85  		server:   server,
    86  		closed:   make(chan struct{}),
    87  		closing:  make(chan struct{}),
    88  	}
    89  	go runListener.Run()
    90  	return runListener, nil
    91  }
    92  
    93  // Run accepts new connections until it encounters an error, or until Close is
    94  // called, and then blocks until all existing connections have been closed.
    95  func (s *RunListener) Run() (err error) {
    96  	logger.Debugf("juju-run listener running")
    97  	var conn net.Conn
    98  	for {
    99  		conn, err = s.listener.Accept()
   100  		if err != nil {
   101  			break
   102  		}
   103  		s.wg.Add(1)
   104  		go func(conn net.Conn) {
   105  			s.server.ServeConn(conn)
   106  			s.wg.Done()
   107  		}(conn)
   108  	}
   109  	logger.Debugf("juju-run listener stopping")
   110  	select {
   111  	case <-s.closing:
   112  		// Someone has called Close(), so it is overwhelmingly likely that
   113  		// the error from Accept is a direct result of the Listener being
   114  		// closed, and can therefore be safely ignored.
   115  		err = nil
   116  	default:
   117  	}
   118  	s.wg.Wait()
   119  	close(s.closed)
   120  	return
   121  }
   122  
   123  // Close immediately stops accepting connections, and blocks until all existing
   124  // connections have been closed.
   125  func (s *RunListener) Close() {
   126  	close(s.closing)
   127  	s.listener.Close()
   128  	<-s.closed
   129  	logger.Debugf("juju-run listener stopped")
   130  }