base.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  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. "container/list"
  17. "context"
  18. "fmt"
  19. "sync"
  20. "time"
  21. "yunion.io/x/jsonutils"
  22. "yunion.io/x/log"
  23. "yunion.io/x/pkg/errors"
  24. "yunion.io/x/pkg/object"
  25. o "yunion.io/x/onecloud/pkg/baremetal/options"
  26. "yunion.io/x/onecloud/pkg/cloudcommon/types"
  27. "yunion.io/x/onecloud/pkg/mcclient"
  28. "yunion.io/x/onecloud/pkg/util/ssh"
  29. )
  30. type Queue struct {
  31. objList *list.List
  32. objListLock *sync.Mutex
  33. }
  34. func NewQueue() *Queue {
  35. return &Queue{
  36. objList: list.New(),
  37. objListLock: new(sync.Mutex),
  38. }
  39. }
  40. func (q *Queue) Append(obj interface{}) *Queue {
  41. q.objListLock.Lock()
  42. defer q.objListLock.Unlock()
  43. q.objList.PushBack(obj)
  44. return q
  45. }
  46. func (q *Queue) First() interface{} {
  47. q.objListLock.Lock()
  48. defer q.objListLock.Unlock()
  49. if q.objList.Len() == 0 {
  50. return nil
  51. }
  52. return q.objList.Front().Value
  53. }
  54. func (q *Queue) IsEmpty() bool {
  55. return q.First() == nil
  56. }
  57. func (q *Queue) Pop() interface{} {
  58. q.objListLock.Lock()
  59. defer q.objListLock.Unlock()
  60. if q.objList.Len() == 0 {
  61. return nil
  62. }
  63. first := q.objList.Front()
  64. q.objList.Remove(first)
  65. return first.Value
  66. }
  67. func (q *Queue) String() string {
  68. itemStrings := debugString(q.objList.Front())
  69. return fmt.Sprintf("%v", itemStrings)
  70. }
  71. func debugString(elem *list.Element) []string {
  72. if elem == nil {
  73. return nil
  74. }
  75. strings := []string{fmt.Sprintf("%v", elem.Value)}
  76. rest := debugString(elem.Next())
  77. if rest != nil {
  78. strings = append(strings, rest...)
  79. }
  80. return strings
  81. }
  82. type TaskQueue struct {
  83. *Queue
  84. }
  85. type TaskStageFunc func(ctx context.Context, args interface{}) error
  86. type SSHTaskStageFunc func(ctx context.Context, cli *ssh.Client, args interface{}) error
  87. type sshStageWrapper struct {
  88. sshStage SSHTaskStageFunc
  89. remoteIP string
  90. password string
  91. }
  92. func sshStageW(
  93. stage SSHTaskStageFunc,
  94. remoteIP string,
  95. password string,
  96. ) *sshStageWrapper {
  97. return &sshStageWrapper{
  98. sshStage: stage,
  99. remoteIP: remoteIP,
  100. password: password,
  101. }
  102. }
  103. func (sw *sshStageWrapper) Do(ctx context.Context, args interface{}) error {
  104. cli, err := ssh.NewClient(sw.remoteIP, 22, "root", sw.password, "")
  105. if err != nil {
  106. return err
  107. }
  108. return sw.sshStage(ctx, cli, args)
  109. }
  110. type ITask interface {
  111. // GetStage return current task stage func
  112. GetStage() TaskStageFunc
  113. // SetStage set task next execute stage func
  114. SetStage(stage TaskStageFunc)
  115. // GetSSHStage return current task ssh stage func
  116. GetSSHStage() SSHTaskStageFunc
  117. // SetSSHStage set task next execute ssh stage func
  118. SetSSHStage(stage SSHTaskStageFunc)
  119. // GetTaskId return remote service task id
  120. GetTaskId() string
  121. GetClientSession() *mcclient.ClientSession
  122. GetTaskQueue() *TaskQueue
  123. // GetData return TaskData from region
  124. GetData() jsonutils.JSONObject
  125. GetName() string
  126. Execute(args interface{})
  127. SetSSHStageParams(remoteIP string, passwd string)
  128. SSHExecute(remoteIP string, passwd string, args interface{})
  129. NeedPXEBoot() bool
  130. GetStartTime() time.Time
  131. }
  132. func NewTaskQueue() *TaskQueue {
  133. return &TaskQueue{
  134. Queue: NewQueue(),
  135. }
  136. }
  137. func (q *TaskQueue) GetTask() ITask {
  138. if q.IsEmpty() {
  139. return nil
  140. }
  141. return q.First().(ITask)
  142. }
  143. func (q *TaskQueue) PopTask() ITask {
  144. if q.IsEmpty() {
  145. return nil
  146. }
  147. return q.Pop().(ITask)
  148. }
  149. func (q *TaskQueue) AppendTask(task ITask) *TaskQueue {
  150. log.Infof("Append task %s", task.GetName())
  151. q.Append(task)
  152. return q
  153. }
  154. func (q *TaskQueue) ClearTasks() {
  155. for {
  156. existTask := q.PopTask()
  157. if existTask != nil {
  158. log.Warningf("Clear task %s", existTask.GetName())
  159. } else {
  160. break
  161. }
  162. }
  163. }
  164. func (q *TaskQueue) DebugString() string {
  165. str := ""
  166. for e := q.objList.Front(); e != nil; e = e.Next() {
  167. str += fmt.Sprintf("%s, ", e.Value.(ITask).GetName())
  168. }
  169. return str
  170. }
  171. type TaskFactory func(userCred mcclient.TokenCredential, bm IBaremetal, taskId string, data jsonutils.JSONObject) ITask
  172. type SBaremetalTaskBase struct {
  173. object.SObject
  174. Baremetal IBaremetal
  175. PxeBoot bool
  176. userCred mcclient.TokenCredential
  177. stageFunc TaskStageFunc
  178. sshStageFunc SSHTaskStageFunc
  179. taskId string
  180. data jsonutils.JSONObject
  181. startTime time.Time
  182. }
  183. func newBaremetalTaskBase(
  184. userCred mcclient.TokenCredential,
  185. baremetal IBaremetal,
  186. taskId string,
  187. data jsonutils.JSONObject,
  188. ) SBaremetalTaskBase {
  189. task := SBaremetalTaskBase{
  190. Baremetal: baremetal,
  191. userCred: userCred,
  192. taskId: taskId,
  193. data: data,
  194. startTime: time.Now().UTC(),
  195. }
  196. return task
  197. }
  198. func (task *SBaremetalTaskBase) ITask() ITask {
  199. return task.GetVirtualObject().(ITask)
  200. }
  201. func (task *SBaremetalTaskBase) GetStartTime() time.Time {
  202. return task.startTime
  203. }
  204. func (task *SBaremetalTaskBase) GetTaskQueue() *TaskQueue {
  205. return task.Baremetal.GetTaskQueue()
  206. }
  207. func (task *SBaremetalTaskBase) GetTaskId() string {
  208. return task.taskId
  209. }
  210. func (task *SBaremetalTaskBase) GetData() jsonutils.JSONObject {
  211. return task.data
  212. }
  213. func (task *SBaremetalTaskBase) GetStage() TaskStageFunc {
  214. return task.stageFunc
  215. }
  216. func (task *SBaremetalTaskBase) GetSSHStage() SSHTaskStageFunc {
  217. return task.sshStageFunc
  218. }
  219. func (task *SBaremetalTaskBase) SetStage(stage TaskStageFunc) {
  220. task.stageFunc = stage
  221. }
  222. func (task *SBaremetalTaskBase) SetSSHStage(stage SSHTaskStageFunc) {
  223. task.sshStageFunc = stage
  224. }
  225. func (task *SBaremetalTaskBase) Execute(args interface{}) {
  226. ExecuteTask(task.ITask(), args)
  227. }
  228. func (task *SBaremetalTaskBase) SetSSHStageParams(remoteIP string, password string) {
  229. task.ITask().SetStage(sshStageW(task.ITask().GetSSHStage(), remoteIP, password).Do)
  230. }
  231. func (task *SBaremetalTaskBase) SSHExecute(remoteIP string, password string, args interface{}) {
  232. //iTask.SetStage(sshStageW(iTask.GetSSHStage(), remoteIP, password).Do)
  233. task.ITask().SetSSHStageParams(remoteIP, password)
  234. ExecuteTask(task.ITask(), args)
  235. }
  236. //func (task *SBaremetalTaskBase) CallNextStage(iTask ITask, stage TaskStageFunc, args interface{}) {
  237. //iTask.SetStage(stage)
  238. //ExecuteTask(iTask, args)
  239. //}
  240. func (task *SBaremetalTaskBase) GetClientSession() *mcclient.ClientSession {
  241. return task.Baremetal.GetClientSession()
  242. }
  243. func (self *SBaremetalTaskBase) EnsurePowerShutdown(soft bool) error {
  244. log.Infof("EnsurePowerShutdown: soft=%v", soft)
  245. status, err := self.Baremetal.GetPowerStatus()
  246. if err != nil {
  247. return err
  248. }
  249. startTime := time.Now()
  250. maxWait := 60 * time.Second
  251. for status == "" || status == types.POWER_STATUS_ON {
  252. if time.Since(startTime).Seconds() >= maxWait.Seconds() && soft {
  253. soft = false
  254. }
  255. err = self.Baremetal.DoPowerShutdown(soft)
  256. if err != nil {
  257. log.Errorf("DoPowerShutdown: %v", err)
  258. }
  259. time.Sleep(20 * time.Second)
  260. status, err = self.Baremetal.GetPowerStatus()
  261. if err != nil {
  262. log.Errorf("GetPowerStatus: %v", err)
  263. }
  264. }
  265. if status != types.POWER_STATUS_OFF {
  266. return fmt.Errorf("Baremetal invalid status %s for shutdown", status)
  267. }
  268. return nil
  269. }
  270. func (self *SBaremetalTaskBase) EnsurePowerUp() error {
  271. log.Infof("EnsurePowerUp: bootdev=pxe %v", self.PxeBoot)
  272. status, err := self.Baremetal.GetPowerStatus()
  273. if err != nil {
  274. return errors.Wrapf(err, "Get power status")
  275. }
  276. maxTries := 10
  277. count := 0
  278. for status == "" || status == types.POWER_STATUS_OFF {
  279. if count > maxTries {
  280. break
  281. }
  282. log.Infof("Try power on %d times, pxe boot %v", count+1, self.PxeBoot)
  283. if status == types.POWER_STATUS_OFF {
  284. if self.PxeBoot {
  285. err = self.Baremetal.DoPXEBoot()
  286. } else {
  287. err = self.Baremetal.DoRedfishPowerOn()
  288. }
  289. if err != nil {
  290. log.Warningf("Do boot power on error: %v", err)
  291. }
  292. }
  293. status, err = self.Baremetal.GetPowerStatus()
  294. if err != nil {
  295. return errors.Wrapf(err, "Get power status")
  296. }
  297. if status == "" || status == types.POWER_STATUS_OFF {
  298. time.Sleep(40 * time.Second)
  299. status, err = self.Baremetal.GetPowerStatus()
  300. if err != nil {
  301. return err
  302. }
  303. }
  304. count++
  305. }
  306. if status != types.POWER_STATUS_ON {
  307. return fmt.Errorf("Baremetal invalid restart status: %s", status)
  308. }
  309. return nil
  310. }
  311. func (self *SBaremetalTaskBase) EnsureSSHReboot(ctx context.Context) error {
  312. if err := self.Baremetal.SSHReboot(ctx); err != nil {
  313. return errors.Wrap(err, "Ensure ssh reboot")
  314. }
  315. var (
  316. err error
  317. canReach bool
  318. )
  319. maxTries := 20
  320. startTime := time.Now()
  321. for count := 0; count < maxTries; count++ {
  322. times := (count % 10) + 1
  323. log.Infof("Try %s ssh connection after reboot %d times, %s passed", self.Baremetal.GetName(), times, time.Now().Sub(startTime))
  324. canReach, err = self.Baremetal.SSHReachable()
  325. if canReach {
  326. return nil
  327. }
  328. time.Sleep(10 * time.Second * time.Duration(times))
  329. }
  330. return errors.Wrapf(err, "Test %s ssh connection after reboot", self.Baremetal.GetName())
  331. }
  332. func (self *SBaremetalTaskBase) NeedPXEBoot() bool {
  333. return false
  334. }
  335. type IPXEBootTask interface {
  336. ITask
  337. OnPXEBoot(ctx context.Context, cli *ssh.Client, args interface{}) error
  338. }
  339. type SBaremetalPXEBootTaskBase struct {
  340. SBaremetalTaskBase
  341. // pxeBootTask IPXEBootTask
  342. startTime time.Time
  343. }
  344. func newBaremetalPXEBootTaskBase(
  345. userCred mcclient.TokenCredential,
  346. baremetal IBaremetal,
  347. taskId string,
  348. data jsonutils.JSONObject,
  349. ) SBaremetalPXEBootTaskBase {
  350. baseTask := newBaremetalTaskBase(userCred, baremetal, taskId, data)
  351. task := SBaremetalPXEBootTaskBase{
  352. SBaremetalTaskBase: baseTask,
  353. }
  354. return task
  355. }
  356. func (self *SBaremetalPXEBootTaskBase) IPXEBootTask() IPXEBootTask {
  357. return self.GetVirtualObject().(IPXEBootTask)
  358. }
  359. func (self *SBaremetalPXEBootTaskBase) OnPXEBoot(ctx context.Context, cli *ssh.Client, args interface{}) error {
  360. log.Debugf("SBaremetalPXEBootTaskBase.OnPXEBoot do nothing")
  361. return nil
  362. }
  363. func (self *SBaremetalPXEBootTaskBase) InitPXEBootTask(ctx context.Context, args interface{}) error {
  364. //OnInitStage(pxeBootTask)
  365. sshConf, _ := self.Baremetal.GetSSHConfig()
  366. if sshConf != nil && self.Baremetal.TestSSHConfig() {
  367. self.IPXEBootTask().SetSSHStage(self.IPXEBootTask().OnPXEBoot)
  368. self.IPXEBootTask().SetSSHStageParams(sshConf.RemoteIP, sshConf.Password)
  369. ExecuteTask(self.ITask(), nil)
  370. return nil
  371. }
  372. // generate ISO
  373. if err := self.Baremetal.GenerateBootISO(); err != nil {
  374. log.Errorf("GenerateBootISO fail: %s", err)
  375. if !o.Options.EnablePxeBoot || !self.Baremetal.EnablePxeBoot() {
  376. return errors.Wrap(err, "self.Baremetal.GenerateBootISO")
  377. }
  378. self.PxeBoot = true
  379. } else {
  380. self.PxeBoot = false
  381. }
  382. if !self.Baremetal.HasBMC() {
  383. // Try remote ssh reboot
  384. if err := self.Baremetal.SSHReboot(ctx); err != nil {
  385. return errors.Wrap(err, "Try ssh reboot")
  386. }
  387. } else {
  388. // Do soft reboot
  389. if self.data != nil && jsonutils.QueryBoolean(self.data, "soft_boot", false) {
  390. self.startTime = time.Now()
  391. if err := self.Baremetal.DoPowerShutdown(true); err != nil {
  392. // ignore error
  393. log.Errorf("DoPowerShutdown error: %v", err)
  394. }
  395. //self.CallNextStage(self, self.WaitForShutdown, nil)
  396. self.SetStage(self.WaitForShutdown)
  397. return nil
  398. }
  399. // shutdown and power up to PXE mode
  400. if err := self.EnsurePowerShutdown(false); err != nil {
  401. return errors.Wrap(err, "EnsurePowerShutdown")
  402. }
  403. if err := self.EnsurePowerUp(); err != nil {
  404. return errors.Wrap(err, "EnsurePowerUp to pxe")
  405. }
  406. }
  407. // this stage will be called by baremetalInstance when pxe start notify
  408. self.SetSSHStage(self.IPXEBootTask().OnPXEBoot)
  409. return nil
  410. }
  411. func (self *SBaremetalPXEBootTaskBase) NeedPXEBoot() bool {
  412. return true
  413. }
  414. func (self *SBaremetalPXEBootTaskBase) WaitForShutdown(ctx context.Context, args interface{}) error {
  415. self.SetStage(self.OnStopComplete)
  416. status, err := self.Baremetal.GetPowerStatus()
  417. if err != nil {
  418. return err
  419. }
  420. if status == types.POWER_STATUS_OFF {
  421. self.ITask().Execute(nil)
  422. } else if time.Since(self.startTime) >= 90*time.Second {
  423. err = self.Baremetal.DoPowerShutdown(false)
  424. if err != nil {
  425. return err
  426. }
  427. }
  428. return nil
  429. }
  430. func (self *SBaremetalPXEBootTaskBase) OnStopComplete(ctx context.Context, args interface{}) error {
  431. err := self.EnsurePowerUp()
  432. if err != nil {
  433. return err
  434. }
  435. self.SetSSHStage(self.IPXEBootTask().OnPXEBoot)
  436. return nil
  437. }
  438. func (self *SBaremetalPXEBootTaskBase) GetName() string {
  439. return "BaremetalPXEBootTaskBase"
  440. }
  441. func AdjustUEFIBootOrder(ctx context.Context, term *ssh.Client, bm IBaremetal) error {
  442. return bm.AdjustUEFICurrentBootOrder(ctx, term)
  443. }