diff --git a/cmd/climc/shell/hosts.go b/cmd/climc/shell/hosts.go index 33c3c4bb2d..46811f67ae 100644 --- a/cmd/climc/shell/hosts.go +++ b/cmd/climc/shell/hosts.go @@ -51,6 +51,8 @@ func init() { StorageNotAttached bool `help:"List hosts not attach specified storage"` + Uuid string `help:"find host with given system uuid"` + options.BaseListOptions } R(&HostListOptions{}, "host-list", "List hosts", func(s *mcclient.ClientSession, opts *HostListOptions) error { @@ -69,6 +71,9 @@ func init() { } else if opts.Disabled { params.Add(jsonutils.NewInt(0), "enabled") } + if len(opts.Uuid) > 0 { + params.Add(jsonutils.NewString(opts.Uuid), "uuid") + } result, err := modules.Hosts.List(s, params) if err != nil { return err @@ -193,7 +198,8 @@ func init() { CpuReserved int64 `help:"CPU reserved"` HostType string `help:"Change host type, CAUTION!!!!" choices:"hypervisor|kubelet|esxi|baremetal"` // AccessIp string `help:"Change access ip, CAUTION!!!!"` - AccessMac string `help:"Change access MAC, CAUTION!!!!"` + AccessMac string `help:"Change baremetal access MAC, CAUTION!!!!"` + Uuid string `help:"Change baremetal UUID, CAUTION!!!!"` } R(&HostUpdateOptions{}, "host-update", "Update information of a host", func(s *mcclient.ClientSession, args *HostUpdateOptions) error { params := jsonutils.NewDict() @@ -221,6 +227,9 @@ func init() { if len(args.AccessMac) > 0 { params.Add(jsonutils.NewString(args.AccessMac), "access_mac") } + if len(args.Uuid) > 0 { + params.Add(jsonutils.NewString(args.Uuid), "uuid") + } if params.Size() == 0 { return fmt.Errorf("Not data to update") } @@ -485,9 +494,12 @@ func init() { AccessNet string `help:"Access network"` AccessWire string `help:"Access wire"` - NoPrepare bool `help:"just initialize, do not reboot baremetal to prepare"` + NoProbe bool `help:"just save the record, do not probe"` + NoPrepare bool `help:"just probe, do not reboot baremetal to prepare"` DisablePxeBoot bool `help:"set enable_pxe_boot to false, which is true by default"` + + Uuid string `help:"host uuid"` } R(&HostCreateOptions{}, "host-create", "Create a baremetal host", func(s *mcclient.ClientSession, args *HostCreateOptions) error { params := jsonutils.NewDict() @@ -518,12 +530,18 @@ func init() { if len(args.AccessWire) > 0 { params.Add(jsonutils.NewString(args.AccessWire), "access_wire") } + if args.NoProbe { + params.Add(jsonutils.JSONTrue, "no_probe") + } if args.NoPrepare { params.Add(jsonutils.JSONTrue, "no_prepare") } if args.DisablePxeBoot { params.Add(jsonutils.JSONFalse, "enable_pxe_boot") } + if len(args.Uuid) > 0 { + params.Add(jsonutils.NewString(args.Uuid), "uuid") + } result, err := modules.Hosts.Create(s, params) if err != nil { return err diff --git a/pkg/apis/compute/host_const.go b/pkg/apis/compute/host_const.go index 33d0121aed..95a8bf6276 100644 --- a/pkg/apis/compute/host_const.go +++ b/pkg/apis/compute/host_const.go @@ -117,3 +117,8 @@ var NIC_TYPES = []string{NIC_TYPE_IPMI, NIC_TYPE_ADMIN} const ( ACCESS_MAC_ANY = "00:00:00:00:00:00" ) + +const ( + BOOT_MODE_PXE = "pxe" + BOOT_MODE_ISO = "iso" +) diff --git a/pkg/baremetal/manager.go b/pkg/baremetal/manager.go index aa17ece00c..2a7ac18bf1 100644 --- a/pkg/baremetal/manager.go +++ b/pkg/baremetal/manager.go @@ -1028,7 +1028,7 @@ LABEL start fmt.Sprintf("token=%s", auth.GetTokenString()), fmt.Sprintf("url=%s", b.GetNotifyUrl()), } - bootmode := "pxe" + bootmode := api.BOOT_MODE_PXE if !isTftp { adminNic := b.GetAdminNic() var addr string @@ -1052,7 +1052,7 @@ LABEL start args = append(args, fmt.Sprintf("gateway=%s", gateway)) args = append(args, fmt.Sprintf("addr=%s", addr)) args = append(args, fmt.Sprintf("mask=%s", mask)) - bootmode = "iso" + bootmode = api.BOOT_MODE_ISO } args = append(args, fmt.Sprintf("bootmod=%s", bootmode)) resp += fmt.Sprintf(" append %s\n", strings.Join(args, " ")) diff --git a/pkg/baremetal/pxe/dhcp.go b/pkg/baremetal/pxe/dhcp.go index 47b07ad04e..852add8b28 100644 --- a/pkg/baremetal/pxe/dhcp.go +++ b/pkg/baremetal/pxe/dhcp.go @@ -15,12 +15,11 @@ package pxe import ( + "encoding/hex" "fmt" "net" "strings" - "github.com/golang-plus/uuid" - "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" @@ -28,6 +27,7 @@ import ( api "yunion.io/x/onecloud/pkg/apis/compute" o "yunion.io/x/onecloud/pkg/baremetal/options" "yunion.io/x/onecloud/pkg/cloudcommon/types" + "yunion.io/x/onecloud/pkg/httperrors" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/modulebase" "yunion.io/x/onecloud/pkg/mcclient/modules" @@ -143,18 +143,36 @@ func (h *DHCPHandler) newRequest(pkt dhcp.Packet, man IBaremetalManager) (*dhcpR log.Errorf("[DHCP] parse vendor option %d error: %v", optCode, err) } } - var cliUUID uuid.UUID + var cliUUIDStr string if len(cliGuid) == 17 { - copy(cliUUID[:], []byte(cliGuid)[1:]) + cliUUIDStr = formatUuidString([]byte(cliGuid)[1:]) } req.VendorClassId = vendorClsId req.ClientArch = cliArch req.NetworkInterfaceIdent = netIfIdent - req.ClientGuid = cliUUID.String() + req.ClientGuid = cliUUIDStr log.Debugf("Client GUID: %s", req.ClientGuid) return req, err } +func swapBytes(input []byte) []byte { + output := make([]byte, len(input)) + for i := range input { + output[i] = input[len(input)-1-i] + } + return output +} + +func formatUuidString(uuidBytes []byte) string { + return strings.Join([]string{ + hex.EncodeToString(swapBytes(uuidBytes[0:4])), + hex.EncodeToString(swapBytes(uuidBytes[4:6])), + hex.EncodeToString(swapBytes(uuidBytes[6:8])), + hex.EncodeToString(uuidBytes[8:10]), + hex.EncodeToString(uuidBytes[10:16]), + }, "-") +} + func (req *dhcpRequest) fetchConfig(session *mcclient.ClientSession) (*dhcp.ResponseConfig, error) { // 1. find_network_conf netConf, err := req.findNetworkConf(session, false) @@ -247,6 +265,19 @@ func (req *dhcpRequest) findNetworkConf(session *mcclient.ClientSession, filterU return &network, err } +func (req *dhcpRequest) findBaremetalsByUuid(session *mcclient.ClientSession) (*modulebase.ListResult, error) { + params := jsonutils.NewDict() + params.Add(jsonutils.NewString(req.ClientGuid), "uuid") + ret, err := modules.Hosts.List(session, params) + if err != nil { + return nil, err + } + if len(ret.Data) > 1 { + return nil, httperrors.NewDuplicateResourceError("duplicate uuid %s", req.ClientGuid) + } + return ret, nil +} + func (req *dhcpRequest) findBaremetalsOfAnyMac(session *mcclient.ClientSession, isBaremetal bool) (*modulebase.ListResult, error) { params := jsonutils.NewDict() params.Add(jsonutils.NewString(req.ClientMac.String()), "any_mac") @@ -260,15 +291,24 @@ 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) { - ret, err := req.findBaremetalsOfAnyMac(session, true) + // first try UUID + ret, err := req.findBaremetalsByUuid(session) if err != nil { return nil, err } if len(ret.Data) == 0 { - ret, err = req.findBaremetalsOfAnyMac(session, false) + // 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 host_type=baremetal + ret, err = req.findBaremetalsOfAnyMac(session, false) + if err != nil { + return nil, err + } + } } switch len(ret.Data) { case 0: diff --git a/pkg/baremetal/tasks/baseprepare.go b/pkg/baremetal/tasks/baseprepare.go index d9b98fc508..0ead217498 100644 --- a/pkg/baremetal/tasks/baseprepare.go +++ b/pkg/baremetal/tasks/baseprepare.go @@ -364,6 +364,8 @@ func (task *sBaremetalPrepareTask) updateBmInfo(cli *ssh.Client, i *baremetalPre size, diskType := task.collectDiskInfo(i.diskInfo) updateInfo["storage_size"] = size updateInfo["storage_type"] = diskType + updateInfo["boot_mode"] = getBootMode(cli) + updateInfo["uuid"] = getSystemGuid(cli) updateData := jsonutils.Marshal(updateInfo) updateData.(*jsonutils.JSONDict).Update(i.ipmiInfo.ToPrepareParams()) _, err := modules.Hosts.Update(task.getClientSession(), task.baremetal.GetId(), updateData) @@ -541,6 +543,7 @@ type ipmiIPConfig struct { func (task *sBaremetalPrepareTask) getIPMIIPConfig(ipAddr string) (*ipmiIPConfig, error) { params := jsonutils.NewDict() params.Add(jsonutils.NewString(ipAddr), "ip") + params.Add(jsonutils.JSONTrue, "is_on_premise") listRet, err := modules.Networks.List(task.getClientSession(), params) if err != nil { return nil, err @@ -577,6 +580,39 @@ func getCPUInfo(cli *ssh.Client) (*types.SCPUInfo, error) { return sysutils.ParseCPUInfo(ret) } +func getBootMode(cli *ssh.Client) string { + lines, err := cli.Run("cat /proc/cmdline") + if err != nil { + return api.BOOT_MODE_PXE + } + const ( + key = "bootmode=" + ) + for _, line := range lines { + pos := strings.Index(line, key) + if pos >= 0 { + if strings.HasPrefix(line[pos+len(key):], api.BOOT_MODE_ISO) { + return api.BOOT_MODE_ISO + } + } + } + return api.BOOT_MODE_PXE +} + +func getSystemGuid(cli *ssh.Client) string { + lines, err := cli.Run("/usr/sbin/dmidecode -s system-uuid") + if err != nil { + return "" + } + for _, line := range lines { + line = strings.TrimSpace(line) + if len(line) > 0 { + return sysutils.NormalizeUuid(line) + } + } + return "" +} + func getDMICPUInfo(cli *ssh.Client) (*types.SDMICPUInfo, error) { ret, err := cli.Run("/usr/sbin/dmidecode -t 4") if err != nil { diff --git a/pkg/baremetal/tasks/ipmiprobe.go b/pkg/baremetal/tasks/ipmiprobe.go index ffe093405f..73f9e9846c 100644 --- a/pkg/baremetal/tasks/ipmiprobe.go +++ b/pkg/baremetal/tasks/ipmiprobe.go @@ -206,7 +206,9 @@ func (self *SBaremetalIpmiProbeTask) doRawIpmiProbe(ctx context.Context, cli ipm } updateInfo["sys_info"] = dmiSysInfo } - if len(guid) > 0 { + // XXX + // Qemu's IPMI guid is not correct, just ignore it + if len(guid) > 0 && sysInfo.SN != "" && sysInfo.SN != "Not Specified" { updateInfo["uuid"] = guid } updateInfo["is_baremetal"] = true diff --git a/pkg/baremetal/utils/ipmitool/ipmitool.go b/pkg/baremetal/utils/ipmitool/ipmitool.go index 4da985b933..1bd4c05d60 100644 --- a/pkg/baremetal/utils/ipmitool/ipmitool.go +++ b/pkg/baremetal/utils/ipmitool/ipmitool.go @@ -149,7 +149,7 @@ func GetSysGuid(exector IPMIExecutor) string { for _, line := range lines { key, val := stringutils.SplitKeyValue(line) if key == "System GUID" { - return val + return sysutils.NormalizeUuid(val) } } return "" diff --git a/pkg/compute/models/hosts.go b/pkg/compute/models/hosts.go index 47ad9d305d..371108d5b0 100644 --- a/pkg/compute/models/hosts.go +++ b/pkg/compute/models/hosts.go @@ -139,7 +139,8 @@ type SHost struct { EnablePxeBoot tristate.TriState `nullable:"false" default:"true" list:"admin" create:"admin_optional" update:"admin"` - Uuid string `nullable:"true" list:"admin" update:"admin" create:"admin_optional"` + Uuid string `width:"64" nullable:"true" list:"admin" update:"admin" create:"admin_optional"` + BootMode string `width:"8" nullable:"true" list:"admin" update:"admin" create:"admin_optional"` } func (manager *SHostManager) GetContextManagers() [][]db.IModelManager { @@ -621,7 +622,7 @@ func (self *SHost) GetBaremetalstorage() *SHoststorage { } return &hs } - log.Errorf("Cannot find baremetalstorage??") + // log.Errorf("Cannot find baremetalstorage??") return nil } @@ -2259,20 +2260,6 @@ func (self *SHost) getGuestsResource(status string) *SHostGuestResourceUsage { } func (self *SHost) getMoreDetails(ctx context.Context, extra *jsonutils.JSONDict) *jsonutils.JSONDict { - /*zone := self.GetZone() - if zone != nil { - extra.Add(jsonutils.NewString(zone.Id), "zone_id") - extra.Add(jsonutils.NewString(zone.Name), "zone") - if len(zone.ExternalId) > 0 { - extra.Add(jsonutils.NewString(zone.ExternalId), "") - } - region := zone.GetRegion() - if region != nil { - extra.Add(jsonutils.NewString(zone.GetRegion().GetName()), "region") - extra.Add(jsonutils.NewString(zone.GetRegion().GetId()), "region_id") - } - }*/ - info := self.getCloudProviderInfo() extra.Update(jsonutils.Marshal(&info)) @@ -2346,6 +2333,16 @@ func (self *SHost) getMoreDetails(ctx context.Context, extra *jsonutils.JSONDict extra.Add(jsonutils.JSONFalse, "is_prepaid_recycle") } + if self.IsBaremetal { + err := self.canPrepare() + if err == nil { + extra.Add(jsonutils.JSONTrue, "can_prepare") + } else { + extra.Add(jsonutils.JSONFalse, "can_prepare") + extra.Add(jsonutils.NewString(err.Error()), "prepare_fail_reason") + } + } + return extra } @@ -2457,8 +2454,12 @@ func (self *SHost) PostCreate(ctx context.Context, userCred mcclient.TokenCreden if len(accessMac) > 0 { self.setAccessMac(userCred, accessMac) } - if len(self.ZoneId) > 0 && self.HostType == api.HOST_TYPE_BAREMETAL { - self.StartBaremetalCreateTask(ctx, userCred, kwargs, "") + noProbe := jsonutils.QueryBoolean(data, "no_probe", false) + if len(self.ZoneId) > 0 && self.HostType == api.HOST_TYPE_BAREMETAL && !noProbe { + ipmiInfo, _ := self.GetIpmiInfo() + if len(ipmiInfo.IpAddr) > 0 { + self.StartBaremetalCreateTask(ctx, userCred, kwargs, "") + } } } @@ -2575,6 +2576,8 @@ func (manager *SHostManager) ValidateCreateData(ctx context.Context, userCred mc data.Set("zone_id", jsonutils.NewString(zoneObj.GetId())) } + noProbe := jsonutils.QueryBoolean(data, "no_probe", false) + data, err := manager.inputUniquenessCheck(data, zoneId, "") if err != nil { return nil, err @@ -2604,7 +2607,10 @@ func (manager *SHostManager) ValidateCreateData(ctx context.Context, userCred mc return nil, httperrors.NewInputParameterError("%s", err) } ipmiIpAddr, _ := ipmiInfo.GetString("ip_addr") - if len(ipmiIpAddr) > 0 { + if len(ipmiIpAddr) == 0 { + noProbe = true + } + if len(ipmiIpAddr) > 0 && !noProbe { net, _ := NetworkManager.GetOnPremiseNetworkOfIP(ipmiIpAddr, "", tristate.None) if net == nil { return nil, httperrors.NewInputParameterError("%s is out of network IP ranges", ipmiIpAddr) @@ -2624,77 +2630,79 @@ func (manager *SHostManager) ValidateCreateData(ctx context.Context, userCred mc } data.Set("zone_id", jsonutils.NewString(zoneObj.GetId())) } - var accessNet *SNetwork - accessIpAddr, _ := data.GetString("access_ip") - if len(accessIpAddr) > 0 { - net, _ := NetworkManager.GetOnPremiseNetworkOfIP(accessIpAddr, "", tristate.None) - if net == nil { - return nil, httperrors.NewInputParameterError("%s is out of network IP ranges", accessIpAddr) - } - accessNet = net - } else { - accessNetStr, _ := data.GetString("access_net") - if len(accessNetStr) > 0 { - netObj, err := NetworkManager.FetchByIdOrName(userCred, accessNetStr) - if err != nil { - if errors.Cause(err) == sql.ErrNoRows { - return nil, httperrors.NewResourceNotFoundError2("network", accessNetStr) - } else { - return nil, httperrors.NewGeneralError(err) - } + if !noProbe { + var accessNet *SNetwork + accessIpAddr, _ := data.GetString("access_ip") + if len(accessIpAddr) > 0 { + net, _ := NetworkManager.GetOnPremiseNetworkOfIP(accessIpAddr, "", tristate.None) + if net == nil { + return nil, httperrors.NewInputParameterError("%s is out of network IP ranges", accessIpAddr) } - accessNet = netObj.(*SNetwork) + accessNet = net } else { - accessWireStr, _ := data.GetString("access_wire") - if len(accessWireStr) > 0 { - wireObj, err := WireManager.FetchByIdOrName(userCred, accessWireStr) + accessNetStr, _ := data.GetString("access_net") + if len(accessNetStr) > 0 { + netObj, err := NetworkManager.FetchByIdOrName(userCred, accessNetStr) if err != nil { if errors.Cause(err) == sql.ErrNoRows { - return nil, httperrors.NewResourceNotFoundError2("wire", accessWireStr) + return nil, httperrors.NewResourceNotFoundError2("network", accessNetStr) } else { return nil, httperrors.NewGeneralError(err) } } - wire := wireObj.(*SWire) - lockman.LockObject(ctx, wire) - defer lockman.ReleaseObject(ctx, wire) - net, err := wire.GetCandidatePrivateNetwork(userCred, false, []string{api.NETWORK_TYPE_PXE, api.NETWORK_TYPE_BAREMETAL, api.NETWORK_TYPE_GUEST}) - if err != nil { - return nil, httperrors.NewGeneralError(err) + accessNet = netObj.(*SNetwork) + } else { + accessWireStr, _ := data.GetString("access_wire") + if len(accessWireStr) > 0 { + wireObj, err := WireManager.FetchByIdOrName(userCred, accessWireStr) + if err != nil { + if errors.Cause(err) == sql.ErrNoRows { + return nil, httperrors.NewResourceNotFoundError2("wire", accessWireStr) + } else { + return nil, httperrors.NewGeneralError(err) + } + } + wire := wireObj.(*SWire) + lockman.LockObject(ctx, wire) + defer lockman.ReleaseObject(ctx, wire) + net, err := wire.GetCandidatePrivateNetwork(userCred, false, []string{api.NETWORK_TYPE_PXE, api.NETWORK_TYPE_BAREMETAL, api.NETWORK_TYPE_GUEST}) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } + accessNet = net } - accessNet = net } } - } - if accessNet != nil { - lockman.LockObject(ctx, accessNet) - defer lockman.ReleaseObject(ctx, accessNet) + if accessNet != nil { + lockman.LockObject(ctx, accessNet) + defer lockman.ReleaseObject(ctx, accessNet) - accessIp, err := accessNet.GetFreeIP(ctx, userCred, nil, nil, accessIpAddr, api.IPAllocationNone, false) - if err != nil { - return nil, httperrors.NewGeneralError(err) - } + accessIp, err := accessNet.GetFreeIP(ctx, userCred, nil, nil, accessIpAddr, api.IPAllocationNone, false) + if err != nil { + return nil, httperrors.NewGeneralError(err) + } - if len(accessIpAddr) > 0 && accessIpAddr != accessIp { - return nil, httperrors.NewConflictError("Access ip %s has been used", accessIpAddr) - } + if len(accessIpAddr) > 0 && accessIpAddr != accessIp { + return nil, httperrors.NewConflictError("Access ip %s has been used", accessIpAddr) + } - zoneObj := accessNet.getZone() - if zoneObj == nil { - return nil, httperrors.NewInputParameterError("Access network has no zone???") - } - originZoneId, _ := data.GetString("zone_id") - if len(originZoneId) > 0 && originZoneId != zoneObj.GetId() { - return nil, httperrors.NewInputParameterError("Access address located in different zone than specified") - } + zoneObj := accessNet.getZone() + if zoneObj == nil { + return nil, httperrors.NewInputParameterError("Access network has no zone???") + } + originZoneId, _ := data.GetString("zone_id") + if len(originZoneId) > 0 && originZoneId != zoneObj.GetId() { + return nil, httperrors.NewInputParameterError("Access address located in different zone than specified") + } - // reserve this IP temporarily - err = accessNet.reserveIpWithDuration(ctx, userCred, accessIp, "reserve for baremetal access IP", 30*time.Minute) - if err != nil { - return nil, err + // reserve this IP temporarily + err = accessNet.reserveIpWithDuration(ctx, userCred, accessIp, "reserve for baremetal access IP", 30*time.Minute) + if err != nil { + return nil, err + } + data.Set("access_ip", jsonutils.NewString(accessIp)) + data.Set("zone_id", jsonutils.NewString(zoneObj.GetId())) } - data.Set("access_ip", jsonutils.NewString(accessIp)) - data.Set("zone_id", jsonutils.NewString(zoneObj.GetId())) } ipmiPasswd, _ := ipmiInfo.GetString("password") if len(ipmiPasswd) > 0 && !seclib2.MeetComplxity(ipmiPasswd) { @@ -2709,6 +2717,15 @@ func (manager *SHostManager) ValidateCreateData(ctx context.Context, userCred mc if hostType == api.HOST_TYPE_BAREMETAL { data.Set("is_baremetal", jsonutils.JSONTrue) } + + if noProbe { + accessMac, _ := data.GetString("access_mac") + uuid, _ := data.GetString("uuid") + if len(accessMac) == 0 && len(uuid) == 0 { + return nil, httperrors.NewInputParameterError("missing access_mac and uuid in no_probe mode") + } + } + return manager.SEnabledStatusStandaloneResourceBaseManager.ValidateCreateData(ctx, userCred, ownerId, query, data) } @@ -3114,22 +3131,34 @@ func (self *SHost) isRedfishCapable() bool { return false } +func (self *SHost) canPrepare() error { + if !self.IsBaremetal { + return httperrors.NewInvalidStatusError("not a baremetal") + } + if !self.isRedfishCapable() && len(self.AccessMac) == 0 && len(self.Uuid) == 0 { + return httperrors.NewInvalidStatusError("need valid access_mac and uuid to do prepare") + } + if !utils.IsInStringArray(self.Status, []string{api.BAREMETAL_READY, api.BAREMETAL_RUNNING, api.BAREMETAL_PREPARE_FAIL}) { + return httperrors.NewInvalidStatusError("Cannot prepare baremetal in status %s", self.Status) + } + server := self.GetBaremetalServer() + if server != nil && server.Status != api.VM_ADMIN { + return httperrors.NewInvalidStatusError("Cannot prepare baremetal in server status %s", server.Status) + } + return nil +} + func (self *SHost) PerformPrepare(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { - if !self.isRedfishCapable() && len(self.AccessMac) == 0 { - return nil, httperrors.NewInvalidStatusError("need valid access_mac to do prepare") + err := self.canPrepare() + if err != nil { + return nil, err } - if utils.IsInStringArray(self.Status, []string{api.BAREMETAL_READY, api.BAREMETAL_RUNNING, api.BAREMETAL_PREPARE_FAIL}) { - var onfinish string - if self.GetBaremetalServer() != nil { - if self.Status == api.BAREMETAL_RUNNING { - onfinish = "restart" - } else if self.Status == api.BAREMETAL_READY { - onfinish = "shotdown" - } - } - return nil, self.StartPrepareTask(ctx, userCred, onfinish, "") + var onfinish string + server := self.GetBaremetalServer() + if server != nil && self.Status == api.BAREMETAL_READY { + onfinish = "shutdown" } - return nil, httperrors.NewInvalidStatusError("Cannot prepare baremetal in status %s", self.Status) + return nil, self.StartPrepareTask(ctx, userCred, onfinish, "") } func (self *SHost) StartPrepareTask(ctx context.Context, userCred mcclient.TokenCredential, onfinish, parentTaskId string) error { @@ -3436,6 +3465,7 @@ func (self *SHost) AllowPerformEnableNetif(ctx context.Context, } func (self *SHost) PerformEnableNetif(ctx context.Context, userCred mcclient.TokenCredential, query jsonutils.JSONObject, data jsonutils.JSONObject) (jsonutils.JSONObject, error) { + log.Debugf("enable_netif %s", data) mac, _ := data.GetString("mac") netif := self.GetNetInterface(mac) if netif == nil { @@ -3460,6 +3490,7 @@ func (self *SHost) PerformEnableNetif(ctx context.Context, userCred mcclient.Tok func (self *SHost) EnableNetif(ctx context.Context, userCred mcclient.TokenCredential, netif *SNetInterface, network, ipAddr, allocDir string, netType string, reserve, requireDesignatedIp bool) error { bn := netif.GetBaremetalNetwork() if bn != nil { + log.Debugf("Netif has been attach2network? %s", jsonutils.Marshal(bn)) return nil } var net *SNetwork @@ -3515,15 +3546,15 @@ func (self *SHost) EnableNetif(ctx context.Context, userCred mcclient.TokenCrede } else if net.WireId != wire.Id { return fmt.Errorf("conflict??? candiate net is not on wire") } - err = self.Attach2Network(ctx, userCred, netif, net, ipAddr, allocDir, reserve, requireDesignatedIp) + bn, err = self.Attach2Network(ctx, userCred, netif, net, ipAddr, allocDir, reserve, requireDesignatedIp) if err != nil { return errors.Wrap(err, "self.Attach2Network") } switch netif.NicType { case api.NIC_TYPE_IPMI: - err = self.setIpmiIp(userCred, ipAddr) + err = self.setIpmiIp(userCred, bn.IpAddr) case api.NIC_TYPE_ADMIN: - err = self.setAccessIp(userCred, ipAddr) + err = self.setAccessIp(userCred, bn.IpAddr) } return err } @@ -3571,17 +3602,16 @@ func (self *SHost) DisableNetif(ctx context.Context, userCred mcclient.TokenCred return err } -func (self *SHost) Attach2Network(ctx context.Context, userCred mcclient.TokenCredential, netif *SNetInterface, net *SNetwork, ipAddr, allocDir string, reserved, requireDesignatedIp bool) error { +func (self *SHost) Attach2Network(ctx context.Context, userCred mcclient.TokenCredential, netif *SNetInterface, net *SNetwork, ipAddr, allocDir string, reserved, requireDesignatedIp bool) (*SHostnetwork, error) { lockman.LockObject(ctx, net) defer lockman.ReleaseObject(ctx, net) freeIp, err := net.GetFreeIP(ctx, userCred, nil, nil, ipAddr, api.IPAllocationDirection(allocDir), reserved) if err != nil { - log.Errorf("attach2network: %s", err) - return err + return nil, errors.Wrap(err, "net.GetFreeIP") } if len(ipAddr) > 0 && ipAddr != freeIp && requireDesignatedIp { - return fmt.Errorf("IP address %s is occupied, get %s instead", ipAddr, freeIp) + return nil, fmt.Errorf("IP address %s is occupied, get %s instead", ipAddr, freeIp) } bn := &SHostnetwork{} bn.BaremetalId = self.Id @@ -3590,13 +3620,12 @@ func (self *SHost) Attach2Network(ctx context.Context, userCred mcclient.TokenCr bn.MacAddr = netif.Mac err = HostnetworkManager.TableSpec().Insert(bn) if err != nil { - log.Errorf("HostnetworkManager.TableSpec().Insert fail %s", err) - return err + return nil, errors.Wrap(err, "HostnetworkManager.TableSpec().Insert") } db.OpsLog.LogAttachEvent(ctx, self, net, userCred, jsonutils.NewString(freeIp)) self.UpdateDnsRecord(netif, true) net.UpdateBaremetalNetmap(bn, self.GetNetifName(netif)) - return nil + return bn, nil } func (self *SHost) AllowPerformRemoveNetif(ctx context.Context, diff --git a/pkg/compute/tasks/baremetal_create_task.go b/pkg/compute/tasks/baremetal_create_task.go index 9d453c6b8a..e90f94f8a5 100644 --- a/pkg/compute/tasks/baremetal_create_task.go +++ b/pkg/compute/tasks/baremetal_create_task.go @@ -18,7 +18,9 @@ import ( "context" "yunion.io/x/jsonutils" + "yunion.io/x/log" + api "yunion.io/x/onecloud/pkg/apis/compute" "yunion.io/x/onecloud/pkg/cloudcommon/db" "yunion.io/x/onecloud/pkg/cloudcommon/db/taskman" "yunion.io/x/onecloud/pkg/compute/models" @@ -53,8 +55,11 @@ func (self *BaremetalCreateTask) OnIpmiProbeComplete(ctx context.Context, obj db self.SetStageComplete(ctx, nil) return } - if baremetal.AccessMac == "" && !ipmiInfo.CdromBoot { - self.SetStageComplete(ctx, nil) + if baremetal.AccessMac == "" && baremetal.Uuid == "" && !ipmiInfo.CdromBoot { + msg := "Fail to find access_mac or uuid, host-prepare aborted. Please supply either access_mac or uuid and try host-prepare" + log.Errorf(msg) + self.SetStageFailed(ctx, msg) + baremetal.SetStatus(self.UserCred, api.BAREMETAL_PREPARE_FAIL, msg) return } self.SetStage("OnPrepareComplete", nil) diff --git a/pkg/multicloud/aliyun/wrapper.go b/pkg/multicloud/aliyun/wrapper.go index beca04c670..514f683268 100644 --- a/pkg/multicloud/aliyun/wrapper.go +++ b/pkg/multicloud/aliyun/wrapper.go @@ -15,7 +15,7 @@ package aliyun import ( - "errors" + "runtime/debug" "github.com/aliyun/alibaba-cloud-sdk-go/sdk" "github.com/aliyun/alibaba-cloud-sdk-go/sdk/requests" @@ -23,17 +23,18 @@ import ( "yunion.io/x/jsonutils" "yunion.io/x/log" + "yunion.io/x/pkg/errors" ) func processCommonRequest(client *sdk.Client, req *requests.CommonRequest) (response *responses.CommonResponse, err error) { defer func() { if r := recover(); r != nil { log.Errorf("client.ProcessCommonRequest error: %s", r) - // debug.PrintStack() + debug.PrintStack() response = nil jsonError := jsonutils.NewDict() jsonError.Add(jsonutils.NewString("SignatureNonceUsed"), "Code") - err = errors.New(jsonError.String()) + err = errors.Error(jsonError.String()) } }() return client.ProcessCommonRequest(req) diff --git a/pkg/util/sysutils/sysutils.go b/pkg/util/sysutils/sysutils.go index 1476df680b..847939e334 100644 --- a/pkg/util/sysutils/sysutils.go +++ b/pkg/util/sysutils/sysutils.go @@ -347,3 +347,15 @@ func ParseIPMIUser(lines []string) []compute.IPMIUser { } return ret } + +const ( + UUID_EMPTY = "00000000-0000-0000-0000-000000000000" +) + +func NormalizeUuid(uuid string) string { + uuid = strings.ToLower(uuid) + if uuid == UUID_EMPTY { + uuid = "" + } + return uuid +}