| #!/usr/bin/python |
| # Copyright 2012 Google Inc. All Rights Reserved. |
| # |
| """Base classes for configuration objects.""" |
| |
| __author__ = 'Avery Pennarun (apenwarr@google.com)' |
| |
| |
| import random |
| |
| |
| class QueryError(Exception): |
| pass |
| |
| |
| 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 value == True and ival: |
| continue # any nonempty value is true, so match it |
| 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', |
| 'has_wifi', |
| ) |
| |
| |
| 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 FindOne(self, **kwargs): |
| v = self._FindOne(**kwargs) |
| if v is None: |
| raise KeyError(kwargs) |
| return v |
| |
| def FindOrAdd(self, **kwargs): |
| h = self._FindOne(**kwargs) |
| if not h: |
| h = Host(**kwargs) |
| self.append(h) |
| if not h.name: |
| h.name = h.ether or h.serialno or h.ip or h.ip6 |
| return h |
| |
| def Query(self, mincount=None, maxcount=None, randomize=False, **kwargs): |
| """Query the hosts list according to the given parameters. |
| |
| Args: |
| mincount: the minimum number of hosts to return. |
| maxcount: the maximum number of hosts to return. |
| randomize: return a random subset or the first maxcount matches. |
| **kwargs: key=value pairs to pass to hosts.FindAll(). |
| Returns: |
| A list of matching Host objects. |
| Raises: |
| QueryError: if mincount > maxcount or other invalid options. |
| """ |
| matches = self.FindAll(**kwargs) |
| if maxcount and maxcount < 0: |
| raise QueryError('max(%s) must be >= 0' % maxcount) |
| if maxcount and mincount > maxcount: |
| raise QueryError('min(%s) > max(%s)' % (mincount, maxcount)) |
| if mincount > len(matches): |
| raise QueryError('not enough matches found (got=%d, wanted=%r)' |
| % (len(matches), mincount)) |
| if randomize: |
| random.shuffle(matches) |
| else: |
| matches.sort(key=lambda i: (i.name, i.serialno, i.ether, i.ip6, i.ip)) |
| if maxcount is not None and len(matches) > maxcount: |
| matches = matches[:maxcount] |
| return matches |
| |
| 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() |