)]}'
{"/COMMIT_MSG":[{"author":{"_account_id":11655,"name":"Julia Kreger","email":"juliaashleykreger@gmail.com","username":"jkreger","status":"Flying to the moon with a Jetpack!"},"change_message_id":"2d657c21ddf1d29f4a5b48c51e13184b213913f2","unresolved":true,"context_lines":[{"line_number":11,"context_line":"favour of the ThermalSubsystem, PowerSubsystem, and Sensors resources"},{"line_number":12,"context_line":"introduced in Redfish 2020.4. Newer BMC firmwares (for example"},{"line_number":13,"context_line":"Supermicro 01.03.02.06 on the X14DBG-DAP platform, which advertises"},{"line_number":14,"context_line":"Chassis.v1_25_2) no longer expose the deprecated blocks at all, which"},{"line_number":15,"context_line":"causes sushy to raise MissingAttributeError when consumers (such as"},{"line_number":16,"context_line":"Ironic\u0027s RedfishManagement.get_sensors_data) read chassis.thermal or"},{"line_number":17,"context_line":"chassis.power. Until those consumers migrate to the new resources,"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":4,"id":"0bf3d461_eed83304","line":14,"range":{"start_line":14,"start_character":17,"end_line":14,"end_character":63},"updated":"2026-07-31 17:31:36.000000000","message":"Yeah, this was not the intent of the Redfish Forum, but they are aware some vendors have removed prior entries. More so the issue is what was the interpretation of deprecation, and many vendors view that more-so as \"immediate removal\" which created the situation anyhow. :\\","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"}],"/PATCHSET_LEVEL":[{"author":{"_account_id":11655,"name":"Julia Kreger","email":"juliaashleykreger@gmail.com","username":"jkreger","status":"Flying to the moon with a Jetpack!"},"change_message_id":"2d657c21ddf1d29f4a5b48c51e13184b213913f2","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"8a20ce80_aaea4fea","updated":"2026-07-31 17:31:36.000000000","message":"Overall, LGTM, but its difficult to really feel like this is a complete review because of the size of the change. It is a lot and we generally advise to try and try and keep the changes to smaller logical blocks. But, this is is still fairly self contained. In discussion with some other reviewers, similar sentiment was noted, its just a lot and folks haven\u0027t really managed to get into reviewing this one as a result. That being said, hopefully this helps. If you need anything else reviewed in the future, please feel free to note in #openstack-ironic.\n\nOne last item, this really should have a release note, but if you don\u0027t need to revise this one, I would not worry that much. We can always tack on a release note after the fact.\n\nThanks!","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"},{"author":{"_account_id":23851,"name":"Riccardo Pittau","email":"elfosardo@gmail.com","username":"elfosardo"},"change_message_id":"67a3fa57a7590587ab588825f4cf9f9fd897f16f","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":4,"id":"199615bb_315602fc","updated":"2026-08-18 13:36:51.000000000","message":"bumped to next release","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"}],"sushy/resources/chassis/chassis.py":[{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"da40040c62b44cdc3b01dd04b4135e0478e599c2","unresolved":false,"context_lines":[{"line_number":33,"context_line":"LOG \u003d logging.getLogger(__name__)"},{"line_number":34,"context_line":""},{"line_number":35,"context_line":""},{"line_number":36,"context_line":"class _SynthesizedStatus:"},{"line_number":37,"context_line":"    \"\"\"Quacks like common.StatusField for synthesized chassis resources.\"\"\""},{"line_number":38,"context_line":""},{"line_number":39,"context_line":"    def __init__(self, state\u003dNone, health\u003dNone, health_rollup\u003dNone):"}],"source_content_type":"text/x-python","patch_set":4,"id":"fa2b996d_128deedb","line":36,"updated":"2026-08-18 15:41:55.000000000","message":"I don\u0027t think this class is needed. sensor.status / fan.status / psu.status are already common.StatusField instances exposing state, health and health_rollup, so the three status properties could just be \u0027return self._sensor.status\u0027. As written the wrapper adds a class and silently drops health_rollup.\n\nThe one thing it buys you is a guaranteed non-None object, but legacy returns None for a missing Status block too (common.StatusField -\u003e None when absent), and ironic handles that fine, so a passthrough is equally compatible.","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"da40040c62b44cdc3b01dd04b4135e0478e599c2","unresolved":false,"context_lines":[{"line_number":78,"context_line":"        return None"},{"line_number":79,"context_line":""},{"line_number":80,"context_line":"    @property"},{"line_number":81,"context_line":"    def max_reading_range_temp(self):"},{"line_number":82,"context_line":"        try:"},{"line_number":83,"context_line":"            return self._sensor.thresholds.upper_fatal.reading"},{"line_number":84,"context_line":"        except AttributeError:"}],"source_content_type":"text/x-python","patch_set":4,"id":"0a8ef269_e647bb07","line":81,"updated":"2026-08-18 15:41:55.000000000","message":"This mapping is not equivalent to the legacy attribute. max_reading_range_temp / min_reading_range_temp are MaxReadingRangeTemp / MinReadingRangeTemp (thermal.py:107-113) -- the sensor\u0027s measurable range, not alarm trip points. Sensor has exact equivalents, ReadingRangeMax / ReadingRangeMin, which the new Sensor class doesn\u0027t model yet; those should be the source here. Same concern for max/min_allowable_operating_value below.\n\nironic passes these keys straight through under the legacy names, so consumers currently receive UpperFatal/LowerFatal labelled as range limits.\n\nRelated: thermal.Sensor (thermal.py:23-60) defines upper_threshold_critical, upper_threshold_fatal, upper_threshold_non_critical and the lower_* counterparts, which are the natural home for the Thresholds values. The adapters don\u0027t expose them at all right now, so hasattr-based consumers silently lose them.","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"da40040c62b44cdc3b01dd04b4135e0478e599c2","unresolved":false,"context_lines":[{"line_number":141,"context_line":"        return speed.reading if speed else None"},{"line_number":142,"context_line":""},{"line_number":143,"context_line":"    @property"},{"line_number":144,"context_line":"    def reading_units(self):"},{"line_number":145,"context_line":"        speed \u003d self._fan.speed_percent"},{"line_number":146,"context_line":"        if speed and speed.speed_rpm is not None:"},{"line_number":147,"context_line":"            return \u0027RPM\u0027"}],"source_content_type":"text/x-python","patch_set":4,"id":"5d101931_090022d1","line":144,"updated":"2026-08-18 15:41:55.000000000","message":"Legacy reading_units is MappedField(\u0027ReadingUnits\u0027, FanReadingUnit) (thermal.py:89), so this should return the_cons.FanReadingUnit.RPM / .PERCENT rather than a bare string. ironic\u0027s _sensor2dict happens to survive it (attr.value if hasattr(attr, \u0027value\u0027) else attr), but anything doing \u0027fan.reading_units \u003d\u003d FanReadingUnit.RPM\u0027 breaks.\n\nAlso, when speed_percent is None this returns \u0027Percent\u0027 while .reading returns None -- units for a reading that doesn\u0027t exist. Probably want None in that case.","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"da40040c62b44cdc3b01dd04b4135e0478e599c2","unresolved":false,"context_lines":[{"line_number":230,"context_line":"    def power_supply_type(self):"},{"line_number":231,"context_line":"        return self._psu.power_supply_type"},{"line_number":232,"context_line":""},{"line_number":233,"context_line":"    def _metrics_safe(self):"},{"line_number":234,"context_line":"        try:"},{"line_number":235,"context_line":"            return self._psu.metrics"},{"line_number":236,"context_line":"        except exceptions.MissingAttributeError:"}],"source_content_type":"text/x-python","patch_set":4,"id":"8960343a_118c0ecc","line":233,"updated":"2026-08-18 15:41:55.000000000","message":"Catching only MissingAttributeError is too narrow. A BMC that advertises a Metrics link which 404s raises ResourceNotFoundError, and that escapes line_input_voltage / last_power_output_watts.\n\nThat matters more than it looks: ironic reaches these through hasattr(), which only swallows AttributeError, so the exception propagates to the per-category \u0027except SushyError\u0027 in _process_chassis_sensors and drops *all* PSU sensors for the chassis. Suggest catching exceptions.SushyError and LOG.debug\u0027ing.","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"da40040c62b44cdc3b01dd04b4135e0478e599c2","unresolved":false,"context_lines":[{"line_number":293,"context_line":"            return _SynthesizedStatus()"},{"line_number":294,"context_line":""},{"line_number":295,"context_line":"    @property"},{"line_number":296,"context_line":"    def fans(self):"},{"line_number":297,"context_line":"        try:"},{"line_number":298,"context_line":"            ts \u003d self._chassis.thermal_subsystem"},{"line_number":299,"context_line":"        except exceptions.MissingAttributeError:"}],"source_content_type":"text/x-python","patch_set":4,"id":"304aeec9_87bc7b89","line":296,"updated":"2026-08-18 15:41:55.000000000","message":"Nit: swallowing MissingAttributeError into [] here (and in _SynthPower.power_supplies) is silent. A LOG.debug would make \u0027chassis reports no fans\u0027 diagnosable, which is the exact class of problem this change is fixing.","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"da40040c62b44cdc3b01dd04b4135e0478e599c2","unresolved":false,"context_lines":[{"line_number":304,"context_line":"            return []"},{"line_number":305,"context_line":""},{"line_number":306,"context_line":"    @property"},{"line_number":307,"context_line":"    def temperatures(self):"},{"line_number":308,"context_line":"        try:"},{"line_number":309,"context_line":"            sensors \u003d self._chassis.sensors"},{"line_number":310,"context_line":"        except exceptions.MissingAttributeError:"}],"source_content_type":"text/x-python","patch_set":4,"id":"ef9cacc6_238f21dd","line":307,"updated":"2026-08-18 15:41:55.000000000","message":"This is the main scalability concern. get_members() issues one GET per member (base.py:844-856), so on the firmware from the commit message this is 1 + 50 requests just for temperatures, and the same shape applies to .fans below and _SynthPower.power_supplies (plus one more GET per PSU Metrics). ironic\u0027s get_sensors_data goes from ~3 requests to ~70+ per node per 60s poll; management.py has a comment warning about exactly this.\n\nAlso worth noting that get_members() is @cache_it-decorated, so repeat calls re-refresh(force\u003dFalse) every member -- another GET each. These synth properties aren\u0027t cached, so accessing .temperatures twice doubles the cost.\n\nThermalSubsystem/ThermalMetrics exposes TemperatureReadingsCelsius as an excerpt array, i.e. all temperatures in a single GET, and the Fans/PowerSupplies collections can be $expand\u0027ed. There\u0027s already an idiom for that in tree: EXPAND_QUERY / storage_expanded / chassis_expanded in sushy/resources/system/system.py:45.","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"da40040c62b44cdc3b01dd04b4135e0478e599c2","unresolved":false,"context_lines":[{"line_number":637,"context_line":"            )"},{"line_number":638,"context_line":"        if \u0027PowerSubsystem\u0027 in self._json:"},{"line_number":639,"context_line":"            return _SynthPower(self)"},{"line_number":640,"context_line":"        raise exceptions.MissingAttributeError("},{"line_number":641,"context_line":"            attribute\u003d\u0027Power/PowerSubsystem\u0027, resource\u003dself._path)"},{"line_number":642,"context_line":""},{"line_number":643,"context_line":"    @property"}],"source_content_type":"text/x-python","patch_set":4,"id":"e3c51c84_d28c3088","line":640,"updated":"2026-08-18 15:41:55.000000000","message":"Nit: these two new raise paths (and the Power one) aren\u0027t covered by a test -- worth one case asserting MissingAttributeError when neither schema is present, since it\u0027s new behaviour replacing the raise that used to come out of get_sub_resource_path_by.","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"da40040c62b44cdc3b01dd04b4135e0478e599c2","unresolved":false,"context_lines":[{"line_number":667,"context_line":"                json_doc\u003dself._get_expanded_data(\u0027Thermal\u0027),"},{"line_number":668,"context_line":"            )"},{"line_number":669,"context_line":"        if \u0027ThermalSubsystem\u0027 in self._json or \u0027Sensors\u0027 in self._json:"},{"line_number":670,"context_line":"            return _SynthThermal(self)"},{"line_number":671,"context_line":"        raise exceptions.MissingAttributeError("},{"line_number":672,"context_line":"            attribute\u003d\u0027Thermal/ThermalSubsystem\u0027, resource\u003dself._path)"},{"line_number":673,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"71a88720_5411b153","line":670,"updated":"2026-08-18 15:41:55.000000000","message":"Design question rather than a defect.\n\nReturning _SynthThermal / _SynthPower here means chassis.thermal and chassis.power may now yield something that is not a ResourceBase: no refresh(), invalidate(), json, path, or oem_vendors. A consumer calling chassis.thermal.refresh() gets an AttributeError where it previously worked. The compat argument holds for the specific ironic helpers you targeted, but not for the published API generally, and the tests assert isinstance(result, _SynthThermal), which makes a private class part of the observable contract.\n\nOptions as I see them: make the adapters public with a documented compat contract; implement the minimal ResourceBase surface; or land the new resources only plus a narrow fix so chassis.thermal doesn\u0027t raise on new-schema BMCs, and let ironic migrate. My preference is the first, with the compat behaviour spelled out in the docstrings, but I\u0027m open to disagreement here. Worth settling before more polish goes in since it determines how much of the shim survives.","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"da40040c62b44cdc3b01dd04b4135e0478e599c2","unresolved":false,"context_lines":[{"line_number":673,"context_line":""},{"line_number":674,"context_line":"    @property"},{"line_number":675,"context_line":"    @utils.cache_it"},{"line_number":676,"context_line":"    def thermal_subsystem(self):"},{"line_number":677,"context_line":"        \"\"\"Property to reference the :class:`ThermalSubsystem` instance."},{"line_number":678,"context_line":""},{"line_number":679,"context_line":"        :raises: MissingAttributeError if \u0027ThermalSubsystem\u0027 is not exposed."}],"source_content_type":"text/x-python","patch_set":4,"id":"d59a1adc_47a3d1dc","line":676,"updated":"2026-08-18 15:41:55.000000000","message":"These three new accessors (thermal_subsystem, power_subsystem, sensors) don\u0027t pass json_doc\u003dself._get_expanded_data(\u0027ThermalSubsystem\u0027) etc., unlike the legacy thermal/power properties immediately above, which do.\n\nironic reaches chassis through system.chassis_expanded (\u0027?$expand\u003d.($levels\u003d1)\u0027), so the ThermalSubsystem/PowerSubsystem bodies are already sitting in self._json and get re-fetched anyway. Three avoidable round-trips, and inconsistent with the pattern this file just established.","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"}],"sushy/resources/chassis/power_subsystem/power_subsystem.py":[{"author":{"_account_id":11655,"name":"Julia Kreger","email":"juliaashleykreger@gmail.com","username":"jkreger","status":"Flying to the moon with a Jetpack!"},"change_message_id":"2d657c21ddf1d29f4a5b48c51e13184b213913f2","unresolved":false,"context_lines":[{"line_number":28,"context_line":"    data_source_uri \u003d base.Field(\u0027DataSourceUri\u0027)"},{"line_number":29,"context_line":""},{"line_number":30,"context_line":""},{"line_number":31,"context_line":"class PowerSupplyMetrics(base.ResourceBase):"},{"line_number":32,"context_line":"    \"\"\"Live electrical metrics for a single PowerSupply.\"\"\""},{"line_number":33,"context_line":""},{"line_number":34,"context_line":"    identity \u003d base.Field(\u0027Id\u0027)"}],"source_content_type":"text/x-python","patch_set":4,"id":"3bbbb8a3_d69038a1","line":31,"updated":"2026-07-31 17:31:36.000000000","message":"as a note to the other reviewers, this is in a separate schema from the power subsystem.","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"da40040c62b44cdc3b01dd04b4135e0478e599c2","unresolved":false,"context_lines":[{"line_number":71,"context_line":"                                      adapter\u003dutils.int_or_none)"},{"line_number":72,"context_line":"    \"\"\"The maximum capacity of this Power Supply, in watts\"\"\""},{"line_number":73,"context_line":""},{"line_number":74,"context_line":"    input_nominal_voltage_type \u003d base.Field(\u0027InputNominalVoltageType\u0027)"},{"line_number":75,"context_line":"    \"\"\"The nominal voltage type supported as input (e.g. AC200To240V)\"\"\""},{"line_number":76,"context_line":""},{"line_number":77,"context_line":"    line_input_status \u003d base.Field(\u0027LineInputStatus\u0027)"}],"source_content_type":"text/x-python","patch_set":4,"id":"ebe49c3e_f7849791","line":74,"updated":"2026-08-18 15:41:55.000000000","message":"Nit: input_nominal_voltage_type and line_input_status are plain Fields where the convention in this package is MappedField plus an enum (cf. pow_cons.LineInputVoltageType). The tests assert the raw strings \u0027AC200To240V\u0027 / \u0027Normal\u0027, which locks the inconsistency in.","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"da40040c62b44cdc3b01dd04b4135e0478e599c2","unresolved":false,"context_lines":[{"line_number":77,"context_line":"    line_input_status \u003d base.Field(\u0027LineInputStatus\u0027)"},{"line_number":78,"context_line":"    \"\"\"The status of the line input (e.g. Normal, LossOfInput)\"\"\""},{"line_number":79,"context_line":""},{"line_number":80,"context_line":"    power_supply_type \u003d base.MappedField(\u0027PowerSupplyType\u0027,"},{"line_number":81,"context_line":"                                         pow_cons.PowerSupplyType)"},{"line_number":82,"context_line":"    \"\"\"The Power Supply type (AC or DC)\"\"\""},{"line_number":83,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"bdd51295_c378d85d","line":80,"updated":"2026-08-18 15:41:55.000000000","message":"Reusing the legacy pow_cons.PowerSupplyType here loses a value: the newer PowerSupply schema adds DCRegulator, which isn\u0027t in that enum. MappedField returns None for unmapped values, so affected PSUs silently report no type. Either add DCRegulator to the legacy enum or define a separate enum for the new schema.","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"}],"sushy/resources/chassis/sensors/constants.py":[{"author":{"_account_id":11655,"name":"Julia Kreger","email":"juliaashleykreger@gmail.com","username":"jkreger","status":"Flying to the moon with a Jetpack!"},"change_message_id":"2d657c21ddf1d29f4a5b48c51e13184b213913f2","unresolved":false,"context_lines":[{"line_number":11,"context_line":"# under the License."},{"line_number":12,"context_line":""},{"line_number":13,"context_line":"# This is referred from Redfish standard schema."},{"line_number":14,"context_line":"# http://redfish.dmtf.org/schemas/v1/Sensor.json"},{"line_number":15,"context_line":""},{"line_number":16,"context_line":"import enum"},{"line_number":17,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"61be6977_e815d658","line":14,"updated":"2026-07-31 17:31:36.000000000","message":"This should likely note the specific schema and not the high level definition.\n\nhttp://redfish.dmtf.org/schemas/v1/Sensor.v1_13_0.json#/definitions/Sensor is what I compared to just to sanity check the values.","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"}],"sushy/resources/chassis/sensors/sensors.py":[{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"da40040c62b44cdc3b01dd04b4135e0478e599c2","unresolved":false,"context_lines":[{"line_number":68,"context_line":"    physical_sub_context \u003d base.Field(\u0027PhysicalSubContext\u0027)"},{"line_number":69,"context_line":"    \"\"\"Refines physical_context within a device, e.g. \u0027Input\u0027, \u0027Output\u0027.\"\"\""},{"line_number":70,"context_line":""},{"line_number":71,"context_line":"    sensor_resetable \u003d base.Field(\u0027SensorResetTime\u0027)"},{"line_number":72,"context_line":"    \"\"\"Indicates whether the sensor reading can be reset\"\"\""},{"line_number":73,"context_line":""},{"line_number":74,"context_line":"    status \u003d common.StatusField(\u0027Status\u0027)"}],"source_content_type":"text/x-python","patch_set":4,"id":"436b593e_803cc41e","line":71,"updated":"2026-08-18 15:41:55.000000000","message":"The name and docstring don\u0027t match the field. SensorResetTime is the timestamp of the most recent reset of the time-based properties, not a boolean saying whether the sensor can be reset. Suggest renaming to sensor_reset_time and adjusting the docstring.","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"da40040c62b44cdc3b01dd04b4135e0478e599c2","unresolved":false,"context_lines":[{"line_number":74,"context_line":"    status \u003d common.StatusField(\u0027Status\u0027)"},{"line_number":75,"context_line":"    \"\"\"Status of the sensor\"\"\""},{"line_number":76,"context_line":""},{"line_number":77,"context_line":"    thresholds \u003d ThresholdsField(\u0027Thresholds\u0027)"},{"line_number":78,"context_line":"    \"\"\"The threshold values used by this sensor for alerts\"\"\""},{"line_number":79,"context_line":""},{"line_number":80,"context_line":""}],"source_content_type":"text/x-python","patch_set":4,"id":"db75b45d_b0df4046","line":77,"updated":"2026-08-18 15:41:55.000000000","message":"Sensor is missing ReadingRangeMax / ReadingRangeMin. Those are the correct sources for the legacy max_reading_range_temp / min_reading_range_temp attributes the adapter synthesises -- see my comment on chassis.py:81 -- so adding reading_range_max / reading_range_min here would let that mapping be right.","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"}],"sushy/resources/chassis/thermal_subsystem/thermal_subsystem.py":[{"author":{"_account_id":11655,"name":"Julia Kreger","email":"juliaashleykreger@gmail.com","username":"jkreger","status":"Flying to the moon with a Jetpack!"},"change_message_id":"2d657c21ddf1d29f4a5b48c51e13184b213913f2","unresolved":false,"context_lines":[{"line_number":19,"context_line":"from sushy import utils"},{"line_number":20,"context_line":""},{"line_number":21,"context_line":""},{"line_number":22,"context_line":"class _FanSpeedReading(base.CompositeField):"},{"line_number":23,"context_line":"    \"\"\"Fan speed reading, either as a percent or raw RPM.\"\"\""},{"line_number":24,"context_line":""},{"line_number":25,"context_line":"    reading \u003d base.Field(\u0027Reading\u0027)"}],"source_content_type":"text/x-python","patch_set":4,"id":"a516eb1a_4d7f4758","line":22,"updated":"2026-07-31 17:31:36.000000000","message":"jfyi, this is rooted on a instance of Sensor, not Fan, as it is a sensor reading.","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"},{"author":{"_account_id":11655,"name":"Julia Kreger","email":"juliaashleykreger@gmail.com","username":"jkreger","status":"Flying to the moon with a Jetpack!"},"change_message_id":"2d657c21ddf1d29f4a5b48c51e13184b213913f2","unresolved":false,"context_lines":[{"line_number":32,"context_line":"    \"\"\"Optional URI to the underlying Sensor resource\"\"\""},{"line_number":33,"context_line":""},{"line_number":34,"context_line":""},{"line_number":35,"context_line":"class Fan(base.ResourceBase):"},{"line_number":36,"context_line":"    \"\"\"A single fan within a ThermalSubsystem.\"\"\""},{"line_number":37,"context_line":""},{"line_number":38,"context_line":"    identity \u003d base.Field(\u0027Id\u0027, required\u003dTrue)"}],"source_content_type":"text/x-python","patch_set":4,"id":"a420f256_54e3344e","line":35,"updated":"2026-07-31 17:31:36.000000000","message":"https://redfish.dmtf.org/schemas/v1/Fan.v1_6_0.json","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"}],"sushy/tests/unit/json_samples/sensor.json":[{"author":{"_account_id":11655,"name":"Julia Kreger","email":"juliaashleykreger@gmail.com","username":"jkreger","status":"Flying to the moon with a Jetpack!"},"change_message_id":"2d657c21ddf1d29f4a5b48c51e13184b213913f2","unresolved":true,"context_lines":[{"line_number":20,"context_line":"        \"UpperFatal\": {"},{"line_number":21,"context_line":"            \"Activation\": \"Increasing\","},{"line_number":22,"context_line":"            \"Reading\": 100"},{"line_number":23,"context_line":"        }"},{"line_number":24,"context_line":"    }"},{"line_number":25,"context_line":"}"}],"source_content_type":"application/json","patch_set":4,"id":"274054f0_b993fd0a","line":23,"updated":"2026-07-31 17:31:36.000000000","message":"As a nit, this feels like the minimal, and there are other values in the support added as I can see it which also cross-references to the latest schema. Then again, not everyone supports everything and the spec is quite... complete. So, I guess LGTM as-is, but might be good to have additional thresholds.","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"}],"sushy/tests/unit/resources/chassis/test_chassis.py":[{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"da40040c62b44cdc3b01dd04b4135e0478e599c2","unresolved":false,"context_lines":[{"line_number":303,"context_line":"        s \u003d self.chassis.sensors"},{"line_number":304,"context_line":"        self.assertEqual(3, len(s.members_identities))"},{"line_number":305,"context_line":""},{"line_number":306,"context_line":"    def test_thermal_returns_synth_when_only_new_schema(self):"},{"line_number":307,"context_line":"        # chassis_new_schema.json has no \u0027Thermal\u0027 key, so the synth"},{"line_number":308,"context_line":"        # adapter should be returned in place of the deprecated"},{"line_number":309,"context_line":"        # thermal.Thermal resource."}],"source_content_type":"text/x-python","patch_set":4,"id":"837bf020_24cf4b2d","line":306,"updated":"2026-08-18 15:41:55.000000000","message":"Two things here: the imports in this and the following methods (and at 404/411) should move to module scope, and asserting isinstance against the private _Synth* classes couples the tests to internals. Asserting on the surfaced attribute values -- which test_synth_thermal_temperatures and friends already do nicely -- would be more robust.","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"da40040c62b44cdc3b01dd04b4135e0478e599c2","unresolved":false,"context_lines":[{"line_number":372,"context_line":"        # line_input_voltage / last_power_output_watts are pulled via"},{"line_number":373,"context_line":"        # the PowerSupplyMetrics sub-resource"},{"line_number":374,"context_line":"        self.assertEqual(205, psus[0].line_input_voltage)"},{"line_number":375,"context_line":"        self.assertEqual(189, psus[0].last_power_output_watts)"},{"line_number":376,"context_line":""},{"line_number":377,"context_line":""},{"line_number":378,"context_line":"class ChassisLegacyPreferredTestCase(base.TestCase):"}],"source_content_type":"text/x-python","patch_set":4,"id":"29cb3e77_e99bbe4f","line":375,"updated":"2026-08-18 15:41:55.000000000","message":"Missing coverage I noticed while reading: the SpeedRPM-absent branch of _SynthFan.reading / reading_units (the Percent fallback) and the no-Metrics branch of _SynthPowerSupply are both untested, and both are the paths most likely to be hit by a vendor that reports a partial ThermalSubsystem or PowerSubsystem.","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"},{"author":{"_account_id":5890,"name":"Doug Goldstein","email":"cardoe@cardoe.com","username":"cardoe"},"change_message_id":"da40040c62b44cdc3b01dd04b4135e0478e599c2","unresolved":false,"context_lines":[{"line_number":400,"context_line":"        self.chassis \u003d chassis.Chassis("},{"line_number":401,"context_line":"            self.conn, \u0027/redfish/v1/Chassis/1\u0027, redfish_version\u003d\u00271.5.0\u0027)"},{"line_number":402,"context_line":""},{"line_number":403,"context_line":"    def test_thermal_returns_legacy_when_both_present(self):"},{"line_number":404,"context_line":"        from sushy.resources.chassis.thermal import thermal as legacy_thermal"},{"line_number":405,"context_line":"        with open(\u0027sushy/tests/unit/json_samples/thermal.json\u0027) as f:"},{"line_number":406,"context_line":"            self.conn.get.return_value.json.return_value \u003d json.load(f)"}],"source_content_type":"text/x-python","patch_set":4,"id":"6a408c27_76d3b777","line":403,"updated":"2026-08-18 15:41:55.000000000","message":"This asserts the returned type but not the behaviour the docstring claims, i.e. that the new-schema resources are *not* consulted when the legacy link is present. An assertion that conn.get was called only for the legacy Thermal path would actually pin that down.","commit_id":"19bb2f0f1b7c990ed68a5f0f1e8303231fbb6b62"}]}
