func TestGetMachines(t *testing.T) { kg := newMockKiteGetter() kg.AddByUrl("http://testhost1:56789") store := storage.NewMemoryStorage() r := &Remote{ localKite: &kite.Kite{ Id: "test id", Config: &config.Config{ Username: "******", }, }, kitesGetter: kg, log: discardLogger, machinesCacheMax: 1 * time.Second, machineNamesCache: map[string]string{}, machines: machine.NewMachines(discardLogger, store), storage: store, } machines, err := r.GetMachines() if err != nil { t.Error(err) } // Should return all kites if machines.Count() != 1 { t.Fatalf( "Expected GetKites to return all current kites. Wanted %d, got %d", 1, machines.Count(), ) } kg.AddByUrl("http://testhost2:56789") machines, err = r.GetMachines() if err != nil { t.Error(err) } // Should return only the cached kites if machines.Count() != 1 { t.Errorf( "Expected GetKites to cache results. Expected %d, got %d", 1, machines.Count(), ) } // Wait longer than the kite timeout time.Sleep(2 * time.Second) machines, err = r.GetMachines() if err != nil { t.Error(err) } // Should not use the cache if machines.Count() != 2 { t.Errorf( "Expected GetKites clear cache. Expected %d results, got %d", 2, machines.Count(), ) } // Tell the kite getter to fail kg.GetKitesError = errors.New("KitesGetter test error") kg.AddByUrl("http://testhost3:56789") machines, err = r.GetMachines() // Should return the old results if the KitesGetter returns an error if machines.Count() != 2 { t.Errorf( "Expected GetKites return the cache when KitesGetter fails, within the clientsErrCacheMax duration. Expected %d results, got %d", 2, machines.Count(), ) } // Should not return an error when returning a cache if err != nil { t.Errorf("Expected GetKites to not return an error if it is returning a cache. Expected nil, got '%s'", err.Error()) } // Sleep past the clientsErrCacheMax duration time.Sleep(3 * time.Second) machines, err = r.GetMachines() // Should return the error when the clientsErrCacheMax is too old if err == nil { t.Error("Expected GetKites to return an error when after clientsErrCacheMax. Got nil.") } // Should not return any results after the clientsErrCacheMax // duration if machines != nil { t.Errorf( "Expected GetMachines not to return the machines cache when KitesGetter failes after clientsErrCacheMax. Expected nil machine", ) } }
func TestMatchMachineToKite(t *testing.T) { Convey("Given a Remote", t, func() { kg := newMockKiteGetter() store := storage.NewMemoryStorage() r := &Remote{ log: discardLogger, kitesGetter: kg, machines: machine.NewMachines(discardLogger, store), } Convey("Given a machine with a URL of http://foo.url", func() { m := &machine.Machine{MachineMeta: machine.MachineMeta{ URL: "http://foo.url", IP: "foo.url", }} So(r.machines.Add(m), ShouldBeNil) Convey("With a kite for http://foo.url", func() { kg.AddByUrl("http://foo.url") Convey("It should match", func() { match, err := r.matchMachineToKite(kg.Clients[0].Client) So(err, ShouldBeNil) So(match, ShouldEqual, m) }) }) Convey("With a kite for http://foo.url:8080", func() { kg.AddByUrl("http://foo.url:8080") Convey("It should not match", func() { match, err := r.matchMachineToKite(kg.Clients[0].Client) So(err, ShouldEqual, machine.ErrMachineNotFound) So(match, ShouldBeNil) }) }) }) Convey("Given a machine with a blank URL and an IP of foo.url", func() { m := &machine.Machine{MachineMeta: machine.MachineMeta{ URL: "", IP: "foo.url", }} So(r.machines.Add(m), ShouldBeNil) Convey("With a kite for http://foo.url", func() { kg.AddByUrl("http://foo.url") Convey("It should match", func() { match, err := r.matchMachineToKite(kg.Clients[0].Client) So(err, ShouldBeNil) So(match, ShouldEqual, m) }) }) Convey("With a kite for http://foo.url:8080", func() { kg.AddByUrl("http://foo.url:8080") Convey("It should match", func() { match, err := r.matchMachineToKite(kg.Clients[0].Client) So(err, ShouldBeNil) So(match, ShouldEqual, m) }) }) }) }) }
func TestGetMachinesWithoutCache(t *testing.T) { Convey("Given a Remote", t, func() { kg := newMockKiteGetter() store := storage.NewMemoryStorage() r := &Remote{ localKite: &kite.Kite{ Id: "test id", Config: &config.Config{ Username: "******", }, }, kitesGetter: kg, log: discardLogger, machines: machine.NewMachines(discardLogger, store), machinesCacheMax: 1 * time.Second, machineNamesCache: map[string]string{}, storage: store, } // Sanity check our count. So(r.machines.Count(), ShouldEqual, 0) Convey("Given a new machine", func() { kg.AddByUrl("http://testhost1:56789") // Sanity check our config So(kg.Clients[0].Reconnect, ShouldBeFalse) Convey("It should configure the kite Client", func() { r.GetMachinesWithoutCache() So(kg.Clients[0].Reconnect, ShouldBeTrue) }) Convey("It should add the new machine", func() { machines, err := r.GetMachinesWithoutCache() So(err, ShouldBeNil) So(machines, ShouldNotBeNil) So(machines.Count(), ShouldEqual, 1) }) Convey("It should create the HTTPTracker", func() { machines, err := r.GetMachinesWithoutCache() So(err, ShouldBeNil) So(machines, ShouldNotBeNil) mach := machines.Machines()[0] So(mach, ShouldNotBeNil) So(mach.HTTPTracker, ShouldNotBeNil) Convey("It should start the HTTPTracker", func() { So(mach.HTTPTracker.IsPinging(), ShouldBeTrue) }) }) }) Convey("Given a loaded but not yet valid machine", func() { kg.AddByUrl("http://testhost1:56789") // Add a machine, with just enough info to serve our needs r.machines.Add(&machine.Machine{ MachineMeta: machine.MachineMeta{ IP: "testhost1", }, }) // Sanity check our config So(kg.Clients[0].Reconnect, ShouldBeFalse) Convey("It should configure the kite Client", func() { r.GetMachinesWithoutCache() So(kg.Clients[0].Reconnect, ShouldBeTrue) }) Convey("It should not add a new machine for the kite", func() { machines, err := r.GetMachinesWithoutCache() So(err, ShouldBeNil) So(machines, ShouldNotBeNil) So(machines.Count(), ShouldEqual, 1) }) Convey("It should create the HTTPTracker", func() { machines, err := r.GetMachinesWithoutCache() So(err, ShouldBeNil) So(machines, ShouldNotBeNil) mach := machines.Machines()[0] So(mach, ShouldNotBeNil) So(mach.HTTPTracker, ShouldNotBeNil) Convey("It should start the HTTPTracker", func() { So(mach.HTTPTracker.IsPinging(), ShouldBeTrue) }) }) }) Convey("Given an pre existing valid machine", func() { kg.AddByUrl("http://testhost1:56789") // Add a machine, with just enough info to serve our needs validMachine := &machine.Machine{ MachineMeta: machine.MachineMeta{ IP: "testhost1", }, Log: testutil.DiscardLogger, KiteTracker: kitepinger.NewPingTracker(nil), // Invalid ping trackers, but HTTPTracker: kitepinger.NewPingTracker(nil), // okay for this test currently // a bit of a trick, Machine implements Transport, so we're using an empty // machine here to implement transport, and thus make the validMachine instance // "valid". Both this and the pingtrackers above are not usable, but satisfy // the checkvalid requirements. Transport: &machine.Machine{}, } So(r.machines.Add(validMachine), ShouldBeNil) // sanity check, to make sure it's valid So(validMachine.CheckValid(), ShouldBeNil) Convey("It should be the same machine instance", func() { machines, err := r.GetMachinesWithoutCache() So(err, ShouldBeNil) returnedMachine := machines.Machines()[0] So(returnedMachine, ShouldEqual, validMachine) }) Convey("It should only return 1 machine", func() { machines, err := r.GetMachinesWithoutCache() So(err, ShouldBeNil) So(machines.Count(), ShouldEqual, 1) }) Convey("With a label that does not match the local label", func() { kg.Clients[0].MachineLabel = "foobarbaz" // Sanity check So(kg.Clients[0].MachineLabel, ShouldNotEqual, validMachine.MachineLabel) Convey("It should update the machine label", func() { machines, err := r.GetMachinesWithoutCache() So(err, ShouldBeNil) returnedMachine := machines.Machines()[0] So(returnedMachine.MachineLabel, ShouldEqual, validMachine.MachineLabel) }) }) }) }) }
func TestLogrotate_Upload(t *testing.T) { ub := make(UserBucket) m := storage.NewMemoryStorage() l := &logrotate.Uploader{ UserBucket: ub, MetaStore: &storage.EncodingStorage{ Interface: m, }, } for i, part := range parts { c := reader(0, part.Size) if _, err := l.Upload("content.gz", c); err != nil { t.Fatalf("%d: Put()=%s", i, err) } key := fmt.Sprintf("content.gz.%d", i) p, ok := ub[key] if !ok { t.Fatalf("%d: %q does not exist", key) } got := bytes.NewReader(p) want := reader(part.Offset, part.Size) if err := equal(got, want); err != nil { t.Fatalf("%d: %s", i, err) } } if len(ub) != len(parts) { t.Fatalf("got %d, want %d", len(ub), len(parts)) } for name, s := range m.M { var meta logrotate.Metadata if err := json.Unmarshal([]byte(s), &meta); err != nil { t.Errorf("%s: Unmarshal()=%s", name, err) continue } for i, p := range meta.Parts { if p.Size == 0 { t.Errorf("%s: %d: want p.Size != 0", name, i) continue } if p.CompressedSize == 0 { t.Errorf("%s: %d: want p.CompressedSize != 0", name, i) continue } if p.Checksum == "" { t.Errorf(`%s: %d: want p.Checksum != ""`, name, i) continue } if p.Size <= p.CompressedSize { t.Errorf("%s: %d: want p.Size=%d > p.CompressedSize=%d", name, i, p.Size, p.CompressedSize) continue } } } }
func TestRestoreMounts(t *testing.T) { Convey("Given a Remote", t, func() { callCounts := map[string]int{} callOrder := []string{} kg := newMockKiteGetter() store := storage.NewMemoryStorage() r := &Remote{ localKite: &kite.Kite{ Id: "test id", Config: &config.Config{ Username: "******", }, }, kitesGetter: kg, log: discardLogger, machines: machine.NewMachines(discardLogger, store), machinesCacheMax: 1 * time.Second, machineNamesCache: map[string]string{}, storage: store, // Set it to a default value, so we don't accidentally call real method mockedRestoreMount: func(m *mount.Mount) error { return errors.New("default error") }, } Convey("With some machines associated to the mounts", func() { r.mounts = []*mount.Mount{ {IP: "foo"}, {IP: "bar"}, } r.maxRestoreAttempts = 3 kg.AddByUrl("http://foo/kite") kg.AddByUrl("http://bar/kite") kg.AddByUrl("http://baz/kite") Convey("It should set machines with mounts' status to remounting", func() { r.restoreMounts() m, err := r.machines.GetByIP("foo") So(err, ShouldBeNil) status, statusMsg := m.GetRawStatus() So(status, ShouldEqual, machine.MachineRemounting) So(statusMsg, ShouldEqual, autoRemounting) m, err = r.machines.GetByIP("bar") So(err, ShouldBeNil) status, statusMsg = m.GetRawStatus() So(status, ShouldEqual, machine.MachineRemounting) So(statusMsg, ShouldEqual, autoRemounting) }) Convey("It should not set machines without mounts' status", func() { r.restoreMounts() m, err := r.machines.GetByIP("baz") So(err, ShouldBeNil) status, statusMsg := m.GetRawStatus() So(status, ShouldEqual, machine.MachineStatusUnknown) So(statusMsg, ShouldEqual, "") }) }) Convey("With a mount that succeeds", func() { r.mounts = []*mount.Mount{{IP: "foo"}} r.maxRestoreAttempts = 5 r.mockedRestoreMount = func(m *mount.Mount) error { callCounts[m.IP]++ return nil } Convey("It should restore it only once", func() { So(r.restoreMounts(), ShouldBeNil) So(callCounts, ShouldResemble, map[string]int{"foo": 1}) }) Convey("It should not remove the mount from the internal mount slice", func() { So(r.restoreMounts(), ShouldBeNil) So(len(r.mounts), ShouldEqual, 1) }) }) Convey("With a mount that succeeds and another that fails", func() { fakeErr := errors.New("Fake error") r.mounts = []*mount.Mount{ {IP: "foo"}, {IP: "bar"}, } r.maxRestoreAttempts = 4 r.mockedRestoreMount = func(m *mount.Mount) error { callCounts[m.IP]++ callOrder = append(callOrder, m.IP) if m.IP == "bar" { return fakeErr } return nil } Convey("It should return the error after giving max attempts", func() { So(r.restoreMounts(), ShouldEqual, fakeErr) }) Convey("It should only attempt the successful mount once", func() { r.restoreMounts() calls, ok := callCounts["foo"] So(ok, ShouldBeTrue) // prevent panics So(calls, ShouldEqual, 1) }) Convey("It should repeat attempts for the failure", func() { r.restoreMounts() calls, ok := callCounts["bar"] So(ok, ShouldBeTrue) // prevent panics So(calls, ShouldEqual, 3) }) Convey("It should call in the expected order", func() { r.restoreMounts() So(callOrder, ShouldResemble, []string{ "foo", "bar", "bar", "bar", }) }) }) Convey("With a mount that eventually succeeds", func() { r.mounts = []*mount.Mount{ {IP: "foo"}, {IP: "bar"}, {IP: "baz"}, } r.maxRestoreAttempts = 20 // more than we should need, given working code. r.mockedRestoreMount = func(m *mount.Mount) error { callCounts[m.IP]++ callOrder = append(callOrder, m.IP) isErrMount := m.IP == "bar" || m.IP == "baz" if isErrMount && callCounts[m.IP] < 4 { return errors.New("Fake error") } return nil } Convey("It should stop after the mount succeeds", func() { So(r.restoreMounts(), ShouldBeNil) So(callCounts, ShouldResemble, map[string]int{ "foo": 1, "bar": 4, // 4th attempt should have succeeded "baz": 4, // 4th attempt should have succeeded }) }) // Skipping to avoid Wercker runtime making this test be flaky. SkipConvey("With a failure pause", func() { // Pause 10ms for each failure r.restoreFailuresPause = 10 * time.Millisecond Convey("It should pause between calls", func() { start := time.Now() So(r.restoreMounts(), ShouldBeNil) runDur := time.Since(start) // 10ms pauses for failures after the first one, 8 failures in total, // 6 that should pause. // Should almost take 60ms, plus some allowence for runtime. So(runDur, ShouldAlmostEqual, 60*time.Millisecond, 10*time.Millisecond) }) }) Convey("It should call in the expected order", func() { So(r.restoreMounts(), ShouldBeNil) So(callOrder, ShouldResemble, []string{ "foo", "bar", "baz", "bar", "baz", "bar", "baz", "bar", "baz", }) }) }) }) }
func TestUnmountFolder(t *testing.T) { Convey("Given a Remote with no mounts", t, func() { kg := newMockKiteGetter() r := Remote{ log: discardLogger, mounts: mount.Mounts{}, kitesGetter: kg, machines: machine.NewMachines(discardLogger, storage.NewMemoryStorage()), localKite: &kite.Kite{ Id: "test id", Config: &config.Config{ Username: "******", }, }, } Convey("When requesting a mount name and no path", func() { Convey("It should return a no-mount-no-path error", func() { var unmountPathCalled bool r.unmountPath = func(p string) error { unmountPathCalled = true return nil } err := r.UnmountFolder(req.UnmountFolder{Name: "foo"}) So(err, ShouldNotBeNil) So(unmountPathCalled, ShouldBeFalse) kErr, ok := err.(*kite.Error) So(ok, ShouldBeTrue) So(kErr.Type, ShouldEqual, mountNotFoundNoPath) }) }) }) Convey("Given a Remote with mounts", t, func() { goodUnmountMocker := &unmountMocker{Name: "good mount"} badUnmountMocker := &unmountMocker{ Name: "bad mount", Error: errors.New("bad mount error"), } kg := newMockKiteGetter() store := storage.NewMemoryStorage() r := Remote{ log: testutil.DiscardLogger, kitesGetter: kg, storage: store, machines: machine.NewMachines(discardLogger, store), mounts: mount.Mounts{ &mount.Mount{ MountName: goodUnmountMocker.Name, Unmounter: goodUnmountMocker, }, &mount.Mount{ MountName: badUnmountMocker.Name, Unmounter: badUnmountMocker, }, }, localKite: &kite.Kite{ Id: "test id", Config: &config.Config{ Username: "******", }, }, } Convey("When called with a name that exists", func() { Convey("It should call the appropriate Unmounter", func() { err := r.UnmountFolder(req.UnmountFolder{Name: goodUnmountMocker.Name}) So(err, ShouldBeNil) So(goodUnmountMocker.Calls, ShouldEqual, 1) So(badUnmountMocker.Calls, ShouldEqual, 0) }) Convey("It should remove the mount from the Mounts slice", func() { err := r.UnmountFolder(req.UnmountFolder{Name: goodUnmountMocker.Name}) So(err, ShouldBeNil) So(len(r.mounts), ShouldEqual, 1) // only the bad unmounter should be left So(r.mounts[0].MountName, ShouldEqual, badUnmountMocker.Name) }) }) Convey("When called with a mount that fails to unmount", func() { Convey("It should still remove the mount from the Mounts slice", func() { r.UnmountFolder(req.UnmountFolder{Name: badUnmountMocker.Name}) So(len(r.mounts), ShouldEqual, 1) // only the good unmounter should be left So(r.mounts[0].MountName, ShouldEqual, goodUnmountMocker.Name) }) }) }) }