github.com/rogpeppe/juju@v0.0.0-20140613142852-6337964b789e/worker/uniter/runlistener.go (about) 1 // Copyright 2013 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // The run listener is a worker go-routine that listens on either a unix 5 // socket or a tcp connection for juju-run commands. 6 7 package uniter 8 9 import ( 10 "net" 11 "net/rpc" 12 "os" 13 "sync" 14 15 "github.com/juju/utils/exec" 16 ) 17 18 const JujuRunEndpoint = "JujuRunServer.RunCommands" 19 20 // A CommandRunner is something that will actually execute the commands and 21 // return the results of that execution in the exec.ExecResponse (which 22 // contains stdout, stderr, and return code). 23 type CommandRunner interface { 24 RunCommands(commands string) (results *exec.ExecResponse, err error) 25 } 26 27 // RunListener is responsible for listening on the network connection and 28 // seting up the rpc server on that net connection. Also starts the go routine 29 // that listens and hands off the work. 30 type RunListener struct { 31 listener net.Listener 32 server *rpc.Server 33 closed chan struct{} 34 closing chan struct{} 35 wg sync.WaitGroup 36 } 37 38 // The JujuRunServer is the entity that has the methods that are called over 39 // the rpc connection. 40 type JujuRunServer struct { 41 runner CommandRunner 42 } 43 44 // RunCommands delegates the actual running to the runner and populates the 45 // response structure. 46 func (r *JujuRunServer) RunCommands(commands string, result *exec.ExecResponse) error { 47 logger.Debugf("RunCommands: %q", commands) 48 runResult, err := r.runner.RunCommands(commands) 49 *result = *runResult 50 return err 51 } 52 53 // NewRunListener returns a new RunListener that is listening on given 54 // unix socket path passed in. If a valid RunListener is returned, is 55 // has the go routine running, and should be closed by the creator 56 // when they are done with it. 57 func NewRunListener(runner CommandRunner, socketPath string) (*RunListener, error) { 58 server := rpc.NewServer() 59 if err := server.Register(&JujuRunServer{runner}); err != nil { 60 return nil, err 61 } 62 // In case the unix socket is present, delete it. 63 if err := os.Remove(socketPath); err != nil { 64 logger.Tracef("ignoring error on removing %q: %v", socketPath, err) 65 } 66 listener, err := net.Listen("unix", socketPath) 67 if err != nil { 68 logger.Errorf("failed to listen on unix:%s: %v", socketPath, err) 69 return nil, err 70 } 71 runListener := &RunListener{ 72 listener: listener, 73 server: server, 74 closed: make(chan struct{}), 75 closing: make(chan struct{}), 76 } 77 go runListener.Run() 78 return runListener, nil 79 } 80 81 // Run accepts new connections until it encounters an error, or until Close is 82 // called, and then blocks until all existing connections have been closed. 83 func (s *RunListener) Run() (err error) { 84 logger.Debugf("juju-run listener running") 85 var conn net.Conn 86 for { 87 conn, err = s.listener.Accept() 88 if err != nil { 89 break 90 } 91 s.wg.Add(1) 92 go func(conn net.Conn) { 93 s.server.ServeConn(conn) 94 s.wg.Done() 95 }(conn) 96 } 97 logger.Debugf("juju-run listener stopping") 98 select { 99 case <-s.closing: 100 // Someone has called Close(), so it is overwhelmingly likely that 101 // the error from Accept is a direct result of the Listener being 102 // closed, and can therefore be safely ignored. 103 err = nil 104 default: 105 } 106 s.wg.Wait() 107 close(s.closed) 108 return 109 } 110 111 // Close immediately stops accepting connections, and blocks until all existing 112 // connections have been closed. 113 func (s *RunListener) Close() { 114 close(s.closing) 115 s.listener.Close() 116 <-s.closed 117 logger.Debugf("juju-run listener stopped") 118 }