bm_register.go 9.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318
  1. // Copyright 2019 Yunion
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. package tasks
  15. import (
  16. "context"
  17. "fmt"
  18. "net"
  19. "strings"
  20. "yunion.io/x/jsonutils"
  21. "yunion.io/x/log"
  22. "yunion.io/x/pkg/errors"
  23. api "yunion.io/x/onecloud/pkg/apis/compute"
  24. o "yunion.io/x/onecloud/pkg/baremetal/options"
  25. "yunion.io/x/onecloud/pkg/baremetal/profiles"
  26. "yunion.io/x/onecloud/pkg/baremetal/utils/ipmitool"
  27. "yunion.io/x/onecloud/pkg/cloudcommon/types"
  28. "yunion.io/x/onecloud/pkg/mcclient"
  29. "yunion.io/x/onecloud/pkg/mcclient/auth"
  30. modules "yunion.io/x/onecloud/pkg/mcclient/modules/compute"
  31. "yunion.io/x/onecloud/pkg/util/redfish"
  32. "yunion.io/x/onecloud/pkg/util/ssh"
  33. "yunion.io/x/onecloud/pkg/util/sysutils"
  34. )
  35. type sBaremetalRegisterTask struct {
  36. sBaremetalPrepareTask
  37. BmManager IBmManager
  38. SshCli *ssh.Client
  39. Hostname string
  40. RemoteIp string
  41. IpmiUsername string
  42. IpmiPassword string
  43. IpmiIpAddr string
  44. IpmiMac net.HardwareAddr
  45. IpmiLanChannel uint8
  46. AdminWire string
  47. IpmiWire string
  48. accessNic *types.SNicDevInfo
  49. }
  50. func NewBaremetalRegisterTask(
  51. userCred mcclient.TokenCredential, bmManager IBmManager, sshCli *ssh.Client,
  52. hostname, remoteIp, ipmiUsername, ipmiPassword, ipmiIpAddr string,
  53. ipmiMac net.HardwareAddr, ipmiLanChannel uint8, adminWire, ipmiWire string) *sBaremetalRegisterTask {
  54. return &sBaremetalRegisterTask{
  55. sBaremetalPrepareTask: sBaremetalPrepareTask{userCred: userCred},
  56. BmManager: bmManager,
  57. SshCli: sshCli,
  58. Hostname: hostname,
  59. RemoteIp: remoteIp,
  60. IpmiUsername: ipmiUsername,
  61. IpmiPassword: ipmiPassword,
  62. IpmiIpAddr: ipmiIpAddr,
  63. IpmiMac: ipmiMac,
  64. IpmiLanChannel: ipmiLanChannel,
  65. AdminWire: adminWire,
  66. IpmiWire: ipmiWire,
  67. }
  68. }
  69. func (s *sBaremetalRegisterTask) getSession() *mcclient.ClientSession {
  70. return auth.GetSession(context.Background(), s.userCred, o.Options.Region)
  71. }
  72. func (s *sBaremetalRegisterTask) getAccessDevMacAddr(ip string) (string, error) {
  73. var dev string
  74. var nicsRet []string
  75. var err error
  76. if strings.Contains(ip, ":") { // ipv6
  77. nicsRet, err = s.SshCli.Run("/sbin/ip -o -6 addr show")
  78. if err != nil {
  79. return "", fmt.Errorf("Failed get access nic %s", err)
  80. }
  81. } else { // ipv4
  82. nicsRet, err = s.SshCli.Run("/sbin/ip -o -4 addr show")
  83. if err != nil {
  84. return "", fmt.Errorf("Failed get access nic %s", err)
  85. }
  86. }
  87. for i := 0; i < len(nicsRet); i++ {
  88. if strings.Contains(nicsRet[i], ip+"/") {
  89. segs := strings.Split(nicsRet[i], " ")
  90. if len(segs) > 1 {
  91. dev = segs[1]
  92. break
  93. }
  94. }
  95. }
  96. if len(dev) == 0 {
  97. return "", fmt.Errorf("Can't get access dev")
  98. }
  99. log.Infof("Access dev is %s", dev)
  100. macRet, err := s.SshCli.Run("/sbin/ip a show " + dev)
  101. if err != nil || len(macRet) < 2 {
  102. return "", fmt.Errorf("Failed get access nic mac address %s", err)
  103. }
  104. segs := strings.Fields(macRet[1])
  105. if len(segs) < 2 {
  106. return "", fmt.Errorf("Failed to find mac address")
  107. }
  108. return segs[1], nil
  109. }
  110. func (s *sBaremetalRegisterTask) CreateBaremetal(ctx context.Context) (string, error) {
  111. zoneId := s.BmManager.GetZoneId()
  112. ret, err := s.SshCli.Run("/lib/mos/lsnic")
  113. if err != nil {
  114. return "", fmt.Errorf("Register baremeatl failed on lsnic: %s", err)
  115. }
  116. accessMac, err := s.getAccessDevMacAddr(s.RemoteIp)
  117. if err != nil {
  118. return "", err
  119. }
  120. accessMacAddr, err := net.ParseMAC(accessMac)
  121. if err != nil {
  122. return "", fmt.Errorf("Failed parse access mac %s", accessMac)
  123. }
  124. nicinfo := sysutils.ParseNicInfo(ret)
  125. for _, nic := range nicinfo {
  126. if nic.Mac.String() == accessMacAddr.String() {
  127. s.accessNic = nic
  128. }
  129. }
  130. if s.accessNic == nil {
  131. s.accessNic = nicinfo[0]
  132. }
  133. params := jsonutils.NewDict()
  134. params.Set("name", jsonutils.NewString(fmt.Sprintf("bm%s", strings.Replace(s.accessNic.Mac.String(), ":", "", -1))))
  135. params.Set("access_mac", jsonutils.NewString(s.accessNic.Mac.String()))
  136. params.Set("host_type", jsonutils.NewString("baremetal"))
  137. params.Set("is_baremetal", jsonutils.JSONTrue)
  138. params.Set("is_import", jsonutils.JSONTrue)
  139. res, err := modules.Hosts.CreateInContext(s.getSession(), params, &modules.Zones, zoneId)
  140. if err != nil {
  141. return "", fmt.Errorf("Create baremetal failed: %s", err)
  142. }
  143. pxeBm, err := s.BmManager.AddBaremetal(ctx, res)
  144. if err != nil {
  145. return "", fmt.Errorf("BmManager add baremetal failed: %s", err)
  146. }
  147. err = pxeBm.InitAdminNetif(ctx,
  148. s.accessNic.Mac, s.AdminWire, api.NIC_TYPE_ADMIN, api.NETWORK_TYPE_PXE, true, s.RemoteIp)
  149. if err != nil {
  150. return "", fmt.Errorf("BmManager add admin netif failed: %s", err)
  151. }
  152. err = pxeBm.InitAdminNetif(ctx,
  153. s.IpmiMac, s.IpmiWire, api.NIC_TYPE_IPMI, api.NETWORK_TYPE_IPMI, true, s.IpmiIpAddr)
  154. if err != nil {
  155. return "", fmt.Errorf("BmManager add ipmi netif failed: %s", err)
  156. }
  157. for _, nic := range nicinfo {
  158. if nic.Dev != s.accessNic.Dev {
  159. pxeBm.RegisterNetif(ctx, nic.Mac, s.AdminWire)
  160. }
  161. }
  162. s.baremetal = pxeBm.(IBaremetal)
  163. bmInstanceId, _ := res.GetString("id")
  164. return bmInstanceId, nil
  165. }
  166. func (s *sBaremetalRegisterTask) UpdateBaremetal(ctx context.Context) (string, error) {
  167. accessMac, err := s.getAccessDevMacAddr(s.RemoteIp)
  168. if err != nil {
  169. return "", errors.Wrap(err, "getAccessDevMacAddr")
  170. }
  171. accessMacAddr, err := net.ParseMAC(accessMac)
  172. if err != nil {
  173. return "", errors.Wrapf(err, "Failed parse access mac %s", accessMac)
  174. }
  175. params := jsonutils.NewDict()
  176. params.Set("any_mac", jsonutils.NewString(accessMacAddr.String()))
  177. params.Set("scope", jsonutils.NewString("system"))
  178. res, err := modules.Hosts.List(s.BmManager.GetClientSession(), params)
  179. if err != nil {
  180. return "", errors.Wrap(err, "Fetch baremetal failed")
  181. }
  182. if len(res.Data) == 0 {
  183. return "", errors.Wrapf(errors.ErrNotFound, "Cann't find baremetal by access mac %s", accessMacAddr)
  184. }
  185. pxeBm, err := s.BmManager.AddBaremetal(ctx, res.Data[0])
  186. if err != nil {
  187. return "", errors.Wrap(err, "BmManager add baremetal failed")
  188. }
  189. s.baremetal = pxeBm.(IBaremetal)
  190. return s.baremetal.GetId(), nil
  191. }
  192. func (s *sBaremetalRegisterTask) doRedfishProbe(ctx context.Context) (redfishSupport bool, cdromBoot bool) {
  193. var endpoint = "https://" + s.IpmiIpAddr
  194. if strings.Contains(s.IpmiIpAddr, ":") {
  195. endpoint = fmt.Sprintf("https://[%s]", s.IpmiIpAddr)
  196. }
  197. redfishCli := redfish.NewRedfishDriver(ctx, endpoint, s.IpmiUsername, s.IpmiPassword, false)
  198. if redfishCli != nil {
  199. _, cdInfo, _ := redfishCli.GetVirtualCdromInfo(ctx)
  200. redfishSupport = true
  201. cdromBoot = cdInfo.SupportAction
  202. }
  203. return
  204. }
  205. func (s *sBaremetalRegisterTask) DoPrepare(ctx context.Context, cli *ssh.Client, registered bool) error {
  206. infos, err := s.prepareBaremetalInfo(cli)
  207. if err != nil {
  208. return err
  209. }
  210. redfishSupport, cdromSupport := s.doRedfishProbe(ctx)
  211. infos.ipmiInfo.IpAddr = s.IpmiIpAddr
  212. infos.ipmiInfo.Username = s.IpmiUsername
  213. infos.ipmiInfo.Password = s.IpmiPassword
  214. infos.ipmiInfo.LanChannel = s.IpmiLanChannel
  215. infos.ipmiInfo.Verified = true
  216. infos.ipmiInfo.Present = true
  217. infos.ipmiInfo.RedfishApi = redfishSupport
  218. infos.ipmiInfo.CdromBoot = cdromSupport
  219. s.updateIpmiInfo(ctx, cli)
  220. return s.updateBmInfo(ctx, cli, infos, registered)
  221. }
  222. func (s *sBaremetalRegisterTask) updateIpmiInfo(ctx context.Context, cli *ssh.Client) {
  223. ipmiTool := ipmitool.NewSSHIPMI(cli)
  224. sysInfo, err := ipmitool.GetSysInfo(ipmiTool)
  225. if err == nil && o.Options.IpmiLanPortShared {
  226. oemName := strings.ToLower(sysInfo.Manufacture)
  227. if strings.Contains(oemName, "huawei") {
  228. ipmitool.SetHuaweiIPMILanPortShared(ipmiTool)
  229. } else if strings.Contains(oemName, "dell") {
  230. ipmitool.SetDellIPMILanPortShared(ipmiTool)
  231. }
  232. }
  233. up := true
  234. var nic = &types.SNicDevInfo{
  235. Up: &up,
  236. Speed: 100,
  237. Mtu: 1500,
  238. }
  239. var conf *types.SIPMILanConfig
  240. profile, err := profiles.GetProfile(ctx, sysInfo)
  241. if profile != nil {
  242. for _, lanChannel := range profile.LanChannels {
  243. conf, _ = ipmitool.GetLanConfig(ipmiTool, lanChannel)
  244. if conf == nil || len(conf.Mac) == 0 {
  245. continue
  246. }
  247. }
  248. }
  249. if conf == nil || len(conf.Mac) == 0 {
  250. log.Errorln("Fail to get IPMI lan config !!!")
  251. } else {
  252. nic.Mac = conf.Mac
  253. }
  254. s.sendNicInfo(ctx, nic, -1, api.NIC_TYPE_IPMI, false, "", false)
  255. }
  256. func (s *sBaremetalRegisterTask) updateBmInfo(ctx context.Context, cli *ssh.Client, i *baremetalPrepareInfo, registered bool) error {
  257. accessMac := ""
  258. if s.accessNic != nil {
  259. accessMac = s.accessNic.Mac.String()
  260. }
  261. err := s.doUpdateBmInfo(ctx, cli, i, "", s.RemoteIp, accessMac)
  262. if err != nil {
  263. log.Errorf("failed do update bminfo %s", err)
  264. }
  265. if registered {
  266. return nil
  267. }
  268. return s.initBaremetalServer(ctx)
  269. }
  270. func (s *sBaremetalRegisterTask) initBaremetalServer(ctx context.Context) error {
  271. if err := s.baremetal.InitializeServer(s.getSession(), s.Hostname); err != nil {
  272. return fmt.Errorf("Baremteal Create Server Failed %s", err)
  273. }
  274. // if err := s.baremetal.SaveSSHConfig("", ""); err != nil {
  275. // log.Errorf("Save ssh config failed %s", err)
  276. // }
  277. if err := s.baremetal.ServerLoadDesc(ctx); err != nil {
  278. log.Errorf("Server load desc failed %s", err)
  279. }
  280. s.baremetal.SyncStatus(ctx, "running", "Register success")
  281. log.Infof("%s Load baremetal info success ...", s.baremetal.GetId())
  282. return nil
  283. }