overlord: eliminate the use of browser_id

In the previous commit we allow clients to send terminal control message
to the frontend browser, which passes terminal session ID to frontend.
We can now use this session ID as subscripbtion channel without having
an extra browser ID.

BUG=none
TEST=manually test upload and download function

Change-Id: Id59318086d496916ec8c3f89d97558318816efbc
Reviewed-on: https://chromium-review.googlesource.com/291384
Reviewed-by: Hsu Wei-Cheng <mojahsu@chromium.org>
Commit-Queue: Wei-Ning Huang <wnhuang@chromium.org>
Tested-by: Wei-Ning Huang <wnhuang@chromium.org>
diff --git a/go/src/overlord/app/dashboard/js/view.jsx b/go/src/overlord/app/dashboard/js/view.jsx
index 8f6fd69..7508771 100644
--- a/go/src/overlord/app/dashboard/js/view.jsx
+++ b/go/src/overlord/app/dashboard/js/view.jsx
@@ -91,6 +91,7 @@
 
     var socket = io(window.location.protocol + "//" + window.location.host,
                     {path: "/api/socket.io/"});
+    this.socket = socket;
 
     socket.on("agent joined", function (msg) {
       // Add to recent client list
@@ -244,8 +245,15 @@
 
 var TerminalGroup = React.createClass({
   render: function () {
+    var onControl = function (control) {
+      if (control.type == "sid") {
+        this.terminal_sid = control.data;
+        this.props.app.socket.emit("subscribe", control.data);
+      }
+    };
     var onClose = function (e) {
       this.props.app.removeTerminal(this.props.mid);
+      this.props.app.socket.emit("unsubscribe", this.terminal_sid);
     };
     return (
       <div>
@@ -258,7 +266,7 @@
                  path={"/api/agent/pty/" + item.mid}
                  uploadPath={"/api/agent/upload/" + item.mid}
                  app={this.props.app} progressBars={this.refs.uploadProgress}
-                 onClose={onClose} />
+                 onControl={onControl} onClose={onClose} />
               );
             }.bind(this))
           }
diff --git a/go/src/overlord/conn_server.go b/go/src/overlord/conn_server.go
index 7f812e0..1901d8e 100644
--- a/go/src/overlord/conn_server.go
+++ b/go/src/overlord/conn_server.go
@@ -51,7 +51,7 @@
 	Bridge        chan interface{}       // Channel for overlord command
 	Sid           string                 // Session ID
 	Mid           string                 // Machine ID
-	Bid           string                 // Associated Browser ID
+	TerminalSid   string                 // Associated terminal session ID
 	Properties    map[string]interface{} // Client properties
 	TargetSSHPort int                    // Target SSH port for forwarding
 	ovl           *Overlord              // Overlord handle
@@ -198,7 +198,7 @@
 	log.Printf("Received %T command from overlord\n", obj)
 	switch v := obj.(type) {
 	case SpawnTerminalCmd:
-		self.SpawnTerminal(v.Sid, v.Bid)
+		self.SpawnTerminal(v.Sid)
 	case SpawnShellCmd:
 		self.SpawnShell(v.Sid, v.Command)
 	case ConnectLogcatCmd:
@@ -391,9 +391,9 @@
 
 func (self *ConnServer) handleDownloadRequest(req *Request) error {
 	type RequestArgs struct {
-		Bid      string `json:"bid"`
-		Filename string `json:"filename"`
-		Size     int64  `json:"size"`
+		TerminalSid string `json:"terminal_sid"`
+		Filename    string `json:"filename"`
+		Size        int64  `json:"size"`
 	}
 
 	var args RequestArgs
@@ -402,7 +402,7 @@
 	}
 
 	self.Download.Ready = true
-	self.Bid = args.Bid
+	self.TerminalSid = args.TerminalSid
 	self.Download.Name = args.Filename
 	self.Download.Size = args.Size
 
@@ -445,8 +445,7 @@
 
 // Spawn a remote terminal connection (a ghost with mode TERMINAL).
 // sid is the session ID, which will be used as the session ID of the new ghost.
-// bid is the browser ID, which identify the browser which started the terminal.
-func (self *ConnServer) SpawnTerminal(sid, bid string) {
+func (self *ConnServer) SpawnTerminal(sid string) {
 	handler := func(res *Response) error {
 		if res == nil {
 			return errors.New("SpawnTerminal: command timeout")
@@ -458,7 +457,7 @@
 		return nil
 	}
 
-	req := NewRequest("terminal", map[string]interface{}{"sid": sid, "bid": bid})
+	req := NewRequest("terminal", map[string]interface{}{"sid": sid})
 	self.SendRequest(req, handler)
 }
 
diff --git a/go/src/overlord/ghost.go b/go/src/overlord/ghost.go
index e746409..b0fd14c 100644
--- a/go/src/overlord/ghost.go
+++ b/go/src/overlord/ghost.go
@@ -67,13 +67,13 @@
 type Ghost struct {
 	*RPCCore
 	addrs           []string               // List of possible Overlord addresses
-	ttyName2Bid     map[string]string      // Mapping between ttyName and bid
+	ttyName2Sid     map[string]string      // Mapping between ttyName and Sid
 	terminalSid2Pid map[string]int         // Mapping between terminalSid and pid
 	server          *rpc.Server            // RPC server handle
 	connectedAddr   string                 // Current connected Overlord address
 	mid             string                 // Machine ID
 	sid             string                 // Session ID
-	bid             string                 // Browser ID
+	terminalSid     string                 // Associated terminal session ID
 	mode            int                    // mode, see constants.go
 	properties      map[string]interface{} // Client properties
 	reset           bool                   // Whether to reset the connection
@@ -103,7 +103,7 @@
 	}
 	return &Ghost{
 		RPCCore:         NewRPCCore(nil),
-		ttyName2Bid:     make(map[string]string),
+		ttyName2Sid:     make(map[string]string),
 		terminalSid2Pid: make(map[string]int),
 		addrs:           addrs,
 		mid:             finalMid,
@@ -123,8 +123,8 @@
 	return self
 }
 
-func (self *Ghost) SetBid(bid string) *Ghost {
-	self.bid = bid
+func (self *Ghost) SetTerminalSid(sid string) *Ghost {
+	self.terminalSid = sid
 	return self
 }
 
@@ -237,7 +237,6 @@
 func (self *Ghost) handleTerminalRequest(req *Request) error {
 	type RequestParams struct {
 		Sid string `json:"sid"`
-		Bid string `json:"bid"`
 	}
 
 	var params RequestParams
@@ -250,7 +249,7 @@
 		addrs := []string{self.connectedAddr}
 		// Terminal sessions are identified with session ID, thus we don't care
 		// machine ID and can make them random.
-		g := NewGhost(addrs, TERMINAL, RANDOM_MID).SetSid(params.Sid).SetBid(params.Bid)
+		g := NewGhost(addrs, TERMINAL, RANDOM_MID).SetSid(params.Sid)
 		g.Start(false, false)
 	}()
 
@@ -514,7 +513,7 @@
 		log.Println("SpawnPTYServer: terminated")
 	}()
 
-	// Register the mapping of browser_id and ttyName
+	// Register the mapping of sid and ttyName
 	ttyName, err := PtsName(tty)
 	if err != nil {
 		return err
@@ -524,7 +523,7 @@
 
 	// Ghost could be launched without RPC server, ignore registraion in that case
 	if err == nil {
-		err = client.Call("rpc.RegisterTTY", []string{self.bid, ttyName},
+		err = client.Call("rpc.RegisterTTY", []string{self.sid, ttyName},
 			&EmptyReply{})
 		if err != nil {
 			return err
@@ -681,9 +680,9 @@
 		}
 
 		req := NewRequest("request_to_download", map[string]interface{}{
-			"bid":      self.bid,
-			"filename": filepath.Base(self.fileOperation.Filename),
-			"size":     fi.Size(),
+			"terminal_sid": self.terminalSid,
+			"filename":     filepath.Base(self.fileOperation.Filename),
+			"size":         fi.Size(),
 		})
 
 		return self.SendRequest(req, nil)
@@ -752,8 +751,8 @@
 // Initiate a client-side download request
 func (self *Ghost) InitiateDownload(info DownloadInfo) {
 	addrs := []string{self.connectedAddr}
-	g := NewGhost(addrs, FILE, RANDOM_MID).SetBid(
-		self.ttyName2Bid[info.Ttyname]).SetFileOp("download", info.Filename, 0)
+	g := NewGhost(addrs, FILE, RANDOM_MID).SetTerminalSid(
+		self.ttyName2Sid[info.Ttyname]).SetFileOp("download", info.Filename, 0)
 	g.Start(false, false)
 }
 
@@ -823,8 +822,8 @@
 	}
 }
 
-func (self *Ghost) RegisterTTY(brower_id, ttyName string) {
-	self.ttyName2Bid[ttyName] = brower_id
+func (self *Ghost) RegisterTTY(session_id, ttyName string) {
+	self.ttyName2Sid[ttyName] = session_id
 }
 
 func (self *Ghost) RegisterSession(session_id, pidStr string) {
diff --git a/go/src/overlord/overlord.go b/go/src/overlord/overlord.go
index 2ac5e50..65740b6 100644
--- a/go/src/overlord/overlord.go
+++ b/go/src/overlord/overlord.go
@@ -35,7 +35,6 @@
 
 type SpawnTerminalCmd struct {
 	Sid string // Session ID
-	Bid string // Browser ID
 }
 
 type SpawnShellCmd struct {
@@ -206,7 +205,7 @@
 func (self *Overlord) RegisterDownloadRequest(conn *ConnServer) {
 	// Use session ID as download session ID instead of machine ID, so a machine
 	// can have multiple download at the same time
-	self.ioserver.BroadcastTo(conn.Bid, "file download", string(conn.Sid))
+	self.ioserver.BroadcastTo(conn.TerminalSid, "file download", string(conn.Sid))
 	self.downloads[conn.Sid] = conn
 }
 
@@ -214,7 +213,7 @@
 func (self *Overlord) RegisterUploadRequest(conn *ConnServer) {
 	// Use session ID as upload session ID instead of machine ID, so a machine
 	// can have multiple upload at the same time
-	self.ioserver.BroadcastTo(conn.Bid, "file upload", string(conn.Sid))
+	self.ioserver.BroadcastTo(conn.TerminalSid, "file upload", string(conn.Sid))
 	self.uploads[conn.Sid] = conn
 }
 
@@ -267,11 +266,6 @@
 	}
 
 	server.On("connection", func(so socketio.Socket) {
-		r := so.Request()
-		bid, err := r.Cookie("browser_id")
-		if err == nil {
-			so.Join(bid.Value)
-		}
 		so.Join("monitor")
 	})
 
@@ -279,6 +273,16 @@
 		log.Println("error:", err)
 	})
 
+	// Client initiated subscribtion
+	server.On("subscribe", func(so socketio.Socket, name string) {
+		so.Join(name)
+	})
+
+	// Client initiated unsubscribtion
+	server.On("unsubscribe", func(so socketio.Socket, name string) {
+		so.Leave(name)
+	})
+
 	self.ioserver = server
 }
 
@@ -335,8 +339,8 @@
 
 type ByMid []map[string]interface{}
 
-func (a ByMid) Len() int           { return len(a) }
-func (a ByMid) Swap(i, j int)      { a[i], a[j] = a[j], a[i] }
+func (a ByMid) Len() int      { return len(a) }
+func (a ByMid) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
 func (a ByMid) Less(i, j int) bool {
 	return a[i]["mid"].(string) < a[j]["mid"].(string)
 }
@@ -361,19 +365,6 @@
 		ws.Close()
 	}
 
-	IndexHandler := func(w http.ResponseWriter, r *http.Request) {
-		handler := http.FileServer(http.Dir(filepath.Join(appDir, "dashboard")))
-		cookie, err := r.Cookie("browser_id")
-		if err != nil {
-			cookie = &http.Cookie{
-				Name:  "browser_id",
-				Value: uuid.NewV4().String(),
-			}
-			http.SetCookie(w, cookie)
-		}
-		handler.ServeHTTP(w, r)
-	}
-
 	// List all apps available on Overlord.
 	AppsListHandler := func(w http.ResponseWriter, r *http.Request) {
 		w.Header().Set("Content-Type", "application/json")
@@ -493,19 +484,12 @@
 			return
 		}
 
-		cookie, err := r.Cookie("browser_id")
-		if err != nil {
-			WebSocketSendError(conn, "No browser ID associated")
-			return
-		}
-		bid := cookie.Value
-
 		vars := mux.Vars(r)
 		mid := vars["mid"]
 		if agent, ok := self.agents[mid]; ok {
 			wc := NewWebsocketContext(conn)
 			self.AddWebsocketContext(wc)
-			agent.Bridge <- SpawnTerminalCmd{wc.Sid, bid}
+			agent.Bridge <- SpawnTerminalCmd{wc.Sid}
 		} else {
 			WebSocketSendError(conn, "No client with mid "+mid)
 		}
@@ -737,7 +721,8 @@
 		http.FileServer(http.Dir(filepath.Join(appDir, "upgrade")))))
 	http.Handle("/vendor/", auth.WrapHandler(http.FileServer(
 		http.Dir(filepath.Join(appDir, "common")))))
-	http.Handle("/", auth.WrapHandlerFunc(IndexHandler))
+	http.Handle("/", auth.WrapHandler(http.FileServer(
+		http.Dir(filepath.Join(appDir, "dashboard")))))
 
 	// Serve all apps
 	appNames, err := self.GetAppNames(false)
diff --git a/py/tools/ghost.py b/py/tools/ghost.py
index 17faff0..07bf93c 100755
--- a/py/tools/ghost.py
+++ b/py/tools/ghost.py
@@ -80,8 +80,8 @@
 
   RANDOM_MID = '##random_mid##'
 
-  def __init__(self, overlord_addrs, mode=AGENT, mid=None, sid=None, bid=None,
-               command=None, file_op=None):
+  def __init__(self, overlord_addrs, mode=AGENT, mid=None, sid=None,
+               terminal_sid=None, command=None, file_op=None):
     """Constructor.
 
     Args:
@@ -91,7 +91,8 @@
         id is randomly generated.
       sid: session ID. If the connection is requested by overlord, sid should
         be set to the corresponding session id assigned by overlord.
-      bid: browser ID. Identifies the browser which started the session.
+      terminal_sid: the terminal session ID associate with this client. This is
+        use for file download.
       command: the command to execute when we are in SHELL mode.
       file_op: a tuple (action, filepath, pid). action is either 'download' or
         'upload'. pid is the pid of the target shell, used to determine where
@@ -110,7 +111,7 @@
     self._sock = None
     self._machine_id = self.GetMachineID()
     self._session_id = sid if sid is not None else str(uuid.uuid4())
-    self._browser_id = bid
+    self._terminal_session_id = terminal_sid
     self._properties = {}
     self._shell_command = command
     self._file_op = file_op
@@ -120,7 +121,7 @@
     self._last_ping = 0
     self._queue = Queue.Queue()
     self._download_queue = Queue.Queue()
-    self._ttyname_to_bid = {}
+    self._ttyname_to_sid = {}
     self._terminal_sid_to_pid = {}
 
   def SetIgnoreChild(self, status):
@@ -199,7 +200,8 @@
       except Exception:
         pass
 
-  def SpawnGhost(self, mode, sid=None, bid=None, command=None, file_op=None):
+  def SpawnGhost(self, mode, sid=None, terminal_sid=None, command=None,
+                 file_op=None):
     """Spawn a child ghost with specific mode.
 
     Returns:
@@ -211,8 +213,8 @@
     pid = os.fork()
     if pid == 0:
       self.CloseSockets()
-      g = Ghost([self._connected_addr], mode, Ghost.RANDOM_MID, sid, bid,
-                command, file_op)
+      g = Ghost([self._connected_addr], mode, Ghost.RANDOM_MID, sid,
+                terminal_sid, command, file_op)
       g.Start()
       sys.exit(0)
     else:
@@ -358,11 +360,10 @@
 
     pid, fd = os.forkpty()
     if pid == 0:
-      # Register the mapping of browser_id and ttyname
       ttyname = os.readlink('/proc/%d/fd/0' % os.getpid())
       try:
         server = GhostRPCServer()
-        server.RegisterTTY(self._browser_id, ttyname)
+        server.RegisterTTY(self._session_id, ttyname)
         server.RegisterSession(self._session_id, os.getpid())
       except Exception:
         # If ghost is launched without RPC server, the call will fail but we
@@ -465,7 +466,7 @@
     if self._file_op[0] == 'download':
       size = os.stat(self._file_op[1]).st_size
       self.SendRequest('request_to_download',
-                       {'bid': self._browser_id,
+                       {'terminal_sid': self._terminal_session_id,
                         'filename': os.path.basename(self._file_op[1]),
                         'size': size})
     elif self._file_op[0] == 'upload':
@@ -536,7 +537,7 @@
     if command == 'upgrade':
       self.Upgrade()
     elif command == 'terminal':
-      self.SpawnGhost(self.TERMINAL, params['sid'], bid=params['bid'])
+      self.SpawnGhost(self.TERMINAL, params['sid'])
       self.SendResponse(msg, RESPONSE_SUCCESS)
     elif command == 'shell':
       self.SpawnGhost(self.SHELL, params['sid'], command=params['command'])
@@ -594,8 +595,9 @@
 
   def InitiateDownload(self):
     ttyname, filename = self._download_queue.get()
-    bid = self._ttyname_to_bid[ttyname]
-    self.SpawnGhost(self.FILE, bid=bid, file_op=('download', filename))
+    sid = self._ttyname_to_sid[ttyname]
+    self.SpawnGhost(self.FILE, terminal_sid=sid,
+                    file_op=('download', filename, None))
 
   def Listen(self):
     try:
@@ -678,8 +680,8 @@
   def AddToDownloadQueue(self, ttyname, filename):
     self._download_queue.put((ttyname, filename))
 
-  def RegisterTTY(self, browser_id, ttyname):
-    self._ttyname_to_bid[ttyname] = browser_id
+  def RegisterTTY(self, session_id, ttyname):
+    self._ttyname_to_sid[ttyname] = session_id
 
   def RegisterSession(self, session_id, process_id):
     self._terminal_sid_to_pid[session_id] = process_id