github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/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  	"github.com/juju/errors"
    12  	jc "github.com/juju/testing/checkers"
    13  	gc "launchpad.net/gocheck"
    14  
    15  	"github.com/juju/juju/constraints"
    16  	"github.com/juju/juju/environs"
    17  	"github.com/juju/juju/environs/config"
    18  	"github.com/juju/juju/instance"
    19  	"github.com/juju/juju/juju/testing"
    20  	"github.com/juju/juju/state"
    21  	"github.com/juju/juju/state/api"
    22  	"github.com/juju/juju/state/api/params"
    23  	coretesting "github.com/juju/juju/testing"
    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.IsNotFound)
    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  			Agent: api.AgentStatus{
   154  				Status: "started",
   155  				Data:   params.StatusData{},
   156  			},
   157  			AgentState:     "down",
   158  			AgentStateInfo: "(started)",
   159  			Series:         "quantal",
   160  			Containers:     map[string]api.MachineStatus{},
   161  			Jobs:           []params.MachineJob{params.JobManageEnviron},
   162  			HasVote:        false,
   163  			WantsVote:      true,
   164  		},
   165  		"1": {
   166  			Id:         "1",
   167  			InstanceId: instance.Id("i-machine-1"),
   168  			Agent: api.AgentStatus{
   169  				Status: "started",
   170  				Data:   params.StatusData{},
   171  			},
   172  			AgentState:     "down",
   173  			AgentStateInfo: "(started)",
   174  			Series:         "quantal",
   175  			Containers:     map[string]api.MachineStatus{},
   176  			Jobs:           []params.MachineJob{params.JobHostUnits},
   177  			HasVote:        false,
   178  			WantsVote:      false,
   179  		},
   180  		"2": {
   181  			Id:         "2",
   182  			InstanceId: instance.Id("i-machine-2"),
   183  			Agent: api.AgentStatus{
   184  				Status: "started",
   185  				Data:   params.StatusData{},
   186  			},
   187  			AgentState:     "down",
   188  			AgentStateInfo: "(started)",
   189  			Series:         "quantal",
   190  			Containers:     map[string]api.MachineStatus{},
   191  			Jobs:           []params.MachineJob{params.JobHostUnits},
   192  			HasVote:        false,
   193  			WantsVote:      false,
   194  		},
   195  	},
   196  	Services: map[string]api.ServiceStatus{
   197  		"logging": api.ServiceStatus{
   198  			Charm: "local:quantal/logging-1",
   199  			Relations: map[string][]string{
   200  				"logging-directory": []string{"wordpress"},
   201  			},
   202  			SubordinateTo: []string{"wordpress"},
   203  		},
   204  		"mysql": api.ServiceStatus{
   205  			Charm:         "local:quantal/mysql-1",
   206  			Relations:     map[string][]string{},
   207  			SubordinateTo: []string{},
   208  			Units:         map[string]api.UnitStatus{},
   209  		},
   210  		"wordpress": api.ServiceStatus{
   211  			Charm: "local:quantal/wordpress-3",
   212  			Relations: map[string][]string{
   213  				"logging-dir": []string{"logging"},
   214  			},
   215  			SubordinateTo: []string{},
   216  			Units: map[string]api.UnitStatus{
   217  				"wordpress/0": api.UnitStatus{
   218  					Agent: api.AgentStatus{
   219  						Status: "error",
   220  						Info:   "blam",
   221  						Data:   params.StatusData{"relation-id": "0"},
   222  					},
   223  					AgentState:     "down",
   224  					AgentStateInfo: "(error: blam)",
   225  					Machine:        "1",
   226  					Subordinates: map[string]api.UnitStatus{
   227  						"logging/0": api.UnitStatus{
   228  							Agent: api.AgentStatus{
   229  								Status: "pending",
   230  								Data:   params.StatusData{},
   231  							},
   232  							AgentState: "pending",
   233  						},
   234  					},
   235  				},
   236  				"wordpress/1": api.UnitStatus{
   237  					Agent: api.AgentStatus{
   238  						Status: "pending",
   239  						Data:   params.StatusData{},
   240  					},
   241  					AgentState: "pending",
   242  					Machine:    "2",
   243  					Subordinates: map[string]api.UnitStatus{
   244  						"logging/1": api.UnitStatus{
   245  							Agent: api.AgentStatus{
   246  								Status: "pending",
   247  								Data:   params.StatusData{},
   248  							},
   249  							AgentState: "pending",
   250  						},
   251  					},
   252  				},
   253  			},
   254  		},
   255  	},
   256  	Relations: []api.RelationStatus{
   257  		api.RelationStatus{
   258  			Id:  0,
   259  			Key: "logging:logging-directory wordpress:logging-dir",
   260  			Endpoints: []api.EndpointStatus{
   261  				api.EndpointStatus{
   262  					ServiceName: "logging",
   263  					Name:        "logging-directory",
   264  					Role:        "requirer",
   265  					Subordinate: true,
   266  				},
   267  				api.EndpointStatus{
   268  					ServiceName: "wordpress",
   269  					Name:        "logging-dir",
   270  					Role:        "provider",
   271  					Subordinate: false,
   272  				},
   273  			},
   274  			Interface: "logging",
   275  			Scope:     "container",
   276  		},
   277  	},
   278  	Networks: map[string]api.NetworkStatus{},
   279  }
   280  
   281  // setUpScenario makes an environment scenario suitable for
   282  // testing most kinds of access scenario. It returns
   283  // a list of all the entities in the scenario.
   284  //
   285  // When the scenario is initialized, we have:
   286  // user-admin
   287  // user-other
   288  // machine-0
   289  //  instance-id="i-machine-0"
   290  //  nonce="fake_nonce"
   291  //  jobs=manage-environ
   292  //  status=started, info=""
   293  // machine-1
   294  //  instance-id="i-machine-1"
   295  //  nonce="fake_nonce"
   296  //  jobs=host-units
   297  //  status=started, info=""
   298  //  constraints=mem=1G
   299  // machine-2
   300  //  instance-id="i-machine-2"
   301  //  nonce="fake_nonce"
   302  //  jobs=host-units
   303  //  status=started, info=""
   304  // service-wordpress
   305  // service-logging
   306  // unit-wordpress-0
   307  //  deployer-name=machine-1
   308  //  status=down with error and status data attached
   309  // unit-logging-0
   310  //  deployer-name=unit-wordpress-0
   311  // unit-wordpress-1
   312  //     deployer-name=machine-2
   313  // unit-logging-1
   314  //  deployer-name=unit-wordpress-1
   315  //
   316  // The passwords for all returned entities are
   317  // set to the entity name with a " password" suffix.
   318  //
   319  // Note that there is nothing special about machine-0
   320  // here - it's the environment manager in this scenario
   321  // just because machine 0 has traditionally been the
   322  // environment manager (bootstrap machine), so is
   323  // hopefully easier to remember as such.
   324  func (s *baseSuite) setUpScenario(c *gc.C) (entities []string) {
   325  	add := func(e state.Entity) {
   326  		entities = append(entities, e.Tag())
   327  	}
   328  	u, err := s.State.User(state.AdminUser)
   329  	c.Assert(err, gc.IsNil)
   330  	setDefaultPassword(c, u)
   331  	add(u)
   332  
   333  	u = s.AddUser(c, "other")
   334  	setDefaultPassword(c, u)
   335  	add(u)
   336  
   337  	m, err := s.State.AddMachine("quantal", state.JobManageEnviron)
   338  	c.Assert(err, gc.IsNil)
   339  	c.Assert(m.Tag(), gc.Equals, "machine-0")
   340  	err = m.SetProvisioned(instance.Id("i-"+m.Tag()), "fake_nonce", nil)
   341  	c.Assert(err, gc.IsNil)
   342  	setDefaultPassword(c, m)
   343  	setDefaultStatus(c, m)
   344  	add(m)
   345  	s.AddTestingService(c, "mysql", s.AddTestingCharm(c, "mysql"))
   346  	wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
   347  	s.AddTestingService(c, "logging", s.AddTestingCharm(c, "logging"))
   348  	eps, err := s.State.InferEndpoints([]string{"logging", "wordpress"})
   349  	c.Assert(err, gc.IsNil)
   350  	rel, err := s.State.AddRelation(eps...)
   351  	c.Assert(err, gc.IsNil)
   352  
   353  	for i := 0; i < 2; i++ {
   354  		wu, err := wordpress.AddUnit()
   355  		c.Assert(err, gc.IsNil)
   356  		c.Assert(wu.Tag(), gc.Equals, fmt.Sprintf("unit-wordpress-%d", i))
   357  		setDefaultPassword(c, wu)
   358  		add(wu)
   359  
   360  		m, err := s.State.AddMachine("quantal", state.JobHostUnits)
   361  		c.Assert(err, gc.IsNil)
   362  		c.Assert(m.Tag(), gc.Equals, fmt.Sprintf("machine-%d", i+1))
   363  		if i == 1 {
   364  			err = m.SetConstraints(constraints.MustParse("mem=1G"))
   365  			c.Assert(err, gc.IsNil)
   366  		}
   367  		err = m.SetProvisioned(instance.Id("i-"+m.Tag()), "fake_nonce", nil)
   368  		c.Assert(err, gc.IsNil)
   369  		setDefaultPassword(c, m)
   370  		setDefaultStatus(c, m)
   371  		add(m)
   372  
   373  		err = wu.AssignToMachine(m)
   374  		c.Assert(err, gc.IsNil)
   375  
   376  		deployer, ok := wu.DeployerTag()
   377  		c.Assert(ok, gc.Equals, true)
   378  		c.Assert(deployer, gc.Equals, fmt.Sprintf("machine-%d", i+1))
   379  
   380  		wru, err := rel.Unit(wu)
   381  		c.Assert(err, gc.IsNil)
   382  
   383  		// Put wordpress/0 in error state (with extra status data set)
   384  		if i == 0 {
   385  			sd := params.StatusData{
   386  				"relation-id": "0",
   387  				// these this should get filtered out
   388  				// (not in StatusData whitelist)
   389  				"remote-unit": "logging/0",
   390  				"foo":         "bar",
   391  			}
   392  			wu.SetStatus(params.StatusError, "blam", sd)
   393  		}
   394  
   395  		// Create the subordinate unit as a side-effect of entering
   396  		// scope in the principal's relation-unit.
   397  		err = wru.EnterScope(nil)
   398  		c.Assert(err, gc.IsNil)
   399  
   400  		lu, err := s.State.Unit(fmt.Sprintf("logging/%d", i))
   401  		c.Assert(err, gc.IsNil)
   402  		c.Assert(lu.IsPrincipal(), gc.Equals, false)
   403  		deployer, ok = lu.DeployerTag()
   404  		c.Assert(ok, gc.Equals, true)
   405  		c.Assert(deployer, gc.Equals, fmt.Sprintf("unit-wordpress-%d", i))
   406  		setDefaultPassword(c, lu)
   407  		add(lu)
   408  	}
   409  	return
   410  }