github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/cmd/jujud/introspect/introspect.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package introspect 5 6 import ( 7 "io" 8 "net" 9 "net/http" 10 "net/http/httputil" 11 "net/url" 12 "os" 13 "path/filepath" 14 15 "github.com/juju/cmd" 16 "github.com/juju/errors" 17 "github.com/juju/gnuflag" 18 "gopkg.in/juju/names.v2" 19 20 jujucmd "github.com/juju/juju/cmd" 21 "github.com/juju/juju/cmd/jujud/agent" 22 cmdutil "github.com/juju/juju/cmd/jujud/util" 23 ) 24 25 type IntrospectCommand struct { 26 cmd.CommandBase 27 dataDir string 28 agent string 29 path string 30 listen string 31 32 // IntrospectionSocketName returns the socket name 33 // for a given tag. If IntrospectionSocketName is nil, 34 // agent.DefaultIntrospectionSocketName is used. 35 IntrospectionSocketName func(names.Tag) string 36 } 37 38 const introspectCommandDoc = ` 39 Introspect Juju agents running on this machine. 40 41 The juju-introspect command can be used to expose 42 the agent's introspection socket via HTTP, using 43 the --listen flag. e.g. 44 45 juju-introspect --listen=:6060 46 47 Otherwise, a single positional argument is required, 48 which is the path to query. e.g. 49 50 juju-introspect /debug/pprof/heap?debug=1 51 52 By default, juju-introspect operates on the 53 machine agent. If you wish to introspect a 54 unit agent on the machine, you can specify the 55 agent using --agent. e.g. 56 57 juju-introspect --agent=unit-mysql-0 metrics 58 ` 59 60 // Info returns usage information for the command. 61 func (c *IntrospectCommand) Info() *cmd.Info { 62 return jujucmd.Info(&cmd.Info{ 63 Name: "juju-introspect", 64 Args: "(--listen=...|<path>)", 65 Purpose: "introspect Juju agents running on this machine", 66 Doc: introspectCommandDoc, 67 }) 68 } 69 70 func (c *IntrospectCommand) SetFlags(f *gnuflag.FlagSet) { 71 c.CommandBase.SetFlags(f) 72 f.StringVar(&c.dataDir, "data-dir", cmdutil.DataDir, "Juju base data directory") 73 f.StringVar(&c.agent, "agent", "", "agent to introspect (defaults to machine agent)") 74 f.StringVar(&c.listen, "listen", "", "address on which to expose the introspection socket") 75 } 76 77 func (c *IntrospectCommand) Init(args []string) error { 78 if len(args) >= 1 { 79 c.path, args = args[0], args[1:] 80 } 81 if c.path == "" && c.listen == "" { 82 return errors.New("either a query path or a --listen address must be specified") 83 } 84 if c.path != "" && c.listen != "" { 85 return errors.New("a query path may not be specified with --listen") 86 } 87 return c.CommandBase.Init(args) 88 } 89 90 func (c *IntrospectCommand) Run(ctx *cmd.Context) error { 91 targetURL, err := url.Parse("http://unix.socket/" + c.path) 92 if err != nil { 93 return err 94 } 95 96 tag, err := c.getAgentTag() 97 if err != nil { 98 return err 99 } 100 101 getSocketName := c.IntrospectionSocketName 102 if getSocketName == nil { 103 getSocketName = agent.DefaultIntrospectionSocketName 104 } 105 socketName := "@" + getSocketName(tag) 106 if c.listen != "" { 107 listener, err := net.Listen("tcp", c.listen) 108 if err != nil { 109 return err 110 } 111 defer listener.Close() 112 ctx.Infof("Exposing %s introspection socket on %s", socketName, listener.Addr()) 113 proxy := httputil.NewSingleHostReverseProxy(targetURL) 114 proxy.Transport = unixSocketHTTPTransport(socketName) 115 return http.Serve(listener, proxy) 116 } 117 118 ctx.Infof("Querying %s introspection socket: %s", socketName, c.path) 119 client := unixSocketHTTPClient(socketName) 120 resp, err := client.Get(targetURL.String()) 121 if err != nil { 122 return err 123 } 124 defer resp.Body.Close() 125 126 if resp.StatusCode != http.StatusOK { 127 io.Copy(ctx.Stderr, resp.Body) 128 return errors.Errorf( 129 "response returned %d (%s)", 130 resp.StatusCode, 131 http.StatusText(resp.StatusCode), 132 ) 133 } 134 _, err = io.Copy(ctx.Stdout, resp.Body) 135 return err 136 } 137 138 func (c *IntrospectCommand) getAgentTag() (names.Tag, error) { 139 if c.agent != "" { 140 return names.ParseTag(c.agent) 141 } 142 agentsDir := filepath.Join(c.dataDir, "agents") 143 dir, err := os.Open(agentsDir) 144 if err != nil { 145 return nil, errors.Annotate(err, "opening agents dir") 146 } 147 defer dir.Close() 148 149 entries, err := dir.Readdir(-1) 150 if err != nil { 151 return nil, errors.Annotate(err, "reading agents dir") 152 } 153 for _, info := range entries { 154 name := info.Name() 155 tag, err := names.ParseTag(name) 156 if err != nil { 157 continue 158 } 159 if tag.Kind() == names.MachineTagKind { 160 return tag, nil 161 } 162 } 163 return nil, errors.New("could not determine machine tag") 164 } 165 166 func unixSocketHTTPClient(socketPath string) *http.Client { 167 return &http.Client{ 168 Transport: unixSocketHTTPTransport(socketPath), 169 } 170 } 171 172 func unixSocketHTTPTransport(socketPath string) *http.Transport { 173 return &http.Transport{ 174 Dial: func(proto, addr string) (net.Conn, error) { 175 return net.Dial("unix", socketPath) 176 }, 177 } 178 }