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