~dave-cheney/pyjuju/go-environ-schema

« back to all changes in this revision

Viewing changes to cmd/jujuc/server/server.go

  • Committer: William Reade
  • Author(s): William Reade
  • Date: 2012-05-03 08:24:26 UTC
  • mfrom: (127.7.10 go)
  • Revision ID: fwereade@gmail.com-20120503082426-x2atgwasyxf1i451
RPC server for remote command invocation

R=
CC=
https://codereview.appspot.com/6120054

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// The cmd/jujuc/server package allows a process to expose an RPC interface that
 
2
// allows client processes to delegate execution of cmd.Commands to a server
 
3
// process (with the exposed commands amenable to specialisation by context id).
 
4
package server
 
5
 
 
6
import (
 
7
        "bytes"
 
8
        "fmt"
 
9
        "launchpad.net/juju/go/cmd"
 
10
        "net"
 
11
        "net/rpc"
 
12
        "os"
 
13
        "path/filepath"
 
14
        "sync"
 
15
)
 
16
 
 
17
// Request contains the information necessary to run a Command remotely.
 
18
type Request struct {
 
19
        ContextId   string
 
20
        Dir         string
 
21
        CommandName string
 
22
        Args        []string
 
23
}
 
24
 
 
25
// Response contains the return code and output generated by a Request.
 
26
type Response struct {
 
27
        Code   int
 
28
        Stdout []byte
 
29
        Stderr []byte
 
30
}
 
31
 
 
32
// CmdGetter looks up a Command implementation connected to a particular Context.
 
33
type CmdGetter func(contextId, cmdName string) (cmd.Command, error)
 
34
 
 
35
// Jujuc implements the jujuc command in the form required by net/rpc.
 
36
type Jujuc struct {
 
37
        getCmd CmdGetter
 
38
}
 
39
 
 
40
// badReqErr returns an error indicating a bad Request.
 
41
func badReqErr(format string, v ...interface{}) error {
 
42
        return fmt.Errorf("bad request: "+format, v...)
 
43
}
 
44
 
 
45
// Main runs the Command specified by req, and fills in resp.
 
46
func (j *Jujuc) Main(req Request, resp *Response) error {
 
47
        if req.CommandName == "" {
 
48
                return badReqErr("command not specified")
 
49
        }
 
50
        if !filepath.IsAbs(req.Dir) {
 
51
                return badReqErr("Dir is not absolute")
 
52
        }
 
53
        c, err := j.getCmd(req.ContextId, req.CommandName)
 
54
        if err != nil {
 
55
                return badReqErr("%s", err)
 
56
        }
 
57
        var stdout, stderr bytes.Buffer
 
58
        ctx := &cmd.Context{req.Dir, &stdout, &stderr}
 
59
        resp.Code = cmd.Main(c, ctx, req.Args)
 
60
        resp.Stdout = stdout.Bytes()
 
61
        resp.Stderr = stderr.Bytes()
 
62
        return nil
 
63
}
 
64
 
 
65
// Server implements a server that serves command invocations via
 
66
// a unix domain socket.
 
67
type Server struct {
 
68
        socketPath string
 
69
        listener   net.Listener
 
70
        server     *rpc.Server
 
71
        closed     chan bool
 
72
        closing    chan bool
 
73
        wg         sync.WaitGroup
 
74
}
 
75
 
 
76
// NewServer creates an RPC server bound to socketPath, which can execute
 
77
// remote command invocations against an appropriate Context. It will not
 
78
// actually do so until Run is called.
 
79
func NewServer(getCmd CmdGetter, socketPath string) (*Server, error) {
 
80
        server := rpc.NewServer()
 
81
        if err := server.Register(&Jujuc{getCmd}); err != nil {
 
82
                return nil, err
 
83
        }
 
84
        listener, err := net.Listen("unix", socketPath)
 
85
        if err != nil {
 
86
                return nil, err
 
87
        }
 
88
        s := &Server{
 
89
                socketPath: socketPath,
 
90
                listener:   listener,
 
91
                server:     server,
 
92
                closed:     make(chan bool),
 
93
                closing:    make(chan bool),
 
94
        }
 
95
        return s, nil
 
96
}
 
97
 
 
98
// Run accepts new connections until it encounters an error, or until Close is
 
99
// called, and then blocks until all existing connections have been closed.
 
100
func (s *Server) Run() (err error) {
 
101
        var conn net.Conn
 
102
        for {
 
103
                conn, err = s.listener.Accept()
 
104
                if err != nil {
 
105
                        break
 
106
                }
 
107
                s.wg.Add(1)
 
108
                go func(conn net.Conn) {
 
109
                        s.server.ServeConn(conn)
 
110
                        s.wg.Done()
 
111
                }(conn)
 
112
        }
 
113
        select {
 
114
        case <-s.closing:
 
115
                // Someone has called Close(), so it is overwhelmingly likely that
 
116
                // the error from Accept is a direct result of the Listener being
 
117
                // closed, and can therefore be safely ignored.
 
118
                err = nil
 
119
        default:
 
120
        }
 
121
        s.wg.Wait()
 
122
        close(s.closed)
 
123
        return
 
124
}
 
125
 
 
126
// Close immediately stops accepting connections, and blocks until all existing
 
127
// connections have been closed.
 
128
func (s *Server) Close() {
 
129
        close(s.closing)
 
130
        s.listener.Close()
 
131
        os.Remove(s.socketPath)
 
132
        <-s.closed
 
133
}