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