nbd.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  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 hostimage
  15. import (
  16. "fmt"
  17. "os"
  18. "path"
  19. "strconv"
  20. "strings"
  21. "sync"
  22. "yunion.io/x/log"
  23. "yunion.io/x/pkg/errors"
  24. "yunion.io/x/pkg/util/version"
  25. "yunion.io/x/onecloud/pkg/util/fileutils2"
  26. "yunion.io/x/onecloud/pkg/util/netutils2"
  27. "yunion.io/x/onecloud/pkg/util/procutils"
  28. "yunion.io/x/onecloud/pkg/util/qemuimg"
  29. "yunion.io/x/onecloud/pkg/util/qemutils"
  30. )
  31. var EXPORT_NBD_BASE_PORT = 7777
  32. var LAST_USED_NBD_SERVER_PORT = 0
  33. type SNbdExportManager struct {
  34. portsLock *sync.Mutex
  35. }
  36. func NewNbdExportManager() *SNbdExportManager {
  37. return &SNbdExportManager{
  38. portsLock: new(sync.Mutex),
  39. }
  40. }
  41. func (m *SNbdExportManager) GetFreePortByBase(basePort int) int {
  42. var port = 1
  43. for {
  44. if netutils2.IsTcpPortUsed("0.0.0.0", basePort+port) {
  45. port += 1
  46. } else {
  47. break
  48. }
  49. }
  50. return port + basePort
  51. }
  52. func (m *SNbdExportManager) GetNBDServerFreePort() int {
  53. basePort := EXPORT_NBD_BASE_PORT + LAST_USED_NBD_SERVER_PORT
  54. var port = 1
  55. for {
  56. if netutils2.IsTcpPortUsed("0.0.0.0", basePort+port) {
  57. port += 1
  58. } else {
  59. break
  60. }
  61. }
  62. LAST_USED_NBD_SERVER_PORT = port
  63. if LAST_USED_NBD_SERVER_PORT > 1000 {
  64. LAST_USED_NBD_SERVER_PORT = 0
  65. }
  66. return port + basePort
  67. }
  68. func (m *SNbdExportManager) getQemuNbdVersion() (string, error) {
  69. output, err := procutils.NewRemoteCommandAsFarAsPossible(qemutils.GetQemuNbd(), "--version").Output()
  70. if err != nil {
  71. log.Errorf("qemu-nbd version failed %s %s", output, err.Error())
  72. return "", errors.Wrapf(err, "qemu-nbd version failed %s", output)
  73. }
  74. lines := strings.Split(strings.TrimSpace(string(output)), "\n")
  75. if len(lines) > 0 {
  76. parts := strings.Split(lines[0], " ")
  77. return parts[1], nil
  78. }
  79. return "", errors.Error("empty version output")
  80. }
  81. func (m *SNbdExportManager) QemuNbdStartExport(imageInfo qemuimg.SImageInfo, diskId string) (int, error) {
  82. m.portsLock.Lock()
  83. defer m.portsLock.Unlock()
  84. nbdPort := m.GetNBDServerFreePort()
  85. pidFilePath := path.Join(HostImageOptions.HostImageNbdPidDir, fmt.Sprintf("nbd_%s.pid", diskId))
  86. nbdVer, err := m.getQemuNbdVersion()
  87. if err != nil {
  88. return -1, errors.Wrap(err, "getQemuNbdVersion")
  89. }
  90. var cmd []string
  91. if imageInfo.Encrypted() {
  92. cmd = []string{
  93. qemutils.GetQemuNbd(),
  94. "--read-only", "--persistent", "-x", diskId, "-b", "::", "-p", strconv.Itoa(nbdPort),
  95. "--object", imageInfo.SecretOptions(),
  96. "--image-opts", imageInfo.ImageOptions(),
  97. }
  98. } else {
  99. cmd = []string{
  100. qemutils.GetQemuNbd(),
  101. "--read-only", "--persistent", "-x", diskId, "-b", "::", "-p", strconv.Itoa(nbdPort),
  102. imageInfo.Path,
  103. }
  104. }
  105. cmd = append(cmd, "--pid-file", pidFilePath)
  106. if version.GE(nbdVer, "4.0.0") {
  107. cmd = append(cmd, "--fork")
  108. }
  109. cmdStr := strings.Join(cmd, " ")
  110. err = procutils.NewRemoteCommandAsFarAsPossible("sh", "-c", cmdStr).Run()
  111. if err != nil {
  112. log.Errorf("qemu-nbd connect failed %s %s", err.Error())
  113. return -1, errors.Wrapf(err, "qemu-nbd connect failed")
  114. }
  115. return nbdPort, nil
  116. }
  117. func (m *SNbdExportManager) QemuNbdCloseExport(diskId string) error {
  118. pidFilePath := path.Join(HostImageOptions.HostImageNbdPidDir, fmt.Sprintf("nbd_%s.pid", diskId))
  119. if !m.nbdProcessExist(diskId) {
  120. if fileutils2.Exists(pidFilePath) {
  121. if err := os.Remove(pidFilePath); err != nil {
  122. log.Errorf("failed remove nbd pid file %s", pidFilePath)
  123. }
  124. }
  125. return nil
  126. }
  127. if fileutils2.Exists(pidFilePath) {
  128. pid, err := fileutils2.FileGetIntContent(pidFilePath)
  129. if err != nil {
  130. return errors.Wrapf(err, "failed get pid of qemu-nbd process %s", pidFilePath)
  131. }
  132. out, err := procutils.NewRemoteCommandAsFarAsPossible("kill", "-9", strconv.Itoa(pid)).Output()
  133. if err != nil {
  134. log.Errorf("failed kill nbd export process %s %s", err, out)
  135. return errors.Wrapf(err, "kill nbd export failed: %s", out)
  136. }
  137. if err := os.Remove(pidFilePath); err != nil {
  138. log.Errorf("failed remove nbd pid file %s", pidFilePath)
  139. }
  140. }
  141. return nil
  142. }
  143. func (m *SNbdExportManager) nbdProcessExist(diskId string) bool {
  144. return procutils.NewRemoteCommandAsFarAsPossible("sh", "-c",
  145. fmt.Sprintf("ps -ef | grep [q]emu-nbd | grep %s", diskId)).Run() == nil
  146. }