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

Side by Side Diff: remoting/tools/me2me_virtual_host.py

Issue 8511029: Initial check-in of Linux virtual Me2Me. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Address .cc comments. 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 unified diff | Download patch | Annotate | Revision Log
« no previous file with comments | « remoting/remoting.gyp ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 #!/usr/bin/env python
2 # Copyright (c) 2011 The Chromium Authors. All rights reserved.
3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file.
5
6 # Virtual Me2Me implementation. This script runs and manages the processes
7 # required for a Virtual Me2Me desktop, which are: X server, X desktop
8 # session, and Host process.
9 # This script is intended to run continuously as a background daemon
10 # process, running under an ordinary (non-root) user account.
11
12 import atexit
13 import getpass
14 import json
15 import logging
16 import os
17 import random
18 import signal
19 import socket
20 import subprocess
21 import sys
22 import time
23 import urllib2
24 import uuid
25
26 # Local modules
27 import gaia_auth
28 import keygen
29
30 REMOTING_COMMAND = "remoting_me2me_host"
31
32 SCRIPT_PATH = os.path.dirname(sys.argv[0])
33 if not SCRIPT_PATH:
34 SCRIPT_PATH = os.getcwd()
35
36 # These are relative to SCRIPT_PATH.
37 EXE_PATHS_TO_TRY = [
38 ".",
39 "../../out/Debug",
40 "../../out/Release"
41 ]
42
43 CONFIG_DIR = os.path.expanduser("~/.config/chrome-remote-desktop")
44
45 X_LOCK_FILE_TEMPLATE = "/tmp/.X%d-lock"
46 FIRST_X_DISPLAY_NUMBER = 20
47
48 X_AUTH_FILE = os.path.expanduser("~/.Xauthority")
49 os.environ["XAUTHORITY"] = X_AUTH_FILE
50
51 def locate_executable(exe_name):
52 for path in EXE_PATHS_TO_TRY:
53 exe_path = os.path.join(SCRIPT_PATH, path, exe_name)
54 if os.path.exists(exe_path):
55 return exe_path
56 raise Exception("Could not locate executable '%s'" % exe_name)
57
58 g_desktops = []
59
60 class Authentication:
61 """Manage authentication tokens for Chromoting/xmpp"""
62 def __init__(self, config_file):
63 self.config_file = config_file
64
65 def refresh_tokens(self):
66 print "Email:",
67 self.login = raw_input()
68 password = getpass.getpass("Password: ")
69
70 chromoting_auth = gaia_auth.GaiaAuthenticator('chromoting')
71 self.chromoting_auth_token = chromoting_auth.authenticate(self.login,
72 password)
73
74 xmpp_authenticator = gaia_auth.GaiaAuthenticator('chromiumsync')
75 self.xmpp_auth_token = xmpp_authenticator.authenticate(self.login,
76 password)
77
78 def load_config(self):
79 try:
80 settings_file = open(self.config_file, 'r')
81 data = json.load(settings_file)
82 settings_file.close()
83 self.login = data["xmpp_login"]
84 self.chromoting_auth_token = data["chromoting_auth_token"]
85 self.xmpp_auth_token = data["xmpp_auth_token"]
86 except:
87 return False
88 return True
89
90 def save_config(self):
91 data = {
92 "xmpp_login": self.login,
93 "chromoting_auth_token": self.chromoting_auth_token,
94 "xmpp_auth_token": self.xmpp_auth_token,
95 }
96 os.umask(0066) # Set permission mask for created file.
97 settings_file = open(self.config_file, 'w')
98 settings_file.write(json.dumps(data, indent=2))
99 settings_file.close()
100
101
102 class Host:
103 """This manages the configuration for a host.
104
105 Callers should instantiate a Host object (passing in a filename where the
106 config will be kept), then should call either of the methods:
107
108 * create_config(auth): Create a new Host configuration and register it with
109 the Directory Service (the "auth" parameter is used to authenticate with the
110 Service).
111 * load_config(): Load a config from disk, with details of an existing Host
112 registration.
113
114 After calling create_config() (or making any config changes) the method
115 save_config() should be called to save the details to disk.
116 """
117
118 server = 'www.googleapis.com'
119 url = 'https://' + server + '/chromoting/v1/@me/hosts'
120
121 def __init__(self, config_file):
122 self.config_file = config_file
123
124 def create_config(self, auth):
125 self.host_id = str(uuid.uuid1())
126 logging.info("HostId: " + self.host_id)
127 self.host_name = socket.gethostname()
128 logging.info("HostName: " + self.host_name)
129
130 logging.info("Generating RSA key pair...")
131 (self.private_key, public_key) = keygen.generateRSAKeyPair()
132 logging.info("Done")
133
134 json_data = {
135 "data": {
136 "hostId": self.host_id,
137 "hostName": self.host_name,
138 "publicKey": public_key,
139 }
140 }
141 params = json.dumps(json_data)
142 headers = {
143 "Authorization": "GoogleLogin auth=" + auth.chromoting_auth_token,
144 "Content-Type": "application/json",
145 }
146
147 request = urllib2.Request(self.url, params, headers)
148 opener = urllib2.OpenerDirector()
149 opener.add_handler(urllib2.HTTPDefaultErrorHandler())
150
151 logging.info("Registering host with directory service...")
152 try:
153 res = urllib2.urlopen(request)
154 data = res.read()
155 except urllib2.HTTPError, err:
156 logging.error("Directory returned error: " + str(err))
157 logging.error(err.read())
158 sys.exit(1)
159 logging.info("Done")
160
161 def load_config(self):
162 try:
163 settings_file = open(self.config_file, 'r')
164 data = json.load(settings_file)
165 settings_file.close()
166 self.host_id = data["host_id"]
167 self.host_name = data["host_name"]
168 self.private_key = data["private_key"]
169 except:
170 return False
171 return True
172
173 def save_config(self):
174 data = {
175 "host_id": self.host_id,
176 "host_name": self.host_name,
177 "private_key": self.private_key,
178 }
179 os.umask(0066) # Set permission mask for created file.
180 settings_file = open(self.config_file, 'w')
181 settings_file.write(json.dumps(data, indent=2))
182 settings_file.close()
183
184
185 def cleanup():
186 logging.info("Cleanup.")
187
188 for desktop in g_desktops:
189 if desktop.x_proc:
190 logging.info("Terminating Xvfb")
191 desktop.x_proc.terminate()
192
193 def signal_handler(signum, stackframe):
194 # Exit cleanly so the atexit handler, cleanup(), gets called.
195 raise SystemExit
196
197
198 class Desktop:
199 """Manage a single virtual desktop"""
200 def __init__(self):
201 self.x_proc = None
202 g_desktops.append(self)
203
204 @staticmethod
205 def get_unused_display_number():
206 """Return a candidate display number for which there is currently no
207 X Server lock file"""
208 display = FIRST_X_DISPLAY_NUMBER
209 while os.path.exists(X_LOCK_FILE_TEMPLATE % display):
210 display += 1
211 return display
212
213 def launch_x_server(self):
214 display = self.get_unused_display_number()
215 ret_code = subprocess.call("xauth add :%d . `mcookie`" % display,
216 shell=True)
217 if ret_code != 0:
218 raise Exception("xauth failed with code %d" % ret_code)
219
220 logging.info("Starting Xvfb on display :%d" % display);
221 self.x_proc = subprocess.Popen(["Xvfb", ":%d" % display,
222 "-auth", X_AUTH_FILE,
223 "-nolisten", "tcp",
224 "-screen", "0", "1024x768x24",
225 ])
226 if not self.x_proc.pid:
227 raise Exception("Could not start Xvfb.")
228
229 # Create clean environment for new session, so it is cleanly separated from
230 # the user's console X session.
231 self.child_env = {"DISPLAY": ":%d" % display}
232 for key in [
233 "HOME",
234 "LOGNAME",
235 "PATH",
236 "SHELL",
237 "USER",
238 "USERNAME"]:
239 if os.environ.has_key(key):
240 self.child_env[key] = os.environ[key]
241
242 # Wait for X to be active.
243 for test in range(5):
244 proc = subprocess.Popen("xdpyinfo > /dev/null", env=self.child_env,
245 shell=True)
246 pid, retcode = os.waitpid(proc.pid, 0)
247 if retcode == 0:
248 break
249 time.sleep(0.5)
250 if retcode != 0:
251 raise Exception("Could not connect to Xvfb.")
252 else:
253 logging.info("Xvfb is active.")
254
255 def launch_x_session(self):
256 # Start desktop session
257 session_proc = subprocess.Popen("/etc/X11/Xsession",
258 cwd=os.environ["HOME"],
259 env=self.child_env)
260 if not session_proc.pid:
261 raise Exception("Could not start X session")
262
263 def launch_host(self):
264 # Start remoting host
265 command = locate_executable(REMOTING_COMMAND)
266 self.host_proc = subprocess.Popen(command, env=self.child_env)
267 if not self.host_proc.pid:
268 raise Exception("Could not start remoting host")
269
270
271 def main():
272 atexit.register(cleanup)
273
274 for s in [signal.SIGINT, signal.SIGTERM]:
275 signal.signal(s, signal_handler)
276
277 # Ensure full path to config directory exists.
278 if not os.path.exists(CONFIG_DIR):
279 os.makedirs(CONFIG_DIR, mode=0700)
280
281 auth = Authentication(os.path.join(CONFIG_DIR, "auth.json"))
282 if not auth.load_config():
283 try:
284 auth.refresh_tokens()
285 except:
286 logging.error("Authentication failed.")
287 return 1
288 auth.save_config()
289
290 host = Host(os.path.join(CONFIG_DIR, "host.json"))
291
292 if not host.load_config():
293 host.create_config(auth)
294 host.save_config()
295
296 logging.info("Using host_id: " + host.host_id)
297
298 desktop = Desktop()
299 desktop.launch_x_server()
300 desktop.launch_x_session()
301 desktop.launch_host()
302
303 while True:
304 pid, status = os.wait()
305 logging.info("wait() returned (%s,%s)" % (pid, status))
306
307 if pid == desktop.x_proc.pid:
308 logging.info("X server process terminated with code %d", status)
309 break
310
311 if pid == desktop.host_proc.pid:
312 logging.info("Host process terminated, relaunching")
313 desktop.launch_host()
314
315 if __name__ == "__main__":
316 logging.basicConfig(level=logging.DEBUG)
317 sys.exit(main())
OLDNEW
« no previous file with comments | « remoting/remoting.gyp ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698