vddk.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512
  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 diskutils
  15. import (
  16. "bytes"
  17. "crypto/sha1"
  18. "crypto/tls"
  19. "encoding/hex"
  20. "fmt"
  21. "io"
  22. "io/ioutil"
  23. "net"
  24. "os"
  25. "os/exec"
  26. "path"
  27. "path/filepath"
  28. "regexp"
  29. "strconv"
  30. "strings"
  31. "time"
  32. "yunion.io/x/log"
  33. "yunion.io/x/pkg/errors"
  34. "yunion.io/x/onecloud/pkg/hostman/guestfs"
  35. "yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver"
  36. "yunion.io/x/onecloud/pkg/hostman/guestfs/kvmpart"
  37. "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis"
  38. "yunion.io/x/onecloud/pkg/util/qemuimg"
  39. )
  40. const (
  41. TMPDIR = "/tmp/vmware-root"
  42. )
  43. var (
  44. MNT_PATTERN = regexp.MustCompile(`Disk flat file mounted under ([^\s]+)`)
  45. )
  46. type VDDKDisk struct {
  47. Host string
  48. Port int
  49. User string
  50. Passwd string
  51. VmRef string
  52. DiskPath string
  53. FUseDir string
  54. PartDirs []string
  55. Proc *Command
  56. Pid int
  57. kvmDisk *SKVMGuestDisk
  58. readOnly bool
  59. deployDriver string
  60. }
  61. func NewVDDKDisk(vddkInfo *apis.VDDKConInfo, diskPath, deployDriver string, readOnly bool) (*VDDKDisk, error) {
  62. return &VDDKDisk{
  63. Host: vddkInfo.Host,
  64. Port: int(vddkInfo.Port),
  65. User: vddkInfo.User,
  66. Passwd: vddkInfo.Passwd,
  67. VmRef: vddkInfo.Vmref,
  68. DiskPath: diskPath,
  69. deployDriver: deployDriver,
  70. readOnly: readOnly,
  71. }, nil
  72. }
  73. type Command struct {
  74. *exec.Cmd
  75. done chan error
  76. stdouterr *bytes.Buffer
  77. stdin io.Writer
  78. }
  79. func NewCommand(name string, arg ...string) *Command {
  80. cmd := Command{
  81. Cmd: exec.Command(name, arg...),
  82. done: make(chan error, 1),
  83. }
  84. cmd.stdouterr = bytes.NewBuffer([]byte{})
  85. cmd.Stdout = cmd.stdouterr
  86. cmd.Stderr = cmd.stdouterr
  87. cmd.stdin, _ = cmd.StdinPipe()
  88. return &cmd
  89. }
  90. func (c *Command) Send(msg []byte) error {
  91. _, err := c.stdin.Write(msg)
  92. return err
  93. }
  94. func (c *Command) Start() error {
  95. if err := c.Cmd.Start(); err != nil {
  96. return err
  97. }
  98. go func() {
  99. c.done <- c.Cmd.Wait()
  100. }()
  101. return nil
  102. }
  103. func (c *Command) Exited() bool {
  104. return len(c.done) == 1
  105. }
  106. // Wait will block
  107. func (c *Command) Wait() error {
  108. return <-c.done
  109. }
  110. func (c *Command) Kill() error {
  111. return c.Process.Kill()
  112. }
  113. func execpath() string {
  114. return "/opt/vmware-vddk/bin/vix-mntapi-sample"
  115. }
  116. func libdir() string {
  117. return "/usr/lib/vmware"
  118. }
  119. func logpath(pid int) string {
  120. return fmt.Sprintf("%s/vixDiskLib-%d.log", TMPDIR, pid)
  121. }
  122. func (vd *VDDKDisk) Cleanup() {
  123. if vd.kvmDisk != nil {
  124. vd.kvmDisk.Cleanup()
  125. vd.kvmDisk = nil
  126. }
  127. }
  128. func (vd *VDDKDisk) Connect(*apis.GuestDesc) error {
  129. flatFile, err := vd.ConnectBlockDevice()
  130. if err != nil {
  131. return errors.Wrap(err, "ConnectBlockDevice")
  132. }
  133. vd.kvmDisk, err = NewKVMGuestDisk(qemuimg.SImageInfo{Path: flatFile}, vd.deployDriver, vd.readOnly)
  134. if err != nil {
  135. vd.DisconnectBlockDevice()
  136. return errors.Wrap(err, "NewKVMGuestDisk")
  137. }
  138. err = vd.kvmDisk.Connect(nil)
  139. if err != nil {
  140. vd.DisconnectBlockDevice()
  141. return errors.Wrap(err, "kvmDisk connect")
  142. }
  143. return nil
  144. }
  145. func (vd *VDDKDisk) ConnectWithDiskId(desc *apis.GuestDesc, diskId string) error {
  146. return vd.Connect(desc)
  147. }
  148. func (vd *VDDKDisk) Disconnect() error {
  149. if vd.kvmDisk != nil {
  150. if err := vd.kvmDisk.Disconnect(); err != nil {
  151. log.Errorf("kvm disk disconnect failed %s", err)
  152. }
  153. vd.kvmDisk.Cleanup()
  154. vd.kvmDisk = nil
  155. }
  156. return vd.DisconnectBlockDevice()
  157. }
  158. func (vd *VDDKDisk) MountRootfs() (fsdriver.IRootFsDriver, error) {
  159. if vd.kvmDisk == nil {
  160. return nil, fmt.Errorf("kvmDisk is nil")
  161. }
  162. return vd.kvmDisk.MountRootfs()
  163. }
  164. func (vd *VDDKDisk) UmountRootfs(fd fsdriver.IRootFsDriver) error {
  165. if vd.kvmDisk == nil {
  166. return nil
  167. }
  168. return vd.kvmDisk.UmountRootfs(fd)
  169. }
  170. func (vd *VDDKDisk) ParsePartitions(buf string) error {
  171. // Disk flat file mounted under /run/vmware/fuse/7673253059900458465
  172. // Mounted Volume 1, Type 1, isMounted 1, symLink /tmp/vmware-root/7673253059900458465_1, numGuestMountPoints 0 (<null>)
  173. // print buf
  174. ms := MNT_PATTERN.FindAllStringSubmatch(buf, -1)
  175. if len(ms) != 0 {
  176. vd.FUseDir = ms[0][1]
  177. diskId := filepath.Base(vd.FUseDir)
  178. files, err := ioutil.ReadDir(TMPDIR)
  179. if err != nil {
  180. return errors.Wrapf(err, "ioutil.ReadDir for %s", TMPDIR)
  181. }
  182. for _, f := range files {
  183. if strings.HasPrefix(f.Name(), diskId) {
  184. vd.PartDirs = append(vd.PartDirs, filepath.Join(TMPDIR, f.Name()))
  185. }
  186. }
  187. }
  188. log.Infof("Fuse path: %s partitiaons: %s", vd.FUseDir, vd.PartDirs)
  189. return nil
  190. }
  191. func (vd *VDDKDisk) Mount() (err error) {
  192. defer func() {
  193. if err == nil {
  194. return
  195. }
  196. vd.Proc = nil
  197. log.Errorf("Exec vix-mntapi-sample error: %s", err)
  198. }()
  199. err = vd.ExecProg()
  200. if err != nil {
  201. return errors.Wrap(err, "VDDKDisk.ExecProg")
  202. }
  203. err = vd.WaitMounted()
  204. if err != nil {
  205. return errors.Wrap(err, "VDDKDisk.Mount")
  206. }
  207. return nil
  208. }
  209. func (vd *VDDKDisk) Umount() error {
  210. if vd.Proc != nil {
  211. err := vd.Proc.Send([]byte{'y'})
  212. if err != nil {
  213. errors.Wrap(err, "send 'y' to VDDKDisk.Proc")
  214. }
  215. err = vd.Proc.Wait()
  216. if err != nil {
  217. return errors.Wrap(err, "vd.Proc.Wait")
  218. }
  219. }
  220. if len(vd.FUseDir) != 0 {
  221. for _, p := range append(vd.PartDirs, vd.FUseDir) {
  222. vd.fuseUmount(p)
  223. }
  224. }
  225. if vd.Pid != 0 {
  226. logpath := logpath(vd.Pid)
  227. _, err := os.Stat(logpath)
  228. if err == nil || os.IsExist(err) {
  229. os.Remove(logpath)
  230. }
  231. }
  232. return nil
  233. }
  234. func (vd *VDDKDisk) fuseUmount(path string) {
  235. maxTries, tried := 4, 0
  236. _, err := os.Stat(path)
  237. if err != nil && os.IsNotExist(err) {
  238. // no such path
  239. return
  240. }
  241. for tried < maxTries {
  242. tried += 1
  243. err := exec.Command("umount", path).Run()
  244. if err != nil {
  245. time.Sleep(time.Duration(tried) * 15 * time.Second)
  246. log.Errorf("Fail to umount %s: %s", path, err)
  247. continue
  248. }
  249. _, err = os.Stat(path)
  250. if err == nil || os.IsExist(err) {
  251. err = exec.Command("rm", "-rf", path).Run()
  252. if err != nil {
  253. time.Sleep(time.Duration(tried) * 15 * time.Second)
  254. log.Errorf("Fail to umount %s: %s", path, err)
  255. continue
  256. }
  257. }
  258. }
  259. }
  260. func (vd *VDDKDisk) ExecProg() error {
  261. thumb, err := vd.getServerCertThumbSha1(fmt.Sprintf("%s:%d", vd.Host, vd.Port))
  262. if err != nil {
  263. return errors.Wrapf(err, "Fail contact server %s", vd.Host)
  264. }
  265. cmd := NewCommand(execpath(), "-info", "-host", vd.Host, "-port", strconv.Itoa(vd.Port), "-user", vd.User,
  266. "-password", vd.Passwd, "-mode", "nbd", "-thumb", thumb, "-vm", fmt.Sprintf("moref=%s", vd.VmRef), vd.DiskPath)
  267. log.Debugf("command to mount: %s", cmd)
  268. env := os.Environ()
  269. env = append(env, fmt.Sprintf("LD_LIBRARY_PATH=%s", libdir()))
  270. cmd.Env = env
  271. vd.Proc = cmd
  272. err = vd.Proc.Start()
  273. if err != nil {
  274. return errors.Wrap(err, "vd.Proc.Start")
  275. }
  276. vd.Pid = cmd.Process.Pid
  277. return nil
  278. }
  279. // getServerCertBin try to obtain the remote ssl certificate
  280. func (vd *VDDKDisk) getServerCertBin(addr string) ([]byte, error) {
  281. rawConn, err := net.Dial("tcp", addr)
  282. if err != nil {
  283. return nil, errors.Wrapf(err, "net.Dial for addr '%s'", addr)
  284. }
  285. defer rawConn.Close()
  286. // get the wrpped conn
  287. sslWrappedConn := tls.Client(rawConn, &tls.Config{InsecureSkipVerify: true})
  288. err = sslWrappedConn.Handshake()
  289. if err != nil {
  290. return nil, errors.Wrapf(err, "fail to complete ssl handshake with addr '%s'", addr)
  291. }
  292. return sslWrappedConn.ConnectionState().PeerCertificates[0].Raw, nil
  293. }
  294. func (vd *VDDKDisk) getServerCertThumbSha1(addr string) (string, error) {
  295. certBin, err := vd.getServerCertBin(addr)
  296. if err != nil {
  297. return "", err
  298. }
  299. sha := sha1.Sum(certBin)
  300. shaHex := hex.EncodeToString(sha[:])
  301. length := len(shaHex) / 2 * 2
  302. tmp := make([][]byte, 0, length)
  303. for i := 1; i < length; i += 2 {
  304. tmp = append(tmp, []byte{shaHex[i-1], shaHex[i]})
  305. }
  306. return string(bytes.Join(tmp, []byte{':'})), nil
  307. }
  308. func (vd *VDDKDisk) WaitMounted() error {
  309. endStr := []byte("Do you want to procede to unmount the volume")
  310. timeout := 300 * time.Second
  311. endClock := time.After(timeout)
  312. isEnd := false
  313. Loop:
  314. for !vd.Proc.Exited() {
  315. select {
  316. case <-endClock:
  317. break Loop
  318. default:
  319. if bytes.Contains(vd.Proc.stdouterr.Bytes(), endStr) {
  320. log.Debugf("find the mark")
  321. isEnd = true
  322. break Loop
  323. }
  324. }
  325. // Reduce inspection density
  326. time.Sleep(100 * time.Millisecond)
  327. }
  328. backup := vd.Proc.stdouterr.String()
  329. log.Debugf("%s", backup)
  330. err := vd.ParsePartitions(backup)
  331. if err != nil {
  332. return errors.Wrap(err, "VDDKDisk.ParsePartitions")
  333. }
  334. if vd.Proc.Exited() {
  335. retCode := vd.Proc.ProcessState.ExitCode()
  336. err := vd.Proc.Kill()
  337. if err != nil {
  338. log.Errorf("unable to kill process '%d'", vd.Proc.Process.Pid)
  339. }
  340. return errors.Error(fmt.Sprintf("VDDKDisk prog exit error(%d): %s", retCode, backup))
  341. } else if !isEnd {
  342. err := vd.Proc.Kill()
  343. if err != nil {
  344. log.Errorf("unable to kill process '%d'", vd.Proc.Process.Pid)
  345. }
  346. return errors.Error("VDDKDisk read timeout, program blocked")
  347. }
  348. return nil
  349. }
  350. // connect vddk disk as fuse block device on local host
  351. // return fuse device path, is null error
  352. func (vd *VDDKDisk) ConnectBlockDevice() (string, error) {
  353. thumb, err := vd.getServerCertThumbSha1(fmt.Sprintf("%s:%d", vd.Host, vd.Port))
  354. if err != nil {
  355. return "", errors.Wrapf(err, "Fail contact server %s", vd.Host)
  356. }
  357. cmd := NewCommand(execpath(), "-info", "-connect-disk", "-host", vd.Host, "-port", strconv.Itoa(vd.Port), "-user", vd.User,
  358. "-password", vd.Passwd, "-mode", "nbd", "-thumb", thumb, "-vm", fmt.Sprintf("moref=%s", vd.VmRef), vd.DiskPath)
  359. log.Infof("command to mount: %s", cmd)
  360. env := os.Environ()
  361. env = append(env, fmt.Sprintf("LD_LIBRARY_PATH=%s", libdir()))
  362. cmd.Env = env
  363. vd.Proc = cmd
  364. err = vd.Proc.Start()
  365. if err != nil {
  366. return "", errors.Wrap(err, "vd.Proc.Start")
  367. }
  368. vd.Pid = cmd.Process.Pid
  369. var (
  370. timeout = time.After(30 * time.Second)
  371. matchStr = "Log: Disk flat file mounted under"
  372. flatFile string
  373. )
  374. Loop:
  375. for !vd.Proc.Exited() {
  376. select {
  377. case <-timeout:
  378. break Loop
  379. default:
  380. if idx := strings.Index(vd.Proc.stdouterr.String(), matchStr); idx >= 0 {
  381. output := vd.Proc.stdouterr.String()
  382. output = output[idx+len(matchStr):]
  383. if idx := strings.Index(output, "\n"); idx < 0 {
  384. return "", fmt.Errorf("find disk flat file failed")
  385. } else {
  386. flatFile = strings.TrimSpace(output[:idx])
  387. }
  388. log.Infof("disk flat file mounted under %s", flatFile)
  389. break Loop
  390. }
  391. }
  392. }
  393. if vd.Proc.Exited() {
  394. log.Errorf("process is exited: %s", vd.Proc.stdouterr.String())
  395. return "", vd.Proc.Wait()
  396. }
  397. vd.FUseDir = flatFile
  398. return path.Join(flatFile, "flat"), nil
  399. }
  400. func (vd *VDDKDisk) DisconnectBlockDevice() error {
  401. if vd.Proc != nil {
  402. _, err := vd.Proc.stdin.Write([]byte("y\n"))
  403. if err != nil {
  404. return errors.Wrap(err, "send 'y' to VDDKDisk.Proc")
  405. }
  406. return vd.Umount()
  407. }
  408. return fmt.Errorf("vddk disk has not connected")
  409. }
  410. func (vd *VDDKDisk) DeployGuestfs(req *apis.DeployParams) (res *apis.DeployGuestFsResponse, err error) {
  411. return vd.kvmDisk.DeployGuestfs(req)
  412. }
  413. func (d *VDDKDisk) ResizeFs(req *apis.ResizeFsParams) (*apis.Empty, error) {
  414. return d.kvmDisk.ResizeFs(req)
  415. }
  416. func (d *VDDKDisk) FormatFs(req *apis.FormatFsParams) (*apis.Empty, error) {
  417. return d.kvmDisk.FormatFs(req)
  418. }
  419. func (d *VDDKDisk) SaveToGlance(req *apis.SaveToGlanceParams) (*apis.SaveToGlanceResponse, error) {
  420. return d.kvmDisk.SaveToGlance(req)
  421. }
  422. func (d *VDDKDisk) ProbeImageInfo(req *apis.ProbeImageInfoPramas) (*apis.ImageInfo, error) {
  423. return d.kvmDisk.ProbeImageInfo(req)
  424. }
  425. type VDDKPartition struct {
  426. *kvmpart.SLocalGuestFS
  427. }
  428. func (vp *VDDKPartition) Mount() bool {
  429. log.Warningf("VDDKPartition.Mount not implement")
  430. return true
  431. }
  432. func (vp *VDDKPartition) MountPartReadOnly() bool {
  433. log.Warningf("VDDKPartition.MountPartReadOnly not implement")
  434. return true
  435. }
  436. func (vp *VDDKPartition) Umount() error {
  437. log.Warningf("VDDKPartition.Umount not implement")
  438. return nil
  439. }
  440. func (vp *VDDKPartition) IsReadonly() bool {
  441. return guestfs.IsPartitionReadonly(vp)
  442. }
  443. func (vp *VDDKPartition) GetPhysicalPartitionType() string {
  444. log.Warningf("VDDKPartition.GetPhysicalPartitionType not implement")
  445. return ""
  446. }
  447. func (vp *VDDKPartition) GetPartDev() string {
  448. return ""
  449. }
  450. func (vp *VDDKPartition) IsMounted() bool {
  451. return true
  452. }
  453. func (vp *VDDKPartition) Zerofree() {
  454. return
  455. }