基于Redfish的服务器管理小应用
前言
本文介绍了近期完成的一个小项目——基于Redfish的服务器管理小应用。一方面记录一下自己的开发经历;另一方面本项目适用于准备学习样式表,信号槽,treeWidget,https请求,SQlite数据库的人。代码在最下方。
注:公司内部网络才可以访问redfish接口,项目的网络请求无法发送成功,所以关于https请求部分仅供参考,也可以参看Qt开发之路——Json解析过程中遇到的readAll()清除内存缓冲区问题~
关于ipmi
IMPI(智能平台管理接口)是一种嵌入式功能,同时也是工业标准,由英特尔与戴尔、惠普和NEC合作开发,可实现对服务器的远程控制。但是这种规范也有它的局限性,层出不穷的安全问题使得它自从2015年更新2.0后没有再翻新,与此同时Redfish兴起。
关于Redfish
Redfish是由分布式管理任务组(DMTF)发布的开放式行业标准规范,旨在对平台硬件进行现代化和安全的管理,是一种管理标准,在超媒体RESTful接口中使用数据模型表示。它是一个超媒体API,所以它能够通过一个一致的接口来表示各种实现。它有管理数据中心资源、处理事件、长期任务和发现的机制。初识Redfish
关于RedfishAPI
RedfishAPI代表了一种新的编程风格,它能够以一致的方式管理从超级规模到刀片服务器再到独立服务器的系统。
对于背景可以移步https://blog.csdn.net/asmartkiller/article/details/106558952作更细致的了解。
效果
开发环境
应用:Qt Creator 4.11.1(Community)
开发环境:MinGW_32_bit
数据库:SQlite
功能介绍
添加服务器:
初始化:删除所有服务器
帮助:简单显示服务器小助手的功能
右键刷新告警及开关机状态信息(再次请求系统信息和告警信息,即两个get请求):
右键删除服务器
技术
1、Qt样式表,信号槽
2、通过treewidget组件显示数据。
3、通过解析https请求到的JSON数据获取相对应的特征信息。
4、将数据存储在Qt自带的SQlite数据库中,方便读取和删除。
Qt样式表
因为程序比较小,且界面不多,所以样式表直接在设计器里实现的。
比如存储按钮的点击状态
QPushButton#save{
border-style:outset;
font: 75 16pt "宋体";
font-weight:bold;
color:rgb(255, 170, 0);
background-color:rgba(225, 225, 225, 0);
}
QPushButton#save:hover{
background-color:rgba(225, 225, 225,200);
}
QPushButton#save:pressed{
background-color:rgba(225, 225, 225, 200);
}
信号槽
为了便于统一管理,将信号和槽写在main函数中。主要用于界面之间的切换和值传递。
/* + function
ui from widget to addid for adding servers
*/
QObject::connect(w,SIGNAL(showAddId()),i,SLOT(receive_widget_addid()));
/* save function
ui from addid to widget for showing servers data
*/
QObject::connect(i,SIGNAL(showLabel(QString,QString,QString,QString,QString,QString)),w,SLOT(receive_save(QString,QString,QString,QString,QString,QString)));
/* cancel function
ui from addid to widget for showing widget
*/
QObject::connect(i,SIGNAL(showWidget()),w,SLOT(receive_addid_cancel()));
/* show loading ui
ui from addid to loading for showing waiting process
*/
QObject::connect(i,SIGNAL(showLoading()),l,SLOT(receive_addid_loading()));
/* close loading ui
ui from addid to loading for showing widget ui
*/
QObject::connect(i,SIGNAL(closeLoading()),l,SLOT(receive_addid_closeloading()));
/* refresh realdata
ui from widget to addid for refreshing realdata——powerstate and serveritynum
*/
QObject::connect(w,SIGNAL(refresh_realtimedata(QString)),i,SLOT(receive_widget_refresh(QString)));
/* refresh realdata
ui from addid to widget for sending realdata——powerstate and serveritynum
*/
QObject::connect(i,SIGNAL(send_realTimeData(QString,QString)),w,SLOT(receive_realTimeData(QString,QString)));
treeWidget的使用
添加一行
QStringList strs;
strs<<QString("******")<<QString("*********")<<QString(powerstate)<<QString(severity)<<QString("***************");
//set the header to appropriate size
QHeaderView *head=ui->treeWidget->header();
head->setSectionResizeMode(QHeaderView::ResizeToContents);
//add one line for treewidget,content is strs
QTreeWidgetItem *strsroot = new QTreeWidgetItem(ui->treeWidget,strs);
//set currentitem
ui->treeWidget->setCurrentItem(strsroot);
删除一行
QTreeWidgetItem *item = ui->treeWidget->currentItem();
delete(item);
删除treewidget的所有数据
//delete the treewidget for ui
ui->treeWidget->clear();
https请求及解析
https请求:QNetworkRequest、QNetworkAccessManager、QNetworkReply、QEventLoop
JSON解析:QJsonDocument、QJsonParseError、QJsonObject、QJsonArray、QJsonValue
主要涉及到对三种形式JSON数据的解析。涉及到3条url,这里记为url1,url2,url3。首先获取token,然后根据token和url2发送get请求先后获取产品名称,开关机状态,健康状态以及uuid。通过url3和token获取服务器告警信息。
url1及用户名密码发送post请求获取token:
QString AddId::postToken(QString id)
{
// new request object
QNetworkRequest request;
// ready for sending https request
QSslConfiguration config;
QSslConfiguration conf = request.sslConfiguration();
conf.setPeerVerifyMode(QSslSocket::VerifyNone);
conf.setProtocol(QSsl::TlsV1SslV3);
request.setSslConfiguration(conf);
request.setUrl(QUrl("https://www.baidu.com"));
request.setUrl(QUrl(id));
request.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/json"));
//set raw header of request
request.setRawHeader("Content-Type", "application/json");
//check supported agreement
qDebug()<< manager->supportedSchemes();//("ftp", "file", "qrc", "http", "https", "data")
//obtain form data——username and password
QString submitMsg = QString(R"(
{
"UserName": "%1",
"Password": "%2"
})").arg(ui->lineEdit_2->text()).arg(ui->lineEdit_3->text());
QNetworkReply *reply=manager->post(request,submitMsg.toUtf8());
//open a local event loop, then wait reply but do not stop thread(significant)
QEventLoop eventLoop;
connect(manager, &QNetworkAccessManager::finished, &eventLoop, &QEventLoop::quit);
eventLoop.exec();
/* for \"X-Auth-Token\":
analysis json data
*/
QString token = analysisSessionsJson(reply);
return token;
得到如下形式的JSON数据:
{
"@odata.context": "/redfish/v1/$metadata#Session.Session",
"@odata.id": "/redfish/v1/****/*********",
"@odata.type": "#Session.*****.Session",
"Id": "********",
"Name": "User Session",
"Description": "Manager User Session",
"UserName": "****",
"SessionType": "Redfish",
"Oem": {
"BMC": {
"LoginTime": "2021-01-04T11:30:51+08:00\n",
"ClientAddress": "****",
"ServerAddress": "id",
"SessionId": "*****",
"EnabledHttps": true,
"Role": "Administrator",
"Location": "/redfish/v1/*****/*************",
"X-Auth-Token": "lhe6Gqi4Lz2CA7rx15vc0IRNh22iz4Vb",
"UserId": 2
}
}
}
解析获取认证X-Token
/*analysis SessionsJson for token
url1
*/
QString AddId::analysisSessionsJson(QNetworkReply *reply){
//judge if format of the json is right
if (reply->error() == QNetworkReply::NoError)
{
QByteArray bytes = reply->readAll(); //read all bytes
QJsonParseError jsonError_login;
//switch to json document
QJsonDocument document = QJsonDocument::fromJson(bytes, &jsonError_login);
//analysis Json error
if (document.isObject())
{
QJsonObject obj = document.object();
if (obj.contains("Oem"))
{
QJsonObject object_value = obj.value("Oem").toObject();
if (object_value.contains("BMC")){
QJsonObject object1_value = object_value.value("BMC").toObject();
if(object1_value.contains("X-Auth-Token"))
{
QString token_val = object1_value.value("X-Auth-Token").toString();
return token_val;
}
}
}
}
}
}
url2及token发送get请求获取系统信息:
QStringList AddId::getCriticalMsg(QString url,QString token)
{
QNetworkRequest request;
QSslConfiguration config;
QSslConfiguration conf = request.sslConfiguration();
conf.setPeerVerifyMode(QSslSocket::VerifyNone);
conf.setProtocol(QSsl::TlsV1SslV3);
request.setSslConfiguration(conf);
request.setUrl(QUrl("https://www.baidu.com"));
//set url and xAuthToken
request.setUrl(QUrl(url));
request.setHeader(QNetworkRequest::ContentTypeHeader,"application/json");
request.setRawHeader(QByteArray("X-Auth-Token"),QByteArray(token.toUtf8()));
QNetworkReply *systeminfo = manager->get(request);
//if no below three codes,will first run the latter code after QNetworkReply *systeminfo = manager->get(request);then to run reply
QEventLoop eventLoop;
connect(manager, &QNetworkAccessManager::finished, &eventLoop, &QEventLoop::quit);
eventLoop.exec();
QStringList criticalstr = analysisSystemJson(systeminfo);
return criticalstr;
}
得到如下形式的JSON数据:
{
"@odata.context": "/redfish/v1/$metadata#ComputerSystem.ComputerSystem",
"@odata.id": "/redfish/v1/******",
"@odata.type": "#ComputerSystem.ComputerSystem",
"Id": "1",
"Name": "Computer System",
"Actions": {
"ResetType@Redfish.AllowableValues": [
"On",
"ForceOff",
"ForceRestart",
"GracefulShutdown",
"ForcePowerCycle",
"Nmi"
],
"target": "/redfish/v1/*******"
},
"AssetTag": "******",
"Manufacturer": "********",
"Model": "********",
"SerialNumber": "********",
"PartNumber": "********",
"HostingRole": "ApplicationServer",
"SystemType": "Physical",
"UUID": "4********",
"HostName": "********",
"PowerState": "On",
"PowerRestorePolicyTypes": "LastState",
"IndicatorLED": "Off",
"BIOSVersion": "********",
"Status": {
"State": "Enabled",
"Health": "OK"
}
}
解析得到Manufacturer,PowerState,UUID,Health
/*analysis processing
*url2
for product name,state of on or off,warnings num,GUID*/
QStringList AddId::analysisSystemJson(QNetworkReply *sys){
QStringList criticalstr;
if (sys->error() == QNetworkReply::NoError)
{
QByteArray bytes = sys->readAll();
QJsonParseError jsonError_system;
QJsonDocument document = QJsonDocument::fromJson(bytes, &jsonError_system);
if (document.isObject())
{
QJsonObject obj = document.object();
if (obj.contains("Manufacturer")) {
QString manufacturer = obj.value("Manufacturer").toString();
criticalstr.append(manufacturer);
qDebug() << manufacturer;
}
if (obj.contains("PowerState")) {
QString powerstate = obj.value("PowerState").toString();
criticalstr.append(powerstate);
qDebug() << powerstate;
}
if (obj.contains("UUID")) {
QString uuid = obj.value("UUID").toString();
criticalstr.append(uuid);
qDebug() << uuid;
}
if (obj.contains("Status"))
{
QJsonObject object_value = obj.value("Status").toObject();
if (object_value.contains("Health")){
QString health = object_value.value("Health").toString();
criticalstr.append(health);
qDebug() << health;
}
}
}
}
}
通过url3和token发送get请求获取告警率:
QString AddId::getSeverityMsg(QString url,QString token)
{
QNetworkRequest request;
QSslConfiguration config;
QSslConfiguration conf = request.sslConfiguration();
conf.setPeerVerifyMode(QSslSocket::VerifyNone);
conf.setProtocol(QSsl::TlsV1SslV3);
request.setSslConfiguration(conf);
request.setUrl(QUrl("https://www.baidu.com"));
//set url and xAuthToken
request.setUrl(QUrl(url));
request.setHeader(QNetworkRequest::ContentTypeHeader,"application/json");
request.setRawHeader(QByteArray("X-Auth-Token"),QByteArray(token.toUtf8()));
QNetworkReply *logserverinfo = manager->get(request);
//if no below three codes,will first run the latter code after QNetworkReply *systeminfo = manager->get(request);then to run reply
QEventLoop eventLoop;
connect(manager, &QNetworkAccessManager::finished, &eventLoop, &QEventLoop::quit);
eventLoop.exec();
QString healthrate = analysisLogServerJson(logserverinfo);
return healthrate;
}
得到如下形式的JSON数据:
{
"@odata.context": "/redfish/v1/******",
"@odata.id": "redfish/v1/******",
"@odata.type": "#LogEntryCollection.LogEntryCollection",
"Description": "Collection of entries for this log service",
"Name": "Log Service Entries Collection",
"Members@odata.count": 24,
"Members": [
{
"@odata.id": "/redfish/v1/******/24",
"Id": "24",
"Name": "Log Entry 24",
"EntryType": "******",
"SensorNumber": 186,
"Created": "2021-01-04T07:14:12",
"EventTimestamp": "2021-01-04T07:14:12",
"Severity": "Ok",
"EntryCode": "Assert",
"SensorType": "******/Interconnect",
"Message": "******/Interconnect is connected"
},
{
"@odata.id": "/redfish/v1/******",
"Id": "23",
"Name": "Log Entry 23",
"EntryType": "SEL",
"SensorNumber": 184,
"Created": "2021-01-04T07:14:12",
"EventTimestamp": "2021-01-04T07:14:12",
"Severity": "Ok",
"EntryCode": "Assert",
"SensorType": "******/Interconnect",
"Message": "******/Interconnect is connected"
},
...........
]
}
解析获得Severity = ok的数量和 不等于ok(严重告警)的数量。
/*get request
*https://id/redfish/v1/Systems
for product name,state of on or off,warnings num,GUID*/
QString AddId::analysisLogServerJson(QNetworkReply *logserver){
int serveritynum = 0;
int healthnum = 0;
qint32 logcount = 0;
if (logserver->error() == QNetworkReply::NoError)
{
QByteArray bytes = logserver->readAll();
QJsonParseError jsonError_logserver;
QJsonDocument document = QJsonDocument::fromJson(bytes, &jsonError_logserver);
if (document.isObject())
{
QJsonObject obj = document.object();
if (obj.contains("Members@odata.count"))
{
//note that value for "Members@odata.count" is not a string
logcount = obj.value("Members@odata.count").toInt();
}
if (obj.contains("Members"))
{
QJsonValue members = obj.value("Members");//the value of members is a array
if(members.isArray())
{
for(int i = 0;i< logcount;i++)
{
QJsonObject members_obj = members.toArray().at(i).toObject();
if(members_obj.contains("Severity"))
{
QString status_value = members_obj.value("Severity").toString();
if(!(status_value == "Ok"))
{
serveritynum +=1;
}
else{
healthnum +=1;
}
}
}
}
}
}
}
QSlite数据库的使用
QSqlDatabase、QSqlQuery、QSqlError、QSqlTableModel
github:https://github.com/hqy7777/SMS-Based-On-Redfish-Qt
参考
Qt开发之路——Json解析过程中遇到的readAll()清除内存缓冲区问题
Qt开发之路——SQlite的使用(简单粗暴)
欢迎讨论交流~