blob: 38614db6cfe37feb0360155f7c8d993fa941eaa8 [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
Moja Hsuc9ecc8b2015-07-13 11:39:17 +080019import struct
20import termios
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080021import threading
22import time
23import uuid
24
Wei-Ning Huang2132de32015-04-13 17:24:38 +080025import jsonrpclib
26from jsonrpclib.SimpleJSONRPCServer import SimpleJSONRPCServer
27
28
29_GHOST_RPC_PORT = 4499
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080030
31_OVERLORD_PORT = 4455
32_OVERLORD_LAN_DISCOVERY_PORT = 4456
33
34_BUFSIZE = 8192
35_RETRY_INTERVAL = 2
36_SEPARATOR = '\r\n'
37_PING_TIMEOUT = 3
38_PING_INTERVAL = 5
39_REQUEST_TIMEOUT_SECS = 60
40_SHELL = os.getenv('SHELL', '/bin/bash')
Wei-Ning Huang2132de32015-04-13 17:24:38 +080041_DEFAULT_BIND_ADDRESS = '0.0.0.0'
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080042
Moja Hsuc9ecc8b2015-07-13 11:39:17 +080043_CONTROL_START = 128
44_CONTROL_END = 129
45
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080046RESPONSE_SUCCESS = 'success'
47RESPONSE_FAILED = 'failed'
48
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080049class PingTimeoutError(Exception):
50 pass
51
52
53class RequestError(Exception):
54 pass
55
56
57class Ghost(object):
58 """Ghost implements the client protocol of Overlord.
59
60 Ghost provide terminal/shell/logcat functionality and manages the client
61 side connectivity.
62 """
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +080063 NONE, AGENT, TERMINAL, SHELL, LOGCAT = range(5)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080064
65 MODE_NAME = {
66 NONE: 'NONE',
67 AGENT: 'Agent',
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +080068 TERMINAL: 'Terminal',
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080069 SHELL: 'Shell',
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +080070 LOGCAT: 'Logcat'
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080071 }
72
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +080073 RANDOM_MID = '##random_mid##'
74
75 def __init__(self, overlord_addrs, mode=AGENT, mid=None, sid=None,
Wei-Ning Huangaed90452015-03-23 17:50:21 +080076 command=None):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080077 """Constructor.
78
79 Args:
80 overlord_addrs: a list of possible address of overlord.
81 mode: client mode, either AGENT, SHELL or LOGCAT
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +080082 mid: a str to set for machine ID. If mid equals Ghost.RANDOM_MID, machine
83 id is randomly generated.
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080084 sid: session id. If the connection is requested by overlord, sid should
85 be set to the corresponding session id assigned by overlord.
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +080086 shell: the command to execute when we are in SHELL mode.
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080087 """
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +080088 assert mode in [Ghost.AGENT, Ghost.TERMINAL, Ghost.SHELL]
89 if mode == Ghost.SHELL:
90 assert command is not None
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080091
92 self._overlord_addrs = overlord_addrs
Wei-Ning Huangad330c52015-03-12 20:34:18 +080093 self._connected_addr = None
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080094 self._mode = mode
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +080095 self._mid = mid
Wei-Ning Huang1cea6112015-03-02 12:45:34 +080096 self._sock = None
97 self._machine_id = self.GetMachineID()
98 self._client_id = sid if sid is not None else str(uuid.uuid4())
Wei-Ning Huang7d029b12015-03-06 10:32:15 +080099 self._properties = {}
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800100 self._shell_command = command
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800101 self._buf = ''
102 self._requests = {}
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800103 self._reset = threading.Event()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800104 self._last_ping = 0
105 self._queue = Queue.Queue()
106
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800107 def LoadPropertiesFromFile(self, filename):
108 try:
109 with open(filename, 'r') as f:
110 self._properties = json.loads(f.read())
111 except Exception as e:
112 logging.exception('LoadPropertiesFromFile: ' + str(e))
113
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800114 def SpawnGhost(self, mode, sid, command=None):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800115 """Spawn a child ghost with specific mode.
116
117 Returns:
118 The spawned child process pid.
119 """
120 pid = os.fork()
121 if pid == 0:
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800122 g = Ghost([self._connected_addr], mode, Ghost.RANDOM_MID, sid, command)
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800123 g.Start()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800124 sys.exit(0)
125 else:
126 return pid
127
128 def Timestamp(self):
129 return int(time.time())
130
131 def GetGateWayIP(self):
132 with open('/proc/net/route', 'r') as f:
133 lines = f.readlines()
134
135 ips = []
136 for line in lines:
137 parts = line.split('\t')
138 if parts[2] == '00000000':
139 continue
140
141 try:
142 h = parts[2].decode('hex')
143 ips.append('%d.%d.%d.%d' % tuple(ord(x) for x in reversed(h)))
144 except TypeError:
145 pass
146
147 return ips
148
Wei-Ning Huang829e0c82015-05-26 14:37:23 +0800149 def GetShopfloorIP(self):
150 try:
151 import factory_common # pylint: disable=W0612
152 from cros.factory.test import shopfloor
153
154 url = shopfloor.get_server_url()
155 match = re.match(r'^https?://(.*):.*$', url)
156 if match:
157 return [match.group(1)]
158 except Exception:
159 pass
160 return []
161
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800162 def GetMachineID(self):
163 """Generates machine-dependent ID string for a machine.
164 There are many ways to generate a machine ID:
165 1. factory device-data
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800166 2. factory device_id
167 3. /sys/class/dmi/id/product_uuid (only available on intel machines)
168 4. MAC address
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800169 We follow the listed order to generate machine ID, and fallback to the next
170 alternative if the previous doesn't work.
171 """
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800172 if self._mid == Ghost.RANDOM_MID:
Wei-Ning Huangaed90452015-03-23 17:50:21 +0800173 return str(uuid.uuid4())
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800174 elif self._mid:
175 return self._mid
Wei-Ning Huangaed90452015-03-23 17:50:21 +0800176
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800177 # Try factory device data
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800178 try:
179 p = subprocess.Popen('factory device-data | grep mlb_serial_number | '
180 'cut -d " " -f 2', stdout=subprocess.PIPE,
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800181 stderr=subprocess.PIPE, shell=True)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800182 stdout, _ = p.communicate()
183 if stdout == '':
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800184 raise RuntimeError('empty mlb number')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800185 return stdout.strip()
186 except Exception:
187 pass
188
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800189 # Try factory device id
190 try:
191 import factory_common # pylint: disable=W0612
192 from cros.factory.test import event_log
193 with open(event_log.DEVICE_ID_PATH) as f:
194 return f.read()
195 except Exception:
196 pass
197
198 # Try DMI product UUID
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800199 try:
200 with open('/sys/class/dmi/id/product_uuid', 'r') as f:
201 return f.read().strip()
202 except Exception:
203 pass
204
Wei-Ning Huang1d7603b2015-07-03 17:38:56 +0800205 # Use MAC address if non is available
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800206 try:
207 macs = []
208 ifaces = sorted(os.listdir('/sys/class/net'))
209 for iface in ifaces:
210 if iface == 'lo':
211 continue
212
213 with open('/sys/class/net/%s/address' % iface, 'r') as f:
214 macs.append(f.read().strip())
215
216 return ';'.join(macs)
217 except Exception:
218 pass
219
Moja Hsuc9ecc8b2015-07-13 11:39:17 +0800220 raise RuntimeError('can\'t generate machine ID')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800221
222 def Reset(self):
223 """Reset state and clear request handlers."""
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800224 self._reset.clear()
Moja Hsuc9ecc8b2015-07-13 11:39:17 +0800225 self._buf = ''
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800226 self._last_ping = 0
227 self._requests = {}
228
229 def SendMessage(self, msg):
230 """Serialize the message and send it through the socket."""
231 self._sock.send(json.dumps(msg) + _SEPARATOR)
232
233 def SendRequest(self, name, args, handler=None,
234 timeout=_REQUEST_TIMEOUT_SECS):
235 if handler and not callable(handler):
236 raise RequestError('Invalid requiest handler for msg "%s"' % name)
237
238 rid = str(uuid.uuid4())
239 msg = {'rid': rid, 'timeout': timeout, 'name': name, 'params': args}
240 self._requests[rid] = [self.Timestamp(), timeout, handler]
241 self.SendMessage(msg)
242
243 def SendResponse(self, omsg, status, params=None):
244 msg = {'rid': omsg['rid'], 'response': status, 'params': params}
245 self.SendMessage(msg)
246
Moja Hsuc9ecc8b2015-07-13 11:39:17 +0800247 def HandlePTYControl(self, fd, control_string):
248 msg = json.loads(control_string)
249 command = msg['command']
250 params = msg['params']
251 if command == 'resize':
252 # some error happened on websocket
253 if len(params) != 2:
254 return
255 winsize = struct.pack('HHHH', params[0], params[1], 0, 0)
256 fcntl.ioctl(fd, termios.TIOCSWINSZ, winsize)
257 else:
258 logging.warn('Invalid request command "%s"', command)
259
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800260 def SpawnPTYServer(self, _):
261 """Spawn a PTY server and forward I/O to the TCP socket."""
262 logging.info('SpawnPTYServer: started')
263
264 pid, fd = os.forkpty()
265 if pid == 0:
266 env = os.environ.copy()
267 env['USER'] = os.getenv('USER', 'root')
268 env['HOME'] = os.getenv('HOME', '/root')
269 os.chdir(env['HOME'])
270 os.execve(_SHELL, [_SHELL], env)
271 else:
272 try:
Moja Hsuc9ecc8b2015-07-13 11:39:17 +0800273 control_state = None
274 control_string = ''
275 write_buffer = ''
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800276 while True:
277 rd, _, _ = select.select([self._sock, fd], [], [])
278
279 if fd in rd:
280 self._sock.send(os.read(fd, _BUFSIZE))
281
282 if self._sock in rd:
283 ret = self._sock.recv(_BUFSIZE)
284 if len(ret) == 0:
Moja Hsuc9ecc8b2015-07-13 11:39:17 +0800285 raise RuntimeError('socket closed')
286 while ret:
287 if control_state:
288 if chr(_CONTROL_END) in ret:
289 index = ret.index(chr(_CONTROL_END))
290 control_string += ret[:index]
291 self.HandlePTYControl(fd, control_string)
292 control_state = None
293 control_string = ''
294 ret = ret[index+1:]
295 else:
296 control_string += ret
297 ret = ''
298 else:
299 if chr(_CONTROL_START) in ret:
300 control_state = _CONTROL_START
301 index = ret.index(chr(_CONTROL_START))
302 write_buffer += ret[:index]
303 ret = ret[index+1:]
304 else:
305 write_buffer += ret
306 ret = ''
307 if write_buffer:
308 os.write(fd, write_buffer)
309 write_buffer = ''
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800310 except (OSError, socket.error, RuntimeError):
311 self._sock.close()
312 logging.info('SpawnPTYServer: terminated')
313 sys.exit(0)
314
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800315 def SpawnShellServer(self, _):
316 """Spawn a shell server and forward input/output from/to the TCP socket."""
317 logging.info('SpawnShellServer: started')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800318
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800319 p = subprocess.Popen(self._shell_command, stdin=subprocess.PIPE,
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800320 stdout=subprocess.PIPE, stderr=subprocess.PIPE,
321 shell=True)
322
323 def make_non_block(fd):
324 fl = fcntl.fcntl(fd, fcntl.F_GETFL)
325 fcntl.fcntl(fd, fcntl.F_SETFL, fl | os.O_NONBLOCK)
326
327 make_non_block(p.stdout)
328 make_non_block(p.stderr)
329
330 try:
331 while True:
332 rd, _, _ = select.select([p.stdout, p.stderr, self._sock], [], [])
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800333 p.poll()
334
335 if p.returncode != None:
Moja Hsuc9ecc8b2015-07-13 11:39:17 +0800336 raise RuntimeError('process complete')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800337
338 if p.stdout in rd:
339 self._sock.send(p.stdout.read(_BUFSIZE))
340
341 if p.stderr in rd:
342 self._sock.send(p.stderr.read(_BUFSIZE))
343
344 if self._sock in rd:
345 ret = self._sock.recv(_BUFSIZE)
346 if len(ret) == 0:
Moja Hsuc9ecc8b2015-07-13 11:39:17 +0800347 raise RuntimeError('socket closed')
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800348 p.stdin.write(ret)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800349 except (OSError, socket.error, RuntimeError):
350 self._sock.close()
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800351 logging.info('SpawnShellServer: terminated')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800352 sys.exit(0)
353
354
355 def Ping(self):
356 def timeout_handler(x):
357 if x is None:
358 raise PingTimeoutError
359
360 self._last_ping = self.Timestamp()
361 self.SendRequest('ping', {}, timeout_handler, 5)
362
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800363 def HandleRequest(self, msg):
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800364 if msg['name'] == 'terminal':
365 self.SpawnGhost(self.TERMINAL, msg['params']['sid'])
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800366 self.SendResponse(msg, RESPONSE_SUCCESS)
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800367 elif msg['name'] == 'shell':
368 self.SpawnGhost(self.SHELL, msg['params']['sid'],
369 msg['params']['command'])
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800370 self.SendResponse(msg, RESPONSE_SUCCESS)
371
372 def HandleResponse(self, response):
373 rid = str(response['rid'])
374 if rid in self._requests:
375 handler = self._requests[rid][2]
376 del self._requests[rid]
377 if callable(handler):
378 handler(response)
379 else:
380 print(response, self._requests.keys())
381 logging.warning('Recvied unsolicited response, ignored')
382
383 def ParseMessage(self):
384 msgs_json = self._buf.split(_SEPARATOR)
385 self._buf = msgs_json.pop()
386
387 for msg_json in msgs_json:
388 try:
389 msg = json.loads(msg_json)
390 except ValueError:
391 # Ignore mal-formed message.
392 continue
393
394 if 'name' in msg:
395 self.HandleRequest(msg)
396 elif 'response' in msg:
397 self.HandleResponse(msg)
398 else: # Ingnore mal-formed message.
399 pass
400
401 def ScanForTimeoutRequests(self):
402 for rid in self._requests.keys()[:]:
403 request_time, timeout, handler = self._requests[rid]
404 if self.Timestamp() - request_time > timeout:
405 handler(None)
406 del self._requests[rid]
407
408 def Listen(self):
409 try:
410 while True:
411 rds, _, _ = select.select([self._sock], [], [], _PING_INTERVAL / 2)
412
413 if self._sock in rds:
414 self._buf += self._sock.recv(_BUFSIZE)
415 self.ParseMessage()
416
417 if self.Timestamp() - self._last_ping > _PING_INTERVAL:
418 self.Ping()
419 self.ScanForTimeoutRequests()
420
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800421 if self._reset.is_set():
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800422 self.Reset()
423 break
424 except socket.error:
425 raise RuntimeError('Connection dropped')
426 except PingTimeoutError:
427 raise RuntimeError('Connection timeout')
428 finally:
429 self._sock.close()
430
431 self._queue.put('resume')
432
433 if self._mode != Ghost.AGENT:
434 sys.exit(1)
435
436 def Register(self):
437 non_local = {}
438 for addr in self._overlord_addrs:
439 non_local['addr'] = addr
440 def registered(response):
441 if response is None:
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800442 self._reset.set()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800443 raise RuntimeError('Register request timeout')
444 logging.info('Registered with Overlord at %s:%d', *non_local['addr'])
Moja Hsuc9ecc8b2015-07-13 11:39:17 +0800445 self._queue.put('pause', True)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800446
447 try:
448 logging.info('Trying %s:%d ...', *addr)
449 self.Reset()
450 self._sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
451 self._sock.settimeout(_PING_TIMEOUT)
452 self._sock.connect(addr)
453
454 logging.info('Connection established, registering...')
455 handler = {
456 Ghost.AGENT: registered,
Wei-Ning Huang0f4a5372015-03-09 15:12:07 +0800457 Ghost.TERMINAL: self.SpawnPTYServer,
458 Ghost.SHELL: self.SpawnShellServer
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800459 }[self._mode]
460
461 # Machine ID may change if MAC address is used (USB-ethernet dongle
462 # plugged/unplugged)
463 self._machine_id = self.GetMachineID()
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800464 self.SendRequest('register',
465 {'mode': self._mode, 'mid': self._machine_id,
466 'cid': self._client_id,
467 'properties': self._properties}, handler)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800468 except socket.error:
469 pass
470 else:
471 self._sock.settimeout(None)
Wei-Ning Huangad330c52015-03-12 20:34:18 +0800472 self._connected_addr = addr
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800473 self.Listen()
474
Moja Hsuc9ecc8b2015-07-13 11:39:17 +0800475 raise RuntimeError('Cannot connect to any server')
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800476
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800477 def Reconnect(self):
478 logging.info('Received reconnect request from RPC server, reconnecting...')
479 self._reset.set()
480
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800481 def StartLanDiscovery(self):
482 """Start to listen to LAN discovery packet at
483 _OVERLORD_LAN_DISCOVERY_PORT."""
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800484
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800485 def thread_func():
486 s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
487 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
488 s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800489 try:
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800490 s.bind(('0.0.0.0', _OVERLORD_LAN_DISCOVERY_PORT))
491 except socket.error as e:
Moja Hsuc9ecc8b2015-07-13 11:39:17 +0800492 logging.error('LAN discovery: %s, abort', e)
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800493 return
494
495 logging.info('LAN Discovery: started')
496 while True:
497 rd, _, _ = select.select([s], [], [], 1)
498
499 if s in rd:
500 data, source_addr = s.recvfrom(_BUFSIZE)
501 parts = data.split()
502 if parts[0] == 'OVERLORD':
503 ip, port = parts[1].split(':')
504 if not ip:
505 ip = source_addr[0]
506 self._queue.put((ip, int(port)), True)
507
508 try:
509 obj = self._queue.get(False)
510 except Queue.Empty:
511 pass
512 else:
513 if type(obj) is not str:
514 self._queue.put(obj)
515 elif obj == 'pause':
516 logging.info('LAN Discovery: paused')
517 while obj != 'resume':
518 obj = self._queue.get(True)
519 logging.info('LAN Discovery: resumed')
520
521 t = threading.Thread(target=thread_func)
522 t.daemon = True
523 t.start()
524
525 def StartRPCServer(self):
526 rpc_server = SimpleJSONRPCServer((_DEFAULT_BIND_ADDRESS, _GHOST_RPC_PORT),
527 logRequests=False)
528 rpc_server.register_function(self.Reconnect, 'Reconnect')
529 t = threading.Thread(target=rpc_server.serve_forever)
530 t.daemon = True
531 t.start()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800532
Wei-Ning Huang829e0c82015-05-26 14:37:23 +0800533 def ScanServer(self):
534 for meth in [self.GetGateWayIP, self.GetShopfloorIP]:
535 for addr in [(x, _OVERLORD_PORT) for x in meth()]:
536 if addr not in self._overlord_addrs:
537 self._overlord_addrs.append(addr)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800538
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800539 def Start(self, lan_disc=False, rpc_server=False):
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800540 logging.info('%s started', self.MODE_NAME[self._mode])
541 logging.info('MID: %s', self._machine_id)
542 logging.info('CID: %s', self._client_id)
543
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800544 if lan_disc:
545 self.StartLanDiscovery()
546
547 if rpc_server:
548 self.StartRPCServer()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800549
550 try:
551 while True:
552 try:
553 addr = self._queue.get(False)
554 except Queue.Empty:
555 pass
556 else:
557 if type(addr) == tuple and addr not in self._overlord_addrs:
558 logging.info('LAN Discovery: got overlord address %s:%d', *addr)
559 self._overlord_addrs.append(addr)
560
561 try:
Wei-Ning Huang829e0c82015-05-26 14:37:23 +0800562 self.ScanServer()
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800563 self.Register()
564 except Exception as e:
565 logging.info(str(e) + ', retrying in %ds' % _RETRY_INTERVAL)
566 time.sleep(_RETRY_INTERVAL)
567
568 self.Reset()
569 except KeyboardInterrupt:
570 logging.error('Received keyboard interrupt, quit')
571 sys.exit(0)
572
573
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800574def GhostRPCServer():
575 return jsonrpclib.Server('http://localhost:%d' % _GHOST_RPC_PORT)
576
577
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800578def main():
579 logger = logging.getLogger()
580 logger.setLevel(logging.INFO)
581
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800582 parser = argparse.ArgumentParser()
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800583 parser.add_argument('--mid', metavar='MID', dest='mid', action='store',
584 default=None, help='use MID as machine ID')
585 parser.add_argument('--rand-mid', dest='mid', action='store_const',
586 const=Ghost.RANDOM_MID, help='use random machine ID')
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800587 parser.add_argument('--no-lan-disc', dest='lan_disc', action='store_false',
588 default=True, help='disable LAN discovery')
589 parser.add_argument('--no-rpc-server', dest='rpc_server',
590 action='store_false', default=True,
591 help='disable RPC server')
Moja Hsuc9ecc8b2015-07-13 11:39:17 +0800592 parser.add_argument('--prop-file', dest='prop_file', type=str, default=None,
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800593 help='file containing the JSON representation of client '
594 'properties')
595 parser.add_argument('overlord_ip', metavar='OVERLORD_IP', type=str,
596 nargs='*', help='overlord server address')
597 args = parser.parse_args()
598
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800599 addrs = [('localhost', _OVERLORD_PORT)]
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800600 addrs += [(x, _OVERLORD_PORT) for x in args.overlord_ip]
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800601
Wei-Ning Huangc9c97f02015-05-19 15:05:42 +0800602 g = Ghost(addrs, Ghost.AGENT, args.mid)
Wei-Ning Huang7d029b12015-03-06 10:32:15 +0800603 if args.prop_file:
604 g.LoadPropertiesFromFile(args.prop_file)
Wei-Ning Huang2132de32015-04-13 17:24:38 +0800605 g.Start(args.lan_disc, args.rpc_server)
Wei-Ning Huang1cea6112015-03-02 12:45:34 +0800606
607
608if __name__ == '__main__':
609 main()