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