[sk8s] Fix setting the maintenance mode in bot_config.

Change-Id: I917086191cc7c210ff942f08931b2b6bb517d286
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/306898
Commit-Queue: Joe Gregorio <jcgregorio@google.com>
Reviewed-by: Ravi Mistry <rmistry@google.com>
diff --git a/sk8s/go/bot_config/machine/machine.go b/sk8s/go/bot_config/machine/machine.go
index 64d1db7..6b32415 100644
--- a/sk8s/go/bot_config/machine/machine.go
+++ b/sk8s/go/bot_config/machine/machine.go
@@ -156,16 +156,20 @@
 		}
 	})
 
-	// Also start a second loop that does a firestore onsnapshot watcher that gets the dims we should
-	// be reporting to swarming.
+	m.startStoreWatch(ctx)
+	return nil
+}
+
+// startStoreWatch starts a loop that does a firestore onsnapshot watcher
+// that gets the dims and state we should be reporting to swarming.
+func (m *Machine) startStoreWatch(ctx context.Context) {
 	go func() {
 		for desc := range m.store.Watch(ctx, m.MachineID) {
 			m.storeWatchArrivalCounter.Inc(1)
 			m.SetDimensionsForSwarming(desc.Dimensions)
+			m.SetMaintenanceMode(desc.Mode == machine.ModeRecovery || desc.Mode == machine.ModeMaintenance)
 		}
 	}()
-
-	return nil
 }
 
 // SetDimensionsForSwarming sets the dimensions that should be reported to swarming. Should only
diff --git a/sk8s/go/bot_config/machine/machine_test.go b/sk8s/go/bot_config/machine/machine_test.go
index af81da9..487b811 100644
--- a/sk8s/go/bot_config/machine/machine_test.go
+++ b/sk8s/go/bot_config/machine/machine_test.go
@@ -162,10 +162,77 @@
 	assert.Equal(t, int64(1), m.storeWatchArrivalCounter.Get())
 	assert.Equal(t, int64(0), m.interrogateAndSendFailures.Get())
 
-	// Confirm the firestore write make it all the way to Dims().
+	// Confirm the firestore write made it all the way to Dims().
 	assert.Equal(t, machine.SwarmingDimensions{"foo": {"bar"}}, m.DimensionsForSwarming())
 }
 
+func TestStart_FirestoreWritesGetReflectedToMachine(t *testing.T) {
+	// Manual because we are testing pubsub.
+	unittest.ManualTest(t)
+
+	ctx, _, instanceConfig := setupConfig(t)
+	ctx, cancel := context.WithCancel(ctx)
+
+	// Set the SWARMING_BOT_ID env variable.
+	oldVar := os.Getenv(swarming.SwarmingBotIDEnvVar)
+	err := os.Setenv(swarming.SwarmingBotIDEnvVar, "my-test-bot-001")
+	require.NoError(t, err)
+	defer func() {
+		err = os.Setenv(swarming.SwarmingBotIDEnvVar, oldVar)
+		require.NoError(t, err)
+	}()
+
+	const imageName = "gcr.io/skia-public/rpi-swarming-client:2020-05-09T19_28_20Z-jcgregorio-4fef3ca-clean"
+
+	// Set the IMAGE_NAME env variable.
+	oldImageVar := os.Getenv(swarming.KubernetesImageEnvVar)
+	err = os.Setenv(swarming.KubernetesImageEnvVar, imageName)
+	require.NoError(t, err)
+	defer func() {
+		err = os.Setenv(swarming.KubernetesImageEnvVar, oldImageVar)
+		require.NoError(t, err)
+	}()
+
+	// Create a Machine instance.
+	start := time.Date(2020, time.May, 1, 0, 0, 0, 0, time.UTC)
+	m, err := New(ctx, true, instanceConfig, start)
+	require.NoError(t, err)
+	assert.Equal(t, "my-test-bot-001", m.MachineID)
+
+	assert.False(t, m.GetMaintenanceMode())
+
+	// Start just the Firestore watcher.
+	m.startStoreWatch(ctx)
+
+	// Write a description into firestore. We expect the dimensions and mode to
+	// bubble down to the machine
+	err = m.store.Update(ctx, "my-test-bot-001", func(machine.Description) machine.Description {
+		ret := machine.NewDescription()
+		ret.Mode = machine.ModeMaintenance
+		ret.Dimensions["foo"] = []string{"bar"}
+		return ret
+	})
+	require.NoError(t, err)
+
+	assert.Equal(t, machine.SwarmingDimensions{"foo": {"bar"}}, m.DimensionsForSwarming())
+	assert.True(t, m.GetMaintenanceMode())
+
+	// Now change the mode.
+	err = m.store.Update(ctx, "my-test-bot-001", func(machine.Description) machine.Description {
+		ret := machine.NewDescription()
+		ret.Mode = machine.ModeAvailable
+		return ret
+	})
+	require.NoError(t, err)
+
+	// Confirm we go out of maintenance mode.
+	assert.False(t, m.GetMaintenanceMode())
+
+	// Cancel Go routine inside startStoreWatch.
+	cancel()
+
+}
+
 func Test_FakeExe_AdbShellGetProp_Success(t *testing.T) {
 	unittest.FakeExeTest(t)
 	if os.Getenv(executil.OverrideEnvironmentVariable) == "" {
diff --git a/sk8s/go/bot_config/server/server_test.go b/sk8s/go/bot_config/server/server_test.go
index 1eec30b..e0a2a86 100644
--- a/sk8s/go/bot_config/server/server_test.go
+++ b/sk8s/go/bot_config/server/server_test.go
@@ -55,6 +55,34 @@
 	require.NoError(t, err)
 	assert.Equal(t, someRackName, dict["sk_rack"])
 	assert.Equal(t, "bar", dict["foo"])
+	_, ok := dict["maintenance"]
+	assert.False(t, ok)
+}
+
+func TestGetState_MaintenanceAppearsInStateResponse(t *testing.T) {
+	unittest.SmallTest(t)
+
+	const someRackName = "some-rack-name"
+
+	err := os.Setenv("MY_RACK_NAME", someRackName)
+	require.NoError(t, err)
+
+	r := httptest.NewRequest("POST", "/get_state", strings.NewReader("{\"foo\":\"bar\"}"))
+
+	m := &botmachine.Machine{}
+	m.SetMaintenanceMode(true)
+	s, err := New(m)
+	require.NoError(t, err)
+	w := httptest.NewRecorder()
+
+	s.getState(w, r)
+
+	res := w.Result()
+	assert.Equal(t, 200, res.StatusCode)
+	var dict map[string]interface{}
+	err = json.NewDecoder(res.Body).Decode(&dict)
+	require.NoError(t, err)
+	assert.True(t, dict["maintenance"].(bool))
 }
 
 func TestGetState_ErrOnInvalidJSON(t *testing.T) {