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