diff --git a/cmd/redfishcli/main.go b/cmd/redfishcli/main.go index d7cce7a29b..6f7b772a8f 100644 --- a/cmd/redfishcli/main.go +++ b/cmd/redfishcli/main.go @@ -82,9 +82,11 @@ func getSubcommandParser() (*structarg.ArgumentParser, error) { func bmcJnlp() { type BmcGetOptions struct { - BRAND string `help:"brand of baremetal" choices:"Lenovo|Huawei|HPE|Dell|Supermicro"` + BRAND string `help:"brand of baremetal" choices:"Lenovo|Huawei|HPE|Dell|Supermicro|Dell6|Dell7|Dell9"` Save string `help:"save to file"` Debug bool `help:"turn on debug mode"` + Sku string `help:"sku"` + Model string `help:"model"` } shellutils.R(&BmcGetOptions{}, "bmc-jnlp", "Get Java Console JNLP file", func(args *BmcGetOptions) error { ctx := context.Background() @@ -98,7 +100,13 @@ func bmcJnlp() { case "hp", "hpe": jnlp, err = bmc.GetIloConsoleJNLP(ctx) case "dell", "dell inc.": - jnlp, err = bmc.GetIdracConsoleJNLP(ctx, "", "") + jnlp, err = bmc.GetIdracConsoleJNLP(ctx, args.Sku, args.Model) + case "dell6": + jnlp, err = bmc.GetIdrac6ConsoleJNLP(ctx, args.Sku, args.Model) + case "dell7": + jnlp, err = bmc.GetIdrac7ConsoleJNLP(ctx, args.Sku, args.Model) + case "dell9": + jnlp, err = bmc.GetIdrac9ConsoleJNLP(ctx) case "supermicro": jnlp, err = bmc.GetSupermicroConsoleJNLP(ctx) case "lenovo": diff --git a/pkg/baremetal/manager.go b/pkg/baremetal/manager.go index e440db7ac2..468fda17ac 100644 --- a/pkg/baremetal/manager.go +++ b/pkg/baremetal/manager.go @@ -1959,7 +1959,7 @@ func (b *SBaremetalInstance) GetConsoleJNLP(ctx context.Context) (string, error) case "hp", "hpe": return bmc.GetIloConsoleJNLP(ctx) case "dell", "dell inc.": - return bmc.GetIdracConsoleJNLP(ctx, "", "") + return bmc.GetIdracConsoleJNLP(ctx, b.GetSerialNumber(), b.GetModel()) case "supermicro": return bmc.GetSupermicroConsoleJNLP(ctx) case "lenovo": diff --git a/pkg/util/redfish/bmconsole/idrac.go b/pkg/util/redfish/bmconsole/idrac.go index d95f0e171c..6eee7060bb 100644 --- a/pkg/util/redfish/bmconsole/idrac.go +++ b/pkg/util/redfish/bmconsole/idrac.go @@ -31,6 +31,32 @@ import ( ) func (r *SBMCConsole) GetIdracConsoleJNLP(ctx context.Context, sku, model string) (string, error) { + if len(model) > 0 { + parts := strings.Split(model, " ") + if len(parts) > 1 && parts[1][0] == 'R' && len(parts[1]) >= 4 { + // PownerEdge R730 + switch parts[1][2] { + case '1': + return r.GetIdrac6ConsoleJNLP(ctx, sku, model) + case '2', '3': + return r.GetIdrac7ConsoleJNLP(ctx, sku, model) + default: + return r.GetIdrac9ConsoleJNLP(ctx) + } + } + } + jnlp, err := r.GetIdrac7ConsoleJNLP(ctx, sku, model) + if err == nil { + return jnlp, nil + } + jnlp, err = r.GetIdrac9ConsoleJNLP(ctx) + if err == nil { + return jnlp, nil + } + return r.GetIdrac6ConsoleJNLP(ctx, sku, model) +} + +func (r *SBMCConsole) GetIdrac7ConsoleJNLP(ctx context.Context, sku, model string) (string, error) { loginData := strings.Join([]string{ "user=" + url.QueryEscape(r.username), "password=" + url.QueryEscape(r.password), @@ -42,7 +68,7 @@ func (r *SBMCConsole) GetIdracConsoleJNLP(ctx context.Context, sku, model string // tokenvalue=478be97abdaeb4d454c0418fcca9094d cookies := make(map[string]string) - cookies["-http-session-"] = "" + // cookies["-http-session-"] = "" // first do html login postHdr := http.Header{} @@ -52,14 +78,19 @@ func (r *SBMCConsole) GetIdracConsoleJNLP(ctx context.Context, sku, model string 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] + log.Debugf("Header: %s %s", hdr, loginResp) + if setCookies, ok := hdr["Set-Cookie"]; ok { + for _, cookieHdr := range setCookies { + parts := strings.Split(cookieHdr, ";") + if len(parts) > 0 { + pparts := strings.Split(parts[0], "=") + if len(pparts) > 1 { + cookies[pparts[0]] = pparts[1] + } } } + } else { + // find no cookie } forwardUrlPattern := regexp.MustCompile(`(.*)`) matched := forwardUrlPattern.FindAllStringSubmatch(string(loginResp), -1) @@ -78,13 +109,32 @@ func (r *SBMCConsole) GetIdracConsoleJNLP(ctx context.Context, sku, model string if len(matched) > 0 && len(matched[0]) > 1 { token = matched[0][1] } + log.Debugf("token: %s", token) cookies["tokenvalue"] = token + cookies["batteriesIcon"] = "status_ok" + cookies["fansIcon"] = "status_ok" + cookies["intrusionIcon"] = "status_ok" + cookies["removableFlashMediaIcon"] = "status_ok" + cookies["temperaturesIcon"] = "status_ok" + cookies["voltagesIcon"] = "status_ok" + cookies["powerSuppliesIcon"] = "status_ok" + cookies["sysidledicon"] = "ledIcon grayLed" getHdr := http.Header{} setCookieHeader(getHdr, cookies) + getHdr.Set("Referer", fmt.Sprintf("https://%s/sysSummaryData.html", r.host)) + getHdr.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9") + getHdr.Set("Accept-Encoding", "gzip, deflate, br") + getHdr.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8") - sysStr := url.QueryEscape(fmt.Sprintf("idrac-%s, %s, User: %s", sku, model, r.username)) - path := fmt.Sprintf("viewer.jnlp(%s@0@%s@%d@ST1=%s)", r.host, sysStr, time.Now().UnixNano()/1000000, token) + sysStr := url.QueryEscape(fmt.Sprintf("idrac-%s, %s, slot , User: %s", sku, model, r.username)) + // sysStr := url.QueryEscape(fmt.Sprintf("idrac-%s, %s, slot , 用户: %s", sku, model, r.username)) + var path string + if len(token) > 0 { + path = fmt.Sprintf("viewer.jnlp(%s@0@%s@%d@ST1=%s)", r.host, sysStr, time.Now().UnixNano()/1000000, token) + } else { + path = fmt.Sprintf("viewer.jnlp(%s@0@%s@%d)", r.host, sysStr, time.Now().UnixNano()/1000000) + } _, rspBody, err := r.RawRequest(ctx, httputils.GET, path, getHdr, nil) if err != nil { diff --git a/pkg/util/redfish/bmconsole/idrac6.go b/pkg/util/redfish/bmconsole/idrac6.go new file mode 100644 index 0000000000..fba88e2d5d --- /dev/null +++ b/pkg/util/redfish/bmconsole/idrac6.go @@ -0,0 +1,111 @@ +// 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" + "fmt" + "net/http" + "net/url" + "regexp" + "strings" + "time" + + "yunion.io/x/log" + "yunion.io/x/pkg/errors" + + "yunion.io/x/onecloud/pkg/httperrors" + "yunion.io/x/onecloud/pkg/util/httputils" +) + +func (r *SBMCConsole) GetIdrac6ConsoleJNLP(ctx context.Context, sku, model string) (string, error) { + cookies := make(map[string]string) + + hdr, _, err := r.RawRequest(ctx, httputils.GET, "/start.html", nil, nil) + if err != nil { + return "", errors.Wrap(err, "r.Get start.html") + } + log.Debugf("start.html hdr %s", hdr) + if setCookies, ok := hdr["Set-Cookie"]; ok { + for _, cookieHdr := range setCookies { + parts := strings.Split(cookieHdr, ";") + if len(parts) > 0 { + pparts := strings.Split(parts[0], "=") + if len(pparts) > 1 { + cookies[pparts[0]] = pparts[1] + } + } + } + } else { + // find no cookie + } + + loginData := strings.Join([]string{ + "user=" + url.QueryEscape(r.username), + "password=" + url.QueryEscape(strings.ReplaceAll(r.password, "@", "@040")), + }, "&") + + postHdr := http.Header{} + postHdr.Set("Content-Type", "application/x-www-form-urlencoded") + setCookieHeader(postHdr, cookies) + _, loginResp, err := r.RawRequest(ctx, httputils.POST, "/data/login", postHdr, []byte(loginData)) + if err != nil { + return "", errors.Wrap(err, "r.FormPost Login") + } + log.Debugf("LoginResp: %s", loginResp) + forwardUrlPattern := regexp.MustCompile(`(.*)`) + matched := forwardUrlPattern.FindAllStringSubmatch(string(loginResp), -1) + indexUrlStr := "" + if len(matched) > 0 && len(matched[0]) > 1 { + indexUrlStr = matched[0][1] + } + if len(indexUrlStr) == 0 { + return "", errors.Wrapf(httperrors.ErrBadRequest, "no valid forwardUrl") + } + + tokenPattern := regexp.MustCompile(`ST1=(\w+),ST2=`) + matched = tokenPattern.FindAllStringSubmatch(indexUrlStr, -1) + log.Debugf("%s", matched) + token := "" + if len(matched) > 0 && len(matched[0]) > 1 { + token = matched[0][1] + } + log.Debugf("token: %s", token) + + cookies["batteriesIcon"] = "status_normal" + cookies["fansIcon"] = "status_normal" + cookies["intrusionIcon"] = "status_normal" + cookies["powerSuppliesIcon"] = "status_normal" + cookies["removableFlashMediaIcon"] = "status_normal" + cookies["temperaturesIcon"] = "status_normal" + cookies["voltagesIcon"] = "status_normal" + + getHdr := http.Header{} + setCookieHeader(getHdr, cookies) + getHdr.Set("Referer", fmt.Sprintf("https://%s/sysSummaryData.html", r.host)) + getHdr.Set("Accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9") + getHdr.Set("Accept-Encoding", "gzip, deflate, br") + getHdr.Set("Accept-Language", "zh-CN,zh;q=0.9,en;q=0.8") + + sysStr := url.QueryEscape(fmt.Sprintf("idrac-%s, %s, User:%s", sku, model, r.username)) + // sysStr := url.QueryEscape(fmt.Sprintf("idrac-%s, %s, slot , 用户: %s", sku, model, r.username)) + path := fmt.Sprintf("viewer.jnlp(%s@0@%s@%d)", r.host, sysStr, time.Now().UnixNano()/1000000) + + _, rspBody, err := r.RawRequest(ctx, httputils.GET, path, getHdr, nil) + if err != nil { + return "", errors.Wrapf(err, "r.RawGet %s", path) + } + return string(rspBody), nil +}