diff --git a/pkg/baremetal/manager.go b/pkg/baremetal/manager.go index b1d78a9e6d..9c5060112c 100644 --- a/pkg/baremetal/manager.go +++ b/pkg/baremetal/manager.go @@ -50,6 +50,7 @@ import ( "yunion.io/x/onecloud/pkg/baremetal/utils/disktool" "yunion.io/x/onecloud/pkg/baremetal/utils/ipmitool" raiddrivers "yunion.io/x/onecloud/pkg/baremetal/utils/raid/drivers" + "yunion.io/x/onecloud/pkg/baremetal/utils/uefi" "yunion.io/x/onecloud/pkg/cloudcommon/types" "yunion.io/x/onecloud/pkg/compute/baremetal" "yunion.io/x/onecloud/pkg/hostman/guestfs" @@ -1306,6 +1307,19 @@ func (b *SBaremetalInstance) GetRawIPMIConfig() *types.SIPMIInfo { return &ipmiInfo } +func (b *SBaremetalInstance) GetUEFIInfo() (*types.EFIBootMgrInfo, error) { + key := "uefi_info" + if !b.desc.Contains(key) { + return nil, nil + } + info := new(types.EFIBootMgrInfo) + if err := b.desc.Unmarshal(info, key); err != nil { + + return nil, errors.Wrap(err, "Unmarshal json") + } + return info, nil +} + func (b *SBaremetalInstance) GetAccessIp() string { accessIp, _ := b.desc.GetString("access_ip") return accessIp @@ -1434,13 +1448,16 @@ func (b *SBaremetalInstance) SSHReachable() (bool, error) { return false, errors.NewAggregate(errs) } -func (b *SBaremetalInstance) sshRun(hostCmd string, serverCmd string) ([]string, error) { +func (b *SBaremetalInstance) sshRunWrapper( + hostRun func(hostCli *ssh.Client) ([]string, error), + serverRun func(serverCli *ssh.Client) ([]string, error), +) ([]string, error) { hostCli, err := b.GetHostSSHClient() if err != nil { log.Warningf("Get host ssh client error: %v", err) } else { if hostCli != nil { - return hostCli.RawRun(hostCmd) + return hostRun(hostCli) } } @@ -1448,10 +1465,99 @@ func (b *SBaremetalInstance) sshRun(hostCmd string, serverCmd string) ([]string, if err != nil { return nil, errors.Wrapf(err, "Get baremetal %s server ssh client", b.GetName()) } - return serverCli.RunWithTTY(serverCmd) + return serverRun(serverCli) +} + +func (b *SBaremetalInstance) sshRun(hostCmd string, serverCmd string) ([]string, error) { + return b.sshRunWrapper( + func(hostCli *ssh.Client) ([]string, error) { + return hostCli.RawRun(hostCmd) + }, + func(serverCli *ssh.Client) ([]string, error) { + return serverCli.RunWithTTY(serverCmd) + }, + ) +} + +func (b *SBaremetalInstance) adjustUEFIWrapper(cli *ssh.Client, f func() error) error { + isUEFI, err := uefi.RemoteIsUEFIBoot(cli) + if err != nil { + return errors.Wrap(err, "Check is uefi boot") + } + if !isUEFI { + return nil + } + return f() +} + +func (b *SBaremetalInstance) AdjustUEFICurrentBootOrder(hostCli *ssh.Client) error { + return b.adjustUEFIWrapper(hostCli, func() error { + mgr, err := uefi.NewEFIBootMgrFromRemote(hostCli, false) + if err != nil { + return errors.Wrap(err, "NewEFIBootMgrFromRemote") + } + if err := uefi.RemoteSetCurrentBootAtFirst(hostCli, mgr); err != nil { + return errors.Wrap(err, "Set current boot order at first") + } + return b.SendUEFIInfo(mgr) + }) +} + +func (b *SBaremetalInstance) SendUEFIInfo(mgr *uefi.BootMgr) error { + info, err := mgr.ToEFIBootMgrInfo() + if err != nil { + return err + } + desc := b.desc + uefiData := jsonutils.Marshal(info) + desc.Add(uefiData, "uefi_info") + if err := b.SaveDesc(desc); err != nil { + return errors.Wrap(err, "Save uefi_info") + } + updateData := jsonutils.NewDict() + updateData.Add(uefiData, "uefi_info") + if _, err := modules.Hosts.Update(b.GetClientSession(), b.GetId(), updateData); err != nil { + return errors.Wrap(err, "Update cloud uefi info") + } + return nil +} + +func (b *SBaremetalInstance) adjustServerUEFIBootOrder(srvCli *ssh.Client) error { + return b.adjustUEFIWrapper(srvCli, func() error { + info, err := b.GetUEFIInfo() + if err != nil { + return errors.Wrap(err, "GetUEFIInfo from local desc") + } + if info == nil { + return uefi.RemoteTryToSetPXEBoot(srvCli) + } + if err := uefi.RemoteTryToSetPXEBoot(srvCli); err != nil { + log.Warningf("RemoteTryToSetPXEBoot error: %v", err) + } + _, err = uefi.RemoteSetBootOrderByInfo(srvCli, info.GetPXEEntry()) + return err + }) +} + +func (b *SBaremetalInstance) AdjustUEFIBootOrder() error { + _, err := b.sshRunWrapper( + func(hostCli *ssh.Client) ([]string, error) { + return nil, b.AdjustUEFICurrentBootOrder(hostCli) + }, + func(srvCli *ssh.Client) ([]string, error) { + return nil, b.adjustServerUEFIBootOrder(srvCli) + }, + ) + return err } func (b *SBaremetalInstance) SSHReboot() error { + if !b.HasBMC() { + // try adjust uefi boot order before reboot + if err := b.AdjustUEFIBootOrder(); err != nil { + return errors.Wrap(err, "Adjust uefi boot order") + } + } if _, err := b.sshRun("/sbin/reboot", "sudo shutdown -r now && exit"); err != nil { if !ssh.IsExitMissingError(err) { return errors.Wrap(err, "Try reboot") @@ -2288,7 +2394,9 @@ func (s *SBaremetalServer) DoDiskConfig(term *ssh.Client) error { if err != nil { return fmt.Errorf("CalculateLayout: %v", err) } + log.Errorf("===layouts: %s", jsonutils.Marshal(layouts).PrettyString()) diskConfs := baremetal.GroupLayoutResultsByDriverAdapter(layouts) + log.Errorf("===diskConfs: %s", jsonutils.Marshal(diskConfs).PrettyString()) for _, dConf := range diskConfs { driver := dConf.Driver raidDrv := raiddrivers.GetDriver(driver, term) diff --git a/pkg/baremetal/pxe/dhcp.go b/pkg/baremetal/pxe/dhcp.go index 1399e3bd8b..b6466d88b3 100644 --- a/pkg/baremetal/pxe/dhcp.go +++ b/pkg/baremetal/pxe/dhcp.go @@ -328,23 +328,23 @@ func (req *dhcpRequest) findBaremetalsOfAnyMac(session *mcclient.ClientSession, // createOrUpdateBaremetal create or update baremetal by client MAC func (req *dhcpRequest) createOrUpdateBaremetal(session *mcclient.ClientSession) (jsonutils.JSONObject, error) { - // first try UUID - ret, err := req.findBaremetalsByUuid(session) + // first try mac and is_baremetal=true + ret, err := req.findBaremetalsOfAnyMac(session, true) if err != nil { return nil, err } if len(ret.Data) == 0 { - // try mac and is_baremetal=true - ret, err = req.findBaremetalsOfAnyMac(session, true) + // try mac and host_type=baremetal + ret, err = req.findBaremetalsOfAnyMac(session, false) if err != nil { return nil, err } - if len(ret.Data) == 0 { - // try mac and host_type=baremetal - ret, err = req.findBaremetalsOfAnyMac(session, false) - if err != nil { - return nil, err - } + } + if len(ret.Data) == 0 { + // try UUID + ret, err = req.findBaremetalsByUuid(session) + if err != nil { + return nil, err } } switch len(ret.Data) { diff --git a/pkg/baremetal/tasks/base.go b/pkg/baremetal/tasks/base.go index 4e9de65756..81435429ca 100644 --- a/pkg/baremetal/tasks/base.go +++ b/pkg/baremetal/tasks/base.go @@ -26,7 +26,6 @@ import ( "yunion.io/x/pkg/errors" o "yunion.io/x/onecloud/pkg/baremetal/options" - "yunion.io/x/onecloud/pkg/baremetal/utils/uefi" "yunion.io/x/onecloud/pkg/cloudcommon/object" "yunion.io/x/onecloud/pkg/cloudcommon/types" "yunion.io/x/onecloud/pkg/mcclient" @@ -492,24 +491,6 @@ func (self *SBaremetalPXEBootTaskBase) GetName() string { return "BaremetalPXEBootTaskBase" } -func AdjustUEFIBootOrder(term *ssh.Client) error { - isUEFI, err := uefi.RemoteIsUEFIBoot(term) - if err != nil { - return errors.Wrap(err, "Check baremetal is UEFI boot") - } - - if !isUEFI { - return nil - } - - mgr, err := uefi.NewEFIBootMgrFromRemote(term) - if err != nil { - return errors.Wrap(err, "NewEFIBootMgrFromRemote") - } - - if err := uefi.RemoteSetCurrentBootAtFirst(term, mgr); err != nil { - return errors.Wrap(err, "Set current pxe boot at fist") - } - - return nil +func AdjustUEFIBootOrder(term *ssh.Client, bm IBaremetal) error { + return bm.AdjustUEFICurrentBootOrder(term) } diff --git a/pkg/baremetal/tasks/basedeploy.go b/pkg/baremetal/tasks/basedeploy.go index 3d36865fc6..fbf83cd324 100644 --- a/pkg/baremetal/tasks/basedeploy.go +++ b/pkg/baremetal/tasks/basedeploy.go @@ -22,6 +22,7 @@ import ( "yunion.io/x/pkg/errors" "yunion.io/x/pkg/utils" + "yunion.io/x/onecloud/pkg/baremetal/utils/uefi" "yunion.io/x/onecloud/pkg/cloudcommon/types" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/util/ssh" @@ -30,6 +31,7 @@ import ( type IServerBaseDeployTask interface { IPXEBootTask + RemoveEFIOSEntry() bool DoDeploys(term *ssh.Client) (jsonutils.JSONObject, error) PostDeploys(term *ssh.Client) error } @@ -75,6 +77,10 @@ func (self *SBaremetalServerBaseDeployTask) GetFinishAction() string { return "" } +func (self *SBaremetalServerBaseDeployTask) RemoveEFIOSEntry() bool { + return false +} + func (self *SBaremetalServerBaseDeployTask) DoDeploys(_ *ssh.Client) (jsonutils.JSONObject, error) { return nil, nil } @@ -85,12 +91,19 @@ func (self *SBaremetalServerBaseDeployTask) PostDeploys(_ *ssh.Client) error { func (self *SBaremetalServerBaseDeployTask) OnPXEBoot(ctx context.Context, term *ssh.Client, args interface{}) error { log.Infof("%s called on stage pxeboot, args: %v", self.GetName(), args) + + if self.IServerBaseDeployTask().RemoveEFIOSEntry() { + if err := uefi.RemoteTryRemoveOSBootEntry(term); err != nil { + return errors.Wrap(err, "Remote uefi boot entry") + } + } + result, err := self.IServerBaseDeployTask().DoDeploys(term) if err != nil { return errors.Wrap(err, "Do deploy") } - if err := AdjustUEFIBootOrder(term); err != nil { + if err := AdjustUEFIBootOrder(term, self.Baremetal); err != nil { return errors.Wrap(err, "Adjust UEFI boot order") } diff --git a/pkg/baremetal/tasks/baseprepare.go b/pkg/baremetal/tasks/baseprepare.go index 080108a9e4..0507905c7c 100644 --- a/pkg/baremetal/tasks/baseprepare.go +++ b/pkg/baremetal/tasks/baseprepare.go @@ -137,22 +137,24 @@ func (task *sBaremetalPrepareTask) prepareBaremetalInfo(cli *ssh.Client) (*barem if ipmiInfo == nil { ipmiInfo = &types.SIPMIInfo{} } - if !ipmiInfo.Present && ipmiEnable { + if !ipmiInfo.Verified && !ipmiInfo.Present && ipmiEnable { ipmiInfo.Present = true ipmiInfo.Verified = false } - return &baremetalPrepareInfo{ - sysInfo, - cpuInfo, - dmiCPUInfo, - memInfo, - nicsInfo, - diskInfo, - storageDriver, - ipmiInfo, - isolatedDevicesInfo, - }, nil + prepareInfo := &baremetalPrepareInfo{ + sysInfo: sysInfo, + cpuInfo: cpuInfo, + dmiCpuInfo: dmiCPUInfo, + memInfo: memInfo, + nicsInfo: nicsInfo, + diskInfo: diskInfo, + storageDriver: storageDriver, + ipmiInfo: ipmiInfo, + isolatedDevicesInfo: isolatedDevicesInfo, + } + + return prepareInfo, nil } func (task *sBaremetalPrepareTask) configIPMISetting(cli *ssh.Client, i *baremetalPrepareInfo) error { @@ -327,7 +329,7 @@ func (task *sBaremetalPrepareTask) DoPrepare(cli *ssh.Client) error { log.Errorf("SetNTP fail: %s", err) } - if err = AdjustUEFIBootOrder(cli); err != nil { + if err = AdjustUEFIBootOrder(cli, task.baremetal); err != nil { logclient.AddActionLogWithStartable(task, task.baremetal, logclient.ACT_PREPARE, err, task.userCred, false) return errors.Wrap(err, "Adjust UEFI boot order") } diff --git a/pkg/baremetal/tasks/create.go b/pkg/baremetal/tasks/create.go index c9402a56e6..250c44b2fa 100644 --- a/pkg/baremetal/tasks/create.go +++ b/pkg/baremetal/tasks/create.go @@ -46,6 +46,10 @@ func (self *SBaremetalServerCreateTask) GetName() string { return "BaremetalServerCreateTask" } +func (self *SBaremetalServerCreateTask) RemoveEFIOSEntry() bool { + return true +} + func (self *SBaremetalServerCreateTask) DoDeploys(term *ssh.Client) (jsonutils.JSONObject, error) { // Build raid err := self.Baremetal.GetServer().DoDiskConfig(term) diff --git a/pkg/baremetal/tasks/deploy.go b/pkg/baremetal/tasks/deploy.go index c973369d4f..5fba72129d 100644 --- a/pkg/baremetal/tasks/deploy.go +++ b/pkg/baremetal/tasks/deploy.go @@ -43,6 +43,10 @@ func (self *SBaremetalServerDeployTask) GetName() string { return "BaremetalServerDeployTask" } +func (self *SBaremetalServerDeployTask) RemoveEFIOSEntry() bool { + return false +} + func (self *SBaremetalServerDeployTask) DoDeploys(term *ssh.Client) (jsonutils.JSONObject, error) { return self.Baremetal.GetServer().DoDeploy(term, self.data, false) } diff --git a/pkg/baremetal/tasks/destroy.go b/pkg/baremetal/tasks/destroy.go index 9469d9856e..172f7cf69d 100644 --- a/pkg/baremetal/tasks/destroy.go +++ b/pkg/baremetal/tasks/destroy.go @@ -44,6 +44,10 @@ func (self *SBaremetalServerDestroyTask) GetName() string { return "BaremetalServerDestroyTask" } +func (self *SBaremetalServerDestroyTask) RemoveEFIOSEntry() bool { + return true +} + func (self *SBaremetalServerDestroyTask) DoDeploys(term *ssh.Client) (jsonutils.JSONObject, error) { if err := self.Baremetal.GetServer().DoEraseDisk(term); err != nil { log.Errorf("Delete server do erase disk: %v", err) diff --git a/pkg/baremetal/tasks/interface.go b/pkg/baremetal/tasks/interface.go index 67a98ebd1e..829d6a7486 100644 --- a/pkg/baremetal/tasks/interface.go +++ b/pkg/baremetal/tasks/interface.go @@ -23,6 +23,7 @@ import ( baremetaltypes "yunion.io/x/onecloud/pkg/baremetal/types" "yunion.io/x/onecloud/pkg/cloudcommon/types" "yunion.io/x/onecloud/pkg/mcclient" + "yunion.io/x/onecloud/pkg/util/ssh" ) type IBaremetal interface { @@ -73,6 +74,7 @@ type IBaremetal interface { SSHReachable() (bool, error) SSHReboot() error SSHShutdown() error + AdjustUEFICurrentBootOrder(cli *ssh.Client) error } type IBmManager interface { diff --git a/pkg/baremetal/tasks/rebuild.go b/pkg/baremetal/tasks/rebuild.go index 411b8118d7..59d2eaafc1 100644 --- a/pkg/baremetal/tasks/rebuild.go +++ b/pkg/baremetal/tasks/rebuild.go @@ -45,6 +45,10 @@ func (self *SBaremetalServerRebuildTask) GetName() string { return "BaremetalServerRebuildTask" } +func (self *SBaremetalServerRebuildTask) RemoveEFIOSEntry() bool { + return true +} + func (self *SBaremetalServerRebuildTask) DoDeploys(term *ssh.Client) (jsonutils.JSONObject, error) { parts, err := self.Baremetal.GetServer().DoRebuildRootDisk(term) if err != nil { diff --git a/pkg/baremetal/utils/disktool/disktool.go b/pkg/baremetal/utils/disktool/disktool.go index 5038c39508..9c3b7d24a5 100644 --- a/pkg/baremetal/utils/disktool/disktool.go +++ b/pkg/baremetal/utils/disktool/disktool.go @@ -616,7 +616,24 @@ func (tool *PartitionTool) parseLsDisk(lines []string, driver string) { } minCnt := int(math.Min(float64(len(disks)), float64(len(tool.diskTable[driver])))) for i := 0; i < minCnt; i++ { - tool.diskTable[driver][i].SetInfo(disks[i]) + driverDisk := tool.diskTable[driver][i] + if utils.IsInStringArray(driver, []string{NONRAID_DRIVER, PCIE_DRIVER}) { + // find size matched disk from disks + size := driverDisk.sizeMB + var remoteDisk *types.SDiskInfo + for ri := range disks { + if disks[ri].Size == size { + remoteDisk = disks[ri] + // remove matched disks + disks = append(disks[:ri], disks[ri+1:]...) + break + } + } + driverDisk.SetInfo(remoteDisk) + } else { + // raid disk set by order + driverDisk.SetInfo(disks[i]) + } } } diff --git a/pkg/baremetal/utils/uefi/uefi.go b/pkg/baremetal/utils/uefi/uefi.go index f09b5a19c0..3f53faa145 100644 --- a/pkg/baremetal/utils/uefi/uefi.go +++ b/pkg/baremetal/utils/uefi/uefi.go @@ -22,13 +22,17 @@ import ( "yunion.io/x/log" "yunion.io/x/pkg/errors" + "yunion.io/x/onecloud/pkg/cloudcommon/types" "yunion.io/x/onecloud/pkg/util/regutils2" "yunion.io/x/onecloud/pkg/util/ssh" ) const ( // efibootmgr useage: https://github.com/rhboot/efibootmgr - CMD_EFIBOOTMGR = "/usr/sbin/efibootmgr" + CMD_EFIBOOTMGR = "/usr/sbin/efibootmgr" + SUDO_EFIBOOTMGR = "sudo efibootmgr" + + MAC_KEYWORD = "MAC" ) type BootMgr struct { @@ -60,6 +64,13 @@ type BootEntry struct { IsActive bool } +func getEFIBootMgrCmd(sudo bool) string { + if sudo { + return SUDO_EFIBOOTMGR + } + return CMD_EFIBOOTMGR +} + func ParseEFIBootMGR(input string) (*BootMgr, error) { lines := strings.Split(input, "\n") @@ -213,8 +224,15 @@ func parseEFIBootMGREntry(line string) *BootEntry { } } -func NewEFIBootMgrFromRemote(cli *ssh.Client) (*BootMgr, error) { - cmd := CMD_EFIBOOTMGR +func NewEFIBootMgrFromRemote(cli *ssh.Client, sudo bool) (*BootMgr, error) { + return newEFIBootMgrFromRemote(cli, sudo, true) +} + +func newEFIBootMgrFromRemote(cli *ssh.Client, sudo bool, verbose bool) (*BootMgr, error) { + cmd := getEFIBootMgrCmd(sudo) + if verbose { + cmd = fmt.Sprintf("%s -v", cmd) + } lines, err := cli.RawRun(cmd) if err != nil { return nil, errors.Wrapf(err, "Execute command: %s", cmd) @@ -222,8 +240,8 @@ func NewEFIBootMgrFromRemote(cli *ssh.Client) (*BootMgr, error) { return ParseEFIBootMGR(strings.Join(lines, "\n")) } -func (m *BootMgr) GetCommand() string { - return CMD_EFIBOOTMGR +func (m *BootMgr) GetCommand(sudo bool) string { + return getEFIBootMgrCmd(sudo) } func (m *BootMgr) GetBootCurrent() string { @@ -246,6 +264,15 @@ func (m *BootMgr) GetBootEntry(num string) *BootEntry { return m.entries[num] } +func (m *BootMgr) GetBootEntryByDesc(desc string) *BootEntry { + for _, entry := range m.entries { + if strings.Contains(entry.Description, desc) { + return entry + } + } + return nil +} + func (m *BootMgr) FindBootOrderPos(num string) int { return stringArraryFindItemPos(m.bootOrder, num) } @@ -293,8 +320,12 @@ func (m *BootMgr) MoveBootOrder(num string, pos int) *BootMgr { return m } +func getSetBootOrderArgs(bootOrder []string) string { + return strings.Join(bootOrder, ",") +} + func (m *BootMgr) GetSetBootOrderArgs() string { - return strings.Join(m.bootOrder, ",") + return getSetBootOrderArgs(m.bootOrder) } func RemoteIsUEFIBoot(cli *ssh.Client) (bool, error) { @@ -311,6 +342,45 @@ func RemoteIsUEFIBoot(cli *ssh.Client) (bool, error) { return false, nil } +func convertToEFIBootMgrInfo(info *BootMgr) (*types.EFIBootMgrInfo, error) { + data := &types.EFIBootMgrInfo{ + PxeBootNum: info.GetBootCurrent(), + BootOrder: make([]*types.EFIBootEntry, len(info.GetBootOrder())), + } + for idx, orderNum := range info.GetBootOrder() { + entry := info.GetBootEntry(orderNum) + if entry == nil { + return nil, errors.Errorf("Not found boot entry by %q", orderNum) + } + data.BootOrder[idx] = &types.EFIBootEntry{ + BootNum: entry.BootNum, + Description: entry.Description, + IsActive: entry.IsActive, + } + } + return data, nil +} + +func (mgr *BootMgr) ToEFIBootMgrInfo() (*types.EFIBootMgrInfo, error) { + return convertToEFIBootMgrInfo(mgr) +} + +func (mgr *BootMgr) sortEntryByKeyword(keyword string) *BootMgr { + newOrder := []string{} + oldOrder := []string{} + for _, num := range mgr.bootOrder { + entry := mgr.GetBootEntry(num) + if strings.Contains(entry.Description, keyword) { + newOrder = append(newOrder, num) + } else { + oldOrder = append(oldOrder, num) + } + } + newOrder = append(newOrder, oldOrder...) + mgr.bootOrder = newOrder + return mgr +} + func RemoteSetCurrentBootAtFirst(cli *ssh.Client, mgr *BootMgr) error { curPos := mgr.FindBootOrderPos(mgr.GetBootCurrent()) if curPos == -1 { @@ -318,7 +388,91 @@ func RemoteSetCurrentBootAtFirst(cli *ssh.Client, mgr *BootMgr) error { } // move to first mgr.MoveBootOrder(mgr.GetBootCurrent(), 0) - cmd := fmt.Sprintf("%s -o %s", mgr.GetCommand(), mgr.GetSetBootOrderArgs()) + cmd := fmt.Sprintf("%s -o %s", mgr.GetCommand(false), mgr.GetSetBootOrderArgs()) _, err := cli.Run(cmd) return err } + +func RemoteSetBootOrder(cli *ssh.Client, order []string) error { + cmd := fmt.Sprintf("%s -o %s", SUDO_EFIBOOTMGR, getSetBootOrderArgs(order)) + _, err := cli.RunWithTTY(cmd) + return err +} + +func RemoteSetBootOrderByInfo(cli *ssh.Client, entry *types.EFIBootEntry) (*BootMgr, error) { + mgr, err := newEFIBootMgrFromRemote(cli, true, true) + if err != nil { + return nil, err + } + + curEntry := mgr.GetBootEntryByDesc(entry.Description) + if curEntry == nil { + return nil, errors.Wrapf(err, "Not found remote boot entry by %q", entry.Description) + } + + mgr = mgr.sortEntryByKeyword(curEntry.Description) + return mgr, RemoteSetBootOrder(cli, mgr.GetBootOrder()) +} + +func RemoteTryToSetPXEBoot(cli *ssh.Client) error { + mgr, err := newEFIBootMgrFromRemote(cli, true, true) + if err != nil { + return err + } + + mgr = mgr.sortEntryByKeyword(MAC_KEYWORD) + return RemoteSetBootOrder(cli, mgr.GetBootOrder()) +} + +func remoteISUEFIBootWrap(cli *ssh.Client, f func(*ssh.Client) error) error { + isUEFI, err := RemoteIsUEFIBoot(cli) + if err != nil { + return errors.Wrap(err, "Check is UEFI boot") + } + if !isUEFI { + return nil + } + return f(cli) +} + +func RemoteTryRemoveOSBootEntry(hostCli *ssh.Client) error { + return remoteISUEFIBootWrap(hostCli, remoteTryRemoveOSBootEntry) +} + +func remoteTryRemoveOSBootEntry(hostCli *ssh.Client) error { + mgr, err := newEFIBootMgrFromRemote(hostCli, false, true) + if err != nil { + return err + } + + // TODO: find other ways to decide whether entry is OS boot + osKeywords := []string{ + "linux", + "centos", + "ubuntu", + "windows", + "grub", + } + + isOsEntry := func(desc string) bool { + for _, key := range osKeywords { + desc := strings.ToLower(desc) + if strings.Contains(desc, key) { + return true + } + } + return false + } + + for _, entry := range mgr.entries { + if !isOsEntry(entry.Description) { + continue + } + // delete entry and remove it from BootOrder + cmd := fmt.Sprintf("%s -b %s -B", mgr.GetCommand(false), entry.BootNum) + if _, err := hostCli.Run(cmd); err != nil { + return errors.Wrapf(err, "remove boot entry: %s", entry.Description) + } + } + return nil +} diff --git a/pkg/cloudcommon/types/uefi.go b/pkg/cloudcommon/types/uefi.go new file mode 100644 index 0000000000..fa88a40cb7 --- /dev/null +++ b/pkg/cloudcommon/types/uefi.go @@ -0,0 +1,35 @@ +// 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 types + +type EFIBootMgrInfo struct { + PxeBootNum string `json:"pxe_boot_num"` + BootOrder []*EFIBootEntry `json:"boot_order"` +} + +type EFIBootEntry struct { + BootNum string `json:"boot_num"` + Description string `json:"description"` + IsActive bool `json:"is_active"` +} + +func (info *EFIBootMgrInfo) GetPXEEntry() *EFIBootEntry { + for _, e := range info.BootOrder { + if e.BootNum == info.PxeBootNum { + return e + } + } + return nil +} diff --git a/pkg/compute/baremetal/diskconfig_test.go b/pkg/compute/baremetal/diskconfig_test.go index aefa8b0f09..5b796f17a3 100644 --- a/pkg/compute/baremetal/diskconfig_test.go +++ b/pkg/compute/baremetal/diskconfig_test.go @@ -21,6 +21,9 @@ import ( "github.com/stretchr/testify/assert" + "yunion.io/x/jsonutils" + "yunion.io/x/log" + api "yunion.io/x/onecloud/pkg/apis/compute" ) @@ -1277,6 +1280,33 @@ var ( Index: 0, }, } + + pcie2Storages = []*BaremetalStorage{ + { + Driver: DISK_DRIVER_LINUX, + Dev: "sda", + Rotate: true, + Size: 3815447, + Adapter: 0, + Index: 0, + }, + { + Driver: DISK_DRIVER_PCIE, + Dev: "nvme0n1", + Rotate: false, + Size: 1953514, + Adapter: 0, + Index: 0, + }, + { + Driver: DISK_DRIVER_PCIE, + Dev: "nvme1n1", + Rotate: false, + Size: 122104, + Adapter: 0, + Index: 0, + }, + } ) func TestPCIEStoragesAllocable(t *testing.T) { @@ -1331,3 +1361,34 @@ func TestPCIEStoragesAllocable(t *testing.T) { t.Errorf("Disk not allocable") } } + +func Test2PCIEStoragesAllocable(t *testing.T) { + adapter0 := 0 + confs := []*api.BaremetalDiskConfig{ + { + Adapter: &adapter0, + Conf: DISK_CONF_NONE, + Count: 0, + Driver: DISK_DRIVER_PCIE, + Range: []int64{1}, + Type: DISK_TYPE_SSD, + }, + } + + layouts, err := CalculateLayout(confs, pcie2Storages) + if err != nil { + t.Errorf("CalculateLayout error: %v", err) + return + } + log.Errorf("layouts: %s", jsonutils.Marshal(layouts)) + disks := []*api.DiskConfig{ + { + Backend: api.STORAGE_LOCAL, + Driver: "scsi", + SizeMb: -1, + }, + } + if ok := IsDisksAllocable(layouts, disks); !ok { + t.Errorf("Disk not allocable") + } +} diff --git a/pkg/compute/models/hosts.go b/pkg/compute/models/hosts.go index cf59158a53..e60d8e31f4 100644 --- a/pkg/compute/models/hosts.go +++ b/pkg/compute/models/hosts.go @@ -188,6 +188,9 @@ type SHost struct { // IPv4地址,作为么有云vpc访问外网时的网关 OvnMappedIpAddr string `width:"16" charset:"ascii" nullable:"true" list:"user"` + + // UEFI详情 + UefiInfo jsonutils.JSONObject `nullable:"true" get:"domain" update:"domain" create:"domain_optional"` } func (manager *SHostManager) GetContextManagers() [][]db.IModelManager { @@ -3530,6 +3533,10 @@ func (self *SHost) PostUpdate(ctx context.Context, userCred mcclient.TokenCreden log.Errorf("baremetal host %s update related server %s spec error: %v", self.GetName(), guest.GetName(), err) } } + + if err := self.startSyncConfig(ctx, userCred, "", true); err != nil { + log.Errorf("start sync host %q config after updated", self.GetName()) + } } func (self *SHost) UpdateDnsRecords(isAdd bool) { @@ -3943,6 +3950,17 @@ func (self *SHost) HasBMC() bool { return false } +func (self *SHost) IsUEFIBoot() bool { + info, _ := self.GetUEFIInfo() + if info == nil { + return false + } + if len(info.PxeBootNum) == 0 { + return false + } + return true +} + func (self *SHost) isRedfishCapable() bool { ipmiInfo, _ := self.GetIpmiInfo() if ipmiInfo.Verified && ipmiInfo.RedfishApi { @@ -5627,6 +5645,17 @@ func (host *SHost) GetIpmiInfo() (types.SIPMIInfo, error) { return info, nil } +func (host *SHost) GetUEFIInfo() (*types.EFIBootMgrInfo, error) { + if host.UefiInfo == nil { + return nil, nil + } + info := new(types.EFIBootMgrInfo) + if err := host.UefiInfo.Unmarshal(info); err != nil { + return nil, errors.Wrap(err, "host.UefiInfo.Unmarshal") + } + return info, nil +} + func (self *SHost) AllowGetDetailsJnlp(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject) bool { return db.IsAdminAllowGetSpec(userCred, self, "jnlp") } @@ -5728,7 +5757,13 @@ func (self *SHost) PerformSyncConfig(ctx context.Context, userCred mcclient.Toke } func (self *SHost) StartSyncConfig(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string) error { - task, err := taskman.TaskManager.NewTask(ctx, "BaremetalSyncConfigTask", self, userCred, nil, parentTaskId, "", nil) + return self.startSyncConfig(ctx, userCred, parentTaskId, false) +} + +func (self *SHost) startSyncConfig(ctx context.Context, userCred mcclient.TokenCredential, parentTaskId string, noStatus bool) error { + data := jsonutils.NewDict() + data.Add(jsonutils.NewBool(noStatus), "not_sync_status") + task, err := taskman.TaskManager.NewTask(ctx, "BaremetalSyncConfigTask", self, userCred, data, parentTaskId, "", nil) if err != nil { return err } diff --git a/pkg/compute/tasks/baremetal_sync_config_task.go b/pkg/compute/tasks/baremetal_sync_config_task.go index 5f5f238c39..a9412e27a8 100644 --- a/pkg/compute/tasks/baremetal_sync_config_task.go +++ b/pkg/compute/tasks/baremetal_sync_config_task.go @@ -54,9 +54,14 @@ func (self *BaremetalSyncConfigTask) DoSyncConfig(ctx context.Context, baremetal } func (self *BaremetalSyncConfigTask) OnSyncConfigComplete(ctx context.Context, baremetal *models.SHost, body jsonutils.JSONObject) { + logclient.AddActionLogWithStartable(self, baremetal, logclient.ACT_SYNC_CONF, "", self.UserCred, true) + notSyncStatus, _ := self.Params.Bool("not_sync_status") + if notSyncStatus { + self.SetStageComplete(ctx, nil) + return + } self.SetStage("OnSyncstatusComplete", nil) baremetal.StartSyncstatus(ctx, self.UserCred, self.GetTaskId()) - logclient.AddActionLogWithStartable(self, baremetal, logclient.ACT_SYNC_CONF, "", self.UserCred, true) } func (self *BaremetalSyncConfigTask) OnSyncstatusComplete(ctx context.Context, baremetal *models.SHost, body jsonutils.JSONObject) { diff --git a/pkg/hostman/guestfs/fsdriver/linux.go b/pkg/hostman/guestfs/fsdriver/linux.go index 7db13e3c18..a714aad2ab 100644 --- a/pkg/hostman/guestfs/fsdriver/linux.go +++ b/pkg/hostman/guestfs/fsdriver/linux.go @@ -596,6 +596,10 @@ func (d *sDebianLikeRootFs) DeployNetworkingScripts(rootFs IDiskPartition, nics if mainNic != nil { mainIp = mainNic.Ip } + + var systemdResolveConfig strings.Builder + dnss := []string{} + domains := []string{} for i := range allNics { nicDesc := allNics[i] cmds.WriteString(fmt.Sprintf("auto %s\n", nicDesc.Name)) @@ -628,8 +632,10 @@ func (d *sDebianLikeRootFs) DeployNetworkingScripts(rootFs IDiskPartition, nics dnslist := netutils2.GetNicDns(nicDesc) if len(dnslist) > 0 { cmds.WriteString(fmt.Sprintf(" dns-nameservers %s\n", strings.Join(dnslist, " "))) + dnss = append(dnss, dnslist...) if len(nicDesc.Domain) > 0 { cmds.WriteString(fmt.Sprintf(" dns-search %s\n", nicDesc.Domain)) + domains = append(domains, nicDesc.Domain) } } if len(nicDesc.TeamingSlaves) > 0 { @@ -644,6 +650,19 @@ func (d *sDebianLikeRootFs) DeployNetworkingScripts(rootFs IDiskPartition, nics cmds.WriteString("\n") } } + + if len(dnss) != 0 { + systemdResolveConfig.WriteString("[Resolve]\n") + systemdResolveConfig.WriteString(fmt.Sprintf("DNS=%s\n", strings.Join(dnss, " "))) + if len(domains) != 0 { + systemdResolveConfig.WriteString(fmt.Sprintf("Domains=%s\n", strings.Join(domains, " "))) + } + systemdResolveFn := "/etc/systemd/resolved.conf" + content := systemdResolveConfig.String() + if err := rootFs.FilePutContents(systemdResolveFn, content, false, false); err != nil { + log.Warningf("Put %s to %s error: %v", content, systemdResolveFn, err) + } + } log.Debugf("%s", cmds.String()) return rootFs.FilePutContents(fn, cmds.String(), false, false) }