// Copyright 2014 Canonical Ltd. // Licensed under the AGPLv3, see LICENCE file for details. package state_test import ( "fmt" "github.com/juju/errors" jc "github.com/juju/testing/checkers" gc "gopkg.in/check.v1" "github.com/juju/juju/agent" "github.com/juju/juju/constraints" "github.com/juju/juju/environs/config" "github.com/juju/juju/instance" "github.com/juju/juju/state" ) type PrecheckerSuite struct { ConnSuite prechecker mockPrechecker } var _ = gc.Suite(&PrecheckerSuite{}) type mockPrechecker struct { precheckInstanceError error precheckInstanceSeries string precheckInstanceConstraints constraints.Value precheckInstancePlacement string } func (p *mockPrechecker) PrecheckInstance(series string, cons constraints.Value, placement string) error { p.precheckInstanceSeries = series p.precheckInstanceConstraints = cons p.precheckInstancePlacement = placement return p.precheckInstanceError } func (s *PrecheckerSuite) SetUpTest(c *gc.C) { s.ConnSuite.SetUpTest(c) s.prechecker = mockPrechecker{} s.policy.GetPrechecker = func(*config.Config) (state.Prechecker, error) { return &s.prechecker, nil } } func (s *PrecheckerSuite) TestPrecheckInstance(c *gc.C) { // PrecheckInstance should be called with the specified // series and placement, and the specified constraints // merged with the environment constraints, when attempting // to create an instance. envCons := constraints.MustParse("mem=4G") placement := "abc123" template, err := s.addOneMachine(c, envCons, placement) c.Assert(err, jc.ErrorIsNil) c.Assert(s.prechecker.precheckInstanceSeries, gc.Equals, template.Series) c.Assert(s.prechecker.precheckInstancePlacement, gc.Equals, placement) validator := constraints.NewValidator() cons, err := validator.Merge(envCons, template.Constraints) c.Assert(err, jc.ErrorIsNil) c.Assert(s.prechecker.precheckInstanceConstraints, gc.DeepEquals, cons) } func (s *PrecheckerSuite) TestPrecheckErrors(c *gc.C) { // Ensure that AddOneMachine fails when PrecheckInstance returns an error. s.prechecker.precheckInstanceError = fmt.Errorf("no instance for you") _, err := s.addOneMachine(c, constraints.Value{}, "placement") c.Assert(err, gc.ErrorMatches, ".*no instance for you") // If the policy's Prechecker method fails, that will be returned first. s.policy.GetPrechecker = func(*config.Config) (state.Prechecker, error) { return nil, fmt.Errorf("no prechecker for you") } _, err = s.addOneMachine(c, constraints.Value{}, "placement") c.Assert(err, gc.ErrorMatches, ".*no prechecker for you") } func (s *PrecheckerSuite) TestPrecheckPrecheckerUnimplemented(c *gc.C) { var precheckerErr error s.policy.GetPrechecker = func(*config.Config) (state.Prechecker, error) { return nil, precheckerErr } _, err := s.addOneMachine(c, constraints.Value{}, "placement") c.Assert(err, gc.ErrorMatches, "cannot add a new machine: policy returned nil prechecker without an error") precheckerErr = errors.NotImplementedf("Prechecker") _, err = s.addOneMachine(c, constraints.Value{}, "placement") c.Assert(err, jc.ErrorIsNil) } func (s *PrecheckerSuite) TestPrecheckNoPolicy(c *gc.C) { s.policy.GetPrechecker = func(*config.Config) (state.Prechecker, error) { c.Errorf("should not have been invoked") return nil, nil } state.SetPolicy(s.State, nil) _, err := s.addOneMachine(c, constraints.Value{}, "placement") c.Assert(err, jc.ErrorIsNil) } func (s *PrecheckerSuite) addOneMachine(c *gc.C, envCons constraints.Value, placement string) (state.MachineTemplate, error) { err := s.State.SetEnvironConstraints(envCons) c.Assert(err, jc.ErrorIsNil) oneJob := []state.MachineJob{state.JobHostUnits} extraCons := constraints.MustParse("cpu-cores=4") template := state.MachineTemplate{ Series: "precise", Constraints: extraCons, Jobs: oneJob, Placement: placement, } _, err = s.State.AddOneMachine(template) return template, err } func (s *PrecheckerSuite) TestPrecheckInstanceInjectMachine(c *gc.C) { template := state.MachineTemplate{ InstanceId: instance.Id("bootstrap"), Series: "precise", Nonce: agent.BootstrapNonce, Jobs: []state.MachineJob{state.JobManageEnviron}, Placement: "anyoldthing", } _, err := s.State.AddOneMachine(template) c.Assert(err, jc.ErrorIsNil) // PrecheckInstance should not have been called, as we've // injected a machine with an existing instance. c.Assert(s.prechecker.precheckInstanceSeries, gc.Equals, "") c.Assert(s.prechecker.precheckInstancePlacement, gc.Equals, "") } func (s *PrecheckerSuite) TestPrecheckContainerNewMachine(c *gc.C) { // Attempting to add a container to a new machine should cause // PrecheckInstance to be called. template := state.MachineTemplate{ Series: "precise", Jobs: []state.MachineJob{state.JobHostUnits}, Placement: "intertubes", } _, err := s.State.AddMachineInsideNewMachine(template, template, instance.LXC) c.Assert(err, jc.ErrorIsNil) c.Assert(s.prechecker.precheckInstanceSeries, gc.Equals, template.Series) c.Assert(s.prechecker.precheckInstancePlacement, gc.Equals, template.Placement) }