github.com/Pankov404/juju@v0.0.0-20150703034450-be266991dceb/worker/uniter/runner/jujuc/server.go (about)

     1  // Copyright 2012, 2013, 2014 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // The worker/uniter/runner/jujuc package implements the server side of the
     5  // jujuc proxy tool, which forwards command invocations to the unit agent
     6  // process so that they can be executed against specific state.
     7  package jujuc
     8  
     9  import (
    10  	"bytes"
    11  	"fmt"
    12  	"io"
    13  	"net"
    14  	"net/rpc"
    15  	"path/filepath"
    16  	"sort"
    17  	"sync"
    18  
    19  	"github.com/juju/cmd"
    20  	"github.com/juju/errors"
    21  	"github.com/juju/loggo"
    22  	"github.com/juju/utils/exec"
    23  
    24  	"github.com/juju/juju/juju/sockets"
    25  )
    26  
    27  var logger = loggo.GetLogger("worker.uniter.jujuc")
    28  
    29  // ErrNoStdin is returned by Jujuc.Main if the hook tool requests
    30  // stdin, and none is supplied.
    31  var ErrNoStdin = errors.New("hook tool requires stdin, none supplied")
    32  
    33  type creator func(Context) cmd.Command
    34  
    35  // baseCommands maps Command names to creators.
    36  var baseCommands = map[string]creator{
    37  	"close-port" + cmdSuffix:    NewClosePortCommand,
    38  	"config-get" + cmdSuffix:    NewConfigGetCommand,
    39  	"juju-log" + cmdSuffix:      NewJujuLogCommand,
    40  	"open-port" + cmdSuffix:     NewOpenPortCommand,
    41  	"opened-ports" + cmdSuffix:  NewOpenedPortsCommand,
    42  	"relation-get" + cmdSuffix:  NewRelationGetCommand,
    43  	"action-get" + cmdSuffix:    NewActionGetCommand,
    44  	"action-set" + cmdSuffix:    NewActionSetCommand,
    45  	"action-fail" + cmdSuffix:   NewActionFailCommand,
    46  	"relation-ids" + cmdSuffix:  NewRelationIdsCommand,
    47  	"relation-list" + cmdSuffix: NewRelationListCommand,
    48  	"relation-set" + cmdSuffix:  NewRelationSetCommand,
    49  	"unit-get" + cmdSuffix:      NewUnitGetCommand,
    50  	"owner-get" + cmdSuffix:     NewOwnerGetCommand,
    51  	"add-metric" + cmdSuffix:    NewAddMetricCommand,
    52  	"juju-reboot" + cmdSuffix:   NewJujuRebootCommand,
    53  	"status-get" + cmdSuffix:    NewStatusGetCommand,
    54  	"status-set" + cmdSuffix:    NewStatusSetCommand,
    55  }
    56  
    57  var storageCommands = map[string]creator{
    58  	"storage-add" + cmdSuffix: NewStorageAddCommand,
    59  	"storage-get" + cmdSuffix: NewStorageGetCommand,
    60  }
    61  
    62  var leaderCommands = map[string]creator{
    63  	"is-leader" + cmdSuffix:  NewIsLeaderCommand,
    64  	"leader-get" + cmdSuffix: NewLeaderGetCommand,
    65  	"leader-set" + cmdSuffix: NewLeaderSetCommand,
    66  }
    67  
    68  func allEnabledCommands() map[string]creator {
    69  	all := map[string]creator{}
    70  	add := func(m map[string]creator) {
    71  		for k, v := range m {
    72  			all[k] = v
    73  		}
    74  	}
    75  	add(baseCommands)
    76  	add(storageCommands)
    77  	add(leaderCommands)
    78  	return all
    79  }
    80  
    81  // CommandNames returns the names of all jujuc commands.
    82  func CommandNames() (names []string) {
    83  	for name := range allEnabledCommands() {
    84  		names = append(names, name)
    85  	}
    86  	sort.Strings(names)
    87  	return
    88  }
    89  
    90  // NewCommand returns an instance of the named Command, initialized to execute
    91  // against the supplied Context.
    92  func NewCommand(ctx Context, name string) (cmd.Command, error) {
    93  	f := allEnabledCommands()[name]
    94  	if f == nil {
    95  		return nil, fmt.Errorf("unknown command: %s", name)
    96  	}
    97  	return f(ctx), nil
    98  }
    99  
   100  // Request contains the information necessary to run a Command remotely.
   101  type Request struct {
   102  	ContextId   string
   103  	Dir         string
   104  	CommandName string
   105  	Args        []string
   106  
   107  	// StdinSet indicates whether or not the client supplied stdin. This is
   108  	// necessary as Stdin will be nil if the client supplied stdin but it
   109  	// is empty.
   110  	StdinSet bool
   111  	Stdin    []byte
   112  }
   113  
   114  // CmdGetter looks up a Command implementation connected to a particular Context.
   115  type CmdGetter func(contextId, cmdName string) (cmd.Command, error)
   116  
   117  // Jujuc implements the jujuc command in the form required by net/rpc.
   118  type Jujuc struct {
   119  	mu     sync.Mutex
   120  	getCmd CmdGetter
   121  }
   122  
   123  // badReqErrorf returns an error indicating a bad Request.
   124  func badReqErrorf(format string, v ...interface{}) error {
   125  	return fmt.Errorf("bad request: "+format, v...)
   126  }
   127  
   128  // Main runs the Command specified by req, and fills in resp. A single command
   129  // is run at a time.
   130  func (j *Jujuc) Main(req Request, resp *exec.ExecResponse) error {
   131  	if req.CommandName == "" {
   132  		return badReqErrorf("command not specified")
   133  	}
   134  	if !filepath.IsAbs(req.Dir) {
   135  		return badReqErrorf("Dir is not absolute")
   136  	}
   137  	c, err := j.getCmd(req.ContextId, req.CommandName)
   138  	if err != nil {
   139  		return badReqErrorf("%s", err)
   140  	}
   141  	var stdin io.Reader
   142  	if req.StdinSet {
   143  		stdin = bytes.NewReader(req.Stdin)
   144  	} else {
   145  		// noStdinReader will error with ErrNoStdin
   146  		// if its Read method is called.
   147  		stdin = noStdinReader{}
   148  	}
   149  	var stdout, stderr bytes.Buffer
   150  	ctx := &cmd.Context{
   151  		Dir:    req.Dir,
   152  		Stdin:  stdin,
   153  		Stdout: &stdout,
   154  		Stderr: &stderr,
   155  	}
   156  	j.mu.Lock()
   157  	defer j.mu.Unlock()
   158  	logger.Infof("running hook tool %q %q", req.CommandName, req.Args)
   159  	logger.Debugf("hook context id %q; dir %q", req.ContextId, req.Dir)
   160  	wrapper := &cmdWrapper{c, nil}
   161  	resp.Code = cmd.Main(wrapper, ctx, req.Args)
   162  	if errors.Cause(wrapper.err) == ErrNoStdin {
   163  		return ErrNoStdin
   164  	}
   165  	resp.Stdout = stdout.Bytes()
   166  	resp.Stderr = stderr.Bytes()
   167  	return nil
   168  }
   169  
   170  // Server implements a server that serves command invocations via
   171  // a unix domain socket.
   172  type Server struct {
   173  	socketPath string
   174  	listener   net.Listener
   175  	server     *rpc.Server
   176  	closed     chan bool
   177  	closing    chan bool
   178  	wg         sync.WaitGroup
   179  }
   180  
   181  // NewServer creates an RPC server bound to socketPath, which can execute
   182  // remote command invocations against an appropriate Context. It will not
   183  // actually do so until Run is called.
   184  func NewServer(getCmd CmdGetter, socketPath string) (*Server, error) {
   185  	server := rpc.NewServer()
   186  	if err := server.Register(&Jujuc{getCmd: getCmd}); err != nil {
   187  		return nil, err
   188  	}
   189  	listener, err := sockets.Listen(socketPath)
   190  	if err != nil {
   191  		return nil, err
   192  	}
   193  	s := &Server{
   194  		socketPath: socketPath,
   195  		listener:   listener,
   196  		server:     server,
   197  		closed:     make(chan bool),
   198  		closing:    make(chan bool),
   199  	}
   200  	return s, nil
   201  }
   202  
   203  // Run accepts new connections until it encounters an error, or until Close is
   204  // called, and then blocks until all existing connections have been closed.
   205  func (s *Server) Run() (err error) {
   206  	var conn net.Conn
   207  	for {
   208  		conn, err = s.listener.Accept()
   209  		if err != nil {
   210  			break
   211  		}
   212  		s.wg.Add(1)
   213  		go func(conn net.Conn) {
   214  			s.server.ServeConn(conn)
   215  			s.wg.Done()
   216  		}(conn)
   217  	}
   218  	select {
   219  	case <-s.closing:
   220  		// Someone has called Close(), so it is overwhelmingly likely that
   221  		// the error from Accept is a direct result of the Listener being
   222  		// closed, and can therefore be safely ignored.
   223  		err = nil
   224  	default:
   225  	}
   226  	s.wg.Wait()
   227  	close(s.closed)
   228  	return
   229  }
   230  
   231  // Close immediately stops accepting connections, and blocks until all existing
   232  // connections have been closed.
   233  func (s *Server) Close() {
   234  	close(s.closing)
   235  	s.listener.Close()
   236  	<-s.closed
   237  }
   238  
   239  type noStdinReader struct{}
   240  
   241  // Read implements io.Reader, simply returning ErrNoStdin any time it's called.
   242  func (noStdinReader) Read([]byte) (int, error) {
   243  	return 0, ErrNoStdin
   244  }
   245  
   246  // cmdWrapper wraps a cmd.Command's Run method so the error returned can be
   247  // intercepted when the command is run via cmd.Main.
   248  type cmdWrapper struct {
   249  	cmd.Command
   250  	err error
   251  }
   252  
   253  func (c *cmdWrapper) Run(ctx *cmd.Context) error {
   254  	c.err = c.Command.Run(ctx)
   255  	return c.err
   256  }