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