blob: d77897816648cd8099b3e92ed93d97014155b34e [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
Lei YU1d8b7cd2016-12-21 11:01:28 +080010from obmc.dbuslib.bindings import DbusProperties, DbusObjectManager, 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
Gunnar Millsa16abc52018-01-27 10:02:34 -060025
26def walk_nest(d, keys=()):
Sergey Solomin4a2433f2016-10-03 10:22:57 -050027 """Arrange dictionary keys and values.
28
29 Walk the dictionary and establish every possible path
30 returned to and processed by 'create_object' below
31 """
32 if isinstance(d, dict):
33 for k, v in d.iteritems():
34 for rv in walk_nest(v, keys + (k, )):
35 yield rv
36 else:
37 yield keys, d
38
Gunnar Millsa16abc52018-01-27 10:02:34 -060039
Sergey Solomin4a2433f2016-10-03 10:22:57 -050040def create_object(settings):
41 """Create and format objects.
42
Sergey Solomin62b55f32016-10-13 10:40:27 -050043 Parse dictionary file and return all objects and settings
44 in the following format: {obj_name {settings}}
Sergey Solomin4a2433f2016-10-03 10:22:57 -050045 """
Sergey Solominf68b8642016-11-14 16:26:17 -060046 bus = get_dbus()
Sergey Solomin62b55f32016-10-13 10:40:27 -050047 mapper = obmc.mapper.Mapper(bus)
Sergey Solomin4a2433f2016-10-03 10:22:57 -050048 allobjects = {}
Sergey Solomin62b55f32016-10-13 10:40:27 -050049 queries = {}
Sergey Solomin4a2433f2016-10-03 10:22:57 -050050 for compound_key, val in walk_nest(settings):
51 obj_name = compound_key[0].lower()
Gunnar Millsa16abc52018-01-27 10:02:34 -060052 obj_name = obj_name.replace(".", "/")
Sergey Solomin4a2433f2016-10-03 10:22:57 -050053 obj_name = "/" + obj_name + "0"
54
Gunnar Millsa16abc52018-01-27 10:02:34 -060055 for i in compound_key[2:len(compound_key) - 2]:
Sergey Solomin4a2433f2016-10-03 10:22:57 -050056 obj_name = obj_name + "/" + i
57
58 setting = compound_key[len(compound_key) - 2]
59 attribute = compound_key[len(compound_key) - 1]
Sergey Solomin62b55f32016-10-13 10:40:27 -050060 if settings.get(compound_key[0], {}).get('query', {}):
61 q = queries.setdefault(obj_name, {})
62 s = q.setdefault(
63 setting, {'name': None, 'type': None, 'default': None})
64 else:
65 o = allobjects.setdefault(obj_name, {})
66 s = o.setdefault(
67 setting, {'name': None, 'type': None, 'default': None})
Sergey Solomin4a2433f2016-10-03 10:22:57 -050068 s[attribute] = val
Sergey Solomin62b55f32016-10-13 10:40:27 -050069 for settings in queries.itervalues():
70 for setting in settings.itervalues():
71 if setting['type'] is not 'instance_query':
72 continue
Lei YU1d8b7cd2016-12-21 11:01:28 +080073 paths = mapper.get_subtree_paths(setting['subtree'], 0,
74 retries=10, interval=0.1)
Matt Spinler371dd022016-12-15 12:48:04 -060075
76 if setting['keyregex'] == 'host':
77 # Always create at least one host object.
78 paths = set(paths + ['/org/openbmc/control/host0'])
79
Sergey Solomin62b55f32016-10-13 10:40:27 -050080 for path in paths:
81 m = re.search(setting['matchregex'], path)
82 if not m:
83 continue
84 allobjects.setdefault(
85 "/org/openbmc/settings/" + m.group(1), settings)
Sergey Solomin4a2433f2016-10-03 10:22:57 -050086 return allobjects
Brad Bishop2a9fe662016-08-31 12:37:35 -040087
Gunnar Millsa16abc52018-01-27 10:02:34 -060088
Lei YU1d8b7cd2016-12-21 11:01:28 +080089class HostSettingsObject(DbusProperties, DbusObjectManager):
Adriana Kobylak41a925e2016-01-28 16:44:27 -060090 def __init__(self, bus, name, settings, path):
Brad Bishopc1e5e9f2016-09-29 09:40:01 -040091 super(HostSettingsObject, self).__init__(
92 conn=bus,
93 object_path=name,
94 validator=self.input_validator)
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +053095 self.bus = bus
Adriana Kobylak41a925e2016-01-28 16:44:27 -060096 self.path = path
Sergey Solomin4a2433f2016-10-03 10:22:57 -050097 self.name = name
98 self.settings = settings
Gunnar Millsa16abc52018-01-27 10:02:34 -060099 self.fname = name[name.rfind("/") + 1:] + '-'
Sergey Solomin4a2433f2016-10-03 10:22:57 -0500100
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530101 # Needed to ignore the validation on default networkconfig values as
102 # opposed to user giving the same.
103 self.adminmode = True
104
Adriana Kobylak41a925e2016-01-28 16:44:27 -0600105 if not os.path.exists(path):
106 os.mkdir(path)
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600107
108 # Listen to changes in the property values and sync them to the BMC
Brad Bishop2a9fe662016-08-31 12:37:35 -0400109 bus.add_signal_receiver(
110 self.settings_signal_handler,
111 dbus_interface="org.freedesktop.DBus.Properties",
112 signal_name="PropertiesChanged",
Sergey Solomin4a2433f2016-10-03 10:22:57 -0500113 path=name)
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600114
115 # Create the dbus properties
Sergey Solomin4a2433f2016-10-03 10:22:57 -0500116 for setting in settings.itervalues():
Sergey Solomin62b55f32016-10-13 10:40:27 -0500117 if setting['type'] is 'instance_query':
118 continue
119 self.set_settings_property(
Yi Li5ebab482016-11-22 16:34:18 +0800120 setting['name'], setting['type'], setting['default'],
121 self.fname)
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530122 # Done with consuming factory settings.
123 self.adminmode = False
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600124
Sergey Solomin62b55f32016-10-13 10:40:27 -0500125 def get_bmc_value(self, name, fname):
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600126 try:
Sergey Solomin62b55f32016-10-13 10:40:27 -0500127 with open(path.join(self.path, fname + name), 'r') as f:
Adriana Kobylak41a925e2016-01-28 16:44:27 -0600128 return f.read()
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600129 except (IOError):
130 pass
Adriana Kobylak41a925e2016-01-28 16:44:27 -0600131 return None
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600132
Brad Bishop2a9fe662016-08-31 12:37:35 -0400133 # Create dbus properties based on bmc value.
134 # This will be either a value previously set,
135 # or the default file value if the BMC value
136 # does not exist.
Sergey Solomin62b55f32016-10-13 10:40:27 -0500137 def set_settings_property(self, attr_name, attr_type, value, fname):
Patrick Williams3b8d0552017-04-06 14:14:15 -0500138 default = value
139
140 # Read from file.
Sergey Solomin62b55f32016-10-13 10:40:27 -0500141 bmcv = self.get_bmc_value(attr_name, fname)
Adriana Kobylak41a925e2016-01-28 16:44:27 -0600142 if bmcv:
143 value = bmcv
Patrick Williams3b8d0552017-04-06 14:14:15 -0500144
145 # Perform type mapping.
146 type_map = {"i": int, "s": str, "b": bool}[attr_type]
147 real_value = type_map(value)
148 real_default = type_map(default)
149
150 try:
151 self.Set(DBUS_NAME, attr_name, real_value)
152 except ValueError:
153 print("Persistent value for {} is invalid: {}{} had '{}', "
154 "using '{}'.".format(attr_name, fname, attr_name,
155 value, default))
156 self.Set(DBUS_NAME, attr_name, real_default)
157 self.set_system_settings(attr_name, real_default, fname)
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600158
159 # Save the settings to the BMC. This will write the settings value in
160 # individual files named by the property name to the BMC.
Sergey Solomin62b55f32016-10-13 10:40:27 -0500161 def set_system_settings(self, name, value, fname):
162 bmcv = self.get_bmc_value(name, fname)
Adriana Kobylak41a925e2016-01-28 16:44:27 -0600163 if bmcv != value:
Sergey Solomin62b55f32016-10-13 10:40:27 -0500164 filepath = path.join(self.path, fname + name)
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600165 with open(filepath, 'w') as f:
Adriana Kobylak41a925e2016-01-28 16:44:27 -0600166 f.write(str(value))
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600167
168 # Signal handler for when one ore more settings properties were updated.
169 # This will sync the changes to the BMC.
Brad Bishop2a9fe662016-08-31 12:37:35 -0400170 def settings_signal_handler(
Yi Li5ebab482016-11-22 16:34:18 +0800171 self, interface_name, changed_properties, invalidated_properties):
Adriana Kobylak41a925e2016-01-28 16:44:27 -0600172 for name, value in changed_properties.items():
Yi Li5ebab482016-11-22 16:34:18 +0800173 self.set_system_settings(name, value, self.fname)
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600174
175 # Placeholder signal. Needed to register the settings interface.
Adriana Kobylak41a925e2016-01-28 16:44:27 -0600176 @dbus.service.signal(DBUS_NAME, signature='s')
177 def SettingsUpdated(self, sname):
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600178 pass
179
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530180 def validate_regex(self, regex, value):
181 if not re.compile(regex).search(value):
Brad Bishopc1e5e9f2016-09-29 09:40:01 -0400182 raise ValueError("Invalid input. Data does not satisfy regex")
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530183
184 def validate_range(self, min, max, value):
185 if value not in range(min, max):
Brad Bishopc1e5e9f2016-09-29 09:40:01 -0400186 raise ValueError("Invalid input. Data not in allowed range")
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530187
Vishwanatha Subbannaeb1bea82017-01-12 16:42:44 +0530188 def validate_list(self, lst, value):
189 if value not in map(lambda val: val, lst):
Brad Bishopc1e5e9f2016-09-29 09:40:01 -0400190 raise ValueError("Invalid input. Data not in allowed values")
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530191
192 # validate host network configuration
193 # need "ipaddress=,prefix=,gateway=,mac=,addr_type="
194 # Must be able to handle any order
195 def validate_net_config(self, value):
196 if self.adminmode:
197 return
198
199 # Need all of these to be given by the user.
200 user_config = []
201 all_config = ['ipaddress', 'prefix', 'gateway', 'mac', 'addr_type']
202
203 # This has a hard data format mentioned above so no blanks allowed.
204 if value.count(" ") or value.count("=") != 5:
Brad Bishopc1e5e9f2016-09-29 09:40:01 -0400205 raise ValueError("Invalid Network Data. No white spaces allowed")
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530206
207 config = value.split(',')
208 for key_value in config:
Brad Bishopc1e5e9f2016-09-29 09:40:01 -0400209 key, value = key_value.split('=')
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530210 if not key or not value:
Brad Bishopc1e5e9f2016-09-29 09:40:01 -0400211 raise ValueError("Invalid key or Data")
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530212
213 # Add the current key seen so we can compare at the end to see
214 # if all values have been given
215 user_config.append(key.lower())
216
217 if key.lower() == 'ipaddress' or key.lower() == 'gateway':
Brad Bishopc1e5e9f2016-09-29 09:40:01 -0400218 IP(value)
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530219
220 elif key.lower() == 'mac':
221 regex = '([a-fA-F0-9]{2}[:|\-]?){6}'
222 self.validate_regex(regex, value)
223
224 elif key.lower() == 'prefix':
225 self.validate_range(0, 33, int(value))
226
227 elif key.lower() == 'addr_type':
228 allowed = ["STATIC", "DYNAMIC"]
Vishwanatha Subbannaeb1bea82017-01-12 16:42:44 +0530229 self.validate_list(allowed, value)
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530230
231 # Did user pass everything ??
232 if set(all_config) - set(user_config):
Brad Bishopc1e5e9f2016-09-29 09:40:01 -0400233 raise ValueError(
234 "Invalid Network Data. All information is mandatory")
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530235
236 # Validate to see if the changes are in order
237 def input_validator(self, iface, proprty, value):
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530238 # User entered key is not present
Sergey Solomin4a2433f2016-10-03 10:22:57 -0500239 shk = None
240 for attr in self.settings.itervalues():
241 if attr['name'] == proprty:
242 shk = attr
243
244 if shk is None:
Brad Bishopc1e5e9f2016-09-29 09:40:01 -0400245 raise KeyError("Invalid Property")
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530246
Sergey Solomin4a2433f2016-10-03 10:22:57 -0500247 validation = shk.get('validation', None)
248
249 if validation == 'list':
Vishwanatha Subbannaeb1bea82017-01-12 16:42:44 +0530250 self.validate_list(shk['allowed'], value)
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530251
Sergey Solomin4a2433f2016-10-03 10:22:57 -0500252 elif validation == 'range':
Gunnar Millsa16abc52018-01-27 10:02:34 -0600253 self.validate_range(shk['min'], shk['max'] + 1, value)
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530254
Sergey Solomin4a2433f2016-10-03 10:22:57 -0500255 elif validation == 'regex':
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530256 self.validate_regex(shk['regex'], value)
257
Sergey Solomin4a2433f2016-10-03 10:22:57 -0500258 elif validation == 'custom':
Brad Bishopc1e5e9f2016-09-29 09:40:01 -0400259 getattr(self, shk['method'])(value)
Vishwanatha Subbanna5b090c62016-09-21 15:49:26 +0530260
Gunnar Millsa16abc52018-01-27 10:02:34 -0600261
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600262if __name__ == '__main__':
263 dbus.mainloop.glib.DBusGMainLoop(set_as_default=True)
264
Brad Bishop5ef7fe62016-05-17 08:39:17 -0400265 bus = get_dbus()
Sergey Solomin4a2433f2016-10-03 10:22:57 -0500266 allobjects = create_object(s.SETTINGS)
267 lastobject = None
268 objs = []
269 for o, settings in allobjects.iteritems():
270 objs.append(HostSettingsObject(bus, o, settings, "/var/lib/obmc/"))
271 objs[-1].unmask_signals()
Sergey Solomin4a2433f2016-10-03 10:22:57 -0500272 mainloop = gobject.MainLoop()
Brad Bishop2cbef3d2016-08-31 12:40:13 -0400273 name = dbus.service.BusName(DBUS_NAME, bus)
Adriana Kobylak4c60e5e2016-01-10 15:22:45 -0600274 print "Running HostSettingsService"
275 mainloop.run()
Brad Bishop31c42f02016-09-29 09:35:32 -0400276
277# vim: tabstop=8 expandtab shiftwidth=4 softtabstop=4