mirror of
https://github.com/yunionio/cloudpods.git
synced 2026-09-01 15:07:17 +08:00
feature: SUSE guest support
This commit is contained in:
@@ -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"
|
||||
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -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"
|
||||
Reference in New Issue
Block a user