Add Redfish ThermalSubsystem schema in bmcweb

The ThermalSubsystem is a new resource in Redfish version 2020.4.
It is a root for fans and temperatures. Fans are a new schema.
Temperature sensors will be part of the new ThermalMetrics schema.

ThermalSubsystem can co-exist with the current Thermal resource.
You can also control compilation through flags.

ThermalSubsystem is an improvement on the existing Thermal schema
because
1. It includes the latest properties like LocationIndicatorActive
2. Fans and Temperatures were arrays in the old Thermal schema and
   this was cumbersome and could hit limits of JSON arrays
3. Large amount of static data mixed with sensor readings, which
   hurt performance
4. Inconsistent definitions of properties vs like Processor and
   Memory schemas

In a future commits Fans and ThermalMetrics will be added soon.

Reference:
https://www.dmtf.org/sites/default/files/standards/documents/DSP0268_2020.4.pdf
https://redfish.dmtf.org/schemas/v1/ThermalSubsystem.v1_0_0.json

Test:
1. Validator passed.
2. doGet method:
~$ curl -k -H "X-Auth-Token: $token" -X GET https://${bmc}/redfish/v1/Chassis/chassis/ThermalSubsystem
{
  "@odata.id": "/redfish/v1/Chassis/chassis/ThermalSubsystem",
  "@odata.type": "#ThermalSubsystem.v1_0_0.ThermalSubsystem",
  "Id": "chassis",
  "Name": "Thermal Subsystem for Chassis",
  "Status": {
    "Health": "OK",
    "State": "Enabled"
  }
}
3. A bad chassis ID:
~$ curl -k -H "X-Auth-Token: $token" -X GET https://${bmc}/redfish/v1/Chassis/chassisSSBAD/ThermalSubsystem
{
  "error": {
    "@Message.ExtendedInfo": [
      {
        "@odata.type": "#Message.v1_1_1.Message",
        "Message": "The requested resource of type Chassis named chassisSSBAD was not found.",
        "MessageArgs": [
          "Chassis",
          "chassisSSBAD"
        ],
        "MessageId": "Base.1.8.1.ResourceNotFound",
        "MessageSeverity": "Critical",
        "Resolution": "Provide a valid resource identifier and resubmit the request."
      }
    ],
    "code": "Base.1.8.1.ResourceNotFound",
    "message": "The requested resource of type Chassis named chassisSSBAD was not found."
  }
}

Signed-off-by: Xiaochao Ma <maxiaochao@inspur.com>
Change-Id: Ib19879f584304e5303f1a83d88bdd18c78a61633
Signed-off-by: Zhenwei Chen <zhenweichen0207@gmail.com>
diff --git a/Redfish.md b/Redfish.md
index e964c2c..0270208 100644
--- a/Redfish.md
+++ b/Redfish.md
@@ -223,6 +223,11 @@
 - MinNumNeeded
 - MaxNumSupported
 
+#### /redfish/v1/Chassis/{ChassisId}/ThermalSubsystem
+
+##### ThermalSubsystem
+- Status
+
 #### /redfish/v1/Chassis/{ChassisId}/Power/
 ##### Power
 PowerControl Voltages PowerSupplies Redundancy
diff --git a/meson.build b/meson.build
index ec70510..ad1f401 100644
--- a/meson.build
+++ b/meson.build
@@ -398,6 +398,7 @@
   'include/ut/openbmc_dbus_rest_test.cpp',
   'redfish-core/include/utils/query_param_test.cpp',
   'redfish-core/lib/ut/service_root_test.cpp',
+  'redfish-core/lib/ut/thermal_subsystem_test.cpp',
   'redfish-core/lib/chassis_test.cpp',
   'redfish-core/ut/configfile_test.cpp',
   'redfish-core/ut/hex_utils_test.cpp',
diff --git a/redfish-core/include/redfish.hpp b/redfish-core/include/redfish.hpp
index ab635f9..ed42b89 100644
--- a/redfish-core/include/redfish.hpp
+++ b/redfish-core/include/redfish.hpp
@@ -44,6 +44,7 @@
 #include "../lib/task.hpp"
 #include "../lib/telemetry_service.hpp"
 #include "../lib/thermal.hpp"
+#include "../lib/thermal_subsystem.hpp"
 #include "../lib/trigger.hpp"
 #include "../lib/update_service.hpp"
 #include "../lib/virtual_media.hpp"
@@ -76,6 +77,9 @@
         requestRoutesThermal(app);
         requestRoutesPower(app);
 #endif
+#ifdef BMCWEB_NEW_POWERSUBSYSTEM_THERMALSUBSYSTEM
+        requestRoutesThermalSubsystem(app);
+#endif
         requestRoutesManagerCollection(app);
         requestRoutesManager(app);
         requestRoutesManagerResetAction(app);
diff --git a/redfish-core/lib/chassis.hpp b/redfish-core/lib/chassis.hpp
index 7982281..f1de38c 100644
--- a/redfish-core/lib/chassis.hpp
+++ b/redfish-core/lib/chassis.hpp
@@ -384,6 +384,11 @@
                 asyncResp->res.jsonValue["Power"]["@odata.id"] =
                     "/redfish/v1/Chassis/" + chassisId + "/Power";
 #endif
