Adds the ability to list atomic labels via 'atest label list --atomicgroup'
Adds ability to parse values from nested dicts to topic_common for the above.

Signed-off-by: Gregory Smith <[email protected]>


git-svn-id: http://test.kernel.org/svn/autotest/trunk@3350 592f7852-d20e-0410-864c-8624ca9c26a4
diff --git a/cli/label.py b/cli/label.py
index 244f795..65ad468 100755
--- a/cli/label.py
+++ b/cli/label.py
@@ -55,7 +55,7 @@
 
 
 class label_list(action_common.atest_list, label):
-    """atest label list [--platform] [--all]
+    """atest label list [--platform] [--all] [--atomicgroup]
     [--valid-only] [--machine <machine>]
     [--blist <file>] [<labels>]"""
     def __init__(self):
@@ -74,6 +74,11 @@
                                      'platform labels'),
                                action='store_true')
 
+        self.parser.add_option('--atomicgroup',
+                               help=('Display only atomic group labels '
+                                     'along with the atomic group name.'),
+                               action='store_true')
+
         self.parser.add_option('-m', '--machine',
                                help='List LABELs of MACHINE',
                                type='string',
@@ -85,9 +90,10 @@
                                                  inline_option='machine')
         (options, leftover) = super(label_list, self).parse([host_info])
 
-        if options.all and options.platform_only:
+        exclusives = [options.all, options.platform_only, options.atomicgroup]
+        if exclusives.count(True) > 1:
             self.invalid_syntax('Only specify one of --all,'
-                                '--platform')
+                                '--platform, --atomicgroup')
 
         if len(self.hosts) > 1:
             self.invalid_syntax(('Only one machine name allowed. '
@@ -95,6 +101,7 @@
                                  'instead.') %
                                 (sys.argv[0], ','.join(self.hosts)))
         self.all = options.all
+        self.atomicgroup = options.atomicgroup
         self.platform_only = options.platform_only
         self.valid_only = options.valid_only
         return (options, leftover)
@@ -125,6 +132,10 @@
             results = [label for label in results
                        if label['platform']]
             keys = ['name', 'invalid']
+        elif self.atomicgroup:
+            results = [label for label in results
+                       if label['atomic_group']]
+            keys = ['name', 'atomic_group.name', 'invalid']
         elif not self.all:
             results = [label for label in results
                        if not label['platform']]
diff --git a/cli/topic_common.py b/cli/topic_common.py
index ce4de41..0070349 100755
--- a/cli/topic_common.py
+++ b/cli/topic_common.py
@@ -100,6 +100,7 @@
                     'synch_count': 'Sync Count',
                     'max_number_of_machines': 'Max. hosts to use',
                     'parse_failed_repair': 'Include failed repair results',
+                    'atomic_group.name': 'Atomic Group Name',
                     }
 
 # In the failure, tag that will replace the item.
@@ -132,6 +133,24 @@
                 'platform': __convert_platform,
                 'labels': lambda labels: ', '.join(labels)}
 
+
+def _get_item_key(item, key):
+    """Allow for lookups in nested dictionaries using '.'s within a key."""
+    if key in item:
+        return item[key]
+    nested_item = item
+    for subkey in key.split('.'):
+        if not subkey:
+            raise ValueError('empty subkey in %r' % key)
+        try:
+            nested_item = nested_item[subkey]
+        except KeyError, e:
+            raise KeyError('%r - looking up key %r in %r' %
+                           (e, key, nested_item))
+    else:
+        return nested_item
+
+
 class CliError(Exception):
     pass
 
@@ -490,7 +509,7 @@
             for key in keys:
                 print '%s: %s' % (KEYS_TO_NAMES_EN[key],
                                   self.__conv_value(key,
-                                                    item[key]))
+                                                    _get_item_key(item, key)))
 
 
     def print_fields_parse(self, items, keys, title=None):
@@ -499,10 +518,10 @@
         for item in items:
             values = ['%s=%s' % (KEYS_TO_NAMES_EN[key],
                                   self.__conv_value(key,
-                                                    item[key]))
+                                                    _get_item_key(item, key)))
                       for key in keys
                       if self.__conv_value(key,
-                                           item[key]) != '']
+                                           _get_item_key(item, key)) != '']
             print self.parse_delim.join(values)
 
 
@@ -517,7 +536,7 @@
             return
         for key in keys[:-1]:
             lens[key] = max(len(self.__conv_value(key,
-                                                  item[key]))
+                                                  _get_item_key(item, key)))
                             for item in items)
             lens[key] = max(lens[key], len(KEYS_TO_NAMES_EN[key]))
         lens[keys[-1]] = 0
@@ -535,13 +554,14 @@
         header = tuple(KEYS_TO_NAMES_EN[key] for key in keys_header)
         print fmt % header
         for item in items:
-            values = tuple(self.__conv_value(key, item[key])
+            values = tuple(self.__conv_value(key,
+                                             _get_item_key(item, key))
                            for key in keys_header)
             print fmt % values
             if sublist_keys:
                 for key in sublist_keys:
                     self.print_wrapped(KEYS_TO_NAMES_EN[key],
-                                       item[key])
+                                       _get_item_key(item, key))
                 print '\n'
 
 
@@ -550,16 +570,16 @@
         format"""
         for item in items:
             values = ['%s=%s' % (KEYS_TO_NAMES_EN[key],
-                                 self.__conv_value(key, item[key]))
+                                 self.__conv_value(key, _get_item_key(item, key)))
                       for key in keys_header
                       if self.__conv_value(key,
-                                           item[key]) != '']
+                                           _get_item_key(item, key)) != '']
 
             if sublist_keys:
                 [values.append('%s=%s'% (KEYS_TO_NAMES_EN[key],
-                                         ','.join(item[key])))
+                                         ','.join(_get_item_key(item, key))))
                  for key in sublist_keys
