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