disktool.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944
  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 disktool
  15. import (
  16. "fmt"
  17. "math"
  18. "strconv"
  19. "strings"
  20. "yunion.io/x/jsonutils"
  21. "yunion.io/x/log"
  22. "yunion.io/x/pkg/errors"
  23. "yunion.io/x/pkg/utils"
  24. api "yunion.io/x/onecloud/pkg/apis/compute"
  25. raiddrivers "yunion.io/x/onecloud/pkg/baremetal/utils/raid/drivers"
  26. "yunion.io/x/onecloud/pkg/cloudcommon/types"
  27. "yunion.io/x/onecloud/pkg/compute/baremetal"
  28. fileutils "yunion.io/x/onecloud/pkg/util/fileutils2"
  29. "yunion.io/x/onecloud/pkg/util/ssh"
  30. "yunion.io/x/onecloud/pkg/util/sysutils"
  31. )
  32. const (
  33. // MB_SECTORS = 2048 // 1MiB = 2014 sectors
  34. GPT_SECTORS = 34
  35. RAID_DRVIER = "raid"
  36. NONRAID_DRIVER = "nonraid"
  37. PCIE_DRIVER = "pcie"
  38. LABEL_MSDOS = "msdos"
  39. LABEL_GPT = "gpt"
  40. )
  41. type Partition struct {
  42. disk *DiskPartitions
  43. index int
  44. bootable bool
  45. start int64
  46. end int64
  47. count int64
  48. diskType string
  49. fs string
  50. dev string
  51. }
  52. func NewPartition(
  53. disk *DiskPartitions,
  54. index int, bootable bool,
  55. start int64, end int64, count int64,
  56. diskType string, fs string, dev string,
  57. ) *Partition {
  58. return &Partition{
  59. disk: disk,
  60. index: index,
  61. bootable: bootable,
  62. start: start,
  63. end: end,
  64. count: count,
  65. diskType: diskType,
  66. fs: fs,
  67. dev: dev,
  68. }
  69. }
  70. func (p *Partition) GetStart() int64 {
  71. return p.start
  72. }
  73. func (p *Partition) GetEnd() int64 {
  74. return p.end
  75. }
  76. func (p *Partition) GetDev() string {
  77. return p.dev
  78. }
  79. func (p *Partition) String() string {
  80. bootStr := ""
  81. if p.bootable {
  82. bootStr = " boot"
  83. }
  84. return fmt.Sprintf("%s %d %d %s %s%s", p.dev, p.start, p.end, p.diskType, p.fs, bootStr)
  85. }
  86. func (p *Partition) Format(fs string, uuid string) error {
  87. cmd := []string{}
  88. cmdUUID := []string{}
  89. switch fs {
  90. case "swap":
  91. cmd = []string{"mkswap", "-U", uuid}
  92. case "ext2":
  93. cmd = []string{"mkfs.ext2"}
  94. cmdUUID = []string{"tune2fs", "-U", uuid}
  95. case "ext3":
  96. cmd = []string{"mkfs.ext3"}
  97. cmdUUID = []string{"tune2fs", "-U", uuid}
  98. case "ext4":
  99. // for baremetal, force 64bit support large disks
  100. cmd = []string{"mkfs.ext4", "-O", "64bit", "-E", "lazy_itable_init=1", "-T", "largefile"}
  101. cmdUUID = []string{"tune2fs", "-U", uuid}
  102. case "ext4dev":
  103. cmd = []string{"mkfs.ext4dev", "-E", "lazy_itable_init=1"}
  104. cmdUUID = []string{"tune2fs", "-U", uuid}
  105. case "xfs":
  106. cmd = []string{"mkfs.xfs", "-f", "-m", "crc=0", "-i", "projid32bit=0", "-n", "ftype=0"}
  107. cmdUUID = []string{"PATH=/bin:/sbin:/usr/bin:/usr/sbin /usr/sbin/xfs_admin", "-U", uuid}
  108. default:
  109. return fmt.Errorf("Unsupported filesystem %s", fs)
  110. }
  111. cmd = append(cmd, p.dev)
  112. cmds := []string{strings.Join(cmd, " ")}
  113. if len(cmdUUID) != 0 {
  114. cmdUUID = append(cmdUUID, p.dev)
  115. cmds = append(cmds, strings.Join(cmdUUID, " "))
  116. }
  117. _, err := p.Run(cmds...)
  118. return err
  119. }
  120. func (p *Partition) Fsck() error {
  121. if p.fs == "" {
  122. return fmt.Errorf("filesystem is empty")
  123. }
  124. cmd := []string{}
  125. if strings.HasPrefix(p.fs, "ext") {
  126. cmd = []string{fmt.Sprintf("fsck.%s", p.fs), "-f", "-p"}
  127. } else if p.fs == "xfs" {
  128. cmd = []string{"fsck.xfs"}
  129. } else {
  130. return fmt.Errorf("Unsupported fsck filesystem: %s", p.fs)
  131. }
  132. cmd = append(cmd, p.dev)
  133. _, err := p.Run(strings.Join(cmd, " "))
  134. return err
  135. }
  136. func (p *Partition) Run(cmds ...string) ([]string, error) {
  137. return p.disk.Run(cmds...)
  138. }
  139. func (p *Partition) ResizeFs() error {
  140. if p.fs == "" {
  141. return nil
  142. }
  143. cmd := []string{}
  144. if strings.HasPrefix(p.fs, "linux-swap") {
  145. cmd = []string{"mkswap", p.dev}
  146. } else if strings.HasPrefix(p.fs, "ext") {
  147. if err := p.Fsck(); err != nil {
  148. log.Warningf("FSCK error: %v", err)
  149. }
  150. cmd = []string{"resize2fs", p.dev}
  151. } else if p.fs == "xfs" {
  152. return p.ResizeXfs()
  153. }
  154. if len(cmd) == 0 {
  155. return nil
  156. }
  157. _, err := p.Run(strings.Join(cmd, " "))
  158. return err
  159. }
  160. func (p *Partition) ResizeXfs() error {
  161. mountPath := fmt.Sprintf("/tmp/%s", strings.Replace(p.dev, "/", "", -1))
  162. cmds := []string{
  163. fmt.Sprintf("mkdir -p %s", mountPath),
  164. fmt.Sprintf("mount -t xfs %s %s", p.dev, mountPath),
  165. fmt.Sprintf("xfs_growfs -d %s", mountPath),
  166. fmt.Sprintf("umount %s", mountPath),
  167. fmt.Sprintf("rm -fr %s", mountPath),
  168. }
  169. _, err := p.Run(cmds...)
  170. if err != nil {
  171. log.Errorf("Resize xfs error: %v", err)
  172. cmds = []string{
  173. fmt.Sprintf("umount %s", mountPath),
  174. fmt.Sprintf("rm -rf %s", mountPath),
  175. }
  176. _, err = p.Run(cmds...)
  177. if err != nil {
  178. log.Errorf("Umount error: %v", err)
  179. return err
  180. }
  181. }
  182. return nil
  183. }
  184. func (p *Partition) GetSizeMB() (int64, error) {
  185. log.Infof("GetSizeMB: start %d, end: %d, count: %d", p.start, p.end, p.count)
  186. if p.count != (p.end - p.start + 1) {
  187. return 0, fmt.Errorf("Count(%d) != End(%d)-Start(%d)+1", p.count, p.end, p.start)
  188. }
  189. return p.count * 512 / 1024 / 1024, nil
  190. }
  191. func (p *Partition) GetDisk() *DiskPartitions {
  192. return p.disk
  193. }
  194. type DiskPartitions struct {
  195. driver string
  196. adapter int
  197. raidConfig string
  198. sizeMB int64 // MB
  199. diskType string // ssd, rotate, hybird
  200. tool *PartitionTool
  201. dev string
  202. devName string
  203. sectors int64
  204. blockSize int64
  205. rotate bool
  206. desc string
  207. label string
  208. pciPath string
  209. partitions []*Partition
  210. }
  211. func newDiskPartitions(driver string, adapter int, raidConfig string, sizeMB int64, blockSize int64, diskType string, softRaidIdx *int, tool *PartitionTool) *DiskPartitions {
  212. ps := new(DiskPartitions)
  213. ps.driver = driver
  214. ps.adapter = adapter
  215. ps.raidConfig = raidConfig
  216. ps.sizeMB = sizeMB
  217. ps.tool = tool
  218. ps.blockSize = blockSize
  219. ps.diskType = diskType
  220. ps.partitions = make([]*Partition, 0)
  221. // soft raid, mdadm
  222. if softRaidIdx != nil {
  223. ps.GetMdadmInfo(softRaidIdx)
  224. }
  225. return ps
  226. }
  227. func (ps *DiskPartitions) GetMdadmInfo(softRaidIdx *int) {
  228. devLinkName := fmt.Sprintf("/dev/md/md%d", *softRaidIdx)
  229. devLinkNickname := fmt.Sprintf("/dev/md/md%d_0", *softRaidIdx)
  230. cmd := fmt.Sprintf("readlink -f $(test -e %s && echo %s || echo %s)", devLinkName, devLinkName, devLinkNickname)
  231. out, err := ps.tool.Run(cmd)
  232. if err != nil || len(out) == 0 {
  233. log.Errorf("failed readlink of %s: %s", devLinkName, err)
  234. return
  235. }
  236. ps.dev = strings.TrimSpace(out[0])
  237. ps.devName = ps.dev
  238. uuid, sectors := ps.tool.GetMdadmUuidAndSector(ps.dev)
  239. ps.pciPath = uuid
  240. ps.sectors = sectors
  241. ps.blockSize = 512
  242. }
  243. func (p *DiskPartitions) IsRaidDriver() bool {
  244. return utils.IsInStringArray(p.driver, []string{
  245. baremetal.DISK_DRIVER_MEGARAID,
  246. baremetal.DISK_DRIVER_HPSARAID,
  247. baremetal.DISK_DRIVER_MARVELRAID,
  248. baremetal.DISK_DRIVER_MPT2SAS,
  249. })
  250. }
  251. func (p *DiskPartitions) GetDev() string {
  252. return p.dev
  253. }
  254. func getPCIPathPrefix(input string) string {
  255. input = strings.TrimSpace(input)
  256. parts := strings.Split(input, "/")
  257. return strings.Join(parts[0:len(parts)-2], "/")
  258. }
  259. func (p *DiskPartitions) SetInfo(info *types.SDiskInfo) (*DiskPartitions, error) {
  260. p.dev = fmt.Sprintf("/dev/%s", info.Dev)
  261. p.devName = info.Dev
  262. p.sectors = info.Sector
  263. p.desc = info.ModuleInfo
  264. p.blockSize = info.Block
  265. if p.blockSize == 4096 {
  266. p.sectors = (p.sectors >> 3)
  267. }
  268. // /sys/block/sda => /sys/devices/pci0000:00/0000:00:04.0/virtio1/host0/target0:0:0/0:0:0:0/block/sda
  269. // /sys/block/nvme1n1 => /sys/devices/pci0000:00/0000:00:1a.0/0000:03:00.0/nvme/nvme1/nvme1n1
  270. outputs, err := p.Run(fmt.Sprintf("readlink -f /sys/block/%s", info.Dev))
  271. if err != nil {
  272. return nil, errors.Wrapf(err, "failed to read device %s pci path", p.dev)
  273. }
  274. if len(outputs) > 0 {
  275. p.pciPath = getPCIPathPrefix(outputs[0])
  276. }
  277. return p, nil
  278. }
  279. func (p *DiskPartitions) ReInitInfo() error {
  280. cmd := "/lib/mos/lsdisk"
  281. lines, err := p.tool.Run(cmd)
  282. if err != nil {
  283. return errors.Wrapf(err, "Disk %#v reset info list disk", p)
  284. }
  285. for _, disk := range sysutils.ParseDiskInfo(lines, p.driver) {
  286. if disk.Dev == p.GetDevName() {
  287. if _, err := p.SetInfo(disk); err != nil {
  288. return errors.Wrapf(err, "set info of disk %v", disk)
  289. }
  290. }
  291. }
  292. return p.RetrievePartitionInfo()
  293. }
  294. func (ps *DiskPartitions) MBSectors() int64 {
  295. return int64(1024 * 1024 / ps.blockSize)
  296. }
  297. func (ps *DiskPartitions) String() string {
  298. return fmt.Sprintf("%s %d %s", ps.devName, ps.sizeMB, ps.driver)
  299. }
  300. func (ps *DiskPartitions) DebugString() string {
  301. partitionsStr := []string{}
  302. for _, p := range ps.partitions {
  303. partitionsStr = append(partitionsStr, fmt.Sprintf("{%#v}", *p))
  304. }
  305. return fmt.Sprintf("driver: %s, dev: %s, sectors: %d, partitions: %#v", ps.driver, ps.dev, ps.sectors, partitionsStr)
  306. }
  307. func (ps *DiskPartitions) IsReady() bool {
  308. if ps.dev == "" {
  309. return false
  310. }
  311. return true
  312. }
  313. func (ps *DiskPartitions) GetDevName() string {
  314. devName := ps.devName
  315. if ps.raidConfig == baremetal.DISK_CONF_NONE {
  316. return devName
  317. }
  318. raidDrv, err := raiddrivers.GetDriverWithInit(ps.driver, ps.tool.runner.Term())
  319. if err != nil {
  320. log.Errorf("Failed to find %s raid driver: %v", ps.driver, err)
  321. return devName
  322. }
  323. // find first raid adapter logical volume
  324. lv, err := raiddrivers.GetFirstLogicalVolume(raidDrv, ps.adapter)
  325. if err != nil {
  326. log.Errorf("Failed to find raid %s adapter %d first logical volume: %v", raidDrv.GetName(), ps.adapter, err)
  327. return devName
  328. }
  329. if len(lv.BlockDev) == 0 {
  330. log.Warningf("Raid %s adapter %d first logical volume block device is empty", raidDrv.GetName(), ps.adapter)
  331. return devName
  332. }
  333. devName = strings.TrimLeft(lv.BlockDev, "/dev/")
  334. return devName
  335. }
  336. func (ps *DiskPartitions) GetPCIPath() string {
  337. return ps.pciPath
  338. }
  339. func (ps *DiskPartitions) RetrievePartitionInfo() error {
  340. ps.partitions = make([]*Partition, 0)
  341. cmd := []string{"parted", "-s", ps.dev, "--", "unit", "s", "print"}
  342. ret, err := ps.Run(strings.Join(cmd, " "))
  343. if err != nil {
  344. return err
  345. }
  346. parts, label := fileutils.ParseDiskPartitions(ps.dev, ret)
  347. ps.label = label
  348. for _, part := range parts {
  349. ps.addPartition(part)
  350. }
  351. return nil
  352. }
  353. func (ps *DiskPartitions) addPartition(p fileutils.Partition) {
  354. part := NewPartition(ps, p.Index, p.Bootable, p.Start, p.End, p.Count, p.DiskType, p.Fs, p.DevName)
  355. ps.partitions = append(ps.partitions, part)
  356. }
  357. func (ps *DiskPartitions) GPTEndSector() int64 {
  358. return ps.sectors - int64(GPT_SECTORS)
  359. }
  360. func (ps *DiskPartitions) FsToTypeCode(fs string) string {
  361. if strings.Contains(fs, "swap") {
  362. return "8200"
  363. } else if strings.HasPrefix(fs, "ntfs") || strings.HasPrefix(fs, "fat") {
  364. return "0700"
  365. }
  366. return "8300"
  367. }
  368. func (ps *DiskPartitions) doResize(dev string, cmd string) error {
  369. cmds := []string{}
  370. cmds = append(cmds, cmd)
  371. cmds = append(cmds, fmt.Sprintf("hdparm -f %s", dev))
  372. cmds = append(cmds, fmt.Sprintf("hdparm -z %s", dev))
  373. _, err := ps.tool.Run(cmds...)
  374. if err != nil {
  375. return err
  376. }
  377. return ps.RetrievePartitionInfo()
  378. }
  379. func (ps *DiskPartitions) GetPartitions() []*Partition {
  380. return ps.partitions
  381. }
  382. func (ps *DiskPartitions) ResizePartition(offsetMB int64) error {
  383. if len(ps.partitions) == 0 {
  384. return fmt.Errorf("ResizePartitions error: total %d partitions", len(ps.partitions))
  385. }
  386. var cmd string
  387. if ps.label == LABEL_MSDOS {
  388. part := ps.partitions[len(ps.partitions)-1]
  389. if part.diskType == "extended" {
  390. log.Infof("Find last partition an empty extended partition, removed it")
  391. cmd := fmt.Sprintf("parted -a none -s %s -- rm %d", part.disk.dev, part.index)
  392. if err := ps.doResize(part.disk.dev, cmd); err != nil {
  393. return fmt.Errorf("Fail to remove empty extended partition: %v", err)
  394. }
  395. }
  396. }
  397. part := ps.partitions[len(ps.partitions)-1]
  398. var end int64
  399. if offsetMB <= 0 {
  400. end = ps.GPTEndSector()
  401. } else {
  402. end = offsetMB*ps.MBSectors() - 1
  403. if end > ps.GPTEndSector() {
  404. end = ps.GPTEndSector()
  405. }
  406. }
  407. if end < part.end {
  408. log.Warningf("Cannot reduce size %d %d, no need to resize", end, part.end)
  409. end = part.end
  410. }
  411. if ps.label == LABEL_MSDOS {
  412. if part.diskType == "logical" {
  413. extendIdx := -1
  414. for i := range ps.partitions {
  415. if ps.partitions[i].diskType == "extended" {
  416. log.Infof("Find extended at %d", i)
  417. extendIdx = i
  418. break
  419. }
  420. }
  421. if extendIdx < 0 {
  422. return fmt.Errorf("To resize logical parition, but fail to find extend partiton")
  423. }
  424. cmd = fmt.Sprintf("parted -a none -s %s -- unit s", part.disk.dev)
  425. partsLen := len(ps.partitions)
  426. for i := partsLen - 1; i > extendIdx-1; i-- {
  427. cmd = fmt.Sprintf("%s rm %d", cmd, ps.partitions[i].index)
  428. }
  429. for i := extendIdx; i < partsLen; i++ {
  430. cmdLet := fmt.Sprintf("mkpart %s %d", ps.partitions[i].diskType, ps.partitions[i].start)
  431. if i == extendIdx {
  432. cmdLet = fmt.Sprintf("%s %d", cmdLet, ps.GPTEndSector())
  433. } else if i == (len(ps.partitions) - 1) {
  434. cmdLet = fmt.Sprintf("%s %d", cmdLet, end)
  435. } else {
  436. cmdLet = fmt.Sprintf("%s %d", cmdLet, ps.partitions[i].end)
  437. }
  438. cmd = fmt.Sprintf("%s %s", cmd, cmdLet)
  439. }
  440. } else {
  441. cmd = fmt.Sprintf("parted -a none -s %s -- unit s rm %d mkpart %s", part.disk.dev, part.index, part.diskType)
  442. cmd = fmt.Sprintf("%s %d %d", cmd, part.start, end)
  443. if part.bootable {
  444. cmd = fmt.Sprintf("%s set %d boot on", cmd, part.index)
  445. }
  446. }
  447. } else {
  448. // gpt
  449. cmd = fmt.Sprintf("sgdisk --set-alignment=1 --delete=%d", part.index)
  450. cmd = fmt.Sprintf("%s --new=%d:%d:%d", cmd, part.index, part.start, end)
  451. if len(part.diskType) != 0 {
  452. cmd = fmt.Sprintf("%s --change-name=%d:\"%s\"", cmd, part.index, part.diskType)
  453. }
  454. if len(part.fs) != 0 {
  455. cmd = fmt.Sprintf("%s --typecode=%d:%s", cmd, part.index, ps.FsToTypeCode(part.fs))
  456. }
  457. cmd = fmt.Sprintf("%s %s", cmd, ps.dev)
  458. }
  459. log.Infof("Resize cmd: %s", cmd)
  460. if err := ps.doResize(part.disk.dev, cmd); err != nil {
  461. return err
  462. }
  463. return ps.partitions[len(ps.partitions)-1].ResizeFs()
  464. }
  465. func (ps *DiskPartitions) IsSpaceAvailable(sizeMB int64) bool {
  466. start := ps.getNextPartStart()
  467. freeSect := ps.GPTEndSector() - start
  468. if sizeMB <= 0 {
  469. sizeMB = 1
  470. }
  471. reqSect := sizeMB * ps.MBSectors()
  472. if reqSect > freeSect {
  473. log.Warningf("No space require %d(%d) left %d", reqSect, sizeMB, freeSect)
  474. return false
  475. }
  476. return true
  477. }
  478. func (ps *DiskPartitions) MakeLabel() error {
  479. label := LABEL_GPT
  480. if ps.sizeMB <= 1024*1024*2 {
  481. label = LABEL_MSDOS
  482. }
  483. return ps.makeLabel(label)
  484. }
  485. func (ps *DiskPartitions) makeLabel(label string) error {
  486. ps.label = label
  487. cmd := fmt.Sprintf("parted -s %s -- mklabel %s", ps.dev, ps.label)
  488. // cmd = ['/usr/sbin/sgdisk', '-og', self.dev]
  489. _, err := ps.Run(cmd)
  490. return err
  491. }
  492. func (ps *DiskPartitions) getNextPartIndex() int {
  493. max := 0
  494. for _, part := range ps.partitions {
  495. if max < part.index {
  496. max = part.index
  497. }
  498. }
  499. return max + 1
  500. }
  501. func (ps *DiskPartitions) getNextPartStart() int64 {
  502. var start int64
  503. if len(ps.partitions) == 0 {
  504. start = ps.MBSectors() // 1MB
  505. } else {
  506. var gap int64 = 2
  507. lastPart := ps.partitions[len(ps.partitions)-1]
  508. start = ((lastPart.end + gap) / ps.MBSectors()) * ps.MBSectors()
  509. if start < lastPart.end+gap {
  510. start += ps.MBSectors()
  511. }
  512. }
  513. return start
  514. }
  515. func (ps *DiskPartitions) Run(cmd ...string) ([]string, error) {
  516. return ps.tool.Run(cmd...)
  517. }
  518. func (ps *DiskPartitions) CreatePartition(sizeMB int64, fs string, doformat bool, uuid string) error {
  519. if len(ps.partitions) == 0 {
  520. if err := ps.MakeLabel(); err != nil {
  521. return err
  522. }
  523. }
  524. start := ps.getNextPartStart()
  525. var end int64
  526. if sizeMB <= 0 {
  527. end = start + (ps.GPTEndSector()-start)/ps.MBSectors()*ps.MBSectors() - 1
  528. } else {
  529. end = start + sizeMB*ps.MBSectors() - 1
  530. }
  531. partIdx := ps.getNextPartIndex()
  532. var cmd string
  533. var diskType string
  534. if ps.label == LABEL_MSDOS {
  535. if partIdx < 5 {
  536. diskType = "primary"
  537. } else if partIdx < 9 {
  538. diskType = "logical"
  539. } else {
  540. return fmt.Errorf("Too many partitions on a MSDOS disk")
  541. }
  542. cmd = fmt.Sprintf("parted -a none -s %s -- unit s mkpart %s", ps.dev, diskType)
  543. if len(fs) != 0 {
  544. cmd = fmt.Sprintf("%s %s", cmd, fileutils.FsFormatToDiskType(fs))
  545. }
  546. cmd = fmt.Sprintf("%s %d %d", cmd, start, end)
  547. } else {
  548. cmd = fmt.Sprintf("sgdisk --set-alignment=1 --new=%d:%d:%d", partIdx, start, end)
  549. if len(fs) != 0 {
  550. cmd = fmt.Sprintf("%s --typecode=%d:%s", cmd, partIdx, ps.FsToTypeCode(fs))
  551. }
  552. cmd = fmt.Sprintf("%s %s", cmd, ps.dev)
  553. }
  554. _, err := ps.Run(cmd)
  555. if err != nil {
  556. return err
  557. }
  558. if err := ps.RetrievePartitionInfo(); err != nil {
  559. return fmt.Errorf("Fail to RetrievePartitionInfo: %v", err)
  560. }
  561. if fs != "" && doformat {
  562. err = ps.partitions[len(ps.partitions)-1].Format(fs, uuid)
  563. if err != nil {
  564. return fmt.Errorf("Fail to format partition: %v", err)
  565. }
  566. if err := ps.RetrievePartitionInfo(); err != nil {
  567. return fmt.Errorf("Fail to RetrievePartitionInfo: %v", err)
  568. }
  569. }
  570. return nil
  571. }
  572. type IPartitionRunner interface {
  573. Run(cmds ...string) ([]string, error)
  574. Term() *ssh.Client
  575. }
  576. type PartitionTool struct {
  577. disks []*DiskPartitions
  578. diskTable map[string][]*DiskPartitions
  579. runner IPartitionRunner
  580. }
  581. func NewPartitionTool(runner IPartitionRunner) *PartitionTool {
  582. return &PartitionTool{
  583. disks: make([]*DiskPartitions, 0),
  584. diskTable: make(map[string][]*DiskPartitions),
  585. runner: runner,
  586. }
  587. }
  588. func (tool *PartitionTool) DebugString() string {
  589. ret := []string{}
  590. disksString := func(disks []*DiskPartitions) []string {
  591. for _, disk := range disks {
  592. ret = append(ret, disk.DebugString())
  593. }
  594. return ret
  595. }
  596. for driver, disks := range tool.diskTable {
  597. s := fmt.Sprintf("(%s: %v)", driver, disksString(disks))
  598. ret = append(ret, s)
  599. }
  600. return strings.Join(ret, "\n")
  601. }
  602. func (tool *PartitionTool) Disks() []*DiskPartitions {
  603. return tool.disks
  604. }
  605. func (tool *PartitionTool) parseDiskInfo(lines []string, driver string) []*types.SDiskInfo {
  606. disks := sysutils.ParseDiskInfo(lines, driver)
  607. return disks
  608. }
  609. func (tool *PartitionTool) parseLsDisk(lines []string, driver string) {
  610. disks := tool.parseDiskInfo(lines, driver)
  611. if len(disks) == 0 {
  612. return
  613. }
  614. minCnt := int(math.Min(float64(len(disks)), float64(len(tool.diskTable[driver]))))
  615. raidSsdDiskCnt := 0
  616. for i := 0; i < minCnt; i++ {
  617. driverDisk := tool.diskTable[driver][i]
  618. if utils.IsInStringArray(driver, []string{NONRAID_DRIVER, PCIE_DRIVER}) {
  619. // find size matched disk from disks
  620. size := driverDisk.sizeMB
  621. var remoteDisk *types.SDiskInfo
  622. for ri := range disks {
  623. if disks[ri].Size == size {
  624. remoteDisk = disks[ri]
  625. // remove matched disks
  626. disks = append(disks[:ri], disks[ri+1:]...)
  627. break
  628. }
  629. }
  630. driverDisk.SetInfo(remoteDisk)
  631. } else {
  632. // raid disk set by order
  633. if driverDisk.diskType == api.DISK_TYPE_SSD {
  634. // find ssd matched remoteDisk
  635. var remoteDisk *types.SDiskInfo
  636. for ri := range disks {
  637. if !disks[ri].Rotate {
  638. remoteDisk = disks[ri]
  639. disks = append(disks[:ri], disks[ri+1:]...)
  640. raidSsdDiskCnt++
  641. break
  642. }
  643. }
  644. if remoteDisk == nil {
  645. // not found ssd disk
  646. remoteDisk = disks[i-raidSsdDiskCnt]
  647. log.Warningf("not found ssd driver disk for %#v, using remote disk %#v", driverDisk, remoteDisk)
  648. }
  649. driverDisk.SetInfo(remoteDisk)
  650. } else {
  651. driverDisk.SetInfo(disks[i-raidSsdDiskCnt])
  652. }
  653. }
  654. }
  655. }
  656. func (tool *PartitionTool) FetchDiskConfs(diskConfs []baremetal.DiskConfiguration) *PartitionTool {
  657. for _, d := range diskConfs {
  658. disk := newDiskPartitions(d.Driver, d.Adapter, d.RaidConfig, d.Size, d.Block, d.DiskType, d.SoftRaidIdx, tool)
  659. tool.disks = append(tool.disks, disk)
  660. isSoftRaid := d.RaidConfig != baremetal.DISK_CONF_NONE
  661. var key string
  662. if d.Driver == baremetal.DISK_DRIVER_LINUX && !isSoftRaid {
  663. key = NONRAID_DRIVER
  664. } else if d.Driver == baremetal.DISK_DRIVER_PCIE && !isSoftRaid {
  665. key = PCIE_DRIVER
  666. } else {
  667. key = RAID_DRVIER
  668. }
  669. if _, ok := tool.diskTable[key]; !ok {
  670. tool.diskTable[key] = make([]*DiskPartitions, 0)
  671. }
  672. tool.diskTable[key] = append(tool.diskTable[key], disk)
  673. }
  674. return tool
  675. }
  676. func (tool *PartitionTool) reorderRootDisk(matcher *api.BaremetalRootDiskMatcher) {
  677. isDiskMatch := func(disk *DiskPartitions, matcher *api.BaremetalRootDiskMatcher) bool {
  678. if matcher.Device != "" {
  679. if disk.dev == matcher.Device {
  680. return true
  681. }
  682. if disk.devName == matcher.Device {
  683. return true
  684. }
  685. }
  686. if matcher.SizeMB > 0 {
  687. if disk.sizeMB == matcher.SizeMB {
  688. return true
  689. }
  690. }
  691. if matcher.SizeMBRange != nil {
  692. if disk.sizeMB >= matcher.SizeMBRange.Start && disk.sizeMB <= matcher.SizeMBRange.End {
  693. return true
  694. }
  695. }
  696. if matcher.PCIPath != "" {
  697. if disk.pciPath == matcher.PCIPath {
  698. return true
  699. }
  700. }
  701. return false
  702. }
  703. var rootDiskStr string
  704. var rootDiskIdx = 0
  705. for idx, disk := range tool.disks {
  706. if isDiskMatch(disk, matcher) {
  707. rootDiskIdx = idx
  708. rootDiskStr = disk.String()
  709. break
  710. }
  711. }
  712. log.Infof("Select %d %q as root disk by matcher: %s", rootDiskIdx, rootDiskStr, jsonutils.Marshal(matcher))
  713. newDisks := make([]*DiskPartitions, 0)
  714. newDisks = append(newDisks, tool.disks[rootDiskIdx])
  715. for idx := range tool.disks {
  716. if idx == rootDiskIdx {
  717. continue
  718. }
  719. newDisks = append(newDisks, tool.disks[idx])
  720. }
  721. tool.disks = newDisks
  722. }
  723. func (tool *PartitionTool) IsAllDisksReady() bool {
  724. for idx, d := range tool.disks {
  725. if !d.IsReady() {
  726. log.Errorf("disk.%d %#v not ready", idx, d)
  727. return false
  728. }
  729. }
  730. return true
  731. }
  732. func (tool *PartitionTool) GetMdadmUuidAndSector(devPath string) (string, int64) {
  733. var uuid string
  734. var sectorsRet int64
  735. // get md uuid as pci path
  736. cmd := fmt.Sprintf("/sbin/mdadm --detail %s | grep UUID", devPath)
  737. output, err := tool.Run(cmd)
  738. if err == nil && len(output) > 0 {
  739. uuidSeg := output[0]
  740. segs := strings.SplitN(strings.TrimSpace(uuidSeg), ":", 2)
  741. if len(segs) == 2 {
  742. uuid = strings.TrimSpace(segs[1])
  743. }
  744. }
  745. // get block size
  746. cmd = fmt.Sprintf("blockdev --getsz %s 2>/dev/null || echo 0", devPath)
  747. output, err = tool.Run(cmd)
  748. if err == nil && len(output) > 0 {
  749. if sectors, err := strconv.ParseInt(strings.TrimSpace(output[0]), 10, 64); err == nil {
  750. sectorsRet = sectors
  751. }
  752. }
  753. return uuid, sectorsRet
  754. }
  755. func (tool *PartitionTool) RetrieveDiskInfo(rootMatcher *api.BaremetalRootDiskMatcher) error {
  756. for _, disk := range tool.disks {
  757. if baremetal.DISK_DRIVERS_SOFT_RAID.Has(disk.driver) && disk.raidConfig != baremetal.DISK_CONF_NONE {
  758. log.Infof("Soft raid mdadm set diskinfo dev %s", disk.dev)
  759. uuid, sectors := tool.GetMdadmUuidAndSector(disk.dev)
  760. disk.pciPath = uuid
  761. disk.sectors = sectors
  762. disk.blockSize = 512
  763. }
  764. }
  765. for _, driver := range []string{RAID_DRVIER, NONRAID_DRIVER, PCIE_DRIVER} {
  766. cmd := fmt.Sprintf("/lib/mos/lsdisk --%s", driver)
  767. ret, err := tool.Run(cmd)
  768. if err != nil {
  769. return err
  770. }
  771. tool.parseLsDisk(ret, driver)
  772. }
  773. // reorder tool.disks
  774. if rootMatcher != nil {
  775. tool.reorderRootDisk(rootMatcher)
  776. }
  777. return nil
  778. }
  779. func (tool *PartitionTool) RetrievePartitionInfo() error {
  780. for _, disk := range tool.disks {
  781. if err := disk.RetrievePartitionInfo(); err != nil {
  782. return err
  783. }
  784. }
  785. return nil
  786. }
  787. func (tool *PartitionTool) ResizePartition(diskIdx int, sizeMB int64) error {
  788. if diskIdx >= 0 && diskIdx < len(tool.disks) {
  789. return tool.disks[diskIdx].ResizePartition(sizeMB)
  790. }
  791. return fmt.Errorf("Invalid disk index: %d", diskIdx)
  792. }
  793. func (tool *PartitionTool) GetDisks() []*DiskPartitions {
  794. return tool.disks
  795. }
  796. func (tool *PartitionTool) GetRootDisk() *DiskPartitions {
  797. if len(tool.disks) == 0 {
  798. return nil
  799. }
  800. return tool.disks[0]
  801. }
  802. func (tool *PartitionTool) GetPCIEDisks() []*DiskPartitions {
  803. disks := make([]*DiskPartitions, 0)
  804. for _, disk := range tool.disks {
  805. if disk.driver == PCIE_DRIVER {
  806. disks = append(disks, disk)
  807. }
  808. }
  809. return disks
  810. }
  811. func (tool *PartitionTool) CreatePartition(diskIdx int, sizeMB int64, fs string, doformat bool, driver string, uuid string) error {
  812. disks := tool.disks
  813. if driver == PCIE_DRIVER {
  814. disks = tool.GetPCIEDisks()
  815. }
  816. if diskIdx < 0 || diskIdx >= len(disks) {
  817. for _, disk := range disks {
  818. if disk.IsSpaceAvailable(sizeMB) {
  819. return disk.CreatePartition(sizeMB, fs, doformat, uuid)
  820. }
  821. }
  822. } else {
  823. disk := disks[diskIdx]
  824. if disk.IsSpaceAvailable(sizeMB) {
  825. return disk.CreatePartition(sizeMB, fs, doformat, uuid)
  826. }
  827. }
  828. return nil
  829. }
  830. func (tool *PartitionTool) GetPartitions() []*Partition {
  831. parts := make([]*Partition, 0)
  832. for _, d := range tool.disks {
  833. parts = append(parts, d.partitions...)
  834. }
  835. return parts
  836. }
  837. func (tool *PartitionTool) Run(cmds ...string) ([]string, error) {
  838. return tool.runner.Run(cmds...)
  839. }
  840. type SSHPartitionTool struct {
  841. *PartitionTool
  842. term *ssh.Client
  843. }
  844. func newSSHPartitionTool(term *ssh.Client) *SSHPartitionTool {
  845. tool := &SSHPartitionTool{
  846. term: term,
  847. }
  848. tool.PartitionTool = NewPartitionTool(tool)
  849. return tool
  850. }
  851. func NewSSHPartitionTool(term *ssh.Client, layouts []baremetal.Layout, rootMatcher *api.BaremetalRootDiskMatcher) (*SSHPartitionTool, error) {
  852. tool := newSSHPartitionTool(term)
  853. tool.FetchDiskConfs(baremetal.GetDiskConfigurations(layouts))
  854. if err := tool.RetrieveDiskInfo(rootMatcher); err != nil {
  855. return nil, errors.Wrapf(err, "RetrieveDiskInfo")
  856. }
  857. return tool, nil
  858. }
  859. func (tool *SSHPartitionTool) Run(cmds ...string) ([]string, error) {
  860. return tool.term.Run(cmds...)
  861. }
  862. func (tool *SSHPartitionTool) Term() *ssh.Client {
  863. return tool.term
  864. }