-                 if len(item[key])]
+                 if len(_get_item_key(item, key))]
 
             print self.parse_delim.join(values)
 
@@ -585,10 +605,10 @@
         for item in items:
             values += ['%s=%s' % (KEYS_TO_NAMES_EN[key],
                                   self.__conv_value(key,
-                                                    item[key]))
+                                                    _get_item_key(item, key)))
                        for key in ['id', 'name']
                        if self.__conv_value(key,
-                                            item[key]) != '']
+                                            _get_item_key(item, key)) != '']
         print self.parse_delim.join(values)
 
 
@@ -596,7 +616,7 @@
         """Print a wrapped list of results"""
         if not items:
             return
-        print ' '.join(item[key] for item in items)
+        print ' '.join(_get_item_key(item, key) for item in items)
 
 
     def print_list_parse(self, items, key):
@@ -604,4 +624,4 @@
         if not items:
             return
         print '%s=%s' % (KEYS_TO_NAMES_EN[key],
-                         ','.join(item[key] for item in items))
+                         ','.join(_get_item_key(item, key) for item in items))
diff --git a/cli/topic_common_unittest.py b/cli/topic_common_unittest.py
index 75075db..7d79e7f 100755
--- a/cli/topic_common_unittest.py
+++ b/cli/topic_common_unittest.py
@@ -11,6 +11,22 @@
 from autotest_lib.frontend.afe.json_rpc import proxy
 
 
+class topic_common_misc_tests(unittest.TestCase):
+    def test_get_item_key(self):
+        get_item_key = topic_common._get_item_key
+        self.assertRaises(ValueError, get_item_key, {}, '')
+        self.assertRaises(ValueError, get_item_key, {}, '.')
+        self.assertRaises(KeyError, get_item_key, {}, 'a')
+        self.assertRaises(KeyError, get_item_key, {}, 'a.')
+        self.assertRaises(ValueError, get_item_key, {'a': {}}, 'a.')
+        self.assertRaises(KeyError, get_item_key, {'a': {}}, 'a.b')
+        self.assertEquals(2, get_item_key({'a.b': 2, 'a': {}}, 'a.b'))
+        self.assertEquals(9, get_item_key({'a': {'b': 9}}, 'a.b'))
+        self.assertEquals(3, get_item_key({'a': {'b': {'c': 3}}}, 'a.b.c'))
+        self.assertEquals(5, get_item_key({'a': 5}, 'a'))
+        self.assertEquals({'b': 9}, get_item_key({'a': {'b': 9}}, 'a'))
+
+
 class item_parse_info_unittest(cli_mock.cli_unittest):
     def __test_parsing_flist_bad(self, options):
         parse_info = topic_common.item_parse_info