Merge "Some tools for auto-probing attached bruno devices."
diff --git a/.gitignore b/.gitignore
index 2f836aa..62523e1 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,2 +1,3 @@
*~
*.pyc
+/*.stamp
diff --git a/Makefile b/Makefile
index 4922c26..d56c8f3 100644
--- a/Makefile
+++ b/Makefile
@@ -1,32 +1,92 @@
-PORT=/dev/ttyS0
+PORT=/dev/ttyS0 #TODO(apenwarr): don't hardcode this
PASSWORD=google
-PORTSH=./portsh/portsh $(PORT) -p'$(PASSWORD)'
-.NOTPARALLEL:
+PORTSH=./portsh/portsh -p'$(PASSWORD)'
+NMAP_SUBNETS=192.168.2.0/24 #TODO(apenwarr): auto-read the subnet from eth1?
default: all
-all:
- @echo "Nothing to do."
+all: map.stamp
+
+lint:
+ gpylint *.py configs/core.py
test: install-wvtest
wvtest/wvtestrun $(MAKE) runtests
-runtests: $(addsuffix .run,$(wildcard [0-9]*.sh [0-9]*.py))
+#TODO(apenwarr): use a smarter allocator.
+# We could enable parallelism by depending on $(addsuffix ...) instead of
+# looping through them one by one. But then we end up running multiple
+# tests on the same device at once, which would cause conflicts. We need
+# a smarter allocator first.
+runtests:
+ for d in $(addsuffix .run,$(wildcard [0-9]*.sh [0-9]*.py)); do \
+ $(MAKE) $$d; \
+ done
-install-wvtest:
+install-wvtest: map.stamp
tar -cf - *.py *.sh wvtest/ | \
- $(PORTSH) \
+ $(PORTSH) $(PORT) \
'mkdir -p /tmp/tests && cd /tmp/tests && tar -xf -'
%.sh.run: %.sh install-wvtest
echo "Testing $<"
- $(PORTSH) \
+ $(PORTSH) $(PORT) \
'cd /tmp/tests && sh ./$<' </dev/null
%.py.run: %.py install-wvtest
echo "Testing $<"
- $(PORTSH) \
+ $(PORTSH) $(PORT) \
'cd /tmp/tests && python ./wvtest/wvtest.py $<' </dev/null
+# This rule regenerates the map *every* time. Run it when your node
+# configuration has changed.
+map: serialmap nmap
+ touch map.stamp
+
+# This rule just runs once; 'make map' or delete map.stamp to re-run.
+# Depend on this rule to ensure we have a map, without regenerating it
+# every time we run make.
+map.stamp:
+ $(MAKE) map
+
+nmap:
+ rm -f configs/nmap.*.tmp
+ $(MAKE) $$(nmap -n -p22 -oG - $(NMAP_SUBNETS) | \
+ grep ^Host: | \
+ awk '{printf "configs/nmap.%s.tmp\n", $$2}')
+ cat configs/header configs/nmap.*.tmp >configs/auto_nmap.py.new
+ mv configs/auto_nmap.py.new configs/auto_nmap.py
+
+serialmap:
+ rm -f configs/tty.*.tmp
+ $(MAKE) $$(cd /dev && /bin/ls ttyS*[0-9] ttyUSB*[0-9] | \
+ awk '{printf "configs/tty.%s.tmp\n", $$1}')
+ cat configs/header configs/tty.*.tmp >configs/auto_serial.py.new
+ mv configs/auto_serial.py.new configs/auto_serial.py
+
+
+PROBE_CMD=\
+ PATH=$$PATH:/bin:/usr/bin:/sbin:/usr/sbin; \
+ hnvram -r 1ST_SERIAL_NUMBER -r SERIAL_NO -r MAC_ADDR -r PLATFORM_NAME; \
+ [ -e /tmp/NFS ] && echo IS_NFSROOT=1; \
+ ip addr show br0; \
+ ip addr show eth0; \
+ ip addr show eth1; \
+ cat /proc/cpuinfo
+
+configs/nmap.%.tmp:
+ ssh -oNumberOfPasswordPrompts=0 \
+ root@$* \
+ 'echo NET_OK=1; $(PROBE_CMD)' 2>&1 | \
+ ./parse-probe-data >$@.new
+ mv $@.new $@
+
+configs/tty.%.tmp:
+ $(PORTSH) $* \
+ 'echo SERIALPORT=$*; $(PROBE_CMD)' 2>&1 | \
+ ./parse-probe-data >$@.new
+ mv $@.new $@
+
clean:
- rm -f *~ .*~ */*~ */.*~ *.pyc */*.pyc
+ rm -f *~ .*~ */*~ */.*~ *.pyc */*.pyc *.stamp \
+ configs/*.tmp configs/*.new configs/auto_*.py
diff --git a/config.py b/config.py
new file mode 100644
index 0000000..701d05c
--- /dev/null
+++ b/config.py
@@ -0,0 +1,16 @@
+#!/usr/bin/python
+# Copyright 2012 Google Inc. All Rights Reserved.
+#
+"""Test configurations. Includes a list of auto-probed bruno devices."""
+
+from configs import hosts
+
+__author__ = 'Avery Pennarun (apenwarr@google.com)'
+
+
+def main():
+ print hosts
+
+
+if __name__ == '__main__':
+ main()
diff --git a/configs/.gitignore b/configs/.gitignore
new file mode 100644
index 0000000..f55461c
--- /dev/null
+++ b/configs/.gitignore
@@ -0,0 +1,3 @@
+/auto_*.py
+/*.tmp
+/*.new
diff --git a/configs/__init__.py b/configs/__init__.py
new file mode 100644
index 0000000..3b6954a
--- /dev/null
+++ b/configs/__init__.py
@@ -0,0 +1,18 @@
+from core import hosts
+
+try:
+ import manual
+except ImportError:
+ pass
+
+try:
+ import auto_nmap
+except ImportError:
+ pass
+
+try:
+ import auto_serial
+except ImportError:
+ pass
+
+
diff --git a/configs/core.py b/configs/core.py
new file mode 100644
index 0000000..2dc6b72
--- /dev/null
+++ b/configs/core.py
@@ -0,0 +1,104 @@
+#!/usr/bin/python
+# Copyright 2012 Google Inc. All Rights Reserved.
+#
+"""Base classes for configuration objects."""
+
+__author__ = 'Avery Pennarun (apenwarr@google.com)'
+
+
+class AttrSet(object):
+ """A simple set of key-value pairs represented as a class with members."""
+
+ __slots__ = ()
+
+ def __init__(self, **kwargs):
+ for key in self.__slots__:
+ setattr(self, key, None)
+ self.Set(**kwargs)
+
+ def Set(self, **kwargs):
+ for key, value in kwargs.iteritems():
+ setattr(self, key, value)
+
+ def Match(self, **kwargs):
+ for key, value in kwargs.iteritems():
+ ival = getattr(self, key)
+ # None and False can match each other
+ #gpylint: disable-msg=C6403
+ if ival == False: ival = None
+ if value == False: value = None
+ if ival != value:
+ return False
+ return True
+
+ def _Items(self):
+ for key in self.__slots__:
+ value = getattr(self, key)
+ if value == True: #gpylint: disable-msg=C6403
+ yield key
+ elif value is not None and value != False: #gpylint: disable-msg=C6403
+ yield '%s=%r' % (key, value)
+
+ def __repr__(self):
+ return '<%s>' % ','.join(self._Items())
+
+ def __str__(self):
+ return '\n '.join(['%s:' % self.__class__.__name__]
+ + list(self._Items()))
+
+
+class Host(AttrSet):
+ __slots__ = (
+ 'name',
+ 'ether',
+ 'ip',
+ 'ip6',
+ 'cpu',
+ 'platform',
+ 'serialport',
+ 'serialno',
+ 'is_canary',
+ 'is_alive',
+ 'is_alive_net',
+ 'is_storage',
+ 'is_tv',
+ 'is_nfsroot',
+ 'has_ether',
+ 'has_moca',
+ )
+
+
+class Hosts(list):
+ """A searchable/queryable list of Host objects."""
+
+ def FindAll(self, **kwargs):
+ return [i for i in self if i.Match(**kwargs)]
+
+ def FindOne(self, **kwargs):
+ l = self.FindAll(**kwargs)
+ if l:
+ return l[0]
+
+ def FindOrAdd(self, **kwargs):
+ h = self.FindOne(**kwargs)
+ if not h:
+ h = Host(**kwargs)
+ self.append(h)
+ return h
+
+ def __str__(self):
+ return '\n'.join(str(i) for i in self)
+
+
+hosts = Hosts()
+
+
+def main():
+ print 'hello'
+ hosts.FindOrAdd(ether='11:22:33:44:55:66').Set(name='myhost',
+ ip='1.2.3.4')
+ print hosts
+
+
+if __name__ == '__main__':
+ main()
diff --git a/configs/header b/configs/header
new file mode 100644
index 0000000..2a22f55
--- /dev/null
+++ b/configs/header
@@ -0,0 +1,4 @@
+#
+# AUTO-GENERATED by Makefile: do not edit!
+#
+from core import hosts
diff --git a/configs/manual.py b/configs/manual.py
new file mode 100644
index 0000000..99cf21b
--- /dev/null
+++ b/configs/manual.py
@@ -0,0 +1,26 @@
+# insert manual configuration here
+from core import hosts
+
+
+#
+# Avery's test network in NYC
+#
+
+h = hosts.FindOrAdd(ether='00:03:9a:33:44:66')
+h.name = 't0-1'
+h.is_canary = True
+
+h = hosts.FindOrAdd(ether='42:42:42:42:42:12')
+h.name = 't0-2'
+
+h = hosts.FindOrAdd(ether='42:21:06:66:aa:03')
+h.name = 't0-3'
+
+h = hosts.FindOrAdd(ether='00:03:42:21:03:69')
+h.name = 't0-4'
+
+h = hosts.FindOrAdd(ether='00:1a:11:30:63:77')
+h.name = 'b2-1'
+
+h = hosts.FindOrAdd(ether='00:1a:11:30:5e:72')
+h.name = 't2-3'
diff --git a/parse-probe-data b/parse-probe-data
new file mode 100755
index 0000000..512fdb3
--- /dev/null
+++ b/parse-probe-data
@@ -0,0 +1,74 @@
+#!/usr/bin/python
+# Copyright 2012 Google Inc. All Rights Reserved.
+#
+
+import re
+import sys
+
+
+__author__ = 'Avery Pennarun (apenwarr@google.com)'
+
+
+def main():
+ data = sys.stdin.read().strip()
+ header_comment = re.sub(re.compile(r'^', re.M), '# ', data)
+ elements = []
+ ether = None
+
+ g = re.search(re.compile(r'^(?:1ST_SERIAL_NUMBER=|SERIAL_NO=)(.+)', re.M),
+ data)
+ if g:
+ elements.append('serialno=%r' % g.group(1))
+
+ g = re.search(re.compile(r'^PLATFORM_NAME=(.+)', re.M), data)
+ if g:
+ platform = g.group(1).upper()
+ elements.append('platform=%r' % platform)
+ if platform.startswith('GFMS'):
+ elements.append('is_storage=1')
+ elif platform.startswith('GFHD'):
+ elements.append('is_tv=1')
+
+ if re.search(re.compile(r'^NET_OK=1', re.M), data):
+ elements.append('is_alive=1')
+ elements.append('is_alive_net=1')
+
+ if re.search(re.compile(r'^IS_NFSROOT=1', re.M), data):
+ elements.append('is_nfsroot=1')
+
+ g = re.search(re.compile(r'^SERIALPORT=(.+)', re.M), data)
+ if g:
+ elements.append('is_alive=1')
+ elements.append('serialport=%r' % g.group(1))
+
+ g = re.search(r'inet ([\d\.]+)', data)
+ if g:
+ elements.append('ip=%r' % g.group(1))
+
+ g = re.search(r'inet6 ([a-fA-F0-9:]+)', data)
+ if g:
+ elements.append('ip6=%r' % g.group(1).lower())
+
+ g = re.search(r'link/ether ([a-fA-F0-9:]+)', data)
+ if g:
+ ether = g.group(1)
+ elements.append('ether=%r' % g.group(1).lower())
+
+ if re.search(r'eth0:.*LOWER_UP', data):
+ elements.append('has_ether=1')
+ if re.search(r'eth1:.*LOWER_UP', data):
+ elements.append('has_moca=1')
+
+ g = re.search(r'system type\s*:\s*(\S+)', data)
+ if g:
+ elements.append('cpu=%r' % g.group(1).upper())
+
+ sys.stdout.write('\n%s\n' % header_comment)
+ if elements and ether:
+ sys.stdout.write('h = hosts.FindOrAdd(ether=%r)\n' % ether)
+ for e in elements:
+ sys.stdout.write('h.%s\n' % e)
+
+
+if __name__ == '__main__':
+ main()