Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code | Sign in
(598)

Unified Diff: worker/apiuniter/jujuc/server.go

Issue 13648044: api/uniter: ReadSettings to use params.Settings (Closed)
Patch Set: Created 11 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Please Sign in to add in-line comments.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « worker/apiuniter/jujuc/relation-set_test.go ('k') | worker/apiuniter/jujuc/server_test.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: worker/apiuniter/jujuc/server.go
=== added file 'worker/apiuniter/jujuc/server.go'
--- worker/apiuniter/jujuc/server.go 1970-01-01 00:00:00 +0000
+++ worker/apiuniter/jujuc/server.go 2013-09-10 12:21:02 +0000
@@ -0,0 +1,183 @@
+// Copyright 2012, 2013 Canonical Ltd.
+// Licensed under the AGPLv3, see LICENCE file for details.
+
+// The worker/uniter/jujuc package implements the server side of the jujuc proxy
+// tool, which forwards command invocations to the unit agent process so that
+// they can be executed against specific state.
+package jujuc
+
+import (
+ "bytes"
+ "fmt"
+ "net"
+ "net/rpc"
+ "path/filepath"
+ "sort"
+ "sync"
+
+ "launchpad.net/loggo"
+
+ "launchpad.net/juju-core/cmd"
+)
+
+var logger = loggo.GetLogger("worker.uniter.jujuc")
+
+// newCommands maps Command names to initializers.
+var newCommands = map[string]func(Context) cmd.Command{
+ "close-port": NewClosePortCommand,
+ "config-get": NewConfigGetCommand,
+ "juju-log": NewJujuLogCommand,
+ "open-port": NewOpenPortCommand,
+ "relation-get": NewRelationGetCommand,
+ "relation-ids": NewRelationIdsCommand,
+ "relation-list": NewRelationListCommand,
+ "relation-set": NewRelationSetCommand,
+ "unit-get": NewUnitGetCommand,
+}
+
+// CommandNames returns the names of all jujuc commands.
+func CommandNames() (names []string) {
+ for name := range newCommands {
+ names = append(names, name)
+ }
+ sort.Strings(names)
+ return
+}
+
+// NewCommand returns an instance of the named Command, initialized to execute
+// against the supplied Context.
+func NewCommand(ctx Context, name string) (cmd.Command, error) {
+ f := newCommands[name]
+ if f == nil {
+ return nil, fmt.Errorf("unknown command: %s", name)
+ }
+ return f(ctx), nil
+}
+
+// Request contains the information necessary to run a Command remotely.
+type Request struct {
+ ContextId string
+ Dir string
+ CommandName string
+ Args []string
+}
+
+// Response contains the return code and output generated by a Request.
+type Response struct {
+ Code int
+ Stdout []byte
+ Stderr []byte
+}
+
+// CmdGetter looks up a Command implementation connected to a particular Context.
+type CmdGetter func(contextId, cmdName string) (cmd.Command, error)
+
+// Jujuc implements the jujuc command in the form required by net/rpc.
+type Jujuc struct {
+ mu sync.Mutex
+ getCmd CmdGetter
+}
+
+// badReqErrorf returns an error indicating a bad Request.
+func badReqErrorf(format string, v ...interface{}) error {
+ return fmt.Errorf("bad request: "+format, v...)
+}
+
+// Main runs the Command specified by req, and fills in resp. A single command
+// is run at a time.
+func (j *Jujuc) Main(req Request, resp *Response) error {
+ if req.CommandName == "" {
+ return badReqErrorf("command not specified")
+ }
+ if !filepath.IsAbs(req.Dir) {
+ return badReqErrorf("Dir is not absolute")
+ }
+ c, err := j.getCmd(req.ContextId, req.CommandName)
+ if err != nil {
+ return badReqErrorf("%s", err)
+ }
+ var stdin, stdout, stderr bytes.Buffer
+ ctx := &cmd.Context{
+ Dir: req.Dir,
+ Stdin: &stdin,
+ Stdout: &stdout,
+ Stderr: &stderr,
+ }
+ j.mu.Lock()
+ defer j.mu.Unlock()
+ logger.Infof("running hook tool %q %q", req.CommandName, req.Args)
+ logger.Debugf("hook context id %q; dir %q", req.ContextId, req.Dir)
+ resp.Code = cmd.Main(c, ctx, req.Args)
+ resp.Stdout = stdout.Bytes()
+ resp.Stderr = stderr.Bytes()
+ return nil
+}
+
+// Server implements a server that serves command invocations via
+// a unix domain socket.
+type Server struct {
+ socketPath string
+ listener net.Listener
+ server *rpc.Server
+ closed chan bool
+ closing chan bool
+ wg sync.WaitGroup
+}
+
+// NewServer creates an RPC server bound to socketPath, which can execute
+// remote command invocations against an appropriate Context. It will not
+// actually do so until Run is called.
+func NewServer(getCmd CmdGetter, socketPath string) (*Server, error) {
+ server := rpc.NewServer()
+ if err := server.Register(&Jujuc{getCmd: getCmd}); err != nil {
+ return nil, err
+ }
+ listener, err := net.Listen("unix", socketPath)
+ if err != nil {
+ return nil, err
+ }
+ s := &Server{
+ socketPath: socketPath,
+ listener: listener,
+ server: server,
+ closed: make(chan bool),
+ closing: make(chan bool),
+ }
+ return s, nil
+}
+
+// Run accepts new connections until it encounters an error, or until Close is
+// called, and then blocks until all existing connections have been closed.
+func (s *Server) Run() (err error) {
+ var conn net.Conn
+ for {
+ conn, err = s.listener.Accept()
+ if err != nil {
+ break
+ }
+ s.wg.Add(1)
+ go func(conn net.Conn) {
+ s.server.ServeConn(conn)
+ s.wg.Done()
+ }(conn)
+ }
+ select {
+ case <-s.closing:
+ // Someone has called Close(), so it is overwhelmingly likely that
+ // the error from Accept is a direct result of the Listener being
+ // closed, and can therefore be safely ignored.
+ err = nil
+ default:
+ }
+ s.wg.Wait()
+ close(s.closed)
+ return
+}
+
+// Close immediately stops accepting connections, and blocks until all existing
+// connections have been closed.
+func (s *Server) Close() {
+ close(s.closing)
+ s.listener.Close()
+ <-s.closed
+}
« no previous file with comments | « worker/apiuniter/jujuc/relation-set_test.go ('k') | worker/apiuniter/jujuc/server_test.go » ('j') | no next file with comments »

Powered by Google App Engine
RSS Feeds Recent Issues | This issue
This is Rietveld f62528b