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 }