feature: SUSE guest support

This commit is contained in:
Qiu Jian
2023-10-01 22:18:20 +08:00
parent 63d6435e51
commit 8f249b8a71
14 changed files with 589 additions and 49 deletions
+1 -1
View File
@@ -1,4 +1,4 @@
FROM registry.cn-beijing.aliyuncs.com/yunionio/host-deployer-base:1.4
FROM registry.cn-beijing.aliyuncs.com/yunionio/host-deployer-base:1.5
MAINTAINER "Yaoqi Wan wanyaoqi@yunionyun.com"
+1
View File
@@ -47,6 +47,7 @@ func Init(initPrivatePrefixes []string, cloudrootDir string) error {
NewFangdeDeskRootfs, NewUKylinRootfs,
NewCentosRootFs, NewFedoraRootFs,
NewRhelRootFs,
NewOpenSuseRootFs,
NewDebianRootFs, NewCirrosRootFs, NewCirrosNewRootFs, NewUbuntuRootFs,
NewGentooRootFs, NewArchLinuxRootFs, NewOpenWrtRootFs, NewCoreOsRootFs,
NewOpenEulerRootFs,
+9 -3
View File
@@ -228,11 +228,12 @@ func (l *sLinuxRootFs) DeployYunionroot(rootFs IDiskPartition, pubkeys *deployap
l.DisableCloudinit(rootFs)
}
var yunionroot = YUNIONROOT_USER
rootdir := path.Join(cloudrootDirectory, yunionroot)
var rootdir string // := path.Join(cloudrootDirectory, yunionroot)
var err error
if rootdir, err = rootFs.CheckOrAddUser(yunionroot, cloudrootDirectory, true); err != nil {
return errors.Wrap(err, "unable to CheckOrAddUser")
}
log.Infof("DeployYunionroot %s home %s", yunionroot, rootdir)
err = DeployAuthorizedKeys(rootFs, rootdir, pubkeys, true)
if err != nil {
log.Infof("DeployAuthorizedKeys error: %s", err.Error())
@@ -738,7 +739,12 @@ func (d *sLinuxRootFs) DeployTelegraf(config string) (bool, error) {
if err != nil {
return false, errors.Wrap(err, "chmod supervise run script")
}
// add crontab: start telegraf on guest boot
initCmd := fmt.Sprintf("%s/supervise %s", cloudMonitorPath, telegrafPath)
err = d.installInitScript("telegraf", initCmd)
if err != nil {
return false, errors.Wrap(err, "installInitScript")
}
/* // add crontab: start telegraf on guest boot
cronJob := fmt.Sprintf("@reboot %s/supervise %s", cloudMonitorPath, telegrafPath)
if procutils.NewCommand("chroot", part.GetMountPath(), "crontab", "-l", "|", "grep", cronJob).Run() == nil {
// if cronjob exist, return success
@@ -749,7 +755,7 @@ func (d *sLinuxRootFs) DeployTelegraf(config string) (bool, error) {
).Output()
if err != nil {
return false, errors.Wrapf(err, "add crontab %s", output)
}
}*/
return true, nil
}
@@ -0,0 +1,85 @@
// 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 fsdriver
import (
"fmt"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/util/procutils"
)
const (
unitDirPath = "/usr/lib/systemd/system"
enableDirPath = "/etc/systemd/system/multi-user.target.wants"
)
func (d *sLinuxRootFs) isSupportSystemd() bool {
return d.rootFs.Exists(unitDirPath, false) && d.rootFs.Exists(enableDirPath, false)
}
func (d *sLinuxRootFs) installInitScript(name, cmd string) error {
if d.isSupportSystemd() {
return d.installSystemd(name, cmd)
} else {
return d.installCrond(cmd)
}
}
func (d *sLinuxRootFs) installCrond(cmd string) error {
cronJob := fmt.Sprintf("@reboot %s", cmd)
if procutils.NewCommand("chroot", d.rootFs.GetMountPath(), "crontab", "-l", "|", "grep", cronJob).Run() == nil {
// if cronjob exist, return success
return nil
}
output, err := procutils.NewCommand("chroot", d.rootFs.GetMountPath(), "sh", "-c",
fmt.Sprintf("(crontab -l 2>/dev/null; echo '%s') |crontab -", cronJob),
).Output()
if err != nil {
return errors.Wrapf(err, "add crontab %s", output)
}
return nil
}
func (d *sLinuxRootFs) installSystemd(name, cmd string) error {
var serviceName = fmt.Sprintf("%s.service", name)
var unitPath = fmt.Sprintf("%s/%s", unitDirPath, serviceName)
var enablePath = fmt.Sprintf("%s/%s", enableDirPath, serviceName)
var unitContent = fmt.Sprintf(`[Unit]
Description=Run once
After=local-fs.target
After=network.target
[Service]
ExecStart=%s
RemainAfterExit=true
Type=oneshot
[Install]
WantedBy=multi-user.target
`, cmd)
err := d.rootFs.FilePutContents(unitPath, unitContent, false, false)
if err != nil {
return errors.Wrap(err, "save user_data unit fail")
}
err = d.rootFs.Symlink(unitPath, enablePath, false)
if err != nil {
return errors.Wrap(err, "create user_data symlink fail")
}
return nil
}
+272
View File
@@ -0,0 +1,272 @@
// 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 fsdriver
import (
"fmt"
"path"
"strings"
"yunion.io/x/jsonutils"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/pkg/utils"
"yunion.io/x/onecloud/pkg/cloudcommon/types"
deployapi "yunion.io/x/onecloud/pkg/hostman/hostdeployer/apis"
"yunion.io/x/onecloud/pkg/util/netutils2"
)
type sSuseLikeRootFs struct {
*sLinuxRootFs
}
func newSuseLikeRootFs(part IDiskPartition) *sSuseLikeRootFs {
return &sSuseLikeRootFs{
sLinuxRootFs: newLinuxRootFs(part),
}
}
func (r *sSuseLikeRootFs) PrepareFsForTemplate(rootFs IDiskPartition) error {
if err := r.sLinuxRootFs.PrepareFsForTemplate(rootFs); err != nil {
return err
}
return r.CleanNetworkScripts(rootFs)
}
func (r *sSuseLikeRootFs) CleanNetworkScripts(rootFs IDiskPartition) error {
networkPath := "/etc/sysconfig/network"
files := rootFs.ListDir(networkPath, false)
for i := 0; i < len(files); i++ {
if strings.HasPrefix(files[i], "ifcfg-") && files[i] != "ifcfg-lo" {
rootFs.Remove(path.Join(networkPath, files[i]), false)
continue
}
if strings.HasPrefix(files[i], "ifroute-") {
rootFs.Remove(path.Join(networkPath, files[i]), false)
}
}
return nil
}
func (r *sSuseLikeRootFs) RootSignatures() []string {
sig := r.sLinuxRootFs.RootSignatures()
return append([]string{"etc/SUSE-brand", "/etc/os-release"}, sig...)
}
func (r *sSuseLikeRootFs) DeployHostname(rootFs IDiskPartition, hn, domain string) error {
if rootFs.Exists("/etc/HOSTNAME", false) {
return rootFs.FilePutContents("/etc/HOSTNAME", getHostname(hn, domain), false, false)
}
return nil
}
func (r *sSuseLikeRootFs) enableBondingModule(rootFs IDiskPartition, bondNics []*types.SServerNic) error {
var content strings.Builder
for i := range bondNics {
content.WriteString("alias ")
content.WriteString(bondNics[i].Name)
content.WriteString(" bonding\n options ")
content.WriteString(bondNics[i].Name)
content.WriteString(" miimon=100 mode=4 lacp_rate=1 xmit_hash_policy=1\n")
}
return rootFs.FilePutContents("/etc/modprobe.d/bonding.conf", content.String(), false, false)
}
func (r *sSuseLikeRootFs) deployNetworkingScripts(rootFs IDiskPartition, nics []*types.SServerNic) error {
if err := r.sLinuxRootFs.DeployNetworkingScripts(rootFs, nics); err != nil {
return err
}
// ToServerNics(nics)
allNics, bondNics := convertNicConfigs(nics)
if len(bondNics) > 0 {
err := r.enableBondingModule(rootFs, bondNics)
if err != nil {
return err
}
}
var dnsSrv []string
mainNic, err := getMainNic(allNics)
if err != nil {
return err
}
var mainIp string
if mainNic != nil {
mainIp = mainNic.Ip
}
for i := range allNics {
nicDesc := allNics[i]
var cmds strings.Builder
cmds.WriteString("STARTMODE=auto\n")
if nicDesc.Mtu > 0 {
cmds.WriteString(fmt.Sprintf("MTU=%d\n", nicDesc.Mtu))
}
if len(nicDesc.Mac) > 0 {
cmds.WriteString("LLADDR=")
cmds.WriteString(nicDesc.Mac)
cmds.WriteString("\n")
}
if len(nicDesc.TeamingSlaves) != 0 {
cmds.WriteString(`BONDING_OPTS="mode=4 miimon=100"\n`)
}
if nicDesc.TeamingMaster != nil {
cmds.WriteString("BOOTPROTO=none\n")
cmds.WriteString("MASTER=")
cmds.WriteString(nicDesc.TeamingMaster.Name)
cmds.WriteString("\n")
cmds.WriteString("SLAVE=yes\n")
} else if nicDesc.Virtual {
cmds.WriteString("BOOTPROTO=none\n")
cmds.WriteString("NETMASK=255.255.255.255\n")
cmds.WriteString("IPADDR=")
cmds.WriteString(netutils2.PSEUDO_VIP)
cmds.WriteString("\n")
} else if nicDesc.Manual {
netmask := netutils2.Netlen2Mask(int(nicDesc.Masklen))
cmds.WriteString("BOOTPROTO=statuc\n")
cmds.WriteString("NETMASK=")
cmds.WriteString(netmask)
cmds.WriteString("\n")
cmds.WriteString("IPADDR=")
cmds.WriteString(nicDesc.Ip)
cmds.WriteString("\n")
var routes = make([][]string, 0)
netutils2.AddNicRoutes(&routes, nicDesc, mainIp, len(nics), privatePrefixes)
if len(nicDesc.Gateway) > 0 && nicDesc.Ip == mainIp {
routes = append(routes, []string{
"0.0.0.0/0",
nicDesc.Gateway,
})
}
var rtbl strings.Builder
for _, r := range routes {
rtbl.WriteString(r[0])
rtbl.WriteString(" ")
rtbl.WriteString(r[1])
rtbl.WriteString(" - ")
rtbl.WriteString(nicDesc.Name)
rtbl.WriteString("\n")
}
rtblStr := rtbl.String()
if len(rtblStr) > 0 {
var fn = fmt.Sprintf("/etc/sysconfig/network/ifroute-%s", nicDesc.Name)
if err := rootFs.FilePutContents(fn, rtblStr, false, false); err != nil {
return err
}
}
dnslist := netutils2.GetNicDns(nicDesc)
for i := 0; i < len(dnslist); i++ {
if !utils.IsInArray(dnslist[i], dnsSrv) {
dnsSrv = append(dnsSrv, dnslist[i])
}
}
} else {
cmds.WriteString("BOOTPROTO=dhcp\n")
}
var fn = fmt.Sprintf("/etc/sysconfig/network/ifcfg-%s", nicDesc.Name)
log.Debugf("%s: %s", fn, cmds.String())
if err := rootFs.FilePutContents(fn, cmds.String(), false, false); err != nil {
return err
}
}
if len(dnsSrv) > 0 {
cont, err := rootFs.FileGetContents("/etc/sysconfig/network/config", false)
if err != nil {
return errors.Wrap(err, "FileGetContents config")
}
lines := strings.Split(string(cont), "\n")
for i := range lines {
line := strings.TrimSpace(lines[i])
if strings.HasPrefix(line, "NETCONFIG_DNS_STATIC_SERVERS=") {
lines[i] = fmt.Sprintf("NETCONFIG_DNS_STATIC_SERVERS=\"%s\"", strings.Join(dnsSrv, " "))
}
}
err = rootFs.FilePutContents("/etc/sysconfig/network/config", strings.Join(lines, "\n"), false, false)
if err != nil {
return errors.Wrap(err, "FilePutContents config")
}
}
return nil
}
func (r *sSuseLikeRootFs) DeployStandbyNetworkingScripts(rootFs IDiskPartition, nics, nicsStandby []*types.SServerNic) error {
if err := r.sLinuxRootFs.DeployStandbyNetworkingScripts(rootFs, nics, nicsStandby); err != nil {
return err
}
for _, nic := range nicsStandby {
var cmds string
if len(nic.NicType) == 0 || nic.NicType != "ipmi" {
cmds += fmt.Sprintf("LLADDR=%s\n", nic.Mac)
cmds += "STARTMODE=off\n"
var fn = fmt.Sprintf("/etc/sysconfig/network/ifcfg-%s%d", NetDevPrefix, nic.Index)
if err := rootFs.FilePutContents(fn, cmds, false, false); err != nil {
return err
}
}
}
return nil
}
func (r *sSuseLikeRootFs) enableSerialConsole(drv IRootFsDriver, rootFs IDiskPartition, sysInfo *jsonutils.JSONDict) error {
return r.enableSerialConsoleSystemd(rootFs)
}
func (r *sSuseLikeRootFs) disableSerialConcole(drv IRootFsDriver, rootFs IDiskPartition) error {
r.disableSerialConsoleSystemd(rootFs)
return nil
}
type SOpenSuseRootFs struct {
*sSuseLikeRootFs
}
func NewOpenSuseRootFs(part IDiskPartition) IRootFsDriver {
return &SOpenSuseRootFs{sSuseLikeRootFs: newSuseLikeRootFs(part)}
}
func (c *SOpenSuseRootFs) String() string {
return "OpenSuseRootFs"
}
func (c *SOpenSuseRootFs) GetName() string {
return "OpenSUSE"
}
func (c *SOpenSuseRootFs) GetReleaseInfo(rootFs IDiskPartition) *deployapi.ReleaseInfo {
rel, _ := rootFs.FileGetContents("/etc/os-release", false)
var version string
if len(rel) > 0 {
lines := strings.Split(string(rel), "\n")
for _, line := range lines {
if strings.HasPrefix(line, "VERSION=") {
version = strings.Trim(line[len("VERSION="):], " \"'")
break
}
}
}
return deployapi.NewReleaseInfo(c.GetName(), version, c.GetArch(rootFs))
}
func (c *SOpenSuseRootFs) DeployNetworkingScripts(rootFs IDiskPartition, nics []*types.SServerNic) error {
if err := c.sSuseLikeRootFs.deployNetworkingScripts(rootFs, nics); err != nil {
return err
}
return nil
}
+10 -41
View File
@@ -64,8 +64,7 @@ func (d *sLinuxRootFs) deployUserDataByCron(userData string) error {
func (d *sLinuxRootFs) deployUserDataBySystemd(userData string) error {
const scriptPath = "/etc/userdata.sh"
const runScriptPath = "/etc/run-userdata.sh"
const serviceName = "cloud-userdata.service"
const serviceName = "cloud-userdata"
{
config, err := cloudinit.ParseUserData(userData)
@@ -76,55 +75,25 @@ func (d *sLinuxRootFs) deployUserDataBySystemd(userData string) error {
scripts = config.UserDataScript()
}
scripts += "\n"
scripts += fmt.Sprintf("systemctl disable --now %s\n", serviceName)
if d.isSupportSystemd() {
// diable systemd service
scripts += fmt.Sprintf("systemctl disable --now %s\n", serviceName)
} else {
// cleanup crontab
scripts += fmt.Sprintf("crontab -l 2>/dev/null | grep -v '%s') |crontab -\n", scriptPath)
}
err = d.rootFs.FilePutContents(scriptPath, scripts, false, false)
if err != nil {
return errors.Wrap(err, "save user_data fail")
}
}
{
runScripts := fmt.Sprintf(`#!/bin/bash
/bin/bash %s >> /var/log/cloud-userdata.log 2>&1
`, scriptPath)
err := d.rootFs.FilePutContents(runScriptPath, runScripts, false, false)
err := d.installInitScript(serviceName, scriptPath)
if err != nil {
return errors.Wrap(err, "save user_data fail")
}
err = d.rootFs.Chmod(runScriptPath, 0755, false)
if err != nil {
return errors.Wrap(err, "chmod user_data fail")
return errors.Wrap(err, "installInitScript")
}
}
{
var unitPath = fmt.Sprintf("/usr/lib/systemd/system/%s", serviceName)
var enablePath = fmt.Sprintf("/etc/systemd/system/multi-user.target.wants/%s", serviceName)
var unitContent = fmt.Sprintf(`[Unit]
Description=Run once
After=local-fs.target
After=network.target
[Service]
ExecStart=%s
RemainAfterExit=true
Type=oneshot
[Install]
WantedBy=multi-user.target
`, runScriptPath)
err := d.rootFs.FilePutContents(unitPath, unitContent, false, false)
if err != nil {
return errors.Wrap(err, "save user_data unit fail")
}
err = d.rootFs.Symlink(unitPath, enablePath, false)
if err != nil {
return errors.Wrap(err, "create user_data symlink fail")
}
}
return nil
}
+30 -2
View File
@@ -28,6 +28,7 @@ import (
"yunion.io/x/onecloud/pkg/hostman/guestfs"
"yunion.io/x/onecloud/pkg/hostman/guestfs/fsdriver"
"yunion.io/x/onecloud/pkg/util/btrfsutils"
"yunion.io/x/onecloud/pkg/util/fileutils2"
"yunion.io/x/onecloud/pkg/util/procutils"
)
@@ -110,18 +111,24 @@ func (p *SKVMGuestDiskPartition) MountPartReadOnly() bool {
}
func (p *SKVMGuestDiskPartition) Mount() bool {
if len(p.fs) == 0 || utils.IsInStringArray(p.fs, []string{"swap", "btrfs"}) {
if len(p.fs) == 0 || utils.IsInStringArray(p.fs, []string{"swap"}) {
log.Errorf("Mount fs failed: unsupport fs %s on %s", p.fs, p.partDev)
return false
}
err := p.fsck()
if err != nil {
log.Errorf("SKVMGuestDiskPartition fsck error: %s", err)
return false
// return false
}
err = p.mount(false)
if err != nil {
log.Errorf("SKVMGuestDiskPartition mount error: %s", err)
} else if p.fs == "btrfs" {
// try mount btrfs subvoume
err := btrfsutils.MountSubvols(p.partDev, p.mountPath)
if err != nil {
log.Errorf("SKVMGuestDiskPartition mount btrfs error %s", err)
}
}
if p.IsReadonly() {
@@ -197,6 +204,9 @@ func (p *SKVMGuestDiskPartition) mount(readonly bool) error {
func (p *SKVMGuestDiskPartition) fsck() error {
var checkCmd, fixCmd []string
switch p.fs {
case "btrfs":
checkCmd = []string{"fsck.btrfs", "--readonly", p.partDev}
fixCmd = []string{"fsck.btrfs", "--repair", p.partDev}
case "hfsplus":
checkCmd = []string{"fsck.hfsplus", "-q", p.partDev}
fixCmd = []string{"fsck.hfsplus", "-fpy", p.partDev}
@@ -257,6 +267,24 @@ func (p *SKVMGuestDiskPartition) Umount() error {
}
}()
if p.fs == "btrfs" {
// first try unmount btrfs subvols
err := btrfsutils.UnmountSubvols(p.mountPath)
if err != nil {
log.Errorf("SKVMGuestDiskPartition unmount btrfs error %s", err)
}
}
// check lsof
{
out, err := procutils.NewCommand("lsof", p.mountPath).Output()
if err != nil {
log.Warningf("lsof %s fail %s", p.mountPath, err)
} else {
log.Infof("unmount path %s lsof %s", p.mountPath, string(out))
}
}
var tries = 0
var err error
var out []byte
+12
View File
@@ -235,6 +235,18 @@ func (f *SLocalGuestFS) CheckOrAddUser(user, homeDir string, isSys bool) (realHo
err = errors.Wrap(err, "chage")
return
}
if !f.Exists(realHomeDir, false) {
err = f.Mkdir(realHomeDir, 0700, false)
if err != nil {
err = errors.Wrapf(err, "Mkdir %s", realHomeDir)
} else {
cmd := []string{"chroot", f.mountPath, "chown", user, realHomeDir}
err = procutils.NewCommand(cmd[0], cmd[1:]...).Run()
if err != nil {
err = errors.Wrap(err, "chown")
}
}
}
}
return
}
+12
View File
@@ -415,6 +415,18 @@ func (p *SSHPartition) CheckOrAddUser(user, homeDir string, isSys bool) (realHom
err = nil
}
}
if !p.Exists(realHomeDir, false) {
err = p.Mkdir(realHomeDir, 0700, false)
if err != nil {
err = errors.Wrapf(err, "Mkdir %s", realHomeDir)
} else {
cmd := []string{"/usr/sbin/chroot", p.mountPath, "chown", user, realHomeDir}
_, err = p.term.Run(strings.Join(cmd, " "))
if err != nil {
err = errors.Wrap(err, "chown")
}
}
}
}
return
}
+3 -2
View File
@@ -11,10 +11,11 @@
package apis
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
)
const (
@@ -8,6 +8,7 @@ package apis
import (
context "context"
grpc "google.golang.org/grpc"
codes "google.golang.org/grpc/codes"
status "google.golang.org/grpc/status"
+90
View File
@@ -0,0 +1,90 @@
// 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 btrfsutils
import (
"path/filepath"
"strings"
"yunion.io/x/log"
"yunion.io/x/pkg/errors"
"yunion.io/x/onecloud/pkg/util/procutils"
)
/*
ID 256 gen 32 top level 5 path @
ID 257 gen 2426 top level 256 path @/var
ID 258 gen 870 top level 256 path @/usr/local
ID 259 gen 2426 top level 256 path @/tmp
ID 260 gen 35 top level 256 path @/srv
ID 261 gen 2426 top level 256 path @/root
ID 262 gen 874 top level 256 path @/opt
ID 263 gen 35 top level 256 path @/home
ID 264 gen 26 top level 256 path @/boot/grub2/x86_64-efi
ID 265 gen 64 top level 256 path @/boot/grub2/i386-pc
ID 266 gen 881 top level 256 path @/.snapshots
ID 267 gen 2426 top level 266 path @/.snapshots/1/snapshot
ID 272 gen 65 top level 266 path @/.snapshots/2/snapshot
*/
func parseBtrfsSubvols(lines string) []string {
log.Debugf("parseBtrfsSubvols %s", lines)
var ret []string
for _, l := range strings.Split(lines, "\n") {
parts := strings.Split(l, " ")
if len(parts) >= 9 && strings.HasPrefix(parts[8], "@/") {
ret = append(ret, strings.TrimSpace(parts[8]))
}
}
return ret
}
func MountSubvols(dev string, mnt string) error {
output, err := procutils.NewCommand("btrfs", "subvolume", "list", mnt).Output()
if err != nil {
return errors.Wrap(err, "btrfs subvolume list")
}
subvols := parseBtrfsSubvols(string(output))
log.Debugf("mount btrfs subvols %s", strings.Join(subvols, ","))
for _, subvol := range subvols {
err := procutils.NewCommand("mount", dev, filepath.Join(mnt, subvol[2:]), "-o", "subvol=/"+subvol).Run()
if err != nil {
return errors.Wrapf(err, "btrfs mount subvolume %s", subvol)
}
}
return nil
}
func UnmountSubvols(mnt string) error {
output, err := procutils.NewCommand("btrfs", "subvolume", "list", mnt).Output()
if err != nil {
return errors.Wrap(err, "btrfs subvolume list")
}
subvols := parseBtrfsSubvols(string(output))
log.Debugf("unmount btrfs subvols %s", strings.Join(subvols, ","))
// scan in reverse order
var errs []error
for i := len(subvols) - 1; i >= 0; i-- {
subvol := subvols[i]
err := procutils.NewCommand("umount", filepath.Join(mnt, subvol[2:])).Run()
if err != nil {
errs = append(errs, errors.Wrapf(err, "btrfs unmount subvolume %s", subvol))
}
}
if len(errs) > 0 {
return errors.NewAggregate(errs)
}
return nil
}
+48
View File
@@ -0,0 +1,48 @@
// 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 btrfsutils
import (
"reflect"
"testing"
)
const (
lines = `ID 256 gen 32 top level 5 path @
ID 257 gen 872 top level 256 path @/var
ID 258 gen 869 top level 256 path @/usr/local
ID 259 gen 872 top level 256 path @/tmp
ID 260 gen 35 top level 256 path @/srv
ID 261 gen 872 top level 256 path @/root
ID 262 gen 871 top level 256 path @/opt
ID 263 gen 35 top level 256 path @/home
ID 264 gen 26 top level 256 path @/boot/grub2/x86_64-efi
ID 265 gen 64 top level 256 path @/boot/grub2/i386-pc
ID 266 gen 66 top level 256 path @/.snapshots
ID 267 gen 873 top level 266 path @/.snapshots/1/snapshot
ID 272 gen 65 top level 266 path @/.snapshots/2/snapshot
`
)
func TestParseBtrfsSubvols(t *testing.T) {
subvols := parseBtrfsSubvols(lines)
want := []string{"@/var", "@/usr/local", "@/tmp", "@/srv", "@/root", "@/opt", "@/home",
"@/boot/grub2/x86_64-efi", "@/boot/grub2/i386-pc", "@/.snapshots",
"@/.snapshots/1/snapshot", "@/.snapshots/2/snapshot",
}
if !reflect.DeepEqual(subvols, want) {
t.Errorf("expect %s got %s", want, subvols)
}
}
+15
View File
@@ -0,0 +1,15 @@
// 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 btrfsutils // import "yunion.io/x/onecloud/pkg/util/btrfsutils"