mirror of
https://hubproxy.babadafafafafa.cn/https://github.com/yunionio/cloudpods.git
synced 2026-09-20 08:03:53 +08:00
feat(baremetal): support IPMI RMCP+ cipher suite probe and persist (#25239)
Auto-detect working ipmitool -C suites (0/3/17) and store cipher_suite on host IPMI config for subsequent connections.
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
FROM registry.cn-beijing.aliyuncs.com/yunionio/baremetal-base:v0.3.9-20251215.0
|
||||
FROM registry.cn-beijing.aliyuncs.com/yunionio/baremetal-base:v0.3.9-20260724.0
|
||||
|
||||
LABEL maintainer="Zexi Li <lizexi@yunionyun.com>"
|
||||
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
#RUN yum install -y https://iso.yunion.cn/vm-images/baremetal-pxerom-1.1.0-21092209.x86_64.rpm
|
||||
#RUN yum install -y http://192.168.23.50:8083/baremetal-pxerom-1.1.0-21092209.x86_64.rpm
|
||||
FROM registry.cn-beijing.aliyuncs.com/yunionio/yunionos:v4.0.0-20251201.0 as yunionos
|
||||
FROM registry.cn-beijing.aliyuncs.com/yunionio/yunionos:v4.0.0-20251203.0 as yunionos
|
||||
|
||||
FROM centos:8 as grub-stage
|
||||
FROM registry.cn-beijing.aliyuncs.com/cloudpods/centos:8 as grub-stage
|
||||
|
||||
RUN sed -i 's/mirrorlist/#mirrorlist/g' /etc/yum.repos.d/CentOS-*
|
||||
RUN sed -i 's|#baseurl=http://mirror.centos.org|baseurl=http://vault.centos.org|g' /etc/yum.repos.d/CentOS-*
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
REGISTRY ?= "registry.cn-beijing.aliyuncs.com/yunionio"
|
||||
DOCKER_BUILD = docker build -t $(REGISTRY)
|
||||
DOCKER_BUILDX = docker buildx build --platform linux/arm64,linux/amd64,linux/riscv64 --push -t $(REGISTRY)
|
||||
DOCKER_BUILDX_BM = docker buildx build --platform linux/arm64,linux/amd64 --push -t $(REGISTRY)
|
||||
|
||||
|
||||
debian10-base:
|
||||
@@ -40,7 +41,7 @@ WEBCONSOLE_BASE_VERSION_3-22-2 = 3.22.2-1
|
||||
webconsole-base:
|
||||
$(DOCKER_BUILDX)/webconsole-base:$(WEBCONSOLE_BASE_VERSION_3-22-2) -f ./Dockerfile.webconsole-base .
|
||||
|
||||
BAREMETAL_BASE_VERSION = v0.3.9-20251112.1
|
||||
BAREMETAL_BASE_VERSION = v0.3.9-20260724.0
|
||||
|
||||
FEDORA_RISCV64_VERSION = 42
|
||||
fedora-riscv64-base:
|
||||
@@ -53,8 +54,9 @@ baremetal-base-riscv: fedora-riscv64-base
|
||||
$(DOCKER_BUILDX)/baremetal-base:$(BAREMETAL_BASE_VERSION) -f ./Dockerfile.baremetal-base-riscv .
|
||||
#docker push $(REGISTRY)/baremetal-base:$(BAREMETAL_BASE_VERSION)
|
||||
|
||||
# TODO: support riscv64 for baremetal-base
|
||||
baremetal-base:
|
||||
$(DOCKER_BUILDX)/baremetal-base:$(BAREMETAL_BASE_VERSION) -f ./Dockerfile.baremetal-base .
|
||||
$(DOCKER_BUILDX_BM)/baremetal-base:$(BAREMETAL_BASE_VERSION) -f ./Dockerfile.baremetal-base .
|
||||
#docker push $(REGISTRY)/baremetal-base:$(BAREMETAL_BASE_VERSION)
|
||||
|
||||
TORRENT_VERSION = 20210815.0
|
||||
|
||||
@@ -81,7 +81,7 @@ func newExecutor(options *BaseOptions) (ipmitool.IPMIExecutor, error) {
|
||||
if options.Port > 0 {
|
||||
port = options.Port
|
||||
}
|
||||
return ipmitool.NewLanPlusIPMIWithPort(options.HOST, options.User, options.PASSWD, port), nil
|
||||
return ipmitool.NewLanPlusIPMIWithPort(options.HOST, options.User, options.PASSWD, port)
|
||||
}
|
||||
return nil, fmt.Errorf("Unsupported mode: %s", options.MODE)
|
||||
}
|
||||
|
||||
@@ -500,6 +500,8 @@ type HostIpmiAttributes struct {
|
||||
IpmiPresent *bool `json:"ipmi_present"`
|
||||
// lan channel
|
||||
IpmiLanChannel *uint8 `json:"ipmi_lan_channel"`
|
||||
// RMCP+ cipher suite for ipmitool -C
|
||||
IpmiCipherSuite *int `json:"ipmi_cipher_suite"`
|
||||
// verified
|
||||
IpmiVerified *bool `json:"ipmi_verified"`
|
||||
// Redfish API support
|
||||
|
||||
@@ -205,7 +205,10 @@ func handleBaremetalValidateIPMI() appsrv.FilterHandler {
|
||||
if redfishCli == nil {
|
||||
resp.IsRedfishSupported = false
|
||||
// use ipmitool to validate
|
||||
tool := ipmitool.NewLanPlusIPMI(input.Ip, input.Username, input.Password)
|
||||
tool, err := ipmitool.NewLanPlusIPMI(input.Ip, input.Username, input.Password)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "NewLanPlusIPMI")
|
||||
}
|
||||
info, err := ipmitool.GetSysInfo(tool)
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "GetSysInfo by ipmitool")
|
||||
|
||||
@@ -498,7 +498,10 @@ func (m *SBaremetalManager) checkSshInfo(input *BmRegisterInput) (*ssh.Client, e
|
||||
}
|
||||
|
||||
func (m *SBaremetalManager) checkIpmiInfo(ctx context.Context, username, password, ipAddr string) (uint8, net.HardwareAddr, error) {
|
||||
lanPlusTool := ipmitool.NewLanPlusIPMI(ipAddr, username, password)
|
||||
lanPlusTool, err := ipmitool.NewLanPlusIPMI(ipAddr, username, password)
|
||||
if err != nil {
|
||||
return 0, nil, errors.Wrap(err, "NewLanPlusIPMI")
|
||||
}
|
||||
sysInfo, err := ipmitool.GetSysInfo(lanPlusTool)
|
||||
if err != nil {
|
||||
return 0, nil, errors.Wrap(err, "GetSysInfo")
|
||||
@@ -1823,7 +1826,12 @@ func (b *SBaremetalInstance) GetIPMITool() *ipmitool.LanPlusIPMI {
|
||||
log.Debugf("GetIPMIConfig is nil")
|
||||
return nil
|
||||
}
|
||||
return ipmitool.NewLanPlusIPMI(conf.IpAddr, conf.Username, conf.Password)
|
||||
tool, err := ipmitool.NewLanPlusIPMIWithCipher(conf.IpAddr, conf.Username, conf.Password, 623, conf.CipherSuite)
|
||||
if err != nil {
|
||||
log.Errorf("NewLanPlusIPMIWithCipher for %s: %v", conf.IpAddr, err)
|
||||
return nil
|
||||
}
|
||||
return tool
|
||||
}
|
||||
|
||||
func (b *SBaremetalInstance) isRedfishCapable() bool {
|
||||
|
||||
@@ -537,7 +537,11 @@ func (task *sBaremetalPrepareTask) tryLocalIpmiAddr(ctx context.Context, sshIPMI
|
||||
log.Errorf("Failed to get lan config after %d tries", tried)
|
||||
return false
|
||||
}
|
||||
rmcpIPMI := ipmitool.NewLanPlusIPMI(tryAddr, ipmiUser, ipmiPasswd)
|
||||
rmcpIPMI, err := ipmitool.NewLanPlusIPMI(tryAddr, ipmiUser, ipmiPasswd)
|
||||
if err != nil {
|
||||
log.Errorf("NewLanPlusIPMI for %s: %v", tryAddr, err)
|
||||
return false
|
||||
}
|
||||
for tried = 0; tried < maxTries; tried += 1 {
|
||||
conf2, err := ipmitool.GetLanConfig(rmcpIPMI, lanChannel)
|
||||
if err != nil {
|
||||
|
||||
@@ -85,7 +85,10 @@ func (self *SBaremetalIpmiProbeTask) DoIpmiProbe(ctx context.Context, args inter
|
||||
// else, redfish call fails, try IPMI
|
||||
} */
|
||||
log.Warningf("BMC not redfish-compatible for IPMI: %s, use raw probe", ipmiInfo.IpAddr)
|
||||
ipmiTool := ipmitool.NewLanPlusIPMI(ipmiInfo.IpAddr, ipmiInfo.Username, ipmiInfo.Password)
|
||||
ipmiTool, err := ipmitool.NewLanPlusIPMI(ipmiInfo.IpAddr, ipmiInfo.Username, ipmiInfo.Password)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "NewLanPlusIPMI")
|
||||
}
|
||||
return self.doRawIpmiProbe(ctx, ipmiTool)
|
||||
}
|
||||
|
||||
@@ -254,6 +257,9 @@ func (self *SBaremetalIpmiProbeTask) doRawIpmiProbe(ctx context.Context, cli ipm
|
||||
ipmiInfo.CdromBoot = false
|
||||
ipmiInfo.PxeBoot = o.Options.EnablePxeBoot
|
||||
ipmiInfo.LanChannel = channel
|
||||
if lanPlus, ok := cli.(*ipmitool.LanPlusIPMI); ok {
|
||||
ipmiInfo.CipherSuite = lanPlus.GetCipherSuite()
|
||||
}
|
||||
updateData := jsonutils.Marshal(updateInfo)
|
||||
updateData.(*jsonutils.JSONDict).Update(ipmiInfo.ToPrepareParams())
|
||||
_, err = modules.Hosts.Update(self.Baremetal.GetClientSession(), self.Baremetal.GetId(), updateData)
|
||||
|
||||
@@ -89,31 +89,60 @@ func (ipmi *SSHIPMI) ExecuteCommand(args ...string) ([]string, error) {
|
||||
return ipmi.sshClient.Run(cmd.String())
|
||||
}
|
||||
|
||||
// DefaultCipherSuites is the probe order for RMCP+ cipher suites.
|
||||
// 0 means do not pass -C (ipmitool default); 3 and 17 are common BMC requirements.
|
||||
var DefaultCipherSuites = []int{0, 3, 17}
|
||||
|
||||
type LanPlusIPMI struct {
|
||||
IPMIParser
|
||||
host string
|
||||
user string
|
||||
password string
|
||||
port int
|
||||
host string
|
||||
user string
|
||||
password string
|
||||
port int
|
||||
cipherSuite int // 0 = no -C; >0 = pass -C N
|
||||
cipherResolved bool // true after Ensure/Detect or constructed with known suite > 0
|
||||
}
|
||||
|
||||
func NewLanPlusIPMI(host, user, password string) *LanPlusIPMI {
|
||||
func NewLanPlusIPMI(host, user, password string) (*LanPlusIPMI, error) {
|
||||
return NewLanPlusIPMIWithPort(host, user, password, 623)
|
||||
}
|
||||
|
||||
func NewLanPlusIPMIWithPort(host, user, password string, port int) *LanPlusIPMI {
|
||||
return &LanPlusIPMI{
|
||||
host: host,
|
||||
user: user,
|
||||
password: password,
|
||||
port: port,
|
||||
func NewLanPlusIPMIWithPort(host, user, password string, port int) (*LanPlusIPMI, error) {
|
||||
return NewLanPlusIPMIWithCipher(host, user, password, port, 0)
|
||||
}
|
||||
|
||||
func NewLanPlusIPMIWithCipher(host, user, password string, port, cipherSuite int) (*LanPlusIPMI, error) {
|
||||
ipmi := &LanPlusIPMI{
|
||||
host: host,
|
||||
user: user,
|
||||
password: password,
|
||||
port: port,
|
||||
cipherSuite: cipherSuite,
|
||||
}
|
||||
// Known non-default suite from persisted config: skip re-detect.
|
||||
if cipherSuite > 0 {
|
||||
ipmi.cipherResolved = true
|
||||
return ipmi, nil
|
||||
}
|
||||
if err := ipmi.ensureCipherSuite(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ipmi, nil
|
||||
}
|
||||
|
||||
func (ipmi *LanPlusIPMI) GetMode() string {
|
||||
return "rmcp"
|
||||
}
|
||||
|
||||
func (ipmi *LanPlusIPMI) SetCipherSuite(suite int) {
|
||||
ipmi.cipherSuite = suite
|
||||
ipmi.cipherResolved = true
|
||||
}
|
||||
|
||||
func (ipmi *LanPlusIPMI) GetCipherSuite() int {
|
||||
return ipmi.cipherSuite
|
||||
}
|
||||
|
||||
func (ipmi *LanPlusIPMI) GetCommand(args ...string) (*procutils.Command, context.CancelFunc) {
|
||||
nArgs := []string{
|
||||
"-I", "lanplus", "-H", ipmi.host,
|
||||
@@ -121,6 +150,9 @@ func (ipmi *LanPlusIPMI) GetCommand(args ...string) (*procutils.Command, context
|
||||
"-U", ipmi.user,
|
||||
"-P", ipmi.password,
|
||||
}
|
||||
if ipmi.cipherSuite > 0 {
|
||||
nArgs = append(nArgs, "-C", strconv.Itoa(ipmi.cipherSuite))
|
||||
}
|
||||
nArgs = append(nArgs, args...)
|
||||
ctx, cancel := context.WithTimeout(context.Background(), ipmi.GetDefaultTimeout())
|
||||
return procutils.NewCommandContext(ctx, "ipmitool", nArgs...), cancel
|
||||
@@ -137,6 +169,34 @@ func (ipmi *LanPlusIPMI) ExecuteCommand(args ...string) ([]string, error) {
|
||||
return ssh.ParseOutput(out), nil
|
||||
}
|
||||
|
||||
// DetectCipherSuite tries DefaultCipherSuites with a single chassis power status each.
|
||||
// On success it sets the working suite on the receiver and returns it.
|
||||
func (ipmi *LanPlusIPMI) DetectCipherSuite() (int, error) {
|
||||
var errs []error
|
||||
for _, suite := range DefaultCipherSuites {
|
||||
ipmi.cipherSuite = suite
|
||||
ipmi.cipherResolved = false
|
||||
_, err := ipmi.ExecuteCommand("chassis", "power", "status")
|
||||
if err == nil {
|
||||
ipmi.SetCipherSuite(suite)
|
||||
log.Infof("[LanPlusIPMI] detected cipher suite %d for %s", suite, ipmi.host)
|
||||
return suite, nil
|
||||
}
|
||||
errs = append(errs, errors.Wrapf(err, "cipher suite %d", suite))
|
||||
log.Debugf("[LanPlusIPMI] cipher suite %d failed for %s: %v", suite, ipmi.host, err)
|
||||
}
|
||||
return 0, errors.Wrapf(errors.NewAggregate(errs), "detect cipher suite for %s", ipmi.host)
|
||||
}
|
||||
|
||||
// ensureCipherSuite uses a known suite when already resolved; otherwise runs DetectCipherSuite.
|
||||
func (ipmi *LanPlusIPMI) ensureCipherSuite() error {
|
||||
if ipmi.cipherResolved {
|
||||
return nil
|
||||
}
|
||||
_, err := ipmi.DetectCipherSuite()
|
||||
return err
|
||||
}
|
||||
|
||||
func GetSysGuid(exector IPMIExecutor) string {
|
||||
args := []string{"mc", "guid"}
|
||||
// args := []string{"raw", "0x06", "0x37"}
|
||||
|
||||
@@ -24,15 +24,16 @@ const (
|
||||
)
|
||||
|
||||
type SIPMIInfo struct {
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
IpAddr string `json:"ip_addr,omitempty"`
|
||||
Present bool `json:"present,omitempty"`
|
||||
LanChannel uint8 `json:"lan_channel,omitzero"`
|
||||
Verified bool `json:"verified,omitfalse"`
|
||||
RedfishApi bool `json:"redfish_api,omitfalse"`
|
||||
CdromBoot bool `json:"cdrom_boot,omitfalse"`
|
||||
PxeBoot bool `json:"pxe_boot,omitfalse"`
|
||||
Username string `json:"username,omitempty"`
|
||||
Password string `json:"password,omitempty"`
|
||||
IpAddr string `json:"ip_addr,omitempty"`
|
||||
Present bool `json:"present,omitempty"`
|
||||
LanChannel uint8 `json:"lan_channel,omitzero"`
|
||||
CipherSuite int `json:"cipher_suite,omitzero"`
|
||||
Verified bool `json:"verified,omitfalse"`
|
||||
RedfishApi bool `json:"redfish_api,omitfalse"`
|
||||
CdromBoot bool `json:"cdrom_boot,omitfalse"`
|
||||
PxeBoot bool `json:"pxe_boot,omitfalse"`
|
||||
}
|
||||
|
||||
func (info SIPMIInfo) ToPrepareParams() jsonutils.JSONObject {
|
||||
@@ -48,6 +49,9 @@ func (info SIPMIInfo) ToPrepareParams() jsonutils.JSONObject {
|
||||
}
|
||||
data.Add(jsonutils.NewBool(info.Present), "ipmi_present")
|
||||
data.Add(jsonutils.NewInt(int64(info.LanChannel)), "ipmi_lan_channel")
|
||||
if info.CipherSuite > 0 {
|
||||
data.Add(jsonutils.NewInt(int64(info.CipherSuite)), "ipmi_cipher_suite")
|
||||
}
|
||||
if info.Verified {
|
||||
data.Add(jsonutils.JSONTrue, "ipmi_verified")
|
||||
}
|
||||
|
||||
@@ -5077,6 +5077,9 @@ func fetchIpmiInfo(data api.HostIpmiAttributes, hostId string) (types.SIPMIInfo,
|
||||
if data.IpmiLanChannel != nil {
|
||||
info.LanChannel = *data.IpmiLanChannel
|
||||
}
|
||||
if data.IpmiCipherSuite != nil {
|
||||
info.CipherSuite = *data.IpmiCipherSuite
|
||||
}
|
||||
if data.IpmiVerified != nil {
|
||||
info.Verified = *data.IpmiVerified
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user