driver.go 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902
  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 qemu_kvm
  15. import (
  16. "encoding/json"
  17. "fmt"
  18. "io/ioutil"
  19. "path"
  20. "strings"
  21. "sync"
  22. "sync/atomic"
  23. "yunion.io/x/jsonutils"
  24. "yunion.io/x/log"
  25. "yunion.io/x/pkg/errors"
  26. "yunion.io/x/pkg/util/qemuimgfmt"
  27. "yunion.io/x/pkg/util/stringutils"
  28. "yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver"
  29. "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis"
  30. "yunion.io/x/onecloud/pkg/hostman/monitor"
  31. "yunion.io/x/onecloud/pkg/util/fileutils2"
  32. "yunion.io/x/onecloud/pkg/util/netutils2"
  33. "yunion.io/x/onecloud/pkg/util/procutils"
  34. "yunion.io/x/onecloud/pkg/util/qemuimg"
  35. "yunion.io/x/onecloud/pkg/util/qemutils"
  36. "yunion.io/x/onecloud/pkg/util/ssh"
  37. "yunion.io/x/onecloud/pkg/util/sysutils"
  38. )
  39. var BASE_SSH_PORT = 28000
  40. var (
  41. QEMU_KVM_PATH = "/usr/libexec/qemu-kvm"
  42. OS_ARCH_AARCH64 = "aarch64"
  43. OS_ARCH_RISCV64 = "riscv64"
  44. ARM_INITRD_PATH = "/yunionos/aarch64/initramfs"
  45. ARM_KERNEL_PATH = "/yunionos/aarch64/kernel"
  46. X86_INITRD_PATH = "/yunionos/x86_64/initramfs"
  47. X86_KERNEL_PATH = "/yunionos/x86_64/kernel"
  48. RISCV_INITRD_PATH = "/yunionos/riscv64/initramfs"
  49. RISCV_KERNEL_PATH = "/yunionos/riscv64/kernel"
  50. RUN_ON_HOST_ROOT_PATH = "/opt/cloud/host-deployer"
  51. DEPLOY_ISO = "/opt/cloud/host-deployer/host_deployer_v1.iso"
  52. // DEPLOYER_BIN = "/opt/yunion/bin/host-deployer --common-config-file /opt/yunion/common.conf --config /opt/yunion/host.conf"
  53. DEPLOYER_BIN = "/opt/yunion/bin/host-deployer --config /opt/yunion/host.conf"
  54. DEPLOY_PARAMS_FILE = "/deploy_params"
  55. YUNIONOS_PASSWD = "mosbaremetal"
  56. )
  57. type QemuDeployManager struct {
  58. cpuArch string
  59. hugepage bool
  60. hugepageSizeKB int
  61. portsInUse *sync.Map
  62. sshPortLock *sync.Mutex
  63. lastUsedSshPort int
  64. qemuCmd string
  65. memSizeMb int
  66. c chan struct{}
  67. }
  68. func (m *QemuDeployManager) runOnHost() bool {
  69. return m.qemuCmd != QEMU_KVM_PATH
  70. }
  71. func (m *QemuDeployManager) startQemu(cmd string) ([]byte, error) {
  72. if m.runOnHost() {
  73. return procutils.NewRemoteCommandAsFarAsPossible("bash", "-c", cmd).Output()
  74. } else {
  75. return procutils.NewCommand("bash", "-c", cmd).Output()
  76. }
  77. }
  78. func (m *QemuDeployManager) GetX86InitrdPath() string {
  79. if m.runOnHost() {
  80. return path.Join(RUN_ON_HOST_ROOT_PATH, X86_INITRD_PATH)
  81. } else {
  82. return X86_INITRD_PATH
  83. }
  84. }
  85. func (m *QemuDeployManager) GetARMInitrdPath() string {
  86. if m.runOnHost() {
  87. return path.Join(RUN_ON_HOST_ROOT_PATH, ARM_INITRD_PATH)
  88. } else {
  89. return ARM_INITRD_PATH
  90. }
  91. }
  92. func (m *QemuDeployManager) GetRISCVInitrdPath() string {
  93. if m.runOnHost() {
  94. return path.Join(RUN_ON_HOST_ROOT_PATH, RISCV_INITRD_PATH)
  95. } else {
  96. return RISCV_INITRD_PATH
  97. }
  98. }
  99. func (m *QemuDeployManager) GetX86KernelPath() string {
  100. if m.runOnHost() {
  101. return path.Join(RUN_ON_HOST_ROOT_PATH, X86_KERNEL_PATH)
  102. } else {
  103. return X86_KERNEL_PATH
  104. }
  105. }
  106. func (m *QemuDeployManager) GetARMKernelPath() string {
  107. if m.runOnHost() {
  108. return path.Join(RUN_ON_HOST_ROOT_PATH, ARM_KERNEL_PATH)
  109. } else {
  110. return ARM_KERNEL_PATH
  111. }
  112. }
  113. func (m *QemuDeployManager) GetRISCVKernelPath() string {
  114. if m.runOnHost() {
  115. return path.Join(RUN_ON_HOST_ROOT_PATH, RISCV_KERNEL_PATH)
  116. } else {
  117. return RISCV_KERNEL_PATH
  118. }
  119. }
  120. func (m *QemuDeployManager) Acquire() {
  121. log.Infof("acquire QemuDeployManager")
  122. m.c <- struct{}{}
  123. }
  124. func (m *QemuDeployManager) Release() {
  125. log.Infof("release QemuDeployManager")
  126. <-m.c
  127. }
  128. func (m *QemuDeployManager) GetFreePortByBase(basePort int) int {
  129. var port = 1
  130. for {
  131. if netutils2.IsTcpPortUsed("0.0.0.0", basePort+port) {
  132. port += 1
  133. } else {
  134. if !m.checkAndSetPort(basePort + port) {
  135. continue
  136. }
  137. break
  138. }
  139. }
  140. return port + basePort
  141. }
  142. func (m *QemuDeployManager) checkAndSetPort(port int) bool {
  143. _, loaded := m.portsInUse.LoadOrStore(port, struct{}{})
  144. return !loaded
  145. }
  146. func (m *QemuDeployManager) unsetPort(port int) {
  147. m.portsInUse.Delete(port)
  148. }
  149. func (m *QemuDeployManager) GetSshFreePort() int {
  150. m.sshPortLock.Lock()
  151. defer m.sshPortLock.Unlock()
  152. port := m.GetFreePortByBase(BASE_SSH_PORT + m.lastUsedSshPort)
  153. m.lastUsedSshPort = port - BASE_SSH_PORT
  154. if m.lastUsedSshPort >= 2000 {
  155. m.lastUsedSshPort = 0
  156. }
  157. return port
  158. }
  159. func (m *QemuDeployManager) getMemSizeMb() int {
  160. return m.memSizeMb
  161. }
  162. var manager *QemuDeployManager
  163. func tryCleanGuest(hmpPath string) {
  164. var c = make(chan error)
  165. onMonitorConnected := func() {
  166. log.Infof("%s monitor connected", hmpPath)
  167. c <- nil
  168. }
  169. onMonitorDisConnect := func(e error) {
  170. log.Errorf("%s monitor disconnect %s", hmpPath, e)
  171. }
  172. onMonitorConnectFailed := func(e error) {
  173. log.Errorf("%s monitor connect failed %s", hmpPath, e)
  174. c <- e
  175. }
  176. m := monitor.NewHmpMonitor("", "", onMonitorDisConnect, onMonitorConnectFailed, onMonitorConnected)
  177. if err := m.ConnectWithSocket(hmpPath); err != nil {
  178. log.Errorf("failed connect socket %s %s", hmpPath, err)
  179. } else {
  180. <-c
  181. m.Quit(func(string) {})
  182. }
  183. }
  184. func InitQemuDeployManager(
  185. cpuArch, qemuVersion string,
  186. enableRemoteExecutor, hugepage bool,
  187. hugepageSizeKB, memSizeMb, deployConcurrent int,
  188. ) error {
  189. if deployConcurrent <= 0 {
  190. deployConcurrent = 10
  191. }
  192. if cpuArch == OS_ARCH_AARCH64 {
  193. qemutils.UseAarch64()
  194. } else if cpuArch == OS_ARCH_RISCV64 {
  195. qemutils.UseRiscv64()
  196. }
  197. var qemuCmd string
  198. if enableRemoteExecutor {
  199. qemuCmd = qemutils.GetQemu(qemuVersion)
  200. }
  201. if qemuCmd == "" {
  202. qemuCmd = QEMU_KVM_PATH
  203. }
  204. err := procutils.NewCommand("mkdir", "-p", "/etc/ceph").Run()
  205. if err != nil {
  206. log.Errorf("Failed to mkdir /etc/ceph: %s", err)
  207. return errors.Wrap(err, "Failed to mkdir /etc/ceph: %s")
  208. }
  209. err = procutils.NewCommand("test", "-f", "/etc/ceph/ceph.conf").Run()
  210. if err != nil {
  211. err = procutils.NewCommand("touch", "/etc/ceph/ceph.conf").Run()
  212. if err != nil {
  213. log.Errorf("failed to create /etc/ceph/ceph.conf: %s", err)
  214. return errors.Wrap(err, "failed to create /etc/ceph/ceph.conf")
  215. }
  216. }
  217. if manager == nil {
  218. manager = &QemuDeployManager{
  219. cpuArch: cpuArch,
  220. hugepage: hugepage,
  221. hugepageSizeKB: hugepageSizeKB,
  222. memSizeMb: memSizeMb,
  223. portsInUse: new(sync.Map),
  224. sshPortLock: new(sync.Mutex),
  225. c: make(chan struct{}, deployConcurrent),
  226. qemuCmd: qemuCmd,
  227. }
  228. }
  229. if manager.runOnHost() {
  230. files, err := ioutil.ReadDir(RUN_ON_HOST_ROOT_PATH)
  231. if err != nil {
  232. return errors.Wrap(err, "readDir RUN_ON_HOST_ROOT_PATH")
  233. }
  234. for _, file := range files {
  235. if file.IsDir() {
  236. continue
  237. }
  238. if strings.HasPrefix(file.Name(), "hmp_") && strings.HasSuffix(file.Name(), ".socket") {
  239. hmpPath := path.Join(RUN_ON_HOST_ROOT_PATH, file.Name())
  240. tryCleanGuest(hmpPath)
  241. }
  242. }
  243. err = procutils.NewCommand("cp", "-rf", "/yunionos", RUN_ON_HOST_ROOT_PATH).Run()
  244. if err != nil {
  245. log.Errorf("Failed to mkdir /opt/cloud/host-deployer: %s", err)
  246. return errors.Wrap(err, "Failed to mkdir /opt/cloud/host-deployer: %s")
  247. }
  248. }
  249. return nil
  250. }
  251. type QemuKvmDriver struct {
  252. imageInfo qemuimg.SImageInfo
  253. qemuArchDriver IQemuArchDriver
  254. sshClient *ssh.Client
  255. partitions []fsdriver.IDiskPartition
  256. lvmPartitions []fsdriver.IDiskPartition
  257. diskId string
  258. }
  259. func NewQemuKvmDriver(imageInfo qemuimg.SImageInfo) *QemuKvmDriver {
  260. return &QemuKvmDriver{
  261. imageInfo: imageInfo,
  262. }
  263. }
  264. func (d *QemuKvmDriver) Connect(desc *apis.GuestDesc, diskId string) error {
  265. manager.Acquire()
  266. d.qemuArchDriver = NewCpuArchDriver(manager.cpuArch)
  267. err := d.connect(desc, diskId)
  268. if err != nil {
  269. d.qemuArchDriver.CleanGuest()
  270. return err
  271. }
  272. return nil
  273. }
  274. func (d *QemuKvmDriver) connect(guestDesc *apis.GuestDesc, diskId string) error {
  275. var (
  276. ncpu = 2
  277. memSizeMB = manager.getMemSizeMb()
  278. disks = make([]string, 0)
  279. diskIds = make([]string, 0)
  280. )
  281. var sshport = manager.GetSshFreePort()
  282. defer manager.unsetPort(sshport)
  283. if guestDesc != nil && len(guestDesc.Disks) > 0 {
  284. for i := range guestDesc.Disks {
  285. disks = append(disks, guestDesc.Disks[i].Path)
  286. diskIds = append(diskIds, guestDesc.Disks[i].DiskId)
  287. }
  288. } else {
  289. if diskId == "" {
  290. diskId = "single-disk"
  291. }
  292. disks = append(disks, d.imageInfo.Path)
  293. diskIds = append(diskIds, diskId)
  294. }
  295. err := d.qemuArchDriver.StartGuest(sshport, ncpu, memSizeMB, manager.hugepage, manager.hugepageSizeKB, d.imageInfo, disks, diskIds)
  296. if err != nil {
  297. return err
  298. }
  299. log.Infof("guest started ....")
  300. for i := 0; i < 3; i++ {
  301. cli, e := ssh.NewClient("localhost", sshport, "root", YUNIONOS_PASSWD, "")
  302. if e == nil {
  303. d.sshClient = cli
  304. break
  305. }
  306. err = e
  307. log.Errorf("new ssh client failed: %s", err)
  308. }
  309. if d.sshClient == nil {
  310. return errors.Wrap(err, "new ssh client")
  311. }
  312. log.Infof("guest ssh connected")
  313. out, err := d.sshRun("mount /dev/sr0 /opt")
  314. if err != nil {
  315. return errors.Wrapf(err, "failed mount iso /dev/sr0: %v", out)
  316. }
  317. return nil
  318. }
  319. func (d *QemuKvmDriver) Disconnect() error {
  320. if d.sshClient != nil {
  321. d.sshClient.Close()
  322. }
  323. d.qemuArchDriver.CleanGuest()
  324. d.qemuArchDriver = nil
  325. return nil
  326. }
  327. func (d *QemuKvmDriver) GetPartitions() []fsdriver.IDiskPartition {
  328. return nil
  329. }
  330. func (d *QemuKvmDriver) IsLVMPartition() bool {
  331. return false
  332. }
  333. func (d *QemuKvmDriver) Zerofree() {}
  334. func (d *QemuKvmDriver) ResizePartition(string, string) error {
  335. return nil
  336. }
  337. func (d *QemuKvmDriver) FormatPartition(fs, uuid string, features *apis.FsFeatures) error {
  338. return nil
  339. }
  340. func (d *QemuKvmDriver) MakePartition(fs string) error {
  341. return nil
  342. }
  343. func (d *QemuKvmDriver) DetectIsUEFISupport(rootfs fsdriver.IRootFsDriver) bool {
  344. return false
  345. }
  346. func (d *QemuKvmDriver) DetectIsBIOSSupport(rootfs fsdriver.IRootFsDriver) bool {
  347. return false
  348. }
  349. func (d *QemuKvmDriver) MountRootfs(readonly bool) (fsdriver.IRootFsDriver, error) {
  350. return nil, nil
  351. }
  352. func (d *QemuKvmDriver) UmountRootfs(fd fsdriver.IRootFsDriver) error {
  353. return nil
  354. }
  355. func (d *QemuKvmDriver) sshRun(cmd string) ([]string, error) {
  356. log.Infof("QemuKvmDriver start command %s", cmd)
  357. return d.sshClient.Run(cmd)
  358. }
  359. func (d *QemuKvmDriver) sshFilePutContent(params interface{}, filePath string) error {
  360. jcontent := jsonutils.Marshal(params).String()
  361. jcontent = strings.ReplaceAll(jcontent, "`", "\\`")
  362. cmd := fmt.Sprintf(`cat << EOF > %s
  363. %s
  364. EOF`, filePath, jcontent)
  365. out, err := d.sshRun(cmd)
  366. if err != nil {
  367. return errors.Wrapf(err, "sshFilePutContent %s", out)
  368. }
  369. return nil
  370. }
  371. func (d *QemuKvmDriver) DeployGuestfs(req *apis.DeployParams) (*apis.DeployGuestFsResponse, error) {
  372. defer func() {
  373. logStr, _ := d.sshRun("test -f /log && cat /log")
  374. log.Infof("DeployGuestfs log: %v", strings.Join(logStr, "\n"))
  375. }()
  376. err := d.sshFilePutContent(req, DEPLOY_PARAMS_FILE)
  377. if err != nil {
  378. return nil, errors.Wrap(err, "DeployGuestfs ssh copy deploy params")
  379. }
  380. cmd := fmt.Sprintf("%s --deploy-action deploy_guest_fs --deploy-params-file %s", DEPLOYER_BIN, DEPLOY_PARAMS_FILE)
  381. out, err := d.sshRun(cmd)
  382. if err != nil {
  383. return nil, errors.Wrapf(err, "run deploy_guest_fs failed %s", out)
  384. }
  385. log.Infof("DeployGuestfs log: %s", strings.Join(out, "\n"))
  386. errStrs, err := d.sshRun("test -f /error && cat /error || true")
  387. if err != nil {
  388. return nil, errors.Wrapf(err, "ssh gather errors failed")
  389. }
  390. log.Infof("deploy error str %v", errStrs)
  391. var retErr error = nil
  392. if len(errStrs[0]) > 0 {
  393. retErr = errors.Errorf("%s", errStrs[0])
  394. }
  395. responseStrs, err := d.sshRun("test -f /response && cat /response || true")
  396. if err != nil {
  397. return nil, errors.Wrapf(err, "ssh gather errors failed")
  398. }
  399. log.Infof("deploy response str %v", responseStrs)
  400. var res = new(apis.DeployGuestFsResponse)
  401. if len(responseStrs[0]) > 0 {
  402. err := json.Unmarshal([]byte(responseStrs[0]), res)
  403. if err != nil {
  404. return nil, errors.Wrapf(err, "failed unmarshal deploy response %s", responseStrs[0])
  405. }
  406. }
  407. return res, retErr
  408. }
  409. func (d *QemuKvmDriver) ResizeFs(req *apis.ResizeFsParams) (*apis.Empty, error) {
  410. defer func() {
  411. logStr, _ := d.sshRun("test -f /log && cat /log")
  412. log.Infof("ResizeFs log: %v", strings.Join(logStr, "\n"))
  413. }()
  414. params, _ := json.Marshal(req)
  415. cmd := fmt.Sprintf("%s --deploy-action resize_fs --deploy-params '%s'", DEPLOYER_BIN, params)
  416. out, err := d.sshRun(cmd)
  417. if err != nil {
  418. return nil, errors.Wrapf(err, "run resize_fs failed %s", out)
  419. }
  420. log.Infof("ResizeFs log: %s", strings.Join(out, "\n"))
  421. errStrs, err := d.sshRun("test -f /error && cat /error || true")
  422. if err != nil {
  423. return nil, errors.Wrapf(err, "ssh gather errors failed")
  424. }
  425. var retErr error = nil
  426. if len(errStrs[0]) > 0 {
  427. retErr = errors.Errorf("%s", errStrs[0])
  428. }
  429. return new(apis.Empty), retErr
  430. }
  431. func (d *QemuKvmDriver) FormatFs(req *apis.FormatFsParams) (*apis.Empty, error) {
  432. defer func() {
  433. logStr, _ := d.sshRun("test -f /log && cat /log")
  434. log.Infof("FormatFs log: %v", strings.Join(logStr, "\n"))
  435. }()
  436. params, _ := json.Marshal(req)
  437. cmd := fmt.Sprintf("%s --deploy-action format_fs --deploy-params '%s'", DEPLOYER_BIN, params)
  438. out, err := d.sshRun(cmd)
  439. if err != nil {
  440. return nil, errors.Wrapf(err, "run format_fs failed %s", out)
  441. }
  442. log.Infof("FormatFs log: %s", strings.Join(out, "\n"))
  443. errStrs, err := d.sshRun("test -f /error && cat /error || true")
  444. if err != nil {
  445. return nil, errors.Wrapf(err, "ssh gather errors failed")
  446. }
  447. var retErr error = nil
  448. if len(errStrs[0]) > 0 {
  449. retErr = errors.Errorf("%s", errStrs[0])
  450. }
  451. return new(apis.Empty), retErr
  452. }
  453. func (d *QemuKvmDriver) SaveToGlance(req *apis.SaveToGlanceParams) (*apis.SaveToGlanceResponse, error) {
  454. defer func() {
  455. logStr, _ := d.sshRun("test -f /log && cat /log")
  456. log.Infof("SaveToGlance log: %s", strings.Join(logStr, "\n"))
  457. }()
  458. params, _ := json.Marshal(req)
  459. cmd := fmt.Sprintf("%s --deploy-action save_to_glance --deploy-params '%s'", DEPLOYER_BIN, params)
  460. out, err := d.sshRun(cmd)
  461. if err != nil {
  462. return nil, errors.Wrapf(err, "run save_to_glance failed %s", out)
  463. }
  464. log.Infof("SaveToGlance log: %s", strings.Join(out, "\n"))
  465. responseStrs, err := d.sshRun("test -f /response && cat /response || true")
  466. if err != nil {
  467. return nil, errors.Wrapf(err, "ssh gather errors failed")
  468. }
  469. var res = new(apis.SaveToGlanceResponse)
  470. if len(responseStrs[0]) > 0 {
  471. err := json.Unmarshal([]byte(responseStrs[0]), res)
  472. if err != nil {
  473. return nil, errors.Wrapf(err, "failed unmarshal deploy response %s", responseStrs[0])
  474. }
  475. }
  476. errStrs, err := d.sshRun("test -f /error && cat /error || true")
  477. if err != nil {
  478. return nil, errors.Wrapf(err, "ssh gather errors failed")
  479. }
  480. var retErr error = nil
  481. if len(errStrs[0]) > 0 {
  482. retErr = errors.Errorf("%s", errStrs[0])
  483. }
  484. return res, retErr
  485. }
  486. func (d *QemuKvmDriver) ProbeImageInfo(req *apis.ProbeImageInfoPramas) (*apis.ImageInfo, error) {
  487. defer func() {
  488. logStr, _ := d.sshRun("test -f /log && cat /log")
  489. log.Infof("ProbeImageInfo log: %v", strings.Join(logStr, "\n"))
  490. }()
  491. params, _ := json.Marshal(req)
  492. cmd := fmt.Sprintf("%s --deploy-action probe_image_info --deploy-params '%s'", DEPLOYER_BIN, params)
  493. out, err := d.sshRun(cmd)
  494. if err != nil {
  495. return nil, errors.Wrapf(err, "run probe_image_info failed %s", out)
  496. }
  497. log.Infof("ProbeImageInfo log: %s", strings.Join(out, "\n"))
  498. responseStrs, err := d.sshRun("test -f /response && cat /response || true")
  499. if err != nil {
  500. return nil, errors.Wrapf(err, "ssh gather errors failed")
  501. }
  502. var res = new(apis.ImageInfo)
  503. if len(responseStrs[0]) > 0 {
  504. err := json.Unmarshal([]byte(responseStrs[0]), res)
  505. if err != nil {
  506. return nil, errors.Wrapf(err, "failed unmarshal deploy response %s", responseStrs[0])
  507. }
  508. }
  509. errStrs, err := d.sshRun("test -f /error && cat /error || true")
  510. if err != nil {
  511. return nil, errors.Wrapf(err, "ssh gather errors failed")
  512. }
  513. var retErr error = nil
  514. if len(errStrs[0]) > 0 {
  515. retErr = errors.Errorf("%s", errStrs[0])
  516. }
  517. return res, retErr
  518. }
  519. // wrap strings
  520. func __(v string, vs ...interface{}) string {
  521. return fmt.Sprintf(" "+v, vs...)
  522. }
  523. type QemuBaseDriver struct {
  524. hmp *monitor.HmpMonitor
  525. hugepagePath string
  526. pidPath string
  527. cleaned uint32
  528. }
  529. func (d *QemuBaseDriver) CleanGuest() {
  530. if d.hmp != nil {
  531. d.hmp.Quit(func(string) {})
  532. d.hmp = nil
  533. }
  534. if d.pidPath != "" && fileutils2.Exists(d.pidPath) {
  535. pid, _ := fileutils2.FileGetContents(d.pidPath)
  536. pid = strings.TrimSpace(pid)
  537. if len(pid) > 0 {
  538. out, err := procutils.NewCommand("kill", pid).Output()
  539. log.Infof("kill process %s %v", out, err)
  540. }
  541. }
  542. if d.hugepagePath != "" {
  543. err, out := procutils.NewCommand("umount", d.hugepagePath).Output()
  544. if err != nil {
  545. log.Errorf("failed umount %s %s : %s", d.hugepagePath, err, out)
  546. } else {
  547. procutils.NewCommand("rm", "-rf", d.hugepagePath).Run()
  548. }
  549. d.hugepagePath = ""
  550. }
  551. if atomic.LoadUint32(&d.cleaned) != 1 {
  552. manager.Release()
  553. atomic.StoreUint32(&d.cleaned, 1)
  554. }
  555. }
  556. func (d *QemuBaseDriver) startCmds(
  557. sshPort, ncpu, memSizeMB int, imageInfo qemuimg.SImageInfo, disksPath, diskIds []string,
  558. machineOpts, cdromDeviceOpts, fwOpts, socketPath, initrdPath, kernelPath string,
  559. ) string {
  560. cmd := manager.qemuCmd
  561. if sysutils.IsKvmSupport() {
  562. cmd += __("-enable-kvm")
  563. isCPUIntel := sysutils.IsProcessorIntel()
  564. isCPUAMD := sysutils.IsProcessorAmd()
  565. if isCPUIntel {
  566. cmd += __("-cpu host,vendor=GenuineIntel")
  567. } else if isCPUAMD {
  568. cmd += __("-cpu host,vendor=AuthenticAMD")
  569. } else {
  570. cmd += __("-cpu host")
  571. }
  572. } else {
  573. cmd += __("-cpu max")
  574. }
  575. cmd += machineOpts
  576. cmd += __("-nodefaults")
  577. cmd += __("-daemonize")
  578. cmd += __("-monitor unix:%s,server,nowait", socketPath)
  579. cmd += __("-pidfile %s", d.pidPath)
  580. cmd += __("-vnc none")
  581. cmd += __("-smp %d", ncpu)
  582. cmd += __("-m %dM", memSizeMB)
  583. cmd += __("-initrd %s", initrdPath)
  584. cmd += __("-kernel %s", kernelPath)
  585. cmd += __("-append rootfstype=ramfs")
  586. cmd += __("-append vsyscall=emulate")
  587. cmd += fwOpts
  588. cmd += __("-device virtio-serial-pci")
  589. cmd += __("-netdev user,id=hostnet0,hostfwd=tcp:127.0.0.1:%d-:22", sshPort)
  590. cmd += __("-device virtio-net-pci,netdev=hostnet0")
  591. cmd += __("-device virtio-scsi-pci,id=scsi")
  592. if imageInfo.Encrypted() {
  593. imageInfo.SetSecId("sec0")
  594. cmd += __("-object %s", imageInfo.SecretOptions())
  595. }
  596. if len(disksPath) == 0 {
  597. disksPath = []string{imageInfo.Path}
  598. }
  599. for i, diskPath := range disksPath {
  600. diskDrive := __("-drive file=%s,if=none,id=drive_%d,cache=none", strings.ReplaceAll(diskPath, "\\", "\\\\"), i)
  601. if imageInfo.Format != qemuimgfmt.RAW && imageInfo.Encrypted() {
  602. diskDrive += ",encrypt.format=luks,encrypt.key-secret=sec0"
  603. }
  604. cmd += diskDrive
  605. serialId := strings.ReplaceAll(diskIds[i], "-", "")
  606. if len(serialId) >= 20 {
  607. serialId = serialId[:20]
  608. }
  609. deviceId := serialId
  610. cmd += __("-device scsi-hd,drive=drive_%d,bus=scsi.0,id=drive_%d,serial=%s,device_id=%s", i, i, serialId, deviceId)
  611. }
  612. cmd += __("-drive id=cd0,if=none,media=cdrom,file=%s", DEPLOY_ISO)
  613. cmd += cdromDeviceOpts
  614. return cmd
  615. }
  616. type QemuX86Driver struct {
  617. QemuBaseDriver
  618. }
  619. func (d *QemuX86Driver) StartGuest(sshPort, ncpu, memSizeMB int, hugePage bool, pageSizeKB int, imageInfo qemuimg.SImageInfo, disksPath, diskIds []string) error {
  620. uuid := stringutils.UUID4()
  621. socketPath := fmt.Sprintf("/opt/cloud/host-deployer/hmp_%s.socket", uuid)
  622. d.pidPath = fmt.Sprintf("/opt/cloud/host-deployer/%s.pid", uuid)
  623. machineOpts := __("-M pc")
  624. cdromDeviceOpts := __("-device ide-cd,drive=cd0,bus=ide.1")
  625. cmd := d.startCmds(
  626. sshPort,
  627. ncpu,
  628. memSizeMB,
  629. imageInfo,
  630. disksPath,
  631. diskIds,
  632. machineOpts,
  633. cdromDeviceOpts,
  634. "",
  635. socketPath,
  636. manager.GetX86InitrdPath(),
  637. manager.GetX86KernelPath(),
  638. )
  639. log.Infof("start guest %s", cmd)
  640. out, err := manager.startQemu(cmd)
  641. if err != nil {
  642. log.Errorf("failed start guest %s: %s", out, err)
  643. return errors.Wrapf(err, "failed start guest %s", out)
  644. }
  645. var c = make(chan error)
  646. onMonitorConnected := func() {
  647. log.Infof("monitor connected")
  648. c <- nil
  649. }
  650. onMonitorDisConnect := func(e error) {
  651. log.Errorf("monitor disconnect %s", e)
  652. }
  653. onMonitorConnectFailed := func(e error) {
  654. log.Errorf("monitor connect failed %s", e)
  655. c <- e
  656. }
  657. m := monitor.NewHmpMonitor("", "", onMonitorDisConnect, onMonitorConnectFailed, onMonitorConnected)
  658. if err = m.ConnectWithSocket(socketPath); err != nil {
  659. return errors.Wrapf(err, "connect socket %s failed", socketPath)
  660. }
  661. if err = <-c; err != nil {
  662. return errors.Wrap(err, "monitor connect failed")
  663. }
  664. d.hmp = m
  665. return nil
  666. }
  667. type QemuARMDriver struct {
  668. QemuBaseDriver
  669. }
  670. func (d *QemuARMDriver) StartGuest(sshPort, ncpu, memSizeMB int, hugePage bool, pageSizeKB int, imageInfo qemuimg.SImageInfo, disksPath, diskIds []string) error {
  671. uuid := stringutils.UUID4()
  672. socketPath := fmt.Sprintf("/opt/cloud/host-deployer/hmp_%s.socket", uuid)
  673. d.pidPath = fmt.Sprintf("/opt/cloud/host-deployer/%s.pid", uuid)
  674. machineOpts := __("-M virt,gic-version=max")
  675. cdromDeviceOpts := __("-device scsi-cd,drive=cd0,share-rw=true")
  676. fwOpts := ""
  677. if manager.runOnHost() {
  678. fwOpts = __("-drive if=pflash,format=raw,unit=0,file=/opt/cloud/contrib/OVMF.fd,readonly=on")
  679. } else {
  680. fwOpts = __("-drive if=pflash,format=raw,unit=0,file=/usr/share/AAVMF/AAVMF_CODE.fd,readonly=on")
  681. }
  682. cmd := d.startCmds(
  683. sshPort,
  684. ncpu,
  685. memSizeMB,
  686. imageInfo,
  687. disksPath,
  688. diskIds,
  689. machineOpts,
  690. cdromDeviceOpts,
  691. fwOpts,
  692. socketPath,
  693. manager.GetARMInitrdPath(),
  694. manager.GetARMKernelPath(),
  695. )
  696. log.Infof("start guest %s", cmd)
  697. out, err := manager.startQemu(cmd)
  698. if err != nil {
  699. log.Errorf("failed start guest %s: %s", out, err)
  700. return errors.Wrapf(err, "failed start guest %s", out)
  701. }
  702. var c = make(chan error)
  703. onMonitorConnected := func() {
  704. log.Infof("monitor connected")
  705. c <- nil
  706. }
  707. onMonitorDisConnect := func(e error) {
  708. log.Errorf("monitor disconnect %s", e)
  709. }
  710. onMonitorConnectFailed := func(e error) {
  711. log.Errorf("monitor connect failed %s", e)
  712. c <- e
  713. }
  714. m := monitor.NewHmpMonitor("", "", onMonitorDisConnect, onMonitorConnectFailed, onMonitorConnected)
  715. if err = m.ConnectWithSocket(socketPath); err != nil {
  716. return errors.Wrapf(err, "connect socket %s failed", socketPath)
  717. }
  718. if err = <-c; err != nil {
  719. return errors.Wrap(err, "monitor connect failed")
  720. }
  721. d.hmp = m
  722. return nil
  723. }
  724. type QemuRISCVDriver struct {
  725. QemuBaseDriver
  726. }
  727. func (d *QemuRISCVDriver) StartGuest(sshPort, ncpu, memSizeMB int, hugePage bool, pageSizeKB int, imageInfo qemuimg.SImageInfo, disksPath, diskIds []string) error {
  728. uuid := stringutils.UUID4()
  729. socketPath := fmt.Sprintf("/opt/cloud/host-deployer/hmp_%s.socket", uuid)
  730. d.pidPath = fmt.Sprintf("/opt/cloud/host-deployer/%s.pid", uuid)
  731. machineOpts := __("-M virt")
  732. cdromDeviceOpts := __("-device scsi-cd,drive=cd0,share-rw=true")
  733. fwOpts := ""
  734. cmd := d.startCmds(
  735. sshPort,
  736. ncpu,
  737. memSizeMB,
  738. imageInfo,
  739. disksPath,
  740. diskIds,
  741. machineOpts,
  742. cdromDeviceOpts,
  743. fwOpts,
  744. socketPath,
  745. manager.GetRISCVInitrdPath(),
  746. manager.GetRISCVKernelPath(),
  747. )
  748. log.Infof("start guest %s", cmd)
  749. out, err := manager.startQemu(cmd)
  750. if err != nil {
  751. log.Errorf("failed start guest %s: %s", out, err)
  752. return errors.Wrapf(err, "failed start guest %s", out)
  753. }
  754. var c = make(chan error)
  755. onMonitorConnected := func() {
  756. log.Infof("monitor connected")
  757. c <- nil
  758. }
  759. onMonitorDisConnect := func(e error) {
  760. log.Errorf("monitor disconnect %s", e)
  761. }
  762. onMonitorConnectFailed := func(e error) {
  763. log.Errorf("monitor connect failed %s", e)
  764. c <- e
  765. }
  766. m := monitor.NewHmpMonitor("", "", onMonitorDisConnect, onMonitorConnectFailed, onMonitorConnected)
  767. if err = m.ConnectWithSocket(socketPath); err != nil {
  768. return errors.Wrapf(err, "connect socket %s failed", socketPath)
  769. }
  770. if err = <-c; err != nil {
  771. return errors.Wrap(err, "monitor connect failed")
  772. }
  773. d.hmp = m
  774. return nil
  775. }
  776. type IQemuArchDriver interface {
  777. StartGuest(sshPort, ncpu, memSizeMB int, hugePage bool, pageSizeKB int, imageInfo qemuimg.SImageInfo, disksPath, diskIds []string) error
  778. CleanGuest()
  779. }
  780. func NewCpuArchDriver(cpuArch string) IQemuArchDriver {
  781. if cpuArch == OS_ARCH_AARCH64 {
  782. return &QemuARMDriver{}
  783. } else if cpuArch == OS_ARCH_RISCV64 {
  784. return &QemuRISCVDriver{}
  785. }
  786. return &QemuX86Driver{}
  787. }