blob: 3e2954905841f638fbe2f65ed937526b7a782871 [file] [log] [blame]
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08001#!/usr/bin/python -u
2# -*- coding: utf-8 -*-
3#
4# Copyright 2015 The Chromium OS Authors. All rights reserved.
5# Use of this source code is governed by a BSD-style license that can be
6# found in the LICENSE file.
7
Wei-Ning Huang7d029b12015-03-06 10:32:15 +08008import argparse
Wei-Ning Huang1cea6112015-03-02 12:45:34 +08009import fcntl
10import json
11import logging
12import os
13import Queue
Wei-Ning Huang829e0c82015-05-26 14:37:23 +080014import re
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080015import select
16import socket
17import subprocess
18import sys
19import threading
20import time
21import uuid
22
Wei-Ning Huang2132de32015-04-13 17:24:38 +080023import jsonrpclib
24from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer
25
26
27_GHOST_RPC_PORT = 4499
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080028
29_OVERLORD_PORT = 4455
30_OVERLORD_LAN_DISCOVERY_PORT = 4456
31
32_BUFSIZE = 8192
33_RETRY_INTERVAL = 2
34_SEPARATOR = '\r\n'
35_PING_TIMEOUT = 3
36_PING_INTERVAL = 5
37_REQUEST_TIMEOUT_SECS = 60
38_SHELL = os.getenv('SHELL', '/bin/bash')
Wei-Ning Huang2132de32015-04-13 17:24:38 +080039_DEFAULT_BIND_ADDRESS = '0.0.0.0'
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080040
41RESPONSE_SUCCESS = 'success'
42RESPONSE_FAILED = 'failed'
43
44
45class PingTimeoutError(Exception):
46 pass
47
48
49class RequestError(Exception):
50 pass
51
52
53class Ghost(object):
54 """Ghost implements the client protocol of Overlord.
55
56 Ghost provide terminal/shell/logcat functionality and manages the client
57 side connectivity.
58 """
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +080059 NONE, AGENT, TERMINAL, SHELL, LOGCAT = range(5)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080060
61 MODE_NAME = {
62 NONE: 'NONE',
63 AGENT: 'Agent',
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +080064 TERMINAL: 'Terminal',
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080065 SHELL: 'Shell',
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +080066 LOGCAT: 'Logcat'
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080067 }
68
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +080069 RANDOM_MID = '##random_mid##'
70
71 def __init__(self, overlord_addrs, mode=AGENT, mid=None, sid=None,
Wei-Ning Huangaed90452015-03-23 17:50:21 +080072 command=None):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080073 """Constructor.
74
75 Args:
76 overlord_addrs: a list of possible address of overlord.
77 mode: client mode, either AGENT, SHELL or LOGCAT
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +080078 mid: a str to set for machine ID. If mid equals Ghost.RANDOM_MID, machine
79 id is randomly generated.
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080080 sid: session id. If the connection is requested by overlord, sid should
81 be set to the corresponding session id assigned by overlord.
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +080082 shell: the command to execute when we are in SHELL mode.
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080083 """
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +080084 assert mode in [Ghost.AGENT, Ghost.TERMINAL, Ghost.SHELL]
85 if mode == Ghost.SHELL:
86 assert command is not None
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080087
88 self._overlord_addrs = overlord_addrs
Wei-Ning Huangad330c52015-03-12 20:34:18 +080089 self._connected_addr = None
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080090 self._mode = mode
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +080091 self._mid = mid
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080092 self._sock = None
93 self._machine_id = self.GetMachineID()
94 self._client_id = sid if sid is not None else str(uuid.uuid4())
Wei-Ning Huang7d029b12015-03-06 10:32:15 +080095 self._properties = {}
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +080096 self._shell_command = command
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080097 self._buf = ''
98 self._requests = {}
Wei-Ning Huang2132de32015-04-13 17:24:38 +080099 self._reset = threading.Event()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800100 self._last_ping = 0
101 self._queue = Queue.Queue()
102
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800103 def LoadPropertiesFromFile(self, filename):
104 try:
105 with open(filename, 'r') as f:
106 self._properties = json.loads(f.read())
107 except Exception as e:
108 logging.exception('LoadPropertiesFromFile: ' + str(e))
109
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800110 def SpawnGhost(self, mode, sid, command=None):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800111 """Spawn a child ghost with specific mode.
112
113 Returns:
114 The spawned child process pid.
115 """
116 pid = os.fork()
117 if pid == 0:
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800118 g = Ghost([self._connected_addr], mode, Ghost.RANDOM_MID, sid, command)
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800119 g.Start()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800120 sys.exit(0)
121 else:
122 return pid
123
124 def Timestamp(self):
125 return int(time.time())
126
127 def GetGateWayIP(self):
128 with open('/proc/net/route', 'r') as f:
129 lines = f.readlines()
130
131 ips = []
132 for line in lines:
133 parts = line.split('\t')
134 if parts[2] == '00000000':
135 continue
136
137 try:
138 h = parts[2].decode('hex')
139 ips.append('%d.%d.%d.%d' % tuple(ord(x) for x in reversed(h)))
140 except TypeError:
141 pass
142
143 return ips
144
Wei-Ning Huang829e0c82015-05-26 14:37:23 +0800145 def GetShopfloorIP(self):
146 try:
147 import factory_common # pylint: disable=W0612
148 from cros.factory.test import shopfloor
149
150 url = shopfloor.get_server_url()
151 match = re.match(r'^https?://(.*):.*$', url)
152 if match:
153 return [match.group(1)]
154 except Exception:
155 pass
156 return []
157
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800158 def GetMachineID(self):
159 """Generates machine-dependent ID string for a machine.
160 There are many ways to generate a machine ID:
161 1. factory device-data
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800162 2. factory device_id
163 3. /sys/class/dmi/id/product_uuid (only available on intel machines)
164 4. MAC address
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800165 We follow the listed order to generate machine ID, and fallback to the next
166 alternative if the previous doesn't work.
167 """
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800168 if self._mid == Ghost.RANDOM_MID:
Wei-Ning Huangaed90452015-03-23 17:50:21 +0800169 return str(uuid.uuid4())
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800170 elif self._mid:
171 return self._mid
Wei-Ning Huangaed90452015-03-23 17:50:21 +0800172
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800173 # Try factory device data
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800174 try:
175 p = subprocess.Popen('factory device-data | grep mlb_serial_number | '
176 'cut -d " " -f 2', stdout=subprocess.PIPE,
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800177 stderr=subprocess.PIPE, shell=True)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800178 stdout, _ = p.communicate()
179 if stdout == '':
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800180 raise RuntimeError('empty mlb number')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800181 return stdout.strip()
182 except Exception:
183 pass
184
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800185 # Try factory device id
186 try:
187 import factory_common # pylint: disable=W0612
188 from cros.factory.test import event_log
189 with open(event_log.DEVICE_ID_PATH) as f:
190 return f.read()
191 except Exception:
192 pass
193
194 # Try DMI product UUID
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800195 try:
196 with open('/sys/class/dmi/id/product_uuid', 'r') as f:
197 return f.read().strip()
198 except Exception:
199 pass
200
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800201 # Use MAC address if non is available
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800202 try:
203 macs = []
204 ifaces = sorted(os.listdir('/sys/class/net'))
205 for iface in ifaces:
206 if iface == 'lo':
207 continue
208
209 with open('/sys/class/net/%s/address' % iface, 'r') as f:
210 macs.append(f.read().strip())
211
212 return ';'.join(macs)
213 except Exception:
214 pass
215
216 raise RuntimeError("can't generate machine ID")
217
218 def Reset(self):
219 """Reset state and clear request handlers."""
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800220 self._reset.clear()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800221 self._buf = ""
222 self._last_ping = 0
223 self._requests = {}
224
225 def SendMessage(self, msg):
226 """Serialize the message and send it through the socket."""
227 self._sock.send(json.dumps(msg) + _SEPARATOR)
228
229 def SendRequest(self, name, args, handler=None,
230 timeout=_REQUEST_TIMEOUT_SECS):
231 if handler and not callable(handler):
232 raise RequestError('Invalid requiest handler for msg "%s"' % name)
233
234 rid = str(uuid.uuid4())
235 msg = {'rid': rid, 'timeout': timeout, 'name': name, 'params': args}
236 self._requests[rid] = [self.Timestamp(), timeout, handler]
237 self.SendMessage(msg)
238
239 def SendResponse(self, omsg, status, params=None):
240 msg = {'rid': omsg['rid'], 'response': status, 'params': params}
241 self.SendMessage(msg)
242
243 def SpawnPTYServer(self, _):
244 """Spawn a PTY server and forward I/O to the TCP socket."""
245 logging.info('SpawnPTYServer: started')
246
247 pid, fd = os.forkpty()
248 if pid == 0:
249 env = os.environ.copy()
250 env['USER'] = os.getenv('USER', 'root')
251 env['HOME'] = os.getenv('HOME', '/root')
252 os.chdir(env['HOME'])
253 os.execve(_SHELL, [_SHELL], env)
254 else:
255 try:
256 while True:
257 rd, _, _ = select.select([self._sock, fd], [], [])
258
259 if fd in rd:
260 self._sock.send(os.read(fd, _BUFSIZE))
261
262 if self._sock in rd:
263 ret = self._sock.recv(_BUFSIZE)
264 if len(ret) == 0:
265 raise RuntimeError("socket closed")
266 os.write(fd, ret)
267 except (OSError, socket.error, RuntimeError):
268 self._sock.close()
269 logging.info('SpawnPTYServer: terminated')
270 sys.exit(0)
271
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800272 def SpawnShellServer(self, _):
273 """Spawn a shell server and forward input/output from/to the TCP socket."""
274 logging.info('SpawnShellServer: started')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800275
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800276 p = subprocess.Popen(self._shell_command, stdin=subprocess.PIPE,
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800277 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
278 shell=True)
279
280 def make_non_block(fd):
281 fl = fcntl.fcntl(fd, fcntl.F_GETFL)
282 fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
283
284 make_non_block(p.stdout)
285 make_non_block(p.stderr)
286
287 try:
288 while True:
289 rd, _, _ = select.select([p.stdout, p.stderr, self._sock], [], [])
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800290 p.poll()
291
292 if p.returncode != None:
293 raise RuntimeError("process complete")
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800294
295 if p.stdout in rd:
296 self._sock.send(p.stdout.read(_BUFSIZE))
297
298 if p.stderr in rd:
299 self._sock.send(p.stderr.read(_BUFSIZE))
300
301 if self._sock in rd:
302 ret = self._sock.recv(_BUFSIZE)
303 if len(ret) == 0:
304 raise RuntimeError("socket closed")
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800305 p.stdin.write(ret)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800306 except (OSError, socket.error, RuntimeError):
307 self._sock.close()
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800308 logging.info('SpawnShellServer: terminated')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800309 sys.exit(0)
310
311
312 def Ping(self):
313 def timeout_handler(x):
314 if x is None:
315 raise PingTimeoutError
316
317 self._last_ping = self.Timestamp()
318 self.SendRequest('ping', {}, timeout_handler, 5)
319
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800320 def HandleRequest(self, msg):
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800321 if msg['name'] == 'terminal':
322 self.SpawnGhost(self.TERMINAL, msg['params']['sid'])
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800323 self.SendResponse(msg, RESPONSE_SUCCESS)
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800324 elif msg['name'] == 'shell':
325 self.SpawnGhost(self.SHELL, msg['params']['sid'],
326 msg['params']['command'])
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800327 self.SendResponse(msg, RESPONSE_SUCCESS)
328
329 def HandleResponse(self, response):
330 rid = str(response['rid'])
331 if rid in self._requests:
332 handler = self._requests[rid][2]
333 del self._requests[rid]
334 if callable(handler):
335 handler(response)
336 else:
337 print(response, self._requests.keys())
338 logging.warning('Recvied unsolicited response, ignored')
339
340 def ParseMessage(self):
341 msgs_json = self._buf.split(_SEPARATOR)
342 self._buf = msgs_json.pop()
343
344 for msg_json in msgs_json:
345 try:
346 msg = json.loads(msg_json)
347 except ValueError:
348 # Ignore mal-formed message.
349 continue
350
351 if 'name' in msg:
352 self.HandleRequest(msg)
353 elif 'response' in msg:
354 self.HandleResponse(msg)
355 else: # Ingnore mal-formed message.
356 pass
357
358 def ScanForTimeoutRequests(self):
359 for rid in self._requests.keys()[:]:
360 request_time, timeout, handler = self._requests[rid]
361 if self.Timestamp() - request_time > timeout:
362 handler(None)
363 del self._requests[rid]
364
365 def Listen(self):
366 try:
367 while True:
368 rds, _, _ = select.select([self._sock], [], [], _PING_INTERVAL / 2)
369
370 if self._sock in rds:
371 self._buf += self._sock.recv(_BUFSIZE)
372 self.ParseMessage()
373
374 if self.Timestamp() - self._last_ping > _PING_INTERVAL:
375 self.Ping()
376 self.ScanForTimeoutRequests()
377
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800378 if self._reset.is_set():
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800379 self.Reset()
380 break
381 except socket.error:
382 raise RuntimeError('Connection dropped')
383 except PingTimeoutError:
384 raise RuntimeError('Connection timeout')
385 finally:
386 self._sock.close()
387
388 self._queue.put('resume')
389
390 if self._mode != Ghost.AGENT:
391 sys.exit(1)
392
393 def Register(self):
394 non_local = {}
395 for addr in self._overlord_addrs:
396 non_local['addr'] = addr
397 def registered(response):
398 if response is None:
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800399 self._reset.set()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800400 raise RuntimeError('Register request timeout')
401 logging.info('Registered with Overlord at %s:%d', *non_local['addr'])
402 self._queue.put("pause", True)
403
404 try:
405 logging.info('Trying %s:%d ...', *addr)
406 self.Reset()
407 self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
408 self._sock.settimeout(_PING_TIMEOUT)
409 self._sock.connect(addr)
410
411 logging.info('Connection established, registering...')
412 handler = {
413 Ghost.AGENT: registered,
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800414 Ghost.TERMINAL: self.SpawnPTYServer,
415 Ghost.SHELL: self.SpawnShellServer
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800416 }[self._mode]
417
418 # Machine ID may change if MAC address is used (USB-ethernet dongle
419 # plugged/unplugged)
420 self._machine_id = self.GetMachineID()
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800421 self.SendRequest('register',
422 {'mode': self._mode, 'mid': self._machine_id,
423 'cid': self._client_id,
424 'properties': self._properties}, handler)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800425 except socket.error:
426 pass
427 else:
428 self._sock.settimeout(None)
Wei-Ning Huangad330c52015-03-12 20:34:18 +0800429 self._connected_addr = addr
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800430 self.Listen()
431
432 raise RuntimeError("Cannot connect to any server")
433
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800434 def Reconnect(self):
435 logging.info('Received reconnect request from RPC server, reconnecting...')
436 self._reset.set()
437
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800438 def StartLanDiscovery(self):
439 """Start to listen to LAN discovery packet at
440 _OVERLORD_LAN_DISCOVERY_PORT."""
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800441
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800442 def thread_func():
443 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
444 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
445 s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800446 try:
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800447 s.bind(('0.0.0.0', _OVERLORD_LAN_DISCOVERY_PORT))
448 except socket.error as e:
449 logging.error("LAN discovery: %s, abort", e)
450 return
451
452 logging.info('LAN Discovery: started')
453 while True:
454 rd, _, _ = select.select([s], [], [], 1)
455
456 if s in rd:
457 data, source_addr = s.recvfrom(_BUFSIZE)
458 parts = data.split()
459 if parts[0] == 'OVERLORD':
460 ip, port = parts[1].split(':')
461 if not ip:
462 ip = source_addr[0]
463 self._queue.put((ip, int(port)), True)
464
465 try:
466 obj = self._queue.get(False)
467 except Queue.Empty:
468 pass
469 else:
470 if type(obj) is not str:
471 self._queue.put(obj)
472 elif obj == 'pause':
473 logging.info('LAN Discovery: paused')
474 while obj != 'resume':
475 obj = self._queue.get(True)
476 logging.info('LAN Discovery: resumed')
477
478 t = threading.Thread(target=thread_func)
479 t.daemon = True
480 t.start()
481
482 def StartRPCServer(self):
483 rpc_server = SimpleJSONRPCServer((_DEFAULT_BIND_ADDRESS, _GHOST_RPC_PORT),
484 logRequests=False)
485 rpc_server.register_function(self.Reconnect, 'Reconnect')
486 t = threading.Thread(target=rpc_server.serve_forever)
487 t.daemon = True
488 t.start()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800489
Wei-Ning Huang829e0c82015-05-26 14:37:23 +0800490 def ScanServer(self):
491 for meth in [self.GetGateWayIP, self.GetShopfloorIP]:
492 for addr in [(x, _OVERLORD_PORT) for x in meth()]:
493 if addr not in self._overlord_addrs:
494 self._overlord_addrs.append(addr)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800495
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800496 def Start(self, lan_disc=False, rpc_server=False):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800497 logging.info('%s started', self.MODE_NAME[self._mode])
498 logging.info('MID: %s', self._machine_id)
499 logging.info('CID: %s', self._client_id)
500
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800501 if lan_disc:
502 self.StartLanDiscovery()
503
504 if rpc_server:
505 self.StartRPCServer()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800506
507 try:
508 while True:
509 try:
510 addr = self._queue.get(False)
511 except Queue.Empty:
512 pass
513 else:
514 if type(addr) == tuple and addr not in self._overlord_addrs:
515 logging.info('LAN Discovery: got overlord address %s:%d', *addr)
516 self._overlord_addrs.append(addr)
517
518 try:
Wei-Ning Huang829e0c82015-05-26 14:37:23 +0800519 self.ScanServer()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800520 self.Register()
521 except Exception as e:
522 logging.info(str(e) + ', retrying in %ds' % _RETRY_INTERVAL)
523 time.sleep(_RETRY_INTERVAL)
524
525 self.Reset()
526 except KeyboardInterrupt:
527 logging.error('Received keyboard interrupt, quit')
528 sys.exit(0)
529
530
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800531def GhostRPCServer():
532 return jsonrpclib.Server('http://localhost:%d' % _GHOST_RPC_PORT)
533
534
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800535def main():
536 logger = logging.getLogger()
537 logger.setLevel(logging.INFO)
538
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800539 parser = argparse.ArgumentParser()
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800540 parser.add_argument('--mid', metavar='MID', dest='mid', action='store',
541 default=None, help='use MID as machine ID')
542 parser.add_argument('--rand-mid', dest='mid', action='store_const',
543 const=Ghost.RANDOM_MID, help='use random machine ID')
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800544 parser.add_argument('--no-lan-disc', dest='lan_disc', action='store_false',
545 default=True, help='disable LAN discovery')
546 parser.add_argument('--no-rpc-server', dest='rpc_server',
547 action='store_false', default=True,
548 help='disable RPC server')
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800549 parser.add_argument("--prop-file", dest="prop_file", type=str, default=None,
550 help='file containing the JSON representation of client '
551 'properties')
552 parser.add_argument('overlord_ip', metavar='OVERLORD_IP', type=str,
553 nargs='*', help='overlord server address')
554 args = parser.parse_args()
555
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800556 addrs = [('localhost', _OVERLORD_PORT)]
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800557 addrs += [(x, _OVERLORD_PORT) for x in args.overlord_ip]
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800558
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800559 g = Ghost(addrs, Ghost.AGENT, args.mid)
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800560 if args.prop_file:
561 g.LoadPropertiesFromFile(args.prop_file)
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800562 g.Start(args.lan_disc, args.rpc_server)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800563
564
565if __name__ == '__main__':
566 main()