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