cmd/coordinator, internal/coordinator/remote: add gomote instances to status

This adds the gomote instances to the status page presented at
farmer.golang.org.

Updates golang/go#52594
For golang/go#47521

Change-Id: I29c73262031fc95cc85cdb43734da49149c958b3
Reviewed-on: https://go-review.googlesource.com/c/build/+/405258
Reviewed-by: Dmitri Shuralyov <dmitshur@golang.org>
Run-TryBot: Carlos Amedee <carlos@golang.org>
Auto-Submit: Carlos Amedee <carlos@golang.org>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Dmitri Shuralyov <dmitshur@google.com>
Reviewed-by: Alex Rakoczy <alex@golang.org>
diff --git a/cmd/coordinator/coordinator.go b/cmd/coordinator/coordinator.go
index 9354c67..b6a4d42 100644
--- a/cmd/coordinator/coordinator.go
+++ b/cmd/coordinator/coordinator.go
@@ -361,6 +361,7 @@
 	dashV2 := &builddash.Handler{Datastore: gce.GoDSClient(), Maintner: maintnerClient}
 	gs := &gRPCServer{dashboardURL: "https://build.golang.org"}
 	sp := remote.NewSessionPool(context.Background())
+	setSessionPool(sp)
 	gomoteServer := gomote.New(sp, sched, sshCA, gomoteBucket, mustStorageClient())
 	protos.RegisterCoordinatorServer(grpcServer, gs)
 	gomoteprotos.RegisterGomoteServiceServer(grpcServer, gomoteServer)
diff --git a/cmd/coordinator/status.go b/cmd/coordinator/status.go
index 20c75de..b109899 100644
--- a/cmd/coordinator/status.go
+++ b/cmd/coordinator/status.go
@@ -36,6 +36,7 @@
 	"golang.org/x/build/cmd/coordinator/internal"
 	"golang.org/x/build/dashboard"
 	"golang.org/x/build/internal/coordinator/pool"
+	"golang.org/x/build/internal/coordinator/remote"
 	"golang.org/x/build/internal/coordinator/schedule"
 	"golang.org/x/build/internal/secret"
 	"golang.org/x/build/kubernetes/api"
@@ -688,6 +689,7 @@
 
 	gce := pool.NewGCEConfiguration()
 	data.RemoteBuildlets = template.HTML(remoteBuildletStatus())
+	data.GomoteInstances = remoteSessionStatus()
 
 	sort.Sort(byAge(data.Active))
 	sort.Sort(byAge(data.Pending))
@@ -784,6 +786,7 @@
 	KubePoolStatus    template.HTML // TODO: embed template
 	ReversePoolStatus template.HTML // TODO: embed template
 	RemoteBuildlets   template.HTML
+	GomoteInstances   template.HTML
 	SchedState        schedule.SchedulerState
 	DiskFree          string
 	Version           string
@@ -826,6 +829,9 @@
 <h2 id=remote>Remote buildlets <a href='#remote'>¶</a></h2>
 {{.RemoteBuildlets}}
 
+<h2 id=gomote>Gomote Remote buildlets <a href='#gomote'>¶</a></h2>
+{{.GomoteInstances}}
+
 <h2 id=trybots>Active Trybot Runs <a href='#trybots'>¶</a></h2>
 {{- if .TrybotsErr}}
 <b>trybots disabled:</b>: {{.TrybotsErr}}
@@ -900,3 +906,28 @@
 func handleStyleCSS(w http.ResponseWriter, r *http.Request) {
 	http.ServeContent(w, r, "style.css", processStartTime, bytes.NewReader(styleCSS))
 }
+
+// statusSessionPool to be used exclusively in the status file.
+var statusSessionPool *remote.SessionPool
+
+// setSessionPool sets the session pool for use in the status file.
+func setSessionPool(sp *remote.SessionPool) {
+	statusSessionPool = sp
+}
+
+// remoteSessionStatus creates the status HTML for the sessions in the session pool.
+func remoteSessionStatus() template.HTML {
+	sessions := statusSessionPool.List()
+	if len(sessions) == 0 {
+		return "<i>(none)</i>"
+	}
+	var buf bytes.Buffer
+	buf.WriteString("<ul>")
+	for _, s := range sessions {
+		fmt.Fprintf(&buf, "<li><b>%s</b>, created %v ago, expires in %v</li>\n",
+			html.EscapeString(s.ID),
+			time.Since(s.Created), time.Until(s.Expires))
+	}
+	buf.WriteString("</ul>")
+	return template.HTML(buf.String())
+}
diff --git a/cmd/coordinator/status_test.go b/cmd/coordinator/status_test.go
index 6e5cb3d..880b221 100644
--- a/cmd/coordinator/status_test.go
+++ b/cmd/coordinator/status_test.go
@@ -18,6 +18,7 @@
 	"testing"
 	"time"
 
+	"golang.org/x/build/internal/coordinator/remote"
 	"golang.org/x/build/internal/coordinator/schedule"
 )
 
@@ -77,6 +78,7 @@
 
 	rec := httptest.NewRecorder()
 	req := httptest.NewRequest("GET", "/", nil)
+	setSessionPool(remote.NewSessionPool(ctx))
 	handleStatus(rec, req)
 	const pre = "<h2 id=health>Health"
 	const suf = "<h2 id=trybots>Active Trybot Runs"
diff --git a/internal/coordinator/remote/remote.go b/internal/coordinator/remote/remote.go
index 676c97b..6aa538a 100644
--- a/internal/coordinator/remote/remote.go
+++ b/internal/coordinator/remote/remote.go
@@ -24,12 +24,12 @@
 // Session stores the metadata for a remote buildlet Session.
 type Session struct {
 	BuilderType string // default builder config to use if not overwritten
+	Created     time.Time
 	Expires     time.Time
 	HostType    string
 	ID          string // unique identifier for instance "user-bradfitz-linux-amd64-0"
 	OwnerID     string // identity aware proxy user id: "accounts.google.com:userIDvalue"
 	buildlet    buildlet.Client
-	created     time.Time
 }
 
 // renew extends the expiration timestamp for a session.
@@ -85,7 +85,7 @@
 			sp.m[name] = &Session{
 				BuilderType: builderType,
 				buildlet:    bc,
-				created:     now,
+				Created:     now,
 				Expires:     now.Add(remoteBuildletIdleTimeout),
 				HostType:    hostType,
 				ID:          name,
@@ -167,6 +167,7 @@
 			HostType:    s.HostType,
 			ID:          s.ID,
 			OwnerID:     s.OwnerID,
+			Created:     s.Created,
 		})
 	}
 	sort.Slice(ss, func(i, j int) bool { return ss[i].ID < ss[j].ID })