Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(272)

Unified Diff: remoting/tools/remoting.py

Issue 8511029: Initial check-in of Linux virtual Me2Me. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Add remoting_me2me_host, and use that instead of remoting_simple_host. Created 9 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« remoting/remoting.gyp ('K') | « remoting/remoting.gyp ('k') | no next file » | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: remoting/tools/remoting.py
diff --git a/remoting/tools/remoting.py b/remoting/tools/remoting.py
new file mode 100755
index 0000000000000000000000000000000000000000..2bb135551cb48a0df960c0b7913bf3a915af6310
--- /dev/null
+++ b/remoting/tools/remoting.py
@@ -0,0 +1,279 @@
+#!/usr/bin/env python
+# Copyright (c) 2011 The Chromium Authors. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
Wez 2011/11/11 01:32:32 Add a description of what this script is for.
Wez 2011/11/11 01:32:32 I think the script name should describe what it do
Lambros 2011/11/17 01:07:05 Done.
Lambros 2011/11/17 01:07:05 Done.
+import atexit
+import getpass
+import json
+import logging
+import os
+import random
+import signal
+import socket
+import subprocess
+import sys
+import time
+import urllib2
+
+# Local modules
+import gaia_auth
+import keygen
+
+REMOTING_COMMAND = ["../../out/Debug/remoting_me2me_host"]
Sergey Ulanov 2011/11/11 05:33:02 Can we detect this path similar to how keygen modu
Lambros 2011/11/17 01:07:05 Done.
+
+CONFIG_DIR = os.path.expanduser("~/.config/chrome-remote-desktop")
+
+X_LOCK_FILE_TEMPLATE = "/tmp/.X%d-lock"
+FIRST_X_DISPLAY_NUMBER = 20
+
+X_AUTH_FILE = os.path.expanduser("~/.Xauthority")
+os.environ["XAUTHORITY"] = X_AUTH_FILE
+
+g_desktops = []
+
+class Authentication:
+ """Manage authentication tokens for Chromoting/xmpp"""
+ def __init__(self, config_file):
+ self.config_file = config_file
+
+ def refresh_tokens(self):
+ print "Email:",
+ self.login = raw_input()
+ password = getpass.getpass("Password: ")
+
+ chromoting_auth = gaia_auth.GaiaAuthenticator('chromoting')
+ self.chromoting_auth_token = chromoting_auth.authenticate(self.login,
+ password)
+
+ xmpp_authenticator = gaia_auth.GaiaAuthenticator('chromiumsync')
+ self.xmpp_auth_token = xmpp_authenticator.authenticate(self.login,
+ password)
+
+ def load_config(self):
Wez 2011/11/11 01:32:32 nit: Why load/write_config rather than read/write_
Lambros 2011/11/17 01:07:05 Done (load/save).
+ try:
+ settings_file = open(self.config_file, 'r')
+ data = json.load(settings_file)
+ settings_file.close()
+ self.login = data["xmpp_login"]
+ self.chromoting_auth_token = data["chromoting_auth_token"]
+ self.xmpp_auth_token = data["xmpp_auth_token"]
+ except:
+ return False
+ return True
+
+ def write_config(self):
+ data = {
+ "xmpp_login": self.login,
+ "chromoting_auth_token": self.chromoting_auth_token,
+ "xmpp_auth_token": self.xmpp_auth_token,
+ }
+ os.umask(0066) # Set permission mask for created file.
Wez 2011/11/11 01:32:32 Does this fail with an error return code, or an ex
Lambros 2011/11/17 01:07:05 umask() cannot fail, but the open/write/close call
+ settings_file = open(self.config_file, 'w')
+ settings_file.write(json.dumps(data, indent=2))
+ settings_file.close()
+
+
+class Host:
+ """Create a new, or manage an existing, host registration."""
Wez 2011/11/11 01:32:32 Surely this object effectively _is_ a virtual Me2M
Lambros 2011/11/17 01:07:05 Reworded comment.
+
+ server = 'www.googleapis.com'
+ url = 'https://' + server + '/chromoting/v1/@me/hosts'
+
+ def __init__(self, config_file):
+ self.config_file = config_file
+
+ @staticmethod
+ def random_uuid():
+ return ("%04x%04x-%04x-%04x-%04x-%04x%04x%04x" %
+ tuple(map(lambda x: random.randrange(0, 0x10000), range(8))))
Wez 2011/11/11 01:32:32 Replace this with uuid.uuid1()?
Lambros 2011/11/17 01:07:05 Done.
+
+ def register_new(self, auth):
Wez 2011/11/11 01:32:32 Split this method down into generate_config(), reg
Wez 2011/11/11 01:32:32 Rename this method create_host() or create_config(
Lambros 2011/11/17 01:07:05 Done.
+ self.host_id = self.random_uuid()
+ logging.info("HostId: " + self.host_id)
+ self.host_name = socket.gethostname()
+ logging.info("HostName: " + self.host_name)
+
+ logging.info("Generating RSA key pair...")
+ (self.private_key, public_key) = keygen.generateRSAKeyPair()
+ logging.info("Done")
+
+ json_data = {
+ "data": {
+ "hostId": self.host_id,
+ "hostName": self.host_name,
+ "publicKey": public_key,
+ }
+ }
+ params = json.dumps(json_data)
+ headers = {
+ "Authorization": "GoogleLogin auth=" + auth.chromoting_auth_token,
+ "Content-Type": "application/json",
+ }
+
+ request = urllib2.Request(self.url, params, headers)
+ opener = urllib2.OpenerDirector()
+ opener.add_handler(urllib2.HTTPDefaultErrorHandler())
+
+ logging.info("Registering host with directory service...")
+ try:
+ res = urllib2.urlopen(request)
+ data = res.read()
+ except urllib2.HTTPError, err:
+ logging.error("Directory returned error: " + str(err))
+ logging.error(err.read())
+ sys.exit(1)
+ logging.info("Done")
+
+ def load_config(self):
Wez 2011/11/11 01:32:32 load vs read / save vs write
Lambros 2011/11/17 01:07:05 Done.
+ try:
+ settings_file = open(self.config_file, 'r')
+ data = json.load(settings_file)
+ settings_file.close()
+ self.host_id = data["host_id"]
+ self.host_name = data["host_name"]
+ self.private_key = data["private_key"]
+ except:
+ return False
+ return True
+
+ def write_config(self):
+ data = {
+ "host_id": self.host_id,
+ "host_name": self.host_name,
+ "private_key": self.private_key,
+ }
+ os.umask(0066) # Set permission mask for created file.
+ settings_file = open(self.config_file, 'w')
+ settings_file.write(json.dumps(data, indent=2))
+ settings_file.close()
+
+
+def cleanup():
+ logging.info("Cleanup.")
+
+ for desktop in g_desktops:
+ if desktop.x_proc:
+ logging.info("Terminating Xvfb")
+ desktop.x_proc.terminate()
+
+def signal_handler(signum, stackframe):
+ # Exit cleanly so the atexit handler, cleanup(), gets called.
+ raise SystemExit
+
+
+class Desktop:
+ """Manage a single virtual desktop"""
+ def __init__(self):
+ self.x_proc = None
+ g_desktops.append(self)
+
+ @staticmethod
+ def get_unused_display_number():
+ """Return a candidate display number for which there is currently no
+ X Server lock file"""
+ display = FIRST_X_DISPLAY_NUMBER
+ while os.path.exists(X_LOCK_FILE_TEMPLATE % display):
+ display += 1
+ return display
+
+ def launch_x_server(self):
+ display = self.get_unused_display_number()
+ ret_code = subprocess.call("xauth add :%d . `mcookie`" % display,
+ shell=True)
+ if ret_code != 0:
+ logging.error("xauth failed with code %d" % ret_code)
+ return 1
+ logging.info("Starting Xvfb on display :%d" % display);
+ self.x_proc = subprocess.Popen(["Xvfb", ":%d" % display,
+ "-auth", X_AUTH_FILE,
+ "-nolisten", "tcp",
+ "-screen", "0", "1024x768x24",
+ ])
+ if not self.x_proc.pid:
+ logging.info("Could not start Xvfb.")
+ return 1
+
+ # Create clean environment for new session, so it is cleanly separated from
+ # the user's console X session.
+ self.child_env = {"DISPLAY": ":%d" % display}
+ for key in ["HOME", "PATH"]:
+ self.child_env[key] = os.environ[key]
Wez 2011/11/11 01:32:32 Are these really all we need for a valid environme
Lambros 2011/11/17 01:07:05 This is essentially what GNOME Display Manager doe
+
+ # Wait for X to be active.
+ for test in range(5):
+ proc = subprocess.Popen("xdpyinfo > /dev/null", env=self.child_env,
+ shell=True)
+ pid, retcode = os.waitpid(proc.pid, 0)
+ if retcode == 0:
+ break
+ time.sleep(0.5)
+ if retcode != 0:
+ logging.info("Could not connect to Xvfb.")
+ return 1
+ else:
+ logging.info("Xvfb is active.")
+
+ def launch_x_session(self):
+ # Start desktop session
+ session_proc = subprocess.Popen("/etc/X11/Xsession",
+ cwd=os.environ["HOME"],
+ env=self.child_env)
+ if not session_proc.pid:
+ logging.info("Could not start X session")
+ return 1
+
+ def launch_host(self):
+ # Start remoting host
+ self.host_proc = subprocess.Popen(REMOTING_COMMAND, env=self.child_env)
+ if not self.host_proc.pid:
+ logging.info("Could not start remoting host")
+ return 1
+
+
+def main():
+ atexit.register(cleanup)
Wez 2011/11/11 01:32:32 This means at present that killing the script will
Lambros 2011/11/17 01:07:05 Future CL - a bit complex to include in here. Not
Wez 2011/11/17 02:19:28 That page is really talking about how self-daemoni
+
+ for s in (
+ signal.SIGINT,
+ signal.SIGTERM,
+ ):
Wez 2011/11/11 01:32:32 Could this be a single line? It looks strange lai
Lambros 2011/11/17 01:07:05 Done. I think that list is exhaustive. Trapping
+ signal.signal(s, signal_handler)
+
+ # Ensure full path to config directory exists.
+ if not os.path.exists(CONFIG_DIR):
+ os.makedirs(CONFIG_DIR, mode=0700)
+
+ auth = Authentication(os.path.join(CONFIG_DIR, "auth.json"))
+ if not auth.load_config():
+ auth.refresh_tokens()
+ auth.write_config()
+
+ host = Host(os.path.join(CONFIG_DIR, "host.json"))
+
+ if not host.load_config():
+ host.register_new(auth)
+ host.write_config()
Wez 2011/11/11 01:32:32 Support for multiple Host configurations, and re-s
Lambros 2011/11/17 01:07:05 I think so.
+
+ logging.info("Using host_id: " + host.host_id)
+
+ desktop = Desktop()
+ desktop.launch_x_server()
+ desktop.launch_x_session()
+ desktop.launch_host()
Wez 2011/11/11 01:32:32 If one of these fails, it looks like you'll get er
Lambros 2011/11/17 01:07:05 Turned these into Exceptions, which will kill the
+
+ while True:
+ pid, status = os.wait()
+ logging.info("wait() returned (%s,%s)" % (pid, status))
+
+ if pid == desktop.x_proc.pid:
+ logging.info("X server process terminated with code %d", status)
+ break
+
+ if pid == desktop.host_proc.pid:
+ logging.info("Host process terminated, relaunching")
+ desktop.launch_host()
+
+if __name__ == "__main__":
+ logging.basicConfig(level=logging.DEBUG)
+ sys.exit(main())
« remoting/remoting.gyp ('K') | « remoting/remoting.gyp ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698