blob: c24fb31c6726b42947e09598fc5f623eec98640d [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
162 2. /sys/class/dmi/id/product_uuid (only available on intel machines)
163 3. MAC address
164 We follow the listed order to generate machine ID, and fallback to the next
165 alternative if the previous doesn't work.
166 """
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800167 if self._mid == Ghost.RANDOM_MID:
Wei-Ning Huangaed90452015-03-23 17:50:21 +0800168 return str(uuid.uuid4())
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800169 elif self._mid:
170 return self._mid
Wei-Ning Huangaed90452015-03-23 17:50:21 +0800171
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800172 try:
173 p = subprocess.Popen('factory device-data | grep mlb_serial_number | '
174 'cut -d " " -f 2', stdout=subprocess.PIPE,
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800175 stderr=subprocess.PIPE, shell=True)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800176 stdout, _ = p.communicate()
177 if stdout == '':
178 raise RuntimeError("empty mlb number")
179 return stdout.strip()
180 except Exception:
181 pass
182
183 try:
184 with open('/sys/class/dmi/id/product_uuid', 'r') as f:
185 return f.read().strip()
186 except Exception:
187 pass
188
189 try:
190 macs = []
191 ifaces = sorted(os.listdir('/sys/class/net'))
192 for iface in ifaces:
193 if iface == 'lo':
194 continue
195
196 with open('/sys/class/net/%s/address' % iface, 'r') as f:
197 macs.append(f.read().strip())
198
199 return ';'.join(macs)
200 except Exception:
201 pass
202
203 raise RuntimeError("can't generate machine ID")
204
205 def Reset(self):
206 """Reset state and clear request handlers."""
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800207 self._reset.clear()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800208 self._buf = ""
209 self._last_ping = 0
210 self._requests = {}
211
212 def SendMessage(self, msg):
213 """Serialize the message and send it through the socket."""
214 self._sock.send(json.dumps(msg) + _SEPARATOR)
215
216 def SendRequest(self, name, args, handler=None,
217 timeout=_REQUEST_TIMEOUT_SECS):
218 if handler and not callable(handler):
219 raise RequestError('Invalid requiest handler for msg "%s"' % name)
220
221 rid = str(uuid.uuid4())
222 msg = {'rid': rid, 'timeout': timeout, 'name': name, 'params': args}
223 self._requests[rid] = [self.Timestamp(), timeout, handler]
224 self.SendMessage(msg)
225
226 def SendResponse(self, omsg, status, params=None):
227 msg = {'rid': omsg['rid'], 'response': status, 'params': params}
228 self.SendMessage(msg)
229
230 def SpawnPTYServer(self, _):
231 """Spawn a PTY server and forward I/O to the TCP socket."""
232 logging.info('SpawnPTYServer: started')
233
234 pid, fd = os.forkpty()
235 if pid == 0:
236 env = os.environ.copy()
237 env['USER'] = os.getenv('USER', 'root')
238 env['HOME'] = os.getenv('HOME', '/root')
239 os.chdir(env['HOME'])
240 os.execve(_SHELL, [_SHELL], env)
241 else:
242 try:
243 while True:
244 rd, _, _ = select.select([self._sock, fd], [], [])
245
246 if fd in rd:
247 self._sock.send(os.read(fd, _BUFSIZE))
248
249 if self._sock in rd:
250 ret = self._sock.recv(_BUFSIZE)
251 if len(ret) == 0:
252 raise RuntimeError("socket closed")
253 os.write(fd, ret)
254 except (OSError, socket.error, RuntimeError):
255 self._sock.close()
256 logging.info('SpawnPTYServer: terminated')
257 sys.exit(0)
258
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800259 def SpawnShellServer(self, _):
260 """Spawn a shell server and forward input/output from/to the TCP socket."""
261 logging.info('SpawnShellServer: started')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800262
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800263 p = subprocess.Popen(self._shell_command, stdin=subprocess.PIPE,
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800264 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
265 shell=True)
266
267 def make_non_block(fd):
268 fl = fcntl.fcntl(fd, fcntl.F_GETFL)
269 fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
270
271 make_non_block(p.stdout)
272 make_non_block(p.stderr)
273
274 try:
275 while True:
276 rd, _, _ = select.select([p.stdout, p.stderr, self._sock], [], [])
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800277 p.poll()
278
279 if p.returncode != None:
280 raise RuntimeError("process complete")
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800281
282 if p.stdout in rd:
283 self._sock.send(p.stdout.read(_BUFSIZE))
284
285 if p.stderr in rd:
286 self._sock.send(p.stderr.read(_BUFSIZE))
287
288 if self._sock in rd:
289 ret = self._sock.recv(_BUFSIZE)
290 if len(ret) == 0:
291 raise RuntimeError("socket closed")
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800292 p.stdin.write(ret)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800293 except (OSError, socket.error, RuntimeError):
294 self._sock.close()
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800295 logging.info('SpawnShellServer: terminated')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800296 sys.exit(0)
297
298
299 def Ping(self):
300 def timeout_handler(x):
301 if x is None:
302 raise PingTimeoutError
303
304 self._last_ping = self.Timestamp()
305 self.SendRequest('ping', {}, timeout_handler, 5)
306
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800307 def HandleRequest(self, msg):
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800308 if msg['name'] == 'terminal':
309 self.SpawnGhost(self.TERMINAL, msg['params']['sid'])
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800310 self.SendResponse(msg, RESPONSE_SUCCESS)
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800311 elif msg['name'] == 'shell':
312 self.SpawnGhost(self.SHELL, msg['params']['sid'],
313 msg['params']['command'])
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800314 self.SendResponse(msg, RESPONSE_SUCCESS)
315
316 def HandleResponse(self, response):
317 rid = str(response['rid'])
318 if rid in self._requests:
319 handler = self._requests[rid][2]
320 del self._requests[rid]
321 if callable(handler):
322 handler(response)
323 else:
324 print(response, self._requests.keys())
325 logging.warning('Recvied unsolicited response, ignored')
326
327 def ParseMessage(self):
328 msgs_json = self._buf.split(_SEPARATOR)
329 self._buf = msgs_json.pop()
330
331 for msg_json in msgs_json:
332 try:
333 msg = json.loads(msg_json)
334 except ValueError:
335 # Ignore mal-formed message.
336 continue
337
338 if 'name' in msg:
339 self.HandleRequest(msg)
340 elif 'response' in msg:
341 self.HandleResponse(msg)
342 else: # Ingnore mal-formed message.
343 pass
344
345 def ScanForTimeoutRequests(self):
346 for rid in self._requests.keys()[:]:
347 request_time, timeout, handler = self._requests[rid]
348 if self.Timestamp() - request_time > timeout:
349 handler(None)
350 del self._requests[rid]
351
352 def Listen(self):
353 try:
354 while True:
355 rds, _, _ = select.select([self._sock], [], [], _PING_INTERVAL / 2)
356
357 if self._sock in rds:
358 self._buf += self._sock.recv(_BUFSIZE)
359 self.ParseMessage()
360
361 if self.Timestamp() - self._last_ping > _PING_INTERVAL:
362 self.Ping()
363 self.ScanForTimeoutRequests()
364
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800365 if self._reset.is_set():
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800366 self.Reset()
367 break
368 except socket.error:
369 raise RuntimeError('Connection dropped')
370 except PingTimeoutError:
371 raise RuntimeError('Connection timeout')
372 finally:
373 self._sock.close()
374
375 self._queue.put('resume')
376
377 if self._mode != Ghost.AGENT:
378 sys.exit(1)
379
380 def Register(self):
381 non_local = {}
382 for addr in self._overlord_addrs:
383 non_local['addr'] = addr
384 def registered(response):
385 if response is None:
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800386 self._reset.set()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800387 raise RuntimeError('Register request timeout')
388 logging.info('Registered with Overlord at %s:%d', *non_local['addr'])
389 self._queue.put("pause", True)
390
391 try:
392 logging.info('Trying %s:%d ...', *addr)
393 self.Reset()
394 self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
395 self._sock.settimeout(_PING_TIMEOUT)
396 self._sock.connect(addr)
397
398 logging.info('Connection established, registering...')
399 handler = {
400 Ghost.AGENT: registered,
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800401 Ghost.TERMINAL: self.SpawnPTYServer,
402 Ghost.SHELL: self.SpawnShellServer
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800403 }[self._mode]
404
405 # Machine ID may change if MAC address is used (USB-ethernet dongle
406 # plugged/unplugged)
407 self._machine_id = self.GetMachineID()
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800408 self.SendRequest('register',
409 {'mode': self._mode, 'mid': self._machine_id,
410 'cid': self._client_id,
411 'properties': self._properties}, handler)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800412 except socket.error:
413 pass
414 else:
415 self._sock.settimeout(None)
Wei-Ning Huangad330c52015-03-12 20:34:18 +0800416 self._connected_addr = addr
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800417 self.Listen()
418
419 raise RuntimeError("Cannot connect to any server")
420
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800421 def Reconnect(self):
422 logging.info('Received reconnect request from RPC server, reconnecting...')
423 self._reset.set()
424
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800425 def StartLanDiscovery(self):
426 """Start to listen to LAN discovery packet at
427 _OVERLORD_LAN_DISCOVERY_PORT."""
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800428
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800429 def thread_func():
430 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
431 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
432 s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800433 try:
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800434 s.bind(('0.0.0.0', _OVERLORD_LAN_DISCOVERY_PORT))
435 except socket.error as e:
436 logging.error("LAN discovery: %s, abort", e)
437 return
438
439 logging.info('LAN Discovery: started')
440 while True:
441 rd, _, _ = select.select([s], [], [], 1)
442
443 if s in rd:
444 data, source_addr = s.recvfrom(_BUFSIZE)
445 parts = data.split()
446 if parts[0] == 'OVERLORD':
447 ip, port = parts[1].split(':')
448 if not ip:
449 ip = source_addr[0]
450 self._queue.put((ip, int(port)), True)
451
452 try:
453 obj = self._queue.get(False)
454 except Queue.Empty:
455 pass
456 else:
457 if type(obj) is not str:
458 self._queue.put(obj)
459 elif obj == 'pause':
460 logging.info('LAN Discovery: paused')
461 while obj != 'resume':
462 obj = self._queue.get(True)
463 logging.info('LAN Discovery: resumed')
464
465 t = threading.Thread(target=thread_func)
466 t.daemon = True
467 t.start()
468
469 def StartRPCServer(self):
470 rpc_server = SimpleJSONRPCServer((_DEFAULT_BIND_ADDRESS, _GHOST_RPC_PORT),
471 logRequests=False)
472 rpc_server.register_function(self.Reconnect, 'Reconnect')
473 t = threading.Thread(target=rpc_server.serve_forever)
474 t.daemon = True
475 t.start()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800476
Wei-Ning Huang829e0c82015-05-26 14:37:23 +0800477 def ScanServer(self):
478 for meth in [self.GetGateWayIP, self.GetShopfloorIP]:
479 for addr in [(x, _OVERLORD_PORT) for x in meth()]:
480 if addr not in self._overlord_addrs:
481 self._overlord_addrs.append(addr)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800482
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800483 def Start(self, lan_disc=False, rpc_server=False):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800484 logging.info('%s started', self.MODE_NAME[self._mode])
485 logging.info('MID: %s', self._machine_id)
486 logging.info('CID: %s', self._client_id)
487
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800488 if lan_disc:
489 self.StartLanDiscovery()
490
491 if rpc_server:
492 self.StartRPCServer()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800493
494 try:
495 while True:
496 try:
497 addr = self._queue.get(False)
498 except Queue.Empty:
499 pass
500 else:
501 if type(addr) == tuple and addr not in self._overlord_addrs:
502 logging.info('LAN Discovery: got overlord address %s:%d', *addr)
503 self._overlord_addrs.append(addr)
504
505 try:
Wei-Ning Huang829e0c82015-05-26 14:37:23 +0800506 self.ScanServer()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800507 self.Register()
508 except Exception as e:
509 logging.info(str(e) + ', retrying in %ds' % _RETRY_INTERVAL)
510 time.sleep(_RETRY_INTERVAL)
511
512 self.Reset()
513 except KeyboardInterrupt:
514 logging.error('Received keyboard interrupt, quit')
515 sys.exit(0)
516
517
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800518def GhostRPCServer():
519 return jsonrpclib.Server('http://localhost:%d' % _GHOST_RPC_PORT)
520
521
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800522def main():
523 logger = logging.getLogger()
524 logger.setLevel(logging.INFO)
525
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800526 parser = argparse.ArgumentParser()
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800527 parser.add_argument('--mid', metavar='MID', dest='mid', action='store',
528 default=None, help='use MID as machine ID')
529 parser.add_argument('--rand-mid', dest='mid', action='store_const',
530 const=Ghost.RANDOM_MID, help='use random machine ID')
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800531 parser.add_argument('--no-lan-disc', dest='lan_disc', action='store_false',
532 default=True, help='disable LAN discovery')
533 parser.add_argument('--no-rpc-server', dest='rpc_server',
534 action='store_false', default=True,
535 help='disable RPC server')
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800536 parser.add_argument("--prop-file", dest="prop_file", type=str, default=None,
537 help='file containing the JSON representation of client '
538 'properties')
539 parser.add_argument('overlord_ip', metavar='OVERLORD_IP', type=str,
540 nargs='*', help='overlord server address')
541 args = parser.parse_args()
542
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800543 addrs = [('localhost', _OVERLORD_PORT)]
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800544 addrs += [(x, _OVERLORD_PORT) for x in args.overlord_ip]
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800545
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800546 g = Ghost(addrs, Ghost.AGENT, args.mid)
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800547 if args.prop_file:
548 g.LoadPropertiesFromFile(args.prop_file)
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800549 g.Start(args.lan_disc, args.rpc_server)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800550
551
552if __name__ == '__main__':
553 main()