basic redfish api

This commit is contained in:
Devaev Maxim 2020-09-28 02:41:46 +03:00
parent 7e874b035d
commit ccab97a56f
4 changed files with 143 additions and 3 deletions

View File

@ -93,3 +93,9 @@ location /streamer {
proxy_buffering off; proxy_buffering off;
proxy_ignore_headers X-Accel-Buffering; proxy_ignore_headers X-Accel-Buffering;
} }
location /redfish {
proxy_pass http://kvmd;
include /etc/kvmd/nginx/loc-proxy.conf;
auth_request off;
}

View File

@ -0,0 +1,127 @@
# ========================================================================== #
# #
# KVMD - The main Pi-KVM daemon. #
# #
# Copyright (C) 2018 Maxim Devaev <mdevaev@gmail.com> #
# #
# This program is free software: you can redistribute it and/or modify #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or #
# (at your option) any later version. #
# #
# This program is distributed in the hope that it will be useful, #
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
# GNU General Public License for more details. #
# #
# You should have received a copy of the GNU General Public License #
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
# #
# ========================================================================== #
import asyncio
from aiohttp.web import Request
from aiohttp.web import Response
from ....plugins.atx import BaseAtx
from ....validators import ValidatorError
from ....validators import check_string_in_list
from ..info import InfoManager
from ..http import HttpError
from ..http import exposed_http
from ..http import make_json_response
# =====
class RedfishApi:
# https://github.com/DMTF/Redfishtool
# https://github.com/DMTF/Redfish-Mockup-Server
# https://redfish.dmtf.org/redfish/v1
# https://www.dmtf.org/documents/redfish-spmf/redfish-mockup-bundle-20191
# https://www.dmtf.org/sites/default/files/Redfish_School-Sessions.pdf
# https://www.ibm.com/support/knowledgecenter/POWER9/p9ej4/p9ej4_kickoff.htm
#
# Quick examples:
# redfishtool -S Never -u admin -p admin -r localhost:8080 Systems
# redfishtool -S Never -u admin -p admin -r localhost:8080 Systems reset ForceOff
def __init__(self, info_manager: InfoManager, atx: BaseAtx) -> None:
self.__info_manager = info_manager
self.__atx = atx
self.__actions = {
"On": self.__atx.power_on,
"ForceOff": self.__atx.power_off_hard,
"GracefulShutdown": self.__atx.power_off,
"ForceRestart": self.__atx.power_reset_hard,
"ForceOn": self.__atx.power_on,
"PushPowerButton": self.__atx.click_power,
}
# =====
@exposed_http("GET", "/redfish/v1", auth_required=False)
async def __root_handler(self, _: Request) -> Response:
return make_json_response({
"@odata.id": "/redfish/v1",
"@odata.type": "#ServiceRoot.v1_6_0.ServiceRoot",
"Id": "RootService",
"Name": "Root Service",
"RedfishVersion": "1.6.0",
"Systems": {"@odata.id": "/redfish/v1/Systems"},
}, wrap_result=False)
@exposed_http("GET", "/redfish/v1/Systems")
async def __systems_handler(self, _: Request) -> Response:
return make_json_response({
"@odata.id": "/redfish/v1/Systems",
"@odata.type": "#ComputerSystemCollection.ComputerSystemCollection",
"Members": [{"@odata.id": "/redfish/v1/Systems/0"}],
"Members@odata.count": 1,
"Name": "Computer System Collection",
}, wrap_result=False)
@exposed_http("GET", "/redfish/v1/Systems/0")
async def __server_handler(self, _: Request) -> Response:
(atx_state, meta_state) = await asyncio.gather(*[
self.__atx.get_state(),
self.__info_manager.get_submanager("meta").get_state(),
])
try:
host = meta_state.get("server", {})["host"]
except Exception:
host = ""
return make_json_response({
"@odata.id": "/redfish/v1/Systems/0",
"@odata.type": "#ComputerSystem.v1_10_0.ComputerSystem",
"Actions": {
"#ComputerSystem.Reset": {
"ResetType@Redfish.AllowableValues": list(self.__actions),
"target": "/redfish/v1/Systems/0/Actions/ComputerSystem.Reset"
},
},
"Id": "0",
"HostName": host,
"PowerState": ("On" if atx_state["leds"]["power"] else "Off"),
}, wrap_result=False)
@exposed_http("POST", "/redfish/v1/Systems/0/Actions/ComputerSystem.Reset")
async def __power_handler(self, request: Request) -> Response:
try:
action = check_string_in_list(
arg=(await request.json())["ResetType"],
name="Redfish ResetType",
variants=set(self.__actions),
lower=False,
)
except Exception as err:
if isinstance(err, ValidatorError):
raise
raise HttpError("Missing Redfish ResetType", 400)
await self.__actions[action](False)
return Response(body=None, status=204)

View File

@ -117,13 +117,14 @@ def make_json_response(
result: Optional[Dict]=None, result: Optional[Dict]=None,
status: int=200, status: int=200,
set_cookies: Optional[Dict[str, str]]=None, set_cookies: Optional[Dict[str, str]]=None,
wrap_result: bool=True,
) -> aiohttp.web.Response: ) -> aiohttp.web.Response:
response = aiohttp.web.Response( response = aiohttp.web.Response(
text=json.dumps({ text=json.dumps(({
"ok": (status == 200), "ok": (status == 200),
"result": (result or {}), "result": (result or {}),
}, sort_keys=True, indent=4), } if wrap_result else result), sort_keys=True, indent=4),
status=status, status=status,
content_type="application/json", content_type="application/json",
) )

View File

@ -91,6 +91,7 @@ from .api.atx import AtxApi
from .api.msd import MsdApi from .api.msd import MsdApi
from .api.streamer import StreamerApi from .api.streamer import StreamerApi
from .api.export import ExportApi from .api.export import ExportApi
from .api.redfish import RedfishApi
# ===== # =====
@ -198,6 +199,7 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
MsdApi(msd, sync_chunk_size), MsdApi(msd, sync_chunk_size),
StreamerApi(streamer), StreamerApi(streamer),
ExportApi(info_manager, atx, user_gpio), ExportApi(info_manager, atx, user_gpio),
RedfishApi(info_manager, atx),
] ]
self.__ws_handlers: Dict[str, Callable] = {} self.__ws_handlers: Dict[str, Callable] = {}
@ -291,7 +293,11 @@ class KvmdServer(HttpServer): # pylint: disable=too-many-arguments,too-many-ins
super().run(**kwargs) super().run(**kwargs)
async def _make_app(self) -> aiohttp.web.Application: async def _make_app(self) -> aiohttp.web.Application:
app = aiohttp.web.Application() app = aiohttp.web.Application(middlewares=[aiohttp.web.normalize_path_middleware(
append_slash=False,
remove_slash=True,
merge_slashes=True,
)])
app.on_shutdown.append(self.__on_shutdown) app.on_shutdown.append(self.__on_shutdown)
app.on_cleanup.append(self.__on_cleanup) app.on_cleanup.append(self.__on_cleanup)