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