mirror of
https://github.com/tailscale/tailscale.git
synced 2025-03-29 04:22:24 +00:00
ipn/ipnlocal: do controlclient.Shutdown in a different goroutine
We do not need to wait for it to complete. And we might have to call Shutdown from callback from the controlclient which might already be holding a lock that Shutdown requires. Updates #713 Signed-off-by: Maisem Ali <maisem@tailscale.com>
This commit is contained in:
parent
e0cadc5496
commit
fe81ee62d7
@ -1141,9 +1141,7 @@ func (b *LocalBackend) Start(opts ipn.Options) error {
|
|||||||
// into sync with the minimal changes. But that's not how it
|
// into sync with the minimal changes. But that's not how it
|
||||||
// is right now, which is a sign that the code is still too
|
// is right now, which is a sign that the code is still too
|
||||||
// complicated.
|
// complicated.
|
||||||
b.mu.Unlock()
|
b.resetControlClientLockedAsync()
|
||||||
b.cc.Shutdown()
|
|
||||||
b.mu.Lock()
|
|
||||||
}
|
}
|
||||||
httpTestClient := b.httpTestClient
|
httpTestClient := b.httpTestClient
|
||||||
|
|
||||||
@ -1956,7 +1954,7 @@ func (b *LocalBackend) InServerMode() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Login implements Backend.
|
// Login implements Backend.
|
||||||
// As of 2022-02-17, this is only exists for tests.
|
// As of 2022-11-15, this is only exists for Android.
|
||||||
func (b *LocalBackend) Login(token *tailcfg.Oauth2Token) {
|
func (b *LocalBackend) Login(token *tailcfg.Oauth2Token) {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
b.assertClientLocked()
|
b.assertClientLocked()
|
||||||
@ -3128,7 +3126,6 @@ func (b *LocalBackend) hasNodeKey() bool {
|
|||||||
// its internal state.
|
// its internal state.
|
||||||
func (b *LocalBackend) nextState() ipn.State {
|
func (b *LocalBackend) nextState() ipn.State {
|
||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
b.assertClientLocked()
|
|
||||||
var (
|
var (
|
||||||
cc = b.cc
|
cc = b.cc
|
||||||
netMap = b.netMap
|
netMap = b.netMap
|
||||||
@ -3150,7 +3147,7 @@ func (b *LocalBackend) nextState() ipn.State {
|
|||||||
case !wantRunning && !loggedOut && !blocked && b.hasNodeKey():
|
case !wantRunning && !loggedOut && !blocked && b.hasNodeKey():
|
||||||
return ipn.Stopped
|
return ipn.Stopped
|
||||||
case netMap == nil:
|
case netMap == nil:
|
||||||
if cc.AuthCantContinue() || loggedOut {
|
if (cc != nil && cc.AuthCantContinue()) || loggedOut {
|
||||||
// Auth was interrupted or waiting for URL visit,
|
// Auth was interrupted or waiting for URL visit,
|
||||||
// so it won't proceed without human help.
|
// so it won't proceed without human help.
|
||||||
return ipn.NeedsLogin
|
return ipn.NeedsLogin
|
||||||
@ -3238,6 +3235,18 @@ func (b *LocalBackend) requestEngineStatusAndWait() {
|
|||||||
b.statusLock.Unlock()
|
b.statusLock.Unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// resetControlClientLockedAsync sets b.cc to nil, and starts a
|
||||||
|
// goroutine to Shutdown the old client. It does not wait for the
|
||||||
|
// shutdown to complete.
|
||||||
|
func (b *LocalBackend) resetControlClientLockedAsync() {
|
||||||
|
if b.cc == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go b.cc.Shutdown()
|
||||||
|
b.cc = nil
|
||||||
|
b.ccAuto = nil
|
||||||
|
}
|
||||||
|
|
||||||
// ResetForClientDisconnect resets the backend for GUI clients running
|
// ResetForClientDisconnect resets the backend for GUI clients running
|
||||||
// in interactive (non-headless) mode. This is currently used only by
|
// in interactive (non-headless) mode. This is currently used only by
|
||||||
// Windows. This causes all state to be cleared, lest an unrelated user
|
// Windows. This causes all state to be cleared, lest an unrelated user
|
||||||
@ -3249,11 +3258,7 @@ func (b *LocalBackend) ResetForClientDisconnect() {
|
|||||||
b.mu.Lock()
|
b.mu.Lock()
|
||||||
defer b.mu.Unlock()
|
defer b.mu.Unlock()
|
||||||
b.logf("LocalBackend.ResetForClientDisconnect")
|
b.logf("LocalBackend.ResetForClientDisconnect")
|
||||||
|
b.resetControlClientLockedAsync()
|
||||||
if b.cc != nil {
|
|
||||||
go b.cc.Shutdown()
|
|
||||||
b.cc = nil
|
|
||||||
}
|
|
||||||
b.setNetMapLocked(nil)
|
b.setNetMapLocked(nil)
|
||||||
b.pm.Reset()
|
b.pm.Reset()
|
||||||
b.keyExpired = false
|
b.keyExpired = false
|
||||||
|
@ -92,29 +92,42 @@ func (nt *notifyThrottler) drain(count int) []ipn.Notify {
|
|||||||
// the state machine works as expected.
|
// the state machine works as expected.
|
||||||
type mockControl struct {
|
type mockControl struct {
|
||||||
tb testing.TB
|
tb testing.TB
|
||||||
|
logf logger.Logf
|
||||||
opts controlclient.Options
|
opts controlclient.Options
|
||||||
logfActual logger.Logf
|
paused atomic.Bool
|
||||||
statusFunc func(controlclient.Status)
|
|
||||||
|
|
||||||
mu sync.Mutex
|
mu sync.Mutex
|
||||||
|
machineKey key.MachinePrivate
|
||||||
|
persist *persist.Persist
|
||||||
calls []string
|
calls []string
|
||||||
authBlocked bool
|
authBlocked bool
|
||||||
persist *persist.Persist
|
shutdown chan struct{}
|
||||||
machineKey key.MachinePrivate
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newMockControl(tb testing.TB) *mockControl {
|
func newClient(tb testing.TB, opts controlclient.Options) *mockControl {
|
||||||
return &mockControl{
|
return &mockControl{
|
||||||
tb: tb,
|
tb: tb,
|
||||||
authBlocked: true,
|
authBlocked: true,
|
||||||
|
logf: opts.Logf,
|
||||||
|
opts: opts,
|
||||||
|
shutdown: make(chan struct{}),
|
||||||
|
persist: opts.Persist.Clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc *mockControl) logf(format string, args ...any) {
|
func (cc *mockControl) assertShutdown(wasPaused bool) {
|
||||||
if cc.logfActual == nil {
|
cc.tb.Helper()
|
||||||
return
|
select {
|
||||||
|
case <-cc.shutdown:
|
||||||
|
// ok
|
||||||
|
case <-time.After(500 * time.Millisecond):
|
||||||
|
cc.tb.Fatalf("timed out waiting for shutdown")
|
||||||
|
}
|
||||||
|
if wasPaused {
|
||||||
|
cc.assertCalls("unpause", "Shutdown")
|
||||||
|
} else {
|
||||||
|
cc.assertCalls("Shutdown")
|
||||||
}
|
}
|
||||||
cc.logfActual(format, args...)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc *mockControl) populateKeys() (newKeys bool) {
|
func (cc *mockControl) populateKeys() (newKeys bool) {
|
||||||
@ -143,7 +156,12 @@ func (cc *mockControl) populateKeys() (newKeys bool) {
|
|||||||
// send publishes a controlclient.Status notification upstream.
|
// send publishes a controlclient.Status notification upstream.
|
||||||
// (In our tests here, upstream is the ipnlocal.Local instance.)
|
// (In our tests here, upstream is the ipnlocal.Local instance.)
|
||||||
func (cc *mockControl) send(err error, url string, loginFinished bool, nm *netmap.NetworkMap) {
|
func (cc *mockControl) send(err error, url string, loginFinished bool, nm *netmap.NetworkMap) {
|
||||||
if cc.statusFunc != nil {
|
if loginFinished {
|
||||||
|
cc.mu.Lock()
|
||||||
|
cc.authBlocked = false
|
||||||
|
cc.mu.Unlock()
|
||||||
|
}
|
||||||
|
if cc.opts.Status != nil {
|
||||||
pv := cc.persist.View()
|
pv := cc.persist.View()
|
||||||
s := controlclient.Status{
|
s := controlclient.Status{
|
||||||
URL: url,
|
URL: url,
|
||||||
@ -156,7 +174,7 @@ func (cc *mockControl) send(err error, url string, loginFinished bool, nm *netma
|
|||||||
} else if url == "" && err == nil && nm == nil {
|
} else if url == "" && err == nil && nm == nil {
|
||||||
s.LogoutFinished = &empty.Message{}
|
s.LogoutFinished = &empty.Message{}
|
||||||
}
|
}
|
||||||
cc.statusFunc(s)
|
cc.opts.Status(s)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,34 +196,16 @@ func (cc *mockControl) assertCalls(want ...string) {
|
|||||||
cc.calls = nil
|
cc.calls = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// setAuthBlocked changes the return value of AuthCantContinue.
|
|
||||||
// Auth is blocked if you haven't called Login, the control server hasn't
|
|
||||||
// provided an auth URL, or it has provided an auth URL and you haven't
|
|
||||||
// visited it yet.
|
|
||||||
func (cc *mockControl) setAuthBlocked(blocked bool) {
|
|
||||||
cc.mu.Lock()
|
|
||||||
defer cc.mu.Unlock()
|
|
||||||
|
|
||||||
cc.authBlocked = blocked
|
|
||||||
}
|
|
||||||
|
|
||||||
// Shutdown disconnects the client.
|
// Shutdown disconnects the client.
|
||||||
//
|
|
||||||
// Note that in a normal controlclient, Shutdown would be the last thing you
|
|
||||||
// do before discarding the object. In this mock, we don't actually discard
|
|
||||||
// the object, but if you see a call to Shutdown, you should always see a
|
|
||||||
// call to New right after it, if the object continues to be used.
|
|
||||||
// (Note that "New" is the ccGen function here; it means ipn.Backend wanted
|
|
||||||
// to create an entirely new controlclient.)
|
|
||||||
func (cc *mockControl) Shutdown() {
|
func (cc *mockControl) Shutdown() {
|
||||||
cc.logf("Shutdown")
|
cc.logf("Shutdown")
|
||||||
cc.called("Shutdown")
|
cc.called("Shutdown")
|
||||||
|
close(cc.shutdown)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Login starts a login process.
|
// Login starts a login process. Note that in this mock, we don't automatically
|
||||||
// Note that in this mock, we don't automatically generate notifications
|
// generate notifications about the progress of the login operation. You have to
|
||||||
// about the progress of the login operation. You have to call setAuthBlocked()
|
// call send() as required by the test.
|
||||||
// and send() as required by the test.
|
|
||||||
func (cc *mockControl) Login(t *tailcfg.Oauth2Token, flags controlclient.LoginFlags) {
|
func (cc *mockControl) Login(t *tailcfg.Oauth2Token, flags controlclient.LoginFlags) {
|
||||||
cc.logf("Login token=%v flags=%v", t, flags)
|
cc.logf("Login token=%v flags=%v", t, flags)
|
||||||
cc.called("Login")
|
cc.called("Login")
|
||||||
@ -213,7 +213,9 @@ func (cc *mockControl) Login(t *tailcfg.Oauth2Token, flags controlclient.LoginFl
|
|||||||
|
|
||||||
interact := (flags & controlclient.LoginInteractive) != 0
|
interact := (flags & controlclient.LoginInteractive) != 0
|
||||||
cc.logf("Login: interact=%v newKeys=%v", interact, newKeys)
|
cc.logf("Login: interact=%v newKeys=%v", interact, newKeys)
|
||||||
cc.setAuthBlocked(interact || newKeys)
|
cc.mu.Lock()
|
||||||
|
defer cc.mu.Unlock()
|
||||||
|
cc.authBlocked = interact || newKeys
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cc *mockControl) StartLogout() {
|
func (cc *mockControl) StartLogout() {
|
||||||
@ -228,6 +230,10 @@ func (cc *mockControl) Logout(ctx context.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (cc *mockControl) SetPaused(paused bool) {
|
func (cc *mockControl) SetPaused(paused bool) {
|
||||||
|
was := cc.paused.Swap(paused)
|
||||||
|
if was == paused {
|
||||||
|
return
|
||||||
|
}
|
||||||
cc.logf("SetPaused=%v", paused)
|
cc.logf("SetPaused=%v", paused)
|
||||||
if paused {
|
if paused {
|
||||||
cc.called("pause")
|
cc.called("pause")
|
||||||
@ -303,16 +309,10 @@ func TestStateMachine(t *testing.T) {
|
|||||||
t.Fatalf("NewLocalBackend: %v", err)
|
t.Fatalf("NewLocalBackend: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
cc := newMockControl(t)
|
var cc, previousCC *mockControl
|
||||||
cc.statusFunc = b.setClientStatus
|
|
||||||
|
|
||||||
b.SetControlClientGetterForTesting(func(opts controlclient.Options) (controlclient.Client, error) {
|
b.SetControlClientGetterForTesting(func(opts controlclient.Options) (controlclient.Client, error) {
|
||||||
cc.mu.Lock()
|
previousCC = cc
|
||||||
cc.opts = opts
|
cc = newClient(t, opts)
|
||||||
cc.logfActual = opts.Logf
|
|
||||||
cc.authBlocked = true
|
|
||||||
cc.persist = cc.opts.Persist.Clone()
|
|
||||||
cc.mu.Unlock()
|
|
||||||
|
|
||||||
t.Logf("ccGen: new mockControl.")
|
t.Logf("ccGen: new mockControl.")
|
||||||
cc.called("New")
|
cc.called("New")
|
||||||
@ -336,7 +336,7 @@ func TestStateMachine(t *testing.T) {
|
|||||||
|
|
||||||
// Check that it hasn't called us right away.
|
// Check that it hasn't called us right away.
|
||||||
// The state machine should be idle until we call Start().
|
// The state machine should be idle until we call Start().
|
||||||
cc.assertCalls()
|
c.Assert(cc, qt.IsNil)
|
||||||
|
|
||||||
// Start the state machine.
|
// Start the state machine.
|
||||||
// Since !WantRunning by default, it'll create a controlclient,
|
// Since !WantRunning by default, it'll create a controlclient,
|
||||||
@ -346,7 +346,7 @@ func TestStateMachine(t *testing.T) {
|
|||||||
c.Assert(b.Start(ipn.Options{}), qt.IsNil)
|
c.Assert(b.Start(ipn.Options{}), qt.IsNil)
|
||||||
{
|
{
|
||||||
// BUG: strictly, it should pause, not unpause, here, since !WantRunning.
|
// BUG: strictly, it should pause, not unpause, here, since !WantRunning.
|
||||||
cc.assertCalls("New", "unpause")
|
cc.assertCalls("New")
|
||||||
|
|
||||||
nn := notifies.drain(2)
|
nn := notifies.drain(2)
|
||||||
cc.assertCalls()
|
cc.assertCalls()
|
||||||
@ -370,8 +370,8 @@ func TestStateMachine(t *testing.T) {
|
|||||||
notifies.expect(2)
|
notifies.expect(2)
|
||||||
c.Assert(b.Start(ipn.Options{}), qt.IsNil)
|
c.Assert(b.Start(ipn.Options{}), qt.IsNil)
|
||||||
{
|
{
|
||||||
// BUG: strictly, it should pause, not unpause, here, since !WantRunning.
|
previousCC.assertShutdown(false)
|
||||||
cc.assertCalls("Shutdown", "unpause", "New", "unpause")
|
cc.assertCalls("New")
|
||||||
|
|
||||||
nn := notifies.drain(2)
|
nn := notifies.drain(2)
|
||||||
cc.assertCalls()
|
cc.assertCalls()
|
||||||
@ -397,6 +397,7 @@ func TestStateMachine(t *testing.T) {
|
|||||||
// (This behaviour is needed so that b.Login() won't
|
// (This behaviour is needed so that b.Login() won't
|
||||||
// start connecting to an old account right away, if one
|
// start connecting to an old account right away, if one
|
||||||
// exists when you launch another login.)
|
// exists when you launch another login.)
|
||||||
|
c.Assert(ipn.NeedsLogin, qt.Equals, b.State())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempted non-interactive login with no key; indicate that
|
// Attempted non-interactive login with no key; indicate that
|
||||||
@ -406,7 +407,7 @@ func TestStateMachine(t *testing.T) {
|
|||||||
url1 := "http://localhost:1/1"
|
url1 := "http://localhost:1/1"
|
||||||
cc.send(nil, url1, false, nil)
|
cc.send(nil, url1, false, nil)
|
||||||
{
|
{
|
||||||
cc.assertCalls("unpause")
|
cc.assertCalls()
|
||||||
|
|
||||||
// ...but backend eats that notification, because the user
|
// ...but backend eats that notification, because the user
|
||||||
// didn't explicitly request interactive login yet, and
|
// didn't explicitly request interactive login yet, and
|
||||||
@ -416,6 +417,7 @@ func TestStateMachine(t *testing.T) {
|
|||||||
c.Assert(nn[0].Prefs, qt.IsNotNil)
|
c.Assert(nn[0].Prefs, qt.IsNotNil)
|
||||||
c.Assert(nn[0].Prefs.LoggedOut(), qt.IsFalse)
|
c.Assert(nn[0].Prefs.LoggedOut(), qt.IsFalse)
|
||||||
c.Assert(nn[0].Prefs.WantRunning(), qt.IsFalse)
|
c.Assert(nn[0].Prefs.WantRunning(), qt.IsFalse)
|
||||||
|
c.Assert(ipn.NeedsLogin, qt.Equals, b.State())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now we'll try an interactive login.
|
// Now we'll try an interactive login.
|
||||||
@ -427,9 +429,10 @@ func TestStateMachine(t *testing.T) {
|
|||||||
b.StartLoginInteractive()
|
b.StartLoginInteractive()
|
||||||
{
|
{
|
||||||
nn := notifies.drain(1)
|
nn := notifies.drain(1)
|
||||||
cc.assertCalls("unpause")
|
cc.assertCalls()
|
||||||
c.Assert(nn[0].BrowseToURL, qt.IsNotNil)
|
c.Assert(nn[0].BrowseToURL, qt.IsNotNil)
|
||||||
c.Assert(url1, qt.Equals, *nn[0].BrowseToURL)
|
c.Assert(url1, qt.Equals, *nn[0].BrowseToURL)
|
||||||
|
c.Assert(ipn.NeedsLogin, qt.Equals, b.State())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sometimes users press the Login button again, in the middle of
|
// Sometimes users press the Login button again, in the middle of
|
||||||
@ -444,6 +447,7 @@ func TestStateMachine(t *testing.T) {
|
|||||||
notifies.drain(0)
|
notifies.drain(0)
|
||||||
// backend asks control for another login sequence
|
// backend asks control for another login sequence
|
||||||
cc.assertCalls("Login")
|
cc.assertCalls("Login")
|
||||||
|
c.Assert(ipn.NeedsLogin, qt.Equals, b.State())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Provide a new interactive login URL.
|
// Provide a new interactive login URL.
|
||||||
@ -452,13 +456,14 @@ func TestStateMachine(t *testing.T) {
|
|||||||
url2 := "http://localhost:1/2"
|
url2 := "http://localhost:1/2"
|
||||||
cc.send(nil, url2, false, nil)
|
cc.send(nil, url2, false, nil)
|
||||||
{
|
{
|
||||||
cc.assertCalls("unpause", "unpause")
|
cc.assertCalls()
|
||||||
|
|
||||||
// This time, backend should emit it to the UI right away,
|
// This time, backend should emit it to the UI right away,
|
||||||
// because the UI is anxiously awaiting a new URL to visit.
|
// because the UI is anxiously awaiting a new URL to visit.
|
||||||
nn := notifies.drain(1)
|
nn := notifies.drain(1)
|
||||||
c.Assert(nn[0].BrowseToURL, qt.IsNotNil)
|
c.Assert(nn[0].BrowseToURL, qt.IsNotNil)
|
||||||
c.Assert(url2, qt.Equals, *nn[0].BrowseToURL)
|
c.Assert(url2, qt.Equals, *nn[0].BrowseToURL)
|
||||||
|
c.Assert(ipn.NeedsLogin, qt.Equals, b.State())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pretend that the interactive login actually happened.
|
// Pretend that the interactive login actually happened.
|
||||||
@ -467,7 +472,6 @@ func TestStateMachine(t *testing.T) {
|
|||||||
// The backend should propagate this upward for the UI.
|
// The backend should propagate this upward for the UI.
|
||||||
t.Logf("\n\nLoginFinished")
|
t.Logf("\n\nLoginFinished")
|
||||||
notifies.expect(3)
|
notifies.expect(3)
|
||||||
cc.setAuthBlocked(false)
|
|
||||||
cc.persist.LoginName = "user1"
|
cc.persist.LoginName = "user1"
|
||||||
cc.send(nil, "", true, &netmap.NetworkMap{})
|
cc.send(nil, "", true, &netmap.NetworkMap{})
|
||||||
{
|
{
|
||||||
@ -480,12 +484,13 @@ func TestStateMachine(t *testing.T) {
|
|||||||
// wait until it gets into Starting.
|
// wait until it gets into Starting.
|
||||||
// TODO: (Currently this test doesn't detect that bug, but
|
// TODO: (Currently this test doesn't detect that bug, but
|
||||||
// it's visible in the logs)
|
// it's visible in the logs)
|
||||||
cc.assertCalls("unpause", "unpause", "unpause")
|
cc.assertCalls()
|
||||||
c.Assert(nn[0].LoginFinished, qt.IsNotNil)
|
c.Assert(nn[0].LoginFinished, qt.IsNotNil)
|
||||||
c.Assert(nn[1].Prefs, qt.IsNotNil)
|
c.Assert(nn[1].Prefs, qt.IsNotNil)
|
||||||
c.Assert(nn[2].State, qt.IsNotNil)
|
c.Assert(nn[2].State, qt.IsNotNil)
|
||||||
c.Assert(nn[1].Prefs.Persist().LoginName, qt.Equals, "user1")
|
c.Assert(nn[1].Prefs.Persist().LoginName, qt.Equals, "user1")
|
||||||
c.Assert(ipn.NeedsMachineAuth, qt.Equals, *nn[2].State)
|
c.Assert(ipn.NeedsMachineAuth, qt.Equals, *nn[2].State)
|
||||||
|
c.Assert(ipn.NeedsMachineAuth, qt.Equals, b.State())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Pretend that the administrator has authorized our machine.
|
// Pretend that the administrator has authorized our machine.
|
||||||
@ -502,7 +507,7 @@ func TestStateMachine(t *testing.T) {
|
|||||||
})
|
})
|
||||||
{
|
{
|
||||||
nn := notifies.drain(1)
|
nn := notifies.drain(1)
|
||||||
cc.assertCalls("unpause", "unpause", "unpause")
|
cc.assertCalls()
|
||||||
c.Assert(nn[0].State, qt.IsNotNil)
|
c.Assert(nn[0].State, qt.IsNotNil)
|
||||||
c.Assert(ipn.Starting, qt.Equals, *nn[0].State)
|
c.Assert(ipn.Starting, qt.Equals, *nn[0].State)
|
||||||
}
|
}
|
||||||
@ -543,7 +548,7 @@ func TestStateMachine(t *testing.T) {
|
|||||||
{
|
{
|
||||||
nn := notifies.drain(2)
|
nn := notifies.drain(2)
|
||||||
// BUG: Login isn't needed here. We never logged out.
|
// BUG: Login isn't needed here. We never logged out.
|
||||||
cc.assertCalls("Login", "unpause", "unpause")
|
cc.assertCalls("Login", "unpause")
|
||||||
// BUG: I would expect Prefs to change first, and state after.
|
// BUG: I would expect Prefs to change first, and state after.
|
||||||
c.Assert(nn[0].State, qt.IsNotNil)
|
c.Assert(nn[0].State, qt.IsNotNil)
|
||||||
c.Assert(nn[1].Prefs, qt.IsNotNil)
|
c.Assert(nn[1].Prefs, qt.IsNotNil)
|
||||||
@ -580,7 +585,7 @@ func TestStateMachine(t *testing.T) {
|
|||||||
b.Logout()
|
b.Logout()
|
||||||
{
|
{
|
||||||
nn := notifies.drain(2)
|
nn := notifies.drain(2)
|
||||||
cc.assertCalls("pause", "StartLogout", "pause")
|
cc.assertCalls("pause", "StartLogout")
|
||||||
c.Assert(nn[0].State, qt.IsNotNil)
|
c.Assert(nn[0].State, qt.IsNotNil)
|
||||||
c.Assert(nn[1].Prefs, qt.IsNotNil)
|
c.Assert(nn[1].Prefs, qt.IsNotNil)
|
||||||
c.Assert(ipn.Stopped, qt.Equals, *nn[0].State)
|
c.Assert(ipn.Stopped, qt.Equals, *nn[0].State)
|
||||||
@ -593,11 +598,11 @@ func TestStateMachine(t *testing.T) {
|
|||||||
// Let's make the logout succeed.
|
// Let's make the logout succeed.
|
||||||
t.Logf("\n\nLogout (async) - succeed")
|
t.Logf("\n\nLogout (async) - succeed")
|
||||||
notifies.expect(3)
|
notifies.expect(3)
|
||||||
cc.setAuthBlocked(true)
|
|
||||||
cc.send(nil, "", false, nil)
|
cc.send(nil, "", false, nil)
|
||||||
{
|
{
|
||||||
|
previousCC.assertShutdown(true)
|
||||||
nn := notifies.drain(3)
|
nn := notifies.drain(3)
|
||||||
cc.assertCalls("unpause", "unpause", "Shutdown", "unpause", "New", "unpause")
|
cc.assertCalls("New")
|
||||||
c.Assert(nn[0].State, qt.IsNotNil)
|
c.Assert(nn[0].State, qt.IsNotNil)
|
||||||
c.Assert(*nn[0].State, qt.Equals, ipn.NoState)
|
c.Assert(*nn[0].State, qt.Equals, ipn.NoState)
|
||||||
c.Assert(nn[1].Prefs, qt.IsNotNil) // emptyPrefs
|
c.Assert(nn[1].Prefs, qt.IsNotNil) // emptyPrefs
|
||||||
@ -617,7 +622,7 @@ func TestStateMachine(t *testing.T) {
|
|||||||
c.Assert(nn[0].Prefs, qt.IsNotNil) // emptyPrefs
|
c.Assert(nn[0].Prefs, qt.IsNotNil) // emptyPrefs
|
||||||
// BUG: the backend has already called StartLogout, and we're
|
// BUG: the backend has already called StartLogout, and we're
|
||||||
// still logged out. So it shouldn't call it again.
|
// still logged out. So it shouldn't call it again.
|
||||||
cc.assertCalls("StartLogout", "unpause")
|
cc.assertCalls("StartLogout")
|
||||||
cc.assertCalls()
|
cc.assertCalls()
|
||||||
c.Assert(b.Prefs().LoggedOut(), qt.IsTrue)
|
c.Assert(b.Prefs().LoggedOut(), qt.IsTrue)
|
||||||
c.Assert(b.Prefs().WantRunning(), qt.IsFalse)
|
c.Assert(b.Prefs().WantRunning(), qt.IsFalse)
|
||||||
@ -627,7 +632,6 @@ func TestStateMachine(t *testing.T) {
|
|||||||
// Let's acknowledge the second logout too.
|
// Let's acknowledge the second logout too.
|
||||||
t.Logf("\n\nLogout2 (async) - succeed")
|
t.Logf("\n\nLogout2 (async) - succeed")
|
||||||
notifies.expect(0)
|
notifies.expect(0)
|
||||||
cc.setAuthBlocked(true)
|
|
||||||
cc.send(nil, "", false, nil)
|
cc.send(nil, "", false, nil)
|
||||||
{
|
{
|
||||||
notifies.drain(0)
|
notifies.drain(0)
|
||||||
@ -645,7 +649,7 @@ func TestStateMachine(t *testing.T) {
|
|||||||
// I guess, since that's supposed to be synchronous.
|
// I guess, since that's supposed to be synchronous.
|
||||||
{
|
{
|
||||||
notifies.drain(0)
|
notifies.drain(0)
|
||||||
cc.assertCalls("Logout", "unpause")
|
cc.assertCalls("Logout")
|
||||||
c.Assert(b.Prefs().LoggedOut(), qt.IsTrue)
|
c.Assert(b.Prefs().LoggedOut(), qt.IsTrue)
|
||||||
c.Assert(b.Prefs().WantRunning(), qt.IsFalse)
|
c.Assert(b.Prefs().WantRunning(), qt.IsFalse)
|
||||||
c.Assert(ipn.NeedsLogin, qt.Equals, b.State())
|
c.Assert(ipn.NeedsLogin, qt.Equals, b.State())
|
||||||
@ -654,7 +658,6 @@ func TestStateMachine(t *testing.T) {
|
|||||||
// Generate the third logout event.
|
// Generate the third logout event.
|
||||||
t.Logf("\n\nLogout3 (sync) - succeed")
|
t.Logf("\n\nLogout3 (sync) - succeed")
|
||||||
notifies.expect(0)
|
notifies.expect(0)
|
||||||
cc.setAuthBlocked(true)
|
|
||||||
cc.send(nil, "", false, nil)
|
cc.send(nil, "", false, nil)
|
||||||
{
|
{
|
||||||
notifies.drain(0)
|
notifies.drain(0)
|
||||||
@ -676,9 +679,10 @@ func TestStateMachine(t *testing.T) {
|
|||||||
notifies.expect(2)
|
notifies.expect(2)
|
||||||
c.Assert(b.Start(ipn.Options{}), qt.IsNil)
|
c.Assert(b.Start(ipn.Options{}), qt.IsNil)
|
||||||
{
|
{
|
||||||
|
previousCC.assertShutdown(false)
|
||||||
// BUG: We already called Shutdown(), no need to do it again.
|
// BUG: We already called Shutdown(), no need to do it again.
|
||||||
// BUG: don't unpause because we're not logged in.
|
// BUG: don't unpause because we're not logged in.
|
||||||
cc.assertCalls("Shutdown", "unpause", "New", "unpause")
|
cc.assertCalls("New")
|
||||||
|
|
||||||
nn := notifies.drain(2)
|
nn := notifies.drain(2)
|
||||||
cc.assertCalls()
|
cc.assertCalls()
|
||||||
@ -693,14 +697,13 @@ func TestStateMachine(t *testing.T) {
|
|||||||
b.Login(nil)
|
b.Login(nil)
|
||||||
t.Logf("\n\nLoginFinished3")
|
t.Logf("\n\nLoginFinished3")
|
||||||
notifies.expect(3)
|
notifies.expect(3)
|
||||||
cc.setAuthBlocked(false)
|
|
||||||
cc.persist.LoginName = "user2"
|
cc.persist.LoginName = "user2"
|
||||||
cc.send(nil, "", true, &netmap.NetworkMap{
|
cc.send(nil, "", true, &netmap.NetworkMap{
|
||||||
MachineStatus: tailcfg.MachineAuthorized,
|
MachineStatus: tailcfg.MachineAuthorized,
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
nn := notifies.drain(3)
|
nn := notifies.drain(3)
|
||||||
cc.assertCalls("Login", "unpause", "unpause", "unpause")
|
cc.assertCalls("Login")
|
||||||
c.Assert(nn[0].LoginFinished, qt.IsNotNil)
|
c.Assert(nn[0].LoginFinished, qt.IsNotNil)
|
||||||
c.Assert(nn[1].Prefs, qt.IsNotNil)
|
c.Assert(nn[1].Prefs, qt.IsNotNil)
|
||||||
c.Assert(nn[1].Prefs.Persist(), qt.IsNotNil)
|
c.Assert(nn[1].Prefs.Persist(), qt.IsNotNil)
|
||||||
@ -736,12 +739,14 @@ func TestStateMachine(t *testing.T) {
|
|||||||
{
|
{
|
||||||
// NOTE: cc.Shutdown() is correct here, since we didn't call
|
// NOTE: cc.Shutdown() is correct here, since we didn't call
|
||||||
// b.Shutdown() explicitly ourselves.
|
// b.Shutdown() explicitly ourselves.
|
||||||
|
previousCC.assertShutdown(false)
|
||||||
|
|
||||||
// Note: unpause happens because ipn needs to get at least one netmap
|
// Note: unpause happens because ipn needs to get at least one netmap
|
||||||
// on startup, otherwise UIs can't show the node list, login
|
// on startup, otherwise UIs can't show the node list, login
|
||||||
// name, etc when in state ipn.Stopped.
|
// name, etc when in state ipn.Stopped.
|
||||||
// Arguably they shouldn't try. But they currently do.
|
// Arguably they shouldn't try. But they currently do.
|
||||||
nn := notifies.drain(2)
|
nn := notifies.drain(2)
|
||||||
cc.assertCalls("Shutdown", "unpause", "New", "Login", "unpause")
|
cc.assertCalls("New", "Login")
|
||||||
c.Assert(nn[0].Prefs, qt.IsNotNil)
|
c.Assert(nn[0].Prefs, qt.IsNotNil)
|
||||||
c.Assert(nn[1].State, qt.IsNotNil)
|
c.Assert(nn[1].State, qt.IsNotNil)
|
||||||
c.Assert(nn[0].Prefs.WantRunning(), qt.IsFalse)
|
c.Assert(nn[0].Prefs.WantRunning(), qt.IsFalse)
|
||||||
@ -765,7 +770,7 @@ func TestStateMachine(t *testing.T) {
|
|||||||
})
|
})
|
||||||
{
|
{
|
||||||
notifies.drain(0)
|
notifies.drain(0)
|
||||||
cc.assertCalls("pause", "pause")
|
cc.assertCalls("pause")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Request connection.
|
// Request connection.
|
||||||
@ -778,7 +783,7 @@ func TestStateMachine(t *testing.T) {
|
|||||||
})
|
})
|
||||||
{
|
{
|
||||||
nn := notifies.drain(2)
|
nn := notifies.drain(2)
|
||||||
cc.assertCalls("Login", "unpause", "unpause")
|
cc.assertCalls("Login", "unpause")
|
||||||
// BUG: I would expect Prefs to change first, and state after.
|
// BUG: I would expect Prefs to change first, and state after.
|
||||||
c.Assert(nn[0].State, qt.IsNotNil)
|
c.Assert(nn[0].State, qt.IsNotNil)
|
||||||
c.Assert(nn[1].Prefs, qt.IsNotNil)
|
c.Assert(nn[1].Prefs, qt.IsNotNil)
|
||||||
@ -817,7 +822,7 @@ func TestStateMachine(t *testing.T) {
|
|||||||
//
|
//
|
||||||
// Because the login hasn't yet completed, the old login
|
// Because the login hasn't yet completed, the old login
|
||||||
// is still valid, so it's correct that we stay paused.
|
// is still valid, so it's correct that we stay paused.
|
||||||
cc.assertCalls("Login", "pause", "pause")
|
cc.assertCalls("Login")
|
||||||
c.Assert(nn[0].BrowseToURL, qt.IsNotNil)
|
c.Assert(nn[0].BrowseToURL, qt.IsNotNil)
|
||||||
c.Assert(*nn[0].BrowseToURL, qt.Equals, url3)
|
c.Assert(*nn[0].BrowseToURL, qt.Equals, url3)
|
||||||
}
|
}
|
||||||
@ -839,7 +844,7 @@ func TestStateMachine(t *testing.T) {
|
|||||||
// and !WantRunning. But since it's a fresh and successful
|
// and !WantRunning. But since it's a fresh and successful
|
||||||
// new login, WantRunning is true, so there was never a
|
// new login, WantRunning is true, so there was never a
|
||||||
// reason to pause().
|
// reason to pause().
|
||||||
cc.assertCalls("pause", "unpause", "unpause")
|
cc.assertCalls("unpause")
|
||||||
c.Assert(nn[0].LoginFinished, qt.IsNotNil)
|
c.Assert(nn[0].LoginFinished, qt.IsNotNil)
|
||||||
c.Assert(nn[1].Prefs, qt.IsNotNil)
|
c.Assert(nn[1].Prefs, qt.IsNotNil)
|
||||||
c.Assert(nn[2].State, qt.IsNotNil)
|
c.Assert(nn[2].State, qt.IsNotNil)
|
||||||
@ -853,35 +858,35 @@ func TestStateMachine(t *testing.T) {
|
|||||||
// The last test case is the most common one: restarting when both
|
// The last test case is the most common one: restarting when both
|
||||||
// logged in and WantRunning.
|
// logged in and WantRunning.
|
||||||
t.Logf("\n\nStart5")
|
t.Logf("\n\nStart5")
|
||||||
notifies.expect(1)
|
notifies.expect(2)
|
||||||
c.Assert(b.Start(ipn.Options{}), qt.IsNil)
|
c.Assert(b.Start(ipn.Options{}), qt.IsNil)
|
||||||
{
|
{
|
||||||
// NOTE: cc.Shutdown() is correct here, since we didn't call
|
// NOTE: cc.Shutdown() is correct here, since we didn't call
|
||||||
// b.Shutdown() ourselves.
|
// b.Shutdown() ourselves.
|
||||||
cc.assertCalls("Shutdown", "unpause", "New", "Login", "unpause")
|
previousCC.assertShutdown(false)
|
||||||
|
cc.assertCalls("New", "Login")
|
||||||
|
|
||||||
nn := notifies.drain(1)
|
nn := notifies.drain(2)
|
||||||
cc.assertCalls()
|
cc.assertCalls()
|
||||||
c.Assert(nn[0].Prefs, qt.IsNotNil)
|
c.Assert(nn[0].Prefs, qt.IsNotNil)
|
||||||
c.Assert(nn[0].Prefs.LoggedOut(), qt.IsFalse)
|
c.Assert(nn[0].Prefs.LoggedOut(), qt.IsFalse)
|
||||||
c.Assert(nn[0].Prefs.WantRunning(), qt.IsTrue)
|
c.Assert(nn[0].Prefs.WantRunning(), qt.IsTrue)
|
||||||
c.Assert(ipn.NoState, qt.Equals, b.State())
|
c.Assert(ipn.NeedsLogin, qt.Equals, b.State())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Control server accepts our valid key from before.
|
// Control server accepts our valid key from before.
|
||||||
t.Logf("\n\nLoginFinished5")
|
t.Logf("\n\nLoginFinished5")
|
||||||
notifies.expect(1)
|
notifies.expect(2)
|
||||||
cc.setAuthBlocked(false)
|
|
||||||
cc.send(nil, "", true, &netmap.NetworkMap{
|
cc.send(nil, "", true, &netmap.NetworkMap{
|
||||||
MachineStatus: tailcfg.MachineAuthorized,
|
MachineStatus: tailcfg.MachineAuthorized,
|
||||||
})
|
})
|
||||||
{
|
{
|
||||||
nn := notifies.drain(1)
|
nn := notifies.drain(2)
|
||||||
cc.assertCalls("unpause", "unpause", "unpause")
|
cc.assertCalls()
|
||||||
// NOTE: No LoginFinished message since no interactive
|
// NOTE: No LoginFinished message since no interactive
|
||||||
// login was needed.
|
// login was needed.
|
||||||
c.Assert(nn[0].State, qt.IsNotNil)
|
c.Assert(nn[1].State, qt.IsNotNil)
|
||||||
c.Assert(ipn.Starting, qt.Equals, *nn[0].State)
|
c.Assert(ipn.Starting, qt.Equals, *nn[1].State)
|
||||||
// NOTE: No prefs change this time. WantRunning stays true.
|
// NOTE: No prefs change this time. WantRunning stays true.
|
||||||
// We were in Starting in the first place, so that doesn't
|
// We were in Starting in the first place, so that doesn't
|
||||||
// change either.
|
// change either.
|
||||||
@ -895,7 +900,7 @@ func TestStateMachine(t *testing.T) {
|
|||||||
})
|
})
|
||||||
{
|
{
|
||||||
nn := notifies.drain(1)
|
nn := notifies.drain(1)
|
||||||
cc.assertCalls("unpause", "unpause")
|
cc.assertCalls()
|
||||||
c.Assert(nn[0].State, qt.IsNotNil)
|
c.Assert(nn[0].State, qt.IsNotNil)
|
||||||
c.Assert(ipn.NeedsLogin, qt.Equals, *nn[0].State)
|
c.Assert(ipn.NeedsLogin, qt.Equals, *nn[0].State)
|
||||||
c.Assert(ipn.NeedsLogin, qt.Equals, b.State())
|
c.Assert(ipn.NeedsLogin, qt.Equals, b.State())
|
||||||
@ -910,7 +915,7 @@ func TestStateMachine(t *testing.T) {
|
|||||||
})
|
})
|
||||||
{
|
{
|
||||||
nn := notifies.drain(1)
|
nn := notifies.drain(1)
|
||||||
cc.assertCalls("unpause", "unpause", "unpause")
|
cc.assertCalls()
|
||||||
c.Assert(nn[0].State, qt.IsNotNil)
|
c.Assert(nn[0].State, qt.IsNotNil)
|
||||||
c.Assert(ipn.Starting, qt.Equals, *nn[0].State)
|
c.Assert(ipn.Starting, qt.Equals, *nn[0].State)
|
||||||
c.Assert(ipn.Starting, qt.Equals, b.State())
|
c.Assert(ipn.Starting, qt.Equals, b.State())
|
||||||
@ -921,7 +926,7 @@ func TestStateMachine(t *testing.T) {
|
|||||||
b.setWgengineStatus(&wgengine.Status{DERPs: 1, AsOf: time.Now()}, nil)
|
b.setWgengineStatus(&wgengine.Status{DERPs: 1, AsOf: time.Now()}, nil)
|
||||||
{
|
{
|
||||||
nn := notifies.drain(1)
|
nn := notifies.drain(1)
|
||||||
cc.assertCalls("unpause")
|
cc.assertCalls()
|
||||||
c.Assert(nn[0].State, qt.IsNotNil)
|
c.Assert(nn[0].State, qt.IsNotNil)
|
||||||
c.Assert(ipn.Running, qt.Equals, *nn[0].State)
|
c.Assert(ipn.Running, qt.Equals, *nn[0].State)
|
||||||
c.Assert(ipn.Running, qt.Equals, b.State())
|
c.Assert(ipn.Running, qt.Equals, b.State())
|
||||||
@ -1017,11 +1022,9 @@ func TestWGEngineStatusRace(t *testing.T) {
|
|||||||
b, err := NewLocalBackend(logf, "logid", new(mem.Store), "", nil, eng, 0)
|
b, err := NewLocalBackend(logf, "logid", new(mem.Store), "", nil, eng, 0)
|
||||||
c.Assert(err, qt.IsNil)
|
c.Assert(err, qt.IsNil)
|
||||||
|
|
||||||
cc := newMockControl(t)
|
var cc *mockControl
|
||||||
b.SetControlClientGetterForTesting(func(opts controlclient.Options) (controlclient.Client, error) {
|
b.SetControlClientGetterForTesting(func(opts controlclient.Options) (controlclient.Client, error) {
|
||||||
cc.mu.Lock()
|
cc = newClient(t, opts)
|
||||||
defer cc.mu.Unlock()
|
|
||||||
cc.logfActual = opts.Logf
|
|
||||||
return cc, nil
|
return cc, nil
|
||||||
})
|
})
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user