github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/worker/uniter/runner/jujuc/server.go (about) 1 // Copyright 2012, 2013, 2014 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // The worker/uniter/runner/jujuc package implements the server side of the 5 // jujuc proxy tool, which forwards command invocations to the unit agent 6 // process so that they can be executed against specific state. 7 package jujuc 8 9 import ( 10 "bytes" 11 "fmt" 12 "io" 13 "net" 14 "net/rpc" 15 "path/filepath" 16 "sort" 17 "sync" 18 19 "github.com/juju/cmd" 20 "github.com/juju/errors" 21 "github.com/juju/loggo" 22 "github.com/juju/utils/exec" 23 24 "github.com/juju/juju/juju/sockets" 25 ) 26 27 var logger = loggo.GetLogger("worker.uniter.jujuc") 28 29 // ErrNoStdin is returned by Jujuc.Main if the hook tool requests 30 // stdin, and none is supplied. 31 var ErrNoStdin = errors.New("hook tool requires stdin, none supplied") 32 33 type creator func(Context) (cmd.Command, error) 34 35 // baseCommands maps Command names to creators. 36 var baseCommands = map[string]creator{ 37 "close-port" + cmdSuffix: NewClosePortCommand, 38 "config-get" + cmdSuffix: NewConfigGetCommand, 39 "juju-log" + cmdSuffix: NewJujuLogCommand, 40 "open-port" + cmdSuffix: NewOpenPortCommand, 41 "opened-ports" + cmdSuffix: NewOpenedPortsCommand, 42 "relation-get" + cmdSuffix: NewRelationGetCommand, 43 "action-get" + cmdSuffix: NewActionGetCommand, 44 "action-set" + cmdSuffix: NewActionSetCommand, 45 "action-fail" + cmdSuffix: NewActionFailCommand, 46 "relation-ids" + cmdSuffix: NewRelationIdsCommand, 47 "relation-list" + cmdSuffix: NewRelationListCommand, 48 "relation-set" + cmdSuffix: NewRelationSetCommand, 49 "unit-get" + cmdSuffix: NewUnitGetCommand, 50 "add-metric" + cmdSuffix: NewAddMetricCommand, 51 "juju-reboot" + cmdSuffix: NewJujuRebootCommand, 52 "status-get" + cmdSuffix: NewStatusGetCommand, 53 "status-set" + cmdSuffix: NewStatusSetCommand, 54 } 55 56 var storageCommands = map[string]creator{ 57 "storage-add" + cmdSuffix: NewStorageAddCommand, 58 "storage-get" + cmdSuffix: NewStorageGetCommand, 59 "storage-list" + cmdSuffix: NewStorageListCommand, 60 } 61 62 var leaderCommands = map[string]creator{ 63 "is-leader" + cmdSuffix: NewIsLeaderCommand, 64 "leader-get" + cmdSuffix: NewLeaderGetCommand, 65 "leader-set" + cmdSuffix: NewLeaderSetCommand, 66 } 67 68 func allEnabledCommands() map[string]creator { 69 all := map[string]creator{} 70 add := func(m map[string]creator) { 71 for k, v := range m { 72 all[k] = v 73 } 74 } 75 add(baseCommands) 76 add(storageCommands) 77 add(leaderCommands) 78 return all 79 } 80 81 // CommandNames returns the names of all jujuc commands. 82 func CommandNames() (names []string) { 83 for name := range allEnabledCommands() { 84 names = append(names, name) 85 } 86 sort.Strings(names) 87 return 88 } 89 90 // NewCommand returns an instance of the named Command, initialized to execute 91 // against the supplied Context. 92 func NewCommand(ctx Context, name string) (cmd.Command, error) { 93 f := allEnabledCommands()[name] 94 if f == nil { 95 return nil, fmt.Errorf("unknown command: %s", name) 96 } 97 return f(ctx) 98 } 99 100 // Request contains the information necessary to run a Command remotely. 101 type Request struct { 102 ContextId string 103 Dir string 104 CommandName string 105 Args []string 106 107 // StdinSet indicates whether or not the client supplied stdin. This is 108 // necessary as Stdin will be nil if the client supplied stdin but it 109 // is empty. 110 StdinSet bool 111 Stdin []byte 112 } 113 114 // CmdGetter looks up a Command implementation connected to a particular Context. 115 type CmdGetter func(contextId, cmdName string) (cmd.Command, error) 116 117 // Jujuc implements the jujuc command in the form required by net/rpc. 118 type Jujuc struct { 119 mu sync.Mutex 120 getCmd CmdGetter 121 } 122 123 // badReqErrorf returns an error indicating a bad Request. 124 func badReqErrorf(format string, v ...interface{}) error { 125 return fmt.Errorf("bad request: "+format, v...) 126 } 127 128 // Main runs the Command specified by req, and fills in resp. A single command 129 // is run at a time. 130 func (j *Jujuc) Main(req Request, resp *exec.ExecResponse) error { 131 if req.CommandName == "" { 132 return badReqErrorf("command not specified") 133 } 134 if !filepath.IsAbs(req.Dir) { 135 return badReqErrorf("Dir is not absolute") 136 } 137 c, err := j.getCmd(req.ContextId, req.CommandName) 138 if err != nil { 139 return badReqErrorf("%s", err) 140 } 141 var stdin io.Reader 142 if req.StdinSet { 143 stdin = bytes.NewReader(req.Stdin) 144 } else { 145 // noStdinReader will error with ErrNoStdin 146 // if its Read method is called. 147 stdin = noStdinReader{} 148 } 149 var stdout, stderr bytes.Buffer 150 ctx := &cmd.Context{ 151 Dir: req.Dir, 152 Stdin: stdin, 153 Stdout: &stdout, 154 Stderr: &stderr, 155 } 156 j.mu.Lock() 157 defer j.mu.Unlock() 158 logger.Infof("running hook tool %q %q", req.CommandName, req.Args) 159 logger.Debugf("hook context id %q; dir %q", req.ContextId, req.Dir) 160 wrapper := &cmdWrapper{c, nil} 161 resp.Code = cmd.Main(wrapper, ctx, req.Args) 162 if errors.Cause(wrapper.err) == ErrNoStdin { 163 return ErrNoStdin 164 } 165 resp.Stdout = stdout.Bytes() 166 resp.Stderr = stderr.Bytes() 167 return nil 168 } 169 170 // Server implements a server that serves command invocations via 171 // a unix domain socket. 172 type Server struct { 173 socketPath string 174 listener net.Listener 175 server *rpc.Server 176 closed chan bool 177 closing chan bool 178 wg sync.WaitGroup 179 } 180 181 // NewServer creates an RPC server bound to socketPath, which can execute 182 // remote command invocations against an appropriate Context. It will not 183 // actually do so until Run is called. 184 func NewServer(getCmd CmdGetter, socketPath string) (*Server, error) { 185 server := rpc.NewServer() 186 if err := server.Register(&Jujuc{getCmd: getCmd}); err != nil { 187 return nil, err 188 } 189 listener, err := sockets.Listen(socketPath) 190 if err != nil { 191 return nil, err 192 } 193 s := &Server{ 194 socketPath: socketPath, 195 listener: listener, 196 server: server, 197 closed: make(chan bool), 198 closing: make(chan bool), 199 } 200 return s, nil 201 } 202 203 // Run accepts new connections until it encounters an error, or until Close is 204 // called, and then blocks until all existing connections have been closed. 205 func (s *Server) Run() (err error) { 206 var conn net.Conn 207 for { 208 conn, err = s.listener.Accept() 209 if err != nil { 210 break 211 } 212 s.wg.Add(1) 213 go func(conn net.Conn) { 214 s.server.ServeConn(conn) 215 s.wg.Done() 216 }(conn) 217 } 218 select { 219 case <-s.closing: 220 // Someone has called Close(), so it is overwhelmingly likely that 221 // the error from Accept is a direct result of the Listener being 222 // closed, and can therefore be safely ignored. 223 err = nil 224 default: 225 } 226 s.wg.Wait() 227 close(s.closed) 228 return 229 } 230 231 // Close immediately stops accepting connections, and blocks until all existing 232 // connections have been closed. 233 func (s *Server) Close() { 234 close(s.closing) 235 s.listener.Close() 236 <-s.closed 237 } 238 239 type noStdinReader struct{} 240 241 // Read implements io.Reader, simply returning ErrNoStdin any time it's called. 242 func (noStdinReader) Read([]byte) (int, error) { 243 return 0, ErrNoStdin 244 } 245 246 // cmdWrapper wraps a cmd.Command's Run method so the error returned can be 247 // intercepted when the command is run via cmd.Main. 248 type cmdWrapper struct { 249 cmd.Command 250 err error 251 } 252 253 func (c *cmdWrapper) Run(ctx *cmd.Context) error { 254 c.err = c.Command.Run(ctx) 255 return c.err 256 }