Skip to content

Commit 8209d7d

Browse files
authored
Enables support for supermicro x11ssl-f and x11scz-f (#411)
2 parents fffd096 + 105a48a commit 8209d7d

File tree

6 files changed

+99
-26
lines changed

6 files changed

+99
-26
lines changed

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ go 1.21
55
require (
66
dario.cat/mergo v1.0.1
77
github.com/Jeffail/gabs/v2 v2.7.0
8-
github.com/bmc-toolbox/common v0.0.0-20241031162543-6b96e5981a0d
8+
github.com/bmc-toolbox/common v0.0.0-20250112191656-b6de52e8303d
99
github.com/bombsimon/logrusr/v2 v2.0.1
1010
github.com/ghodss/yaml v1.0.0
1111
github.com/go-logr/logr v1.4.2

go.sum

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ github.com/VictorLowther/soap v0.0.0-20150314151524-8e36fca84b22 h1:a0MBqYm44o0N
88
github.com/VictorLowther/soap v0.0.0-20150314151524-8e36fca84b22/go.mod h1:/B7V22rcz4860iDqstGvia/2+IYWXf3/JdQCVd/1D2A=
99
github.com/bmc-toolbox/common v0.0.0-20241031162543-6b96e5981a0d h1:dMmFDAAEpXizInaNwPSa5LM6tX/xDIPKjL6v9jYfMxo=
1010
github.com/bmc-toolbox/common v0.0.0-20241031162543-6b96e5981a0d/go.mod h1:Cdnkm+edb6C0pVkyCrwh3JTXAe0iUF9diDG/DztPI9I=
11+
github.com/bmc-toolbox/common v0.0.0-20250112191656-b6de52e8303d h1:5c0jhS9jNLm1t3GVEESsWv+p6recFRLGW90zp8HDIDs=
12+
github.com/bmc-toolbox/common v0.0.0-20250112191656-b6de52e8303d/go.mod h1:Cdnkm+edb6C0pVkyCrwh3JTXAe0iUF9diDG/DztPI9I=
1113
github.com/bombsimon/logrusr/v2 v2.0.1 h1:1VgxVNQMCvjirZIYaT9JYn6sAVGVEcNtRE0y4mvaOAM=
1214
github.com/bombsimon/logrusr/v2 v2.0.1/go.mod h1:ByVAX+vHdLGAfdroiMg6q0zgq2FODY2lc5YJvzmOJio=
1315
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=

providers/supermicro/errors.go

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,10 +8,13 @@ import (
88
)
99

1010
var (
11-
ErrQueryFRUInfo = errors.New("FRU information query returned error")
12-
ErrXMLAPIUnsupported = errors.New("XML API is unsupported")
13-
ErrModelUnknown = errors.New("Model number unknown")
14-
ErrModelUnsupported = errors.New("Model not supported")
11+
ErrQueryFRUInfo = errors.New("FRU information query returned error")
12+
ErrXMLAPIUnsupported = errors.New("XML API is unsupported")
13+
ErrModelUnknown = errors.New("Model number unknown")
14+
ErrModelUnsupported = errors.New("Model not supported")
15+
ErrBoardIDUnknown = errors.New("BoardID could not be identified")
16+
ErrUnexpectedResponse = errors.New("Unexpected response content")
17+
ErrUnexpectedStatusCode = errors.New("Unexpected status code")
1518
)
1619

1720
type UnexpectedResponseError struct {

providers/supermicro/supermicro.go

Lines changed: 35 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -150,34 +150,60 @@ func NewClient(host, user, pass string, log logr.Logger, opts ...Option) *Client
150150
}
151151
}
152152

153-
// Open a connection to a Supermicro BMC using the vendor API.
154-
func (c *Client) Open(ctx context.Context) (err error) {
153+
func (c *Client) login(ctx context.Context, encodeCreds bool) error {
154+
var user, pass string
155+
if encodeCreds {
156+
user = base64.StdEncoding.EncodeToString([]byte(c.serviceClient.user))
157+
pass = base64.StdEncoding.EncodeToString([]byte(c.serviceClient.pass))
158+
} else {
159+
user = c.serviceClient.user
160+
pass = c.serviceClient.pass
161+
}
162+
155163
data := fmt.Sprintf(
156164
"name=%s&pwd=%s&check=00",
157-
base64.StdEncoding.EncodeToString([]byte(c.serviceClient.user)),
158-
base64.StdEncoding.EncodeToString([]byte(c.serviceClient.pass)),
165+
user,
166+
pass,
159167
)
160168

161169
headers := map[string]string{"Content-Type": "application/x-www-form-urlencoded"}
162170

163171
body, status, err := c.serviceClient.query(ctx, "cgi/login.cgi", http.MethodPost, bytes.NewBufferString(data), headers, 0)
164172
if err != nil {
165-
return errors.Wrap(bmclibErrs.ErrLoginFailed, err.Error())
173+
return err
166174
}
167175

168176
if status != 200 {
169-
return errors.Wrap(bmclibErrs.ErrLoginFailed, strconv.Itoa(status))
177+
return errors.Wrap(ErrUnexpectedStatusCode, strconv.Itoa(status))
178+
}
179+
180+
// Older Supermicro boards return 200 for failed logins
181+
if !bytes.Contains(body, []byte(`url_redirect.cgi?url_name=mainmenu`)) &&
182+
!bytes.Contains(body, []byte(`url_redirect.cgi?url_name=topmenu`)) {
183+
return ErrUnexpectedResponse
170184
}
171185

186+
return nil
187+
}
188+
189+
// Open a connection to a Supermicro BMC using the vendor API.
190+
func (c *Client) Open(ctx context.Context) (err error) {
172191
// called after a session was opened but further login dependencies failed
173192
closeWithError := func(ctx context.Context, err error) error {
174193
_ = c.Close(ctx)
175194
return err
176195
}
177196

178-
if !bytes.Contains(body, []byte(`url_redirect.cgi?url_name=mainmenu`)) &&
179-
!bytes.Contains(body, []byte(`url_redirect.cgi?url_name=topmenu`)) {
180-
return closeWithError(ctx, errors.Wrap(bmclibErrs.ErrLoginFailed, "unexpected response contents"))
197+
// first attempt login with base64 encoded user,pass
198+
if err := c.login(ctx, true); err != nil {
199+
if !errors.Is(err, ErrUnexpectedResponse) && !errors.Is(err, ErrUnexpectedStatusCode) {
200+
return closeWithError(ctx, errors.Wrap(bmclibErrs.ErrLoginFailed, err.Error()))
201+
}
202+
203+
// retry with plain text user, pass
204+
if err2 := c.login(ctx, false); err2 != nil {
205+
return closeWithError(ctx, errors.Wrap(bmclibErrs.ErrLoginFailed, err2.Error()))
206+
}
181207
}
182208

183209
contentsTopMenu, status, err := c.serviceClient.query(ctx, "cgi/url_redirect.cgi?url_name=topmenu", http.MethodGet, nil, nil, 0)

providers/supermicro/supermicro_test.go

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -187,21 +187,13 @@ func TestOpen(t *testing.T) {
187187
},
188188
{
189189
"login error",
190-
"401: failed to login",
190+
"failed to login",
191191
"foo",
192192
"bar",
193193
handlerFuncMap{
194194
"/cgi/login.cgi": func(w http.ResponseWriter, r *http.Request) {
195195
assert.Equal(t, r.Method, http.MethodPost)
196196
assert.Equal(t, r.Header.Get("Content-Type"), "application/x-www-form-urlencoded")
197-
198-
b, err := io.ReadAll(r.Body)
199-
if err != nil {
200-
t.Fatal(err)
201-
}
202-
203-
assert.Equal(t, `name=Zm9v&pwd=YmFy&check=00`, string(b))
204-
205197
response := []byte(`barf`)
206198
w.WriteHeader(401)
207199
_, _ = w.Write(response)

providers/supermicro/x11.go

Lines changed: 53 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import (
1515
"github.com/bmc-toolbox/common"
1616
"github.com/go-logr/logr"
1717
"github.com/pkg/errors"
18+
"github.com/stmcginnis/gofish/oem/smc"
1819
"github.com/stmcginnis/gofish/redfish"
1920
)
2021

@@ -36,6 +37,23 @@ func (c *x11) deviceModel() string {
3637
}
3738

3839
func (c *x11) queryDeviceModel(ctx context.Context) (string, error) {
40+
model, err := c.deviceModelFromFruInfo(ctx)
41+
if err != nil {
42+
// Identify BoardID from Redfish since fru info failed to return the information
43+
model, err2 := c.deviceModelFromBoardID(ctx)
44+
if err2 != nil {
45+
return "", errors.Wrap(err, err2.Error())
46+
}
47+
48+
c.model = model
49+
return model, nil
50+
}
51+
52+
c.model = model
53+
return model, nil
54+
}
55+
56+
func (c *x11) deviceModelFromFruInfo(ctx context.Context) (string, error) {
3957
errBoardPartNumUnknown := errors.New("baseboard part number unknown")
4058
data, err := c.fruInfo(ctx)
4159
if err != nil {
@@ -47,14 +65,46 @@ func (c *x11) queryDeviceModel(ctx context.Context) (string, error) {
4765
}
4866

4967
partNum := strings.TrimSpace(data.Board.PartNum)
50-
5168
if data.Board == nil || partNum == "" {
5269
return "", errors.Wrap(errBoardPartNumUnknown, "baseboard part number empty")
5370
}
5471

55-
c.model = common.FormatProductName(partNum)
72+
return common.FormatProductName(partNum), nil
73+
}
74+
75+
func (c *x11) deviceModelFromBoardID(ctx context.Context) (string, error) {
76+
if err := c.redfishSession(ctx); err != nil {
77+
return "", err
78+
}
79+
80+
chassis, err := c.redfish.Chassis(ctx)
81+
if err != nil {
82+
return "", err
83+
}
84+
85+
var boardID string
86+
for _, ch := range chassis {
87+
smcChassis, err := smc.FromChassis(ch)
88+
if err != nil {
89+
return "", errors.Wrap(ErrBoardIDUnknown, err.Error())
90+
}
91+
92+
if smcChassis.BoardID != "" {
93+
boardID = smcChassis.BoardID
94+
break
95+
}
96+
}
97+
98+
if boardID == "" {
99+
return "", ErrBoardIDUnknown
100+
}
101+
102+
model := common.SupermicroModelFromBoardID(boardID)
103+
if model == "" {
104+
return "", errors.Wrap(ErrModelUnknown, "unable to identify model from board ID: "+boardID)
105+
}
56106

57-
return c.model, nil
107+
return model, nil
58108
}
59109

60110
func (c *x11) fruInfo(ctx context.Context) (*FruInfo, error) {

0 commit comments

Comments
 (0)