+#ifdef BMCWEB_NEW_POWERSUBSYSTEM_THERMALSUBSYSTEM
+                asyncResp->res.jsonValue["ThermalSubsystem"]["@odata.id"] =
+                    crow::utility::urlFromPieces("redfish", "v1", "Chassis",
+                                                 chassisId, "ThermalSubsystem");
+#endif
                 // SensorCollection
                 asyncResp->res.jsonValue["Sensors"]["@odata.id"] =
                     "/redfish/v1/Chassis/" + chassisId + "/Sensors";
diff --git a/redfish-core/lib/thermal_subsystem.hpp b/redfish-core/lib/thermal_subsystem.hpp
new file mode 100644
index 0000000..0d5b88b
--- /dev/null
+++ b/redfish-core/lib/thermal_subsystem.hpp
@@ -0,0 +1,59 @@
+#pragma once
+
+#include "app.hpp"
+#include "query.hpp"
+#include "registries/privilege_registry.hpp"
+#include "utils/chassis_utils.hpp"
+#include "utils/json_utils.hpp"
+
+namespace redfish
+{
+
+inline void doThermalSubsystemCollection(
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& chassisId,
+    const std::optional<std::string>& validChassisPath)
+{
+    if (!validChassisPath)
+    {
+        BMCWEB_LOG_ERROR << "Not a valid chassis ID" << chassisId;
+        messages::resourceNotFound(asyncResp->res, "Chassis", chassisId);
+        return;
+    }
+    asyncResp->res.jsonValue["@odata.type"] =
+        "#ThermalSubsystem.v1_0_0.ThermalSubsystem";
+    asyncResp->res.jsonValue["Name"] = "Thermal Subsystem";
+    asyncResp->res.jsonValue["Id"] = "ThermalSubsystem";
+
+    asyncResp->res.jsonValue["@odata.id"] = crow::utility::urlFromPieces(
+        "redfish", "v1", "Chassis", chassisId, "ThermalSubsystem");
+
+    asyncResp->res.jsonValue["Status"]["State"] = "Enabled";
+    asyncResp->res.jsonValue["Status"]["Health"] = "OK";
+}
+
+inline void handleThermalSubsystemCollectionGet(
+    App& app, const crow::Request& req,
+    const std::shared_ptr<bmcweb::AsyncResp>& asyncResp,
+    const std::string& param)
+{
+    if (!redfish::setUpRedfishRoute(app, req, asyncResp))
+    {
+        return;
+    }
+    const std::string& chassisId = param;
+
+    redfish::chassis_utils::getValidChassisPath(
+        asyncResp, chassisId,
+        std::bind_front(doThermalSubsystemCollection, asyncResp, chassisId));
+}
+
+inline void requestRoutesThermalSubsystem(App& app)
+{
+    BMCWEB_ROUTE(app, "/redfish/v1/Chassis/<str>/ThermalSubsystem/")
+        .privileges(redfish::privileges::getThermalSubsystem)
+        .methods(boost::beast::http::verb::get)(std::bind_front(
+            handleThermalSubsystemCollectionGet, std::ref(app)));
+}
+
+} // namespace redfish
diff --git a/redfish-core/lib/ut/thermal_subsystem_test.cpp b/redfish-core/lib/ut/thermal_subsystem_test.cpp
new file mode 100644
index 0000000..c688023
--- /dev/null
+++ b/redfish-core/lib/ut/thermal_subsystem_test.cpp
@@ -0,0 +1,42 @@
+#include "include/async_resp.hpp"
+#include "thermal_subsystem.hpp"
+
+#include <nlohmann/json.hpp>
+
+#include <optional>
+#include <string>
+
+#include <gtest/gtest.h>
+
+namespace redfish
+{
+namespace
+{
+
+constexpr const char* chassisId = "ChassisId";
+constexpr const char* validChassisPath = "ChassisPath";
+
+void assertThemalCollectionGet(crow::Response& res)
+{
+    nlohmann::json& json = res.jsonValue;
+    EXPECT_EQ(json["@odata.type"], "#ThermalSubsystem.v1_0_0.ThermalSubsystem");
+    EXPECT_EQ(json["Name"], "Thermal Subsystem");
+    EXPECT_EQ(json["Id"], "ThermalSubsystem");
+    EXPECT_EQ(json["@odata.id"],
+              "/redfish/v1/Chassis/ChassisId/ThermalSubsystem");
+    EXPECT_EQ(json["Status"]["State"], "Enabled");
+    EXPECT_EQ(json["Status"]["Health"], "OK");
+}
+
+TEST(ThermalSubsystemCollectionTest,
+     ThermalSubsystemCollectionStaticAttributesAreExpected)
+{
+    auto shareAsyncResp = std::make_shared<bmcweb::AsyncResp>();
+    shareAsyncResp->res.setCompleteRequestHandler(assertThemalCollectionGet);
+    doThermalSubsystemCollection(
+        shareAsyncResp, chassisId,
+        std::make_optional<std::string>(validChassisPath));
+}
+
+} // namespace
+} // namespace redfish