Merge pull request #5970 from swordqiu/automated-cherry-pick-of-#5966-upstream-release-3.2

Automated cherry pick of #5966: supermicro redfish support
This commit is contained in:
Zexi Li
2020-04-23 09:56:43 +08:00
committed by GitHub
6 changed files with 393 additions and 24 deletions
+2
View File
@@ -1949,6 +1949,8 @@ func (b *SBaremetalInstance) GetConsoleJNLP(ctx context.Context) (string, error)
return bmc.GetIloConsoleJNLP(ctx)
case "dell", "dell inc.":
return bmc.GetIdracConsoleJNLP(ctx, "", "")
case "supermicro":
return bmc.GetSupermicroConsoleJNLP(ctx)
}
return "", httperrors.NewNotImplementedError("Unsupported manufacture %s", manufacture)
}
+88
View File
@@ -0,0 +1,88 @@
// Copyright 2019 Yunion
//
// 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.
package bmconsole
import (
"context"
"net/http"
"net/url"
"strings"
"time"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/util/timeutils"
"yunion.io/x/onecloud/pkg/util/httputils"
)
func (r *SBMCConsole) GetSupermicroConsoleJNLP(ctx context.Context) (string, error) {
loginData := strings.Join([]string{
"name=" + url.QueryEscape(r.username),
"pwd=" + url.QueryEscape(r.password),
}, "&")
// cookie:
// -http-session-=::http.session::0103fd02ceac2d642361b6fdcd4a5994;
// sysidledicon=ledIcon%20grayLed;
// tokenvalue=478be97abdaeb4d454c0418fcca9094d
cookies := make(map[string]string)
// first do html login
postHdr := http.Header{}
postHdr.Set("Content-Type", "application/x-www-form-urlencoded")
setCookieHeader(postHdr, cookies)
hdr, _, err := r.RawRequest(ctx, httputils.POST, "/cgi/login.cgi", postHdr, []byte(loginData))
if err != nil {
return "", errors.Wrap(err, "r.FormPost Login")
}
for _, cookieHdr := range hdr["Set-Cookie"] {
parts := strings.Split(cookieHdr, ";")
if len(parts) > 0 {
pparts := strings.Split(parts[0], "=")
if len(pparts) > 1 {
cookies[pparts[0]] = pparts[1]
}
}
}
getHdr := http.Header{}
setCookieHeader(getHdr, cookies)
now := time.Now()
// fwtype=255&time_stamp=Thu%20Apr%2023%202020%2002%3A25%3A13%20GMT%2B0800%20(%E4%B8%AD%E5%9B%BD%E6%A0%87%E5%87%86%E6%97%B6%E9%97%B4)&_=
loginData = strings.Join([]string{
"fwtype=255",
"time_stamp=" + url.QueryEscape(timeutils.RFC2882Time(now)),
"_=",
}, "&")
_, rspBody, err := r.RawRequest(ctx, httputils.POST, "/cgi/upgrade_process.cgi", getHdr, []byte(loginData))
if err != nil {
return "", errors.Wrapf(err, "r.RawPost %s", loginData)
}
if r.isDebug {
log.Debugf("upgrade_process.cgi %s", rspBody)
}
_, rspBody, err = r.RawRequest(ctx, httputils.GET, "/cgi/url_redirect.cgi?url_name=ikvm&url_type=jwsk", getHdr, nil)
if err != nil {
return "", errors.Wrap(err, "r.RawGet")
}
return string(rspBody), nil
}
+1
View File
@@ -21,6 +21,7 @@ import (
_ "yunion.io/x/onecloud/pkg/util/redfish/idrac"
_ "yunion.io/x/onecloud/pkg/util/redfish/idrac9"
_ "yunion.io/x/onecloud/pkg/util/redfish/ilo"
_ "yunion.io/x/onecloud/pkg/util/redfish/supermicro"
)
func init() {
+35 -24
View File
@@ -78,7 +78,11 @@ func (r *SBaseRedfishClient) GetHost() string {
log.Errorf("urlParse %s fail %s", r.endpoint, err)
return ""
}
return parts.Hostname()
ports := parts.Port()
if len(ports) == 0 {
return parts.Hostname()
}
return parts.Hostname() + ":" + parts.Port()
}
func (r *SBaseRedfishClient) GetUsername() string {
@@ -421,32 +425,36 @@ func (r *SBaseRedfishClient) GetSystemInfo(ctx context.Context) (string, SSystem
sysInfo.ResetTypeSupported[i] = resetTypeStr
}
nicListPath, _ := resp.GetString("EthernetInterfaces", "@odata.id")
nicListInfo, err := r.Get(ctx, nicListPath)
nicListPath, err := resp.GetString("EthernetInterfaces", "@odata.id")
if err != nil {
return path, sysInfo, errors.Wrap(err, "Get EthernetInterfaces fail")
}
nicList, _ := nicListInfo.GetArray("Members")
if len(nicList) > 0 {
sysInfo.EthernetNICs = make([]string, len(nicList))
for i := range nicList {
nicPath, _ := nicList[i].GetString("@odata.id")
nicInfo, err := r.Get(ctx, nicPath)
if err != nil {
return path, sysInfo, errors.Wrapf(err, "Get EthernetInterface[%d] error", i)
}
var macAddr string
for _, key := range []string{
"MacAddress",
"MACAddress",
"PermanentMACAddress",
} {
macAddr, _ = nicInfo.GetString(key)
if len(macAddr) > 0 {
break
log.Warningf("no EthernetInterfaces info")
} else {
nicListInfo, err := r.Get(ctx, nicListPath)
if err != nil {
return path, sysInfo, errors.Wrap(err, "Get EthernetInterfaces fail")
}
nicList, _ := nicListInfo.GetArray("Members")
if len(nicList) > 0 {
sysInfo.EthernetNICs = make([]string, len(nicList))
for i := range nicList {
nicPath, _ := nicList[i].GetString("@odata.id")
nicInfo, err := r.Get(ctx, nicPath)
if err != nil {
return path, sysInfo, errors.Wrapf(err, "Get EthernetInterface[%d] error", i)
}
var macAddr string
for _, key := range []string{
"MacAddress",
"MACAddress",
"PermanentMACAddress",
} {
macAddr, _ = nicInfo.GetString(key)
if len(macAddr) > 0 {
break
}
}
sysInfo.EthernetNICs[i] = netutils.FormatMacAddr(macAddr)
}
sysInfo.EthernetNICs[i] = netutils.FormatMacAddr(macAddr)
}
}
@@ -542,6 +550,9 @@ func (r *SBaseRedfishClient) readLogs(ctx context.Context, path string, subsys s
// }
// return nil, errors.Wrap(err, path)
}
if r.IsDebug {
log.Debugf("readLogs %s", resp.PrettyString())
}
tmpEvents := make([]SEvent, 0)
err = resp.Unmarshal(&tmpEvents, r.IRedfishDriver().LogItemsKey())
if err != nil {
+15
View File
@@ -0,0 +1,15 @@
// Copyright 2019 Yunion
//
// 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.
package supermicro // import "yunion.io/x/onecloud/pkg/util/redfish/supermicro"
+252
View File
@@ -0,0 +1,252 @@
// Copyright 2019 Yunion
//
// 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.
package supermicro
import (
"context"
"time"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/httperrors"
"yunion.io/x/onecloud/pkg/util/httputils"
"yunion.io/x/onecloud/pkg/util/redfish"
"yunion.io/x/onecloud/pkg/util/redfish/bmconsole"
"yunion.io/x/onecloud/pkg/util/redfish/generic"
)
type SSupermicroRedfishApiFactory struct {
}
func (f *SSupermicroRedfishApiFactory) Name() string {
return "Supermicro"
}
func (f *SSupermicroRedfishApiFactory) NewApi(endpoint, username, password string, debug bool) redfish.IRedfishDriver {
return NewSupermicroRedfishApi(endpoint, username, password, debug)
}
func init() {
redfish.RegisterApiFactory(&SSupermicroRedfishApiFactory{})
}
type SSupermicroRefishApi struct {
generic.SGenericRefishApi
}
func NewSupermicroRedfishApi(endpoint, username, password string, debug bool) redfish.IRedfishDriver {
api := &SSupermicroRefishApi{
SGenericRefishApi: generic.SGenericRefishApi{
SBaseRedfishClient: redfish.NewBaseRedfishClient(endpoint, username, password, debug),
},
}
api.SetVirtualObject(api)
return api
}
func (r *SSupermicroRefishApi) ParseRoot(root jsonutils.JSONObject) error {
if root.Contains("UUID") && root.Contains("UpdateService") && root.Contains("Oem") {
return nil
}
return errors.Error("not iDrac")
}
func (r *SSupermicroRefishApi) GetVirtualCdromJSON(ctx context.Context) (string, jsonutils.JSONObject, error) {
_, resp, err := r.GetResource(ctx, "Managers", "0", "VirtualMedia")
if err != nil {
return "", nil, errors.Wrap(err, "r.GetResource")
}
log.Debugf("resp %s", resp)
path, err := resp.GetString("Oem", "Supermicro", "VirtualMediaConfig", "@odata.id")
if err != nil {
return "", nil, errors.Wrap(err, "GetString Oem Supermicro VirtualMediaConfig @odata.id")
}
cdResp, err := r.Get(ctx, path)
if err != nil {
return "", nil, errors.Wrap(err, "Get Cdrom info fail")
}
if cdResp != nil {
return path, cdResp, nil
}
return "", nil, httperrors.ErrNotFound
}
func (r *SSupermicroRefishApi) GetVirtualCdromInfo(ctx context.Context) (string, redfish.SCdromInfo, error) {
cdInfo := redfish.SCdromInfo{}
path, jsonResp, err := r.GetVirtualCdromJSON(ctx)
if err != nil {
return "", cdInfo, errors.Wrap(err, "r.GetVirtualCdromJSON")
}
imgPath, _ := jsonResp.GetString("Image")
if imgPath == "null" {
imgPath = ""
}
cdInfo.Image = imgPath
if jsonResp.Contains("Actions") {
cdInfo.SupportAction = true
}
return path, cdInfo, nil
}
func (r *SSupermicroRefishApi) MountVirtualCdrom(ctx context.Context, path string, cdromUrl string, boot bool) error {
info := jsonutils.NewDict()
info.Set("Image", jsonutils.NewString(cdromUrl))
path = httputils.JoinPath(path, "Actions/VirtualMedia.InsertMedia")
_, _, err := r.Post(ctx, path, info)
if err != nil {
return errors.Wrap(err, "r.Post")
}
// log.Debugf("%s", resp.PrettyString())
if boot {
err = r.SetNextBootVirtualCdrom(ctx)
if err != nil {
return errors.Wrap(err, "r.SetNextBootVirtualCdrom")
}
}
return nil
}
func (r *SSupermicroRefishApi) UmountVirtualCdrom(ctx context.Context, path string) error {
info := jsonutils.NewDict()
path = httputils.JoinPath(path, "Actions/VirtualMedia.EjectMedia")
_, _, err := r.Post(ctx, path, info)
if err != nil {
return errors.Wrap(err, "r.Post")
}
// log.Debugf("%s", resp.PrettyString())
return nil
}
func (r *SSupermicroRefishApi) GetConsoleJNLP(ctx context.Context) (string, error) {
bmc := bmconsole.NewBMCConsole(r.GetHost(), r.GetUsername(), r.GetPassword(), r.IsDebug)
return bmc.GetSupermicroConsoleJNLP(ctx)
}
func (r *SSupermicroRefishApi) ReadSystemLogs(ctx context.Context, since time.Time) ([]redfish.SEvent, error) {
_, resp, err := r.GetResource(ctx, "Managers", "0", "LogServices", "0", "Entries")
if err != nil {
return nil, errors.Wrap(err, "GetResource Managers 0 LogServices 0 Entries")
}
members, err := resp.GetArray("Members")
if err != nil {
return nil, errors.Wrap(err, "find Members")
}
events := make([]redfish.SEvent, 0)
for i := range members {
path, _ := members[i].GetString("@odata.id")
resp, err := r.Get(ctx, path)
if err != nil {
log.Errorf("Get url for event %s error %s", path, err)
continue
}
event := redfish.SEvent{}
err = resp.Unmarshal(&event)
if err == nil {
event.Type, _ = resp.GetString("EntryType")
event.Severity, _ = resp.GetString("EntryCode")
events = append(events, event)
} else {
log.Errorf("unmarshal event fail %s %s", resp, err)
}
}
return events, nil
}
func (r *SSupermicroRefishApi) ReadManagerLogs(ctx context.Context, since time.Time) ([]redfish.SEvent, error) {
return nil, httperrors.ErrNotSupported
}
func (r *SSupermicroRefishApi) GetClearSystemLogsPath() string {
return "/redfish/v1/Managers/1/LogServices/Log1/Actions/LogService.Reset"
}
func (r *SSupermicroRefishApi) GetClearManagerLogsPath() string {
return "/redfish/v1/Managers/1/LogServices/Log1/Actions/LogService.Reset"
}
func (r *SSupermicroRefishApi) GetPowerPath() string {
return "/redfish/v1/Chassis/1/Power"
}
func (r *SSupermicroRefishApi) GetThermalPath() string {
return "/redfish/v1/Chassis/1/Thermal"
}
type sNtpConf struct {
NTPEnable bool `json:"NTPEnable"`
PrimaryNTPServer string `json:"PrimaryNTPServer"`
SecondaryNTPServer string `json:"SecondaryNTPServer"`
}
func (r *SSupermicroRefishApi) GetNTPConf(ctx context.Context) (redfish.SNTPConf, error) {
ntpConf := redfish.SNTPConf{}
_, resp, err := r.GetResource(ctx, "Managers", "0")
if err != nil {
return ntpConf, errors.Wrap(err, "GetResource")
}
path, err := resp.GetString("Oem", "Supermicro", "NTP", "@odata.id")
if err != nil {
return ntpConf, errors.Wrap(err, "GetString \"Oem\", \"Supermicro\", \"NTP\", \"@odata.id\"")
}
resp, err = r.Get(ctx, path)
if err != nil {
return ntpConf, errors.Wrapf(err, "Get %s fail %s", path, err)
}
conf := sNtpConf{}
err = resp.Unmarshal(&conf)
if err != nil {
return ntpConf, errors.Wrap(err, "resp.Unmarshal NTP")
}
ntpConf.ProtocolEnabled = conf.NTPEnable
if len(conf.PrimaryNTPServer) > 0 {
ntpConf.NTPServers = append(ntpConf.NTPServers, conf.PrimaryNTPServer)
}
if len(conf.SecondaryNTPServer) > 0 {
ntpConf.NTPServers = append(ntpConf.NTPServers, conf.SecondaryNTPServer)
}
return ntpConf, nil
}
func (r *SSupermicroRefishApi) SetNTPConf(ctx context.Context, conf redfish.SNTPConf) error {
_, resp, err := r.GetResource(ctx, "Managers", "0")
if err != nil {
return errors.Wrap(err, "GetResource")
}
path, err := resp.GetString("Oem", "Supermicro", "NTP", "@odata.id")
if err != nil {
return errors.Wrap(err, "GetString \"Oem\", \"Supermicro\", \"NTP\", \"@odata.id\"")
}
nconf := sNtpConf{
NTPEnable: conf.ProtocolEnabled,
}
if len(conf.NTPServers) > 0 {
nconf.PrimaryNTPServer = conf.NTPServers[0]
}
if len(conf.NTPServers) > 1 {
nconf.SecondaryNTPServer = conf.NTPServers[1]
}
resp, err = r.Patch(ctx, path, jsonutils.Marshal(nconf))
if err != nil {
return errors.Wrap(err, "r.Patch")
}
if r.IsDebug {
log.Debugf("%s", resp.PrettyString())
}
return nil
}