phosphor-settingsd - Support YAML categories and sub-categories

Currently phosphor-settingsd only supports a single category of 'host0',
but the YAML format supports categories and sub-categories.
This enhancement supports categories and sub-categories and
automatically creates corresponding dbus objects to represent.

Resolves openbmc/openbmc#551

Change-Id: I42bf246881ca08e8429771666415321fa997f0dc
Signed-off-by: Sergey Solomin <sergey.solomin@us.ibm.com>
diff --git a/settings_manager.py b/settings_manager.py
index 0a09826..3e09ce5 100644
--- a/settings_manager.py
+++ b/settings_manager.py
@@ -19,9 +19,45 @@
 import re
 
 DBUS_NAME = 'org.openbmc.settings.Host'
-OBJ_NAME = '/org/openbmc/settings/host0'
 CONTROL_INTF = 'org.openbmc.Settings'
 
+def walk_nest(d, keys =()):
+    """Arrange dictionary keys and values.
+
+    Walk the dictionary and establish every possible path
+    returned to and processed by 'create_object' below
+    """
+    if isinstance(d, dict):
+        for k, v in d.iteritems():
+            for rv in walk_nest(v, keys + (k, )):
+                yield rv
+    else:
+        yield keys, d
+
+def create_object(settings):
+    """Create and format objects.
+
+    Parse dictionary file and return all objects and main properties
+    (name, type, default) for each attribute in the following format:
+    [obj_name, attr_ name, attr_ type, default]
+    """
+    allobjects = {}
+    for compound_key, val in walk_nest(settings):
+        obj_name = compound_key[0].lower()
+        obj_name = obj_name.replace(".","/")
+        obj_name = "/" + obj_name + "0"
+
+        for i in compound_key[1:len(compound_key)-2]:
+            obj_name = obj_name + "/" + i
+
+        setting = compound_key[len(compound_key) - 2]
+        attribute = compound_key[len(compound_key) - 1]
+
+        o = allobjects.setdefault(obj_name, {})
+        s = o.setdefault(setting, {'name': None, 'type': None, 'default': None})
+        s[attribute] = val
+
+    return allobjects
 
 class HostSettingsObject(DbusProperties):
     def __init__(self, bus, name, settings, path):
@@ -31,6 +67,9 @@
             validator=self.input_validator)
         self.bus = bus
         self.path = path
+        self.name = name
+        self.settings = settings
+
         # Needed to ignore the validation on default networkconfig values as
         # opposed to user giving the same.
         self.adminmode = True
@@ -43,14 +82,13 @@
             self.settings_signal_handler,
             dbus_interface="org.freedesktop.DBus.Properties",
             signal_name="PropertiesChanged",
-            path="/org/openbmc/settings/host0")
+            path=name)
 
         # Create the dbus properties
-        for i in settings[DBUS_NAME].iterkeys():
-            shk = settings[DBUS_NAME][i]
-            self.set_settings_property(shk['name'],
-                                       shk['type'],
-                                       shk['default'])
+        for setting in settings.itervalues():
+            self.set_settings_property(setting['name'],
+                    setting['type'],
+                    setting['default'])
         # Done with consuming factory settings.
         self.adminmode = False
 
@@ -66,16 +104,16 @@
     # This will be either a value previously set,
     # or the default file value if the BMC value
     # does not exist.
-    def set_settings_property(self, name, type, value):
-        bmcv = self.get_bmc_value(name)
+    def set_settings_property(self, attr_name, attr_type, value):
+        bmcv = self.get_bmc_value(attr_name)
         if bmcv:
             value = bmcv
-        if type == "i":
-            self.Set(DBUS_NAME, name, int(value))
-        elif type == "s":
-            self.Set(DBUS_NAME, name, str(value))
-        elif type == "b":
-            self.Set(DBUS_NAME, name, bool(value))
+        if attr_type == "i":
+            self.Set(DBUS_NAME, attr_name, int(value))
+        elif attr_type == "s":
+            self.Set(DBUS_NAME, attr_name, str(value))
+        elif attr_type == "b":
+            self.Set(DBUS_NAME, attr_name, bool(value))
 
     # Save the settings to the BMC. This will write the settings value in
     # individual files named by the property name to the BMC.
@@ -156,37 +194,41 @@
 
     # Validate to see if the changes are in order
     def input_validator(self, iface, proprty, value):
-        settings = s.SETTINGS
-        shk = {}
-        for key in settings[iface].iterkeys():
-            if proprty == settings[iface][key]['name']:
-                shk = settings[iface][key]
-                break
-
         # User entered key is not present
-        if not shk:
+        shk = None
+        for attr in self.settings.itervalues():
+            if attr['name'] == proprty:
+                shk = attr
+
+        if shk is None:
             raise KeyError("Invalid Property")
 
-        if shk['validation'] == 'list':
+        validation = shk.get('validation', None)
+
+        if validation == 'list':
             self.validate_list_ignore_case(shk['allowed'], value)
 
-        elif shk['validation'] == 'range':
+        elif validation == 'range':
             self.validate_range(shk['min'], shk['max']+1, value)
 
-        elif shk['validation'] == 'regex':
+        elif validation == 'regex':
             self.validate_regex(shk['regex'], value)
 
-        elif shk['validation'] == 'custom':
+        elif validation == 'custom':
             getattr(self, shk['method'])(value)
 
 if __name__ == '__main__':
     dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
 
     bus = get_dbus()
-    obj = HostSettingsObject(bus, OBJ_NAME, s.SETTINGS, "/var/lib/obmc/")
-    mainloop = gobject.MainLoop()
+    allobjects = create_object(s.SETTINGS)
+    lastobject = None
+    objs = []
+    for o, settings in allobjects.iteritems():
+        objs.append(HostSettingsObject(bus, o, settings, "/var/lib/obmc/"))
+        objs[-1].unmask_signals()
 
-    obj.unmask_signals()
+    mainloop = gobject.MainLoop()
     name = dbus.service.BusName(DBUS_NAME, bus)
     print "Running HostSettingsService"
     mainloop.run()