github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/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  	"gopkg.in/tomb.v1"
    15  
    16  	"github.com/juju/errors"
    17  	"github.com/juju/utils/exec"
    18  
    19  	"github.com/juju/juju/juju/sockets"
    20  	"github.com/juju/juju/worker"
    21  	"github.com/juju/juju/worker/uniter/operation"
    22  	"github.com/juju/juju/worker/uniter/runcommands"
    23  )
    24  
    25  const JujuRunEndpoint = "JujuRunServer.RunCommands"
    26  
    27  var errCommandAborted = errors.New("command execution aborted")
    28  
    29  // RunCommandsArgs stores the arguments for a RunCommands call.
    30  type RunCommandsArgs struct {
    31  	// Commands is the arbitrary commands to execute on the unit
    32  	Commands string
    33  	// RelationId is the relation context to execute the commands in.
    34  	RelationId int
    35  	// RemoteUnitName is the remote unit for the relation context.
    36  	RemoteUnitName string
    37  	// ForceRemoteUnit skips relation membership and existence validation.
    38  	ForceRemoteUnit bool
    39  }
    40  
    41  // A CommandRunner is something that will actually execute the commands and
    42  // return the results of that execution in the exec.ExecResponse (which
    43  // contains stdout, stderr, and return code).
    44  type CommandRunner interface {
    45  	RunCommands(RunCommandsArgs RunCommandsArgs) (results *exec.ExecResponse, err error)
    46  }
    47  
    48  // RunListenerConfig contains the configuration for a RunListener.
    49  type RunListenerConfig struct {
    50  	// SocketPath is the path of the socket to listen on for run commands.
    51  	SocketPath string
    52  
    53  	// CommandRunner is the CommandRunner that will run commands.
    54  	CommandRunner CommandRunner
    55  }
    56  
    57  func (cfg *RunListenerConfig) Validate() error {
    58  	if cfg.SocketPath == "" {
    59  		return errors.NotValidf("SocketPath unspecified")
    60  	}
    61  	if cfg.CommandRunner == nil {
    62  		return errors.NotValidf("CommandRunner unspecified")
    63  	}
    64  	return nil
    65  }
    66  
    67  // RunListener is responsible for listening on the network connection and
    68  // setting up the rpc server on that net connection. Also starts the go routine
    69  // that listens and hands off the work.
    70  type RunListener struct {
    71  	RunListenerConfig
    72  	listener net.Listener
    73  	server   *rpc.Server
    74  	closed   chan struct{}
    75  	closing  chan struct{}
    76  	wg       sync.WaitGroup
    77  }
    78  
    79  // NewRunListener returns a new RunListener that is listening on given
    80  // socket or named pipe passed in. If a valid RunListener is returned, is
    81  // has the go routine running, and should be closed by the creator
    82  // when they are done with it.
    83  func NewRunListener(cfg RunListenerConfig) (*RunListener, error) {
    84  	if err := cfg.Validate(); err != nil {
    85  		return nil, errors.Trace(err)
    86  	}
    87  	listener, err := sockets.Listen(cfg.SocketPath)
    88  	if err != nil {
    89  		return nil, errors.Trace(err)
    90  	}
    91  	runListener := &RunListener{
    92  		RunListenerConfig: cfg,
    93  		listener:          listener,
    94  		server:            rpc.NewServer(),
    95  		closed:            make(chan struct{}),
    96  		closing:           make(chan struct{}),
    97  	}
    98  	if err := runListener.server.Register(&JujuRunServer{runListener}); err != nil {
    99  		return nil, errors.Trace(err)
   100  	}
   101  	go runListener.Run()
   102  	return runListener, nil
   103  }
   104  
   105  // Run accepts new connections until it encounters an error, or until Close is
   106  // called, and then blocks until all existing connections have been closed.
   107  func (s *RunListener) Run() (err error) {
   108  	logger.Debugf("juju-run listener running")
   109  	var conn net.Conn
   110  	for {
   111  		conn, err = s.listener.Accept()
   112  		if err != nil {
   113  			break
   114  		}
   115  		s.wg.Add(1)
   116  		go func(conn net.Conn) {
   117  			s.server.ServeConn(conn)
   118  			s.wg.Done()
   119  		}(conn)
   120  	}
   121  	logger.Debugf("juju-run listener stopping")
   122  	select {
   123  	case <-s.closing:
   124  		// Someone has called Close(), so it is overwhelmingly likely that
   125  		// the error from Accept is a direct result of the Listener being
   126  		// closed, and can therefore be safely ignored.
   127  		err = nil
   128  	default:
   129  	}
   130  	s.wg.Wait()
   131  	close(s.closed)
   132  	return
   133  }
   134  
   135  // Close immediately stops accepting connections, and blocks until all existing
   136  // connections have been closed.
   137  func (s *RunListener) Close() error {
   138  	defer func() {
   139  		<-s.closed
   140  		logger.Debugf("juju-run listener stopped")
   141  	}()
   142  	close(s.closing)
   143  	return s.listener.Close()
   144  }
   145  
   146  // RunCommands executes the supplied commands in a hook context.
   147  func (r *RunListener) RunCommands(args RunCommandsArgs) (results *exec.ExecResponse, err error) {
   148  	logger.Tracef("run commands: %s", args.Commands)
   149  	return r.CommandRunner.RunCommands(args)
   150  }
   151  
   152  // newRunListenerWrapper returns a worker that will Close the supplied run
   153  // listener when the worker is killed. The Wait() method will never return
   154  // an error -- NewRunListener just drops the Run error on the floor and that's
   155  // not what I'm fixing here.
   156  func newRunListenerWrapper(rl *RunListener) worker.Worker {
   157  	rlw := &runListenerWrapper{rl: rl}
   158  	go func() {
   159  		defer rlw.tomb.Done()
   160  		defer rlw.tearDown()
   161  		<-rlw.tomb.Dying()
   162  	}()
   163  	return rlw
   164  }
   165  
   166  type runListenerWrapper struct {
   167  	tomb tomb.Tomb
   168  	rl   *RunListener
   169  }
   170  
   171  func (rlw *runListenerWrapper) tearDown() {
   172  	if err := rlw.rl.Close(); err != nil {
   173  		logger.Warningf("error closing runlistener: %v", err)
   174  	}
   175  }
   176  
   177  // Kill is part of the worker.Worker interface.
   178  func (rlw *runListenerWrapper) Kill() {
   179  	rlw.tomb.Kill(nil)
   180  }
   181  
   182  // Wait is part of the worker.Worker interface.
   183  func (rlw *runListenerWrapper) Wait() error {
   184  	return rlw.tomb.Wait()
   185  }
   186  
   187  // The JujuRunServer is the entity that has the methods that are called over
   188  // the rpc connection.
   189  type JujuRunServer struct {
   190  	runner CommandRunner
   191  }
   192  
   193  // RunCommands delegates the actual running to the runner and populates the
   194  // response structure.
   195  func (r *JujuRunServer) RunCommands(args RunCommandsArgs, result *exec.ExecResponse) error {
   196  	logger.Debugf("RunCommands: %+v", args)
   197  	runResult, err := r.runner.RunCommands(args)
   198  	if err != nil {
   199  		return errors.Annotate(err, "r.runner.RunCommands")
   200  	}
   201  	*result = *runResult
   202  	return err
   203  }
   204  
   205  // ChannelCommandRunnerConfig contains the configuration for a ChannelCommandRunner.
   206  type ChannelCommandRunnerConfig struct {
   207  	// Abort is a channel that will be closed when the runner should abort
   208  	// the execution of run commands.
   209  	Abort <-chan struct{}
   210  
   211  	// Commands is used to add commands received from the listener.
   212  	Commands runcommands.Commands
   213  
   214  	// CommandChannel will be sent the IDs of commands added to Commands.
   215  	CommandChannel chan<- string
   216  }
   217  
   218  func (cfg ChannelCommandRunnerConfig) Validate() error {
   219  	if cfg.Abort == nil {
   220  		return errors.NotValidf("Abort unspecified")
   221  	}
   222  	if cfg.Commands == nil {
   223  		return errors.NotValidf("Commands unspecified")
   224  	}
   225  	if cfg.CommandChannel == nil {
   226  		return errors.NotValidf("CommandChannel unspecified")
   227  	}
   228  	return nil
   229  }
   230  
   231  // ChannelCommandRunner is a CommandRunner that registers command
   232  // arguments in a runcommands.Commands, sends the returned IDs to
   233  // a channel and waits for response callbacks.
   234  type ChannelCommandRunner struct {
   235  	config ChannelCommandRunnerConfig
   236  }
   237  
   238  // NewChannelCommandRunner returns a new ChannelCommandRunner with the
   239  // given configuration.
   240  func NewChannelCommandRunner(cfg ChannelCommandRunnerConfig) (*ChannelCommandRunner, error) {
   241  	if err := cfg.Validate(); err != nil {
   242  		return nil, errors.Trace(err)
   243  	}
   244  	return &ChannelCommandRunner{cfg}, nil
   245  }
   246  
   247  // RunCommands executes the supplied run commands by registering the
   248  // arguments in a runcommands.Commands, and then sending the returned
   249  // ID to a channel and waiting for a response callback.
   250  func (c *ChannelCommandRunner) RunCommands(args RunCommandsArgs) (results *exec.ExecResponse, err error) {
   251  	type responseInfo struct {
   252  		response *exec.ExecResponse
   253  		err      error
   254  	}
   255  
   256  	// NOTE(axw) the response channel must be synchronous so that the
   257  	// response is received before the uniter resumes operation, and
   258  	// potentially aborts. This prevents a race when rebooting.
   259  	responseChan := make(chan responseInfo)
   260  	responseFunc := func(response *exec.ExecResponse, err error) {
   261  		select {
   262  		case <-c.config.Abort:
   263  		case responseChan <- responseInfo{response, err}:
   264  		}
   265  	}
   266  
   267  	id := c.config.Commands.AddCommand(
   268  		operation.CommandArgs{
   269  			Commands:        args.Commands,
   270  			RelationId:      args.RelationId,
   271  			RemoteUnitName:  args.RemoteUnitName,
   272  			ForceRemoteUnit: args.ForceRemoteUnit,
   273  		},
   274  		responseFunc,
   275  	)
   276  	select {
   277  	case <-c.config.Abort:
   278  		return nil, errCommandAborted
   279  	case c.config.CommandChannel <- id:
   280  	}
   281  
   282  	select {
   283  	case <-c.config.Abort:
   284  		return nil, errCommandAborted
   285  	case response := <-responseChan:
   286  		return response.response, response.err
   287  	}
   288  }