From 0f4556fc2343dc0ade0bb1e0d1fc6f85770d77af Mon Sep 17 00:00:00 2001 From: "Andrey V.Kosteltsev" Date: Fri, 15 Jul 2022 10:36:51 +0300 Subject: First commit: Sila SNMP Sub Agent and configuration manager --- .clang-format | 84 ++ .gitignore | 3 + LICENSE | 201 +++++ Makefile.am | 11 + README.md | 230 +++++ agent/Makefile.am | 18 + agent/data/enums.hpp | 57 ++ agent/data/scalar.hpp | 150 ++++ agent/data/table.hpp | 348 ++++++++ agent/data/table/item.hpp | 157 ++++ agent/main.cpp | 180 ++++ agent/sila-snmp-agent.service.in | 15 + agent/sila/inventory.cpp | 249 ++++++ agent/sila/inventory.hpp | 32 + agent/sila/powerstate.cpp | 140 +++ agent/sila/powerstate.hpp | 38 + agent/sila/sensors.cpp | 385 +++++++++ agent/sila/sensors.hpp | 33 + agent/sila/sila_oid.hpp | 27 + agent/sila/software.cpp | 225 +++++ agent/sila/software.hpp | 32 + agent/snmp.cpp | 140 +++ agent/snmp.hpp | 26 + agent/snmp_oid.hpp | 83 ++ agent/snmptrap.hpp | 164 ++++ agent/snmpvars.hpp | 121 +++ agent/tracing.hpp | 30 + bootstrap.sh | 23 + configure.ac | 99 +++ mibs/SILA-MIB.txt | 947 +++++++++++++++++++++ sdbusplus/helper.hpp | 168 ++++ snmpcfg/Makefile.am | 41 + snmpcfg/sila-snmp-cfg-manager.service.in | 13 + snmpcfg/snmpcfg-server.cpp | 291 +++++++ snmpcfg/xyz/openbmc_project/SNMPCfg.interface.yaml | 13 + tests/emit-added-MEMBUF1.sh | 29 + tests/emit-added-ambient.sh | 29 + tests/emit-added-bmc.sh | 26 + tests/emit-added-opfw.sh | 26 + tests/emit-added-system-chassis.sh | 14 + tests/emit-change-ambient-warn.sh | 9 + tests/emit-change-ambient.sh | 8 + tests/emit-change-power-state.sh | 7 + tests/emit-removed-ambient.sh | 12 + 44 files changed, 4934 insertions(+) create mode 100644 .clang-format create mode 100644 .gitignore create mode 100644 LICENSE create mode 100644 Makefile.am create mode 100644 README.md create mode 100644 agent/Makefile.am create mode 100644 agent/data/enums.hpp create mode 100644 agent/data/scalar.hpp create mode 100644 agent/data/table.hpp create mode 100644 agent/data/table/item.hpp create mode 100644 agent/main.cpp create mode 100644 agent/sila-snmp-agent.service.in create mode 100644 agent/sila/inventory.cpp create mode 100644 agent/sila/inventory.hpp create mode 100644 agent/sila/powerstate.cpp create mode 100644 agent/sila/powerstate.hpp create mode 100644 agent/sila/sensors.cpp create mode 100644 agent/sila/sensors.hpp create mode 100644 agent/sila/sila_oid.hpp create mode 100644 agent/sila/software.cpp create mode 100644 agent/sila/software.hpp create mode 100644 agent/snmp.cpp create mode 100644 agent/snmp.hpp create mode 100644 agent/snmp_oid.hpp create mode 100644 agent/snmptrap.hpp create mode 100644 agent/snmpvars.hpp create mode 100644 agent/tracing.hpp create mode 100755 bootstrap.sh create mode 100644 configure.ac create mode 100644 mibs/SILA-MIB.txt create mode 100644 sdbusplus/helper.hpp create mode 100644 snmpcfg/Makefile.am create mode 100644 snmpcfg/sila-snmp-cfg-manager.service.in create mode 100644 snmpcfg/snmpcfg-server.cpp create mode 100644 snmpcfg/xyz/openbmc_project/SNMPCfg.interface.yaml create mode 100755 tests/emit-added-MEMBUF1.sh create mode 100755 tests/emit-added-ambient.sh create mode 100755 tests/emit-added-bmc.sh create mode 100755 tests/emit-added-opfw.sh create mode 100755 tests/emit-added-system-chassis.sh create mode 100755 tests/emit-change-ambient-warn.sh create mode 100755 tests/emit-change-ambient.sh create mode 100755 tests/emit-change-power-state.sh create mode 100755 tests/emit-removed-ambient.sh diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..37469de --- /dev/null +++ b/.clang-format @@ -0,0 +1,84 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -2 +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlinesLeft: false +AlignOperands: true +AlignTrailingComments: true +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: false +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterClass: true + AfterControlStatement: true + AfterEnum: true + AfterFunction: true + AfterNamespace: true + AfterObjCDeclaration: true + AfterStruct: true + AfterUnion: true + BeforeCatch: true + BeforeElse: true + IndentBraces: false +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Custom +BreakBeforeTernaryOperators: true +BreakConstructorInitializers: AfterColon +ColumnLimit: 80 +CommentPragmas: '^ IWYU pragma:' +ConstructorInitializerAllOnOneLineOrOnePerLine: false +ConstructorInitializerIndentWidth: 4 +ContinuationIndentWidth: 4 +Cpp11BracedListStyle: true +DerivePointerAlignment: true +PointerAlignment: Left +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: [ foreach, Q_FOREACH, BOOST_FOREACH ] +IndentCaseLabels: true +IndentWidth: 4 +IndentWrappedFunctionNames: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: false +SpaceAfterCStyleCast: false +SpaceBeforeAssignmentOperators: true +SpaceBeforeParens: ControlStatements +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +TabWidth: 4 +UseTab: Never +... diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..25a2537 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +ycm_extra_conf.py +cscope.files +cscope.out diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..8dada3e --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "{}" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright {yyyy} {name of copyright owner} + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/Makefile.am b/Makefile.am new file mode 100644 index 0000000..7226319 --- /dev/null +++ b/Makefile.am @@ -0,0 +1,11 @@ +SUBDIRS = . + +if WANT_AGENT +SUBDIRS += agent +endif +if WANT_CFG_MANAGER +SUBDIRS += snmpcfg +endif + +mibsdir = $(datarootdir)/snmp/mibs +mibs_DATA = mibs/SILA-MIB.txt diff --git a/README.md b/README.md new file mode 100644 index 0000000..e950a2a --- /dev/null +++ b/README.md @@ -0,0 +1,230 @@ +# sila-snmp + +This project containt two subprojects: +- [sila-snmp-agent](#sila-snmp-agent) +- [snmpcfg - DBus service for manage snmp configuration](#snmpcfg) + +## sila-snmp-agent + +This is a subagent for the original net-snmp daemon. +It queries DBus for values of available sensors, inventory items and some +other stuff, and exports their actual values over SNMP. + +### SILA-MIB.txt + +File SILA-MIB.txt describe a struc of exported data. +This file can be found at OpenBMC hosts in folder `/usr/share/snmp/mibs` and may be fetched from OpenBMC host over http. +```shell +$ wget https:///mibs/SILA-MIB.txt +``` + +### Basic SNMP + +This module export a host power state field and tree tables of sensors values. +```shell +$ snmptranslate -Tp -IR SILA-MIB::sila ++--sila(49769) + | + +--silaNotifications(0) + | | + | +--silaHostPowerStateNotification(1) + | +--silaTempSensorStateNotification(2) + | +--silaVoltSensorStateNotification(3) + | +--silaTachSensorStateNotification(4) + | + +--silaSensors(1) + | | + | +-- -R-- EnumVal silaHostPowerState(1) + | | Values: unknown(-1), off(0), on(1) + | | + | +--silaTempSensorsTable(2) + | | | + | | +--silaTempSensorsEntry(1) + | | | Index: silaTempSensorName + | | | + | | +-- ---- String silaTempSensorName(1) + | | | Size: 1..32 + | | +-- -R-- Integer32 silaTempSensorValue(2) + | | | Textual Convention: Degrees + | | +-- -R-- Integer32 silaTempSensorWarnLow(3) + | | | Textual Convention: Degrees + | | +-- -R-- Integer32 silaTempSensorWarnHigh(4) + | | | Textual Convention: Degrees + | | +-- -R-- Integer32 silaTempSensorCritLow(5) + | | | Textual Convention: Degrees + | | +-- -R-- Integer32 silaTempSensorCritHigh(6) + | | | Textual Convention: Degrees + | | +-- -R-- EnumVal silaTempSensorState(7) + | | Textual Convention: SensorState + | | Values: disabled(0), normal(1), warningLow(2), warningHigh(3), criticalLow(4), criticalHigh(5) + | | + | +--silaVoltSensorsTable(3) + | | | + | | +--silaVoltSensorsEntry(1) + | | | Index: silaVoltSensorName + | | | + | | +-- ---- String silaVoltSensorName(1) + | | | Size: 1..32 + | | +-- -R-- Integer32 silaVoltSensorValue(2) + | | | Textual Convention: Voltage + | | +-- -R-- Integer32 silaVoltSensorWarnLow(3) + | | | Textual Convention: Voltage + | | +-- -R-- Integer32 silaVoltSensorWarnHigh(4) + | | | Textual Convention: Voltage + | | +-- -R-- Integer32 silaVoltSensorCritLow(5) + | | | Textual Convention: Voltage + | | +-- -R-- Integer32 silaVoltSensorCritHigh(6) + | | | Textual Convention: Voltage + | | +-- -R-- EnumVal silaVoltSensorState(7) + | | Textual Convention: SensorState + | | Values: disabled(0), normal(1), warningLow(2), warningHigh(3), criticalLow(4), criticalHigh(5) + | | + | +--silaTachSensorsTable(4) + | | + | +--silaTachSensorsEntry(1) + | | Index: silaTachSensorName + | | + | +-- ---- String silaTachSensorName(1) + | | Size: 1..32 + | +-- -R-- Integer32 silaTachSensorValue(2) + | | Textual Convention: RPMS + | +-- -R-- Integer32 silaTachSensorWarnLow(3) + | | Textual Convention: RPMS + | +-- -R-- Integer32 silaTachSensorWarnHigh(4) + | | Textual Convention: RPMS + | +-- -R-- Integer32 silaTachSensorCritLow(5) + | | Textual Convention: RPMS + | +-- -R-- Integer32 silaTachSensorCritHigh(6) + | | Textual Convention: RPMS + | +-- -R-- EnumVal silaTachSensorState(7) + | Textual Convention: SensorState + | Values: disabled(0), normal(1), warningLow(2), warningHigh(3), criticalLow(4), criticalHigh(5) + | + +--silaConformance(2) + | + +--silaCompliances(1) + | | + | +--silaCommpliance(1) + | + +--silaGroups(2) + | + +--silaScalarFieldsGroup(1) + +--silaTempSensorsTableGroup(2) + +--silaVoltSensorsTableGroup(3) + +--silaTachSensorsTableGroup(4) + +--silaNotificationsGroup(10) +``` + +Content of tables may be viewed by command `snmptable`: +```shell +$ snmptable -v2c -cpublic -Ci manzoni.dev.sila.com SILA-MIB::silaTempSensorsTable +SNMP table: SILA-MIB::silaTempSensorsTable + + index silaTempSensorValue silaTempSensorWarnLow silaTempSensorWarnHigh silaTempSensorCritLow silaTempSensorCritHigh silaTempSensorState + "ambient" 27.000 °C .000 °C 35.000 °C .000 °C 40.000 °C normal + "RTC_temp1" 21.000 °C .000 °C 35.000 °C .000 °C 40.000 °C normal + "INlet_Temp1" 21.000 °C .000 °C 35.000 °C .000 °C 40.000 °C normal + "INlet_Temp2" 20.500 °C .000 °C 35.000 °C .000 °C 40.000 °C normal +"OUTlet_Temp1" 21.500 °C .000 °C 35.000 °C .000 °C 40.000 °C normal +"OUTlet_Temp2" 22.000 °C .000 °C 35.000 °C .000 °C 40.000 °C normal +... +``` + +All exporting data may be viewed by command `snmpwalk`: +```shell +$ snmpwalk -v2c -cpublic manzoni.dev.sila.com SILA-MIB::silaTempSensorsTable +SILA-MIB::silaTempSensorValue."ambient" = INTEGER: 26.750 °C +SILA-MIB::silaTempSensorValue."RTC_temp1" = INTEGER: 20.750 °C +SILA-MIB::silaTempSensorValue."INlet_Temp1" = INTEGER: 21.500 °C +SILA-MIB::silaTempSensorValue."INlet_Temp2" = INTEGER: 20.500 °C +SILA-MIB::silaTempSensorValue."OUTlet_Temp1" = INTEGER: 22.000 °C +SILA-MIB::silaTempSensorValue."OUTlet_Temp2" = INTEGER: 22.000 °C +SILA-MIB::silaTempSensorWarnLow."ambient" = INTEGER: .000 °C +SILA-MIB::silaTempSensorWarnLow."RTC_temp1" = INTEGER: .000 °C +SILA-MIB::silaTempSensorWarnLow."INlet_Temp1" = INTEGER: .000 °C +SILA-MIB::silaTempSensorWarnLow."INlet_Temp2" = INTEGER: .000 °C +SILA-MIB::silaTempSensorWarnLow."OUTlet_Temp1" = INTEGER: .000 °C +SILA-MIB::silaTempSensorWarnLow."OUTlet_Temp2" = INTEGER: .000 °C +SILA-MIB::silaTempSensorWarnHigh."ambient" = INTEGER: 35.000 °C +SILA-MIB::silaTempSensorWarnHigh."RTC_temp1" = INTEGER: 35.000 °C +SILA-MIB::silaTempSensorWarnHigh."INlet_Temp1" = INTEGER: 35.000 °C +SILA-MIB::silaTempSensorWarnHigh."INlet_Temp2" = INTEGER: 35.000 °C +SILA-MIB::silaTempSensorWarnHigh."OUTlet_Temp1" = INTEGER: 35.000 °C +SILA-MIB::silaTempSensorWarnHigh."OUTlet_Temp2" = INTEGER: 35.000 °C +SILA-MIB::silaTempSensorCritLow."ambient" = INTEGER: .000 °C +SILA-MIB::silaTempSensorCritLow."RTC_temp1" = INTEGER: .000 °C +SILA-MIB::silaTempSensorCritLow."INlet_Temp1" = INTEGER: .000 °C +SILA-MIB::silaTempSensorCritLow."INlet_Temp2" = INTEGER: .000 °C +SILA-MIB::silaTempSensorCritLow."OUTlet_Temp1" = INTEGER: .000 °C +SILA-MIB::silaTempSensorCritLow."OUTlet_Temp2" = INTEGER: .000 °C +SILA-MIB::silaTempSensorCritHigh."ambient" = INTEGER: 40.000 °C +SILA-MIB::silaTempSensorCritHigh."RTC_temp1" = INTEGER: 40.000 °C +SILA-MIB::silaTempSensorCritHigh."INlet_Temp1" = INTEGER: 40.000 °C +SILA-MIB::silaTempSensorCritHigh."INlet_Temp2" = INTEGER: 40.000 °C +SILA-MIB::silaTempSensorCritHigh."OUTlet_Temp1" = INTEGER: 40.000 °C +SILA-MIB::silaTempSensorCritHigh."OUTlet_Temp2" = INTEGER: 40.000 °C +SILA-MIB::silaTempSensorState."ambient" = INTEGER: normal(1) +SILA-MIB::silaTempSensorState."RTC_temp1" = INTEGER: normal(1) +SILA-MIB::silaTempSensorState."INlet_Temp1" = INTEGER: normal(1) +SILA-MIB::silaTempSensorState."INlet_Temp2" = INTEGER: normal(1) +SILA-MIB::silaTempSensorState."OUTlet_Temp1" = INTEGER: normal(1) +SILA-MIB::silaTempSensorState."OUTlet_Temp2" = INTEGER: normal(1) +... +``` + +Get current value of a specific sensor: +``` +$ snmpget -v2c -cpublic malevich.dev.sila.com SILA-MIB::silaTempSensorValue.\"OUTlet_Temp2\" + +SILA-MIB::silaTempSensorValue."OUTlet_Temp2" = INTEGER: 29.500 °C +``` + +### SNMPv3 support + +For manage SNMPv3 access in runtime required `snmpusm` and `snmpvacm` tools from `net-snmp` package. + +I did create the snmp user `root` with password `0penBmcAA` for control snmpd. +But by default him have access only from localhost. +For allowing access from other hosts, you should add/modify access rule in snmpd.conf: +```shell +... +com2sec readwrite private +... +``` + +New user can be created over 3 steps: +1. Create copy of existen user +```shell +$ snmpusm -v3 -uroot -a0penBmcAA -x0penBmcAA -lauthPriv create user01 +``` + +2. Change password for new user: +```shell +$ snmpusm -v3 -uroot -a0penBmcAA -x0penBmcAA -lauthPriv passwd 0penBmcAA NewPassword +``` + +3. Allow for new user read our data over snmp (add user to group MyRoGroup): +```shell +$ snmpvacm -v3 -uroot -a0penBmcAA -x0penBmc -lauthPriv createSec2Group 3 user01 MyRoGroup +``` + +All users present in `SNMP-USER-BASED-SM-MIB::usmUserTable`. + +### SNMP traps + +For receive SNMP traps you should add receivers to snmpd.conf +```shell +trap2sink +``` + +## snmpcfg + +This is a DBus service with interface `xyz.openbmc_project.SNMPCfg` +and object path `/xyz/openbmc_project/snmpcfg` for manage snmpd.conf content. + +There has three methods: +- GetConfig - return body of actual snmpd.conf +- SetConfig - replace body of snmpd.conf with specified data and restart snmpd. +- ResetConfig - restore default version of snmpd.conf and restart snmpd. + +This subproject will be moved to separate project in fututre. diff --git a/agent/Makefile.am b/agent/Makefile.am new file mode 100644 index 0000000..851c3a1 --- /dev/null +++ b/agent/Makefile.am @@ -0,0 +1,18 @@ +AM_CPPFLAGS = -iquote $(top_srcdir) + +bin_PROGRAMS = sila-snmp-agent + +sila_snmp_agent_SOURCES = \ + snmp.cpp \ + sila/powerstate.cpp \ + sila/sensors.cpp \ + sila/software.cpp \ + sila/inventory.cpp \ + main.cpp + +sila_snmp_agent_CXXFLAGS = $(SDBUSPLUS_CFLAGS) $(SDEVENTPLUS_CFLAGS) $(NETSNMP_CFLAGS) +sila_snmp_agent_LDADD = $(SDBUSPLUS_LIBS) $(SDEVENTPLUS_LIBS) $(NETSNMP_AGENT_LIBS) + +if HAVE_SYSTEMD +systemdsystemunit_DATA = sila-snmp-agent.service +endif diff --git a/agent/data/enums.hpp b/agent/data/enums.hpp new file mode 100644 index 0000000..bd6eb94 --- /dev/null +++ b/agent/data/enums.hpp @@ -0,0 +1,57 @@ +/** + * @brief DBus enums to base type converter. + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#pragma once + +#include +#include + +namespace phosphor +{ +namespace snmp +{ +namespace data +{ + +template struct DBusEnum +{ + std::string base; + std::map values; + T wrongValue; + + T get(const std::string& str) const + { + const auto len = base.length(); + if (0 == str.compare(0, len, base)) + { + const auto& it = values.find(str.substr(len + 1)); + if (it != values.end()) + { + return it->second; + } + } + + return wrongValue; + } +}; + +} // namespace data +} // namespace snmp +} // namespace phosphor diff --git a/agent/data/scalar.hpp b/agent/data/scalar.hpp new file mode 100644 index 0000000..2fc4a24 --- /dev/null +++ b/agent/data/scalar.hpp @@ -0,0 +1,150 @@ +/** + * @brief Export DBus property to scalar MIB value + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#pragma once + +#include "sdbusplus/helper.hpp" +#include + +namespace phosphor +{ +namespace snmp +{ +namespace data +{ +template class Scalar +{ + public: + using value_t = std::variant; + + /* Define all of the basic class operations: + * Not allowed: + * - Default constructor to avoid nullptrs. + * - Copy operations due to internal unique_ptr. + * Allowed: + * - Move operations. + * - Destructor. + */ + Scalar() = delete; + Scalar(const Scalar&) = delete; + Scalar& operator=(const Scalar&) = delete; + Scalar(Scalar&&) = default; + Scalar& operator=(Scalar&&) = default; + ~Scalar() = default; + + /** + * @brief Object constructor + */ + Scalar(const std::string& path, const std::string& iface, + const std::string& prop, const T& initValue) : + _value(initValue), + _path(path), _iface(iface), _prop(prop), + _onChangedMatch(sdbusplus::helper::helper::getBus(), + sdbusplus::bus::match::rules::propertiesChanged( + path.c_str(), iface.c_str()), + std::bind(&Scalar::onPropertyChanged, this, + std::placeholders::_1)) + { + } + + /** + * @brief Sent request to DBus object and store new value of property + */ + void update() + { + try + { + auto service = sdbusplus::helper::helper::getService(_path, _iface); + auto r = sdbusplus::helper::helper::callMethod( + service, _path, sdbusplus::helper::PROPERTIES_IFACE, "Get", + _iface, _prop); + + value_t var; + r.read(var); + setValue(var); + } + catch (const sdbusplus::exception::SdBusError&) + { + // Corresponding service is not started yet. + // When service is started, we'll receive data with + // `propertiesChanged` signal. + // So, this catch block is just for silencing the exception. + } + } + + const T& getValue() const + { + return _value; + } + + const std::string& getPath() const + { + return _path; + } + + const std::string& getInterface() const + { + return _iface; + } + + const std::string& getProperty() const + { + return _prop; + } + + protected: + /** + * @brief DBus signal `PropertiesChanged` handler + */ + void onPropertyChanged(sdbusplus::message::message& m) + { + std::string iface; + std::map data; + std::vector v; + + m.read(iface, data, v); + + if (data.find(_prop) != data.end()) + { + setValue(data[_prop]); + } + } + + /** + * @brief Setter for actual value + */ + virtual void setValue(value_t& var) + { + auto newValue = std::get(var); + std::swap(_value, newValue); + } + + private: + T _value; + std::string _path; + std::string _iface; + std::string _prop; + + sdbusplus::bus::match::match _onChangedMatch; +}; + +} // namespace data +} // namespace snmp +} // namespace phosphor diff --git a/agent/data/table.hpp b/agent/data/table.hpp new file mode 100644 index 0000000..ab891c4 --- /dev/null +++ b/agent/data/table.hpp @@ -0,0 +1,348 @@ +/** + * @brief Export DBus objects tree to MIB table + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#pragma once + +#include "sdbusplus/helper.hpp" +#include +#include +#include + +namespace phosphor +{ +namespace snmp +{ +namespace data +{ + +/** + * @brief MIB Table implementation. + */ +template class Table +{ + public: + using interfaces_t = std::vector; + + /* Define all of the basic class operations: + * Not allowed: + * - Default constructor to avoid nullptrs. + * - Copy operations due to internal unique_ptr. + * Allowed: + * - Move operations. + * - Destructor. + */ + Table() = delete; + Table(const Table&) = delete; + Table& operator=(const Table&) = delete; + Table(Table&&) = default; + Table& operator=(Table&&) = default; + ~Table() = default; + + /** + * @brief Object constructor + * + * @param folder - DBus folder where required objects exist. + * @param interfaces - List of required DBus properties interfaces + */ + Table(const std::string& folder, const interfaces_t interfaces = {}) : + _path(folder), _interfaces(interfaces) + { + _matches.emplace_back(sdbusplus::helper::helper::getBus(), + sdbusplus::bus::match::rules::interfacesAdded(), + std::bind(&Table::onInterfacesAdded, + this, std::placeholders::_1)); + _matches.emplace_back(sdbusplus::helper::helper::getBus(), + sdbusplus::bus::match::rules::interfacesRemoved(), + std::bind(&Table::onInterfacesRemoved, + this, std::placeholders::_1)); + } + + /** + * @brief Force update table items + */ + void update() + { + auto data = sdbusplus::helper::helper::getSubTree(_path, _interfaces); + + // Drop sensors if it not present in answer + auto path = _path + "/"; + for (auto it = _items.begin(); it != _items.end();) + { + if (data.find(path + (*it)->name) == data.end()) + { + it = dropItem(it); + } + else + { + ++it; + } + } + + // Update existing and create new items. + using fields_map_t = typename ItemType::fields_map_t; + for (const auto& pi : data) + { + for (const auto& bi : pi.second) + { + auto fields = + sdbusplus::helper::helper::callMethodAndRead( + bi.first, pi.first, sdbusplus::helper::PROPERTIES_IFACE, + "GetAll", ""); + getItem(pi.first).setFields(fields); + } + } + } + + /** + * @brief Register MIB handlers + * + * @param name - Name of table in MIB + * @param table_oid - OID of table + * @param table_oid_len - Table OID length + * @param min_column - Minimum columns number + * @param max_column - Maximum columns number + */ + void init_mib(const char* name, const oid* table_oid, size_t table_oid_len, + size_t min_column, size_t max_column) + { + netsnmp_handler_registration* reg = netsnmp_create_handler_registration( + name, Table::snmp_handler, table_oid, table_oid_len, + HANDLER_CAN_RONLY); + + netsnmp_table_registration_info* table_info = + SNMP_MALLOC_TYPEDEF(netsnmp_table_registration_info); + netsnmp_table_helper_add_indexes(table_info, ASN_OCTET_STR, 0); + table_info->min_column = min_column; + table_info->max_column = max_column; + + netsnmp_iterator_info* iinfo = + SNMP_MALLOC_TYPEDEF(netsnmp_iterator_info); + iinfo->get_first_data_point = Table::get_first_data_point; + iinfo->get_next_data_point = Table::get_next_data_point; + iinfo->table_reginfo = table_info; + iinfo->myvoid = this; + + netsnmp_register_table_iterator(reg, iinfo); + } + + protected: + using ItemPtr = std::unique_ptr; + using Items = std::vector; + + /** + * @brief DBus signal `InterfacesAdded` handler. + */ + void onInterfacesAdded(sdbusplus::message::message& m) + { + using Data = std::map; + + sdbusplus::message::object_path path; + Data data; + m.read(path, data); + + if (0 == path.str.compare(0, _path.length(), _path)) + { + // Skip unnecessary objects + bool isOwned = _interfaces.empty(); + if (!isOwned) + { + auto it = std::find_first_of( + _interfaces.begin(), _interfaces.end(), data.begin(), + data.end(), + [](const std::string& iface, + const typename Data::value_type& item) { + return iface == item.first; + }); + isOwned = (it != _interfaces.end()); + } + + if (isOwned) + { + auto& item = getItem(path.str); + + for (const auto& [iface, fields] : data) + { + item.setFields(fields); + } + } + } + } + + /** + * @brief DBus signal `InterfacesRemoved` handler. + */ + void onInterfacesRemoved(sdbusplus::message::message& m) + { + sdbusplus::message::object_path path; + std::vector data; + try + { + m.read(path, data); + } + catch (const sdbusplus::exception::SdBusError& e) + { + TRACE_ERROR("data/table: Failed to parse signal data. " + "ERROR='%s', REPLY_SIG='%s', PATH='%s', " + "IFACE='%s', MEMBER='%s', object path='%s'\n", + e.what(), m.get_signature(), m.get_path(), + m.get_interface(), m.get_member(), path.str.c_str()); + } + + if (0 == path.str.compare(0, _path.length(), _path)) + { + bool isOwned = _interfaces.empty(); + if (!isOwned) + { + auto it = + std::find_first_of(_interfaces.begin(), _interfaces.end(), + data.begin(), data.end()); + isOwned = (it != _interfaces.end()); + } + + if (isOwned) + { + dropItem(path.str); + } + } + } + + /** + * @brief Create new item if does not exist and return reference. + */ + ItemType& getItem(const std::string& path) + { + auto name = path.substr(_path.length() + 1); // Skip following '/' + auto it = std::lower_bound(_items.begin(), _items.end(), name); + if (it != _items.end() && (*it)->name == name) + { + return *(*it); + } + + it = _items.emplace(it, std::make_unique(_path, name)); + (*it)->onCreate(); + return *(*it); + } + + /** + * @brief Drop item specified by iterator. + * + * @param it - Items iterator + * + * @return Iterator pointing to the new location of the item that followed + * the item erased. + */ + typename Items::iterator dropItem(typename Items::iterator it) + { + if (it != _items.end()) + { + (*it)->onDestroy(); + it = _items.erase(it); + } + return it; + } + + /** + * @brief Drop item. + */ + void dropItem(const std::string& path) + { + auto name = path.substr(_path.length() + 1); // Skip following '/' + auto it = std::lower_bound(_items.begin(), _items.end(), name); + if (it != _items.end() && (*it)->name == name) + { + dropItem(it); + } + } + + /** + * @brief Iterator hook routines + */ + static netsnmp_variable_list* + get_first_data_point(void** loop_ctx, void** data_ctx, + netsnmp_variable_list* idx_data, + netsnmp_iterator_info* data) + { + *loop_ctx = reinterpret_cast(0); + return Table::get_next_data_point(loop_ctx, data_ctx, + idx_data, data); + } + static netsnmp_variable_list* + get_next_data_point(void** loop_ctx, void** data_ctx, + netsnmp_variable_list* idx_data, + netsnmp_iterator_info* data) + { + auto index = reinterpret_cast(*loop_ctx); + auto& table = *reinterpret_cast*>(data->myvoid); + + if (index < table._items.size()) + { + auto& item = table._items[index]; + + snmp_set_var_value(idx_data, item->name.c_str(), + item->name.length()); + + *data_ctx = item.get(); + *loop_ctx = reinterpret_cast(index + 1); + return idx_data; + } + + return nullptr; + } + + /** + * @brief handles snmp requests for the table data. + */ + static int snmp_handler(netsnmp_mib_handler* handler, + netsnmp_handler_registration* reginfo, + netsnmp_agent_request_info* reqinfo, + netsnmp_request_info* requests) + { + switch (reqinfo->mode) + { + case MODE_GET: + for (auto request = requests; request; request = request->next) + { + auto entry = reinterpret_cast( + netsnmp_extract_iterator_context(request)); + + if (!entry) + { + netsnmp_set_request_error(reqinfo, request, + SNMP_NOSUCHINSTANCE); + continue; + } + + entry->get_snmp_reply(reqinfo, request); + } + break; + } + + return SNMP_ERR_NOERROR; + } + + std::string _path; + interfaces_t _interfaces; + std::vector _matches; + Items _items; +}; + +} // namespace data +} // namespace snmp +} // namespace phosphor diff --git a/agent/data/table/item.hpp b/agent/data/table/item.hpp new file mode 100644 index 0000000..7c4c0eb --- /dev/null +++ b/agent/data/table/item.hpp @@ -0,0 +1,157 @@ +/** + * @brief MIB tables item definition. + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#pragma once + +#include "sdbusplus/helper.hpp" + +namespace phosphor +{ +namespace snmp +{ +namespace data +{ +namespace table +{ + +/** + * @brief MIB Table row implementation. + */ +template struct Item +{ + /* + * The `std::variant` allows to keep duplicates of types, + * but `std::get<>()` and `std::holds_alternative<>()` is ill-formed + * in this case. + * + * So we can't use here: + * using variant_t = std::variant; + * and we should specify all possible types. + */ + using variant_t = std::variant; + + using values_t = std::tuple; + using fields_map_t = std::map; + + /* Define all of the basic class operations: + * Not allowed: + * - Default constructor to avoid nullptrs. + * - Copy operations due to internal unique_ptr. + * Allowed: + * - Move operations. + * - Destructor. + */ + Item() = delete; + Item(const Item&) = delete; + Item& operator=(const Item&) = delete; + Item(Item&&) = default; + Item& operator=(Item&&) = default; + ~Item() = default; + + /** + * @brief Object constructor + * + * @param folder - Base folder for DBus object path + * @param name - DBus object path relative by folder + * @param args - Default values for each fields + */ + Item(const std::string& folder, const std::string& name, T&&... args) : + name(name), data(std::forward(args)...), + changedMatch(sdbusplus::helper::helper::getBus(), + sdbusplus::bus::match::rules::propertiesChanged( + folder + "/" + name), + std::bind(&Item::onPropertiesChanged, this, + std::placeholders::_1)) + { + } + + /** + * @brief PropertiesChanged signal handler + */ + virtual void onPropertiesChanged(sdbusplus::message::message& m) + { + std::string iface; + fields_map_t data; + std::vector v; + m.read(iface, data, v); + + setFields(data); + } + + /** + * @brief Store fields values recieved from DBus + */ + virtual void setFields(const fields_map_t& fields) = 0; + + /** + * @brief Called after object has been created. + */ + virtual void onCreate() + { + } + + /** + * @brief Called before object will be destroyed. + */ + virtual void onDestroy() + { + } + + /** + * @brief String fields vlaues helper + */ + template + void setField(const fields_map_t& fieldsMap, const char* propertyName) + { + using FieldType = typename std::tuple_element::type; + if (fieldsMap.find(propertyName) != fieldsMap.end() && + std::holds_alternative(fieldsMap.at(propertyName))) + { + std::get(data) = + std::get(fieldsMap.at(propertyName)); + } + } + + /** + * @brief Fill snmp reply with fields values + */ + virtual void get_snmp_reply(netsnmp_agent_request_info* reqinfo, + netsnmp_request_info* request) const = 0; + + std::string name; + values_t data; + + private: + sdbusplus::bus::match::match changedMatch; +}; + +/** + * @brief Used for std::lower_bound throw vector of smartpointers. + */ +template +inline bool operator<(const std::unique_ptr& o, const std::string& s) +{ + return o->name < s; +} + +} // namespace table +} // namespace data +} // namespace snmp +} // namespace phosphor diff --git a/agent/main.cpp b/agent/main.cpp new file mode 100644 index 0000000..033696e --- /dev/null +++ b/agent/main.cpp @@ -0,0 +1,180 @@ +/** + * @brief SNMP Agent entry point + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "config.h" +#include "tracing.hpp" +#include "sdbusplus/helper.hpp" +#include "snmp.hpp" + +#include +#include + +#include +#include +#include + +#include + +#include "sila/powerstate.hpp" +#include "sila/sensors.hpp" +#include "sila/software.hpp" +#include "sila/inventory.hpp" + +void print_usage() +{ + fprintf(stderr, "Usage: %s [OPTIONS]\n\n", PACKAGE_NAME); + fprintf(stderr, " Version: %s\n\nOPTIONS:\n", PACKAGE_VERSION); + fprintf(stderr, " -h,--help\t\tdisplay this help message\n"); + fprintf(stderr, " -d\t\t\tdump sent and received SNMP packets\n"); + fprintf( + stderr, + " -D[TOKEN[,...]]\tturn on debugging output for the given TOKEN(s)\n" + "\t\t\t (try ALL for extremely verbose output)\n"); + fprintf(stderr, + " -L \t\ttoggle options controlling where to log to\n"); + snmp_log_options_usage("\t\t\t ", stderr); + fflush(stderr); +} + +// parse_args error codes +enum +{ + EC_SHOW_USAGE = 1, + EC_ERROR = -1, + EC_SUCCESS = 0, +}; + +int parse_args(int argc, char** argv) +{ + constexpr auto Opts = "dD:L:h"; + + optind = 1; + int arg; + int rc = EC_SUCCESS; + + while (EC_SUCCESS == rc && (arg = getopt(argc, argv, Opts)) != EOF) + { + DEBUGMSGTL(("parse_args", "handling (#%d): %c\n", optind, arg)); + switch (arg) + { + case 'D': + debug_register_tokens(optarg); + snmp_set_do_debugging(1); + break; + + case 'd': + netsnmp_ds_set_boolean(NETSNMP_DS_LIBRARY_ID, + NETSNMP_DS_LIB_DUMP_PACKET, 1); + break; + + case 'L': + rc = snmp_log_options(optarg, argc, argv); + break; + + case 'h': + rc = EC_SHOW_USAGE; + break; + + case '?': + default: + rc = EC_ERROR; + break; + } + } + DEBUGMSGTL(("parse_args", "finished: %d/%d\n", optind, argc)); + return rc; +} + +static void clean_exit(sdeventplus::source::Signal& source, + const struct signalfd_siginfo*) +{ + TRACE_INFO("Signal %d received, terminating...\n", source.get_signal()); + source.get_event().exit(EXIT_SUCCESS); +} + +int main(int argc, char* argv[]) +{ + int rc = parse_args(argc, argv); + if (rc < EC_SUCCESS) + { + return EXIT_FAILURE; + } + else if (rc > EC_SUCCESS) + { + print_usage(); + return EXIT_SUCCESS; + } + + auto evt = sdeventplus::Event::get_default(); + sdbusplus::helper::helper::getBus().attach_event(evt.get(), + SD_EVENT_PRIORITY_NORMAL); + + sigset_t ss; + if (sigemptyset(&ss) < 0 || sigaddset(&ss, SIGTERM) < 0 || + sigaddset(&ss, SIGINT) < 0) + { + TRACE_ERROR("Failed to setup signal hanlders.\n"); + return EXIT_FAILURE; + } + + /* Block SIGTERM first, so than the event loop can handle it */ + if (sigprocmask(SIG_BLOCK, &ss, NULL) < 0) + { + TRACE_ERROR("Failed to block SIGTERM.\n"); + return EXIT_FAILURE; + } + + sdeventplus::source::Signal sigterm(evt, SIGTERM, clean_exit); + sdeventplus::source::Signal sigint(evt, SIGINT, clean_exit); + + snmpagent_init(evt); + + // Initialize DBus and MIB objects + + sila::host::power::state::init(); + sila::sensors::init(); + sila::software::init(); + sila::inventory::init(); + + // main loop + + TRACE_INFO("%s is up and running.\n", PACKAGE_STRING); + + rc = evt.loop(); + + TRACE_INFO("%s shuting down.\n", PACKAGE_STRING); + + // Release DBus and MIB objects resources + + sila::inventory::destroy(); + sila::software::destroy(); + sila::sensors::destroy(); + sila::host::power::state::destroy(); + + snmpagent_destroy(); + + if (rc < 0) + { + TRACE_ERROR("Event loop returned error %d, %s\n", rc, strerror(-rc)); + return -rc; + } + return EXIT_SUCCESS; +} diff --git a/agent/sila-snmp-agent.service.in b/agent/sila-snmp-agent.service.in new file mode 100644 index 0000000..9658558 --- /dev/null +++ b/agent/sila-snmp-agent.service.in @@ -0,0 +1,15 @@ +[Unit] +Description=Sila SNMP Sub Agent +PartOf=snmpd.service +After=snmpd.service +After=obmc-mapper.target + +[Service] +Restart=always +Environment=OPTIONS="-Ls0-6d" +EnvironmentFile=-@sysconfdir@/default/sila-snmp-agent +ExecStart=@bindir@/sila-snmp-agent $OPTIONS +SyslogIdentifier=sila-snmp + +[Install] +WantedBy=snmpd.service diff --git a/agent/sila/inventory.cpp b/agent/sila/inventory.cpp new file mode 100644 index 0000000..233b8f8 --- /dev/null +++ b/agent/sila/inventory.cpp @@ -0,0 +1,249 @@ +/** + * @brief SILA inventory table implementation. + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "tracing.hpp" +#include "data/table.hpp" +#include "data/table/item.hpp" +#include "sila/sila_oid.hpp" +#include "snmptrap.hpp" + +namespace sila +{ +namespace inventory +{ +using OID = phosphor::snmp::agent::OID; +static const OID NOTIFY_OID = SILA_OID(0, 7); + +struct InventoryItem : public phosphor::snmp::data::table::Item< + std::string, std::string, std::string, std::string, + std::string, std::string, std::string, bool, bool> +{ + // Indexes of fields in tuble + enum Fields + { + FIELD_INVENTORY_PRETTY_NAME = 0, + FIELD_INVENTORY_MANUFACTURER, + FIELD_INVENTORY_BUILD_DATE, + FIELD_INVENTORY_MODEL, + FIELD_INVENTORY_PART_NUMBER, + FIELD_INVENTORY_SERIAL_NUMBER, + FIELD_INVENTORY_VERSION, + FIELD_INVENTORY_PRESENT, + FIELD_INVENTORY_FUNCTIONAL, + }; + + enum Columns + { + COLUMN_SILAINVENTORY_PATH = 1, + COLUMN_SILAINVENTORY_NAME, + COLUMN_SILAINVENTORY_MANUFACTURER, + COLUMN_SILAINVENTORY_BUILD_DATE, + COLUMN_SILAINVENTORY_MODEL, + COLUMN_SILAINVENTORY_PART_NUMBER, + COLUMN_SILAINVENTORY_SERIAL_NUMBER, + COLUMN_SILAINVENTORY_VERSION, + COLUMN_SILAINVENTORY_PRESENT, + COLUMN_SILAINVENTORY_FUNCTIONAL, + }; + + InventoryItem(const std::string& folder, const std::string& name) : + phosphor::snmp::data::table::Item( + folder, name, + "", // Pretty Name + "", // Manufacturer + "", // Build date + "", // Model + "", // Part number + "", // Serial number + "", // Version + false, // Present + false) // Functional + { + phosphor::snmp::agent::make_oid( + _presentOid, ".1.3.6.1.4.1.49769.4.1.%lu.\"%s\"", + COLUMN_SILAINVENTORY_PRESENT, name.c_str()); + phosphor::snmp::agent::make_oid( + _functionalOid, ".1.3.6.1.4.1.49769.4.1.%lu.\"%s\"", + COLUMN_SILAINVENTORY_FUNCTIONAL, name.c_str()); + } + + void setFields(const fields_map_t& fields) override + { + bool isPresent = std::get(data); + bool isFunctional = std::get(data); + + setField(fields, "PrettyName"); + setField(fields, "Manufacturer"); + setField(fields, "BuildDate"); + setField(fields, "Model"); + setField(fields, "PartNumber"); + setField(fields, "SerialNumber"); + setField(fields, "Version"); + setField(fields, "Present"); + setField(fields, "Functional"); + + if (isPresent != std::get(data) || + isFunctional != std::get(data)) + { + DEBUGMSGTL(("sila:inventory", + "Inventory item '%s' at '%s': " + "present %d -> %d, function %d -> %d\n", + std::get(data).c_str(), + name.c_str(), isPresent, + std::get(data), isFunctional, + std::get(data))); + + phosphor::snmp::agent::Trap trap(NOTIFY_OID); + trap.add_field(_presentOid, + std::get(data)); + trap.add_field(_functionalOid, + std::get(data)); + trap.send(); + } + } + + void get_snmp_reply(netsnmp_agent_request_info* reqinfo, + netsnmp_request_info* request) const override + { + using namespace phosphor::snmp::agent; + + netsnmp_table_request_info* tinfo = netsnmp_extract_table_info(request); + + switch (tinfo->colnum) + { + case COLUMN_SILAINVENTORY_PATH: + VariableList::set(request->requestvb, name); + break; + + case COLUMN_SILAINVENTORY_NAME: + VariableList::set(request->requestvb, + std::get(data)); + break; + + case COLUMN_SILAINVENTORY_MANUFACTURER: + VariableList::set(request->requestvb, + std::get(data)); + break; + + case COLUMN_SILAINVENTORY_BUILD_DATE: + VariableList::set(request->requestvb, + std::get(data)); + break; + + case COLUMN_SILAINVENTORY_MODEL: + VariableList::set(request->requestvb, + std::get(data)); + break; + + case COLUMN_SILAINVENTORY_PART_NUMBER: + VariableList::set(request->requestvb, + std::get(data)); + break; + + case COLUMN_SILAINVENTORY_SERIAL_NUMBER: + VariableList::set( + request->requestvb, + std::get(data)); + break; + + case COLUMN_SILAINVENTORY_VERSION: + VariableList::set(request->requestvb, + std::get(data)); + break; + + case COLUMN_SILAINVENTORY_PRESENT: + VariableList::set(request->requestvb, + std::get(data)); + break; + + case COLUMN_SILAINVENTORY_FUNCTIONAL: + VariableList::set(request->requestvb, + std::get(data)); + break; + + default: + netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHOBJECT); + break; + } + } + + void onCreate() override + { + DEBUGMSGTL( + ("sila:inventory", "Inventory item '%s' added.\n", name.c_str())); + } + + void onDestroy() override + { + DEBUGMSGTL(("sila:inventory", "Inventory item '%s' removed.\n", + name.c_str())); + if (std::get(data) || + std::get(data)) + { + phosphor::snmp::agent::Trap trap(NOTIFY_OID); + trap.add_field(_presentOid, false); + trap.add_field(_functionalOid, false); + trap.send(); + } + } + + OID _presentOid; + OID _functionalOid; +}; + +static phosphor::snmp::agent::OID inventoryTableOid = SILA_OID(4); + +static phosphor::snmp::data::Table + inventoryTable("/xyz/openbmc_project/inventory", + { + "xyz.openbmc_project.Inventory.Item", + "xyz.openbmc_project.Inventory.Decorator.Asset", + "xyz.openbmc_project.Inventory.Decorator.Revision", + "xyz.openbmc_project.State.Decorator.OperationalStatus", + }); + +/** + * @brief Initialize inventory table + */ +void init() +{ + DEBUGMSGTL(("sila:init", "Initialize silaInventoryTable\n")); + + inventoryTable.update(); + inventoryTable.init_mib("silaInventoryTable", inventoryTableOid.data(), + inventoryTableOid.size(), + InventoryItem::COLUMN_SILAINVENTORY_PATH, + InventoryItem::COLUMN_SILAINVENTORY_FUNCTIONAL); +} + +/** + * @brief Deinitialize inventory table + */ +void destroy() +{ + DEBUGMSGTL(("sila:shutdown", "Deinitialize silaInventoryTable\n")); + unregister_mib(inventoryTableOid.data(), inventoryTableOid.size()); +} + +} // namespace inventory +} // namespace sila diff --git a/agent/sila/inventory.hpp b/agent/sila/inventory.hpp new file mode 100644 index 0000000..ad8a3cc --- /dev/null +++ b/agent/sila/inventory.hpp @@ -0,0 +1,32 @@ +/** + * @brief SILA inventory table implementation. + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#pragma once + +namespace sila +{ +namespace inventory +{ + +void init(); +void destroy(); + +} // namespace inventory +} // namespace sila diff --git a/agent/sila/powerstate.cpp b/agent/sila/powerstate.cpp new file mode 100644 index 0000000..d82ad23 --- /dev/null +++ b/agent/sila/powerstate.cpp @@ -0,0 +1,140 @@ +/** + * @brief SILA host power state implementation. + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "data/scalar.hpp" +#include "sila/sila_oid.hpp" +#include "tracing.hpp" + +#include +#include +#include + +#include "snmptrap.hpp" + +namespace sila +{ +namespace host +{ +namespace power +{ +namespace state +{ +using OID = phosphor::snmp::agent::OID; + +static const OID state_oid = SILA_OID(1, 1); +static const OID notify_oid = SILA_OID(0, 1); + +// Values specified in the MIB file. +constexpr int UNKNOWN = -1; +constexpr int OFF = 0; +constexpr int ON = 1; + +struct State : public phosphor::snmp::data::Scalar +{ + static constexpr auto IFACE = "xyz.openbmc_project.State.Host"; + static constexpr auto PATH = "/xyz/openbmc_project/state/host0"; + + State() : + phosphor::snmp::data::Scalar(PATH, IFACE, + "CurrentHostState", "") + { + } + + void setValue(value_t& var) + { + auto prev = getValue(); + phosphor::snmp::data::Scalar::setValue(var); + + auto curr = getValue(); + if (prev != curr) + { + DEBUGMSGTL(("sila:powerstate", + "Host power state changed: '%s' -> '%s'\n", + prev.c_str(), curr.c_str())); + + phosphor::snmp::agent::Trap trap(notify_oid); + trap.add_field(state_oid, toSNMPValue()); + trap.send(); + } + } + + int toSNMPValue() const + { + auto value = getValue(); + if (value == "xyz.openbmc_project.State.Host.HostState.Running") + { + return ON; + } + if (value == "xyz.openbmc_project.State.Host.HostState.Off") + { + return OFF; + } + + return UNKNOWN; + } +}; + +static State state; + +/** @brief Handler for snmp requests */ +static int State_snmp_handler(netsnmp_mib_handler* /*handler*/, + netsnmp_handler_registration* /*reginfo*/, + netsnmp_agent_request_info* reqinfo, + netsnmp_request_info* requests) +{ + DEBUGMSGTL(("sila:handle", + "Processing request (%d) for silaHostPowerState\n", + reqinfo->mode)); + + switch (reqinfo->mode) + { + case MODE_GET: + for (netsnmp_request_info* request = requests; request; + request = request->next) + { + phosphor::snmp::agent::VariableList::set(request->requestvb, + state.toSNMPValue()); + } + break; + } + + return SNMP_ERR_NOERROR; +} + +void init() +{ + DEBUGMSGTL(("sila:init", "Initialize silaHostPowerState\n")); + + state.update(); + + netsnmp_register_read_only_instance(netsnmp_create_handler_registration( + "silaHostPowerState", State_snmp_handler, state_oid.data(), + state_oid.size(), HANDLER_CAN_RONLY)); +} +void destroy() +{ + DEBUGMSGTL(("sila:shutdown", "destroy silaHostPowerState\n")); + unregister_mib(const_cast(state_oid.data()), state_oid.size()); +} + +} // namespace state +} // namespace power +} // namespace host +} // namespace sila diff --git a/agent/sila/powerstate.hpp b/agent/sila/powerstate.hpp new file mode 100644 index 0000000..937786d --- /dev/null +++ b/agent/sila/powerstate.hpp @@ -0,0 +1,38 @@ +/** + * @brief SILA host power state implementation. + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#pragma once + +namespace sila +{ +namespace host +{ +namespace power +{ +namespace state +{ + +void init(); +void destroy(); + +} // namespace state +} // namespace power +} // namespace host +} // namespace sila diff --git a/agent/sila/sensors.cpp b/agent/sila/sensors.cpp new file mode 100644 index 0000000..5cb8392 --- /dev/null +++ b/agent/sila/sensors.cpp @@ -0,0 +1,385 @@ +/** + * @brief SILA sensors tables implementation. + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "tracing.hpp" +#include "data/table.hpp" +#include "data/table/item.hpp" +#include "sila/sila_oid.hpp" +#include "snmptrap.hpp" + +#include + +namespace sila +{ +namespace sensors +{ +/** + * @brief Sensor implementation. + */ +struct Sensor + : public phosphor::snmp::data::table::Item +{ + /** + * @brief State codes like in MIB file. + */ + enum state_t + { + E_DISABLED = 0, + E_NORMAL, + E_WARNING_LOW, + E_WARNING_HIGH, + E_CRITICAL_LOW, + E_CRITICAL_HIGH, + }; + + // SNMP table columns + enum Columns + { + COLUMN_SILASENSOR_NAME = 1, + COLUMN_SILASENSOR_VALUE, + COLUMN_SILASENSOR_WARNLOW, + COLUMN_SILASENSOR_WARNHIGH, + COLUMN_SILASENSOR_CRITLOW, + COLUMN_SILASENSOR_CRITHIGH, + COLUMN_SILASENSOR_STATE, + }; + + // Indexes of fields in tuple + enum Fields + { + FIELD_SENSOR_VALUE = 0, + FIELD_SENSOR_WARNLOW, + FIELD_SENSOR_WARNLOW_ALARM, + FIELD_SENSOR_WARNHI, + FIELD_SENSOR_WARNHI_ALARM, + FIELD_SENSOR_CRITLOW, + FIELD_SENSOR_CRITLOW_ALARM, + FIELD_SENSOR_CRITHI, + FIELD_SENSOR_CRITHI_ALARM, + }; + + // Sensor types (first letter in sensors folder name) + enum Types + { + ST_TEMPERATURE = 't', // temperature + ST_VOLTAGE = 'v', // voltage + ST_TACHOMETER = 'f', // fan_tach + ST_CURRENT = 'c', // current + ST_POWER = 'p', // power + }; + + /** + * @brief Object contructor. + */ + Sensor(const std::string& folder, const std::string& name) : + phosphor::snmp::data::table::Item( + folder, name, + .0, // Value + .0, false, // WarningLow + .0, false, // WarningHigh + .0, false, // CriticalLow + .0, false) // CriticalHigh + { + auto n = folder.rfind('/'); + if (n != std::string::npos) + { + // Prepare for send traps + switch (folder[n + 1]) + { + case ST_TEMPERATURE: + _notifyOid.assign(SILA_OID(0, 2)); + break; + + case ST_VOLTAGE: + _notifyOid.assign(SILA_OID(0, 3)); + break; + + case ST_TACHOMETER: + _notifyOid.assign(SILA_OID(0, 4)); + break; + + case ST_CURRENT: + _notifyOid.assign(SILA_OID(0, 5)); + break; + + case ST_POWER: + _notifyOid.assign(SILA_OID(0, 6)); + break; + } + + // Correct scale power + // Required for TEXTUAL-CONVENTION in SILA-MIB.txt + switch (folder[n + 1]) + { + case ST_TACHOMETER: + _power = 0; + break; + } + } + + if (!_notifyOid.empty()) + { + phosphor::snmp::agent::make_oid( + _stateOid, ".1.3.6.1.4.1.49769.1.%lu.1.7.\"%s\"", + _notifyOid.back(), name.c_str()); + } + } + + /** + * @brief Update fields with new values recieved from DBus. + */ + void setFields(const fields_map_t& fields) override + { + auto prevValue = getValue(); + auto prevState = getState(); + + setField(fields, "Value"); + setField(fields, "WarningLow"); + setField(fields, "WarningAlarmLow"); + setField(fields, "WarningHigh"); + setField(fields, "WarningAlarmHigh"); + setField(fields, "CriticalLow"); + setField(fields, "CriticalAlarmLow"); + setField(fields, "CriticalHigh"); + setField(fields, "CriticalAlarmHigh"); + + auto lastState = getState(); + + if (prevValue != getValue() || + prevState != lastState) + { + DEBUGMSGTL(("sila:sensors", + "Sensor '%s' changed: %d -> %d, state: %d -> %d\n", + name.c_str(), prevValue, getValue(), + prevState, lastState)); + } + + if (prevState != lastState) + { + send_notify(lastState); + } + } + + /** + * @brief Called when sensor added to table. + */ + void onCreate() override + { + DEBUGMSGTL(("sila:sensors", "Sensor '%s' added, state=%d\n", + name.c_str(), getState())); + send_notify(E_NORMAL); + } + + /** + * @brief Called when sensor removed from table. + */ + void onDestroy() override + { + DEBUGMSGTL(("sila:sensors", "Sensor '%s' removed, state=%d\n", + name.c_str(), getState())); + send_notify(E_DISABLED); + } + + /** + * @brief Send snmptrap about changed state. + * + * @param state - state value for sent with trap. + */ + void send_notify(state_t state) + { + if (!_notifyOid.empty() && !_stateOid.empty()) + { + phosphor::snmp::agent::Trap trap(_notifyOid); + trap.add_field(_stateOid.data(), _stateOid.size(), state); + trap.send(); + } + else + { + TRACE_ERROR("Notify is unsupported for sensor '%s'\n", + name.c_str()); + } + } + + /** + * @brief Get current state. + */ + state_t getState() const + { + if (std::get(data)) + { + return E_CRITICAL_HIGH; + } + else if (std::get(data)) + { + return E_WARNING_HIGH; + } + else if (std::get(data)) + { + return E_WARNING_LOW; + } + else if (std::get(data)) + { + return E_CRITICAL_LOW; + } + + return E_NORMAL; + } + + /** + * @brief snmp request handler. + */ + void get_snmp_reply(netsnmp_agent_request_info* reqinfo, + netsnmp_request_info* request) const override + { + using namespace phosphor::snmp::agent; + + netsnmp_table_request_info* tinfo = netsnmp_extract_table_info(request); + + switch (tinfo->colnum) + { + case COLUMN_SILASENSOR_NAME: + VariableList::set(request->requestvb, name); + break; + + case COLUMN_SILASENSOR_VALUE: + VariableList::set(request->requestvb, + getValue()); + break; + + case COLUMN_SILASENSOR_WARNLOW: + VariableList::set(request->requestvb, + getValue()); + break; + + case COLUMN_SILASENSOR_WARNHIGH: + VariableList::set(request->requestvb, + getValue()); + break; + + case COLUMN_SILASENSOR_CRITLOW: + VariableList::set(request->requestvb, + getValue()); + break; + + case COLUMN_SILASENSOR_CRITHIGH: + VariableList::set(request->requestvb, + getValue()); + break; + + case COLUMN_SILASENSOR_STATE: + VariableList::set(request->requestvb, getState()); + break; + + default: + netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHOBJECT); + break; + } + } + + /** + * @brief Scale and round sensors value. + */ + template int getValue() const + { + return static_cast( + std::round(std::get(data) * powf(10.f, _power))); + } + + std::vector _notifyOid; + std::vector _stateOid; + int _power = 3; +}; + +struct SensorsTable : public phosphor::snmp::data::Table +{ + using OID = std::vector; + + SensorsTable(const std::string& folder, const std::string& tableName, + const OID& tableOID) : + phosphor::snmp::data::Table( + "/xyz/openbmc_project/sensors/" + folder, + { + "xyz.openbmc_project.Sensor.Value", + "xyz.openbmc_project.Sensor.Threshold.Warning", + "xyz.openbmc_project.Sensor.Threshold.Critical", + "xyz.openbmc_project.Sensor.Threshold.Fatal", + }), + tableName(tableName), tableOID(tableOID) + + { + } + + std::string tableName; + OID tableOID; +}; + +static std::array sensors = { + SensorsTable{"temperature", "silaTempSensorsTable", SILA_OID(1, 2)}, + SensorsTable{"voltage", "silaVoltSensorsTable", SILA_OID(1, 3)}, + SensorsTable{"fan_tach", "silaTachSensorsTable", SILA_OID(1, 4)}, + SensorsTable{"current", "silaCurrSensorsTable", SILA_OID(1, 5)}, + SensorsTable{"power", "silaPowerSensorsTable", SILA_OID(1, 6)}, +}; + +/** + * @brief Update all sensors. + */ +void update() +{ + for (auto& s : sensors) + { + s.update(); + } +} + +/** + * @brief Initialize sensors. + */ +void init() +{ + DEBUGMSGTL(("sila:init", "Initialize silaSensors\n")); + + for (auto& s : sensors) + { + s.init_mib(s.tableName.c_str(), s.tableOID.data(), s.tableOID.size(), + Sensor::COLUMN_SILASENSOR_NAME, + Sensor::COLUMN_SILASENSOR_STATE); + s.update(); + } +} + +/** + * @brief Deinitialize sensors. + */ +void destroy() +{ + DEBUGMSGTL(("sila:shutdown", "Destroy silaSensors\n")); + + for (auto& s : sensors) + { + unregister_mib(s.tableOID.data(), s.tableOID.size()); + } +} + +} // namespace sensors +} // namespace sila diff --git a/agent/sila/sensors.hpp b/agent/sila/sensors.hpp new file mode 100644 index 0000000..6ce123c --- /dev/null +++ b/agent/sila/sensors.hpp @@ -0,0 +1,33 @@ +/** + * @brief SILA sensors tables implementation. + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#pragma once + +namespace sila +{ +namespace sensors +{ + +void init(); +void update(); +void destroy(); + +} // namespace sensors +} // namespace sila diff --git a/agent/sila/sila_oid.hpp b/agent/sila/sila_oid.hpp new file mode 100644 index 0000000..033a7e7 --- /dev/null +++ b/agent/sila/sila_oid.hpp @@ -0,0 +1,27 @@ +/** + * @brief SILA OID helper + * + * This file is part of yadro-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#pragma once + +#define SILA_OID(args...) \ + { \ + 1, 3, 6, 1, 4, 1, 49769, ##args \ + } diff --git a/agent/sila/software.cpp b/agent/sila/software.cpp new file mode 100644 index 0000000..bed1130 --- /dev/null +++ b/agent/sila/software.cpp @@ -0,0 +1,225 @@ +/** + * @brief SILA software table implementation. + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include "tracing.hpp" +#include "data/table.hpp" +#include "data/table/item.hpp" +#include "data/enums.hpp" +#include "sila/sila_oid.hpp" +#include "snmpvars.hpp" + +#define INVALID_ENUM_VALUE 0xFF + +namespace sila +{ +namespace software +{ + +/** + * @brief DBus enum to byte converters. + */ +const phosphor::snmp::data::DBusEnum + purpose = {"xyz.openbmc_project.Software.Version.VersionPurpose", + { + {"Unknown", 0}, + {"Other", 1}, + {"System", 2}, + {"BMC", 3}, + {"Host", 4}, + }, + INVALID_ENUM_VALUE}, + + activation = {"xyz.openbmc_project.Software.Activation.Activations", + { + {"NotReady", 0}, + {"Invalid", 1}, + {"Ready", 2}, + {"Activating", 3}, + {"Active", 4}, + {"Failed", 5}, + }, + INVALID_ENUM_VALUE}; + +/** + * @brief Software item implementation. + */ +struct Software : public phosphor::snmp::data::table::Item +{ + // Indexes of fields in tuple + enum Fields + { + FIELD_SOFTWARE_VERSION = 0, + FIELD_SOFTWARE_PURPOSE, + FIELD_SOFTWARE_ACTIVATION, + FIELD_SOFTWARE_PRIORITY, + }; + + /** + * @brief Object constructor. + */ + Software(const std::string& folder, const std::string& name) : + phosphor::snmp::data::table::Item( + folder, name, + "", // Version + INVALID_ENUM_VALUE, // Purpose + INVALID_ENUM_VALUE, // Activation + INVALID_ENUM_VALUE) // Priority + { + } + + /** + * @brief Set field value with DBus enum converter. + * + * @tparam Idx - Index of field + * @param fields - DBus fields map + * @param field - Name of field in DBus + * @param enumcvt - Enum converter + */ + template + void setFieldEnum(const fields_map_t& fields, const char* field, + const phosphor::snmp::data::DBusEnum& enumcvt) + { + if (fields.find(field) != fields.end() && + std::holds_alternative(fields.at(field))) + { + std::get(data) = + enumcvt.get(std::get(fields.at(field))); + } + } + + /** + * @brief Update fields with new values recieved from DBus. + */ + void setFields(const fields_map_t& fields) override + { + uint8_t prevActivation = std::get(data), + prevPriority = std::get(data); + + setField(fields, "Version"); + setFieldEnum(fields, "Purpose", purpose); + setFieldEnum(fields, "Activation", + activation); + setField(fields, "Priority"); + + if (prevActivation != std::get(data) || + prevPriority != std::get(data)) + { + DEBUGMSGTL(("sila:software", + "Software '%s' version='%s', purpose=%d changed: " + "activation %d -> %d, priority %d -> %d\n", + name.c_str(), + std::get(data).c_str(), + std::get(data), prevActivation, + std::get(data), prevPriority, + std::get(data))); + } + } + + enum Columns + { + COLUMN_SILASOFTWARE_HASH = 1, + COLUMN_SILASOFTWARE_VERSION = 2, + COLUMN_SILASOFTWARE_PURPOSE = 3, + COLUMN_SILASOFTWARE_ACTIVATION = 4, + COLUMN_SILASOFTWARE_PRIORITY = 5, + }; + + /** + * @brief snmp request handler. + */ + void get_snmp_reply(netsnmp_agent_request_info* reqinfo, + netsnmp_request_info* request) const override + { + using namespace phosphor::snmp::agent; + + netsnmp_table_request_info* tinfo = netsnmp_extract_table_info(request); + + switch (tinfo->colnum) + { + case COLUMN_SILASOFTWARE_HASH: + VariableList::set(request->requestvb, name); + break; + + case COLUMN_SILASOFTWARE_VERSION: + VariableList::set(request->requestvb, + std::get(data)); + break; + + case COLUMN_SILASOFTWARE_PURPOSE: + VariableList::set(request->requestvb, + std::get(data)); + break; + + case COLUMN_SILASOFTWARE_ACTIVATION: + VariableList::set(request->requestvb, + std::get(data)); + break; + + case COLUMN_SILASOFTWARE_PRIORITY: + VariableList::set(request->requestvb, + std::get(data)); + break; + + default: + netsnmp_set_request_error(reqinfo, request, SNMP_NOSUCHOBJECT); + break; + } + } +}; + +constexpr oid softwareOid[] = SILA_OID(5); +constexpr auto SOFTWARE_FOLDER = "/xyz/openbmc_project/software"; + +static phosphor::snmp::data::Table + softwareTable("/xyz/openbmc_project/software", + { + "xyz.openbmc_project.Software.Version", + "xyz.openbmc_project.Software.Activation", + "xyz.openbmc_project.Software.RedundancyPriority", + }); + +/** + * @brief Initialize software table + */ +void init() +{ + DEBUGMSGTL(("sila:init", "Initialize silaSoftwareTable\n")); + + softwareTable.update(); + softwareTable.init_mib("silaSoftwareTable", softwareOid, + OID_LENGTH(softwareOid), + Software::COLUMN_SILASOFTWARE_HASH, + Software::COLUMN_SILASOFTWARE_PRIORITY); +} + +/** + * @brief Deinitialize software table + */ +void destroy() +{ + DEBUGMSGTL(("sila:shutdown", "Deinitialize silaSoftwareTable\n")); + unregister_mib(const_cast(softwareOid), OID_LENGTH(softwareOid)); +} + +} // namespace software +} // namespace sila diff --git a/agent/sila/software.hpp b/agent/sila/software.hpp new file mode 100644 index 0000000..3cd1d87 --- /dev/null +++ b/agent/sila/software.hpp @@ -0,0 +1,32 @@ +/** + * @brief SILA software tables implementation. + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#pragma once + +namespace sila +{ +namespace software +{ + +void init(); +void destroy(); + +} // namespace software +} // namespace sila diff --git a/agent/snmp.cpp b/agent/snmp.cpp new file mode 100644 index 0000000..b6a2881 --- /dev/null +++ b/agent/snmp.cpp @@ -0,0 +1,140 @@ +/** + * @brief SNMP Agent implementation. + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#include "config.h" +#include "tracing.hpp" + +#include +#include +#include +#include +#include + +#include +#include +#include + +#include + +constexpr auto clockId = sdeventplus::ClockId::Monotonic; +using Clock = sdeventplus::Clock; +using Time = sdeventplus::source::Time; + +// list of snmp file descriptors attached to sd_event loop. +static std::map snmp_fds; + +/** @brief Called when snmp file descriptors have data for reading. */ +static void sdevent_snmp_read(sdeventplus::source::IO& /*source*/, int fd, + uint32_t /*revents*/) +{ + DEBUGMSGTL(("snmpagent:handle", "Handle fd=%d\n", fd)); + fd_set fdset; + FD_ZERO(&fdset); + FD_SET(fd, &fdset); + snmp_read(&fdset); +} + +/** @brief Refresh list of snmp file descriptors. */ +static void sdevent_snmp_update(const sdeventplus::Event& event) +{ + netsnmp_check_outstanding_agent_requests(); + + int maxfd = 0; + int is_blocked = 1; + fd_set fdset; + timeval timeout; + + FD_ZERO(&fdset); + snmp_select_info(&maxfd, &fdset, &timeout, &is_blocked); + + static Time snmpTimer(event, {}, std::chrono::microseconds{1}, + [](Time&, Time::TimePoint) { + DEBUGMSGTL(("snmpagent:handle", "Time out\n")); + snmp_timeout(); + run_alarms(); + }); + + if (timeout.tv_sec || timeout.tv_usec) + { + snmpTimer.set_time(Clock(event).now() + + std::chrono::seconds{timeout.tv_sec} + + std::chrono::microseconds{timeout.tv_usec}); + snmpTimer.set_enabled(sdeventplus::source::Enabled::OneShot); + } + + // We need to untrack any event whose FD is not in `fdset` anymore. + for (auto it = snmp_fds.begin(); it != snmp_fds.end();) + { + if (it->first >= maxfd || (!FD_ISSET(it->first, &fdset))) + { + DEBUGMSGTL( + ("snmpagent:handle", "Remove fd=%d from set.\n", it->first)); + it->second.set_enabled(sdeventplus::source::Enabled::Off); + it = snmp_fds.erase(it); + } + else + { + FD_CLR(it->first, &fdset); + ++it; + } + } + + // Invariant: FD in `fdset` are not in `snmp_fds` + for (int fd = 0; fd < maxfd; ++fd) + { + if (FD_ISSET(fd, &fdset)) + { + DEBUGMSGTL(("snmpagent:handle", "Add fd=%d to set.\n", fd)); + snmp_fds.emplace(fd, sdeventplus::source::IO(event, fd, EPOLLIN, + sdevent_snmp_read)); + } + } +} + +/** @brief Initialize snmp agent */ +void snmpagent_init(const sdeventplus::Event& event) +{ + // make us a agentx client + netsnmp_ds_set_boolean(NETSNMP_DS_APPLICATION_ID, NETSNMP_DS_AGENT_ROLE, 1); + + // initialize tcpip, if necessary + SOCK_STARTUP; + + // initialize the agent library + init_agent(PACKAGE_NAME); + + // We will be used to read .conf files. + init_snmp(PACKAGE_NAME); + + // This is run before the event loop will sleep and wait for new events. + static sdeventplus::source::Post post( + event, [](sdeventplus::source::EventBase& source) { + sdevent_snmp_update(source.get_event()); + }); + + sdevent_snmp_update(event); +} + +/** @brief Deinitialize snmp agen */ +void snmpagent_destroy() +{ + snmp_shutdown(PACKAGE_NAME); + SOCK_CLEANUP; +} diff --git a/agent/snmp.hpp b/agent/snmp.hpp new file mode 100644 index 0000000..8332850 --- /dev/null +++ b/agent/snmp.hpp @@ -0,0 +1,26 @@ +/** + * @brief SNMP Agent implementation. + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#pragma once + +#include + +void snmpagent_init(const sdeventplus::Event& event); +void snmpagent_destroy(); diff --git a/agent/snmp_oid.hpp b/agent/snmp_oid.hpp new file mode 100644 index 0000000..5faf623 --- /dev/null +++ b/agent/snmp_oid.hpp @@ -0,0 +1,83 @@ +/** + * @brief SNMP OID manipulation helper. + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#pragma once + +#include +#include +#include + +namespace phosphor +{ +namespace snmp +{ +namespace agent +{ + +using OID = std::vector; + +/** + * @brief Parse OID present as string + * + * @param oid_value - Container for parsed oid + * @param fmt - Format string for oid presence + * @param ... - Additional args for oid string + */ +inline void make_oid(OID& oid_value, const char* fmt, ...) +{ + va_list ap; + std::vector oid_str; + + // Calculate required size of oid_strfer + va_start(ap, fmt); + int n = vsnprintf(nullptr, 0, fmt, ap); + va_end(ap); + + if (n > 0) + { + oid_str.resize(n + 1); // for terminating '\0' + + // Create string oid + va_start(ap, fmt); + n = vsnprintf(oid_str.data(), oid_str.size(), fmt, ap); + va_end(ap); + } + + if (n < 0) + { + oid_str.clear(); + } + + // Parse oid string + size_t oid_len = MAX_OID_LEN; + oid_value.resize(oid_len); + if (read_objid(oid_str.data(), oid_value.data(), &oid_len)) + { + oid_value.resize(oid_len); + } + else + { + oid_value.clear(); + } +} + +} // namespace agent +} // namespace snmp +} // namespace phosphor diff --git a/agent/snmptrap.hpp b/agent/snmptrap.hpp new file mode 100644 index 0000000..59ef74b --- /dev/null +++ b/agent/snmptrap.hpp @@ -0,0 +1,164 @@ +/** + * @brief SNMP Traps implementation + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#pragma once + +#include +#include +#include "snmp_oid.hpp" +#include "snmpvars.hpp" + +namespace phosphor +{ +namespace snmp +{ +namespace agent +{ + +namespace details +{ + +/** + * @brief unique_ptr functor to release an variable list reference. + */ +struct VariableListDeleter +{ + void operator()(netsnmp_variable_list* ptr) const + { + deleter(ptr); + } + + decltype(&snmp_free_varbind) deleter = snmp_free_varbind; +}; + +/** + * @brief Alias 'VariableList' to a unique_ptr type for auto-release. + */ +using VariableList = + std::unique_ptr; + +} // namespace details + +/* + * In the notification, we have to assign our notification OID to + * the snmpTrapOID.0 object. Here is it's defintion. + */ +constexpr std::array SNMPTRAP_OID = {1, 3, 6, 1, 6, 3, 1, 1, 4, 1, 0}; + +class Trap +{ + public: + /* Define all of the basic class operations: + * Not allowed: + * - Default constructor to avoid nullptrs. + * - Copy operations due to internal unique_ptr. + * Allowed: + * - Move operations. + * - Destructor. + */ + Trap() = delete; + Trap(const Trap&) = delete; + Trap& operator=(const Trap&) = delete; + Trap(Trap&&) = default; + Trap& operator=(Trap&&) = default; + ~Trap() = default; + + explicit Trap(const OID& trap_oid) + { + create_variable_list(trap_oid.data(), trap_oid.size()); + } + + Trap(const oid* trap_oid, size_t trap_oid_len) + { + create_variable_list(trap_oid, trap_oid_len); + } + + template void add_field(const OID& field_oid, T&& field_value) + { + add_field(field_oid.data(), field_oid.size(), + std::forward(field_value)); + } + + void add_field(const oid* field_oid, size_t field_oid_len, + const std::string field_value) + { + DEBUGMSGTL(("snmpagent:trap", " Field OID: ")); + DEBUGMSGOID(("snmpagent:trap", field_oid, field_oid_len)); + DEBUGMSG( + ("snmpagent:trap", ", Value: STRING(%s)\n", field_value.c_str())); + + VariableList::add(_vars.get(), field_oid, field_oid_len, field_value); + } + + void add_field(const oid* field_oid, size_t field_oid_len, bool field_value) + { + DEBUGMSGTL(("snmpagent:trap", " Field OID: ")); + DEBUGMSGOID(("snmpagent:trap", field_oid, field_oid_len)); + DEBUGMSG(("snmpagent:trap", ", Value: BOOLEAN(%s)\n", + field_value ? "True" : "False")); + + VariableList::add(_vars.get(), field_oid, field_oid_len, field_value); + } + + template + void add_field(const oid* field_oid, size_t field_oid_len, T&& field_value) + { + DEBUGMSGTL(("snmpagent:trap", " Field OID: ")); + DEBUGMSGOID(("snmpagent:trap", field_oid, field_oid_len)); + DEBUGMSG(("snmpagent:trap", ", Value: INTEGER(%d)\n", field_value)); + + VariableList::add(_vars.get(), field_oid, field_oid_len, + std::forward(field_value)); + } + + void send() const + { + DEBUGMSGTL(("snmpagent:trap", "send trap\n")); + send_v2trap(_vars.get()); + } + + protected: + void create_variable_list(const oid* trap_oid, size_t trap_oid_len) + { + DEBUGMSGTL(("snmpagent:trap", "Trap OID: ")); + DEBUGMSGOID(("snmpagent:trap", trap_oid, trap_oid_len)); + DEBUGMSG(("snmpagent:trap", "\n")); + + netsnmp_variable_list* vars = nullptr; + + // add in the trap definition object + snmp_varlist_add_variable(&vars, + /* the snmpTrapOID.0 variable */ + SNMPTRAP_OID.data(), SNMPTRAP_OID.size(), + /* value type is an OID */ + ASN_OBJECT_ID, + /* value contents is our notification OID */ + reinterpret_cast(trap_oid), + /* size of notification OID in bytes */ + trap_oid_len * sizeof(oid)); + _vars.reset(vars); + } + + details::VariableList _vars; +}; + +} // namespace agent +} // namespace snmp +} // namespace phosphor diff --git a/agent/snmpvars.hpp b/agent/snmpvars.hpp new file mode 100644 index 0000000..67cfa17 --- /dev/null +++ b/agent/snmpvars.hpp @@ -0,0 +1,121 @@ +/** + * @brief netsnmp_variable_list C++ wrapper. + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#pragma once + +namespace phosphor +{ +namespace snmp +{ +namespace agent +{ + +/** + * @brief SNMP representation of boolean type. + */ +enum class TruthValue : int +{ + True = 1, + False = 2, +}; + +#define SNMPBOOL(value) \ + static_cast(value ? TruthValue::True : TruthValue::False) + +struct VariableList +{ + /** + * @brief Fill snmp field with string value. + */ + static void set(netsnmp_variable_list* var, const std::string& value) + { + snmp_set_var_typed_value(var, ASN_OCTET_STR, value.c_str(), + value.length()); + } + + /** + * @brief Fill snmp field with boolean value. + */ + static void set(netsnmp_variable_list* var, bool value) + { + snmp_set_var_typed_integer(var, ASN_INTEGER, SNMPBOOL(value)); + } + + /** + * @brief Fill snmp field with integral value. + */ + template static void set(netsnmp_variable_list* var, T&& value) + { + snmp_set_var_typed_integer(var, ASN_INTEGER, value); + } + + /** + * @brief Add string as field into snmp variables list. + * + * @param vars - Pointer to snmp variables list + * @param field_oid - field OID + * @param field_oid_len - length of field OID + * @param field_value - field value + */ + static void add(netsnmp_variable_list* vars, const oid* field_oid, + size_t field_oid_len, const std::string& field_value) + { + snmp_varlist_add_variable( + &vars, field_oid, field_oid_len, ASN_OCTET_STR, + reinterpret_cast(field_value.data()), + field_value.length()); + } + + /** + * @brief Add boolean as field into snmp variables list. + * + * @param vars - Pointer to snmp variables list + * @param field_oid - field OID + * @param field_oid_len - length of field OID + * @param field_value - field value + */ + static void add(netsnmp_variable_list* vars, const oid* field_oid, + size_t field_oid_len, bool field_value) + { + auto v = SNMPBOOL(field_value); + snmp_varlist_add_variable(&vars, field_oid, field_oid_len, ASN_INTEGER, + &v, sizeof(v)); + } + /** + * @brief Add value of integral type as field into snmp variables list. + * + * @param vars - Pointer to snmp variables list + * @param field_oid - field OID + * @param field_oid_len - length of field OID + * @param field_value - field value + */ + template + static void add(netsnmp_variable_list* vars, const oid* field_oid, + size_t field_oid_len, T&& field_value) + { + snmp_varlist_add_variable(&vars, field_oid, field_oid_len, ASN_INTEGER, + reinterpret_cast(&field_value), + sizeof(field_value)); + } +}; + +} // namespace agent +} // namespace snmp +} // namespace phosphor diff --git a/agent/tracing.hpp b/agent/tracing.hpp new file mode 100644 index 0000000..cc9e1c8 --- /dev/null +++ b/agent/tracing.hpp @@ -0,0 +1,30 @@ +/** + * @brief TRACE_* macroses definitions. + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#pragma once + +#include +#include + +#define TRACE_ERROR(fmt, ...) snmp_log(LOG_ERR, fmt, ##__VA_ARGS__) +#define TRACE_WARNING(fmt, ...) snmp_log(LOG_WARNING, fmt, ##__VA_ARGS__) +#define TRACE_NOTICE(fmt, ...) snmp_log(LOG_NOTICE, fmt, ##__VA_ARGS__) +#define TRACE_INFO(fmt, ...) snmp_log(LOG_INFO, fmt, ##__VA_ARGS__) +#define TRACE_DEBUG(fmt, ...) snmp_log(LOG_DEBUG, fmt, ##__VA_ARGS__) diff --git a/bootstrap.sh b/bootstrap.sh new file mode 100755 index 0000000..5e9e7e5 --- /dev/null +++ b/bootstrap.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +PREFIX=$(readlink -f "${0%/*}") +AUTOCONF_FILES="aclocal.m4 autom4te.cache compile \ + config.guess config.sub configure depcomp \ + install-sh ltmain.sh Makefile.in missing \ + ar-lib config.h.in" + +TARGET=${PREFIX}/sila/.libs/sila.so + +case $1 in + clean) + cd ${PREFIX} + test -f Makefile && make maintainer-clean + for file in ${AUTOCONF_FILES}; do + find -name "$file" | xargs -r rm -rf + done + exit 0 + ;; +esac + +autoreconf --install ${PREFIX} +echo 'Run: "./configure ${CONFIGURE_FLAGS} && make"' diff --git a/configure.ac b/configure.ac new file mode 100644 index 0000000..c20c2e5 --- /dev/null +++ b/configure.ac @@ -0,0 +1,99 @@ +AC_PREREQ([2.69]) +AC_INIT([sila-snmp], + [m4_esyscmd_s([git describe --dirty --long --always])], + [https://github.com/SILA/obmc-sila-snmp]) +AC_LANG([C++]) +AM_INIT_AUTOMAKE([subdir-objects -Wall -Werror foreign]) +AM_SILENT_RULES([yes]) + +# Checks for programs +AC_PROG_CXX +AM_PROG_AR +AC_PROG_INSTALL +AC_PROG_MAKE_SET +AC_PROG_MKDIR_P + +# Checks for typedefs, structures and compiler characteristics. +AX_CXX_COMPILE_STDCXX_17([noext]) +AX_APPEND_COMPILE_FLAGS([-fpic -Wall -Werror], [CXXFLAGS]) + +# Checks for libraries. +PKG_CHECK_MODULES([SDBUSPLUS], [sdbusplus]) +AC_SUBST(SDBUSPLUS_CFLAGS) +AC_SUBST(SDBUSPLUS_LIBS) + +PKG_CHECK_MODULES([SDEVENTPLUS], [sdeventplus]) +AC_SUBST(SDEVENTPLUS_CLFAGS) +AC_SUBST(SDEVENTPLUS_LIBS) + +PKG_CHECK_MODULES([PHOSPHOR_LOGGING], [phosphor-logging]) +PKG_CHECK_MODULES([PHOSPHOR_DBUS_INTERFACES], [phosphor-dbus-interfaces]) + +# Check for sdbus++ +AC_PATH_PROG([SDBUSPLUSPLUS], [sdbus++]) +AS_IF([test "x$SDBUSPLUSPLUS" == "x"], + AC_MSG_ERROR(["Requires sdbus++"])) + +AC_CHECK_HEADERS([net-snmp/net-snmp-config.h],,\ + AC_MSG_ERROR(["Requires net-snmp headers"])) + +# Check for net-snmp-config +AC_PATH_PROG([NETSNMP_CONFIG], [net-snmp-config]) +AS_IF([test "x$NETSNMP_CONFIG" == "x"], + AC_MSG_ERROR(["Requires net-snmp-config"])) + +NETSNMP_CFLAGS=`$NETSNMP_CONFIG --base-cflags` +NETSNMP_AGENT_LIBS=`$NETSNMP_CONFIG --agent-libs` +AC_SUBST([NETSNMP_CFLAGS]) +AC_SUBST([NETSNMP_AGENT_LIBS]) + +# Checks for library functions +LT_INIT # Required for systemd linking + +PKG_PROG_PKG_CONFIG +AC_ARG_WITH([systemdsystemunitdir], + [AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [Directory for systemd service files])], + [], + [with_systemdsystemunitdir=auto] +) +AS_IF([test "x$with_systemdsystemunitdir" = "xyes" -o "x$with_systemdsystemunitdir" = "xauto"], + [def_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd) + AS_IF([test "x$def_systemdsystemunitdir" = "x"], + [AS_IF([test "x$with_systemdsystemunitdir" = "xyes"], + [AC_MSG_ERROR([systemd support requested but pkg-config unable to query systemd package])] + ) + with_systemdsystemunitdir=no], + [with_systemdsystemunitdir="$def_systemdsystemunitdir"] + )] +) +AS_IF([test "x$with_systemdsystemunitdir" != "xno"], + [AC_SUBST([systemdsystemunitdir], [$with_systemdsystemunitdir])] +) +AM_CONDITIONAL([HAVE_SYSTEMD], [test "x$with_systemdsystemunitdir" != "xno"]) + +AC_ARG_ENABLE([agent], + AS_HELP_STRING([--disable-agent], [Disable sila-snmp-agent.])) +AC_ARG_ENABLE([cfg-manager], + AS_HELP_STRING([--disable-cfg-manager], [Disable sila-snmp-cfg-manager.])) + +AM_CONDITIONAL([WANT_AGENT], [test "x$enable_agent" != "xno"]) +AM_CONDITIONAL([WANT_CFG_MANAGER], [test "x$enable_cfg_manager" != "xno"]) + +AC_ARG_VAR(SYSTEMD_TARGET, "Target for starting this service") +AS_IF([test "x$SYSTEMD_TARGET" = "x"], [SYSTEMD_TARGET="multi-user.target"]) + +# Package specific checks. +AS_IF([test "x$enable_agent" != "xno"], [ + AC_CONFIG_FILES([agent/Makefile]) + AC_CONFIG_FILES([agent/sila-snmp-agent.service]) +]) +AS_IF([test "x$enable_cfg_manager" != "xno"], [ + AC_CONFIG_FILES([snmpcfg/Makefile]) + AC_CONFIG_FILES([snmpcfg/sila-snmp-cfg-manager.service]) +]) + +# Create configured output +AC_CONFIG_HEADERS([config.h]) +AC_CONFIG_FILES([Makefile]) +AC_OUTPUT + diff --git a/mibs/SILA-MIB.txt b/mibs/SILA-MIB.txt new file mode 100644 index 0000000..8b77ae1 --- /dev/null +++ b/mibs/SILA-MIB.txt @@ -0,0 +1,947 @@ +SILA-MIB DEFINITIONS ::= BEGIN + +IMPORTS + MODULE-IDENTITY, OBJECT-TYPE, NOTIFICATION-TYPE, + Integer32, enterprises + FROM SNMPv2-SMI + TEXTUAL-CONVENTION, TruthValue + FROM SNMPv2-TC + MODULE-COMPLIANCE, OBJECT-GROUP, NOTIFICATION-GROUP + FROM SNMPv2-CONF +; + +sila MODULE-IDENTITY + LAST-UPDATED "202110010000Z" + ORGANIZATION "Sila" + CONTACT-INFO + "Primary Contact: SNMP support team + email: snmp@sila.ru" + DESCRIPTION + "Made table indexes read-only fields" + REVISION "202110010000Z" + DESCRIPTION + "Added SILA products OIDs" + REVISION "202106170000Z" + DESCRIPTION + "Added node for products manufactored by SILA LLC" + REVISION "201909260000Z" + DESCRIPTION + "Added inventory table" + REVISION "201806190000Z" + DESCRIPTION + "Added software items table" + REVISION "201805110000Z" + DESCRIPTION + "Added current and power sensor objects" + REVISION "201802080000Z" + DESCRIPTION + "This MIB module defines objects for OpenBMC sensors derived data." + REVISION "201712080000Z" + DESCRIPTION + "Add sensors state fields" + REVISION "201711150000Z" + DESCRIPTION + "Add format for sensors values" + REVISION "201711031000Z" + DESCRIPTION + "First draft" + ::= { enterprises 49769 } + +silaNotifications OBJECT IDENTIFIER ::= { sila 0 } +silaSensors OBJECT IDENTIFIER ::= { sila 1 } +silaConformance OBJECT IDENTIFIER ::= { sila 2 } +silaCompliances OBJECT IDENTIFIER ::= { silaConformance 1 } +silaGroups OBJECT IDENTIFIER ::= { silaConformance 2 } +silaProducts OBJECT IDENTIFIER ::= { sila 3 } +-- silaInventoryTable takes { sila 4 } +-- silaSoftwareTable takes { sila 5 } + +-- OIDs for SNMPv2-MIB::sysObjectID field +vesninBmc OBJECT IDENTIFIER ::= { silaProducts 1 } +vegmanBmc OBJECT IDENTIFIER ::= { silaProducts 20 } +tatlinArchiveSBmc OBJECT IDENTIFIER ::= { silaProducts 21 } + +-- + +silaHostPowerState OBJECT-TYPE + SYNTAX INTEGER { unknown(-1), off(0), on(1) } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Power state of the host" + DEFVAL { 0 } + ::= { silaSensors 1 } + +-- + +Degrees ::= TEXTUAL-CONVENTION + DISPLAY-HINT "d-3" + STATUS current + DESCRIPTION "Degrees Celsius" + SYNTAX Integer32 + +Voltage ::= TEXTUAL-CONVENTION + DISPLAY-HINT "d-3" + STATUS current + DESCRIPTION "Volt" + SYNTAX Integer32 + +RPMS ::= TEXTUAL-CONVENTION + DISPLAY-HINT "d-0" + STATUS current + DESCRIPTION "RPM" + SYNTAX Integer32 + +Current ::= TEXTUAL-CONVENTION + DISPLAY-HINT "d-3" + STATUS current + DESCRIPTION "Ampere" + SYNTAX Integer32 + +Power ::= TEXTUAL-CONVENTION + DISPLAY-HINT "d-3" + STATUS current + DESCRIPTION "Watt" + SYNTAX Integer32 + +SensorState ::= TEXTUAL-CONVENTION + STATUS current + DESCRIPTION "Actual state of sensor" + SYNTAX INTEGER { + disabled(0), + normal(1), + warningLow(2), + warningHigh(3), + criticalLow(4), + criticalHigh(5) + } + +-- + +silaTempSensorsTable OBJECT-TYPE + SYNTAX SEQUENCE OF SILATempSensorsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Table of temperature sensors and their values." + ::= { silaSensors 2 } + +silaTempSensorsEntry OBJECT-TYPE + SYNTAX SILATempSensorsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry containing a device and its statistics." + INDEX { silaTempSensorName } + ::= { silaTempSensorsTable 1 } + +SILATempSensorsEntry ::= SEQUENCE { + silaTempSensorName OCTET STRING, + silaTempSensorValue Degrees, + silaTempSensorWarnLow Degrees, + silaTempSensorWarnHigh Degrees, + silaTempSensorCritLow Degrees, + silaTempSensorCritHigh Degrees, + silaTempSensorState SensorState +} + +silaTempSensorName OBJECT-TYPE + SYNTAX OCTET STRING (SIZE(1..32)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The name of the temperature sensor." + ::= { silaTempSensorsEntry 1 } + +silaTempSensorValue OBJECT-TYPE + SYNTAX Degrees + UNITS "°C" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The current temperature from sensor." + ::= { silaTempSensorsEntry 2 } + +silaTempSensorWarnLow OBJECT-TYPE + SYNTAX Degrees + UNITS "°C" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The warning low value of sensor." + ::= { silaTempSensorsEntry 3 } + +silaTempSensorWarnHigh OBJECT-TYPE + SYNTAX Degrees + UNITS "°C" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The warning high value of sensor." + ::= { silaTempSensorsEntry 4 } + +silaTempSensorCritLow OBJECT-TYPE + SYNTAX Degrees + UNITS "°C" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The critical low value of sensor." + ::= { silaTempSensorsEntry 5 } + +silaTempSensorCritHigh OBJECT-TYPE + SYNTAX Degrees + UNITS "°C" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The critical high value of sensor." + ::= { silaTempSensorsEntry 6 } + +silaTempSensorState OBJECT-TYPE + SYNTAX SensorState + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Current state of sensor" + ::= { silaTempSensorsEntry 7 } + +-- + +silaVoltSensorsTable OBJECT-TYPE + SYNTAX SEQUENCE OF SILAVoltSensorsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Table of voltage sensors and their values." + ::= { silaSensors 3 } + +silaVoltSensorsEntry OBJECT-TYPE + SYNTAX SILAVoltSensorsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry containing a device and its statistics." + INDEX { silaVoltSensorName } + ::= { silaVoltSensorsTable 1 } + +SILAVoltSensorsEntry ::= SEQUENCE { + silaVoltSensorName OCTET STRING, + silaVoltSensorValue Voltage, + silaVoltSensorWarnLow Voltage, + silaVoltSensorWarnHigh Voltage, + silaVoltSensorCritLow Voltage, + silaVoltSensorCritHigh Voltage, + silaVoltSensorState SensorState +} + +silaVoltSensorName OBJECT-TYPE + SYNTAX OCTET STRING (SIZE(1..32)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The name of the voltage sensor." + ::= { silaVoltSensorsEntry 1 } + +silaVoltSensorValue OBJECT-TYPE + SYNTAX Voltage + UNITS "V" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The current voltage from sensor." + ::= { silaVoltSensorsEntry 2 } + +silaVoltSensorWarnLow OBJECT-TYPE + SYNTAX Voltage + UNITS "V" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The warning low value of sensor." + ::= { silaVoltSensorsEntry 3 } + +silaVoltSensorWarnHigh OBJECT-TYPE + SYNTAX Voltage + UNITS "V" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The warning high value of sensor." + ::= { silaVoltSensorsEntry 4 } + +silaVoltSensorCritLow OBJECT-TYPE + SYNTAX Voltage + UNITS "V" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The critical low value of sensor." + ::= { silaVoltSensorsEntry 5 } + +silaVoltSensorCritHigh OBJECT-TYPE + SYNTAX Voltage + UNITS "V" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The critical high value of sensor." + ::= { silaVoltSensorsEntry 6 } + +silaVoltSensorState OBJECT-TYPE + SYNTAX SensorState + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Current state of sensor" + ::= { silaVoltSensorsEntry 7 } + +-- + +silaTachSensorsTable OBJECT-TYPE + SYNTAX SEQUENCE OF SILATachSensorsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Table of tachometer sensors and their values." + ::= { silaSensors 4 } + +silaTachSensorsEntry OBJECT-TYPE + SYNTAX SILATachSensorsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry containing a device and its statistics." + INDEX { silaTachSensorName } + ::= { silaTachSensorsTable 1 } + +SILATachSensorsEntry ::= SEQUENCE { + silaTachSensorName OCTET STRING, + silaTachSensorValue RPMS, + silaTachSensorWarnLow RPMS, + silaTachSensorWarnHigh RPMS, + silaTachSensorCritLow RPMS, + silaTachSensorCritHigh RPMS, + silaTachSensorState SensorState +} + +silaTachSensorName OBJECT-TYPE + SYNTAX OCTET STRING (SIZE(1..32)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The name of the tachometer sensor." + ::= { silaTachSensorsEntry 1 } + +silaTachSensorValue OBJECT-TYPE + SYNTAX RPMS + UNITS "RPM" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The current tachometer from sensor." + ::= { silaTachSensorsEntry 2 } + +silaTachSensorWarnLow OBJECT-TYPE + SYNTAX RPMS + UNITS "RPM" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The warning low value of sensor." + ::= { silaTachSensorsEntry 3 } + +silaTachSensorWarnHigh OBJECT-TYPE + SYNTAX RPMS + UNITS "RPM" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The warning high value of sensor." + ::= { silaTachSensorsEntry 4 } + +silaTachSensorCritLow OBJECT-TYPE + SYNTAX RPMS + UNITS "RPM" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The critical low value of sensor." + ::= { silaTachSensorsEntry 5 } + +silaTachSensorCritHigh OBJECT-TYPE + SYNTAX RPMS + UNITS "RPM" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The critical high value of sensor." + ::= { silaTachSensorsEntry 6 } + +silaTachSensorState OBJECT-TYPE + SYNTAX SensorState + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Current state of sensor" + ::= { silaTachSensorsEntry 7 } + +-- + +silaCurrSensorsTable OBJECT-TYPE + SYNTAX SEQUENCE OF SILACurrSensorsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Table of current sensors and their values." + ::= { silaSensors 5 } + +silaCurrSensorsEntry OBJECT-TYPE + SYNTAX SILACurrSensorsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry containing a device and its statistics." + INDEX { silaCurrSensorName } + ::= { silaCurrSensorsTable 1 } + +SILACurrSensorsEntry ::= SEQUENCE { + silaCurrSensorName OCTET STRING, + silaCurrSensorValue Current, + silaCurrSensorWarnLow Current, + silaCurrSensorWarnHigh Current, + silaCurrSensorCritLow Current, + silaCurrSensorCritHigh Current, + silaCurrSensorState SensorState +} + +silaCurrSensorName OBJECT-TYPE + SYNTAX OCTET STRING (SIZE(1..32)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The name of the current sensor." + ::= { silaCurrSensorsEntry 1 } + +silaCurrSensorValue OBJECT-TYPE + SYNTAX Current + UNITS "A" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The current amperage from sensor." + ::= { silaCurrSensorsEntry 2 } + +silaCurrSensorWarnLow OBJECT-TYPE + SYNTAX Current + UNITS "A" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The warning low value of sensor." + ::= { silaCurrSensorsEntry 3 } + +silaCurrSensorWarnHigh OBJECT-TYPE + SYNTAX Current + UNITS "A" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The warning high value of sensor." + ::= { silaCurrSensorsEntry 4 } + +silaCurrSensorCritLow OBJECT-TYPE + SYNTAX Current + UNITS "A" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The critical low value of sensor." + ::= { silaCurrSensorsEntry 5 } + +silaCurrSensorCritHigh OBJECT-TYPE + SYNTAX Current + UNITS "A" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The critical high value of sensor." + ::= { silaCurrSensorsEntry 6 } + +silaCurrSensorState OBJECT-TYPE + SYNTAX SensorState + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Current state of sensor" + ::= { silaCurrSensorsEntry 7 } + +-- + +silaPowerSensorsTable OBJECT-TYPE + SYNTAX SEQUENCE OF SILAPowerSensorsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Table of power sensors and their values." + ::= { silaSensors 6 } + +silaPowerSensorsEntry OBJECT-TYPE + SYNTAX SILAPowerSensorsEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry containing a device and its statistics." + INDEX { silaPowerSensorName } + ::= { silaPowerSensorsTable 1 } + +SILAPowerSensorsEntry ::= SEQUENCE { + silaPowerSensorName OCTET STRING, + silaPowerSensorValue Power, + silaPowerSensorWarnLow Power, + silaPowerSensorWarnHigh Power, + silaPowerSensorCritLow Power, + silaPowerSensorCritHigh Power, + silaPowerSensorState SensorState +} + +silaPowerSensorName OBJECT-TYPE + SYNTAX OCTET STRING (SIZE(1..32)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The name of the power sensor." + ::= { silaPowerSensorsEntry 1 } + +silaPowerSensorValue OBJECT-TYPE + SYNTAX Power + UNITS "W" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The current amperage from sensor." + ::= { silaPowerSensorsEntry 2 } + +silaPowerSensorWarnLow OBJECT-TYPE + SYNTAX Power + UNITS "W" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The warning low value of sensor." + ::= { silaPowerSensorsEntry 3 } + +silaPowerSensorWarnHigh OBJECT-TYPE + SYNTAX Power + UNITS "W" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The warning high value of sensor." + ::= { silaPowerSensorsEntry 4 } + +silaPowerSensorCritLow OBJECT-TYPE + SYNTAX Power + UNITS "W" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The critical low value of sensor." + ::= { silaPowerSensorsEntry 5 } + +silaPowerSensorCritHigh OBJECT-TYPE + SYNTAX Power + UNITS "W" + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The critical high value of sensor." + ::= { silaPowerSensorsEntry 6 } + +silaPowerSensorState OBJECT-TYPE + SYNTAX SensorState + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Current state of sensor" + ::= { silaPowerSensorsEntry 7 } + +-- +silaSoftwareTable OBJECT-TYPE + SYNTAX SEQUENCE OF SILASoftwareEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Table of existing firmware images." + ::= { sila 5 } + +silaSoftwareEntry OBJECT-TYPE + SYNTAX SILASoftwareEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry containing a firmware images details." + INDEX { silaSoftwareHash } + ::= { silaSoftwareTable 1 } + +SILASoftwareEntry ::= SEQUENCE { + silaSoftwareHash OCTET STRING, + silaSoftwareVersion OCTET STRING, + silaSoftwarePurpose Integer32, + silaSoftwareActivation Integer32, + silaSoftwarePriority Integer32 +} + +silaSoftwareHash OBJECT-TYPE + SYNTAX OCTET STRING (SIZE(1..8)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The software hash represent as string." + ::= { silaSoftwareEntry 1 } + +silaSoftwareVersion OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The software version." + ::= { silaSoftwareEntry 2 } + +silaSoftwarePurpose OBJECT-TYPE + SYNTAX INTEGER { unknown(0), other(1), system(2), bmc(3), host(4) } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The software version purpose." + ::= { silaSoftwareEntry 3 } + +silaSoftwareActivation OBJECT-TYPE + SYNTAX INTEGER { notReady(0), invalid(1), ready(2), + activating(3), active(4), failed(5) } + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The current activation state of the software." + ::= { silaSoftwareEntry 4 } + +silaSoftwarePriority OBJECT-TYPE + SYNTAX Integer32 + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The piority, for redundancy purposes, of the associated software." + ::= { silaSoftwareEntry 5 } + +-- + +silaInventoryTable OBJECT-TYPE + SYNTAX SEQUENCE OF SILAInventoryEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "Table of inventory items." + ::= { sila 4 } + +silaInventoryEntry OBJECT-TYPE + SYNTAX SILAInventoryEntry + MAX-ACCESS not-accessible + STATUS current + DESCRIPTION + "An entry containing a inventory item details." + INDEX { silaInventoryPath } + ::= { silaInventoryTable 1 } + +SILAInventoryEntry ::= SEQUENCE { + silaInventoryPath OCTET STRING, + silaInventoryName OCTET STRING, + silaInventoryManufacturer OCTET STRING, + silaInventoryBuildDate OCTET STRING, + silaInventoryModel OCTET STRING, + silaInventoryPartNumber OCTET STRING, + silaInventorySerialNumber OCTET STRING, + silaInventoryVersion OCTET STRING, + silaInventoryPresent TruthValue, + silaInventoryFunctional TruthValue +} + +silaInventoryPath OBJECT-TYPE + SYNTAX OCTET STRING(SIZE(1..117)) + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The DBus path of the inventory item based on inventory folder." + ::= { silaInventoryEntry 1 } + +silaInventoryName OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The human readable name of the inventory item." + ::= { silaInventoryEntry 2 } + +silaInventoryManufacturer OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The inventory item manufacturer." + ::= { silaInventoryEntry 3 } + +silaInventoryBuildDate OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The date of inventory item manufacture in YYYYMMDD format." + ::= { silaInventoryEntry 4 } + +silaInventoryModel OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The model of the inventory item." + ::= { silaInventoryEntry 5 } + +silaInventoryPartNumber OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The inventory item part number, typically a stocking number." + ::= { silaInventoryEntry 6 } + +silaInventorySerialNumber OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The inventory item serial number." + ::= { silaInventoryEntry 7 } + +silaInventoryVersion OBJECT-TYPE + SYNTAX OCTET STRING + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "The version of the inventory item." + ::= { silaInventoryEntry 8 } + +silaInventoryPresent OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "Whether or not the inventory item is present." + ::= { silaInventoryEntry 9 } + +silaInventoryFunctional OBJECT-TYPE + SYNTAX TruthValue + MAX-ACCESS read-only + STATUS current + DESCRIPTION + "the inventory item is functional or not." + ::= { silaInventoryEntry 10 } + +-- + +silaHostPowerStateNotification NOTIFICATION-TYPE + OBJECTS { silaHostPowerState } + STATUS current + DESCRIPTION + "Notification about changed power state of host" + ::= { silaNotifications 1 } + +silaTachSensorStateNotification NOTIFICATION-TYPE + OBJECTS { silaTachSensorState } + STATUS current + DESCRIPTION + "Notification about changed state of tachometer sensor" + ::= { silaNotifications 4 } + +silaTempSensorStateNotification NOTIFICATION-TYPE + OBJECTS { silaTempSensorState } + STATUS current + DESCRIPTION + "Notification about changed state of temperature sensor" + ::= { silaNotifications 2 } + +silaVoltSensorStateNotification NOTIFICATION-TYPE + OBJECTS { silaVoltSensorState } + STATUS current + DESCRIPTION + "Notification about changed state of voltage sensor" + ::= { silaNotifications 3 } + +silaCurrSensorStateNotification NOTIFICATION-TYPE + OBJECTS { silaCurrSensorState } + STATUS current + DESCRIPTION + "Notification about changed state of current sensor" + ::= { silaNotifications 5 } + +silaPwrSensorStateNotification NOTIFICATION-TYPE + OBJECTS { silaPowerSensorState } + STATUS current + DESCRIPTION + "Notification about changed state of current sensor" + ::= { silaNotifications 6 } + +silaInventoryItemNotification NOTIFICATION-TYPE + OBJECTS { silaInventoryPresent, + silaInventoryFunctional } + STATUS current + DESCRIPTION + "Notification about changed presence or functional + of the inventory item." + ::= { silaNotifications 7 } + +-- + +silaCommpliance MODULE-COMPLIANCE + STATUS current + DESCRIPTION + "The compliance statement for sensors" + MODULE -- this module + MANDATORY-GROUPS { silaNotificationsGroup, + silaScalarFieldsGroup, + silaTempSensorsTableGroup, + silaVoltSensorsTableGroup, + silaTachSensorsTableGroup, + silaCurrSensorsTableGroup, + silaPowerSensorsTableGroup, + silaSoftwareTableGroup, + silaInventoryTableGroup + } + ::= { silaCompliances 1 } + +silaNotificationsGroup NOTIFICATION-GROUP + NOTIFICATIONS { silaHostPowerStateNotification, + silaTachSensorStateNotification, + silaTempSensorStateNotification, + silaVoltSensorStateNotification, + silaCurrSensorStateNotification, + silaPwrSensorStateNotification, + silaInventoryItemNotification + } + STATUS current + DESCRIPTION + "A collectopn of notifications" + ::= { silaGroups 10 } + +silaScalarFieldsGroup OBJECT-GROUP + OBJECTS { silaHostPowerState } + STATUS current + DESCRIPTION + "A collection of scalar fields" + ::= { silaGroups 1 } + +silaTempSensorsTableGroup OBJECT-GROUP + OBJECTS { silaTempSensorName, + silaTempSensorValue, + silaTempSensorWarnLow, + silaTempSensorWarnHigh, + silaTempSensorCritLow, + silaTempSensorCritHigh, + silaTempSensorState + } + STATUS current + DESCRIPTION + "A collection of objects providing information + about temperature sensors." + ::= { silaGroups 2 } + +silaVoltSensorsTableGroup OBJECT-GROUP + OBJECTS { silaVoltSensorName, + silaVoltSensorValue, + silaVoltSensorWarnLow, + silaVoltSensorWarnHigh, + silaVoltSensorCritLow, + silaVoltSensorCritHigh, + silaVoltSensorState + } + STATUS current + DESCRIPTION + "A collection of objects providing information + about voltage sensors." + ::= { silaGroups 3 } + +silaTachSensorsTableGroup OBJECT-GROUP + OBJECTS { silaTachSensorName, + silaTachSensorValue, + silaTachSensorWarnLow, + silaTachSensorWarnHigh, + silaTachSensorCritLow, + silaTachSensorCritHigh, + silaTachSensorState + } + STATUS current + DESCRIPTION + "A collection of objects providing information + about tachometer sensors." + ::= { silaGroups 4 } + +silaCurrSensorsTableGroup OBJECT-GROUP + OBJECTS { silaCurrSensorName, + silaCurrSensorValue, + silaCurrSensorWarnLow, + silaCurrSensorWarnHigh, + silaCurrSensorCritLow, + silaCurrSensorCritHigh, + silaCurrSensorState + } + STATUS current + DESCRIPTION + "A collection of objects providing information + about current sensors." + ::= { silaGroups 5 } + +silaPowerSensorsTableGroup OBJECT-GROUP + OBJECTS { silaPowerSensorName, + silaPowerSensorValue, + silaPowerSensorWarnLow, + silaPowerSensorWarnHigh, + silaPowerSensorCritLow, + silaPowerSensorCritHigh, + silaPowerSensorState + } + STATUS current + DESCRIPTION + "A collection of objects providing information + about power sensors." + ::= { silaGroups 6 } + +silaSoftwareTableGroup OBJECT-GROUP + OBJECTS { silaSoftwareHash, + silaSoftwareVersion, + silaSoftwarePurpose, + silaSoftwareActivation, + silaSoftwarePriority + } + STATUS current + DESCRIPTION + "A collection for objects providing information + about existing software." + ::= { silaGroups 7 } + +silaInventoryTableGroup OBJECT-GROUP + OBJECTS { silaInventoryPath, + silaInventoryName, + silaInventoryManufacturer, + silaInventoryBuildDate, + silaInventoryPartNumber, + silaInventorySerialNumber, + silaInventoryVersion, + silaInventoryModel, + silaInventoryPresent, + silaInventoryFunctional + } + STATUS current + DESCRIPTION + "A collection of objects providing information + about existing itventory items." + ::= { silaGroups 8 } + +END diff --git a/sdbusplus/helper.hpp b/sdbusplus/helper.hpp new file mode 100644 index 0000000..512eada --- /dev/null +++ b/sdbusplus/helper.hpp @@ -0,0 +1,168 @@ +/** + * @brief sdbusplus library helper + * + * This file is part of sila-snmp-agent project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +#pragma once + +#include +#include +#include +#include + +namespace sdbusplus +{ +namespace helper +{ + +constexpr auto OBJECT_MAPPER_IFACE = "xyz.openbmc_project.ObjectMapper"; +constexpr auto OBJECT_MAPPER_PATH = "/xyz/openbmc_project/object_mapper"; +constexpr auto PROPERTIES_IFACE = "org.freedesktop.DBus.Properties"; + +struct helper +{ + static auto& getBus() + { + static auto bus = sdbusplus::bus::new_system(); + return bus; + } + + /** @brief Invoke a method. */ + template + static auto callMethod(const std::string& busName, const std::string& path, + const std::string& interface, + const std::string& method, Args&&... args) + { + auto reqMsg = getBus().new_method_call( + busName.c_str(), path.c_str(), interface.c_str(), method.c_str()); + reqMsg.append(std::forward(args)...); + return getBus().call(reqMsg); + } + + /** @brief Invoke a method and read the response. */ + template + static Ret callMethodAndRead(const std::string& busName, + const std::string& path, + const std::string& interface, + const std::string& method, Args&&... args) + { + Ret resp; + try + { + sdbusplus::message::message respMsg = callMethod( + busName, path, interface, method, std::forward(args)...); + respMsg.read(resp); + } + catch (const sdbusplus::exception::SdBusError&) + { + } + return resp; + } + + using Path = std::string; + using Interface = std::string; + using Service = std::string; + using Interfaces = std::vector; + using Services = std::map; + using Objects = std::map; + + /** @brief Get subtree from the mapper. */ + static Objects getSubTree(const std::string& path, const Interfaces& ifaces, + int32_t depth = 0) + { + using namespace std::literals::string_literals; + + return callMethodAndRead( + OBJECT_MAPPER_IFACE, OBJECT_MAPPER_PATH, OBJECT_MAPPER_IFACE, + "GetSubTree", path, depth, ifaces); + } + + /** @brief Get subtree paths from mapper. */ + static Interfaces getSubTreePaths(const std::string& path, + const Interfaces& ifaces, + int32_t depth = 0) + { + return callMethodAndRead( + OBJECT_MAPPER_IFACE, OBJECT_MAPPER_PATH, OBJECT_MAPPER_IFACE, + "GetSubTreePaths", path, depth, ifaces); + } + + /** @brief Get service provides specified object */ + static Service getService(const Path& path, const Interface& iface) + { + Interfaces ifaces = {iface}; + auto services = callMethodAndRead( + OBJECT_MAPPER_IFACE, OBJECT_MAPPER_PATH, OBJECT_MAPPER_IFACE, + "GetObject", path, ifaces); + + if (!services.empty()) + { + return std::move(services.begin()->first); + } + + return {}; + } + + /** @brief Get a property. */ + template + static Property + getProperty(const std::string& busName, const std::string& path, + const std::string& interface, const std::string& property) + { + auto reqMsg = callMethod(busName, path, PROPERTIES_IFACE, "Get", + interface, property); + std::variant value; + reqMsg.read(value); + return value.template get(); + } + + /** @brief Get all properties. */ + template + static auto getAllProperties(const std::string& busName, + const std::string& path, + const std::string& interface) + { + using Value = std::variant; + using Values = std::map; + + auto reqMsg = + callMethod(busName, path, PROPERTIES_IFACE, "GetAll", interface); + + Values values; + reqMsg.read(values); + return values; + } +}; + +} // namespace helper + +namespace bus +{ +namespace match +{ +namespace rules +{ +inline auto propertiesChanged(const std::string& p) +{ + return type::signal() + path(p) + member("PropertiesChanged") + + interface(sdbusplus::helper::PROPERTIES_IFACE); +} +} // namespace rules +} // namespace match +} // namespace bus +} // namespace sdbusplus diff --git a/snmpcfg/Makefile.am b/snmpcfg/Makefile.am new file mode 100644 index 0000000..5741fd0 --- /dev/null +++ b/snmpcfg/Makefile.am @@ -0,0 +1,41 @@ +bin_PROGRAMS = sila-snmpcfg + +nobase_nodist_include_HEADERS = \ + xyz/openbmc_project/SNMPCfg/server.hpp + +sila_snmpcfg_SOURCES = \ + snmpcfg-server.cpp \ + xyz/openbmc_project/SNMPCfg/server.cpp + +sila_snmpcfg_CXXFLAGS = \ + $(SDBUSPLUS_CFLAGS) \ + $(PHOSPHOR_DBUS_INTERFACES_CFLAGS) \ + $(PHOSPHOR_LOGGING_CFLAGS) +sila_snmpcfg_LDADD = \ + $(SDBUSPLUS_LIBS) \ + $(PHOSPHOR_DBUS_INTERFACES_LIBS) \ + $(PHOSPHOR_LOGGING_LIBS) + +# Be sure to build needed files before compiling +BUILT_SOURCES = \ + xyz/openbmc_project/SNMPCfg/server.cpp \ + xyz/openbmc_project/SNMPCfg/server.hpp + +CLEANFILES=${BUILT_SOURCES} + +xyz/openbmc_project/SNMPCfg/server.cpp: \ +xyz/openbmc_project/SNMPCfg.interface.yaml \ +xyz/openbmc_project/SNMPCfg/server.hpp + @mkdir -p $(@D) + $(SDBUSPLUSPLUS) -r $(srcdir) interface server-cpp \ +xyz.openbmc_project.SNMPCfg > $@ + +xyz/openbmc_project/SNMPCfg/server.hpp: \ +xyz/openbmc_project/SNMPCfg.interface.yaml + @mkdir -p $(@D) + $(SDBUSPLUSPLUS) -r $(srcdir) interface server-header \ +xyz.openbmc_project.SNMPCfg > $@ + +if HAVE_SYSTEMD +systemdsystemunit_DATA = sila-snmp-cfg-manager.service +endif diff --git a/snmpcfg/sila-snmp-cfg-manager.service.in b/snmpcfg/sila-snmp-cfg-manager.service.in new file mode 100644 index 0000000..d80b9a6 --- /dev/null +++ b/snmpcfg/sila-snmp-cfg-manager.service.in @@ -0,0 +1,13 @@ +[Unit] +Description=Sila SNMP configuration manager +After=snmpd.service + +[Service] +ExecStart=@bindir@/sila-snmpcfg +SyslogIdentifier=sila-snmpcfg +Restart=always +Type=dbus +BusName=xyz.openbmc_project.SNMPCfg + +[Install] +WantedBy=@SYSTEMD_TARGET@ diff --git a/snmpcfg/snmpcfg-server.cpp b/snmpcfg/snmpcfg-server.cpp new file mode 100644 index 0000000..da76555 --- /dev/null +++ b/snmpcfg/snmpcfg-server.cpp @@ -0,0 +1,291 @@ +/** + * @brief SNMP Configuration manager + * + * This file is part of sila-snmp project. + * + * Copyright (c) 2022 SILA + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace phosphor::logging; +using SNMPCfg_inherit = sdbusplus::server::object_t< + sdbusplus::xyz::openbmc_project::server::SNMPCfg>; + +using InternalFailure = + sdbusplus::xyz::openbmc_project::Common::Error::InternalFailure; +using InvalidArgument = + sdbusplus::xyz::openbmc_project::Common::Error::InvalidArgument; +using Argument = xyz::openbmc_project::Common::InvalidArgument; + +static constexpr auto snmpdConf = "/etc/snmp/snmpd.conf"; +static constexpr auto whitespace = " \t"; + +/** + * @brief Check if the string contains the token at the position or after + * several spaces. + * + * @param[in] str - string to be checked + * @param[in] token - expected token + * @param[in/out] pos - start position for checking, will keep the last + * checked position. + * + * @return - true if token found. + */ +static bool getToken(const std::string& str, const std::string& token, + size_t& pos) +{ + pos = str.find_first_not_of(whitespace, pos); + if (pos != std::string::npos && 0 == str.compare(pos, token.size(), token)) + { + auto endPos = str.find_first_of(whitespace, pos); + if (endPos == std::string::npos || pos + token.size() == endPos) + { + pos = endPos; + return true; + } + } + return false; +} + +class Configurator : public SNMPCfg_inherit +{ + public: + /** + * @brief Configurator object constructor + * + * @param bus - DBus connection object reference + * @param path - DBus object path + */ + Configurator(sdbusplus::bus::bus& bus, const char* path) : + SNMPCfg_inherit(bus, path), bus(bus) + { + readConfig(); + } + + std::string community(std::string value) override + { + static constexpr auto communityMaxLen = 256; + static constexpr auto communityMinLen = 1; + + auto communityLen = value.length(); + if (communityLen < communityMinLen || communityLen > communityMaxLen) + { + log("Invalid community name length"); + elog(Argument::ARGUMENT_NAME("Community"), + Argument::ARGUMENT_VALUE(value.c_str())); + } + + SNMPCfg_inherit::community(value); + writeConfig(); + return value; + } + + private: + /** + * @brief Parse line and get community name + * + * @param line - the line from configuration file + * + * @return community name if corresponding statement found + * and nullopt otherwise + */ + std::optional getCommunityName(std::string line) const + { + // Required line should be in format: + // 'com2sec readonly default ' + size_t pos = 0; + + for (const auto& token : {"com2sec", "readonly", "default"}) + { + if (!getToken(line, token, pos) || pos == std::string::npos) + { + return std::nullopt; + } + } + + pos = line.find_first_not_of(whitespace, pos); + if (pos == std::string::npos) + { + return std::nullopt; + } + + auto endPos = line.find_last_not_of(whitespace); + if (pos < endPos && line[pos] == '"' && line[endPos] == '"') + { + pos++; + endPos--; + } + + return (pos <= endPos ? line.substr(pos, endPos - pos + 1) + : std::string{}); + } + + /** + * @brief Read actual settings from configuration file + */ + void readConfig() + { + std::ifstream fileToRead(snmpdConf, std::ios::in); + if (!fileToRead.is_open()) + { + log("Failed to open SNMP daemon configuration file", + entry("FILE_NAME=%s", snmpdConf)); + return; + } + + std::string line; + while (std::getline(fileToRead, line)) + { + auto communityName = getCommunityName(line); + if (communityName) + { + SNMPCfg_inherit::community(*communityName); + return; + } + } + log("Community not found", + entry("FILE_NAME=%s", snmpdConf)); + } + + /** + * @brief Write actual settings to the configuration file. + */ + void writeConfig() + { + std::string tmpFileName{snmpdConf}; + tmpFileName += ".tmp"; + + std::ifstream fileToRead(snmpdConf, std::ios::in); + std::ofstream fileToWrite(tmpFileName, std::ios::out); + if (!fileToRead.is_open()) + { + log("Failed to open SNMP daemon configuration file", + entry("FILE_NAME=%s", snmpdConf)); + return; + } + + if (!fileToWrite.is_open()) + { + log("Failed to create new configuration file", + entry("FILE_NAME=%s", tmpFileName.c_str())); + return; + } + + auto value = SNMPCfg_inherit::community(); + bool isQuoteRequired = + (value.find_first_of(whitespace) != std::string::npos); + bool updated = false; + std::string line; + while (std::getline(fileToRead, line)) + { + auto communityName = getCommunityName(line); + if (communityName && *communityName != value) + { + fileToWrite << "com2sec readonly default\t " + << (isQuoteRequired ? "\"" : "") << value + << (isQuoteRequired ? "\"" : "") << std::endl; + updated = true; + } + else + { + fileToWrite << line << std::endl; + } + } + fileToWrite.close(); + fileToRead.close(); + + if (updated) + { + if (0 != std::rename(tmpFileName.c_str(), snmpdConf)) + { + int error = errno; + log("Failed to update SNMP daemon configuarion", + entry("WHAT=%s", strerror(error)), + entry("FILE_NAME=%s", snmpdConf)); + elog(); + } + + reloadSnmpDaemon(); + } + else + { + if (0 != std::remove(tmpFileName.c_str())) + { + int error = errno; + log("Failed to remove temporary file", + entry("WHAT=%s", strerror(error)), + entry("FILE_NAME=%s", tmpFileName.c_str())); + } + } + } + + sdbusplus::bus::bus& bus; + + /** + * @brief Ask SNMP daemon to reread configuration. + */ + void reloadSnmpDaemon() + { + auto m = bus.new_method_call( + "org.freedesktop.systemd1", "/org/freedesktop/systemd1", + "org.freedesktop.systemd1.Manager", "ReloadUnit"); + m.append("snmpd.service", "replace"); + try + { + bus.call_noreply(m); + } + catch (const sdbusplus::exception::SdBusError& e) + { + log("Failed to reload SNMP daemon", + entry("WHATE=%s", e.what())); + elog(); + } + } +}; + +/** + * @brief Application entry point + * + * @return exit status + */ +int main() +{ + constexpr auto path = "/xyz/openbmc_project/snmpcfg"; + + auto b = sdbusplus::bus::new_default(); + sdbusplus::server::manager_t m{b, path}; + + b.request_name("xyz.openbmc_project.SNMPCfg"); + Configurator cfg{b, path}; + + while (1) + { + b.process_discard(); + b.wait(); + } + + return 0; +} diff --git a/snmpcfg/xyz/openbmc_project/SNMPCfg.interface.yaml b/snmpcfg/xyz/openbmc_project/SNMPCfg.interface.yaml new file mode 100644 index 0000000..5017f7b --- /dev/null +++ b/snmpcfg/xyz/openbmc_project/SNMPCfg.interface.yaml @@ -0,0 +1,13 @@ +description: > + SNMP daemon configuration manager. + +properties: + - name: Community + type: string + description: > + SNMPv1 and SNMPv2 community that allows read-only (GET and GETNEXT) access + to the data providing by SNMP daemon. + errors: + - xyz.openbmc_project.Common.Error.InternalFailure + - xyz.openbmc_project.Common.Error.InvalidArgument + diff --git a/tests/emit-added-MEMBUF1.sh b/tests/emit-added-MEMBUF1.sh new file mode 100755 index 0000000..e40690d --- /dev/null +++ b/tests/emit-added-MEMBUF1.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +gdbus emit --system --object-path '/xyz/openbmc_project/sensors' \ + --signal org.freedesktop.DBus.ObjectManager.InterfacesAdded \ + 'objectpath "/xyz/openbmc_project/sensors/temperature/MEMBUF1"' \ + "{ \ + 'org.freedesktop.DBus.Peer': @a{sv} {}, \ + 'org.freedesktop.DBus.Introspectable': @a{sv} {}, \ + 'org.freedesktop.DBus.Properties': @a{sv} {}, \ + 'org.freedesktop.DBus.ObjectManager': @a{sv} {}, \ + 'xyz.openbmc_project.Sensor.Threshold.Critical': { \ + 'CriticalHigh':, \ + 'CriticalLow':, \ + 'CriticalAlarmHigh':, \ + 'CriticalAlarmLow': \ + }, \ + 'xyz.openbmc_project.Sensor.Threshold.Warning': { \ + 'WarningHigh':, \ + 'WarningLow':, \ + 'WarningAlarmHigh':, \ + 'WarningAlarmLow': \ + }, \ + 'xyz.openbmc_project.Sensor.Value': { \ + 'Value':, \ + 'Unit':<'xyz.openbmc_project.Sensor.Value.Unit.DegreesC'>, \ + 'Scale': \ + } \ + }" + diff --git a/tests/emit-added-ambient.sh b/tests/emit-added-ambient.sh new file mode 100755 index 0000000..b0a023f --- /dev/null +++ b/tests/emit-added-ambient.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +gdbus emit --system --object-path '/xyz/openbmc_project/sensors' \ + --signal org.freedesktop.DBus.ObjectManager.InterfacesAdded \ + 'objectpath "/xyz/openbmc_project/sensors/temperature/ambient"' \ + "{ \ + 'org.freedesktop.DBus.Peer': @a{sv} {}, \ + 'org.freedesktop.DBus.Introspectable': @a{sv} {}, \ + 'org.freedesktop.DBus.Properties': @a{sv} {}, \ + 'org.freedesktop.DBus.ObjectManager': @a{sv} {}, \ + 'xyz.openbmc_project.Sensor.Threshold.Critical': { \ + 'CriticalHigh':, \ + 'CriticalLow':, \ + 'CriticalAlarmHigh':, \ + 'CriticalAlarmLow': \ + }, \ + 'xyz.openbmc_project.Sensor.Threshold.Warning': { \ + 'WarningHigh':, \ + 'WarningLow':, \ + 'WarningAlarmHigh':, \ + 'WarningAlarmLow': \ + }, \ + 'xyz.openbmc_project.Sensor.Value': { \ + 'Value':, \ + 'Unit':<'xyz.openbmc_project.Sensor.Value.Unit.DegreesC'>, \ + 'Scale': \ + } \ + }" + diff --git a/tests/emit-added-bmc.sh b/tests/emit-added-bmc.sh new file mode 100755 index 0000000..35b1d92 --- /dev/null +++ b/tests/emit-added-bmc.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +gdbus emit --system --object-path '/xyz/openbmc_project/software' \ + --signal org.freedesktop.DBus.ObjectManager.InterfacesAdded \ + 'objectpath "/xyz/openbmc_project/software/1f6027c9"' \ + "{ \ + 'org.freedesktop.DBus.Peer': @a{sv} {}, \ + 'org.freedesktop.DBus.Introspectable': @a{sv} {}, \ + 'org.freedesktop.DBus.Properties': @a{sv} {}, \ + 'xyz.openbmc_project.Common.FilePath': { \ + 'Path':<'/tmp/images/1f6027c9'> \ + }, \ + 'xyz.openbmc_project.Software.Version': { \ + 'Version':<'v2.2-441-gc04c198-dirty'>, \ + 'Purpose':<'xyz.openbmc_project.Software.Version.VersionPurpose.BMC'> \ + }, \ + 'xyz.openbmc_project.Software.Activation': { \ + 'Activation':<'xyz.openbmc_project.Software.Activation.Activations.Ready'>, \ + 'RequestedActivation':<'xyz.openbmc_project.Software.Activation.RequestedActivations.None'> \ + } + }" + + +# 'org.openbmc.Associations': { \ +# 'associations': \ +# } \ diff --git a/tests/emit-added-opfw.sh b/tests/emit-added-opfw.sh new file mode 100755 index 0000000..b2ef3dd --- /dev/null +++ b/tests/emit-added-opfw.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +gdbus emit --system --object-path '/xyz/openbmc_project/inventory' \ + --signal org.freedesktop.DBus.ObjectManager.InterfacesAdded \ + 'objectpath "/xyz/openbmc_project/inventory/system/chassis/motherboard/opfw"' \ + "{ \ + 'org.freedesktop.DBus.Peer': @a{sv} {}, \ + 'org.freedesktop.DBus.Introspectable': @a{sv} {}, \ + 'org.freedesktop.DBus.Properties': @a{sv} {}, \ + 'xyz.openbmc_project.Inventory.Opfw': { \ + 'BuildrootVersion':<'buildroot-2018.02.2-7-gcb36c6d'>, \ + 'SkibootVersion': <'skiboot-v6.0.1-27-g34e9c3c1edb3-p13e1584'>, \ + 'HostbootVersion': <'hostboot-p8-d3025f5-pe7d78e0'>, \ + 'LinuxVersion': <'occ-p8-28f2cec-pf0b771d'>, \ + 'PetitbootVersion':<'linux-4.16.13-openpower1-p908fb4b'>, \ + 'MachinexmlVersion':<'petitboot-1.8.0'> \ + }, \ + 'xyz.openbmc_project.Inventory.Item': { \ + 'PrettyName':<'OpenPOWER Firmware'>, \ + 'Present': \ + }, \ + 'xyz.openbmc_project.Inventory.Decorator.Revision': { \ + 'Version':<'open-power-vesnin-v2.0-45-g16f9312'> \ + } \ + }" + diff --git a/tests/emit-added-system-chassis.sh b/tests/emit-added-system-chassis.sh new file mode 100755 index 0000000..3f5407f --- /dev/null +++ b/tests/emit-added-system-chassis.sh @@ -0,0 +1,14 @@ +#!/bin/sh + +gdbus emit --system --object-path '/xyz/openbmc_project/inventory' \ + --signal org.freedesktop.DBus.ObjectManager.InterfacesAdded \ + 'objectpath "/xyz/openbmc_project/inventory/system/chassis"' \ + "{ \ + 'org.freedesktop.DBus.Peer': @a{sv} {}, \ + 'org.freedesktop.DBus.Introspectable': @a{sv} {}, \ + 'org.freedesktop.DBus.Properties': @a{sv} {}, \ + 'xyz.openbmc_project.Inventory.Decorator.CoolingType': { \ + 'AirCooled': , 'WaterCooled': \ + } \ + }" + diff --git a/tests/emit-change-ambient-warn.sh b/tests/emit-change-ambient-warn.sh new file mode 100755 index 0000000..0860fcf --- /dev/null +++ b/tests/emit-change-ambient-warn.sh @@ -0,0 +1,9 @@ +#!/bin/sh + +gdbus emit --system \ + --object-path '/xyz/openbmc_project/sensors/temperature/ambient' \ + --signal 'org.freedesktop.DBus.Properties.PropertiesChanged' \ + 'xyz.openbmc_project.Sensor.Threshold.Warning' \ + "{'WarningAlarmHigh':}" \ + '@as []' + diff --git a/tests/emit-change-ambient.sh b/tests/emit-change-ambient.sh new file mode 100755 index 0000000..b74fa46 --- /dev/null +++ b/tests/emit-change-ambient.sh @@ -0,0 +1,8 @@ +#!/bin/sh + +gdbus emit --system \ + --object-path '/xyz/openbmc_project/sensors/temperature/ambient' \ + --signal 'org.freedesktop.DBus.Properties.PropertiesChanged' \ + 'xyz.openbmc_project.Sensor.Value' "{'Value':}" \ + '@as []' + diff --git a/tests/emit-change-power-state.sh b/tests/emit-change-power-state.sh new file mode 100755 index 0000000..0d46cca --- /dev/null +++ b/tests/emit-change-power-state.sh @@ -0,0 +1,7 @@ +#!/bin/sh + +gdbus emit --system \ + --object-path '/org/openbmc/control/power0' \ + --signal 'org.freedesktop.DBus.Properties.PropertiesChanged' \ + 'org.openbmc.control.Power' "{'state':}" '@as []' + diff --git a/tests/emit-removed-ambient.sh b/tests/emit-removed-ambient.sh new file mode 100755 index 0000000..a01dd0e --- /dev/null +++ b/tests/emit-removed-ambient.sh @@ -0,0 +1,12 @@ +#!/bin/sh + +gdbus emit --system --object-path '/xyz/openbmc_project/sensors' \ + --signal org.freedesktop.DBus.ObjectManager.InterfacesRemoved \ + 'objectpath "/xyz/openbmc_project/sensors/temperature/ambient"' \ + "[ \ + 'org.freedesktop.DBus.Peer', \ + 'org.freedesktop.DBus.Introspectable', \ + 'org.freedesktop.DBus.Properties', \ + 'xyz.openbmc_project.Sensor.Value' \ + ]" + -- cgit v1.2.3