1
// Copyright 2013 Canonical Ltd.
2
// Licensed under the AGPLv3, see LICENCE file for details.
13
"github.com/juju/errors"
14
gitjujutesting "github.com/juju/testing"
15
jc "github.com/juju/testing/checkers"
16
gc "gopkg.in/check.v1"
17
"gopkg.in/juju/names.v2"
19
"github.com/juju/juju/api"
20
apimachiner "github.com/juju/juju/api/machiner"
21
"github.com/juju/juju/apiserver/params"
22
"github.com/juju/juju/juju/testing"
23
"github.com/juju/juju/network"
24
"github.com/juju/juju/state"
25
"github.com/juju/juju/status"
26
coretesting "github.com/juju/juju/testing"
27
"github.com/juju/juju/worker"
28
"github.com/juju/juju/worker/machiner"
31
func TestPackage(t *stdtesting.T) {
32
coretesting.MgoTestPackage(t)
35
type MachinerSuite struct {
37
accessor *mockMachineAccessor
38
machineTag names.MachineTag
42
var _ = gc.Suite(&MachinerSuite{})
44
func (s *MachinerSuite) SetUpTest(c *gc.C) {
45
s.BaseSuite.SetUpTest(c)
46
s.accessor = &mockMachineAccessor{}
47
s.accessor.machine.watcher.changes = make(chan struct{})
48
s.accessor.machine.life = params.Alive
49
s.machineTag = names.NewMachineTag("123")
50
s.addresses = []net.Addr{ // anything will do
51
&net.IPAddr{IP: net.IPv4bcast},
52
&net.IPAddr{IP: net.IPv4zero},
54
s.PatchValue(machiner.InterfaceAddrs, func() ([]net.Addr, error) {
55
return s.addresses, nil
57
s.PatchValue(machiner.GetObservedNetworkConfig, func() ([]params.NetworkConfig, error) {
62
func (s *MachinerSuite) TestMachinerConfigValidate(c *gc.C) {
63
_, err := machiner.NewMachiner(machiner.Config{})
64
c.Assert(err, gc.ErrorMatches, "validating config: unspecified MachineAccessor not valid")
65
_, err = machiner.NewMachiner(machiner.Config{
66
MachineAccessor: &mockMachineAccessor{},
68
c.Assert(err, gc.ErrorMatches, "validating config: unspecified Tag not valid")
70
w, err := machiner.NewMachiner(machiner.Config{
71
MachineAccessor: &mockMachineAccessor{},
72
Tag: names.NewMachineTag("123"),
74
c.Assert(err, jc.ErrorIsNil)
76
// must stop the worker to prevent a data race when cleanup suite
77
// rolls back the patches
79
c.Assert(err, jc.ErrorIsNil)
82
func (s *MachinerSuite) TestMachinerMachineNotFound(c *gc.C) {
83
// Accessing the machine initially yields "not found or unauthorized".
84
// We don't know which, so we don't report that the machine is dead.
85
var machineDead machineDeathTracker
86
w, err := machiner.NewMachiner(machiner.Config{
87
s.accessor, s.machineTag, false,
88
machineDead.machineDead,
90
c.Assert(err, jc.ErrorIsNil)
91
s.accessor.machine.SetErrors(
92
nil, // SetMachineAddresses
95
¶ms.Error{Code: params.CodeNotFound}, // Refresh
97
s.accessor.machine.watcher.changes <- struct{}{}
99
c.Assert(errors.Cause(err), gc.Equals, worker.ErrTerminateAgent)
100
c.Assert(bool(machineDead), jc.IsFalse)
103
func (s *MachinerSuite) TestMachinerSetStatusStopped(c *gc.C) {
104
w, err := machiner.NewMachiner(machiner.Config{
105
MachineAccessor: s.accessor,
108
c.Assert(err, jc.ErrorIsNil)
109
s.accessor.machine.life = params.Dying
110
s.accessor.machine.SetErrors(
111
nil, // SetMachineAddresses
112
nil, // SetStatus (started)
115
errors.New("cannot set status"), // SetStatus (stopped)
117
s.accessor.machine.watcher.changes <- struct{}{}
120
err, gc.ErrorMatches,
121
"machine-123 failed to set status stopped: cannot set status",
123
s.accessor.machine.CheckCallNames(c,
124
"SetMachineAddresses",
131
s.accessor.machine.CheckCall(
133
status.StatusStopped,
135
map[string]interface{}(nil),
139
func (s *MachinerSuite) TestMachinerMachineEnsureDeadError(c *gc.C) {
140
w, err := machiner.NewMachiner(machiner.Config{
141
MachineAccessor: s.accessor,
144
c.Assert(err, jc.ErrorIsNil)
145
s.accessor.machine.life = params.Dying
146
s.accessor.machine.SetErrors(
147
nil, // SetMachineAddresses
152
errors.New("cannot ensure machine is dead"), // EnsureDead
154
s.accessor.machine.watcher.changes <- struct{}{}
157
err, gc.ErrorMatches,
158
"machine-123 failed to set machine to dead: cannot ensure machine is dead",
162
func (s *MachinerSuite) TestMachinerMachineAssignedUnits(c *gc.C) {
163
w, err := machiner.NewMachiner(machiner.Config{
164
MachineAccessor: s.accessor,
167
c.Assert(err, jc.ErrorIsNil)
168
s.accessor.machine.life = params.Dying
169
s.accessor.machine.SetErrors(
170
nil, // SetMachineAddresses
175
¶ms.Error{Code: params.CodeHasAssignedUnits}, // EnsureDead
177
s.accessor.machine.watcher.changes <- struct{}{}
180
// If EnsureDead fails with "machine has assigned units", then
181
// the worker will not fail, but will wait for more events.
182
c.Check(err, jc.ErrorIsNil)
184
s.accessor.machine.CheckCallNames(c,
185
"SetMachineAddresses",
195
func (s *MachinerSuite) TestMachinerStorageAttached(c *gc.C) {
196
// Machine is dying. We'll respond to "EnsureDead" by
197
// saying that there are still storage attachments;
198
// this should not cause an error.
199
s.accessor.machine.life = params.Dying
200
s.accessor.machine.SetErrors(
201
nil, // SetMachineAddresses
206
¶ms.Error{Code: params.CodeMachineHasAttachedStorage},
209
worker, err := machiner.NewMachiner(machiner.Config{
210
s.accessor, s.machineTag, false,
211
func() error { return nil },
213
c.Assert(err, jc.ErrorIsNil)
214
s.accessor.machine.watcher.changes <- struct{}{}
215
err = stopWorker(worker)
216
c.Check(err, jc.ErrorIsNil)
218
s.accessor.CheckCalls(c, []gitjujutesting.StubCall{{
220
Args: []interface{}{s.machineTag},
223
s.accessor.machine.CheckCalls(c, []gitjujutesting.StubCall{{
224
FuncName: "SetMachineAddresses",
226
network.NewAddresses(
232
FuncName: "SetStatus",
234
status.StatusStarted,
236
map[string]interface{}(nil),
245
FuncName: "SetStatus",
247
status.StatusStopped,
249
map[string]interface{}(nil),
252
FuncName: "EnsureDead",
256
// worstCase is used for timeouts when timing out
257
// will fail the test. Raising this value should
258
// not affect the overall running time of the tests
260
const worstCase = 5 * time.Second
262
type MachinerStateSuite struct {
263
testing.JujuConnSuite
266
machinerState *apimachiner.State
267
machine *state.Machine
268
apiMachine *apimachiner.Machine
271
var _ = gc.Suite(&MachinerStateSuite{})
273
func (s *MachinerStateSuite) SetUpTest(c *gc.C) {
274
s.JujuConnSuite.SetUpTest(c)
275
s.st, s.machine = s.OpenAPIAsNewMachine(c)
277
// Create the machiner API facade.
278
s.machinerState = apimachiner.NewState(s.st)
279
c.Assert(s.machinerState, gc.NotNil)
281
// Get the machine through the facade.
283
s.apiMachine, err = s.machinerState.Machine(s.machine.Tag().(names.MachineTag))
284
c.Assert(err, jc.ErrorIsNil)
285
c.Assert(s.apiMachine.Tag(), gc.Equals, s.machine.Tag())
286
// Isolate tests better by not using real interface addresses.
287
s.PatchValue(machiner.InterfaceAddrs, func() ([]net.Addr, error) {
290
s.PatchValue(&network.InterfaceByNameAddrs, func(string) ([]net.Addr, error) {
293
s.PatchValue(&network.LXCNetDefaultConfig, "")
294
s.PatchValue(machiner.GetObservedNetworkConfig, func() ([]params.NetworkConfig, error) {
299
func (s *MachinerStateSuite) waitMachineStatus(c *gc.C, m *state.Machine, expectStatus status.Status) {
300
timeout := time.After(worstCase)
304
c.Fatalf("timeout while waiting for machine status to change")
305
case <-time.After(10 * time.Millisecond):
306
statusInfo, err := m.Status()
307
c.Assert(err, jc.ErrorIsNil)
308
if statusInfo.Status != expectStatus {
309
c.Logf("machine %q status is %s, still waiting", m, statusInfo.Status)
317
func (s *MachinerStateSuite) TestNotFoundOrUnauthorized(c *gc.C) {
318
mr, err := machiner.NewMachiner(machiner.Config{
319
machiner.APIMachineAccessor{s.machinerState},
320
names.NewMachineTag("99"),
322
// the "machineDead" callback should not be invoked
323
// because we don't know whether the agent is
324
// legimitately not found or unauthorized; we err on
325
// the side of caution, in case the password got mucked
326
// up, or state got mucked up (e.g. during an upgrade).
327
func() error { return errors.New("should not be called") },
329
c.Assert(err, jc.ErrorIsNil)
330
c.Assert(mr.Wait(), gc.Equals, worker.ErrTerminateAgent)
333
func (s *MachinerStateSuite) makeMachiner(
335
ignoreAddresses bool,
336
machineDead func() error,
338
if machineDead == nil {
339
machineDead = func() error { return nil }
341
w, err := machiner.NewMachiner(machiner.Config{
342
machiner.APIMachineAccessor{s.machinerState},
343
s.apiMachine.Tag().(names.MachineTag),
347
c.Assert(err, jc.ErrorIsNil)
351
type machineDeathTracker bool
353
func (t *machineDeathTracker) machineDead() error {
358
func (s *MachinerStateSuite) TestRunStop(c *gc.C) {
359
var machineDead machineDeathTracker
360
mr := s.makeMachiner(c, false, machineDead.machineDead)
361
c.Assert(worker.Stop(mr), jc.ErrorIsNil)
362
c.Assert(s.apiMachine.Refresh(), jc.ErrorIsNil)
363
c.Assert(s.apiMachine.Life(), gc.Equals, params.Alive)
364
c.Assert(bool(machineDead), jc.IsFalse)
367
func (s *MachinerStateSuite) TestStartSetsStatus(c *gc.C) {
368
statusInfo, err := s.machine.Status()
369
c.Assert(err, jc.ErrorIsNil)
370
c.Assert(statusInfo.Status, gc.Equals, status.StatusPending)
371
c.Assert(statusInfo.Message, gc.Equals, "")
373
mr := s.makeMachiner(c, false, nil)
374
defer worker.Stop(mr)
376
s.waitMachineStatus(c, s.machine, status.StatusStarted)
379
func (s *MachinerStateSuite) TestSetsStatusWhenDying(c *gc.C) {
380
mr := s.makeMachiner(c, false, nil)
381
defer worker.Stop(mr)
382
c.Assert(s.machine.Destroy(), jc.ErrorIsNil)
383
s.waitMachineStatus(c, s.machine, status.StatusStopped)
386
func (s *MachinerStateSuite) TestSetDead(c *gc.C) {
387
var machineDead machineDeathTracker
388
mr := s.makeMachiner(c, false, machineDead.machineDead)
389
defer worker.Stop(mr)
390
c.Assert(s.machine.Destroy(), jc.ErrorIsNil)
392
c.Assert(mr.Wait(), gc.Equals, worker.ErrTerminateAgent)
393
c.Assert(s.machine.Refresh(), jc.ErrorIsNil)
394
c.Assert(s.machine.Life(), gc.Equals, state.Dead)
395
c.Assert(bool(machineDead), jc.IsTrue)
398
func (s *MachinerStateSuite) TestSetDeadWithDyingUnit(c *gc.C) {
399
var machineDead machineDeathTracker
400
mr := s.makeMachiner(c, false, machineDead.machineDead)
401
defer worker.Stop(mr)
403
// Add a service, assign to machine.
404
wordpress := s.AddTestingService(c, "wordpress", s.AddTestingCharm(c, "wordpress"))
405
unit, err := wordpress.AddUnit()
406
c.Assert(err, jc.ErrorIsNil)
407
err = unit.AssignToMachine(s.machine)
408
c.Assert(err, jc.ErrorIsNil)
410
// Service alive, can't destroy machine.
411
err = s.machine.Destroy()
412
c.Assert(err, jc.Satisfies, state.IsHasAssignedUnitsError)
414
err = wordpress.Destroy()
415
c.Assert(err, jc.ErrorIsNil)
417
// With dying unit, machine can now be marked as dying.
418
c.Assert(s.machine.Destroy(), jc.ErrorIsNil)
420
c.Assert(s.machine.Refresh(), jc.ErrorIsNil)
421
c.Assert(s.machine.Life(), gc.Equals, state.Dying)
422
c.Assert(bool(machineDead), jc.IsFalse)
424
// When the unit is ultimately destroyed, the machine becomes dead.
426
c.Assert(err, jc.ErrorIsNil)
428
c.Assert(mr.Wait(), gc.Equals, worker.ErrTerminateAgent)
429
c.Assert(bool(machineDead), jc.IsTrue)
433
func (s *MachinerStateSuite) setupSetMachineAddresses(c *gc.C, ignore bool) {
434
lxcFakeNetConfig := filepath.Join(c.MkDir(), "lxc-net")
439
LXC_BRIDGE="foobar" # detected
440
anything else ignored
441
LXC_BRIDGE="ignored"`[1:])
442
err := ioutil.WriteFile(lxcFakeNetConfig, netConf, 0644)
443
c.Assert(err, jc.ErrorIsNil)
444
s.PatchValue(machiner.InterfaceAddrs, func() ([]net.Addr, error) {
446
&net.IPAddr{IP: net.IPv4(10, 0, 0, 1)},
447
&net.IPAddr{IP: net.IPv4(127, 0, 0, 1)},
448
&net.IPAddr{IP: net.IPv4(10, 0, 3, 1)}, // lxc bridge address ignored
449
&net.IPAddr{IP: net.IPv6loopback},
450
&net.UnixAddr{}, // not IP, ignored
451
&net.IPAddr{IP: net.IPv4(10, 0, 3, 4)}, // lxc bridge address ignored
452
&net.IPNet{IP: net.ParseIP("2001:db8::1")},
453
&net.IPAddr{IP: net.IPv4(169, 254, 1, 20)}, // LinkLocal Ignored
454
&net.IPNet{IP: net.ParseIP("fe80::1")}, // LinkLocal Ignored
458
s.PatchValue(&network.InterfaceByNameAddrs, func(name string) ([]net.Addr, error) {
459
if name == "foobar" {
460
// The addresses on the LXC bridge
462
&net.IPAddr{IP: net.IPv4(10, 0, 3, 1)},
463
&net.IPAddr{IP: net.IPv4(10, 0, 3, 4)},
465
} else if name == network.DefaultLXDBridge {
466
// The addresses on the LXD bridge
468
&net.IPAddr{IP: net.IPv4(10, 0, 4, 1)},
469
&net.IPAddr{IP: net.IPv4(10, 0, 4, 4)},
472
c.Fatalf("unknown bridge in testing: %v", name)
475
s.PatchValue(&network.LXCNetDefaultConfig, lxcFakeNetConfig)
477
mr := s.makeMachiner(c, ignore, nil)
478
defer worker.Stop(mr)
479
c.Assert(s.machine.Destroy(), jc.ErrorIsNil)
481
c.Assert(mr.Wait(), gc.Equals, worker.ErrTerminateAgent)
482
c.Assert(s.machine.Refresh(), jc.ErrorIsNil)
485
func (s *MachinerStateSuite) TestMachineAddresses(c *gc.C) {
486
s.setupSetMachineAddresses(c, false)
487
c.Assert(s.machine.MachineAddresses(), jc.SameContents, []network.Address{
488
network.NewAddress("2001:db8::1"),
489
network.NewScopedAddress("10.0.0.1", network.ScopeCloudLocal),
490
network.NewScopedAddress("::1", network.ScopeMachineLocal),
491
network.NewScopedAddress("127.0.0.1", network.ScopeMachineLocal),
495
func (s *MachinerStateSuite) TestMachineAddressesWithIgnoreFlag(c *gc.C) {
496
s.setupSetMachineAddresses(c, true)
497
c.Assert(s.machine.MachineAddresses(), gc.HasLen, 0)
500
func stopWorker(w worker.Worker) error {