launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/worker/uniter/jujuc/server.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // The worker/uniter/jujuc package implements the server side of the jujuc proxy
     5  // tool, which forwards command invocations to the unit agent process so that
     6  // they can be executed against specific state.
     7  package jujuc
     8  
     9  import (
    10  	"bytes"
    11  	"fmt"
    12  	"net"
    13  	"net/rpc"
    14  	"path/filepath"
    15  	"sort"
    16  	"sync"
    17  
    18  	"github.com/loggo/loggo"
    19  
    20  	"launchpad.net/errgo/errors"
    21  	"launchpad.net/juju-core/cmd"
    22  	"launchpad.net/juju-core/utils/exec"
    23  )
    24  
    25  var mask = errors.Mask
    26  
    27  var logger = loggo.GetLogger("worker.uniter.jujuc")
    28  
    29  // newCommands maps Command names to initializers.
    30  var newCommands = map[string]func(Context) cmd.Command{
    31  	"close-port":    NewClosePortCommand,
    32  	"config-get":    NewConfigGetCommand,
    33  	"juju-log":      NewJujuLogCommand,
    34  	"open-port":     NewOpenPortCommand,
    35  	"relation-get":  NewRelationGetCommand,
    36  	"relation-ids":  NewRelationIdsCommand,
    37  	"relation-list": NewRelationListCommand,
    38  	"relation-set":  NewRelationSetCommand,
    39  	"unit-get":      NewUnitGetCommand,
    40  	"owner-get":     NewOwnerGetCommand,
    41  }
    42  
    43  // CommandNames returns the names of all jujuc commands.
    44  func CommandNames() (names []string) {
    45  	for name := range newCommands {
    46  		names = append(names, name)
    47  	}
    48  	sort.Strings(names)
    49  	return
    50  }
    51  
    52  // NewCommand returns an instance of the named Command, initialized to execute
    53  // against the supplied Context.
    54  func NewCommand(ctx Context, name string) (cmd.Command, error) {
    55  	f := newCommands[name]
    56  	if f == nil {
    57  		return nil, errors.Newf("unknown command: %s", name)
    58  	}
    59  	return f(ctx), nil
    60  }
    61  
    62  // Request contains the information necessary to run a Command remotely.
    63  type Request struct {
    64  	ContextId   string
    65  	Dir         string
    66  	CommandName string
    67  	Args        []string
    68  }
    69  
    70  // CmdGetter looks up a Command implementation connected to a particular Context.
    71  type CmdGetter func(contextId, cmdName string) (cmd.Command, error)
    72  
    73  // Jujuc implements the jujuc command in the form required by net/rpc.
    74  type Jujuc struct {
    75  	mu     sync.Mutex
    76  	getCmd CmdGetter
    77  }
    78  
    79  // badReqErrorf returns an error indicating a bad Request.
    80  func badReqErrorf(format string, v ...interface{}) error {
    81  	return fmt.Errorf("bad request: "+format, v...)
    82  }
    83  
    84  // Main runs the Command specified by req, and fills in resp. A single command
    85  // is run at a time.
    86  func (j *Jujuc) Main(req Request, resp *exec.ExecResponse) error {
    87  	if req.CommandName == "" {
    88  		return badReqErrorf("command not specified")
    89  	}
    90  	if !filepath.IsAbs(req.Dir) {
    91  		return badReqErrorf("Dir is not absolute")
    92  	}
    93  	c, err := j.getCmd(req.ContextId, req.CommandName)
    94  	if err != nil {
    95  		return badReqErrorf("%s", err)
    96  	}
    97  	var stdin, stdout, stderr bytes.Buffer
    98  	ctx := &cmd.Context{
    99  		Dir:    req.Dir,
   100  		Stdin:  &stdin,
   101  		Stdout: &stdout,
   102  		Stderr: &stderr,
   103  	}
   104  	j.mu.Lock()
   105  	defer j.mu.Unlock()
   106  	logger.Infof("running hook tool %q %q", req.CommandName, req.Args)
   107  	logger.Debugf("hook context id %q; dir %q", req.ContextId, req.Dir)
   108  	resp.Code = cmd.Main(c, ctx, req.Args)
   109  	resp.Stdout = stdout.Bytes()
   110  	resp.Stderr = stderr.Bytes()
   111  	return nil
   112  }
   113  
   114  // Server implements a server that serves command invocations via
   115  // a unix domain socket.
   116  type Server struct {
   117  	socketPath string
   118  	listener   net.Listener
   119  	server     *rpc.Server
   120  	closed     chan bool
   121  	closing    chan bool
   122  	wg         sync.WaitGroup
   123  }
   124  
   125  // NewServer creates an RPC server bound to socketPath, which can execute
   126  // remote command invocations against an appropriate Context. It will not
   127  // actually do so until Run is called.
   128  func NewServer(getCmd CmdGetter, socketPath string) (*Server, error) {
   129  	server := rpc.NewServer()
   130  	if err := server.Register(&Jujuc{getCmd: getCmd}); err != nil {
   131  		return nil, mask(err)
   132  	}
   133  	listener, err := net.Listen("unix", socketPath)
   134  	if err != nil {
   135  		return nil, mask(err)
   136  	}
   137  	s := &Server{
   138  		socketPath: socketPath,
   139  		listener:   listener,
   140  		server:     server,
   141  		closed:     make(chan bool),
   142  		closing:    make(chan bool),
   143  	}
   144  	return s, nil
   145  }
   146  
   147  // Run accepts new connections until it encounters an error, or until Close is
   148  // called, and then blocks until all existing connections have been closed.
   149  func (s *Server) Run() (err error) {
   150  	var conn net.Conn
   151  	for {
   152  		conn, err = s.listener.Accept()
   153  		if err != nil {
   154  			break
   155  		}
   156  		s.wg.Add(1)
   157  		go func(conn net.Conn) {
   158  			s.server.ServeConn(conn)
   159  			s.wg.Done()
   160  		}(conn)
   161  	}
   162  	select {
   163  	case <-s.closing:
   164  		// Someone has called Close(), so it is overwhelmingly likely that
   165  		// the error from Accept is a direct result of the Listener being
   166  		// closed, and can therefore be safely ignored.
   167  		err = nil
   168  	default:
   169  	}
   170  	s.wg.Wait()
   171  	close(s.closed)
   172  	return
   173  }
   174  
   175  // Close immediately stops accepting connections, and blocks until all existing
   176  // connections have been closed.
   177  func (s *Server) Close() {
   178  	close(s.closing)
   179  	s.listener.Close()
   180  	<-s.closed
   181  }