github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/worker/uniter/runcommands/runcommands.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package runcommands
     5  
     6  import (
     7  	"fmt"
     8  	"sync"
     9  
    10  	"github.com/juju/juju/worker/uniter/operation"
    11  	"github.com/juju/juju/worker/uniter/remotestate"
    12  	"github.com/juju/juju/worker/uniter/resolver"
    13  )
    14  
    15  // Commands is an interface providing a means of storing and retrieving
    16  // arguments for running commands.
    17  type Commands interface {
    18  	// AddCommand adds the given command arguments and response function
    19  	// and returns a unique identifier.
    20  	AddCommand(operation.CommandArgs, operation.CommandResponseFunc) string
    21  
    22  	// GetCommand returns the command arguments and response function
    23  	// with the specified ID, as registered in AddCommand.
    24  	GetCommand(id string) (operation.CommandArgs, operation.CommandResponseFunc)
    25  
    26  	// RemoveCommand removes the command arguments and response function
    27  	// associated with the specified ID.
    28  	RemoveCommand(id string)
    29  }
    30  
    31  type commands struct {
    32  	mu      sync.Mutex
    33  	nextId  int
    34  	pending map[string]command
    35  }
    36  
    37  type command struct {
    38  	args     operation.CommandArgs
    39  	response operation.CommandResponseFunc
    40  }
    41  
    42  func NewCommands() Commands {
    43  	return &commands{pending: make(map[string]command)}
    44  }
    45  
    46  func (c *commands) AddCommand(args operation.CommandArgs, response operation.CommandResponseFunc) string {
    47  	c.mu.Lock()
    48  	defer c.mu.Unlock()
    49  	id := fmt.Sprint(c.nextId)
    50  	c.nextId++
    51  	c.pending[id] = command{args, response}
    52  	return id
    53  }
    54  
    55  func (c *commands) RemoveCommand(id string) {
    56  	c.mu.Lock()
    57  	delete(c.pending, id)
    58  	c.mu.Unlock()
    59  }
    60  
    61  func (c *commands) GetCommand(id string) (operation.CommandArgs, operation.CommandResponseFunc) {
    62  	c.mu.Lock()
    63  	defer c.mu.Unlock()
    64  	command := c.pending[id]
    65  	return command.args, command.response
    66  }
    67  
    68  // commandsResolver is a Resolver that returns operations to run pending
    69  // commands. When a command is completed, the "commandCompleted" callback
    70  // is invoked to remove the pending command from the remote state.
    71  type commandsResolver struct {
    72  	commands         Commands
    73  	commandCompleted func(id string)
    74  }
    75  
    76  // NewCommandsResolver returns a new Resolver that returns operations to
    77  // execute "juju run" commands.
    78  //
    79  // The returned resolver's NextOp method will return operations to execute
    80  // run commands whenever the remote state's "Commands" is non-empty, by
    81  // taking the first ID in the sequence and fetching the command arguments
    82  // from the Commands interface passed into this function. When the command
    83  // execution operation is committed, the ID of the command is passed to the
    84  // "commandCompleted" callback.
    85  func NewCommandsResolver(commands Commands, commandCompleted func(string)) resolver.Resolver {
    86  	return &commandsResolver{commands, commandCompleted}
    87  }
    88  
    89  // NextOp is part of the resolver.Resolver interface.
    90  func (s *commandsResolver) NextOp(
    91  	localState resolver.LocalState,
    92  	remoteState remotestate.Snapshot,
    93  	opFactory operation.Factory,
    94  ) (operation.Operation, error) {
    95  	if len(remoteState.Commands) == 0 {
    96  		return nil, resolver.ErrNoOperation
    97  	}
    98  	id := remoteState.Commands[0]
    99  	op, err := opFactory.NewCommands(s.commands.GetCommand(id))
   100  	if err != nil {
   101  		return nil, err
   102  	}
   103  	commandCompleted := func() {
   104  		s.commands.RemoveCommand(id)
   105  		s.commandCompleted(id)
   106  	}
   107  	return &commandCompleter{op, commandCompleted}, nil
   108  }
   109  
   110  type commandCompleter struct {
   111  	operation.Operation
   112  	commandCompleted func()
   113  }
   114  
   115  func (c *commandCompleter) Commit(st operation.State) (*operation.State, error) {
   116  	result, err := c.Operation.Commit(st)
   117  	if err == nil {
   118  		c.commandCompleted()
   119  	}
   120  	return result, err
   121  }