github.com/cloudbase/juju-core@v0.0.0-20140504232958-a7271ac7912f/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/juju/loggo"
    19  
    20  	"launchpad.net/juju-core/cmd"
    21  	"launchpad.net/juju-core/utils/exec"
    22  	"launchpad.net/juju-core/juju/osenv"
    23  )
    24  
    25  var logger = loggo.GetLogger("worker.uniter.jujuc")
    26  
    27  // CommandNames returns the names of all jujuc commands.
    28  func CommandNames() (names []string) {
    29  	for name := range newCommands {
    30  		names = append(names, name)
    31  	}
    32  	sort.Strings(names)
    33  	return
    34  }
    35  
    36  // NewCommand returns an instance of the named Command, initialized to execute
    37  // against the supplied Context.
    38  func NewCommand(ctx Context, name string) (cmd.Command, error) {
    39  	f := newCommands[name]
    40  	if f == nil {
    41  		return nil, fmt.Errorf("unknown command: %s", name)
    42  	}
    43  	return f(ctx), nil
    44  }
    45  
    46  // Request contains the information necessary to run a Command remotely.
    47  type Request struct {
    48  	ContextId   string
    49  	Dir         string
    50  	CommandName string
    51  	Args        []string
    52  }
    53  
    54  // CmdGetter looks up a Command implementation connected to a particular Context.
    55  type CmdGetter func(contextId, cmdName string) (cmd.Command, error)
    56  
    57  // Jujuc implements the jujuc command in the form required by net/rpc.
    58  type Jujuc struct {
    59  	mu     sync.Mutex
    60  	getCmd CmdGetter
    61  }
    62  
    63  // badReqErrorf returns an error indicating a bad Request.
    64  func badReqErrorf(format string, v ...interface{}) error {
    65  	return fmt.Errorf("bad request: "+format, v...)
    66  }
    67  
    68  // Main runs the Command specified by req, and fills in resp. A single command
    69  // is run at a time.
    70  func (j *Jujuc) Main(req Request, resp *exec.ExecResponse) error {
    71  	if req.CommandName == "" {
    72  		return badReqErrorf("command not specified")
    73  	}
    74  	if !filepath.IsAbs(req.Dir) {
    75  		return badReqErrorf("Dir is not absolute")
    76  	}
    77  	c, err := j.getCmd(req.ContextId, req.CommandName)
    78  	if err != nil {
    79  		return badReqErrorf("%s", err)
    80  	}
    81  	var stdin, stdout, stderr bytes.Buffer
    82  	ctx := &cmd.Context{
    83  		Dir:    req.Dir,
    84  		Stdin:  &stdin,
    85  		Stdout: &stdout,
    86  		Stderr: &stderr,
    87  	}
    88  	j.mu.Lock()
    89  	defer j.mu.Unlock()
    90  	logger.Infof("running hook tool %q %q", req.CommandName, req.Args)
    91  	logger.Debugf("hook context id %q; dir %q", req.ContextId, req.Dir)
    92  	resp.Code = cmd.Main(c, ctx, req.Args)
    93  	resp.Stdout = stdout.Bytes()
    94  	resp.Stderr = stderr.Bytes()
    95  	return nil
    96  }
    97  
    98  // Server implements a server that serves command invocations via
    99  // a unix domain socket.
   100  type Server struct {
   101  	socketPath string
   102  	listener   net.Listener
   103  	server     *rpc.Server
   104  	closed     chan bool
   105  	closing    chan bool
   106  	wg         sync.WaitGroup
   107  }
   108  
   109  // NewServer creates an RPC server bound to socketPath, which can execute
   110  // remote command invocations against an appropriate Context. It will not
   111  // actually do so until Run is called.
   112  func NewServer(getCmd CmdGetter, socketPath string) (*Server, error) {
   113  	server := rpc.NewServer()
   114  	if err := server.Register(&Jujuc{getCmd: getCmd}); err != nil {
   115  		return nil, err
   116  	}
   117  
   118  	listener, err := net.Listen(osenv.SocketType, socketPath)
   119  	if err != nil {
   120  		return nil, err
   121  	}
   122  	s := &Server{
   123  		socketPath: socketPath,
   124  		listener:   listener,
   125  		server:     server,
   126  		closed:     make(chan bool),
   127  		closing:    make(chan bool),
   128  	}
   129  	return s, nil
   130  }
   131  
   132  // Run accepts new connections until it encounters an error, or until Close is
   133  // called, and then blocks until all existing connections have been closed.
   134  func (s *Server) Run() (err error) {
   135  	var conn net.Conn
   136  	for {
   137  		conn, err = s.listener.Accept()
   138  		if err != nil {
   139  			break
   140  		}
   141  		s.wg.Add(1)
   142  		go func(conn net.Conn) {
   143  			s.server.ServeConn(conn)
   144  			s.wg.Done()
   145  		}(conn)
   146  	}
   147  	select {
   148  	case <-s.closing:
   149  		// Someone has called Close(), so it is overwhelmingly likely that
   150  		// the error from Accept is a direct result of the Listener being
   151  		// closed, and can therefore be safely ignored.
   152  		err = nil
   153  	default:
   154  	}
   155  	s.wg.Wait()
   156  	close(s.closed)
   157  	return
   158  }
   159  
   160  // Close immediately stops accepting connections, and blocks until all existing
   161  // connections have been closed.
   162  func (s *Server) Close() {
   163  	close(s.closing)
   164  	s.listener.Close()
   165  	<-s.closed
   166  }