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

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package httpserver_test
     5  
     6  import (
     7  	"crypto/tls"
     8  	"fmt"
     9  	"io"
    10  	"io/ioutil"
    11  	"net"
    12  	"net/http"
    13  	"net/url"
    14  	"os"
    15  	"path/filepath"
    16  	"strings"
    17  	"time"
    18  
    19  	"github.com/juju/clock/testclock"
    20  	"github.com/juju/pubsub"
    21  	"github.com/juju/testing"
    22  	jc "github.com/juju/testing/checkers"
    23  	gc "gopkg.in/check.v1"
    24  	"gopkg.in/juju/worker.v1/workertest"
    25  
    26  	"github.com/juju/juju/api"
    27  	"github.com/juju/juju/apiserver/apiserverhttp"
    28  	"github.com/juju/juju/pubsub/apiserver"
    29  	coretesting "github.com/juju/juju/testing"
    30  	"github.com/juju/juju/worker/httpserver"
    31  )
    32  
    33  type workerFixture struct {
    34  	testing.IsolationSuite
    35  	prometheusRegisterer stubPrometheusRegisterer
    36  	agentName            string
    37  	mux                  *apiserverhttp.Mux
    38  	clock                *testclock.Clock
    39  	hub                  *pubsub.StructuredHub
    40  	config               httpserver.Config
    41  	logDir               string
    42  	stub                 testing.Stub
    43  }
    44  
    45  func (s *workerFixture) SetUpTest(c *gc.C) {
    46  	s.IsolationSuite.SetUpTest(c)
    47  	certPool, err := api.CreateCertPool(coretesting.CACert)
    48  	c.Assert(err, jc.ErrorIsNil)
    49  	tlsConfig := api.NewTLSConfig(certPool)
    50  	tlsConfig.ServerName = "juju-apiserver"
    51  	tlsConfig.Certificates = []tls.Certificate{*coretesting.ServerTLSCert}
    52  	s.prometheusRegisterer = stubPrometheusRegisterer{}
    53  	s.mux = apiserverhttp.NewMux()
    54  	s.clock = testclock.NewClock(time.Now())
    55  	s.hub = pubsub.NewStructuredHub(nil)
    56  	s.agentName = "machine-42"
    57  	s.logDir = c.MkDir()
    58  	s.config = httpserver.Config{
    59  		AgentName:            s.agentName,
    60  		Clock:                s.clock,
    61  		TLSConfig:            tlsConfig,
    62  		Mux:                  s.mux,
    63  		PrometheusRegisterer: &s.prometheusRegisterer,
    64  		LogDir:               s.logDir,
    65  		MuxShutdownWait:      1 * time.Minute,
    66  		Hub:                  s.hub,
    67  		APIPort:              0,
    68  		APIPortOpenDelay:     0,
    69  		ControllerAPIPort:    0,
    70  	}
    71  }
    72  
    73  type WorkerValidationSuite struct {
    74  	workerFixture
    75  }
    76  
    77  var _ = gc.Suite(&WorkerValidationSuite{})
    78  
    79  func (s *WorkerValidationSuite) TestValidateErrors(c *gc.C) {
    80  	type test struct {
    81  		f      func(*httpserver.Config)
    82  		expect string
    83  	}
    84  	tests := []test{{
    85  		func(cfg *httpserver.Config) { cfg.AgentName = "" },
    86  		"empty AgentName not valid",
    87  	}, {
    88  		func(cfg *httpserver.Config) { cfg.TLSConfig = nil },
    89  		"nil TLSConfig not valid",
    90  	}, {
    91  		func(cfg *httpserver.Config) { cfg.Mux = nil },
    92  		"nil Mux not valid",
    93  	}, {
    94  		func(cfg *httpserver.Config) { cfg.PrometheusRegisterer = nil },
    95  		"nil PrometheusRegisterer not valid",
    96  	}}
    97  	for i, test := range tests {
    98  		c.Logf("test #%d (%s)", i, test.expect)
    99  		s.testValidateError(c, test.f, test.expect)
   100  	}
   101  }
   102  
   103  func (s *WorkerValidationSuite) testValidateError(c *gc.C, f func(*httpserver.Config), expect string) {
   104  	config := s.config
   105  	f(&config)
   106  	w, err := httpserver.NewWorker(config)
   107  	if !c.Check(err, gc.NotNil) {
   108  		workertest.DirtyKill(c, w)
   109  		return
   110  	}
   111  	c.Check(w, gc.IsNil)
   112  	c.Check(err, gc.ErrorMatches, expect)
   113  }
   114  
   115  type WorkerSuite struct {
   116  	workerFixture
   117  	worker *httpserver.Worker
   118  }
   119  
   120  var _ = gc.Suite(&WorkerSuite{})
   121  
   122  func (s *WorkerSuite) SetUpTest(c *gc.C) {
   123  	s.workerFixture.SetUpTest(c)
   124  	worker, err := httpserver.NewWorker(s.config)
   125  	c.Assert(err, jc.ErrorIsNil)
   126  	s.AddCleanup(func(c *gc.C) {
   127  		workertest.DirtyKill(c, worker)
   128  	})
   129  	s.worker = worker
   130  }
   131  
   132  func (s *WorkerSuite) TestStartStop(c *gc.C) {
   133  	workertest.CleanKill(c, s.worker)
   134  }
   135  
   136  func (s *WorkerSuite) TestURL(c *gc.C) {
   137  	url := s.worker.URL()
   138  	c.Assert(url, gc.Matches, "https://.*")
   139  }
   140  
   141  func (s *WorkerSuite) TestURLWorkerDead(c *gc.C) {
   142  	workertest.CleanKill(c, s.worker)
   143  	url := s.worker.URL()
   144  	c.Assert(url, gc.Matches, "")
   145  }
   146  
   147  func (s *WorkerSuite) TestRoundTrip(c *gc.C) {
   148  	s.makeRequest(c, s.worker.URL())
   149  }
   150  
   151  func (s *WorkerSuite) makeRequest(c *gc.C, url string) {
   152  	s.mux.AddHandler("GET", "/hello/:name", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   153  		w.WriteHeader(http.StatusOK)
   154  		io.WriteString(w, "hello, "+r.URL.Query().Get(":name"))
   155  	}))
   156  	client := &http.Client{
   157  		Transport: &http.Transport{
   158  			TLSClientConfig: s.config.TLSConfig,
   159  		},
   160  	}
   161  	resp, err := client.Get(url + "/hello/world")
   162  	c.Assert(err, jc.ErrorIsNil)
   163  	defer resp.Body.Close()
   164  
   165  	c.Assert(resp.StatusCode, gc.Equals, http.StatusOK)
   166  	out, err := ioutil.ReadAll(resp.Body)
   167  	c.Assert(err, jc.ErrorIsNil)
   168  	c.Assert(string(out), gc.Equals, "hello, world")
   169  }
   170  
   171  func (s *WorkerSuite) TestWaitsForClients(c *gc.C) {
   172  	// Check that the httpserver stays functional until any clients
   173  	// have finished with it.
   174  	s.mux.AddClient()
   175  
   176  	// Shouldn't take effect until the client has done.
   177  	s.worker.Kill()
   178  
   179  	waitResult := make(chan error)
   180  	go func() {
   181  		waitResult <- s.worker.Wait()
   182  	}()
   183  
   184  	select {
   185  	case <-waitResult:
   186  		c.Fatalf("didn't wait for clients to finish with the mux")
   187  	case <-time.After(coretesting.ShortWait):
   188  	}
   189  
   190  	s.mux.ClientDone()
   191  	select {
   192  	case err := <-waitResult:
   193  		c.Assert(err, jc.ErrorIsNil)
   194  	case <-time.After(coretesting.LongWait):
   195  		c.Fatalf("didn't stop after clients were finished")
   196  	}
   197  	// Normal exit, no debug file.
   198  	_, err := os.Stat(filepath.Join(s.logDir, "apiserver-debug.log"))
   199  	c.Assert(err, jc.Satisfies, os.IsNotExist)
   200  }
   201  
   202  func (s *WorkerSuite) TestExitsWithTardyClients(c *gc.C) {
   203  	// Check that the httpserver shuts down eventually if
   204  	// clients appear to be stuck.
   205  	s.mux.AddClient()
   206  
   207  	// Shouldn't take effect until the timeout.
   208  	s.worker.Kill()
   209  
   210  	waitResult := make(chan error)
   211  	go func() {
   212  		waitResult <- s.worker.Wait()
   213  	}()
   214  
   215  	select {
   216  	case <-waitResult:
   217  		c.Fatalf("didn't wait for timeout")
   218  	case <-time.After(coretesting.ShortWait):
   219  	}
   220  
   221  	// Don't call s.mux.ClientDone(), timeout instead.
   222  	s.clock.Advance(1 * time.Minute)
   223  	select {
   224  	case err := <-waitResult:
   225  		c.Assert(err, jc.ErrorIsNil)
   226  	case <-time.After(coretesting.LongWait):
   227  		c.Fatalf("didn't stop after timeout")
   228  	}
   229  	// There should be a log file with goroutines.
   230  	data, err := ioutil.ReadFile(filepath.Join(s.logDir, "apiserver-debug.log"))
   231  	c.Assert(err, jc.ErrorIsNil)
   232  	lines := strings.Split(string(data), "\n")
   233  	c.Assert(len(lines), jc.GreaterThan, 1)
   234  	c.Assert(lines[1], gc.Matches, "goroutine profile:.*")
   235  }
   236  
   237  func (s *WorkerSuite) TestMinTLSVersion(c *gc.C) {
   238  	parsed, err := url.Parse(s.worker.URL())
   239  	c.Assert(err, jc.ErrorIsNil)
   240  
   241  	tlsConfig := s.config.TLSConfig
   242  	// Specify an unsupported TLS version
   243  	tlsConfig.MaxVersion = tls.VersionSSL30
   244  
   245  	conn, err := tls.Dial("tcp", parsed.Host, tlsConfig)
   246  	c.Assert(err, gc.ErrorMatches, ".*protocol version not supported")
   247  	c.Assert(conn, gc.IsNil)
   248  }
   249  
   250  func (s *WorkerSuite) TestHeldListener(c *gc.C) {
   251  	// Worker url comes back as "" when the worker is dying.
   252  	url := s.worker.URL()
   253  
   254  	// Simulate having a slow request being handled.
   255  	s.mux.AddClient()
   256  
   257  	err := s.mux.AddHandler("GET", "/quick", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   258  		w.WriteHeader(http.StatusOK)
   259  	}))
   260  	c.Assert(err, jc.ErrorIsNil)
   261  
   262  	quickErr := make(chan error)
   263  	request := func() {
   264  		// Make a new client each request so we don't reuse
   265  		// connections.
   266  		client := &http.Client{
   267  			Transport: &http.Transport{
   268  				TLSClientConfig: s.config.TLSConfig,
   269  			},
   270  		}
   271  		_, err := client.Get(url + "/quick")
   272  		quickErr <- err
   273  	}
   274  
   275  	// Sanity check - the quick one should be quick normally.
   276  	go request()
   277  
   278  	select {
   279  	case err := <-quickErr:
   280  		c.Assert(err, jc.ErrorIsNil)
   281  	case <-time.After(coretesting.ShortWait):
   282  		c.Fatalf("timed out waiting for quick request")
   283  	}
   284  
   285  	// Stop the server.
   286  	s.worker.Kill()
   287  
   288  	// Eventually quick requests get blocked by the held listener.
   289  	var quickBlocked bool
   290  attempts:
   291  	for a := coretesting.LongAttempt.Start(); a.Next(); {
   292  		go request()
   293  		select {
   294  		case <-quickErr:
   295  		case <-time.After(coretesting.ShortWait):
   296  			quickBlocked = true
   297  			break attempts
   298  		}
   299  	}
   300  	c.Assert(quickBlocked, gc.Equals, true)
   301  
   302  	// The server doesn't die yet - it's kept alive by the slow
   303  	// request.
   304  	workertest.CheckAlive(c, s.worker)
   305  
   306  	// Let the slow request complete.  See that the server
   307  	// stops, and the 2nd request completes.
   308  	s.mux.ClientDone()
   309  
   310  	select {
   311  	case err := <-quickErr:
   312  		// It doesn't really matter what the error is.
   313  		c.Assert(err, gc.NotNil)
   314  	case <-time.After(coretesting.ShortWait):
   315  		c.Fatalf("timed out waiting for 2nd quick request")
   316  	}
   317  	workertest.CheckKilled(c, s.worker)
   318  }
   319  
   320  type WorkerControllerPortSuite struct {
   321  	workerFixture
   322  }
   323  
   324  var _ = gc.Suite(&WorkerControllerPortSuite{})
   325  
   326  func (s *WorkerControllerPortSuite) newWorker(c *gc.C) *httpserver.Worker {
   327  	worker, err := httpserver.NewWorker(s.config)
   328  	c.Assert(err, jc.ErrorIsNil)
   329  	s.AddCleanup(func(c *gc.C) {
   330  		workertest.DirtyKill(c, worker)
   331  	})
   332  	return worker
   333  }
   334  
   335  func (s *WorkerControllerPortSuite) TestDualPortListenerWithDelay(c *gc.C) {
   336  	err := s.mux.AddHandler("GET", "/quick", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   337  		w.WriteHeader(http.StatusOK)
   338  	}))
   339  	c.Assert(err, jc.ErrorIsNil)
   340  
   341  	request := func(url string) error {
   342  		client := &http.Client{
   343  			Transport: &http.Transport{
   344  				TLSClientConfig: s.config.TLSConfig,
   345  			},
   346  		}
   347  		_, err := client.Get(url + "/quick")
   348  		return err
   349  	}
   350  
   351  	// Make a worker with a controller API port.
   352  	port := testing.FindTCPPort()
   353  	controllerPort := testing.FindTCPPort()
   354  	s.config.APIPort = port
   355  	s.config.ControllerAPIPort = controllerPort
   356  	s.config.APIPortOpenDelay = 10 * time.Second
   357  
   358  	worker := s.newWorker(c)
   359  
   360  	// The worker reports its URL as the controller port.
   361  	controllerURL := worker.URL()
   362  	parsed, err := url.Parse(controllerURL)
   363  	c.Assert(err, jc.ErrorIsNil)
   364  	c.Assert(parsed.Port(), gc.Equals, fmt.Sprint(controllerPort))
   365  
   366  	reportPorts := map[string]interface{}{
   367  		"controller": fmt.Sprintf("[::]:%d", s.config.ControllerAPIPort),
   368  		"status":     "waiting for signal to open agent port",
   369  	}
   370  	report := map[string]interface{}{
   371  		"api-port":            s.config.APIPort,
   372  		"api-port-open-delay": s.config.APIPortOpenDelay,
   373  		"controller-api-port": s.config.ControllerAPIPort,
   374  		"status":              "running",
   375  		"ports":               reportPorts,
   376  	}
   377  	c.Check(worker.Report(), jc.DeepEquals, report)
   378  
   379  	// Requests on that port work.
   380  	c.Assert(request(controllerURL), jc.ErrorIsNil)
   381  
   382  	// Requests on the regular API port fail to connect.
   383  	parsed.Host = net.JoinHostPort(parsed.Hostname(), fmt.Sprint(port))
   384  	normalURL := parsed.String()
   385  	c.Assert(request(normalURL), gc.ErrorMatches, `.*: connection refused$`)
   386  
   387  	// Getting a connection from someone else doesn't unblock.
   388  	handled, err := s.hub.Publish(apiserver.ConnectTopic, apiserver.APIConnection{
   389  		AgentTag: "machine-13",
   390  		Origin:   s.agentName,
   391  	})
   392  	c.Assert(err, jc.ErrorIsNil)
   393  	select {
   394  	case <-handled:
   395  	case <-time.After(testing.LongWait):
   396  		c.Fatalf("the handler should have exited early and not be waiting")
   397  	}
   398  
   399  	// Send API details on the hub - still no luck connecting on the
   400  	// non-controller port.
   401  	_, err = s.hub.Publish(apiserver.ConnectTopic, apiserver.APIConnection{
   402  		AgentTag: s.agentName,
   403  		Origin:   s.agentName,
   404  	})
   405  	c.Assert(err, jc.ErrorIsNil)
   406  
   407  	err = s.clock.WaitAdvance(5*time.Second, coretesting.LongWait, 1)
   408  	c.Assert(err, jc.ErrorIsNil)
   409  	c.Assert(request(controllerURL), jc.ErrorIsNil)
   410  	c.Assert(request(normalURL), gc.ErrorMatches, `.*: connection refused$`)
   411  
   412  	reportPorts["status"] = "waiting prior to opening agent port"
   413  	c.Check(worker.Report(), jc.DeepEquals, report)
   414  
   415  	// After the required delay the port eventually opens.
   416  	err = s.clock.WaitAdvance(5*time.Second, coretesting.LongWait, 1)
   417  
   418  	// The reported url changes to the regular port.
   419  	for a := coretesting.LongAttempt.Start(); a.Next(); {
   420  		if worker.URL() == normalURL {
   421  			break
   422  		}
   423  	}
   424  	c.Assert(worker.URL(), gc.Equals, normalURL)
   425  
   426  	// Requests on both ports work.
   427  	c.Assert(request(controllerURL), jc.ErrorIsNil)
   428  	c.Assert(request(normalURL), jc.ErrorIsNil)
   429  
   430  	delete(reportPorts, "status")
   431  	reportPorts["agent"] = fmt.Sprintf("[::]:%d", s.config.APIPort)
   432  	c.Check(worker.Report(), jc.DeepEquals, report)
   433  }