mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-19 10:46:58 +08:00
Merge pull request #11205 from zexi/feat/bm-no-bmc-management
feat(baremetal): support management of none BMC host
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
FROM registry.cn-beijing.aliyuncs.com/yunionio/baremetal-base:v0.2
|
||||
FROM registry.cn-beijing.aliyuncs.com/yunionio/baremetal-base:v0.3
|
||||
|
||||
MAINTAINER "Zexi Li <lizexi@yunionyun.com>"
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
FROM registry.cn-beijing.aliyuncs.com/yunionio/centos-build:1.1-4 as build
|
||||
RUN yum install -y https://iso.yunion.cn/3.6/rpms/packages/host/baremetal-pxerom-1.1.0-20072810.x86_64.rpm
|
||||
FROM --platform=linux/amd64 registry.cn-beijing.aliyuncs.com/yunionio/centos-build:1.1-4 as build
|
||||
RUN yum install -y https://iso.yunion.cn/vm-images/baremetal-pxerom-1.1.0-21060506.x86_64.rpm
|
||||
|
||||
FROM frolvlad/alpine-glibc:glibc-2.28
|
||||
FROM registry.cn-beijing.aliyuncs.com/yunionio/onecloud-base:v0.2
|
||||
|
||||
MAINTAINER "Yaoqi Wan <wanyaoqi@yunionyun.com>"
|
||||
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
REGISTRY ?= "registry.cn-beijing.aliyuncs.com/yunionio"
|
||||
|
||||
DOCKER_BUILD = docker build -t $(REGISTRY)
|
||||
DOCKER_BUILDX = docker buildx build --platform linux/arm64,linux/amd64 --push -t $(REGISTRY)
|
||||
|
||||
debian10-base:
|
||||
docker buildx build --platform linux/arm64,linux/amd64 --push \
|
||||
-t registry.cn-beijing.aliyuncs.com/yunionio/debian10-base:1.0 -f ./Dockerfile.debian-base .
|
||||
@@ -5,3 +10,9 @@ debian10-base:
|
||||
climc-base:
|
||||
docker buildx build --platform linux/arm64,linux/amd64 --push \
|
||||
-t registry.cn-beijing.aliyuncs.com/yunionio/climc-base:$(VERSION) -f ./Dockerfile.climc-base .
|
||||
|
||||
BAREMETAL_BASE_VERSION = v0.3
|
||||
|
||||
baremetal-base:
|
||||
$(DOCKER_BUILD)/baremetal-base:$(BAREMETAL_BASE_VERSION) -f ./Dockerfile.baremetal-base .
|
||||
docker push $(REGISTRY)/baremetal-base:$(BAREMETAL_BASE_VERSION)
|
||||
|
||||
@@ -96,7 +96,7 @@ func (job *SStatusProbeJob) Do(ctx context.Context, now time.Time) error {
|
||||
if bStatus == api.BAREMETAL_READY || bStatus == api.BAREMETAL_RUNNING || bStatus == api.BAREMETAL_UNKNOWN {
|
||||
ps, err := job.baremetal.GetPowerStatus()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "GetPowerStatus")
|
||||
return errors.Wrap(err, "StatusProbeJob get power status")
|
||||
}
|
||||
job.lastTime = now
|
||||
pps := PowerStatusToBaremetalStatus(ps)
|
||||
|
||||
@@ -212,9 +212,13 @@ func handleServerStart(ctx *Context, bm *baremetal.SBaremetalInstance, _ baremet
|
||||
}
|
||||
|
||||
func handleServerStop(ctx *Context, bm *baremetal.SBaremetalInstance, _ baremetaltypes.IBaremetalServer) {
|
||||
if err := bm.StartServerStopTask(ctx.UserCred(), ctx.TaskId(), ctx.Data()); err != nil {
|
||||
ctx.ResponseError(httperrors.NewGeneralError(err))
|
||||
return
|
||||
if bm.HasBMC() {
|
||||
if err := bm.StartServerStopTask(ctx.UserCred(), ctx.TaskId(), ctx.Data()); err != nil {
|
||||
ctx.ResponseError(httperrors.NewGeneralError(err))
|
||||
return
|
||||
}
|
||||
} else {
|
||||
bm.StartBaremetalMaintenanceTask(ctx.UserCred(), ctx.TaskId(), ctx.Data())
|
||||
}
|
||||
ctx.ResponseOk()
|
||||
}
|
||||
|
||||
+268
-3
@@ -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"
|
||||
@@ -797,7 +798,11 @@ func PowerStatusToServerStatus(bm *SBaremetalInstance, status string) string {
|
||||
if conf, _ := bm.GetSSHConfig(); conf == nil {
|
||||
return baremetalstatus.SERVER_RUNNING
|
||||
} else {
|
||||
return baremetalstatus.SERVER_ADMIN
|
||||
if !bm.HasBMC() && !bm.IsMaintenance() {
|
||||
return baremetalstatus.SERVER_READY
|
||||
} else {
|
||||
return baremetalstatus.SERVER_ADMIN
|
||||
}
|
||||
}
|
||||
case types.POWER_STATUS_OFF:
|
||||
return baremetalstatus.SERVER_READY
|
||||
@@ -927,13 +932,18 @@ func (b *SBaremetalInstance) NeedPXEBoot() bool {
|
||||
taskNeedPXEBoot = true
|
||||
}
|
||||
ret := false
|
||||
if taskNeedPXEBoot || (task == nil && len(serverId) == 0 && b.GetHostType() == "baremetal") {
|
||||
if taskNeedPXEBoot || (task == nil && len(serverId) == 0 && b.GetHostType() == "baremetal") || (task == nil && b.IsMaintenance()) {
|
||||
ret = true
|
||||
}
|
||||
log.Infof("Check task %s, server %s NeedPXEBoot: %v", taskName, serverId, ret)
|
||||
return ret
|
||||
}
|
||||
|
||||
func (b *SBaremetalInstance) IsMaintenance() bool {
|
||||
isMt, _ := b.desc.Bool("is_maintenance")
|
||||
return isMt
|
||||
}
|
||||
|
||||
func (b *SBaremetalInstance) GetHostType() string {
|
||||
hostType, _ := b.desc.GetString("host_type")
|
||||
return hostType
|
||||
@@ -990,6 +1000,13 @@ func (b *SBaremetalInstance) getDHCPConfig(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if isPxe && IsUEFIPxeArch(arch) && !b.NeedPXEBoot() {
|
||||
// TODO: use chainloader boot UEFI firmware,
|
||||
// currently not response PXE request,
|
||||
// and let BIOS detect bootable device
|
||||
b.ClearSSHConfig()
|
||||
return nil, errors.Errorf("Baremetal %s not need UEFI PXE boot", b.GetName())
|
||||
}
|
||||
return GetNicDHCPConfig(nic, serverIP.String(), hostName, isPxe, arch)
|
||||
}
|
||||
|
||||
@@ -1290,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
|
||||
@@ -1347,6 +1377,206 @@ func (b *SBaremetalInstance) SetExistingIPMIIPAddr(ipAddr string) {
|
||||
b.desc.Set("ipmi_info", info)
|
||||
}
|
||||
|
||||
func (b *SBaremetalInstance) HasBMC() bool {
|
||||
conf := b.GetIPMIConfig()
|
||||
if conf == nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (b *SBaremetalInstance) GetHostSSHClient() (*ssh.Client, error) {
|
||||
conf, err := b.GetSSHConfig()
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "Get host ssh config")
|
||||
}
|
||||
if conf == nil {
|
||||
return nil, errors.Errorf("Host ssh config is empty")
|
||||
}
|
||||
sshCli, err := ssh.NewClient(conf.RemoteIP, 22, "root", conf.Password, "")
|
||||
if err != nil {
|
||||
return nil, errors.Wrap(err, "New ssh client")
|
||||
}
|
||||
return sshCli, nil
|
||||
}
|
||||
|
||||
func (b *SBaremetalInstance) GetServerSSHClient() (*ssh.Client, error) {
|
||||
s := b.GetServer()
|
||||
if s == nil {
|
||||
return nil, errors.Error("No server")
|
||||
}
|
||||
|
||||
privateKey, err := modules.Sshkeypairs.FetchPrivateKey(context.TODO(), auth.AdminCredential())
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Get server %s login info", s.GetId())
|
||||
}
|
||||
nics := s.GetNics()
|
||||
var errs []error
|
||||
for _, nic := range nics {
|
||||
if nic.LinkUp && nic.Ip != "" {
|
||||
for _, user := range []string{"cloudroot", "root"} {
|
||||
sshCli, err := ssh.NewClient(nic.Ip, 22, user, "", privateKey)
|
||||
if err != nil {
|
||||
err = errors.Wrapf(err, "New server %s ssh client %s@%s", s.GetName(), user, nic.Ip)
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
return sshCli, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, errors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
func (b *SBaremetalInstance) SSHReachable() (bool, error) {
|
||||
var errs []error
|
||||
if cli, err := b.GetHostSSHClient(); err != nil {
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
// host ssh reachable
|
||||
cli.Close()
|
||||
return true, nil
|
||||
}
|
||||
if cli, err := b.GetServerSSHClient(); err != nil {
|
||||
errs = append(errs, err)
|
||||
} else {
|
||||
// server ssh reachable
|
||||
cli.Close()
|
||||
return true, nil
|
||||
}
|
||||
return false, errors.NewAggregate(errs)
|
||||
}
|
||||
|
||||
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 hostRun(hostCli)
|
||||
}
|
||||
}
|
||||
|
||||
serverCli, err := b.GetServerSSHClient()
|
||||
if err != nil {
|
||||
return nil, errors.Wrapf(err, "Get baremetal %s server ssh client", b.GetName())
|
||||
}
|
||||
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")
|
||||
}
|
||||
}
|
||||
b.ClearSSHConfig()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *SBaremetalInstance) SSHShutdown() error {
|
||||
if _, err := b.sshRun("/sbin/poweroff", "sudo shutdown -h now && exit"); err != nil {
|
||||
if ssh.IsExitMissingError(err) {
|
||||
return nil
|
||||
}
|
||||
return errors.Wrap(err, "Try poweroff")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *SBaremetalInstance) GetIPMITool() *ipmitool.LanPlusIPMI {
|
||||
conf := b.GetIPMIConfig()
|
||||
if conf == nil {
|
||||
@@ -1429,9 +1659,34 @@ func (b *SBaremetalInstance) DoDiskBoot() error {
|
||||
*/
|
||||
|
||||
func (b *SBaremetalInstance) GetPowerStatus() (string, error) {
|
||||
status, err := b.getPowerStatus()
|
||||
if err != nil {
|
||||
if errors.Cause(err) != types.ErrIPMIToolNull {
|
||||
return "", errors.Wrap(err, "GetPowerStatus")
|
||||
} else if b.HasBMC() {
|
||||
return "", errors.Wrap(err, "GetPowerStatus from ipmi")
|
||||
}
|
||||
}
|
||||
return status, nil
|
||||
}
|
||||
|
||||
func (b *SBaremetalInstance) getPowerStatus() (string, error) {
|
||||
ipmiCli := b.GetIPMITool()
|
||||
if ipmiCli == nil {
|
||||
return "", fmt.Errorf("Baremetal %s ipmitool is nil", b.GetId())
|
||||
if cli, err := b.GetHostSSHClient(); err == nil {
|
||||
cli.Close()
|
||||
return types.POWER_STATUS_ON, nil
|
||||
} else {
|
||||
log.Warningf("Use host %s ssh client get powerstatus: %v", b.GetName(), err)
|
||||
}
|
||||
if cli, err := b.GetServerSSHClient(); err == nil {
|
||||
cli.Close()
|
||||
b.ClearSSHConfig()
|
||||
return types.POWER_STATUS_ON, nil
|
||||
} else {
|
||||
log.Warningf("Use server %s ssh client get powerstatus: %v", b.GetServerName(), err)
|
||||
}
|
||||
return "", errors.Wrapf(types.ErrIPMIToolNull, "Baremetal %s", b.GetId())
|
||||
}
|
||||
return ipmitool.GetChassisPowerStatus(ipmiCli)
|
||||
}
|
||||
@@ -2111,6 +2366,14 @@ func (s *SBaremetalServer) GetDiskConfig() ([]*api.BaremetalDiskConfig, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if len(layouts) != 0 {
|
||||
firstDisk := layouts[0]
|
||||
// convert to normal order if first disk is PCIE driver
|
||||
if firstDisk.Conf.Driver == baremetal.DISK_DRIVER_PCIE {
|
||||
return baremetal.GetLayoutDiskConfig(layouts), nil
|
||||
}
|
||||
}
|
||||
return baremetal.GetLayoutRaidConfig(layouts), nil
|
||||
}
|
||||
|
||||
@@ -2131,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)
|
||||
|
||||
+26
-2
@@ -28,6 +28,30 @@ import (
|
||||
"yunion.io/x/onecloud/pkg/util/dhcp"
|
||||
)
|
||||
|
||||
const (
|
||||
// ref: https://datatracker.ietf.org/doc/html/rfc4578#section-2.1
|
||||
PXE_CLIENT_ARCH_INTEL_X86PC = iota
|
||||
PXE_CLIENT_ARCH_NEC_PC98
|
||||
PXE_CLIENT_ARCH_EFI_ITANIUM
|
||||
PXE_CLIENT_ARCH_DEC_ALPHA
|
||||
PXE_CLIENT_ARCH_ARC_X86
|
||||
PXE_CLIENT_ARCH_INTEL_LEAN_CLIENT
|
||||
PXE_CLIENT_ARCH_EFI_IA32
|
||||
PXE_CLIENT_ARCH_EFI_BC
|
||||
PXE_CLIENT_ARCH_EFI_XSCALE
|
||||
PXE_CLIENT_ARCH_EFI_X86_64
|
||||
)
|
||||
|
||||
func IsUEFIPxeArch(arch uint16) bool {
|
||||
switch arch {
|
||||
case PXE_CLIENT_ARCH_EFI_IA32:
|
||||
return true
|
||||
case PXE_CLIENT_ARCH_EFI_BC, PXE_CLIENT_ARCH_EFI_XSCALE, PXE_CLIENT_ARCH_EFI_X86_64:
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetNicDHCPConfig(
|
||||
n *types.SNic,
|
||||
serverIP string,
|
||||
@@ -71,9 +95,9 @@ func GetNicDHCPConfig(
|
||||
if isPxe {
|
||||
conf.BootServer = serverIP
|
||||
switch arch {
|
||||
case 7, 9:
|
||||
case PXE_CLIENT_ARCH_EFI_BC, PXE_CLIENT_ARCH_EFI_X86_64:
|
||||
conf.BootFile = "bootx64.efi"
|
||||
case 6:
|
||||
case PXE_CLIENT_ARCH_EFI_IA32:
|
||||
conf.BootFile = "bootia32.efi"
|
||||
default:
|
||||
//if o.Options.EnableTftpHttpDownload {
|
||||
|
||||
+10
-10
@@ -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) {
|
||||
|
||||
+52
-17
@@ -342,6 +342,29 @@ func (self *SBaremetalTaskBase) EnsurePowerUp() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SBaremetalTaskBase) EnsureSSHReboot() error {
|
||||
if err := self.Baremetal.SSHReboot(); err != nil {
|
||||
return errors.Wrap(err, "Ensure ssh reboot")
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
canReach bool
|
||||
)
|
||||
maxTries := 20
|
||||
startTime := time.Now()
|
||||
for count := 0; count < maxTries; count++ {
|
||||
times := (count % 10) + 1
|
||||
log.Infof("Try %s ssh connection after reboot %d times, %s passed", self.Baremetal.GetName(), times, time.Now().Sub(startTime))
|
||||
canReach, err = self.Baremetal.SSHReachable()
|
||||
if canReach {
|
||||
return nil
|
||||
}
|
||||
time.Sleep(10 * time.Second * time.Duration(times))
|
||||
}
|
||||
return errors.Wrapf(err, "Test %s ssh connection after reboot", self.Baremetal.GetName())
|
||||
}
|
||||
|
||||
func (self *SBaremetalTaskBase) NeedPXEBoot() bool {
|
||||
return false
|
||||
}
|
||||
@@ -400,27 +423,35 @@ func (self *SBaremetalPXEBootTaskBase) InitPXEBootTask(ctx context.Context, args
|
||||
self.PxeBoot = false
|
||||
}
|
||||
|
||||
// Do soft reboot
|
||||
if self.data != nil && jsonutils.QueryBoolean(self.data, "soft_boot", false) {
|
||||
self.startTime = time.Now()
|
||||
if err := self.Baremetal.DoPowerShutdown(true); err != nil {
|
||||
// ignore error
|
||||
log.Errorf("DoPowerShutdown error: %v", err)
|
||||
if !self.Baremetal.HasBMC() {
|
||||
// Try remote ssh reboot
|
||||
if err := self.Baremetal.SSHReboot(); err != nil {
|
||||
return errors.Wrap(err, "Try ssh reboot")
|
||||
}
|
||||
//self.CallNextStage(self, self.WaitForShutdown, nil)
|
||||
self.SetStage(self.WaitForShutdown)
|
||||
} else {
|
||||
// Do soft reboot
|
||||
if self.data != nil && jsonutils.QueryBoolean(self.data, "soft_boot", false) {
|
||||
self.startTime = time.Now()
|
||||
if err := self.Baremetal.DoPowerShutdown(true); err != nil {
|
||||
// ignore error
|
||||
log.Errorf("DoPowerShutdown error: %v", err)
|
||||
}
|
||||
//self.CallNextStage(self, self.WaitForShutdown, nil)
|
||||
self.SetStage(self.WaitForShutdown)
|
||||
|
||||
return nil
|
||||
return nil
|
||||
}
|
||||
|
||||
// shutdown and power up to PXE mode
|
||||
if err := self.EnsurePowerShutdown(false); err != nil {
|
||||
return errors.Wrap(err, "EnsurePowerShutdown")
|
||||
}
|
||||
|
||||
if err := self.EnsurePowerUp(); err != nil {
|
||||
return errors.Wrap(err, "EnsurePowerUp to pxe")
|
||||
}
|
||||
}
|
||||
|
||||
// shutdown and power up to PXE mode
|
||||
if err := self.EnsurePowerShutdown(false); err != nil {
|
||||
return errors.Wrap(err, "EnsurePowerShutdown")
|
||||
}
|
||||
|
||||
if err := self.EnsurePowerUp(); err != nil {
|
||||
return errors.Wrap(err, "EnsurePowerUp to pxe")
|
||||
}
|
||||
// this stage will be called by baremetalInstance when pxe start notify
|
||||
self.SetSSHStage(self.IPXEBootTask().OnPXEBoot)
|
||||
return nil
|
||||
@@ -459,3 +490,7 @@ func (self *SBaremetalPXEBootTaskBase) OnStopComplete(ctx context.Context, args
|
||||
func (self *SBaremetalPXEBootTaskBase) GetName() string {
|
||||
return "BaremetalPXEBootTaskBase"
|
||||
}
|
||||
|
||||
func AdjustUEFIBootOrder(term *ssh.Client, bm IBaremetal) error {
|
||||
return bm.AdjustUEFICurrentBootOrder(term)
|
||||
}
|
||||
|
||||
@@ -22,6 +22,8 @@ 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"
|
||||
)
|
||||
@@ -29,12 +31,15 @@ import (
|
||||
type IServerBaseDeployTask interface {
|
||||
IPXEBootTask
|
||||
|
||||
RemoveEFIOSEntry() bool
|
||||
DoDeploys(term *ssh.Client) (jsonutils.JSONObject, error)
|
||||
PostDeploys(term *ssh.Client) error
|
||||
}
|
||||
|
||||
type SBaremetalServerBaseDeployTask struct {
|
||||
SBaremetalPXEBootTaskBase
|
||||
|
||||
needPXEBoot bool
|
||||
}
|
||||
|
||||
func newBaremetalServerBaseDeployTask(
|
||||
@@ -45,6 +50,7 @@ func newBaremetalServerBaseDeployTask(
|
||||
) SBaremetalServerBaseDeployTask {
|
||||
task := SBaremetalServerBaseDeployTask{
|
||||
SBaremetalPXEBootTaskBase: newBaremetalPXEBootTaskBase(userCred, baremetal, taskId, data),
|
||||
needPXEBoot: true,
|
||||
}
|
||||
// any inheritance must call:
|
||||
// task.SetStage(task.InitPXEBootTask)
|
||||
@@ -59,6 +65,10 @@ func (self *SBaremetalServerBaseDeployTask) GetName() string {
|
||||
return "BaremetalServerBaseDeployTask"
|
||||
}
|
||||
|
||||
func (self *SBaremetalServerBaseDeployTask) NeedPXEBoot() bool {
|
||||
return self.needPXEBoot
|
||||
}
|
||||
|
||||
func (self *SBaremetalServerBaseDeployTask) GetFinishAction() string {
|
||||
if self.data != nil {
|
||||
action, _ := self.data.GetString("on_finish")
|
||||
@@ -67,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
|
||||
}
|
||||
@@ -77,10 +91,22 @@ 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, self.Baremetal); err != nil {
|
||||
return errors.Wrap(err, "Adjust UEFI boot order")
|
||||
}
|
||||
|
||||
_, err = term.Run(
|
||||
"/bin/sync",
|
||||
"/sbin/sysctl -w vm.drop_caches=3",
|
||||
@@ -88,23 +114,42 @@ func (self *SBaremetalServerBaseDeployTask) OnPXEBoot(ctx context.Context, term
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Sync disk")
|
||||
}
|
||||
|
||||
if err := self.IServerBaseDeployTask().PostDeploys(term); err != nil {
|
||||
return errors.Wrap(err, "post deploy")
|
||||
}
|
||||
|
||||
onFinishAction := self.GetFinishAction()
|
||||
if utils.IsInStringArray(onFinishAction, []string{"restart", "shutdown"}) {
|
||||
err = self.EnsurePowerShutdown(false)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Ensure power off")
|
||||
}
|
||||
if onFinishAction == "restart" {
|
||||
err = self.EnsurePowerUp()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "Ensure power up")
|
||||
if self.Baremetal.HasBMC() {
|
||||
if err := self.EnsurePowerShutdown(false); err != nil {
|
||||
return errors.Wrap(err, "Ensure power off")
|
||||
}
|
||||
if onFinishAction == "restart" {
|
||||
if err := self.EnsurePowerUp(); err != nil {
|
||||
return errors.Wrap(err, "Ensure power up")
|
||||
}
|
||||
}
|
||||
self.Baremetal.AutoSyncAllStatus()
|
||||
} else {
|
||||
if onFinishAction == "shutdown" {
|
||||
log.Infof("None BMC baremetal can't shutdown when deploying")
|
||||
/*
|
||||
* if err := self.Baremetal.SSHShutdown(); err != nil {
|
||||
* return errors.Wrap(err, "Try ssh shutdown")
|
||||
* }
|
||||
*/
|
||||
} else {
|
||||
// do restart
|
||||
// hack: ssh reboot to disk
|
||||
self.needPXEBoot = false
|
||||
if err := self.EnsureSSHReboot(); err != nil {
|
||||
return errors.Wrap(err, "Try ssh reboot")
|
||||
}
|
||||
}
|
||||
}
|
||||
self.Baremetal.SyncAllStatus(types.POWER_STATUS_ON)
|
||||
}
|
||||
self.Baremetal.AutoSyncAllStatus()
|
||||
SetTaskComplete(self, result)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
@@ -313,7 +315,7 @@ func (task *sBaremetalPrepareTask) DoPrepare(cli *ssh.Client) error {
|
||||
// set ipmi nic address and user password
|
||||
if err = task.configIPMISetting(cli, infos); err != nil {
|
||||
logclient.AddActionLogWithStartable(task, task.baremetal, logclient.ACT_PREPARE, err, task.userCred, false)
|
||||
return err
|
||||
return errors.Wrap(err, "Config IPMI setting")
|
||||
}
|
||||
|
||||
if err = task.updateBmInfo(cli, infos); err != nil {
|
||||
@@ -327,6 +329,11 @@ func (task *sBaremetalPrepareTask) DoPrepare(cli *ssh.Client) error {
|
||||
log.Errorf("SetNTP fail: %s", err)
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
logclient.AddActionLogWithStartable(task, task.baremetal, logclient.ACT_PREPARE, infos.sysInfo, task.userCred, true)
|
||||
|
||||
log.Infof("Prepare complete")
|
||||
@@ -358,7 +365,7 @@ func (task *sBaremetalPrepareTask) findAdminNic(cli *ssh.Client, nicsInfo []*typ
|
||||
|
||||
func (task *sBaremetalPrepareTask) updateBmInfo(cli *ssh.Client, i *baremetalPrepareInfo) error {
|
||||
adminNic := task.baremetal.GetAdminNic()
|
||||
if adminNic == nil {
|
||||
if adminNic == nil || (adminNic != nil && !adminNic.LinkUp) {
|
||||
adminIdx, adminNicDev, err := task.findAdminNic(cli, i.nicsInfo)
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "task.findAdminNic")
|
||||
@@ -368,7 +375,7 @@ func (task *sBaremetalPrepareTask) updateBmInfo(cli *ssh.Client, i *baremetalPre
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "send Admin Nic Info")
|
||||
}
|
||||
adminNic = task.baremetal.GetAdminNic()
|
||||
adminNic = task.baremetal.GetNicByMac(adminNicDev.Mac)
|
||||
}
|
||||
// collect params
|
||||
updateInfo := make(map[string]interface{})
|
||||
|
||||
@@ -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)
|
||||
@@ -90,7 +94,10 @@ func doPoweroff(term *ssh.Client) error {
|
||||
}
|
||||
|
||||
func (self *SBaremetalServerCreateTask) PostDeploys(term *ssh.Client) error {
|
||||
return doPoweroff(term)
|
||||
if self.Baremetal.HasBMC() {
|
||||
return doPoweroff(term)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *SBaremetalServerCreateTask) onError(term *ssh.Client, err error) error {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
@@ -68,6 +69,12 @@ type IBaremetal interface {
|
||||
SaveSSHConfig(remoteAddr string, key string) error
|
||||
ServerLoadDesc() error
|
||||
GetDHCPServerIP() (net.IP, error)
|
||||
|
||||
HasBMC() bool
|
||||
SSHReachable() (bool, error)
|
||||
SSHReboot() error
|
||||
SSHShutdown() error
|
||||
AdjustUEFICurrentBootOrder(cli *ssh.Client) error
|
||||
}
|
||||
|
||||
type IBmManager interface {
|
||||
|
||||
@@ -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 {
|
||||
@@ -65,5 +69,8 @@ func (self *SBaremetalServerRebuildTask) DoDeploys(term *ssh.Client) (jsonutils.
|
||||
}
|
||||
|
||||
func (self *SBaremetalServerRebuildTask) PostDeploys(term *ssh.Client) error {
|
||||
return doPoweroff(term)
|
||||
if self.Baremetal.HasBMC() {
|
||||
return doPoweroff(term)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -48,3 +48,8 @@ func (self *SBaremetalReprepareTask) DoDeploys(term *ssh.Client) (jsonutils.JSON
|
||||
err := task.DoPrepare(term)
|
||||
return nil, err
|
||||
}
|
||||
|
||||
func (self *SBaremetalReprepareTask) PostDeploys(term *ssh.Client) error {
|
||||
self.Baremetal.AutoSyncStatus()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -44,16 +44,22 @@ func NewBaremetalServerStartTask(
|
||||
}
|
||||
|
||||
func (self *SBaremetalServerStartTask) DoBoot(ctx context.Context, args interface{}) error {
|
||||
conf := self.Baremetal.GetRawIPMIConfig()
|
||||
if !conf.CdromBoot {
|
||||
err := self.Baremetal.DoPXEBoot()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "DoPXEBoot")
|
||||
if self.Baremetal.HasBMC() {
|
||||
conf := self.Baremetal.GetRawIPMIConfig()
|
||||
if !conf.CdromBoot {
|
||||
err := self.Baremetal.DoPXEBoot()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "DoPXEBoot")
|
||||
}
|
||||
} else {
|
||||
err := self.Baremetal.DoRedfishPowerOn()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "DoRedfishPowerOn")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
err := self.Baremetal.DoRedfishPowerOn()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "DoRedfishPowerOn")
|
||||
if err := self.Baremetal.SSHReboot(); err != nil {
|
||||
return errors.Wrap(err, "Try reboot")
|
||||
}
|
||||
}
|
||||
self.SetStage(self.WaitForStart)
|
||||
@@ -68,7 +74,7 @@ func (self *SBaremetalServerStartTask) GetName() string {
|
||||
func (self *SBaremetalServerStartTask) WaitForStart(ctx context.Context, args interface{}) error {
|
||||
status, err := self.Baremetal.GetPowerStatus()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "GetPowerStatus")
|
||||
return errors.Wrap(err, "Wait for start")
|
||||
}
|
||||
log.Infof("%s WaitForStart status=%s", self.GetName(), status)
|
||||
if status == types.POWER_STATUS_ON {
|
||||
|
||||
@@ -46,9 +46,16 @@ func NewBaremetalServerStopTask(
|
||||
}
|
||||
|
||||
func (task *SBaremetalServerStopTask) DoStop(ctx context.Context, args interface{}) error {
|
||||
task.SetStage(task.WaitForStop)
|
||||
if err := task.Baremetal.DoPowerShutdown(true); err != nil {
|
||||
log.Errorf("Do power shutdown error: %s", err)
|
||||
if task.Baremetal.HasBMC() {
|
||||
task.SetStage(task.WaitForStop)
|
||||
if err := task.Baremetal.DoPowerShutdown(true); err != nil {
|
||||
log.Errorf("Do power shutdown error: %s", err)
|
||||
}
|
||||
} else {
|
||||
if err := task.Baremetal.SSHShutdown(); err != nil {
|
||||
return errors.Wrap(err, "Try ssh shutdown")
|
||||
}
|
||||
task.SetStage(task.OnStopComplete)
|
||||
}
|
||||
task.startTime = time.Now()
|
||||
ExecuteTask(task, nil)
|
||||
|
||||
@@ -16,9 +16,9 @@ package tasks
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/pkg/errors"
|
||||
|
||||
baremetalstatus "yunion.io/x/onecloud/pkg/baremetal/status"
|
||||
"yunion.io/x/onecloud/pkg/cloudcommon/types"
|
||||
@@ -45,14 +45,23 @@ func NewBaremetalUnmaintenanceTask(
|
||||
|
||||
func (task *SBaremetalUnmaintenanceTask) DoUnmaintenance(ctx context.Context, args interface{}) error {
|
||||
var err error
|
||||
if jsonutils.QueryBoolean(task.data, "guest_running", false) {
|
||||
err = task.EnsurePowerShutdown(false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("EnsurePowerShutdown hard: %v", err)
|
||||
}
|
||||
err = task.EnsurePowerUp()
|
||||
if err != nil {
|
||||
return fmt.Errorf("EnsurePowerUp disk: %v", err)
|
||||
hasBMC := task.Baremetal.HasBMC()
|
||||
if jsonutils.QueryBoolean(task.data, "guest_running", false) || !hasBMC {
|
||||
if hasBMC {
|
||||
err = task.EnsurePowerShutdown(false)
|
||||
if err != nil {
|
||||
return errors.Errorf("EnsurePowerShutdown hard: %v", err)
|
||||
}
|
||||
err = task.EnsurePowerUp()
|
||||
if err != nil {
|
||||
return errors.Errorf("EnsurePowerUp disk: %v", err)
|
||||
}
|
||||
} else {
|
||||
if task.Baremetal.GetServer() != nil {
|
||||
if err := task.EnsureSSHReboot(); err != nil {
|
||||
return errors.Wrap(err, "Do unmaintenance for none BMC server")
|
||||
}
|
||||
}
|
||||
}
|
||||
task.Baremetal.SyncStatus(baremetalstatus.RUNNING, "")
|
||||
SetTaskComplete(task, nil)
|
||||
@@ -61,7 +70,7 @@ func (task *SBaremetalUnmaintenanceTask) DoUnmaintenance(ctx context.Context, ar
|
||||
task.SetStage(task.WaitForStop)
|
||||
err = task.EnsurePowerShutdown(true)
|
||||
if err != nil {
|
||||
return fmt.Errorf("EnsurePowerShutdown soft: %v", err)
|
||||
return errors.Errorf("EnsurePowerShutdown soft: %v", err)
|
||||
}
|
||||
ExecuteTask(task, nil)
|
||||
return nil
|
||||
|
||||
@@ -36,6 +36,7 @@ type IBaremetalServer interface {
|
||||
SyncPartitionSize(term *ssh.Client, parts []*disktool.Partition) ([]jsonutils.JSONObject, error)
|
||||
DoDeploy(term *ssh.Client, data jsonutils.JSONObject, isInit bool) (jsonutils.JSONObject, error)
|
||||
SaveDesc(desc jsonutils.JSONObject) error
|
||||
GetNics() []types.SServerNic
|
||||
GetNicByMac(mac net.HardwareAddr) *types.SNic
|
||||
|
||||
GetRootTemplateId() string
|
||||
|
||||
@@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
package uefi // import "yunion.io/x/onecloud/pkg/baremetal/utils/uefi"
|
||||
@@ -0,0 +1,478 @@
|
||||
// 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 uefi
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"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"
|
||||
SUDO_EFIBOOTMGR = "sudo efibootmgr"
|
||||
|
||||
MAC_KEYWORD = "MAC"
|
||||
)
|
||||
|
||||
type BootMgr struct {
|
||||
// bootCurrent - the boot entry used to start the currently running system.
|
||||
bootCurrent string
|
||||
|
||||
// bootOrder - the boot order as would appear in the boot manager.
|
||||
// The boot manager tries to boot the first active entry on this list.
|
||||
// If unsuccessful, it tries the next entry, and so on.
|
||||
bootOrder []string
|
||||
|
||||
// bootNext - the boot entry which is scheduled to be run on next boot.
|
||||
// This superceeds BootOrder for one boot only, and is deleted by the
|
||||
// boot manager after first use.
|
||||
// This allows you to change the next boot behavior without changing BootOrder.
|
||||
bootNext string
|
||||
|
||||
// timeout - the time in seconds between when the boot manager appears on the screen
|
||||
// until when it automatically chooses the startup value from BootNext or BootOrder.
|
||||
timeout int
|
||||
|
||||
// entries - the boot entry parsed in map
|
||||
entries map[string]*BootEntry
|
||||
}
|
||||
|
||||
type BootEntry struct {
|
||||
BootNum string
|
||||
Description string
|
||||
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")
|
||||
|
||||
mgr := &BootMgr{
|
||||
bootOrder: []string{},
|
||||
timeout: -1,
|
||||
entries: make(map[string]*BootEntry),
|
||||
}
|
||||
|
||||
pf := func(ff func(string) bool) {
|
||||
for _, l := range lines {
|
||||
if ok := ff(l); ok {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// parse BootCurrent
|
||||
pf(func(l string) bool {
|
||||
if current := parseEFIBootMGRBootCurrent(l); current != "" {
|
||||
mgr.bootCurrent = current
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// parse Timeout second
|
||||
pf(func(l string) bool {
|
||||
if timeout := parseEFIBootMGRTimeout(l); timeout != -1 {
|
||||
mgr.timeout = timeout
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// parse BootOrder
|
||||
pf(func(l string) bool {
|
||||
if order := parseEFIBootMGRBootOrder(l); len(order) != 0 {
|
||||
mgr.bootOrder = order
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// parse BootNext
|
||||
pf(func(l string) bool {
|
||||
if next := parseEFIBootMGRBootNext(l); next != "" {
|
||||
mgr.bootNext = next
|
||||
return true
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// parse entries
|
||||
pf(func(l string) bool {
|
||||
if entry := parseEFIBootMGREntry(l); entry != nil {
|
||||
mgr.entries[entry.BootNum] = entry
|
||||
}
|
||||
return false
|
||||
})
|
||||
|
||||
// finally check
|
||||
if err := mgr.DataCheck(); err != nil {
|
||||
return nil, errors.Wrap(err, "Invalid efibootmgr parse")
|
||||
}
|
||||
|
||||
return mgr, nil
|
||||
}
|
||||
|
||||
func (m *BootMgr) DataCheck() error {
|
||||
if m.bootCurrent == "" {
|
||||
return errors.Error("BootCurrent is empty")
|
||||
}
|
||||
|
||||
if len(m.bootOrder) == 0 {
|
||||
return errors.Error("BootOrder length is 0")
|
||||
}
|
||||
|
||||
// check if BootOrder in entries
|
||||
for _, orderNum := range m.bootOrder {
|
||||
if _, ok := m.entries[orderNum]; !ok {
|
||||
return errors.Errorf("Not found BootOrder %s entry", orderNum)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func parseEFIBootMGRBootCurrent(line string) string {
|
||||
prefix := "BootCurrent: "
|
||||
if strings.HasPrefix(line, prefix) {
|
||||
return strings.Split(line, prefix)[1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func parseEFIBootMGRBootOrder(line string) []string {
|
||||
prefix := "BootOrder: "
|
||||
if !strings.HasPrefix(line, prefix) {
|
||||
return nil
|
||||
}
|
||||
orderStr := strings.Split(line, prefix)[1]
|
||||
return strings.Split(orderStr, ",")
|
||||
}
|
||||
|
||||
func parseEFIBootMGRBootNext(line string) string {
|
||||
prefix := "BootNext: "
|
||||
if !strings.HasPrefix(line, prefix) {
|
||||
return ""
|
||||
}
|
||||
return strings.Split(line, prefix)[1]
|
||||
}
|
||||
|
||||
func parseEFIBootMGRTimeout(line string) int {
|
||||
timeoutRegexp := `^Timeout: (?P<seconds>[0-9]{1,}) seconds`
|
||||
matches := regutils2.SubGroupMatch(timeoutRegexp, line)
|
||||
if len(matches) == 0 {
|
||||
return -1
|
||||
}
|
||||
secondStr := matches["seconds"]
|
||||
second, err := strconv.Atoi(secondStr)
|
||||
if err != nil {
|
||||
log.Errorf("parse %s seconds error: %v", secondStr, err)
|
||||
return -1
|
||||
}
|
||||
return second
|
||||
}
|
||||
|
||||
func parseEFIBootMGREntry(line string) *BootEntry {
|
||||
entryRegexp := `^Boot(?P<num>[0-9a-zA-Z]{4})[^:]+?\s+(?P<description>.*)`
|
||||
matches := regutils2.SubGroupMatch(entryRegexp, line)
|
||||
if len(matches) == 0 {
|
||||
return nil
|
||||
}
|
||||
num, ok := matches["num"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
desc, ok := matches["description"]
|
||||
if !ok {
|
||||
return nil
|
||||
}
|
||||
isActive := false
|
||||
if strings.Contains(line, "* ") {
|
||||
isActive = true
|
||||
}
|
||||
return &BootEntry{
|
||||
BootNum: num,
|
||||
Description: desc,
|
||||
IsActive: isActive,
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
return ParseEFIBootMGR(strings.Join(lines, "\n"))
|
||||
}
|
||||
|
||||
func (m *BootMgr) GetCommand(sudo bool) string {
|
||||
return getEFIBootMgrCmd(sudo)
|
||||
}
|
||||
|
||||
func (m *BootMgr) GetBootCurrent() string {
|
||||
return m.bootCurrent
|
||||
}
|
||||
|
||||
func (m *BootMgr) GetBootOrder() []string {
|
||||
return m.bootOrder
|
||||
}
|
||||
|
||||
func (m *BootMgr) GetBootNext() string {
|
||||
return m.bootNext
|
||||
}
|
||||
|
||||
func (m *BootMgr) GetTimeout() int {
|
||||
return m.timeout
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
func stringArraryFindItemPos(items []string, item string) int {
|
||||
for idx, elem := range items {
|
||||
if elem == item {
|
||||
return idx
|
||||
}
|
||||
}
|
||||
return -1
|
||||
}
|
||||
|
||||
func stringArraryMove(items []string, item string, pos int) []string {
|
||||
origPos := stringArraryFindItemPos(items, item)
|
||||
if origPos == -1 {
|
||||
items = append(items, item)
|
||||
origPos = stringArraryFindItemPos(items, item)
|
||||
}
|
||||
|
||||
for i := origPos; i != pos; {
|
||||
if i < pos {
|
||||
// from left to right
|
||||
tmp := items[i]
|
||||
items[i] = items[i+1]
|
||||
items[i+1] = tmp
|
||||
i++
|
||||
} else if i > pos {
|
||||
// from right to left
|
||||
tmp := items[i]
|
||||
items[i] = items[i-1]
|
||||
items[i-1] = tmp
|
||||
i--
|
||||
}
|
||||
}
|
||||
return items
|
||||
}
|
||||
|
||||
func (m *BootMgr) MoveBootOrder(num string, pos int) *BootMgr {
|
||||
if entry := m.GetBootEntry(num); entry == nil {
|
||||
log.Warningf("Not found boot entry by %q", num)
|
||||
return m
|
||||
}
|
||||
m.bootOrder = stringArraryMove(m.bootOrder, num, pos)
|
||||
return m
|
||||
}
|
||||
|
||||
func getSetBootOrderArgs(bootOrder []string) string {
|
||||
return strings.Join(bootOrder, ",")
|
||||
}
|
||||
|
||||
func (m *BootMgr) GetSetBootOrderArgs() string {
|
||||
return getSetBootOrderArgs(m.bootOrder)
|
||||
}
|
||||
|
||||
func RemoteIsUEFIBoot(cli *ssh.Client) (bool, error) {
|
||||
checkCmd := "test -d /sys/firmware/efi && echo is || echo not"
|
||||
lines, err := cli.Run(checkCmd)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "is") {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
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 {
|
||||
return errors.Errorf("Not found BootCurrent position %q", mgr.GetBootCurrent())
|
||||
}
|
||||
// move to first
|
||||
mgr.MoveBootOrder(mgr.GetBootCurrent(), 0)
|
||||
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
|
||||
}
|
||||
@@ -0,0 +1,316 @@
|
||||
// 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 uefi
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
TestPCOutput = `BootCurrent: 0007
|
||||
Timeout: 2 seconds
|
||||
BootOrder: 0003,0002,0001,0000,0004,0005,0007,0008,0009,000A
|
||||
Boot0000 Windows Boot Manager
|
||||
Boot0001 ubuntu
|
||||
Boot0002 ubuntu
|
||||
Boot0003 CentOS Linux
|
||||
Boot0004* Onboard NIC(IPV4)
|
||||
Boot0005 Onboard NIC(IPV6)
|
||||
Boot0007* PXE IPv4 Intel(R) Ethernet Server Adapter X520-2
|
||||
Boot0008 PXE IPv6 Intel(R) Ethernet Server Adapter X520-2
|
||||
Boot0009* PXE IPv4 Intel(R) Ethernet Server Adapter X520-2
|
||||
Boot000A PXE IPv6 Intel(R) Ethernet Server Adapter X520-2`
|
||||
|
||||
TestQemuOutput = `BootCurrent: 0005
|
||||
Timeout: 0 seconds
|
||||
BootOrder: 0005,0009,0008,0007,0002,0003,0004,0006,0001,0000
|
||||
Boot0000* UiApp
|
||||
Boot0001* UEFI QEMU DVD-ROM QM00003
|
||||
Boot0002* UEFI Floppy
|
||||
Boot0003* UEFI Floppy 2
|
||||
Boot0004* UEFI QEMU HARDDISK QM00001
|
||||
Boot0005* UEFI PXEv4 (MAC:525400123456)
|
||||
Boot0006* EFI Internal Shell
|
||||
Boot0007* CentOS
|
||||
Boot0008* CentOS Linux
|
||||
Boot0009* ubuntu`
|
||||
)
|
||||
|
||||
func Test_parseTimeout(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
want int
|
||||
}{
|
||||
{
|
||||
input: "Timeout: 0 seconds",
|
||||
want: 0,
|
||||
},
|
||||
{
|
||||
input: "Timeout: 30 seconds",
|
||||
want: 30,
|
||||
},
|
||||
{
|
||||
input: "Timeout: seconds",
|
||||
want: -1,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
if got := parseEFIBootMGRTimeout(tc.input); !reflect.DeepEqual(got, tc.want) {
|
||||
t.Errorf("parseEFIBootMGRTimeout() = %v, want %v", got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseEFIBootMGRBootOrder(t *testing.T) {
|
||||
tests := []struct {
|
||||
input string
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
input: "BootOrder: 0005,0009,0008,0007,0002,0003,0004,0006,0001,0000",
|
||||
want: []string{"0005", "0009", "0008", "0007", "0002", "0003", "0004", "0006", "0001", "0000"},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range tests {
|
||||
if got := parseEFIBootMGRBootOrder(tc.input); !reflect.DeepEqual(got, tc.want) {
|
||||
t.Errorf("parseEFIBootMGRBootOrder() = %v, want %v", got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseEFIBootMGRBootCurrent(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
want string
|
||||
}{
|
||||
{
|
||||
name: "BootCurrent: 0005",
|
||||
line: "BootCurrent: 0005",
|
||||
want: "0005",
|
||||
},
|
||||
{
|
||||
name: "BootCurrent: ",
|
||||
line: "BootCurrent: ",
|
||||
want: "",
|
||||
},
|
||||
{
|
||||
name: "BootCurrent:",
|
||||
line: "BootCurrent:",
|
||||
want: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := parseEFIBootMGRBootCurrent(tt.line); got != tt.want {
|
||||
t.Errorf("parseEFIBootMGRBootCurrent() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parseEFIBootMGREntry(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
line string
|
||||
want *BootEntry
|
||||
}{
|
||||
{
|
||||
name: "Boot0009* ubuntu",
|
||||
line: "Boot0009* ubuntu",
|
||||
want: &BootEntry{
|
||||
BootNum: "0009",
|
||||
Description: "ubuntu",
|
||||
IsActive: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Boot0005* UEFI PXEv4 (MAC:525400123456)",
|
||||
line: "Boot0005* UEFI PXEv4 (MAC:525400123456)",
|
||||
want: &BootEntry{
|
||||
BootNum: "0005",
|
||||
Description: "UEFI PXEv4 (MAC:525400123456)",
|
||||
IsActive: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Boot0003 CentOS Linux",
|
||||
line: "Boot0003 CentOS Linux",
|
||||
want: &BootEntry{
|
||||
BootNum: "0003",
|
||||
Description: "CentOS Linux",
|
||||
IsActive: false,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Timeout: 2 seconds",
|
||||
line: "Timeout: 2 seconds",
|
||||
want: nil,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := parseEFIBootMGREntry(tt.line); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("parseEFIBootMGREntry() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestParseEFIBootMGR(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
input string
|
||||
want *BootMgr
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "Parse PC output",
|
||||
input: TestPCOutput,
|
||||
want: &BootMgr{
|
||||
bootCurrent: "0007",
|
||||
timeout: 2,
|
||||
bootOrder: []string{"0003", "0002", "0001", "0000", "0004", "0005", "0007", "0008", "0009", "000A"},
|
||||
entries: map[string]*BootEntry{
|
||||
"0000": {
|
||||
BootNum: "0000",
|
||||
IsActive: false,
|
||||
Description: "Windows Boot Manager",
|
||||
},
|
||||
"0001": {
|
||||
BootNum: "0001",
|
||||
IsActive: false,
|
||||
Description: "ubuntu",
|
||||
},
|
||||
"0002": {
|
||||
BootNum: "0002",
|
||||
IsActive: false,
|
||||
Description: "ubuntu",
|
||||
},
|
||||
"0003": {
|
||||
BootNum: "0003",
|
||||
IsActive: false,
|
||||
Description: "CentOS Linux",
|
||||
},
|
||||
"0004": {
|
||||
BootNum: "0004",
|
||||
IsActive: true,
|
||||
Description: "Onboard NIC(IPV4)",
|
||||
},
|
||||
"0005": {
|
||||
BootNum: "0005",
|
||||
IsActive: false,
|
||||
Description: "Onboard NIC(IPV6)",
|
||||
},
|
||||
"0007": {
|
||||
BootNum: "0007",
|
||||
IsActive: true,
|
||||
Description: "PXE IPv4 Intel(R) Ethernet Server Adapter X520-2",
|
||||
},
|
||||
"0008": {
|
||||
BootNum: "0008",
|
||||
IsActive: false,
|
||||
Description: "PXE IPv6 Intel(R) Ethernet Server Adapter X520-2",
|
||||
},
|
||||
"0009": {
|
||||
BootNum: "0009",
|
||||
IsActive: true,
|
||||
Description: "PXE IPv4 Intel(R) Ethernet Server Adapter X520-2",
|
||||
},
|
||||
"000A": {
|
||||
BootNum: "000A",
|
||||
IsActive: false,
|
||||
Description: "PXE IPv6 Intel(R) Ethernet Server Adapter X520-2",
|
||||
},
|
||||
}},
|
||||
},
|
||||
}
|
||||
|
||||
assert := assert.New(t)
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := ParseEFIBootMGR(tt.input)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ParseEFIBootMGR() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if equal := assert.Equal(tt.want, got); !equal {
|
||||
t.Errorf("ParseEFIBootMGR() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_stringArraryMove(t *testing.T) {
|
||||
type args struct {
|
||||
items []string
|
||||
item string
|
||||
pos int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want []string
|
||||
}{
|
||||
{
|
||||
name: "move left to right",
|
||||
args: args{
|
||||
items: []string{"1", "2", "3"},
|
||||
item: "3",
|
||||
pos: 0,
|
||||
},
|
||||
want: []string{"3", "1", "2"},
|
||||
},
|
||||
{
|
||||
name: "move right to left",
|
||||
args: args{
|
||||
items: []string{"1", "2", "3"},
|
||||
item: "1",
|
||||
pos: 2,
|
||||
},
|
||||
want: []string{"2", "3", "1"},
|
||||
},
|
||||
{
|
||||
name: "no move",
|
||||
args: args{
|
||||
items: []string{"1", "2", "3"},
|
||||
item: "2",
|
||||
pos: 1,
|
||||
},
|
||||
want: []string{"1", "2", "3"},
|
||||
},
|
||||
{
|
||||
name: "add item move",
|
||||
args: args{
|
||||
items: []string{"1", "2", "3"},
|
||||
item: "4",
|
||||
pos: 0,
|
||||
},
|
||||
want: []string{"4", "1", "2", "3"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := stringArraryMove(tt.args.items, tt.args.item, tt.args.pos); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("stringArraryMove() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,12 @@ package types
|
||||
import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
"yunion.io/x/pkg/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
ErrIPMIToolNull = errors.Error("IPMI tool is null")
|
||||
)
|
||||
|
||||
type SSHConfig struct {
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -58,7 +58,7 @@ func isDiskConfigStorageMatch(
|
||||
adapterIsEqual := (confAdapter == nil || *confAdapter == adapter) &&
|
||||
(confDriver == nil || *confDriver == driver)
|
||||
|
||||
log.V(10).Debugf("typeIsHybrid: %v, typeIsRotate: %v, typeIsSSD: %v, rangeIsNoneAndCountZero: %v, rangeIsNotNoneAndIndexInRange: %v, rangeIsNoneAndSmallThanCount: %v, adapterIsEqual: %v", typeIsHybrid, typeIsRotate, typeIsSSD, rangeIsNoneAndCountZero, rangeIsNotNoneAndIndexInRange, rangeIsNoneAndSmallThanCount, adapterIsEqual)
|
||||
log.V(10).Debugf("Try storage: %#v, typeIsHybrid: %v, typeIsRotate: %v, typeIsSSD: %v, rangeIsNoneAndCountZero: %v, rangeIsNotNoneAndIndexInRange: %v, rangeIsNoneAndSmallThanCount: %v, adapterIsEqual: %v", *storage, typeIsHybrid, typeIsRotate, typeIsSSD, rangeIsNoneAndCountZero, rangeIsNotNoneAndIndexInRange, rangeIsNoneAndSmallThanCount, adapterIsEqual)
|
||||
|
||||
if (typeIsHybrid || typeIsRotate || typeIsSSD) &&
|
||||
(rangeIsNoneAndCountZero || rangeIsNotNoneAndIndexInRange || rangeIsNoneAndSmallThanCount) &&
|
||||
@@ -109,7 +109,11 @@ func RetrieveStorages(diskConfig *api.BaremetalDiskConfig, storages []*Baremetal
|
||||
} else {
|
||||
rest = append(rest, storage)
|
||||
}
|
||||
idx++
|
||||
if confDriver == nil {
|
||||
idx++
|
||||
} else if *confDriver == storage.Driver {
|
||||
idx++
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
@@ -298,10 +302,18 @@ func ExpandNoneConf(layouts []Layout) (ret []Layout) {
|
||||
}
|
||||
|
||||
func GetLayoutRaidConfig(layouts []Layout) []*api.BaremetalDiskConfig {
|
||||
return getLayoutConfig(layouts, true)
|
||||
}
|
||||
|
||||
func GetLayoutDiskConfig(layouts []Layout) []*api.BaremetalDiskConfig {
|
||||
return getLayoutConfig(layouts, false)
|
||||
}
|
||||
|
||||
func getLayoutConfig(layouts []Layout, onlyRaidDisk bool) []*api.BaremetalDiskConfig {
|
||||
var disk []*BaremetalStorage
|
||||
ret := make([]*api.BaremetalDiskConfig, 0)
|
||||
for _, layout := range layouts {
|
||||
if layout.Conf.Conf == DISK_CONF_NONE &&
|
||||
if onlyRaidDisk && layout.Conf.Conf == DISK_CONF_NONE &&
|
||||
sets.NewString(DISK_DRIVER_LINUX, DISK_DRIVER_PCIE).Has(layout.Disks[0].Driver) {
|
||||
continue
|
||||
}
|
||||
@@ -335,8 +347,8 @@ func CalculateLayout(confs []*api.BaremetalDiskConfig, storages []*BaremetalStor
|
||||
noneConf, _ := ParseDiskConfig(DISK_CONF_NONE)
|
||||
conf = &noneConf
|
||||
}
|
||||
selected, storage1 := RetrieveStorages(conf, storages)
|
||||
storages = storage1
|
||||
selected, restStorges := RetrieveStorages(conf, storages)
|
||||
storages = restStorges
|
||||
if len(selected) == 0 {
|
||||
err = fmt.Errorf("Not found matched storages by config: %#v", conf)
|
||||
return
|
||||
|
||||
@@ -19,6 +19,11 @@ import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
|
||||
"yunion.io/x/jsonutils"
|
||||
"yunion.io/x/log"
|
||||
|
||||
api "yunion.io/x/onecloud/pkg/apis/compute"
|
||||
)
|
||||
|
||||
@@ -464,7 +469,9 @@ func TestCalculateLayout(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("Unmarshal expectedLayoutJson err: %v", err)
|
||||
}
|
||||
if !reflect.DeepEqual(layout, expectedLayout) {
|
||||
|
||||
assert := assert.New(t)
|
||||
if !assert.Equal(expectedLayout, layout) {
|
||||
t.Errorf("CalculateLayout() = %v, want %v", layout, expectedLayout)
|
||||
}
|
||||
}
|
||||
@@ -475,7 +482,7 @@ func TestCheckDisksAllocable(t *testing.T) {
|
||||
"6:raid5:adapter2",
|
||||
"6:raid5:adapter2",
|
||||
)
|
||||
bitmainConfs, err := NewBaremetalDiskConfigs("raid10:(60g,)")
|
||||
bitmainConfs, err := NewBaremetalDiskConfigs("MarvelRaid:raid10:(60g,)")
|
||||
if err != nil {
|
||||
t.Fatalf("NewDiskConfigs err: %v", err)
|
||||
}
|
||||
@@ -1255,3 +1262,133 @@ func TestGetSplitSizes(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
pcieStorages = []*BaremetalStorage{
|
||||
{
|
||||
Driver: DISK_DRIVER_LINUX,
|
||||
Rotate: true,
|
||||
Size: 51200,
|
||||
Adapter: 0,
|
||||
Index: 0,
|
||||
},
|
||||
{
|
||||
Driver: DISK_DRIVER_PCIE,
|
||||
Rotate: false,
|
||||
Size: 61440,
|
||||
Adapter: 0,
|
||||
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) {
|
||||
adapter0 := 0
|
||||
confs := []*api.BaremetalDiskConfig{
|
||||
{
|
||||
Adapter: &adapter0,
|
||||
Conf: DISK_CONF_NONE,
|
||||
Count: 1,
|
||||
Driver: DISK_DRIVER_PCIE,
|
||||
Range: []int64{0},
|
||||
Type: DISK_TYPE_SSD,
|
||||
},
|
||||
{
|
||||
Adapter: &adapter0,
|
||||
Conf: DISK_CONF_NONE,
|
||||
Count: 1,
|
||||
Driver: DISK_DRIVER_LINUX,
|
||||
Range: []int64{0},
|
||||
Type: DISK_TYPE_ROTATE,
|
||||
},
|
||||
}
|
||||
|
||||
layouts, err := CalculateLayout(confs, pcieStorages)
|
||||
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: 30720,
|
||||
},
|
||||
{
|
||||
Backend: api.STORAGE_LOCAL,
|
||||
Driver: "scsi",
|
||||
Fs: "ext4",
|
||||
Mountpoint: "/opt",
|
||||
SizeMb: -1,
|
||||
},
|
||||
{
|
||||
Backend: api.STORAGE_LOCAL,
|
||||
Driver: "scsi",
|
||||
Fs: "ext4",
|
||||
Mountpoint: "/data",
|
||||
SizeMb: -1,
|
||||
},
|
||||
}
|
||||
if ok := IsDisksAllocable(layouts, disks); !ok {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -345,7 +345,7 @@ func (self *SBaremetalGuestDriver) RequestStopGuestForDelete(ctx context.Context
|
||||
return guest.StartGuestStopTask(ctx, task.GetUserCred(), true, false, task.GetTaskId())
|
||||
}
|
||||
if host != nil && !host.GetEnabled() && !purge {
|
||||
return fmt.Errorf("fail to contact baremetal")
|
||||
return errors.Errorf("fail to contact baremetal")
|
||||
}
|
||||
task.ScheduleRun(nil)
|
||||
return nil
|
||||
|
||||
@@ -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 {
|
||||
@@ -3534,6 +3537,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) {
|
||||
@@ -3939,6 +3946,25 @@ func (self *SHost) AllowPerformPrepare(ctx context.Context,
|
||||
return db.IsAdminAllowPerform(userCred, self, "prepare")
|
||||
}
|
||||
|
||||
func (self *SHost) HasBMC() bool {
|
||||
ipmiInfo, _ := self.GetIpmiInfo()
|
||||
if ipmiInfo.Username != "" && ipmiInfo.Password != "" {
|
||||
return true
|
||||
}
|
||||
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 {
|
||||
@@ -5623,6 +5649,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")
|
||||
}
|
||||
@@ -5724,7 +5761,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
|
||||
}
|
||||
|
||||
@@ -69,12 +69,13 @@ func (self *BaremetalMaintenanceTask) OnEnterMaintenantModeSucc(ctx context.Cont
|
||||
metadatas["__maint_guest_running"] = guestRunning
|
||||
}
|
||||
baremetal.SetAllMetadata(ctx, metadatas, self.UserCred)
|
||||
baremetal.StartSyncConfig(ctx, self.GetUserCred(), "")
|
||||
self.SetStageComplete(ctx, nil)
|
||||
}
|
||||
|
||||
func (self *BaremetalMaintenanceTask) OnEnterMaintenantModeSuccFailed(ctx context.Context, baremetal *models.SHost, body jsonutils.JSONObject) {
|
||||
self.SetStageFailed(ctx, body)
|
||||
baremetal.StartSyncstatus(ctx, self.UserCred, "")
|
||||
baremetal.StartSyncstatus(ctx, self.GetUserCred(), "")
|
||||
guest := baremetal.GetBaremetalServer()
|
||||
if guest != nil {
|
||||
guest.StartSyncstatus(ctx, self.UserCred, "")
|
||||
|
||||
@@ -57,6 +57,7 @@ func (self *BaremetalServerSyncStatusTask) OnInit(ctx context.Context, obj db.IS
|
||||
func (self *BaremetalServerSyncStatusTask) OnGuestStatusTaskComplete(ctx context.Context, guest *models.SGuest, data jsonutils.JSONObject) {
|
||||
var status string
|
||||
var hostStatus string
|
||||
host := guest.GetHost()
|
||||
if data.Contains("status") {
|
||||
statusStr, _ := data.GetString("status")
|
||||
switch statusStr {
|
||||
@@ -69,6 +70,9 @@ func (self *BaremetalServerSyncStatusTask) OnGuestStatusTaskComplete(ctx context
|
||||
case "admin":
|
||||
status = api.VM_ADMIN
|
||||
hostStatus = api.HOST_STATUS_RUNNING
|
||||
if !host.IsMaintenance && !host.HasBMC() {
|
||||
status = api.VM_READY
|
||||
}
|
||||
default:
|
||||
status = api.VM_INIT
|
||||
hostStatus = api.HOST_STATUS_UNKNOWN
|
||||
@@ -78,7 +82,6 @@ func (self *BaremetalServerSyncStatusTask) OnGuestStatusTaskComplete(ctx context
|
||||
hostStatus = api.HOST_STATUS_UNKNOWN
|
||||
}
|
||||
guest.SetStatus(self.UserCred, status, "BaremetalServerSyncStatusTask")
|
||||
host := guest.GetHost()
|
||||
host.SetStatus(self.UserCred, hostStatus, "BaremetalServerSyncStatusTask")
|
||||
|
||||
self.SetStageComplete(ctx, nil)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -56,6 +56,10 @@ func (self *BaremetalSyncStatusTask) OnSyncstatusComplete(ctx context.Context, b
|
||||
self.SetStageComplete(ctx, nil)
|
||||
}
|
||||
|
||||
func (self *BaremetalSyncStatusTask) OnSyncstatusCompleteFailed(ctx context.Context, baremetal *models.SHost, body jsonutils.JSONObject) {
|
||||
self.SetStageFailed(ctx, body)
|
||||
}
|
||||
|
||||
type BaremetalSyncAllGuestsStatusTask struct {
|
||||
SBaremetalBaseTask
|
||||
}
|
||||
@@ -112,6 +116,10 @@ func (self *BaremetalSyncAllGuestsStatusTask) OnGuestSyncStatusComplete(ctx cont
|
||||
self.SetStageComplete(ctx, nil)
|
||||
}
|
||||
|
||||
func (self *BaremetalSyncAllGuestsStatusTask) OnGuestSyncStatusCompleteFailed(ctx context.Context, baremetal *models.SHost, body jsonutils.JSONObject) {
|
||||
self.SetStageFailed(ctx, body)
|
||||
}
|
||||
|
||||
func init() {
|
||||
taskman.RegisterTask(BaremetalSyncStatusTask{})
|
||||
taskman.RegisterTask(BaremetalSyncAllGuestsStatusTask{})
|
||||
|
||||
@@ -63,10 +63,11 @@ func (self *BaremetalUnmaintenanceTask) OnUnmaintenantComplete(ctx context.Conte
|
||||
"__maint_password": "None",
|
||||
"__maint_ip": "None",
|
||||
}
|
||||
baremetal.SetAllMetadata(ctx, metadatas, self.UserCred)
|
||||
baremetal.SetAllMetadata(ctx, metadatas, self.GetUserCred())
|
||||
self.SetStageComplete(ctx, nil)
|
||||
guest := baremetal.GetBaremetalServer()
|
||||
if guest != nil {
|
||||
guest.StartSyncstatus(ctx, self.UserCred, "")
|
||||
guest.StartSyncstatus(ctx, self.GetUserCred(), "")
|
||||
}
|
||||
baremetal.StartSyncConfig(ctx, self.GetUserCred(), "")
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
+29
-4
@@ -144,18 +144,23 @@ func (s *Client) GetConfig() ClientConfig {
|
||||
}
|
||||
|
||||
func (s *Client) RawRun(cmds ...string) ([]string, error) {
|
||||
return s.run(false, cmds, nil)
|
||||
return s.run(false, cmds, nil, false)
|
||||
}
|
||||
|
||||
func (s *Client) Run(cmds ...string) ([]string, error) {
|
||||
return s.run(true, cmds, nil)
|
||||
return s.run(true, cmds, nil, false)
|
||||
}
|
||||
|
||||
func (s *Client) RunWithInput(input io.Reader, cmds ...string) ([]string, error) {
|
||||
return s.run(true, cmds, input)
|
||||
return s.run(true, cmds, input, false)
|
||||
}
|
||||
|
||||
func (s *Client) run(parseOutput bool, cmds []string, input io.Reader) ([]string, error) {
|
||||
// RunWithTTY request Pty before run command.
|
||||
func (s *Client) RunWithTTY(cmds ...string) ([]string, error) {
|
||||
return s.run(false, cmds, nil, true)
|
||||
}
|
||||
|
||||
func (s *Client) run(parseOutput bool, cmds []string, input io.Reader, withPty bool) ([]string, error) {
|
||||
ret := []string{}
|
||||
for _, cmd := range cmds {
|
||||
session, err := s.client.NewSession()
|
||||
@@ -163,6 +168,18 @@ func (s *Client) run(parseOutput bool, cmds []string, input io.Reader) ([]string
|
||||
return nil, err
|
||||
}
|
||||
defer session.Close()
|
||||
|
||||
if withPty {
|
||||
modes := ssh.TerminalModes{
|
||||
ssh.ECHO: 1, // enable echoing
|
||||
ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
|
||||
ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
|
||||
}
|
||||
if err := session.RequestPty("xterm", 24, 80, modes); err != nil {
|
||||
return nil, errors.Wrap(err, "Setup TTY")
|
||||
}
|
||||
}
|
||||
|
||||
log.Debugf("Run command: %s", cmd)
|
||||
var stdOut bytes.Buffer
|
||||
var stdErr bytes.Buffer
|
||||
@@ -255,3 +272,11 @@ func (s *Client) RunTerminal() error {
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsExitMissingError(err error) bool {
|
||||
errStr := new(ssh.ExitMissingError).Error()
|
||||
if strings.Contains(err.Error(), errStr) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user