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 }