github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/worker/introspection/socket_test.go (about)

     1  /// Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package introspection_test
     5  
     6  import (
     7  	"bufio"
     8  	"bytes"
     9  	"fmt"
    10  	"io/ioutil"
    11  	"net"
    12  	"os"
    13  	"regexp"
    14  	"runtime"
    15  	"time"
    16  
    17  	"github.com/juju/clock/testclock"
    18  	"github.com/juju/testing"
    19  	jc "github.com/juju/testing/checkers"
    20  	"github.com/prometheus/client_golang/prometheus"
    21  	gc "gopkg.in/check.v1"
    22  	"gopkg.in/juju/worker.v1"
    23  	"gopkg.in/juju/worker.v1/workertest"
    24  
    25  	// Bring in the state package for the tracker profile.
    26  	"github.com/juju/juju/core/presence"
    27  	_ "github.com/juju/juju/state"
    28  	"github.com/juju/juju/worker/introspection"
    29  )
    30  
    31  type suite struct {
    32  	testing.IsolationSuite
    33  }
    34  
    35  var _ = gc.Suite(&suite{})
    36  
    37  func (s *suite) TestConfigValidation(c *gc.C) {
    38  	w, err := introspection.NewWorker(introspection.Config{})
    39  	c.Check(w, gc.IsNil)
    40  	c.Assert(err, gc.ErrorMatches, "empty SocketName not valid")
    41  }
    42  
    43  func (s *suite) TestStartStop(c *gc.C) {
    44  	if runtime.GOOS != "linux" {
    45  		c.Skip("introspection worker not supported on non-linux")
    46  	}
    47  
    48  	w, err := introspection.NewWorker(introspection.Config{
    49  		SocketName:         "introspection-test",
    50  		PrometheusGatherer: prometheus.NewRegistry(),
    51  	})
    52  	c.Assert(err, jc.ErrorIsNil)
    53  	workertest.CheckKill(c, w)
    54  }
    55  
    56  type introspectionSuite struct {
    57  	testing.IsolationSuite
    58  
    59  	name     string
    60  	worker   worker.Worker
    61  	reporter introspection.DepEngineReporter
    62  	gatherer prometheus.Gatherer
    63  	recorder presence.Recorder
    64  }
    65  
    66  var _ = gc.Suite(&introspectionSuite{})
    67  
    68  func (s *introspectionSuite) SetUpTest(c *gc.C) {
    69  	if runtime.GOOS != "linux" {
    70  		c.Skip("introspection worker not supported on non-linux")
    71  	}
    72  	s.IsolationSuite.SetUpTest(c)
    73  	s.reporter = nil
    74  	s.worker = nil
    75  	s.recorder = nil
    76  	s.gatherer = newPrometheusGatherer()
    77  	s.startWorker(c)
    78  }
    79  
    80  func (s *introspectionSuite) startWorker(c *gc.C) {
    81  	s.name = fmt.Sprintf("introspection-test-%d", os.Getpid())
    82  	w, err := introspection.NewWorker(introspection.Config{
    83  		SocketName:         s.name,
    84  		DepEngine:          s.reporter,
    85  		PrometheusGatherer: s.gatherer,
    86  		Presence:           s.recorder,
    87  	})
    88  	c.Assert(err, jc.ErrorIsNil)
    89  	s.worker = w
    90  	s.AddCleanup(func(c *gc.C) {
    91  		workertest.CheckKill(c, w)
    92  	})
    93  }
    94  
    95  func (s *introspectionSuite) call(c *gc.C, url string) []byte {
    96  	path := "@" + s.name
    97  	conn, err := net.Dial("unix", path)
    98  	c.Assert(err, jc.ErrorIsNil)
    99  	defer conn.Close()
   100  
   101  	_, err = fmt.Fprintf(conn, "GET %s HTTP/1.0\r\n\r\n", url)
   102  	c.Assert(err, jc.ErrorIsNil)
   103  
   104  	buf, err := ioutil.ReadAll(conn)
   105  	c.Assert(err, jc.ErrorIsNil)
   106  	return buf
   107  }
   108  
   109  func (s *introspectionSuite) TestCmdLine(c *gc.C) {
   110  	buf := s.call(c, "/debug/pprof/cmdline")
   111  	c.Assert(buf, gc.NotNil)
   112  	matches(c, buf, ".*/introspection.test")
   113  }
   114  
   115  func (s *introspectionSuite) TestGoroutineProfile(c *gc.C) {
   116  	buf := s.call(c, "/debug/pprof/goroutine?debug=1")
   117  	c.Assert(buf, gc.NotNil)
   118  	matches(c, buf, `^goroutine profile: total \d+`)
   119  }
   120  
   121  func (s *introspectionSuite) TestTrace(c *gc.C) {
   122  	buf := s.call(c, "/debug/pprof/trace?seconds=1")
   123  	c.Assert(buf, gc.NotNil)
   124  	matches(c, buf, `^Content-Type: application/octet-stream*`)
   125  }
   126  
   127  func (s *introspectionSuite) TestMissingDepEngineReporter(c *gc.C) {
   128  	buf := s.call(c, "/depengine")
   129  	matches(c, buf, "404 Not Found")
   130  	matches(c, buf, "missing dependency engine reporter")
   131  }
   132  
   133  func (s *introspectionSuite) TestMissingStatePoolReporter(c *gc.C) {
   134  	buf := s.call(c, "/statepool")
   135  	matches(c, buf, "404 Not Found")
   136  	matches(c, buf, "State Pool Report: missing reporter")
   137  }
   138  
   139  func (s *introspectionSuite) TestMissingPubSubReporter(c *gc.C) {
   140  	buf := s.call(c, "/pubsub")
   141  	matches(c, buf, "404 Not Found")
   142  	matches(c, buf, "PubSub Report: missing reporter")
   143  }
   144  
   145  func (s *introspectionSuite) TestMissingMachineLock(c *gc.C) {
   146  	buf := s.call(c, "/machinelock/")
   147  	matches(c, buf, "404 Not Found")
   148  	matches(c, buf, "missing machine lock reporter")
   149  }
   150  
   151  func (s *introspectionSuite) TestStateTrackerReporter(c *gc.C) {
   152  	buf := s.call(c, "/debug/pprof/juju/state/tracker?debug=1")
   153  	matches(c, buf, "200 OK")
   154  	matches(c, buf, "juju/state/tracker profile: total")
   155  }
   156  
   157  func (s *introspectionSuite) TestEngineReporter(c *gc.C) {
   158  	// We need to make sure the existing worker is shut down
   159  	// so we can connect to the socket.
   160  	workertest.CheckKill(c, s.worker)
   161  	s.reporter = &reporter{
   162  		values: map[string]interface{}{
   163  			"working": true,
   164  		},
   165  	}
   166  	s.startWorker(c)
   167  	buf := s.call(c, "/depengine")
   168  
   169  	matches(c, buf, "200 OK")
   170  	matches(c, buf, "working: true")
   171  }
   172  
   173  func (s *introspectionSuite) TestMissingPresenceReporter(c *gc.C) {
   174  	buf := s.call(c, "/presence/")
   175  	matches(c, buf, "404 Not Found")
   176  	matches(c, buf, "page not found")
   177  }
   178  
   179  func (s *introspectionSuite) TestDisabledPresenceReporter(c *gc.C) {
   180  	// We need to make sure the existing worker is shut down
   181  	// so we can connect to the socket.
   182  	workertest.CheckKill(c, s.worker)
   183  	s.recorder = presence.New(testclock.NewClock(time.Now()))
   184  	s.startWorker(c)
   185  
   186  	buf := s.call(c, "/presence/")
   187  	matches(c, buf, "404 Not Found")
   188  	matches(c, buf, "agent is not an apiserver")
   189  }
   190  
   191  func (s *introspectionSuite) TestEnabledPresenceReporter(c *gc.C) {
   192  	// We need to make sure the existing worker is shut down
   193  	// so we can connect to the socket.
   194  	workertest.CheckKill(c, s.worker)
   195  	s.recorder = presence.New(testclock.NewClock(time.Now()))
   196  	s.recorder.Enable()
   197  	s.recorder.Connect("server", "model-uuid", "agent-1", 42, false, "")
   198  	s.startWorker(c)
   199  
   200  	buf := s.call(c, "/presence/")
   201  	matches(c, buf, "200 OK")
   202  	matches(c, buf, "AGENT    SERVER  CONN ID  STATUS")
   203  	matches(c, buf, "agent-1  server  42       alive")
   204  }
   205  
   206  func (s *introspectionSuite) TestPrometheusMetrics(c *gc.C) {
   207  	buf := s.call(c, "/metrics/")
   208  	c.Assert(buf, gc.NotNil)
   209  	matches(c, buf, "# HELP tau Tau")
   210  	matches(c, buf, "# TYPE tau counter")
   211  	matches(c, buf, "tau 6.283185")
   212  }
   213  
   214  // matches fails if regex is not found in the contents of b.
   215  // b is expected to be the response from the pprof http server, and will
   216  // contain some HTTP preamble that should be ignored.
   217  func matches(c *gc.C, b []byte, regex string) {
   218  	re, err := regexp.Compile(regex)
   219  	c.Assert(err, jc.ErrorIsNil)
   220  	r := bytes.NewReader(b)
   221  	sc := bufio.NewScanner(r)
   222  	for sc.Scan() {
   223  		if re.MatchString(sc.Text()) {
   224  			return
   225  		}
   226  	}
   227  	c.Fatalf("%q did not match regex %q", string(b), regex)
   228  }
   229  
   230  type reporter struct {
   231  	values map[string]interface{}
   232  }
   233  
   234  func (r *reporter) Report() map[string]interface{} {
   235  	return r.values
   236  }
   237  
   238  func newPrometheusGatherer() prometheus.Gatherer {
   239  	counter := prometheus.NewCounter(prometheus.CounterOpts{Name: "tau", Help: "Tau."})
   240  	counter.Add(6.283185)
   241  	r := prometheus.NewPedanticRegistry()
   242  	r.MustRegister(counter)
   243  	return r
   244  }