blob: 686850c4f5959f2e8a94de532b7c716c829ebdea [file] [log] [blame]
Hung-Te Linf2f78f72012-02-08 19:27:11 +08001#!/usr/bin/env python
Jon Salz27dfe032012-08-01 14:57:33 +08002# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Hung-Te Linf2f78f72012-02-08 19:27:11 +08003# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6
7'''
8This module provides both client and server side of a XML RPC based server which
9can be used to handle factory test states (status) and shared persistent data.
10'''
11
12
Jon Salzc7c25df2012-07-06 17:19:49 +080013import glob
Jon Salz758e6cc2012-04-03 15:47:07 +080014import logging
Jon Salz258a40c2012-04-19 12:34:01 +080015import mimetypes
Jon Salz758e6cc2012-04-03 15:47:07 +080016import os
Jon Salz5eee01c2012-06-04 16:07:33 +080017import Queue
18import re
Jon Salz758e6cc2012-04-03 15:47:07 +080019import shelve
20import shutil
Jon Salz258a40c2012-04-19 12:34:01 +080021import SocketServer
Jon Salz758e6cc2012-04-03 15:47:07 +080022import sys
23import threading
Jon Salz258a40c2012-04-19 12:34:01 +080024import time
Jon Salzc7c25df2012-07-06 17:19:49 +080025import yaml
Jon Salz258a40c2012-04-19 12:34:01 +080026
27from hashlib import sha1
Jon Salz5eee01c2012-06-04 16:07:33 +080028from uuid import uuid4
Hung-Te Linf2f78f72012-02-08 19:27:11 +080029
Jon Salz0697cbf2012-07-04 15:14:04 +080030import factory_common # pylint: disable=W0611
Jon Salz258a40c2012-04-19 12:34:01 +080031
32from jsonrpclib import jsonclass
33from jsonrpclib import jsonrpc
34from jsonrpclib import SimpleJSONRPCServer
jcliangcd688182012-08-20 21:01:26 +080035from cros.factory import system
Jon Salz83591782012-06-26 11:09:58 +080036from cros.factory.test import factory
37from cros.factory.test.factory import TestState
Jon Salz83591782012-06-26 11:09:58 +080038from cros.factory.test import unicode_to_string
Hung-Te Linf2f78f72012-02-08 19:27:11 +080039
Jon Salz49a7d152012-06-19 15:04:09 +080040
Hung-Te Linf2f78f72012-02-08 19:27:11 +080041DEFAULT_FACTORY_STATE_PORT = 0x0FAC
42DEFAULT_FACTORY_STATE_ADDRESS = 'localhost'
43DEFAULT_FACTORY_STATE_BIND_ADDRESS = 'localhost'
Jon Salz8796e362012-05-24 11:39:09 +080044DEFAULT_FACTORY_STATE_FILE_PATH = factory.get_state_root()
Hung-Te Linf2f78f72012-02-08 19:27:11 +080045
46
47def _synchronized(f):
Jon Salz0697cbf2012-07-04 15:14:04 +080048 '''
49 Decorates a function to grab a lock.
50 '''
51 def wrapped(self, *args, **kw):
52 with self._lock: # pylint: disable=W0212
53 return f(self, *args, **kw)
54 return wrapped
Hung-Te Linf2f78f72012-02-08 19:27:11 +080055
56
Jon Salz758e6cc2012-04-03 15:47:07 +080057def clear_state(state_file_path=None):
Jon Salz0697cbf2012-07-04 15:14:04 +080058 '''Clears test state (removes the state file path).
Jon Salz758e6cc2012-04-03 15:47:07 +080059
Jon Salz0697cbf2012-07-04 15:14:04 +080060 Args:
61 state_file_path: Path to state; uses the default path if None.
62 '''
63 state_file_path = state_file_path or DEFAULT_FACTORY_STATE_FILE_PATH
64 logging.warn('Clearing state file path %s' % state_file_path)
65 if os.path.exists(state_file_path):
66 shutil.rmtree(state_file_path)
Jon Salz758e6cc2012-04-03 15:47:07 +080067
68
Jon Salzaeb4fd42012-06-05 15:08:30 +080069class PathResolver(object):
Jon Salz0697cbf2012-07-04 15:14:04 +080070 '''Resolves paths in URLs.'''
71 def __init__(self):
72 self._paths = {}
Jon Salzaeb4fd42012-06-05 15:08:30 +080073
Jon Salz0697cbf2012-07-04 15:14:04 +080074 def AddPath(self, url_path, local_path):
75 '''Adds a prefix mapping:
Jon Salzaeb4fd42012-06-05 15:08:30 +080076
Jon Salz0697cbf2012-07-04 15:14:04 +080077 For example,
Jon Salzaeb4fd42012-06-05 15:08:30 +080078
Jon Salz0697cbf2012-07-04 15:14:04 +080079 AddPath('/foo', '/usr/local/docs')
Jon Salzaeb4fd42012-06-05 15:08:30 +080080
Jon Salz0697cbf2012-07-04 15:14:04 +080081 will cause paths to resolved as follows:
Jon Salzaeb4fd42012-06-05 15:08:30 +080082
Jon Salz0697cbf2012-07-04 15:14:04 +080083 /foo -> /usr/local/docs
84 /foo/index.html -> /usr/local/docs/index.html
Jon Salzaeb4fd42012-06-05 15:08:30 +080085
Jon Salz0697cbf2012-07-04 15:14:04 +080086 Args:
87 url_path: The path in the URL
88 '''
89 self._paths[url_path] = local_path
Jon Salzaeb4fd42012-06-05 15:08:30 +080090
Jon Salz0697cbf2012-07-04 15:14:04 +080091 def Resolve(self, url_path):
92 '''Resolves a path mapping.
Jon Salzaeb4fd42012-06-05 15:08:30 +080093
Jon Salz0697cbf2012-07-04 15:14:04 +080094 Returns None if no paths match.'
Jon Salzaeb4fd42012-06-05 15:08:30 +080095
Jon Salz0697cbf2012-07-04 15:14:04 +080096 Args:
97 url_path: A path in a URL (starting with /).
98 '''
99 if not url_path.startswith('/'):
100 return None
Jon Salzaeb4fd42012-06-05 15:08:30 +0800101
Jon Salz0697cbf2012-07-04 15:14:04 +0800102 prefix = url_path
103 while prefix != '':
104 local_prefix = self._paths.get(prefix)
105 if local_prefix:
106 return local_prefix + url_path[len(prefix):]
107 prefix, _, _ = prefix.rpartition('/')
Jon Salzaeb4fd42012-06-05 15:08:30 +0800108
Jon Salz0697cbf2012-07-04 15:14:04 +0800109 root_prefix = self._paths.get('/')
110 if root_prefix:
111 return root_prefix + url_path
Jon Salzaeb4fd42012-06-05 15:08:30 +0800112
113
Jon Salz258a40c2012-04-19 12:34:01 +0800114@unicode_to_string.UnicodeToStringClass
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800115class FactoryState(object):
Jon Salz0697cbf2012-07-04 15:14:04 +0800116 '''
117 The core implementation for factory state control.
118 The major provided features are:
119
120 SHARED DATA
121 You can get/set simple data into the states and share between all tests.
122 See get_shared_data(name) and set_shared_data(name, value) for more
123 information.
124
125 TEST STATUS
126 To track the execution status of factory auto tests, you can use
127 get_test_state, get_test_states methods, and update_test_state
128 methods.
129
130 All arguments may be provided either as strings, or as Unicode strings in
131 which case they are converted to strings using UTF-8. All returned values
132 are strings (not Unicode).
133
134 This object is thread-safe.
135
136 See help(FactoryState.[methodname]) for more information.
137
138 Properties:
139 _generated_files: Map from UUID to paths on disk. These are
140 not persisted on disk (though they could be if necessary).
141 _generated_data: Map from UUID to (mime_type, data) pairs for
142 transient objects to serve.
143 _generated_data_expiration: Priority queue of expiration times
144 for objects in _generated_data.
145 '''
146
147 def __init__(self, state_file_path=None):
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800148 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800149 Initializes the state server.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800150
Jon Salz0697cbf2012-07-04 15:14:04 +0800151 Parameters:
152 state_file_path: External file to store the state information.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800153 '''
Jon Salz0697cbf2012-07-04 15:14:04 +0800154 state_file_path = state_file_path or DEFAULT_FACTORY_STATE_FILE_PATH
155 if not os.path.exists(state_file_path):
156 os.makedirs(state_file_path)
157 self._tests_shelf = shelve.open(state_file_path + '/tests')
158 self._data_shelf = shelve.open(state_file_path + '/data')
Jon Salz0697cbf2012-07-04 15:14:04 +0800159 self._lock = threading.RLock()
160 self.test_list_struct = None
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800161
Jon Salz0697cbf2012-07-04 15:14:04 +0800162 self._generated_files = {}
163 self._generated_data = {}
164 self._generated_data_expiration = Queue.PriorityQueue()
165 self._resolver = PathResolver()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800166
Jon Salz0697cbf2012-07-04 15:14:04 +0800167 if TestState not in jsonclass.supported_types:
168 jsonclass.supported_types.append(TestState)
Jon Salz258a40c2012-04-19 12:34:01 +0800169
Jon Salz0697cbf2012-07-04 15:14:04 +0800170 @_synchronized
171 def close(self):
172 '''
173 Shuts down the state instance.
174 '''
175 for shelf in [self._tests_shelf,
Jon Salzc7c25df2012-07-06 17:19:49 +0800176 self._data_shelf]:
Jon Salz0697cbf2012-07-04 15:14:04 +0800177 try:
178 shelf.close()
179 except:
180 logging.exception('Unable to close shelf')
Jon Salz5eee01c2012-06-04 16:07:33 +0800181
Jon Salz0697cbf2012-07-04 15:14:04 +0800182 @_synchronized
183 def update_test_state(self, path, **kw):
184 '''
185 Updates the state of a test.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800186
Jon Salz0697cbf2012-07-04 15:14:04 +0800187 See TestState.update for the allowable keyword arguments.
Jon Salz66f65e62012-05-24 17:40:26 +0800188
Jon Salz0697cbf2012-07-04 15:14:04 +0800189 @param path: The path to the test (see FactoryTest for a description
190 of test paths).
191 @param kw: See TestState.update for allowable arguments (e.g.,
192 status and increment_count).
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800193
Jon Salz0697cbf2012-07-04 15:14:04 +0800194 @return: A tuple containing the new state, and a boolean indicating
195 whether the state was just changed.
196 '''
197 state = self._tests_shelf.get(path)
198 old_state_repr = repr(state)
199 changed = False
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800200
Jon Salz0697cbf2012-07-04 15:14:04 +0800201 if not state:
202 changed = True
203 state = TestState()
Jon Salz20d8e932012-03-17 15:04:23 +0800204
Jon Salz0697cbf2012-07-04 15:14:04 +0800205 changed = changed | state.update(**kw) # Don't short-circuit
Jon Salz20d8e932012-03-17 15:04:23 +0800206
Jon Salz0697cbf2012-07-04 15:14:04 +0800207 if changed:
208 logging.debug('Updating test state for %s: %s -> %s',
209 path, old_state_repr, state)
210 self._tests_shelf[path] = state
211 self._tests_shelf.sync()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800212
Jon Salz0697cbf2012-07-04 15:14:04 +0800213 return state, changed
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800214
Jon Salz0697cbf2012-07-04 15:14:04 +0800215 @_synchronized
216 def get_test_state(self, path):
217 '''
218 Returns the state of a test.
219 '''
220 return self._tests_shelf[path]
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800221
Jon Salz0697cbf2012-07-04 15:14:04 +0800222 @_synchronized
223 def get_test_paths(self):
224 '''
225 Returns a list of all tests' paths.
226 '''
227 return self._tests_shelf.keys()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800228
Jon Salz0697cbf2012-07-04 15:14:04 +0800229 @_synchronized
230 def get_test_states(self):
231 '''
232 Returns a map of each test's path to its state.
233 '''
234 return dict(self._tests_shelf)
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800235
Jon Salz0697cbf2012-07-04 15:14:04 +0800236 def get_test_list(self):
237 '''
238 Returns the test list.
239 '''
240 return self.test_list.to_struct()
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800241
Jon Salz0697cbf2012-07-04 15:14:04 +0800242 @_synchronized
243 def set_shared_data(self, *key_value_pairs):
244 '''
245 Sets shared data items.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800246
Jon Salz0697cbf2012-07-04 15:14:04 +0800247 Args:
248 key_value_pairs: A series of alternating keys and values
249 (k1, v1, k2, v2...). In the simple case this can just
250 be a single key and value.
251 '''
252 assert len(key_value_pairs) % 2 == 0, repr(key_value_pairs)
253 for i in range(0, len(key_value_pairs), 2):
254 self._data_shelf[key_value_pairs[i]] = key_value_pairs[i + 1]
255 self._data_shelf.sync()
Jon Salz258a40c2012-04-19 12:34:01 +0800256
Jon Salz0697cbf2012-07-04 15:14:04 +0800257 @_synchronized
258 def get_shared_data(self, key, optional=False):
259 '''
260 Retrieves a shared data item.
Jon Salzb1b39092012-05-03 02:05:09 +0800261
Jon Salz0697cbf2012-07-04 15:14:04 +0800262 Args:
263 key: The key whose value to retrieve.
264 optional: True to return None if not found; False to raise
265 a KeyError.
266 '''
267 if optional:
268 return self._data_shelf.get(key)
269 else:
270 return self._data_shelf[key]
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800271
Jon Salz0697cbf2012-07-04 15:14:04 +0800272 @_synchronized
273 def has_shared_data(self, key):
274 '''
275 Returns if a shared data item exists.
276 '''
277 return key in self._data_shelf
Jon Salz4f6c7172012-06-11 20:45:36 +0800278
Jon Salz0697cbf2012-07-04 15:14:04 +0800279 @_synchronized
280 def del_shared_data(self, key, optional=False):
281 '''
282 Deletes a shared data item.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800283
Jon Salz0697cbf2012-07-04 15:14:04 +0800284 Args:
285 key: The key whose value to retrieve.
286 optional: False to raise a KeyError if not found.
287 '''
288 try:
289 del self._data_shelf[key]
290 except KeyError:
291 if not optional:
292 raise
Hung-Te Lin163f7512012-02-17 18:58:57 +0800293
Jon Salzc7c25df2012-07-06 17:19:49 +0800294 def get_test_history(self, *test_paths):
295 '''Returns metadata for all previous (and current) runs of a test.'''
Jon Salz0697cbf2012-07-04 15:14:04 +0800296 ret = []
297
Jon Salzc7c25df2012-07-06 17:19:49 +0800298 for path in test_paths:
299 for f in glob.glob(os.path.join(factory.get_test_data_root(),
300 path + '-*',
301 'metadata')):
302 try:
303 ret.append(yaml.load(open(f)))
304 except:
305 logging.exception('Unable to load test metadata %s', f)
Jon Salz0697cbf2012-07-04 15:14:04 +0800306
Jon Salz27dfe032012-08-01 14:57:33 +0800307 ret.sort(key=lambda item: item.get('init_time', None))
Jon Salz0697cbf2012-07-04 15:14:04 +0800308 return ret
309
Jon Salzc7c25df2012-07-06 17:19:49 +0800310 def get_test_history_entry(self, path, invocation):
311 '''Returns metadata and log for one test invocation.'''
312 test_dir = os.path.join(factory.get_test_data_root(),
313 '%s-%s' % (path, invocation))
314
315 log_file = os.path.join(test_dir, 'log')
316 try:
317 log = open(log_file).read()
318 except:
319 # Oh well
320 logging.exception('Unable to read log file %s', log_file)
321 log = None
322
323 return {'metadata': yaml.load(open(os.path.join(test_dir, 'metadata'))),
324 'log': log}
325
Jon Salz0697cbf2012-07-04 15:14:04 +0800326 @_synchronized
327 def url_for_file(self, path):
328 '''Returns a URL that can be used to serve a local file.
329
330 Args:
331 path: path to the local file
332
333 Returns:
334 url: A (possibly relative) URL that refers to the file
335 '''
336 uuid = str(uuid4())
337 uri_path = '/generated-files/%s/%s' % (uuid, os.path.basename(path))
338 self._generated_files[uuid] = path
339 return uri_path
340
341 @_synchronized
342 def url_for_data(self, mime_type, data, expiration_secs=None):
343 '''Returns a URL that can be used to serve a static collection
344 of bytes.
345
346 Args:
347 mime_type: MIME type for the data
348 data: Data to serve
349 expiration_secs: If not None, the number of seconds in which
350 the data will expire.
351 '''
352 uuid = str(uuid4())
353 self._generated_data[uuid] = mime_type, data
354 if expiration_secs:
355 now = time.time()
356 self._generated_data_expiration.put(
357 (now + expiration_secs, uuid))
358
359 # Reap old items.
360 while True:
Jon Salz4f6c7172012-06-11 20:45:36 +0800361 try:
Jon Salz0697cbf2012-07-04 15:14:04 +0800362 item = self._generated_data_expiration.get_nowait()
363 except Queue.Empty:
364 break
Hung-Te Lin163f7512012-02-17 18:58:57 +0800365
Jon Salz0697cbf2012-07-04 15:14:04 +0800366 if item[0] < now:
367 del self._generated_data[item[1]]
368 else:
369 # Not expired yet; put it back and we're done
370 self._generated_data_expiration.put(item)
371 break
372 uri_path = '/generated-data/%s' % uuid
373 return uri_path
Jon Salz258a40c2012-04-19 12:34:01 +0800374
Jon Salz0697cbf2012-07-04 15:14:04 +0800375 @_synchronized
376 def register_path(self, url_path, local_path):
377 self._resolver.AddPath(url_path, local_path)
Jon Salz258a40c2012-04-19 12:34:01 +0800378
Jon Salz0697cbf2012-07-04 15:14:04 +0800379 def get_system_status(self):
380 '''Returns system status information.
Jon Salz258a40c2012-04-19 12:34:01 +0800381
Jon Salz0697cbf2012-07-04 15:14:04 +0800382 This may include system load, battery status, etc. See
383 system.SystemStatus().
384 '''
385 return system.SystemStatus().__dict__
Jon Salz49a7d152012-06-19 15:04:09 +0800386
Jon Salzaeb4fd42012-06-05 15:08:30 +0800387
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800388def get_instance(address=DEFAULT_FACTORY_STATE_ADDRESS,
Jon Salz0697cbf2012-07-04 15:14:04 +0800389 port=DEFAULT_FACTORY_STATE_PORT):
390 '''
391 Gets an instance (for client side) to access the state server.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800392
Jon Salz0697cbf2012-07-04 15:14:04 +0800393 @param address: Address of the server to be connected.
394 @param port: Port of the server to be connected.
395 @return An object with all public functions from FactoryState.
396 See help(FactoryState) for more information.
397 '''
398 return jsonrpc.ServerProxy('http://%s:%d' % (address, port),
399 verbose=False)
Jon Salz258a40c2012-04-19 12:34:01 +0800400
401
402class MyJSONRPCRequestHandler(SimpleJSONRPCServer.SimpleJSONRPCRequestHandler):
Jon Salz0697cbf2012-07-04 15:14:04 +0800403 def do_GET(self):
404 logging.debug('HTTP request for path %s', self.path)
Jon Salz258a40c2012-04-19 12:34:01 +0800405
Jon Salz0697cbf2012-07-04 15:14:04 +0800406 handler = self.server.handlers.get(self.path)
407 if handler:
408 return handler(self)
Jon Salz258a40c2012-04-19 12:34:01 +0800409
Jon Salz0697cbf2012-07-04 15:14:04 +0800410 match = re.match('^/generated-data/([-0-9a-f]+)$', self.path)
411 if match:
412 generated_data = self.server._generated_data.get(match.group(1))
413 if not generated_data:
414 logging.warn('Unknown or expired generated data %s',
415 match.group(1))
416 self.send_response(404)
417 return
Jon Salz5eee01c2012-06-04 16:07:33 +0800418
Jon Salz0697cbf2012-07-04 15:14:04 +0800419 mime_type, data = generated_data
Jon Salz5eee01c2012-06-04 16:07:33 +0800420
Jon Salz0697cbf2012-07-04 15:14:04 +0800421 self.send_response(200)
422 self.send_header('Content-Type', mime_type)
423 self.send_header('Content-Length', len(data))
424 self.end_headers()
425 self.wfile.write(data)
Jon Salz5eee01c2012-06-04 16:07:33 +0800426
Jon Salz0697cbf2012-07-04 15:14:04 +0800427 if self.path.endswith('/'):
428 self.path += 'index.html'
Jon Salz258a40c2012-04-19 12:34:01 +0800429
Jon Salz0697cbf2012-07-04 15:14:04 +0800430 if ".." in self.path.split("/"):
431 logging.warn("Invalid path")
432 self.send_response(404)
433 return
Jon Salz258a40c2012-04-19 12:34:01 +0800434
Jon Salz0697cbf2012-07-04 15:14:04 +0800435 mime_type = mimetypes.guess_type(self.path)
436 if not mime_type:
437 logging.warn("Unable to guess MIME type")
438 self.send_response(404)
439 return
Jon Salz258a40c2012-04-19 12:34:01 +0800440
Jon Salz0697cbf2012-07-04 15:14:04 +0800441 local_path = None
442 match = re.match('^/generated-files/([-0-9a-f]+)/', self.path)
443 if match:
444 local_path = self.server._generated_files.get(match.group(1))
445 if not local_path:
446 logging.warn('Unknown generated file %s in path %s',
447 match.group(1), self.path)
448 self.send_response(404)
449 return
Jon Salz5eee01c2012-06-04 16:07:33 +0800450
Jon Salz0697cbf2012-07-04 15:14:04 +0800451 local_path = self.server._resolver.Resolve(self.path)
452 if not local_path or not os.path.exists(local_path):
453 logging.warn("File not found: %s", (local_path or self.path))
454 self.send_response(404)
455 return
Jon Salz258a40c2012-04-19 12:34:01 +0800456
Jon Salz0697cbf2012-07-04 15:14:04 +0800457 self.send_response(200)
458 self.send_header("Content-Type", mime_type[0])
459 self.send_header("Content-Length", os.path.getsize(local_path))
460 self.end_headers()
461 with open(local_path) as f:
462 shutil.copyfileobj(f, self.wfile)
Jon Salz258a40c2012-04-19 12:34:01 +0800463
464
465class ThreadedJSONRPCServer(SocketServer.ThreadingMixIn,
Jon Salz0697cbf2012-07-04 15:14:04 +0800466 SimpleJSONRPCServer.SimpleJSONRPCServer):
467 '''The JSON/RPC server.
Jon Salz258a40c2012-04-19 12:34:01 +0800468
Jon Salz0697cbf2012-07-04 15:14:04 +0800469 Properties:
470 handlers: A map from URLs to callbacks handling them. (The callback
471 takes a single argument: the request to handle.)
472 '''
473 def __init__(self, *args, **kwargs):
474 SimpleJSONRPCServer.SimpleJSONRPCServer.__init__(self, *args, **kwargs)
475 self.handlers = {}
Jon Salz258a40c2012-04-19 12:34:01 +0800476
Jon Salz0697cbf2012-07-04 15:14:04 +0800477 def add_handler(self, url, callback):
478 self.handlers[url] = callback
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800479
480
481def create_server(state_file_path=None, bind_address=None, port=None):
Jon Salz0697cbf2012-07-04 15:14:04 +0800482 '''
483 Creates a FactoryState object and an JSON/RPC server to serve it.
Hung-Te Linf2f78f72012-02-08 19:27:11 +0800484
Jon Salz0697cbf2012-07-04 15:14:04 +0800485 @param state_file_path: The path containing the saved state.
486 @param bind_address: Address to bind to, defaulting to
487 DEFAULT_FACTORY_STATE_BIND_ADDRESS.
488 @param port: Port to bind to, defaulting to DEFAULT_FACTORY_STATE_PORT.
489 @return A tuple of the FactoryState instance and the SimpleJSONRPCServer
490 instance.
491 '''
492 # We have some icons in SVG format, but this isn't recognized in
493 # the standard Python mimetypes set.
494 mimetypes.add_type('image/svg+xml', '.svg')
Jon Salz258a40c2012-04-19 12:34:01 +0800495
Jon Salz0697cbf2012-07-04 15:14:04 +0800496 if not bind_address:
497 bind_address = DEFAULT_FACTORY_STATE_BIND_ADDRESS
498 if not port:
499 port = DEFAULT_FACTORY_STATE_PORT
500 instance = FactoryState(state_file_path)
501 instance._resolver.AddPath(
502 '/',
503 os.path.join(factory.FACTORY_PACKAGE_PATH, 'goofy/static'))
Jon Salzaeb4fd42012-06-05 15:08:30 +0800504
Jon Salz0697cbf2012-07-04 15:14:04 +0800505 server = ThreadedJSONRPCServer(
506 (bind_address, port),
507 requestHandler=MyJSONRPCRequestHandler,
508 logRequests=False)
Jon Salz258a40c2012-04-19 12:34:01 +0800509
Jon Salz0697cbf2012-07-04 15:14:04 +0800510 # Give the server the information it needs to resolve URLs.
511 server._generated_files = instance._generated_files
512 server._generated_data = instance._generated_data
513 server._resolver = instance._resolver
Jon Salz5eee01c2012-06-04 16:07:33 +0800514
Jon Salz0697cbf2012-07-04 15:14:04 +0800515 server.register_introspection_functions()
516 server.register_instance(instance)
517 server.web_socket_handler = None
518 return instance, server