github.com/juju/juju@v0.0.0-20240327075706-a90865de2538/api/controller/instancepoller/machine_test.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package instancepoller_test
     5  
     6  import (
     7  	"reflect"
     8  	"time"
     9  
    10  	"github.com/juju/names/v5"
    11  	jc "github.com/juju/testing/checkers"
    12  	gc "gopkg.in/check.v1"
    13  
    14  	apitesting "github.com/juju/juju/api/base/testing"
    15  	"github.com/juju/juju/api/controller/instancepoller"
    16  	apiservertesting "github.com/juju/juju/apiserver/testing"
    17  	"github.com/juju/juju/core/instance"
    18  	"github.com/juju/juju/core/life"
    19  	"github.com/juju/juju/core/network"
    20  	"github.com/juju/juju/core/status"
    21  	"github.com/juju/juju/rpc/params"
    22  	coretesting "github.com/juju/juju/testing"
    23  )
    24  
    25  type MachineSuite struct {
    26  	coretesting.BaseSuite
    27  
    28  	tag names.MachineTag
    29  }
    30  
    31  var _ = gc.Suite(&MachineSuite{})
    32  
    33  func (s *MachineSuite) SetUpTest(c *gc.C) {
    34  	s.BaseSuite.SetUpTest(c)
    35  	s.tag = names.NewMachineTag("42")
    36  }
    37  
    38  func (s *MachineSuite) TestNonFacadeMethods(c *gc.C) {
    39  	nopCaller := apitesting.APICallerFunc(
    40  		func(_ string, _ int, _, _ string, _, _ interface{}) error {
    41  			c.Fatalf("facade call was not expected")
    42  			return nil
    43  		},
    44  	)
    45  	machine := instancepoller.NewMachine(nopCaller, s.tag, life.Dying)
    46  
    47  	c.Assert(machine.Id(), gc.Equals, "42")
    48  	c.Assert(machine.Tag(), jc.DeepEquals, s.tag)
    49  	c.Assert(machine.String(), gc.Equals, "42")
    50  	c.Assert(machine.Life(), gc.Equals, life.Dying)
    51  }
    52  
    53  // methodWrapper wraps a Machine method call and returns the error,
    54  // ignoring the result (if any).
    55  type methodWrapper func(*instancepoller.Machine) error
    56  
    57  // machineErrorTests contains all the necessary information to test
    58  // how each Machine method handles client- and server-side API errors,
    59  // as well as the case when the server-side API returns more results
    60  // than expected.
    61  var machineErrorTests = []struct {
    62  	method     string // only for logging
    63  	wrapper    methodWrapper
    64  	resultsRef interface{} // an instance of the server-side method's result type
    65  }{{
    66  	method:     "Refresh",
    67  	wrapper:    (*instancepoller.Machine).Refresh,
    68  	resultsRef: params.LifeResults{},
    69  }, {
    70  	method: "IsManual",
    71  	wrapper: func(m *instancepoller.Machine) error {
    72  		_, err := m.IsManual()
    73  		return err
    74  	},
    75  	resultsRef: params.BoolResults{},
    76  }, {
    77  	method: "InstanceId",
    78  	wrapper: func(m *instancepoller.Machine) error {
    79  		_, err := m.InstanceId()
    80  		return err
    81  	},
    82  	resultsRef: params.StringResults{},
    83  }, {
    84  	method: "Status",
    85  	wrapper: func(m *instancepoller.Machine) error {
    86  		_, err := m.Status()
    87  		return err
    88  	},
    89  	resultsRef: params.StatusResults{},
    90  }, {
    91  	method: "InstanceStatus",
    92  	wrapper: func(m *instancepoller.Machine) error {
    93  		_, err := m.InstanceStatus()
    94  		return err
    95  	},
    96  	resultsRef: params.StatusResults{},
    97  }, {
    98  	method: "SetInstanceStatus",
    99  	wrapper: func(m *instancepoller.Machine) error {
   100  		return m.SetInstanceStatus("", "", nil)
   101  	},
   102  	resultsRef: params.ErrorResults{},
   103  }, {
   104  	method: "SetProviderNetworkConfig",
   105  	wrapper: func(m *instancepoller.Machine) error {
   106  		_, _, err := m.SetProviderNetworkConfig(nil)
   107  		return err
   108  	},
   109  	resultsRef: params.SetProviderNetworkConfigResults{},
   110  }}
   111  
   112  func (s *MachineSuite) TestClientError(c *gc.C) {
   113  	for i, test := range machineErrorTests {
   114  		c.Logf("test #%d: %s", i, test.method)
   115  		s.CheckClientError(c, test.wrapper)
   116  	}
   117  }
   118  
   119  func (s *MachineSuite) TestServerError(c *gc.C) {
   120  	err := apiservertesting.ServerError("server error!")
   121  	expected := err.Error()
   122  	for i, test := range machineErrorTests {
   123  		c.Logf("test #%d: %s", i, test.method)
   124  		results := MakeResultsWithErrors(test.resultsRef, err, 1)
   125  		s.CheckServerError(c, test.wrapper, expected, results)
   126  	}
   127  }
   128  
   129  func (s *MachineSuite) TestTooManyResultsServerError(c *gc.C) {
   130  	err := apiservertesting.ServerError("some error")
   131  	expected := "expected 1 result, got 2"
   132  	for i, test := range machineErrorTests {
   133  		c.Logf("test #%d: %s", i, test.method)
   134  		results := MakeResultsWithErrors(test.resultsRef, err, 2)
   135  		s.CheckServerError(c, test.wrapper, expected, results)
   136  	}
   137  }
   138  
   139  func (s *MachineSuite) TestRefreshSuccess(c *gc.C) {
   140  	results := params.LifeResults{
   141  		Results: []params.LifeResult{{Life: life.Dying}},
   142  	}
   143  	apiCaller := successAPICaller(c, "Life", entitiesArgs, results)
   144  	machine := instancepoller.NewMachine(apiCaller, s.tag, life.Alive)
   145  	c.Check(machine.Refresh(), jc.ErrorIsNil)
   146  	c.Check(machine.Life(), gc.Equals, life.Dying)
   147  	c.Check(apiCaller.CallCount, gc.Equals, 1)
   148  }
   149  
   150  func (s *MachineSuite) TestStatusSuccess(c *gc.C) {
   151  	now := time.Now()
   152  	expectStatus := params.StatusResult{
   153  		Status: "foo",
   154  		Info:   "bar",
   155  		Data: map[string]interface{}{
   156  			"int":    42,
   157  			"bool":   true,
   158  			"float":  3.14,
   159  			"slice":  []string{"a", "b"},
   160  			"map":    map[int]string{5: "five"},
   161  			"string": "argh",
   162  		},
   163  		Since: &now,
   164  	}
   165  	results := params.StatusResults{Results: []params.StatusResult{expectStatus}}
   166  	apiCaller := successAPICaller(c, "Status", entitiesArgs, results)
   167  	machine := instancepoller.NewMachine(apiCaller, s.tag, life.Alive)
   168  	status, err := machine.Status()
   169  	c.Check(err, jc.ErrorIsNil)
   170  	c.Check(status, jc.DeepEquals, expectStatus)
   171  	c.Check(apiCaller.CallCount, gc.Equals, 1)
   172  }
   173  
   174  func (s *MachineSuite) TestIsManualSuccess(c *gc.C) {
   175  	results := params.BoolResults{
   176  		Results: []params.BoolResult{{Result: true}},
   177  	}
   178  	apiCaller := successAPICaller(c, "AreManuallyProvisioned", entitiesArgs, results)
   179  	machine := instancepoller.NewMachine(apiCaller, s.tag, life.Alive)
   180  	isManual, err := machine.IsManual()
   181  	c.Check(err, jc.ErrorIsNil)
   182  	c.Check(isManual, jc.IsTrue)
   183  	c.Check(apiCaller.CallCount, gc.Equals, 1)
   184  }
   185  
   186  func (s *MachineSuite) TestInstanceIdSuccess(c *gc.C) {
   187  	results := params.StringResults{
   188  		Results: []params.StringResult{{Result: "i-foo"}},
   189  	}
   190  	apiCaller := successAPICaller(c, "InstanceId", entitiesArgs, results)
   191  	machine := instancepoller.NewMachine(apiCaller, s.tag, life.Alive)
   192  	instId, err := machine.InstanceId()
   193  	c.Check(err, jc.ErrorIsNil)
   194  	c.Check(instId, gc.Equals, instance.Id("i-foo"))
   195  	c.Check(apiCaller.CallCount, gc.Equals, 1)
   196  }
   197  
   198  func (s *MachineSuite) TestInstanceStatusSuccess(c *gc.C) {
   199  	results := params.StatusResults{
   200  		Results: []params.StatusResult{{
   201  			Status: status.Provisioning.String(),
   202  		}},
   203  	}
   204  	apiCaller := successAPICaller(c, "InstanceStatus", entitiesArgs, results)
   205  	machine := instancepoller.NewMachine(apiCaller, s.tag, life.Alive)
   206  	statusResult, err := machine.InstanceStatus()
   207  	c.Check(err, jc.ErrorIsNil)
   208  	c.Check(statusResult.Status, gc.DeepEquals, status.Provisioning.String())
   209  	c.Check(apiCaller.CallCount, gc.Equals, 1)
   210  }
   211  
   212  func (s *MachineSuite) TestSetInstanceStatusSuccess(c *gc.C) {
   213  	expectArgs := params.SetStatus{
   214  		Entities: []params.EntityStatusArgs{{
   215  			Tag:    "machine-42",
   216  			Status: "RUNNING",
   217  		}}}
   218  	results := params.ErrorResults{
   219  		Results: []params.ErrorResult{{Error: nil}},
   220  	}
   221  	apiCaller := successAPICaller(c, "SetInstanceStatus", expectArgs, results)
   222  	machine := instancepoller.NewMachine(apiCaller, s.tag, life.Alive)
   223  	err := machine.SetInstanceStatus("RUNNING", "", nil)
   224  	c.Check(err, jc.ErrorIsNil)
   225  	c.Check(apiCaller.CallCount, gc.Equals, 1)
   226  }
   227  
   228  func (s *MachineSuite) TestSetProviderNetworkConfigSuccess(c *gc.C) {
   229  	cfg := network.InterfaceInfos{{
   230  		DeviceIndex: 0,
   231  		Addresses: []network.ProviderAddress{
   232  			network.NewMachineAddress("10.0.0.42", network.WithCIDR("10.0.0.0/24")).AsProviderAddress(),
   233  		},
   234  	}}
   235  	expectArgs := params.SetProviderNetworkConfig{
   236  		Args: []params.ProviderNetworkConfig{{
   237  			Tag:     "machine-42",
   238  			Configs: params.NetworkConfigFromInterfaceInfo(cfg),
   239  		}},
   240  	}
   241  	results := params.SetProviderNetworkConfigResults{
   242  		Results: []params.SetProviderNetworkConfigResult{{Error: nil}},
   243  	}
   244  	apiCaller := successAPICaller(c, "SetProviderNetworkConfig", expectArgs, results)
   245  	machine := instancepoller.NewMachine(apiCaller, s.tag, life.Alive)
   246  	_, _, err := machine.SetProviderNetworkConfig(cfg)
   247  	c.Check(err, jc.ErrorIsNil)
   248  	c.Check(apiCaller.CallCount, gc.Equals, 1)
   249  }
   250  
   251  func (s *MachineSuite) CheckClientError(c *gc.C, wf methodWrapper) {
   252  	apiCaller := clientErrorAPICaller(c, "", nil)
   253  	machine := instancepoller.NewMachine(apiCaller, s.tag, life.Alive)
   254  	c.Check(wf(machine), gc.ErrorMatches, "client error!")
   255  	c.Check(apiCaller.CallCount, gc.Equals, 1)
   256  }
   257  
   258  func (s *MachineSuite) CheckServerError(c *gc.C, wf methodWrapper, expectErr string, serverResults interface{}) {
   259  	apiCaller := successAPICaller(c, "", nil, serverResults)
   260  	machine := instancepoller.NewMachine(apiCaller, s.tag, life.Alive)
   261  	c.Check(wf(machine), gc.ErrorMatches, expectErr)
   262  	c.Check(apiCaller.CallCount, gc.Equals, 1)
   263  }
   264  
   265  var entitiesArgs = params.Entities{
   266  	Entities: []params.Entity{{Tag: "machine-42"}},
   267  }
   268  
   269  // MakeResultsWithErrors constructs a new instance of the results type
   270  // (from apiserver/params), matching the given resultsRef, finds its
   271  // first field (expected to be a slice, usually "Results") and adds
   272  // howMany elements to it, setting the Error field of each element to
   273  // err.
   274  //
   275  // This helper makes a few assumptions:
   276  // - resultsRef's type is a struct and has a single field (commonly - "Results")
   277  // - that field is a slice of structs, which have an Error field
   278  // - the Error field is of type *params.Error
   279  //
   280  // Example:
   281  //
   282  //	err := apiservertesting.ServerError("foo")
   283  //	r := MakeResultsWithErrors(params.LifeResults{}, err, 2)
   284  //
   285  // is equvalent to:
   286  //
   287  //	r := params.LifeResults{Results: []params.LifeResult{{Error: err}, {Error: err}}}
   288  func MakeResultsWithErrors(resultsRef interface{}, err *params.Error, howMany int) interface{} {
   289  	// Make a new instance of the same type as resultsRef.
   290  	resultsType := reflect.TypeOf(resultsRef)
   291  	newResults := reflect.New(resultsType).Elem()
   292  
   293  	// Make a new empty slice for the results.
   294  	sliceField := newResults.Field(0)
   295  	newSlice := reflect.New(sliceField.Type()).Elem()
   296  
   297  	// Make a new result of the slice's element type and set it to err.
   298  	newResult := reflect.New(newSlice.Type().Elem()).Elem()
   299  	newResult.FieldByName("Error").Set(reflect.ValueOf(err))
   300  
   301  	// Append howMany copies of newResult to the slice.
   302  	for howMany > 0 {
   303  		sliceField.Set(reflect.Append(sliceField, newResult))
   304  		howMany--
   305  	}
   306  
   307  	return newResults.Interface()
   308  }
   309  
   310  // TODO(dimitern): Move this and MakeResultsWithErrors in params/testing ?
   311  func (MachineSuite) TestMakeResultsWithErrors(c *gc.C) {
   312  	err := apiservertesting.ServerError("foo")
   313  	r1 := MakeResultsWithErrors(params.LifeResults{}, err, 2)
   314  	r2 := params.LifeResults{Results: []params.LifeResult{{Error: err}, {Error: err}}}
   315  	c.Assert(r1, jc.DeepEquals, r2)
   316  }