launchpad.net/~rogpeppe/juju-core/500-errgo-fix@v0.0.0-20140213181702-000000002356/state/apiserver/client/api_test.go (about)

     1  // Copyright 2012, 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package client_test
     5  
     6  import (
     7  	"fmt"
     8  	gc "launchpad.net/gocheck"
     9  	stdtesting "testing"
    10  	"time"
    11  
    12  	"launchpad.net/juju-core/constraints"
    13  	"launchpad.net/juju-core/environs/config"
    14  	"launchpad.net/juju-core/errors"
    15  	"launchpad.net/juju-core/instance"
    16  	"launchpad.net/juju-core/juju/testing"
    17  	"launchpad.net/juju-core/state"
    18  	"launchpad.net/juju-core/state/api"
    19  	"launchpad.net/juju-core/state/api/params"
    20  	coretesting "launchpad.net/juju-core/testing"
    21  	jc "launchpad.net/juju-core/testing/checkers"
    22  )
    23  
    24  func TestAll(t *stdtesting.T) {
    25  	coretesting.MgoTestPackage(t)
    26  }
    27  
    28  type baseSuite struct {
    29  	testing.JujuConnSuite
    30  }
    31  
    32  var _ = gc.Suite(&baseSuite{})
    33  
    34  func chanReadEmpty(c *gc.C, ch <-chan struct{}, what string) bool {
    35  	select {
    36  	case _, ok := <-ch:
    37  		return ok
    38  	case <-time.After(10 * time.Second):
    39  		c.Fatalf("timed out reading from %s", what)
    40  	}
    41  	panic("unreachable")
    42  }
    43  
    44  func chanReadStrings(c *gc.C, ch <-chan []string, what string) ([]string, bool) {
    45  	select {
    46  	case changes, ok := <-ch:
    47  		return changes, ok
    48  	case <-time.After(10 * time.Second):
    49  		c.Fatalf("timed out reading from %s", what)
    50  	}
    51  	panic("unreachable")
    52  }
    53  
    54  func chanReadConfig(c *gc.C, ch <-chan *config.Config, what string) (*config.Config, bool) {
    55  	select {
    56  	case envConfig, ok := <-ch:
    57  		return envConfig, ok
    58  	case <-time.After(10 * time.Second):
    59  		c.Fatalf("timed out reading from %s", what)
    60  	}
    61  	panic("unreachable")
    62  }
    63  
    64  func removeServiceAndUnits(c *gc.C, service *state.Service) {
    65  	// Destroy all units for the service.
    66  	units, err := service.AllUnits()
    67  	c.Assert(err, gc.IsNil)
    68  	for _, unit := range units {
    69  		err = unit.EnsureDead()
    70  		c.Assert(err, gc.IsNil)
    71  		err = unit.Remove()
    72  		c.Assert(err, gc.IsNil)
    73  	}
    74  	err = service.Destroy()
    75  	c.Assert(err, gc.IsNil)
    76  
    77  	err = service.Refresh()
    78  	c.Assert(err, jc.Satisfies, errors.IsNotFoundError)
    79  }
    80  
    81  // apiAuthenticator represents a simple authenticator object with only the
    82  // SetPassword and Tag methods.  This will fit types from both the state
    83  // and api packages, as those in the api package do not have PasswordValid().
    84  type apiAuthenticator interface {
    85  	state.Entity
    86  	SetPassword(string) error
    87  }
    88  
    89  func setDefaultPassword(c *gc.C, e apiAuthenticator) {
    90  	err := e.SetPassword(defaultPassword(e))
    91  	c.Assert(err, gc.IsNil)
    92  }
    93  
    94  func defaultPassword(e apiAuthenticator) string {
    95  	return e.Tag() + " password-1234567890"
    96  }
    97  
    98  type setStatuser interface {
    99  	SetStatus(status params.Status, info string, data params.StatusData) error
   100  }
   101  
   102  func setDefaultStatus(c *gc.C, entity setStatuser) {
   103  	err := entity.SetStatus(params.StatusStarted, "", nil)
   104  	c.Assert(err, gc.IsNil)
   105  }
   106  
   107  func (s *baseSuite) tryOpenState(c *gc.C, e apiAuthenticator, password string) error {
   108  	stateInfo := s.StateInfo(c)
   109  	stateInfo.Tag = e.Tag()
   110  	stateInfo.Password = password
   111  	st, err := state.Open(stateInfo, state.DialOpts{
   112  		Timeout: 25 * time.Millisecond,
   113  	})
   114  	if err == nil {
   115  		st.Close()
   116  	}
   117  	return err
   118  }
   119  
   120  // openAs connects to the API state as the given entity
   121  // with the default password for that entity.
   122  func (s *baseSuite) openAs(c *gc.C, tag string) *api.State {
   123  	_, info, err := s.APIConn.Environ.StateInfo()
   124  	c.Assert(err, gc.IsNil)
   125  	info.Tag = tag
   126  	// Must match defaultPassword()
   127  	info.Password = fmt.Sprintf("%s password-1234567890", tag)
   128  	// Set this always, so that the login attempts as a machine will
   129  	// not fail with ErrNotProvisioned; it's not used otherwise.
   130  	info.Nonce = "fake_nonce"
   131  	c.Logf("opening state; entity %q; password %q", info.Tag, info.Password)
   132  	st, err := api.Open(info, api.DialOpts{})
   133  	c.Assert(err, gc.IsNil)
   134  	c.Assert(st, gc.NotNil)
   135  	return st
   136  }
   137  
   138  // scenarioStatus describes the expected state
   139  // of the juju environment set up by setUpScenario.
   140  //
   141  // NOTE: AgentState: "down", AgentStateInfo: "(started)" here is due
   142  // to the scenario not calling SetAgentAlive on the respective entities,
   143  // but this behavior is already tested in cmd/juju/status_test.go and
   144  // also tested live and it works.
   145  var scenarioStatus = &api.Status{
   146  	EnvironmentName: "dummyenv",
   147  	Machines: map[string]api.MachineStatus{
   148  		"0": {
   149  			Id:             "0",
   150  			InstanceId:     instance.Id("i-machine-0"),
   151  			AgentState:     "down",
   152  			AgentStateInfo: "(started)",
   153  			Series:         "quantal",
   154  			Containers:     map[string]api.MachineStatus{},
   155  		},
   156  		"1": {
   157  			Id:             "1",
   158  			InstanceId:     instance.Id("i-machine-1"),
   159  			AgentState:     "down",
   160  			AgentStateInfo: "(started)",
   161  			Series:         "quantal",
   162  			Containers:     map[string]api.MachineStatus{},
   163  		},
   164  		"2": {
   165  			Id:             "2",
   166  			InstanceId:     instance.Id("i-machine-2"),
   167  			AgentState:     "down",
   168  			AgentStateInfo: "(started)",
   169  			Series:         "quantal",
   170  			Containers:     map[string]api.MachineStatus{},
   171  		},
   172  	},
   173  	Services: map[string]api.ServiceStatus{
   174  		"logging": api.ServiceStatus{
   175  			Charm: "local:quantal/logging-1",
   176  			Relations: map[string][]string{
   177  				"logging-directory": []string{"wordpress"},
   178  			},
   179  			SubordinateTo: []string{"wordpress"},
   180  		},
   181  		"mysql": api.ServiceStatus{
   182  			Charm:         "local:quantal/mysql-1",
   183  			Relations:     map[string][]string{},
   184  			SubordinateTo: []string{},
   185  			Units:         map[string]api.UnitStatus{},
   186  		},
   187  		"wordpress": api.ServiceStatus{
   188  			Charm: "local:quantal/wordpress-3",
   189  			Relations: map[string][]string{
   190  				"logging-dir": []string{"logging"},
   191  			},
   192  			SubordinateTo: []string{},
   193  			Units: map[string]api.UnitStatus{
   194  				"wordpress/0": api.UnitStatus{
   195  					AgentState: "pending",
   196  					Machine:    "1",
   197  					Subordinates: map[string]api.UnitStatus{
   198  						"logging/0": api.UnitStatus{
   199  							AgentState: "pending",
   200  						},
   201  					},
   202  				},
   203  				"wordpress/1": api.UnitStatus{
   204  					AgentState: "pending",
   205  					Machine:    "2",
   206  					Subordinates: map[string]api.UnitStatus{
   207  						"logging/1": api.UnitStatus{
   208  							AgentState: "pending",
   209  						},
   210  					},
   211  				},
   212  			},
   213  		},
   214  	},
   215  }
   216  
   217  // setUpScenario makes an environment scenario suitable for
   218  // testing most kinds of access scenario. It returns
   219  // a list of all the entities in the scenario.
   220  //
   221  // When the scenario is initialized, we have:
   222  // user-admin
   223  // user-other
   224  // machine-0
   225  //  instance-id="i-machine-0"
   226  //  nonce="fake_nonce"
   227  //  jobs=manage-environ
   228  //  status=started, info=""
   229  // machine-1
   230  //  instance-id="i-machine-1"
   231  //  nonce="fake_nonce"
   232  //  jobs=host-units
   233  //  status=started, info=""
   234  //  constraints=mem=1G
   235  // machine-2
   236  //  instance-id="i-machine-2"
   237  //  nonce="fake_nonce"
   238  //  jobs=host-units
   239  //  status=started, info=""
   240  // service-wordpress
   241  // service-logging
   242  // unit-wordpress-0
   243  //     deployer-name=machine-1
   244  // unit-logging-0
   245  //  deployer-name=unit-wordpress-0
   246  // unit-wordpress-1
   247  //     deployer-name=machine-2
   248  // unit-logging-1
   249  //  deployer-name=unit-wordpress-1
   250  //
   251  // The passwords for all returned entities are
   252  // set to the entity name with a " password" suffix.
   253  //
   254  // Note that there is nothing special about machine-0
   255  // here - it's the environment manager in this scenario
   256  // just because machine 0 has traditionally been the
   257  // environment manager (bootstrap machine), so is
   258  // hopefully easier to remember as such.
   259  func (s *baseSuite) setUpScenario(c *gc.C) (entities []string) {
   260  	add := func(e state.Entity) {
   261  		entities = append(entities, e.Tag())
   262  	}
   263  	u, err := s.State.User("admin")
   264  	c.Assert(err, gc.IsNil)
   265  	setDefaultPassword(c, u)
   266  	add(u)
   267  
   268  	u, err = s.State.AddUser("other", "")
   269  	c.Assert(err, gc.IsNil)
   270  	setDefaultPassword(c, u)
   271  	add(u)
   272  
   273  	m, err := s.State.AddMachine("quantal", state.JobManageEnviron)
   274  	c.Assert(err, gc.IsNil)
   275  	c.Assert(m.Tag(), gc.Equals, "machine-0")
   276  	err = m.SetProvisioned(instance.Id("i-"+m.Tag()), "fake_nonce", nil)
   277  	c.Assert(err, gc.IsNil)
   278  	setDefaultPassword(c, m)
   279  	setDefaultStatus(c, m)
   280  	add(m)
   281  	s.AddTestingService(c, "mysql", s.AddTestingCharm(c, "mysql"))
   282  	wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   283  	s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging"))
   284  	eps, err := s.State.InferEndpoints([]string{"logging", "wordpress"})
   285  	c.Assert(err, gc.IsNil)
   286  	rel, err := s.State.AddRelation(eps...)
   287  	c.Assert(err, gc.IsNil)
   288  
   289  	for i := 0; i < 2; i++ {
   290  		wu, err := wordpress.AddUnit()
   291  		c.Assert(err, gc.IsNil)
   292  		c.Assert(wu.Tag(), gc.Equals, fmt.Sprintf("unit-wordpress-%d", i))
   293  		setDefaultPassword(c, wu)
   294  		add(wu)
   295  
   296  		m, err := s.State.AddMachine("quantal", state.JobHostUnits)
   297  		c.Assert(err, gc.IsNil)
   298  		c.Assert(m.Tag(), gc.Equals, fmt.Sprintf("machine-%d", i+1))
   299  		if i == 1 {
   300  			err = m.SetConstraints(constraints.MustParse("mem=1G"))
   301  			c.Assert(err, gc.IsNil)
   302  		}
   303  		err = m.SetProvisioned(instance.Id("i-"+m.Tag()), "fake_nonce", nil)
   304  		c.Assert(err, gc.IsNil)
   305  		setDefaultPassword(c, m)
   306  		setDefaultStatus(c, m)
   307  		add(m)
   308  
   309  		err = wu.AssignToMachine(m)
   310  		c.Assert(err, gc.IsNil)
   311  
   312  		deployer, ok := wu.DeployerTag()
   313  		c.Assert(ok, gc.Equals, true)
   314  		c.Assert(deployer, gc.Equals, fmt.Sprintf("machine-%d", i+1))
   315  
   316  		wru, err := rel.Unit(wu)
   317  		c.Assert(err, gc.IsNil)
   318  
   319  		// Create the subordinate unit as a side-effect of entering
   320  		// scope in the principal's relation-unit.
   321  		err = wru.EnterScope(nil)
   322  		c.Assert(err, gc.IsNil)
   323  
   324  		lu, err := s.State.Unit(fmt.Sprintf("logging/%d", i))
   325  		c.Assert(err, gc.IsNil)
   326  		c.Assert(lu.IsPrincipal(), gc.Equals, false)
   327  		deployer, ok = lu.DeployerTag()
   328  		c.Assert(ok, gc.Equals, true)
   329  		c.Assert(deployer, gc.Equals, fmt.Sprintf("unit-wordpress-%d", i))
   330  		setDefaultPassword(c, lu)
   331  		add(lu)
   332  	}
   333  	return
   334  }