diff options
author | Chicago Duan <duanzhijia01@inspur.com> | 2020-11-26 09:12:12 +0300 |
---|---|---|
committer | Gunnar Mills <gmills@us.ibm.com> | 2023-06-21 23:25:33 +0300 |
commit | 3d30708fb65e40cd28da601b3e91a527a8862e15 (patch) | |
tree | 702197e96fcd5f160e1eeed6c14a827f85135621 | |
parent | 100afe56dc38deff4d9bbefb9b88b467c2a9ff95 (diff) | |
download | bmcweb-3d30708fb65e40cd28da601b3e91a527a8862e15.tar.xz |
Redfish: Implement SNMP Trap
Implement SNMPTrap in EventDestination of Redfish. We can use
this Redfish interface to add/get/delete the SNMPTrap port and
destination address. When the error
log is generated, phosphor-snmp
will send SNMPTrap messages to our configured SNMPTrap destination.
The MIB is here:
[1] https://github.com/openbmc/phosphor-snmp/blob/master/mibs/NotificationMIB.txt
Refer:
[1] https://www.dmtf.org/sites/default/files/standards/documents/DSP0268_2019.3.pdf
SNMPTrap test: Tested ok on the Witherspoon machine.
Steps are as follows:
1. Use this Redfish interface to configure the port and
destination address:
curl -k -H "X-Auth-Token: $token" -X POST
https://${bmc}/redfish/v1/EventService/Subscriptions
-d '{"Destination": "snmp://192.168.31.89:162",
"SubscriptionType": "SNMPTrap", "Protocol": "SNMPv2c"}'
2. Run the SNMPTrap receiver tool in the destination
computer(192.168.31.89),I used iReasoning MIB Browser as the
SNMPTrap receiving tool.
3. Trigger error logs such as power supply AC Lost. We will see
the error log under /xyz/openbmc_project/logging.
4. The SNMPTrap receiver tool in the destination computer received
the SNMPTrap sent by OpenBMC.
Tested: Validator passes
1. Add snmp client:
curl -k -H "X-Auth-Token: $token" -X POST
https://${bmc}/redfish/v1/EventService/Subscriptions
-d '{"Destination": "snmp://192.168.31.89:162",
"SubscriptionType": "SNMPTrap", "Protocol": "SNMPv2c",
"Context": "testContext"}'
{
"@Message.ExtendedInfo": [
{
"@odata.type": "#Message.v1_0_0.Message",
"Message": "The resource has been created successfully",
"MessageArgs": [],
"MessageId": "Base.1.8.1.Created",
"MessageSeverity": "OK",
"Resolution": "None"
}
]
}
2. Get snmp trap client configurations:
curl -k -H "X-Auth-Token: $token" -X GET
https://${bmc}/redfish/v1/EventService/Subscriptions/snmp1
{
"@odata.id": "/redfish/v1/EventService/Subscriptions/snmp1",
"@odata.type": "#EventDestination.v1_7_0.EventDestination",
"Context": "testContext",
"Destination": "snmp://192.168.31.89:162",
"EventFormatType": "Event",
"Id": "snmp1",
"Name": "Event Destination snmp1",
"Protocol": "SNMPv2c",
"SubscriptionType": "SNMPTrap"
}
Reboot the BMC, and get the snmp trap client again:
curl -k -H "X-Auth-Token: $token" -X GET
https://${bmc}/redfish/v1/EventService/Subscriptions/snmp1
{
"@odata.id": "/redfish/v1/EventService/Subscriptions/snmp1",
"@odata.type": "#EventDestination.v1_7_0.EventDestination",
"Context": "testContext",
"Destination": "snmp://192.168.31.89:162",
"EventFormatType": "Event",
"Id": "snmp1",
"Name": "Event Destination snmp1",
"Protocol": "SNMPv2c",
"SubscriptionType": "SNMPTrap"
}
3. Delete snmp client:
curl -k -H "X-Auth-Token: $token" -X DELETE
https://${bmc}/redfish/v1/EventService/Subscriptions/snmp1
{
"@Message.ExtendedInfo": [
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "Successfully Completed Request",
"MessageArgs": [],
"MessageId": "Base.1.8.1.Success",
"MessageSeverity": "OK",
"Resolution": "None"
}
]
}
4. After we have added some SNMP clients using Redfish, we can see them
in Dbus:
busctl tree xyz.openbmc_project.Network.SNMP
`-/xyz
`-/xyz/openbmc_project
`-/xyz/openbmc_project/network
`-/xyz/openbmc_project/network/snmp
`-/xyz/openbmc_project/network/snmp/manager
|-/xyz/openbmc_project/network/snmp/manager/1
busctl introspect xyz.openbmc_project.Network.SNMP
/xyz/openbmc_project/network/snmp/manager/1
xyz.openbmc_project.Network.Client
NAME TYPE SIGNATURE RESULT/VALUE FLAGS
.Address property s "192.168.31.89" emits-change writable
.Port property q 162 emits-change writable
5. Use "busctl call" add client
busctl call xyz.openbmc_project.Network.SNMP
/xyz/openbmc_project/network/snmp/manager
xyz.openbmc_project.Network.Client.Create
Client sq 192.168.31.90 162
s "/xyz/openbmc_project/network/snmp/manager/2"
We will see it use the redfish url:
curl -k -H "X-Auth-Token: $token" -X GET
https://${bmc}/redfish/v1/EventService/Subscriptions/snmp2
{
"@odata.id": "/redfish/v1/EventService/Subscriptions/snmp2",
"@odata.type": "#EventDestination.v1_7_0.EventDestination",
"Context": "",
"Destination": "snmp://192.168.31.90:162",
"EventFormatType": "Event",
"Id": "snmp2",
"Name": "Event Destination snmp2",
"Protocol": "SNMPv2c",
"SubscriptionType": "SNMPTrap"
}
6. Deleting snmp client using "busctl"
First, we use redfish to add some SNMP clients:
curl -k -H "X-Auth-Token: $token" -X POST
https://${bmc}/redfish/v1/EventService/Subscriptions
-d '{"Destination": "snmp://192.168.31.90:162",
"SubscriptionType": "SNMPTrap", "Protocol": "SNMPv2c",
"Context": "testContext0"}'
curl -k -H "X-Auth-Token: $token" -X POST
https://${bmc}/redfish/v1/EventService/Subscriptions
-d '{"Destination": "snmp://192.168.31.91:162",
"SubscriptionType": "SNMPTrap", "Protocol": "SNMPv2c",
"Context": "testContext1"}'
Then we can use redfish to get the subscriptions:
curl -k -H "X-Auth-Token: $token" -XGET
https://${bmc}/redfish/v1/EventService/Subscriptions
{
"@odata.id": "/redfish/v1/EventService/Subscriptions",
"@odata.type":"#EventDestinationCollection.EventDestinationCollection",
"Members": [
{
"@odata.id": "/redfish/v1/EventService/Subscriptions/snmp1"
},
{
"@odata.id": "/redfish/v1/EventService/Subscriptions/snmp2"
}
],
"Members@odata.count": 2,
"Name": "Event Destination Collections"
}
Now we use busctl to delete SNMP client 2:
busctl call xyz.openbmc_project.Network.SNMP
/xyz/openbmc_project/network/snmp/manager/2
xyz.openbmc_project.Object.Delete Delete
Then we won't see snmp2 in the subscriptions of redfish:
curl -k -H "X-Auth-Token: $token" -XGET
https://${bmc}/redfish/v1/EventService/Subscriptions
{
"@odata.id": "/redfish/v1/EventService/Subscriptions",
"@odata.type":"#EventDestinationCollection.EventDestinationCollection",
"Members": [
{
"@odata.id": "/redfish/v1/EventService/Subscriptions/snmp1"
}
],
"Members@odata.count": 1,
"Name": "Event Destination Collections"
}
7. Test the generic event subscription to make sure it didn't impacted
Add Redfish subscription:
curl -k -H "X-Auth-Token: $token" -X POST
https://${bmc}/redfish/v1/EventService/Subscriptions
-d '{"Destination": "https://192.168.31.189:443",
"SubscriptionType": "RedfishEvent", "Protocol": "Redfish",
"Context": "testContext"}'
{
"@Message.ExtendedInfo": [
{
"@odata.type": "#Message.v1_1_1.Message",
"Message": "The resource has been created successfully.",
"MessageArgs": [],
"MessageId": "Base.1.13.0.Created",
"MessageSeverity": "OK",
"Resolution": "None."
}
]
Get Redfish subscription:
curl -k -H "X-Auth-Token: $token" -X GET
https://${bmc}/redfish/v1/EventService/Subscriptions/1358109191
{
"@odata.id": "/redfish/v1/EventService/Subscriptions/1358109191",
"@odata.type": "#EventDestination.v1_8_0.EventDestination",
"Context": "testContext",
"DeliveryRetryPolicy": "TerminateAfterRetries",
"Destination": "https://192.168.31.189:443",
"EventFormatType": "Event",
"HttpHeaders": [],
"Id": "1358109191",
"MessageIds": [],
"MetricReportDefinitions": [],
"Name": "Event Destination 1358109191",
"Protocol": "Redfish",
"RegistryPrefixes": [],
"ResourceTypes": [],
"SubscriptionType": "RedfishEvent"
}
Signed-off-by: Chicago Duan <duanzhijia01@inspur.com>
Signed-off-by: Ed Tanous <edtanous@google.com>
Change-Id: Ie589b3934ee749c7e0add35e3ed1b0b7e817c557
-rw-r--r-- | http/utility.hpp | 8 | ||||
-rw-r--r-- | redfish-core/include/snmp_trap_event_clients.hpp | 232 | ||||
-rw-r--r-- | redfish-core/lib/event_service.hpp | 126 |
3 files changed, 363 insertions, 3 deletions
diff --git a/http/utility.hpp b/http/utility.hpp index aa64043132..3fbdd0c32f 100644 --- a/http/utility.hpp +++ b/http/utility.hpp @@ -551,6 +551,10 @@ inline std::string setProtocolDefaults(boost::urls::url_view urlView) } return ""; } + if (urlView.scheme() == "snmp") + { + return "snmp"; + } return ""; } @@ -573,6 +577,10 @@ inline uint16_t setPortDefaults(boost::urls::url_view url) { return 443; } + if (url.scheme() == "snmp") + { + return 162; + } return 0; } diff --git a/redfish-core/include/snmp_trap_event_clients.hpp b/redfish-core/include/snmp_trap_event_clients.hpp new file mode 100644 index 0000000000..08ff81b439 --- /dev/null +++ b/redfish-core/include/snmp_trap_event_clients.hpp @@ -0,0 +1,232 @@ +#pragma once + +#include "async_resp.hpp" +#include "dbus_singleton.hpp" +#include "dbus_utility.hpp" +#include "error_messages.hpp" +#include "event_service_manager.hpp" +#include "http_request.hpp" +#include "http_response.hpp" +#include "logging.hpp" +#include "utils/dbus_utils.hpp" + +#include <boost/system/error_code.hpp> +#include <boost/url/format.hpp> +#include <sdbusplus/unpack_properties.hpp> + +#include <memory> +#include <string> +#include <string_view> + +namespace redfish +{ + +inline void afterGetSnmpTrapClientdata( + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + const boost::system::error_code& ec, + const dbus::utility::DBusPropertiesMap& propertiesList) +{ + if (ec) + { + BMCWEB_LOG_ERROR << "D-Bus response error on GetSubTree " << ec; + messages::internalError(asyncResp->res); + return; + } + + std::string address; + uint16_t port = 0; + + bool success = sdbusplus::unpackPropertiesNoThrow( + dbus_utils::UnpackErrorPrinter(), propertiesList, "Address", address, + "Port", port); + + if (!success) + { + messages::internalError(asyncResp->res); + return; + } + + asyncResp->res.jsonValue["Destination"] = + boost::urls::format("snmp://{}:{}", address, port); +} + +inline void + getSnmpTrapClientdata(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + const std::string& id, const std::string& objectPath) +{ + asyncResp->res.jsonValue["@odata.type"] = + "#EventDestination.v1_8_0.EventDestination"; + asyncResp->res.jsonValue["Protocol"] = "SNMPv2c"; + asyncResp->res.jsonValue["@odata.id"] = + boost::urls::format("/redfish/v1/EventService/Subscriptions/{}", id); + + asyncResp->res.jsonValue["Id"] = id; + asyncResp->res.jsonValue["Name"] = "Event Destination"; + + asyncResp->res.jsonValue["SubscriptionType"] = "SNMPTrap"; + asyncResp->res.jsonValue["EventFormatType"] = "Event"; + + std::shared_ptr<Subscription> subValue = + EventServiceManager::getInstance().getSubscription(id); + if (subValue != nullptr) + { + asyncResp->res.jsonValue["Context"] = subValue->customText; + } + else + { + asyncResp->res.jsonValue["Context"] = ""; + } + + sdbusplus::asio::getAllProperties( + *crow::connections::systemBus, "xyz.openbmc_project.Network.SNMP", + objectPath, "xyz.openbmc_project.Network.Client", + [asyncResp](const boost::system::error_code& ec, + const dbus::utility::DBusPropertiesMap& properties) { + afterGetSnmpTrapClientdata(asyncResp, ec, properties); + }); +} + +inline void + getSnmpTrapClient(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + const std::string& id) +{ + crow::connections::systemBus->async_method_call( + [asyncResp, id](const boost::system::error_code& ec, + dbus::utility::ManagedObjectType& resp) { + if (ec) + { + BMCWEB_LOG_ERROR << "D-Bus response error on GetManagedObjects " + << ec; + messages::internalError(asyncResp->res); + return; + } + + for (const auto& objpath : resp) + { + sdbusplus::message::object_path path(objpath.first); + const std::string snmpId = path.filename(); + if (snmpId.empty()) + { + BMCWEB_LOG_ERROR << "The SNMP client ID is wrong"; + messages::internalError(asyncResp->res); + return; + } + const std::string subscriptionId = "snmp" + snmpId; + if (id != subscriptionId) + { + continue; + } + + getSnmpTrapClientdata(asyncResp, id, objpath.first); + return; + } + + messages::resourceNotFound(asyncResp->res, "Subscriptions", id); + EventServiceManager::getInstance().deleteSubscription(id); + }, + "xyz.openbmc_project.Network.SNMP", + "/xyz/openbmc_project/network/snmp/manager", + "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); +} + +inline void + afterSnmpClientCreate(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + const boost::system::error_code& ec, + const std::string& dbusSNMPid) +{ + if (ec) + { + if (ec.value() != EBADR) + { + // SNMP not installed + messages::propertyValueOutOfRange(asyncResp->res, "SNMPv2c", + "Protocol"); + return; + } + messages::internalError(asyncResp->res); + return; + } + + sdbusplus::message::object_path path(dbusSNMPid); + const std::string snmpId = path.filename(); + if (snmpId.empty()) + { + messages::internalError(asyncResp->res); + return; + } + + std::string subscriptionId = "snmp" + snmpId; + + boost::urls::url uri = boost::urls::format( + "/redfish/v1/EventService/Subscriptions/{}", subscriptionId); + asyncResp->res.addHeader("Location", uri.buffer()); + messages::created(asyncResp->res); +} + +inline void + addSnmpTrapClient(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + const std::string& host, uint16_t snmpTrapPort) +{ + crow::connections::systemBus->async_method_call( + [asyncResp](const boost::system::error_code& ec, + const std::string& dbusSNMPid) { + afterSnmpClientCreate(asyncResp, ec, dbusSNMPid); + }, + "xyz.openbmc_project.Network.SNMP", + "/xyz/openbmc_project/network/snmp/manager", + "xyz.openbmc_project.Network.Client.Create", "Client", host, + snmpTrapPort); +} + +inline void + getSnmpSubscriptionList(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + const std::string& snmpId, + nlohmann::json& memberArray) +{ + const std::string subscriptionId = "snmp" + snmpId; + + nlohmann::json::object_t member; + member["@odata.id"] = boost::urls::format( + "/redfish/v1/EventService/Subscriptions/{}", subscriptionId); + memberArray.push_back(std::move(member)); + + asyncResp->res.jsonValue["Members@odata.count"] = memberArray.size(); +} + +inline void + deleteSnmpTrapClient(const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + const std::string& param) +{ + std::string_view snmpTrapId = param; + + // Erase "snmp" in the request to find the corresponding + // dbus snmp client id. For example, the snmpid in the + // request is "snmp1", which will be "1" after being erased. + snmpTrapId.remove_prefix(4); + + sdbusplus::message::object_path snmpPath = + sdbusplus::message::object_path( + "/xyz/openbmc_project/network/snmp/manager") / + std::string(snmpTrapId); + + crow::connections::systemBus->async_method_call( + [asyncResp, param](const boost::system::error_code& ec) { + if (ec) + { + // The snmp trap id is incorrect + if (ec.value() == EBADR) + { + messages::resourceNotFound(asyncResp->res, "Subscription", + param); + return; + } + messages::internalError(asyncResp->res); + return; + } + messages::success(asyncResp->res); + }, + "xyz.openbmc_project.Network.SNMP", static_cast<std::string>(snmpPath), + "xyz.openbmc_project.Object.Delete", "Delete"); +} + +} // namespace redfish diff --git a/redfish-core/lib/event_service.hpp b/redfish-core/lib/event_service.hpp index 28a7ac6b6d..aef4d47278 100644 --- a/redfish-core/lib/event_service.hpp +++ b/redfish-core/lib/event_service.hpp @@ -20,10 +20,17 @@ #include "logging.hpp" #include "query.hpp" #include "registries/privilege_registry.hpp" +#include "snmp_trap_event_clients.hpp" #include <boost/beast/http/fields.hpp> +#include <boost/system/error_code.hpp> +#include <sdbusplus/unpack_properties.hpp> +#include <utils/dbus_utils.hpp> +#include <charconv> +#include <memory> #include <span> +#include <string> namespace redfish { @@ -183,6 +190,39 @@ inline void requestRoutesSubmitTestEvent(App& app) }); } +inline void doSubscriptionCollection( + const boost::system::error_code ec, + const std::shared_ptr<bmcweb::AsyncResp>& asyncResp, + const dbus::utility::ManagedObjectType& resp) +{ + if (ec) + { + if (ec.value() == EBADR) + { + // This is an optional process so just return if it isn't there + return; + } + + BMCWEB_LOG_ERROR << "D-Bus response error on GetManagedObjects " << ec; + messages::internalError(asyncResp->res); + return; + } + nlohmann::json& memberArray = asyncResp->res.jsonValue["Members"]; + for (const auto& objpath : resp) + { + sdbusplus::message::object_path path(objpath.first); + const std::string snmpId = path.filename(); + if (snmpId.empty()) + { + BMCWEB_LOG_ERROR << "The SNMP client ID is wrong"; + messages::internalError(asyncResp->res); + return; + } + + getSnmpSubscriptionList(asyncResp, snmpId, memberArray); + } +} + inline void requestRoutesEventDestinationCollection(App& app) { BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/") @@ -210,11 +250,20 @@ inline void requestRoutesEventDestinationCollection(App& app) for (const std::string& id : subscripIds) { nlohmann::json::object_t member; - member["@odata.id"] = "/redfish/v1/EventService/Subscriptions/" + - id; + member["@odata.id"] = boost::urls::format( + "/redfish/v1/EventService/Subscriptions/{}" + id); memberArray.emplace_back(std::move(member)); } + crow::connections::systemBus->async_method_call( + [asyncResp](const boost::system::error_code ec, + const dbus::utility::ManagedObjectType& resp) { + doSubscriptionCollection(ec, asyncResp, resp); + }, + "xyz.openbmc_project.Network.SNMP", + "/xyz/openbmc_project/network/snmp/manager", + "org.freedesktop.DBus.ObjectManager", "GetManagedObjects"); }); + BMCWEB_ROUTE(app, "/redfish/v1/EventService/Subscriptions/") .privileges(redfish::privileges::postEventDestinationCollection) .methods(boost::beast::http::verb::post)( @@ -287,10 +336,66 @@ inline void requestRoutesEventDestinationCollection(App& app) return; } + if (protocol == "SNMPv2c") + { + if (context) + { + messages::propertyValueConflict(asyncResp->res, "Context", + "Protocol"); + return; + } + if (eventFormatType2) + { + messages::propertyValueConflict(asyncResp->res, + "EventFormatType", "Protocol"); + return; + } + if (retryPolicy) + { + messages::propertyValueConflict(asyncResp->res, "RetryPolicy", + "Protocol"); + return; + } + if (msgIds) + { + messages::propertyValueConflict(asyncResp->res, "MessageIds", + "Protocol"); + return; + } + if (regPrefixes) + { + messages::propertyValueConflict(asyncResp->res, + "RegistryPrefixes", "Protocol"); + return; + } + if (resTypes) + { + messages::propertyValueConflict(asyncResp->res, "ResourceTypes", + "Protocol"); + return; + } + if (headers) + { + messages::propertyValueConflict(asyncResp->res, "HttpHeaders", + "Protocol"); + return; + } + if (mrdJsonArray) + { + messages::propertyValueConflict( + asyncResp->res, "MetricReportDefinitions", "Protocol"); + return; + } + + addSnmpTrapClient(asyncResp, host, port); + return; + } + if (path.empty()) { path = "/"; } + std::shared_ptr<Subscription> subValue = std::make_shared<Subscription>( host, port, path, urlProto, app.ioContext()); @@ -526,6 +631,13 @@ inline void requestRoutesEventDestination(App& app) { return; } + + if (param.starts_with("snmp")) + { + getSnmpTrapClient(asyncResp, param); + return; + } + std::shared_ptr<Subscription> subValue = EventServiceManager::getInstance().getSubscription(param); if (subValue == nullptr) @@ -536,7 +648,7 @@ inline void requestRoutesEventDestination(App& app) const std::string& id = param; asyncResp->res.jsonValue["@odata.type"] = - "#EventDestination.v1_7_0.EventDestination"; + "#EventDestination.v1_8_0.EventDestination"; asyncResp->res.jsonValue["Protocol"] = "Redfish"; asyncResp->res.jsonValue["@odata.id"] = "/redfish/v1/EventService/Subscriptions/" + id; @@ -653,6 +765,14 @@ inline void requestRoutesEventDestination(App& app) { return; } + + if (param.starts_with("snmp")) + { + deleteSnmpTrapClient(asyncResp, param); + EventServiceManager::getInstance().deleteSubscription(param); + return; + } + if (!EventServiceManager::getInstance().isSubscriptionExist(param)) { asyncResp->res.result(boost::beast::http::status::not_found); |