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