| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378 |
- // Copyright 2019 Yunion
- //
- // Licensed under the Apache License, Version 2.0 (the "License");
- // you may not use this file except in compliance with the License.
- // You may obtain a copy of the License at
- //
- // http://www.apache.org/licenses/LICENSE-2.0
- //
- // Unless required by applicable law or agreed to in writing, software
- // distributed under the License is distributed on an "AS IS" BASIS,
- // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- // See the License for the specific language governing permissions and
- // limitations under the License.
- package nbd
- import (
- "fmt"
- "io/ioutil"
- "path"
- "path/filepath"
- "strings"
- "sync"
- "time"
- "yunion.io/x/log"
- "yunion.io/x/pkg/errors"
- "yunion.io/x/pkg/util/version"
- "yunion.io/x/onecloud/pkg/hostman/diskutils/fsutils"
- "yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver"
- "yunion.io/x/onecloud/pkg/hostman/guestfs/kvmpart"
- "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis"
- "yunion.io/x/onecloud/pkg/util/procutils"
- "yunion.io/x/onecloud/pkg/util/qemuimg"
- "yunion.io/x/onecloud/pkg/util/qemutils"
- )
- const MAX_TRIES = 3
- type NBDDriver struct {
- partitions []fsdriver.IDiskPartition
- lvms []*SKVMGuestLVMPartition
- imageRootBackFilePath string
- imageInfo qemuimg.SImageInfo
- nbdDev string
- }
- func NewNBDDriver(imageInfo qemuimg.SImageInfo) *NBDDriver {
- return &NBDDriver{
- imageInfo: imageInfo,
- partitions: make([]fsdriver.IDiskPartition, 0),
- }
- }
- var lock *sync.Mutex
- func init() {
- lock = new(sync.Mutex)
- }
- func (d *NBDDriver) Connect(*apis.GuestDesc, string) error {
- d.nbdDev = GetNBDManager().AcquireNbddev()
- if len(d.nbdDev) == 0 {
- return errors.Errorf("Cannot get nbd device")
- }
- rootPath := d.rootImagePath()
- log.Infof("lock root image path %s", rootPath)
- lock.Lock()
- defer lock.Unlock()
- defer log.Infof("unlock root image path %s", rootPath)
- if err := QemuNbdConnect(d.imageInfo, d.nbdDev); err != nil {
- return err
- }
- var tried uint = 0
- for len(d.partitions) == 0 && tried < MAX_TRIES {
- time.Sleep((1 << tried) * time.Second)
- err := d.findPartitions()
- if err != nil {
- log.Errorln(err.Error())
- return err
- }
- tried += 1
- }
- hasLVM, err := d.setupLVMS()
- log.Infof("%s hasLVM %v err %v", d.nbdDev, hasLVM, err)
- if err != nil {
- return err
- }
- return nil
- }
- func (d *NBDDriver) findPartitions() error {
- if len(d.nbdDev) == 0 {
- return fmt.Errorf("Want find partitions but dosen't have nbd dev")
- }
- dev := filepath.Base(d.nbdDev)
- devpath := filepath.Dir(d.nbdDev)
- files, err := ioutil.ReadDir(devpath)
- if err != nil {
- return errors.Wrapf(err, "read dir %s", devpath)
- }
- for i := 0; i < len(files); i++ {
- if files[i].Name() != dev && strings.HasPrefix(files[i].Name(), dev+"p") {
- var part = kvmpart.NewKVMGuestDiskPartition(path.Join(devpath, files[i].Name()), "", false)
- d.partitions = append(d.partitions, part)
- }
- }
- return nil
- }
- func (d *NBDDriver) rootImagePath() string {
- if len(d.imageRootBackFilePath) > 0 {
- return d.imageRootBackFilePath
- }
- d.imageRootBackFilePath = d.imageInfo.Path
- img, err := qemuimg.NewQemuImage(d.imageInfo.Path)
- if err != nil {
- return d.imageRootBackFilePath
- }
- for len(img.BackFilePath) > 0 {
- d.imageRootBackFilePath = img.BackFilePath
- img, err = qemuimg.NewQemuImage(img.BackFilePath)
- if err != nil {
- break
- }
- }
- return d.imageRootBackFilePath
- }
- func (d *NBDDriver) setupLVMS() (bool, error) {
- // Scan all devices and send the metadata to lvmetad
- output, err := procutils.NewCommand("pvscan", "--cache").Output()
- if err != nil {
- log.Errorf("pvscan error %s", output)
- return false, err
- }
- lvmPartitions := []fsdriver.IDiskPartition{}
- for _, part := range d.partitions {
- vg, err := findVg(part.GetPartDev())
- if err != nil {
- log.Errorf("unable to find vg from %s: %v", part.GetPartDev(), err)
- continue
- }
- log.Infof("find vg %s from %s", vg.Name, part.GetPartDev())
- lvm := NewKVMGuestLVMPartition(part.GetPartDev(), vg)
- d.lvms = append(d.lvms, lvm)
- if lvm.SetupDevice() {
- if subparts := lvm.FindPartitions(); len(subparts) > 0 {
- for i := 0; i < len(subparts); i++ {
- lvmPartitions = append(lvmPartitions, subparts[i])
- }
- } else {
- log.Infof("wait a second and try again")
- time.Sleep(time.Second)
- subparts := lvm.FindPartitions()
- if len(subparts) > 0 {
- for i := 0; i < len(subparts); i++ {
- lvmPartitions = append(lvmPartitions, subparts[i])
- }
- }
- }
- }
- }
- if len(lvmPartitions) > 0 {
- d.partitions = append(d.partitions, lvmPartitions...)
- return true, nil
- } else {
- return false, nil
- }
- }
- func (d *NBDDriver) findLVMPartitions(partDev string) string {
- return findVgname(partDev)
- }
- func (nbdDriver *NBDDriver) setupAndPutdownLVMS() error {
- _, err := nbdDriver.setupLVMS()
- if err != nil {
- return err
- }
- if !nbdDriver.putdownLVMs() {
- return errors.Errorf("failed putdown lvms")
- }
- return nil
- }
- func (d *NBDDriver) Disconnect() error {
- if len(d.nbdDev) > 0 {
- rootPath := d.rootImagePath()
- log.Infof("Disconnect lock root image path %s", rootPath)
- lock.Lock()
- defer lock.Unlock()
- defer log.Infof("Disconnect unlock root image path %s", rootPath)
- if !d.putdownLVMs() {
- return fmt.Errorf("failed putdown lvm devices %s", d.nbdDev)
- }
- return d.disconnect()
- } else {
- return nil
- }
- }
- func (d *NBDDriver) disconnect() error {
- if err := QemuNbdDisconnect(d.nbdDev); err != nil {
- return err
- }
- GetNBDManager().ReleaseNbddev(d.nbdDev)
- d.nbdDev = ""
- d.partitions = d.partitions[len(d.partitions):]
- return nil
- }
- func (d *NBDDriver) putdownLVMs() bool {
- for _, lvm := range d.lvms {
- if !lvm.PutdownDevice() {
- return false
- }
- }
- d.lvms = []*SKVMGuestLVMPartition{}
- return true
- }
- func (d *NBDDriver) GetPartitions() []fsdriver.IDiskPartition {
- return d.partitions
- }
- func (d *NBDDriver) MakePartition(fs string) error {
- return fsutils.Mkpartition(d.nbdDev, fs)
- }
- func (d *NBDDriver) FormatPartition(fs, uuid string, features *apis.FsFeatures) error {
- return fsutils.FormatPartition(fmt.Sprintf("%sp1", d.nbdDev), fs, uuid, features)
- }
- func (d *NBDDriver) ResizePartition(string, string) error {
- if d.IsLVMPartition() {
- // do not resize LVM partition
- return nil
- }
- return fsutils.ResizeDiskFs(d.nbdDev, 0, false)
- }
- func (d *NBDDriver) Zerofree() {
- startTime := time.Now()
- for _, part := range d.partitions {
- part.Zerofree()
- }
- log.Infof("Zerofree %d partitions takes %f seconds", len(d.partitions), time.Now().Sub(startTime).Seconds())
- }
- func (d *NBDDriver) IsLVMPartition() bool {
- return len(d.lvms) > 0
- }
- func (d *NBDDriver) DetectIsUEFISupport(rootfs fsdriver.IRootFsDriver) bool {
- return fsutils.DetectIsUEFISupport(rootfs, d.GetPartitions())
- }
- func (d *NBDDriver) DetectIsBIOSSupport(rootfs fsdriver.IRootFsDriver) bool {
- return fsutils.DetectIsBIOSSupport(d.nbdDev, rootfs)
- }
- func (d *NBDDriver) MountRootfs(readonly bool) (fsdriver.IRootFsDriver, error) {
- return fsutils.MountRootfs(readonly, d.GetPartitions())
- }
- func (d *NBDDriver) UmountRootfs(fd fsdriver.IRootFsDriver) error {
- if part := fd.GetPartition(); part != nil {
- return part.Umount()
- }
- return nil
- }
- func (d *NBDDriver) DeployGuestfs(req *apis.DeployParams) (res *apis.DeployGuestFsResponse, err error) {
- return fsutils.DeployGuestfs(d, req)
- }
- func (d *NBDDriver) ResizeFs(*apis.ResizeFsParams) (*apis.Empty, error) {
- return fsutils.ResizeFs(d, "")
- }
- func (d *NBDDriver) SaveToGlance(req *apis.SaveToGlanceParams) (*apis.SaveToGlanceResponse, error) {
- return fsutils.SaveToGlance(d, req)
- }
- func (d *NBDDriver) FormatFs(req *apis.FormatFsParams) (*apis.Empty, error) {
- return fsutils.FormatFs(d, req)
- }
- func (d *NBDDriver) ProbeImageInfo(req *apis.ProbeImageInfoPramas) (*apis.ImageInfo, error) {
- return fsutils.ProbeImageInfo(d)
- }
- func getQemuNbdVersion() (string, error) {
- output, err := procutils.NewRemoteCommandAsFarAsPossible(qemutils.GetQemuNbd(), "--version").Output()
- if err != nil {
- log.Errorf("qemu-nbd version failed %s %s", output, err.Error())
- return "", errors.Wrapf(err, "qemu-nbd version failed %s", output)
- }
- lines := strings.Split(strings.TrimSpace(string(output)), "\n")
- if len(lines) > 0 {
- parts := strings.Split(lines[0], " ")
- return parts[1], nil
- }
- return "", errors.Error("empty version output")
- }
- func QemuNbdConnect(imageInfo qemuimg.SImageInfo, nbddev string) error {
- nbdVer, err := getQemuNbdVersion()
- if err != nil {
- return errors.Wrap(err, "getQemuNbdVersion")
- }
- var cmd []string
- if strings.HasPrefix(imageInfo.Path, "rbd:") || getImageFormat(imageInfo) == "raw" {
- //qemu-nbd 连接ceph时 /etc/ceph/ceph.conf 必须存在
- if strings.HasPrefix(imageInfo.Path, "rbd:") {
- err := procutils.NewRemoteCommandAsFarAsPossible("mkdir", "-p", "/etc/ceph").Run()
- if err != nil {
- log.Errorf("Failed to mkdir /etc/ceph: %s", err)
- return errors.Wrap(err, "Failed to mkdir /etc/ceph: %s")
- }
- err = procutils.NewRemoteCommandAsFarAsPossible("test", "-f", "/etc/ceph/ceph.conf").Run()
- if err != nil {
- err = procutils.NewRemoteCommandAsFarAsPossible("touch", "/etc/ceph/ceph.conf").Run()
- if err != nil {
- log.Errorf("failed to create /etc/ceph/ceph.conf: %s", err)
- return errors.Wrap(err, "failed to create /etc/ceph/ceph.conf")
- }
- }
- }
- cmd = []string{qemutils.GetQemuNbd(), "-c", nbddev, "-f", "raw", imageInfo.Path}
- } else if imageInfo.Encrypted() {
- cmd = []string{
- qemutils.GetQemuNbd(), "-c", nbddev,
- "--object", imageInfo.SecretOptions(),
- "--image-opts", imageInfo.ImageOptions(),
- }
- } else {
- cmd = []string{qemutils.GetQemuNbd(), "-c", nbddev, imageInfo.Path}
- }
- if version.GE(nbdVer, "4.0.0") {
- cmd = append(cmd, "--fork")
- }
- output, err := procutils.NewRemoteCommandAsFarAsPossible(cmd[0], cmd[1:]...).Output()
- if err != nil {
- log.Errorf("qemu-nbd connect failed %s %s", output, err.Error())
- return errors.Wrapf(err, "qemu-nbd connect failed %s", output)
- }
- return nil
- }
- func getImageFormat(imageInfo qemuimg.SImageInfo) string {
- img, err := qemuimg.NewQemuImage(imageInfo.Path)
- if err == nil {
- return string(img.Format)
- }
- log.Errorf("NBD getImageFormat fail: %s", err)
- return ""
- }
- func QemuNbdDisconnect(nbddev string) error {
- output, err := procutils.NewRemoteCommandAsFarAsPossible(qemutils.GetQemuNbd(), "-d", nbddev).Output()
- if err != nil {
- return errors.Wrapf(err, "qemu-nbd disconnect %s", output)
- }
- return nil
- }
|