// 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 tasks import ( "context" "fmt" "net" "strings" "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/pkg/errors" api "yunion.io/x/onecloud/pkg/apis/compute" o "yunion.io/x/onecloud/pkg/baremetal/options" "yunion.io/x/onecloud/pkg/baremetal/profiles" "yunion.io/x/onecloud/pkg/baremetal/utils/ipmitool" "yunion.io/x/onecloud/pkg/cloudcommon/types" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/auth" modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute" "yunion.io/x/onecloud/pkg/util/redfish" "yunion.io/x/onecloud/pkg/util/ssh" "yunion.io/x/onecloud/pkg/util/sysutils" ) type sBaremetalRegisterTask struct { sBaremetalPrepareTask BmManager IBmManager SshCli *ssh.Client Hostname string RemoteIp string IpmiUsername string IpmiPassword string IpmiIpAddr string IpmiMac net.HardwareAddr IpmiLanChannel uint8 AdminWire string IpmiWire string accessNic *types.SNicDevInfo } func NewBaremetalRegisterTask( userCred mcclient.TokenCredential, bmManager IBmManager, sshCli *ssh.Client, hostname, remoteIp, ipmiUsername, ipmiPassword, ipmiIpAddr string, ipmiMac net.HardwareAddr, ipmiLanChannel uint8, adminWire, ipmiWire string) *sBaremetalRegisterTask { return &sBaremetalRegisterTask{ sBaremetalPrepareTask: sBaremetalPrepareTask{userCred: userCred}, BmManager: bmManager, SshCli: sshCli, Hostname: hostname, RemoteIp: remoteIp, IpmiUsername: ipmiUsername, IpmiPassword: ipmiPassword, IpmiIpAddr: ipmiIpAddr, IpmiMac: ipmiMac, IpmiLanChannel: ipmiLanChannel, AdminWire: adminWire, IpmiWire: ipmiWire, } } func (s *sBaremetalRegisterTask) getSession() *mcclient.ClientSession { return auth.GetSession(context.Background(), s.userCred, o.Options.Region) } func (s *sBaremetalRegisterTask) getAccessDevMacAddr(ip string) (string, error) { var dev string var nicsRet []string var err error if strings.Contains(ip, ":") { // ipv6 nicsRet, err = s.SshCli.Run("/sbin/ip -o -6 addr show") if err != nil { return "", fmt.Errorf("Failed get access nic %s", err) } } else { // ipv4 nicsRet, err = s.SshCli.Run("/sbin/ip -o -4 addr show") if err != nil { return "", fmt.Errorf("Failed get access nic %s", err) } } for i := 0; i < len(nicsRet); i++ { if strings.Contains(nicsRet[i], ip+"/") { segs := strings.Split(nicsRet[i], " ") if len(segs) > 1 { dev = segs[1] break } } } if len(dev) == 0 { return "", fmt.Errorf("Can't get access dev") } log.Infof("Access dev is %s", dev) macRet, err := s.SshCli.Run("/sbin/ip a show " + dev) if err != nil || len(macRet) < 2 { return "", fmt.Errorf("Failed get access nic mac address %s", err) } segs := strings.Fields(macRet[1]) if len(segs) < 2 { return "", fmt.Errorf("Failed to find mac address") } return segs[1], nil } func (s *sBaremetalRegisterTask) CreateBaremetal(ctx context.Context) (string, error) { zoneId := s.BmManager.GetZoneId() ret, err := s.SshCli.Run("/lib/mos/lsnic") if err != nil { return "", fmt.Errorf("Register baremeatl failed on lsnic: %s", err) } accessMac, err := s.getAccessDevMacAddr(s.RemoteIp) if err != nil { return "", err } accessMacAddr, err := net.ParseMAC(accessMac) if err != nil { return "", fmt.Errorf("Failed parse access mac %s", accessMac) } nicinfo := sysutils.ParseNicInfo(ret) for _, nic := range nicinfo { if nic.Mac.String() == accessMacAddr.String() { s.accessNic = nic } } if s.accessNic == nil { s.accessNic = nicinfo[0] } params := jsonutils.NewDict() params.Set("name", jsonutils.NewString(fmt.Sprintf("bm%s", strings.Replace(s.accessNic.Mac.String(), ":", "", -1)))) params.Set("access_mac", jsonutils.NewString(s.accessNic.Mac.String())) params.Set("host_type", jsonutils.NewString("baremetal")) params.Set("is_baremetal", jsonutils.JSONTrue) params.Set("is_import", jsonutils.JSONTrue) res, err := modules.Hosts.CreateInContext(s.getSession(), params, &modules.Zones, zoneId) if err != nil { return "", fmt.Errorf("Create baremetal failed: %s", err) } pxeBm, err := s.BmManager.AddBaremetal(ctx, res) if err != nil { return "", fmt.Errorf("BmManager add baremetal failed: %s", err) } err = pxeBm.InitAdminNetif(ctx, s.accessNic.Mac, s.AdminWire, api.NIC_TYPE_ADMIN, api.NETWORK_TYPE_PXE, true, s.RemoteIp) if err != nil { return "", fmt.Errorf("BmManager add admin netif failed: %s", err) } err = pxeBm.InitAdminNetif(ctx, s.IpmiMac, s.IpmiWire, api.NIC_TYPE_IPMI, api.NETWORK_TYPE_IPMI, true, s.IpmiIpAddr) if err != nil { return "", fmt.Errorf("BmManager add ipmi netif failed: %s", err) } for _, nic := range nicinfo { if nic.Dev != s.accessNic.Dev { pxeBm.RegisterNetif(ctx, nic.Mac, s.AdminWire) } } s.baremetal = pxeBm.(IBaremetal) bmInstanceId, _ := res.GetString("id") return bmInstanceId, nil } func (s *sBaremetalRegisterTask) UpdateBaremetal(ctx context.Context) (string, error) { accessMac, err := s.getAccessDevMacAddr(s.RemoteIp) if err != nil { return "", errors.Wrap(err, "getAccessDevMacAddr") } accessMacAddr, err := net.ParseMAC(accessMac) if err != nil { return "", errors.Wrapf(err, "Failed parse access mac %s", accessMac) } params := jsonutils.NewDict() params.Set("any_mac", jsonutils.NewString(accessMacAddr.String())) params.Set("scope", jsonutils.NewString("system")) res, err := modules.Hosts.List(s.BmManager.GetClientSession(), params) if err != nil { return "", errors.Wrap(err, "Fetch baremetal failed") } if len(res.Data) == 0 { return "", errors.Wrapf(errors.ErrNotFound, "Cann't find baremetal by access mac %s", accessMacAddr) } pxeBm, err := s.BmManager.AddBaremetal(ctx, res.Data[0]) if err != nil { return "", errors.Wrap(err, "BmManager add baremetal failed") } s.baremetal = pxeBm.(IBaremetal) return s.baremetal.GetId(), nil } func (s *sBaremetalRegisterTask) doRedfishProbe(ctx context.Context) (redfishSupport bool, cdromBoot bool) { var endpoint = "https://" + s.IpmiIpAddr if strings.Contains(s.IpmiIpAddr, ":") { endpoint = fmt.Sprintf("https://[%s]", s.IpmiIpAddr) } redfishCli := redfish.NewRedfishDriver(ctx, endpoint, s.IpmiUsername, s.IpmiPassword, false) if redfishCli != nil { _, cdInfo, _ := redfishCli.GetVirtualCdromInfo(ctx) redfishSupport = true cdromBoot = cdInfo.SupportAction } return } func (s *sBaremetalRegisterTask) DoPrepare(ctx context.Context, cli *ssh.Client, registered bool) error { infos, err := s.prepareBaremetalInfo(cli) if err != nil { return err } redfishSupport, cdromSupport := s.doRedfishProbe(ctx) infos.ipmiInfo.IpAddr = s.IpmiIpAddr infos.ipmiInfo.Username = s.IpmiUsername infos.ipmiInfo.Password = s.IpmiPassword infos.ipmiInfo.LanChannel = s.IpmiLanChannel infos.ipmiInfo.Verified = true infos.ipmiInfo.Present = true infos.ipmiInfo.RedfishApi = redfishSupport infos.ipmiInfo.CdromBoot = cdromSupport s.updateIpmiInfo(ctx, cli) return s.updateBmInfo(ctx, cli, infos, registered) } func (s *sBaremetalRegisterTask) updateIpmiInfo(ctx context.Context, cli *ssh.Client) { ipmiTool := ipmitool.NewSSHIPMI(cli) sysInfo, err := ipmitool.GetSysInfo(ipmiTool) if err == nil && o.Options.IpmiLanPortShared { oemName := strings.ToLower(sysInfo.Manufacture) if strings.Contains(oemName, "huawei") { ipmitool.SetHuaweiIPMILanPortShared(ipmiTool) } else if strings.Contains(oemName, "dell") { ipmitool.SetDellIPMILanPortShared(ipmiTool) } } up := true var nic = &types.SNicDevInfo{ Up: &up, Speed: 100, Mtu: 1500, } var conf *types.SIPMILanConfig profile, err := profiles.GetProfile(ctx, sysInfo) if profile != nil { for _, lanChannel := range profile.LanChannels { conf, _ = ipmitool.GetLanConfig(ipmiTool, lanChannel) if conf == nil || len(conf.Mac) == 0 { continue } } } if conf == nil || len(conf.Mac) == 0 { log.Errorln("Fail to get IPMI lan config !!!") } else { nic.Mac = conf.Mac } s.sendNicInfo(ctx, nic, -1, api.NIC_TYPE_IPMI, false, "", false) } func (s *sBaremetalRegisterTask) updateBmInfo(ctx context.Context, cli *ssh.Client, i *baremetalPrepareInfo, registered bool) error { accessMac := "" if s.accessNic != nil { accessMac = s.accessNic.Mac.String() } err := s.doUpdateBmInfo(ctx, cli, i, "", s.RemoteIp, accessMac) if err != nil { log.Errorf("failed do update bminfo %s", err) } if registered { return nil } return s.initBaremetalServer(ctx) } func (s *sBaremetalRegisterTask) initBaremetalServer(ctx context.Context) error { if err := s.baremetal.InitializeServer(s.getSession(), s.Hostname); err != nil { return fmt.Errorf("Baremteal Create Server Failed %s", err) } // if err := s.baremetal.SaveSSHConfig("", ""); err != nil { // log.Errorf("Save ssh config failed %s", err) // } if err := s.baremetal.ServerLoadDesc(ctx); err != nil { log.Errorf("Server load desc failed %s", err) } s.baremetal.SyncStatus(ctx, "running", "Register success") log.Infof("%s Load baremetal info success ...", s.baremetal.GetId()) return nil }