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