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