blob: 458af5e25c4c1f31262492b9eeead0b5fc24b7f8 [file] [log] [blame]
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -06001#!/usr/bin/python -u
2
3import gobject
4import dbus
5import dbus.service
6import dbus.mainloop.glib
Adriana Kobylak41a925e2016-01-28 16:44:27 -06007import os
8import os.path as path
Adriana Kobylak256be782016-08-24 15:43:16 -05009import sys
Brad Bishop5ef7fe62016-05-17 08:39:17 -040010from obmc.dbuslib.bindings import DbusProperties, get_dbus
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +053011from IPy import IP
Adriana Kobylak256be782016-08-24 15:43:16 -050012
Brad Bishop966fcd52016-09-29 09:22:32 -040013settings_file_path = os.path.join(
14 sys.prefix,
15 'share',
16 'phosphor-settings')
Adriana Kobylak256be782016-08-24 15:43:16 -050017sys.path.insert(1, settings_file_path)
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -060018import settings_file as s
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +053019import re
Sergey Solomin62b55f32016-10-13 10:40:27 -050020import obmc.mapper
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -060021
22DBUS_NAME = 'org.openbmc.settings.Host'
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -060023CONTROL_INTF = 'org.openbmc.Settings'
24
Sergey Solomin4a2433f2016-10-03 10:22:57 -050025def walk_nest(d, keys =()):
26 """Arrange dictionary keys and values.
27
28 Walk the dictionary and establish every possible path
29 returned to and processed by 'create_object' below
30 """
31 if isinstance(d, dict):
32 for k, v in d.iteritems():
33 for rv in walk_nest(v, keys + (k, )):
34 yield rv
35 else:
36 yield keys, d
37
38def create_object(settings):
39 """Create and format objects.
40
Sergey Solomin62b55f32016-10-13 10:40:27 -050041 Parse dictionary file and return all objects and settings
42 in the following format: {obj_name {settings}}
Sergey Solomin4a2433f2016-10-03 10:22:57 -050043 """
Sergey Solominf68b8642016-11-14 16:26:17 -060044 bus = get_dbus()
Sergey Solomin62b55f32016-10-13 10:40:27 -050045 mapper = obmc.mapper.Mapper(bus)
Sergey Solomin4a2433f2016-10-03 10:22:57 -050046 allobjects = {}
Sergey Solomin62b55f32016-10-13 10:40:27 -050047 queries = {}
Sergey Solomin4a2433f2016-10-03 10:22:57 -050048 for compound_key, val in walk_nest(settings):
49 obj_name = compound_key[0].lower()
50 obj_name = obj_name.replace(".","/")
51 obj_name = "/" + obj_name + "0"
52
Sergey Solomin62b55f32016-10-13 10:40:27 -050053 for i in compound_key[2:len(compound_key)-2]:
Sergey Solomin4a2433f2016-10-03 10:22:57 -050054 obj_name = obj_name + "/" + i
55
56 setting = compound_key[len(compound_key) - 2]
57 attribute = compound_key[len(compound_key) - 1]
Sergey Solomin62b55f32016-10-13 10:40:27 -050058 if settings.get(compound_key[0], {}).get('query', {}):
59 q = queries.setdefault(obj_name, {})
60 s = q.setdefault(
61 setting, {'name': None, 'type': None, 'default': None})
62 else:
63 o = allobjects.setdefault(obj_name, {})
64 s = o.setdefault(
65 setting, {'name': None, 'type': None, 'default': None})
Sergey Solomin4a2433f2016-10-03 10:22:57 -050066 s[attribute] = val
Sergey Solomin62b55f32016-10-13 10:40:27 -050067 for settings in queries.itervalues():
68 for setting in settings.itervalues():
69 if setting['type'] is not 'instance_query':
70 continue
71 paths = mapper.get_subtree_paths(setting['subtree'], 0)
72 for path in paths:
73 m = re.search(setting['matchregex'], path)
74 if not m:
75 continue
76 allobjects.setdefault(
77 "/org/openbmc/settings/" + m.group(1), settings)
Sergey Solomin4a2433f2016-10-03 10:22:57 -050078 return allobjects
Brad Bishop2a9fe662016-08-31 12:37:35 -040079
Brad Bishop5ef7fe62016-05-17 08:39:17 -040080class HostSettingsObject(DbusProperties):
Adriana Kobylak41a925e2016-01-28 16:44:27 -060081 def __init__(self, bus, name, settings, path):
Brad Bishopc1e5e9f2016-09-29 09:40:01 -040082 super(HostSettingsObject, self).__init__(
83 conn=bus,
84 object_path=name,
85 validator=self.input_validator)
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +053086 self.bus = bus
Adriana Kobylak41a925e2016-01-28 16:44:27 -060087 self.path = path
Sergey Solomin4a2433f2016-10-03 10:22:57 -050088 self.name = name
89 self.settings = settings
Sergey Solomin62b55f32016-10-13 10:40:27 -050090 fname = name[name.rfind("/")+1:] + '-'
Sergey Solomin4a2433f2016-10-03 10:22:57 -050091
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +053092 # Needed to ignore the validation on default networkconfig values as
93 # opposed to user giving the same.
94 self.adminmode = True
95
Adriana Kobylak41a925e2016-01-28 16:44:27 -060096 if not os.path.exists(path):
97 os.mkdir(path)
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -060098
99 # Listen to changes in the property values and sync them to the BMC
Brad Bishop2a9fe662016-08-31 12:37:35 -0400100 bus.add_signal_receiver(
101 self.settings_signal_handler,
102 dbus_interface="org.freedesktop.DBus.Properties",
103 signal_name="PropertiesChanged",
Sergey Solomin4a2433f2016-10-03 10:22:57 -0500104 path=name)
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600105
106 # Create the dbus properties
Sergey Solomin4a2433f2016-10-03 10:22:57 -0500107 for setting in settings.itervalues():
Sergey Solomin62b55f32016-10-13 10:40:27 -0500108 if setting['type'] is 'instance_query':
109 continue
110 self.set_settings_property(
111 setting['name'], setting['type'], setting['default'], fname)
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530112 # Done with consuming factory settings.
113 self.adminmode = False
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600114
Sergey Solomin62b55f32016-10-13 10:40:27 -0500115 def get_bmc_value(self, name, fname):
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600116 try:
Sergey Solomin62b55f32016-10-13 10:40:27 -0500117 with open(path.join(self.path, fname + name), 'r') as f:
Adriana Kobylak41a925e2016-01-28 16:44:27 -0600118 return f.read()
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600119 except (IOError):
120 pass
Adriana Kobylak41a925e2016-01-28 16:44:27 -0600121 return None
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600122
Brad Bishop2a9fe662016-08-31 12:37:35 -0400123 # Create dbus properties based on bmc value.
124 # This will be either a value previously set,
125 # or the default file value if the BMC value
126 # does not exist.
Sergey Solomin62b55f32016-10-13 10:40:27 -0500127 def set_settings_property(self, attr_name, attr_type, value, fname):
128 bmcv = self.get_bmc_value(attr_name, fname)
Adriana Kobylak41a925e2016-01-28 16:44:27 -0600129 if bmcv:
130 value = bmcv
Sergey Solomin4a2433f2016-10-03 10:22:57 -0500131 if attr_type == "i":
132 self.Set(DBUS_NAME, attr_name, int(value))
133 elif attr_type == "s":
134 self.Set(DBUS_NAME, attr_name, str(value))
135 elif attr_type == "b":
136 self.Set(DBUS_NAME, attr_name, bool(value))
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600137
138 # Save the settings to the BMC. This will write the settings value in
139 # individual files named by the property name to the BMC.
Sergey Solomin62b55f32016-10-13 10:40:27 -0500140 def set_system_settings(self, name, value, fname):
141 bmcv = self.get_bmc_value(name, fname)
Adriana Kobylak41a925e2016-01-28 16:44:27 -0600142 if bmcv != value:
Sergey Solomin62b55f32016-10-13 10:40:27 -0500143 filepath = path.join(self.path, fname + name)
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600144 with open(filepath, 'w') as f:
Adriana Kobylak41a925e2016-01-28 16:44:27 -0600145 f.write(str(value))
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600146
147 # Signal handler for when one ore more settings properties were updated.
148 # This will sync the changes to the BMC.
Brad Bishop2a9fe662016-08-31 12:37:35 -0400149 def settings_signal_handler(
Sergey Solomin62b55f32016-10-13 10:40:27 -0500150 self, interface_name, changed_properties, invalidated_properties,
151 fname):
Adriana Kobylak41a925e2016-01-28 16:44:27 -0600152 for name, value in changed_properties.items():
Sergey Solomin62b55f32016-10-13 10:40:27 -0500153 self.set_system_settings(name, value, fname)
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600154
155 # Placeholder signal. Needed to register the settings interface.
Adriana Kobylak41a925e2016-01-28 16:44:27 -0600156 @dbus.service.signal(DBUS_NAME, signature='s')
157 def SettingsUpdated(self, sname):
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600158 pass
159
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530160 def validate_regex(self, regex, value):
161 if not re.compile(regex).search(value):
Brad Bishopc1e5e9f2016-09-29 09:40:01 -0400162 raise ValueError("Invalid input. Data does not satisfy regex")
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530163
164 def validate_range(self, min, max, value):
165 if value not in range(min, max):
Brad Bishopc1e5e9f2016-09-29 09:40:01 -0400166 raise ValueError("Invalid input. Data not in allowed range")
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530167
168 def validate_list_ignore_case(self, lst, value):
169 if value.lower() not in map(lambda val: val.lower(), lst):
Brad Bishopc1e5e9f2016-09-29 09:40:01 -0400170 raise ValueError("Invalid input. Data not in allowed values")
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530171
172 # validate host network configuration
173 # need "ipaddress=,prefix=,gateway=,mac=,addr_type="
174 # Must be able to handle any order
175 def validate_net_config(self, value):
176 if self.adminmode:
177 return
178
179 # Need all of these to be given by the user.
180 user_config = []
181 all_config = ['ipaddress', 'prefix', 'gateway', 'mac', 'addr_type']
182
183 # This has a hard data format mentioned above so no blanks allowed.
184 if value.count(" ") or value.count("=") != 5:
Brad Bishopc1e5e9f2016-09-29 09:40:01 -0400185 raise ValueError("Invalid Network Data. No white spaces allowed")
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530186
187 config = value.split(',')
188 for key_value in config:
Brad Bishopc1e5e9f2016-09-29 09:40:01 -0400189 key, value = key_value.split('=')
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530190 if not key or not value:
Brad Bishopc1e5e9f2016-09-29 09:40:01 -0400191 raise ValueError("Invalid key or Data")
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530192
193 # Add the current key seen so we can compare at the end to see
194 # if all values have been given
195 user_config.append(key.lower())
196
197 if key.lower() == 'ipaddress' or key.lower() == 'gateway':
Brad Bishopc1e5e9f2016-09-29 09:40:01 -0400198 IP(value)
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530199
200 elif key.lower() == 'mac':
201 regex = '([a-fA-F0-9]{2}[:|\-]?){6}'
202 self.validate_regex(regex, value)
203
204 elif key.lower() == 'prefix':
205 self.validate_range(0, 33, int(value))
206
207 elif key.lower() == 'addr_type':
208 allowed = ["STATIC", "DYNAMIC"]
209 self.validate_list_ignore_case(allowed, value)
210
211 # Did user pass everything ??
212 if set(all_config) - set(user_config):
Brad Bishopc1e5e9f2016-09-29 09:40:01 -0400213 raise ValueError(
214 "Invalid Network Data. All information is mandatory")
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530215
216 # Validate to see if the changes are in order
217 def input_validator(self, iface, proprty, value):
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530218 # User entered key is not present
Sergey Solomin4a2433f2016-10-03 10:22:57 -0500219 shk = None
220 for attr in self.settings.itervalues():
221 if attr['name'] == proprty:
222 shk = attr
223
224 if shk is None:
Brad Bishopc1e5e9f2016-09-29 09:40:01 -0400225 raise KeyError("Invalid Property")
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530226
Sergey Solomin4a2433f2016-10-03 10:22:57 -0500227 validation = shk.get('validation', None)
228
229 if validation == 'list':
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530230 self.validate_list_ignore_case(shk['allowed'], value)
231
Sergey Solomin4a2433f2016-10-03 10:22:57 -0500232 elif validation == 'range':
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530233 self.validate_range(shk['min'], shk['max']+1, value)
234
Sergey Solomin4a2433f2016-10-03 10:22:57 -0500235 elif validation == 'regex':
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530236 self.validate_regex(shk['regex'], value)
237
Sergey Solomin4a2433f2016-10-03 10:22:57 -0500238 elif validation == 'custom':
Brad Bishopc1e5e9f2016-09-29 09:40:01 -0400239 getattr(self, shk['method'])(value)
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530240
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600241if __name__ == '__main__':
242 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
243
Brad Bishop5ef7fe62016-05-17 08:39:17 -0400244 bus = get_dbus()
Sergey Solomin4a2433f2016-10-03 10:22:57 -0500245 allobjects = create_object(s.SETTINGS)
246 lastobject = None
247 objs = []
248 for o, settings in allobjects.iteritems():
249 objs.append(HostSettingsObject(bus, o, settings, "/var/lib/obmc/"))
250 objs[-1].unmask_signals()
Sergey Solomin4a2433f2016-10-03 10:22:57 -0500251 mainloop = gobject.MainLoop()
Brad Bishop2cbef3d2016-08-31 12:40:13 -0400252 name = dbus.service.BusName(DBUS_NAME, bus)
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600253 print "Running HostSettingsService"
254 mainloop.run()
Brad Bishop31c42f02016-09-29 09:35:32 -0400255
256# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4