github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/state/apiserver/server_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package apiserver_test
     5  
     6  import (
     7  	"crypto/tls"
     8  	"crypto/x509"
     9  	"fmt"
    10  	"io"
    11  	"net"
    12  	stdtesting "testing"
    13  	"time"
    14  
    15  	"code.google.com/p/go.net/websocket"
    16  	jc "github.com/juju/testing/checkers"
    17  	"github.com/juju/utils"
    18  	gc "launchpad.net/gocheck"
    19  
    20  	"github.com/juju/juju/cert"
    21  	jujutesting "github.com/juju/juju/juju/testing"
    22  	"github.com/juju/juju/rpc"
    23  	"github.com/juju/juju/state"
    24  	"github.com/juju/juju/state/api"
    25  	"github.com/juju/juju/state/api/params"
    26  	"github.com/juju/juju/state/apiserver"
    27  	coretesting "github.com/juju/juju/testing"
    28  )
    29  
    30  func TestAll(t *stdtesting.T) {
    31  	coretesting.MgoTestPackage(t)
    32  }
    33  
    34  var fastDialOpts = api.DialOpts{}
    35  
    36  type serverSuite struct {
    37  	jujutesting.JujuConnSuite
    38  }
    39  
    40  var _ = gc.Suite(&serverSuite{})
    41  
    42  func (s *serverSuite) TestStop(c *gc.C) {
    43  	// Start our own instance of the server so we have
    44  	// a handle on it to stop it.
    45  	srv, err := apiserver.NewServer(
    46  		s.State, "localhost:0",
    47  		[]byte(coretesting.ServerCert), []byte(coretesting.ServerKey),
    48  		"", "")
    49  	c.Assert(err, gc.IsNil)
    50  	defer srv.Stop()
    51  
    52  	stm, err := s.State.AddMachine("quantal", state.JobHostUnits)
    53  	c.Assert(err, gc.IsNil)
    54  	err = stm.SetProvisioned("foo", "fake_nonce", nil)
    55  	c.Assert(err, gc.IsNil)
    56  	password, err := utils.RandomPassword()
    57  	c.Assert(err, gc.IsNil)
    58  	err = stm.SetPassword(password)
    59  	c.Assert(err, gc.IsNil)
    60  
    61  	// Note we can't use openAs because we're not connecting to
    62  	// s.APIConn.
    63  	apiInfo := &api.Info{
    64  		Tag:      stm.Tag(),
    65  		Password: password,
    66  		Nonce:    "fake_nonce",
    67  		Addrs:    []string{srv.Addr()},
    68  		CACert:   coretesting.CACert,
    69  	}
    70  	st, err := api.Open(apiInfo, fastDialOpts)
    71  	c.Assert(err, gc.IsNil)
    72  	defer st.Close()
    73  
    74  	_, err = st.Machiner().Machine(stm.Tag())
    75  	c.Assert(err, gc.IsNil)
    76  
    77  	err = srv.Stop()
    78  	c.Assert(err, gc.IsNil)
    79  
    80  	_, err = st.Machiner().Machine(stm.Tag())
    81  	// The client has not necessarily seen the server shutdown yet,
    82  	// so there are two possible errors.
    83  	if err != rpc.ErrShutdown && err != io.ErrUnexpectedEOF {
    84  		c.Fatalf("unexpected error from request: %v", err)
    85  	}
    86  
    87  	// Check it can be stopped twice.
    88  	err = srv.Stop()
    89  	c.Assert(err, gc.IsNil)
    90  }
    91  
    92  func (s *serverSuite) TestOpenAsMachineErrors(c *gc.C) {
    93  	assertNotProvisioned := func(err error) {
    94  		c.Assert(err, gc.NotNil)
    95  		c.Assert(err, jc.Satisfies, params.IsCodeNotProvisioned)
    96  		c.Assert(err, gc.ErrorMatches, `machine \d+ is not provisioned`)
    97  	}
    98  	stm, err := s.State.AddMachine("quantal", state.JobHostUnits)
    99  	c.Assert(err, gc.IsNil)
   100  	err = stm.SetProvisioned("foo", "fake_nonce", nil)
   101  	c.Assert(err, gc.IsNil)
   102  	password, err := utils.RandomPassword()
   103  	c.Assert(err, gc.IsNil)
   104  	err = stm.SetPassword(password)
   105  	c.Assert(err, gc.IsNil)
   106  
   107  	// This does almost exactly the same as OpenAPIAsMachine but checks
   108  	// for failures instead.
   109  	_, info, err := s.APIConn.Environ.StateInfo()
   110  	info.Tag = stm.Tag()
   111  	info.Password = password
   112  	info.Nonce = "invalid-nonce"
   113  	st, err := api.Open(info, fastDialOpts)
   114  	assertNotProvisioned(err)
   115  	c.Assert(st, gc.IsNil)
   116  
   117  	// Try with empty nonce as well.
   118  	info.Nonce = ""
   119  	st, err = api.Open(info, fastDialOpts)
   120  	assertNotProvisioned(err)
   121  	c.Assert(st, gc.IsNil)
   122  
   123  	// Finally, with the correct one succeeds.
   124  	info.Nonce = "fake_nonce"
   125  	st, err = api.Open(info, fastDialOpts)
   126  	c.Assert(err, gc.IsNil)
   127  	c.Assert(st, gc.NotNil)
   128  	st.Close()
   129  
   130  	// Now add another machine, intentionally unprovisioned.
   131  	stm1, err := s.State.AddMachine("quantal", state.JobHostUnits)
   132  	c.Assert(err, gc.IsNil)
   133  	err = stm1.SetPassword(password)
   134  	c.Assert(err, gc.IsNil)
   135  
   136  	// Try connecting, it will fail.
   137  	info.Tag = stm1.Tag()
   138  	info.Nonce = ""
   139  	st, err = api.Open(info, fastDialOpts)
   140  	assertNotProvisioned(err)
   141  	c.Assert(st, gc.IsNil)
   142  }
   143  
   144  func (s *serverSuite) TestMachineLoginStartsPinger(c *gc.C) {
   145  	// This is the same steps as OpenAPIAsNewMachine but we need to assert
   146  	// the agent is not alive before we actually open the API.
   147  	// Create a new machine to verify "agent alive" behavior.
   148  	machine, err := s.State.AddMachine("quantal", state.JobHostUnits)
   149  	c.Assert(err, gc.IsNil)
   150  	err = machine.SetProvisioned("foo", "fake_nonce", nil)
   151  	c.Assert(err, gc.IsNil)
   152  	password, err := utils.RandomPassword()
   153  	c.Assert(err, gc.IsNil)
   154  	err = machine.SetPassword(password)
   155  	c.Assert(err, gc.IsNil)
   156  
   157  	// Not alive yet.
   158  	s.assertAlive(c, machine, false)
   159  
   160  	// Login as the machine agent of the created machine.
   161  	st := s.OpenAPIAsMachine(c, machine.Tag(), password, "fake_nonce")
   162  
   163  	// Make sure the pinger has started.
   164  	s.assertAlive(c, machine, true)
   165  
   166  	// Now make sure it stops when connection is closed.
   167  	c.Assert(st.Close(), gc.IsNil)
   168  
   169  	// Sync, then wait for a bit to make sure the state is updated.
   170  	s.State.StartSync()
   171  	<-time.After(coretesting.ShortWait)
   172  	s.State.StartSync()
   173  
   174  	s.assertAlive(c, machine, false)
   175  }
   176  
   177  func (s *serverSuite) TestUnitLoginStartsPinger(c *gc.C) {
   178  	// Create a new service and unit to verify "agent alive" behavior.
   179  	service := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   180  	unit, err := service.AddUnit()
   181  	c.Assert(err, gc.IsNil)
   182  	password, err := utils.RandomPassword()
   183  	c.Assert(err, gc.IsNil)
   184  	err = unit.SetPassword(password)
   185  	c.Assert(err, gc.IsNil)
   186  
   187  	// Not alive yet.
   188  	s.assertAlive(c, unit, false)
   189  
   190  	// Login as the unit agent of the created unit.
   191  	st := s.OpenAPIAs(c, unit.Tag(), password)
   192  
   193  	// Make sure the pinger has started.
   194  	s.assertAlive(c, unit, true)
   195  
   196  	// Now make sure it stops when connection is closed.
   197  	c.Assert(st.Close(), gc.IsNil)
   198  
   199  	// Sync, then wait for a bit to make sure the state is updated.
   200  	s.State.StartSync()
   201  	<-time.After(coretesting.ShortWait)
   202  	s.State.StartSync()
   203  
   204  	s.assertAlive(c, unit, false)
   205  }
   206  
   207  type agentAliver interface {
   208  	AgentAlive() (bool, error)
   209  }
   210  
   211  func (s *serverSuite) assertAlive(c *gc.C, entity agentAliver, isAlive bool) {
   212  	s.State.StartSync()
   213  	alive, err := entity.AgentAlive()
   214  	c.Assert(err, gc.IsNil)
   215  	c.Assert(alive, gc.Equals, isAlive)
   216  }
   217  
   218  func dialWebsocket(c *gc.C, addr, path string) (*websocket.Conn, error) {
   219  	origin := "http://localhost/"
   220  	url := fmt.Sprintf("wss://%s%s", addr, path)
   221  	config, err := websocket.NewConfig(url, origin)
   222  	c.Assert(err, gc.IsNil)
   223  	pool := x509.NewCertPool()
   224  	xcert, err := cert.ParseCert(coretesting.CACert)
   225  	c.Assert(err, gc.IsNil)
   226  	pool.AddCert(xcert)
   227  	config.TlsConfig = &tls.Config{RootCAs: pool}
   228  	return websocket.DialConfig(config)
   229  }
   230  
   231  func (s *serverSuite) TestNonCompatiblePathsAre404(c *gc.C) {
   232  	// we expose the API at '/' for compatibility, and at '/ENVUUID/api'
   233  	// for the correct location, but other Paths should fail.
   234  	srv, err := apiserver.NewServer(
   235  		s.State, "localhost:0",
   236  		[]byte(coretesting.ServerCert), []byte(coretesting.ServerKey),
   237  		"", "")
   238  	c.Assert(err, gc.IsNil)
   239  	defer srv.Stop()
   240  	// We have to use 'localhost' because that is what the TLS cert says.
   241  	// So find just the Port for the server
   242  	_, portString, err := net.SplitHostPort(srv.Addr())
   243  	c.Assert(err, gc.IsNil)
   244  	addr := "localhost:" + portString
   245  	// '/' should be fine
   246  	conn, err := dialWebsocket(c, addr, "/")
   247  	c.Assert(err, gc.IsNil)
   248  	conn.Close()
   249  	// '/environment/ENVIRONUUID/api' should be fine
   250  	conn, err = dialWebsocket(c, addr, "/environment/dead-beef-123456/api")
   251  	c.Assert(err, gc.IsNil)
   252  	conn.Close()
   253  
   254  	// '/randompath' is not ok
   255  	conn, err = dialWebsocket(c, addr, "/randompath")
   256  	// Unfortunately go.net/websocket just returns Bad Status, it doesn't
   257  	// give us any information (whether this was a 404 Not Found, Internal
   258  	// Server Error, 200 OK, etc.)
   259  	c.Assert(err, gc.ErrorMatches, `websocket.Dial wss://localhost:\d+/randompath: bad status`)
   260  	c.Assert(conn, gc.IsNil)
   261  }