diff --git a/pkg/cloudcommon/fstabutils/fstabutils.go b/pkg/cloudcommon/fstabutils/fstabutils.go new file mode 100644 index 0000000000..a2e8e816e1 --- /dev/null +++ b/pkg/cloudcommon/fstabutils/fstabutils.go @@ -0,0 +1,105 @@ +package fstabutils + +import ( + "fmt" + "regexp" + "strings" + + "yunion.io/x/log" +) + +type SFSRecord struct { + Dev string + Mount string + Fs string + Opt string + Dump string + Pas string +} + +func FSRecord(line string) *SFSRecord { + data := regexp.MustCompile(`\s+`).Split(line, -1) + if len(data) > 5 { + return &SFSRecord{data[0], data[1], data[2], data[3], data[4], data[5]} + } + log.Errorf("Invalid fstab record %s", line) + return nil +} + +func (fsr *SFSRecord) String() string { + return fmt.Sprintf("%s\t%s\t%s\t%s\t%s\t%s", + fsr.Dev, fsr.Mount, fsr.Fs, fsr.Opt, fsr.Dump, fsr.Pas) +} + +var VDISK_PREFIX = "/dev/vd" + +type FsTab []*SFSRecord + +func FSTabFile(content string) *FsTab { + if len(content) > 0 { + var res = make(FsTab, 0) + lines := strings.Split(content, "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if len(line) > 0 && line[0] != '#' { + if fsr := FSRecord(line); fsr != nil { + res = append(res, fsr) + } + } + } + return &res + } + return nil +} + +func (ft *FsTab) IsExists(dev string) bool { + for _, f := range *ft { + if f.Dev == dev { + return true + } + } + return false +} + +func (ft *FsTab) AddFsrec(line string) { + rec := FSRecord(line) + if rec != nil { + *ft = append(*ft, rec) + } +} + +func (ft *FsTab) RemoveDevices(devCnt int) { + var newList = make(FsTab, 0) + for _, f := range *ft { + if strings.HasPrefix(f.Dev, VDISK_PREFIX) { + devChar := f.Dev[len(VDISK_PREFIX)] + devIdx := devChar - 'a' + if devIdx >= 0 && int(devIdx) < devCnt { + newList = append(newList, f) + } + } else { + newList = append(newList, f) + } + } + ft = &newList +} + +func (ft *FsTab) ToConf() string { + var res string + for _, f := range *ft { + res += fmt.Sprintf("%s\n", f) + } + return res +} + +/* TODO: test +if __name__ == '__main__': + with open('/etc/fstab') as f: + cont = f.read(4096) + print cont + fstab = FSTabFile(cont) + print fstab.is_exists('/dev/sdc2') + fstab.add_fsrec('/dev/sdd1 /data ext4 defaults 0 0') + fstab.add_fsrec('/dev/sdd2') + print fstab.to_conf() +*/ diff --git a/pkg/hostman/guestfs/android.go b/pkg/hostman/guestfs/android.go new file mode 100644 index 0000000000..5e4e06ba56 --- /dev/null +++ b/pkg/hostman/guestfs/android.go @@ -0,0 +1,13 @@ +package guestfs + +type SAndroidRootFs struct { + *SGuestRootFsDriver +} + +func NewAndroidRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { + return &SAndroidRootFs{SGuestRootFsDriver: NewGuestRootFsDriver(part).(*SGuestRootFsDriver)} +} + +func init() { + rootfsDrivers = append(rootfsDrivers, NewAndroidRootFs) +} diff --git a/pkg/hostman/guestfs/core.go b/pkg/hostman/guestfs/core.go index 071bbb6c3e..e6f183357d 100644 --- a/pkg/hostman/guestfs/core.go +++ b/pkg/hostman/guestfs/core.go @@ -2,96 +2,298 @@ package guestfs import ( "fmt" - "io/ioutil" - "math/rand" - "os" - "path" - "strings" + "path/filepath" + "syscall" "yunion.io/x/jsonutils" "yunion.io/x/log" "yunion.io/x/onecloud/pkg/cloudcommon/sshkeys" - "yunion.io/x/onecloud/pkg/hostman" + "yunion.io/x/pkg/util/netutils" ) type SDeployInfo struct { publicKey *sshkeys.SSHKeys - deploys jsonutils.JSONObject + deploys []jsonutils.JSONObject password string isInit bool + enableTty bool } -type SLocalGuestFS struct { - mountPath string - readOnly bool -} +type newRootFsDriverFunc func(part *SKVMGuestDiskPartition) IRootFsDriver -func (f *SLocalGuestFS) isReadonly() bool { - log.Infof("Test if read-only fs ...") - var filename = fmt.Sprint("./%f", rand.Float32()) - if err := hostman.FilePutContents(filename, fmt.Sprint("%f", rand.Float32()), false); err == nil { - f.Remove(filename, false) - return false - } else { - log.Errorf("File system is readonly: %s", err) - f.readOnly = true - return true - } -} +var rootfsDrivers = make([]newRootFsDriverFunc, 0) -func (f *SLocalGuestFS) getLocalPath(sPath string, caseInsensitive bool) string { - var fullPath = f.mountPath - pathSegs := strings.Split(sPath, "/") - for _, seg := range pathSegs { - if len(seg) > 0 { - var realSeg string - files, _ := ioutil.ReadDir(fullPath) - for _, file := range files { - var f = file.Name() - if f == seg || (caseInsensitive && (strings.ToLower(f)) == strings.ToLower(seg)) || - (seg[len(seg)-1] == '*' && strings.HasPrefix(f, seg[:len(seg)-1])) || - (caseInsensitive && strings.HasPrefix(strings.ToLower(f), - strings.ToLower(seg[:len(seg)]))) { - realSeg = f - break - } - } - if len(realSeg) > 0 { - fullPath = path.Join(fullPath, realSeg) - } else { - return "" - } +func DetectRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { + for _, newDriverFunc := range rootfsDrivers { + d := newDriverFunc(part) + if testRootfs(d) { + return d } } - return fullPath + return nil } -func (f *SLocalGuestFS) Remove(path string, caseInsensitive bool) { - path = f.getLocalPath(path, caseInsensitive) - if len(path) > 0 { - os.Remove(path) +func testRootfs(d IRootFsDriver) bool { + caseInsensitive := d.IsFsCaseInsensitive() + for _, rd := range d.RootSignatures() { + if !d.GetPartition().Exists(rd, caseInsensitive) { + log.Infof("[%s] test root fs: %s not exists", d, rd) + return false + } } -} - -func NewLocalGuestFS(mountPath string) *SLocalGuestFS { - var ret = new(SLocalGuestFS) - ret.mountPath = mountPath - return ret + for _, rd := range d.RootExcludeSignatures() { + if d.GetPartition().Exists(rd, caseInsensitive) { + log.Infof("[%s] test root fs: %s exists, test failed", d, rd) + return false + } + } + return true } type IRootFsDriver interface { GetPartition() *SKVMGuestDiskPartition String() string - TestRootfs() bool + + IsFsCaseInsensitive() bool + RootSignatures() []string + RootExcludeSignatures() []string + GetReleaseInfo() []string + GetOs() string + DeployFiles([]jsonutils.JSONObject) error + DeployHostname(hn, domain string) error + DeployHost(hn, domain string, ips []string) error + DeployNetworkingScripts([]jsonutils.JSONObject) error + DeployStandbyNetworkingScripts(nics, nicsStandby []jsonutils.JSONObject) error + DeployUdevSubsystemScripts() error + DeployFstabScripts([]jsonutils.JSONObject) error + GetLoginAccount() string + DeployPublicKey(string, *sshkeys.SSHKeys) error + ChangeUserPasswd(account, gid, publicKey, password string) string + DeployYunionroot(*sshkeys.SSHKeys) error + EnableSerialConsole(*jsonutils.JSONDict) error + DisableSerialConsole() error + CommitChanges() error + + DeployGuestFs(IRootFsDriver, *jsonutils.JSONDict, *SDeployInfo) (jsonutils.JSONObject, error) } -var rootfsDrivers map[string]IRootFsDriver +type SGuestRootFsDriver struct { + rootFs *SKVMGuestDiskPartition +} -func DetectRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { - for k, v := range rootfsDrivers { - if v.TestRootfs(part) { - return v +func NewGuestRootFsDriver(rootFs *SKVMGuestDiskPartition) IRootFsDriver { + return &SGuestRootFsDriver{rootFs} +} + +func (d *SGuestRootFsDriver) GetPartition() *SKVMGuestDiskPartition { + return d.rootFs +} + +func (d *SGuestRootFsDriver) String() string { + return "" +} + +func (d *SGuestRootFsDriver) IsFsCaseInsensitive() bool { + return false +} + +func (d *SGuestRootFsDriver) RootSignatures() []string { + return nil +} + +func (d *SGuestRootFsDriver) RootExcludeSignatures() []string { + return nil +} + +func (d *SGuestRootFsDriver) GetReleaseInfo() []string { + return nil +} + +func (d *SGuestRootFsDriver) GetOs() string { + return "" +} + +func (d *SGuestRootFsDriver) DeployFiles(deploys []jsonutils.JSONObject) error { + caseInsensitive := d.IsFsCaseInsensitive() + for _, deploy := range deploys { + var modAppend = false + if action, _ := deploy.GetString("action"); action == "append" { + modAppend = true + } + sPath, err := deploy.GetString("path") + if err != nil { + return err + } + dirname := filepath.Dir(sPath) + if !d.rootFs.Exists(sPath, caseInsensitive) { + modeRWXOwner := syscall.S_IRUSR | syscall.S_IWUSR | syscall.S_IXUSR + err := d.rootFs.Mkdir(dirname, modeRWXOwner, caseInsensitive) + if err != nil { + return err + } + } + if content, err := deploy.GetString("content"); err != nil { + err := d.rootFs.FilePutContents(sPath, content, modAppend, caseInsensitive) + if err != nil { + return err + } } } return nil } + +func (d *SGuestRootFsDriver) DeployHostname(hn, domain string) error { + return fmt.Errorf("Not Implemented") +} + +func (d *SGuestRootFsDriver) DeployHost(hn, domain string, ips []string) error { + return fmt.Errorf("Not Implemented") +} + +func (d *SGuestRootFsDriver) DeployNetworkingScripts([]jsonutils.JSONObject) error { + return fmt.Errorf("Not Implemented") +} + +func (d *SGuestRootFsDriver) DeployStandbyNetworkingScripts(nics, nicsStandby []jsonutils.JSONObject) error { + return fmt.Errorf("Not Implemented") +} + +func (d *SGuestRootFsDriver) DeployUdevSubsystemScripts() error { + return fmt.Errorf("Not Implemented") +} + +func (d *SGuestRootFsDriver) DeployFstabScripts([]jsonutils.JSONObject) error { + return fmt.Errorf("Not Implemented") +} + +func (d *SGuestRootFsDriver) GetLoginAccount() string { + return "" +} + +func (d *SGuestRootFsDriver) DeployPublicKey(string, *sshkeys.SSHKeys) error { + return fmt.Errorf("Not Implemented") +} + +func (d *SGuestRootFsDriver) ChangeUserPasswd(account, gid, publicKey, password string) string { + return "" +} + +func (d *SGuestRootFsDriver) DeployYunionroot(*sshkeys.SSHKeys) error { + return fmt.Errorf("Not Implemented") +} + +func (d *SGuestRootFsDriver) EnableSerialConsole(*jsonutils.JSONDict) error { + return fmt.Errorf("Not Implemented") +} + +func (d *SGuestRootFsDriver) DisableSerialConsole() error { + return fmt.Errorf("Not Implemented") +} + +func (d *SGuestRootFsDriver) CommitChanges() error { + return fmt.Errorf("Not Implemented") +} + +func (d *SGuestRootFsDriver) DeployGuestFs(rootfs IRootFsDriver, guestDesc *jsonutils.JSONDict, + deployInfo *SDeployInfo) (jsonutils.JSONObject, error) { + var ret = jsonutils.NewDict() + var ips = make([]string, 0) + var releaseInfo = rootfs.GetReleaseInfo() + hn, _ := guestDesc.GetString("name") + domain, _ := guestDesc.GetString("domain") + gid, _ := guestDesc.GetString("uuid") + nics, _ := guestDesc.GetArray("nisc") + + var err error + for _, n := range nics { + ip, _ := n.GetString("ip") + var addr netutils.IPV4Addr + if addr, err = netutils.NewIPV4Addr(ip); err != nil { + log.Errorln(err) + return nil, err + } + if netutils.IsPrivate(addr) { + ips = append(ips, ip) + } + } + if releaseInfo != nil { + ret.Set("distro", jsonutils.NewString(releaseInfo[0])) + if len(releaseInfo) > 1 && len(releaseInfo[1]) > 0 { + ret.Set("version", jsonutils.NewString(releaseInfo[1])) + } + if len(releaseInfo) > 2 && len(releaseInfo[2]) > 0 { + ret.Set("arch", jsonutils.NewString(releaseInfo[2])) + } + if len(releaseInfo) > 3 && len(releaseInfo[3]) > 0 { + ret.Set("language", jsonutils.NewString(releaseInfo[3])) + } + } + ret.Set("os", jsonutils.NewString(rootfs.GetOs())) + if d.rootFs.GetReadonly() { + if len(deployInfo.deploys) > 0 { + if err = rootfs.DeployFiles(deployInfo.deploys); err != nil { + log.Errorln(err) + return nil, err + } + } + if err = rootfs.DeployHostname(hn, domain); err != nil { + log.Errorln(err) + return nil, err + } + if err = rootfs.DeployHost(hn, domain, ips); err != nil { + log.Errorln(err) + return nil, err + } + if err = rootfs.DeployNetworkingScripts(nics); err != nil { + log.Errorln(err) + return nil, err + } + if nicsStandby, e := guestDesc.GetArray("nics_standby"); e == nil { + rootfs.DeployStandbyNetworkingScripts(nics, nicsStandby) + } + if err = rootfs.DeployUdevSubsystemScripts(); err != nil { + log.Errorln(err) + return nil, err + } + if deployInfo.isInit { + disks, _ := guestDesc.GetArray("disks") + if err = rootfs.DeployFstabScripts(disks); err != nil { + log.Errorln(err) + return nil, err + } + } + if len(deployInfo.password) > 0 { + if account := rootfs.GetLoginAccount(); len(account) > 0 { + if err = rootfs.DeployPublicKey(account, deployInfo.publicKey); err != nil { + log.Errorln(err) + return nil, err + } + secret := rootfs.ChangeUserPasswd(account, gid, + deployInfo.publicKey.PublicKey, deployInfo.password) + if len(secret) > 0 { + ret.Set("key", jsonutils.NewString(secret)) + } + } + } + if err = rootfs.DeployYunionroot(deployInfo.publicKey); err != nil { + log.Errorln(err) + return nil, err + } + if deployInfo.enableTty { + if err = rootfs.EnableSerialConsole(ret); err != nil { + log.Errorln(err) + return nil, err + } + } else { + if err = rootfs.DisableSerialConsole(); err != nil { + log.Errorln(err) + return nil, err + } + } + if err = rootfs.CommitChanges(); err != nil { + log.Errorln(err) + return nil, err + } + } + return ret, nil +} diff --git a/pkg/hostman/guestfs/esxi.go b/pkg/hostman/guestfs/esxi.go new file mode 100644 index 0000000000..6bda01754c --- /dev/null +++ b/pkg/hostman/guestfs/esxi.go @@ -0,0 +1,13 @@ +package guestfs + +type SEsxiRootFs struct { + *SGuestRootFsDriver +} + +func NewEsxiRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { + return &SEsxiRootFs{SGuestRootFsDriver: NewGuestRootFsDriver(part).(*SGuestRootFsDriver)} +} + +func init() { + rootfsDrivers = append(rootfsDrivers, NewEsxiRootFs) +} diff --git a/pkg/hostman/guestfs/guestfs.go b/pkg/hostman/guestfs/guestfs.go index b118973ca6..1d99e1b1a8 100644 --- a/pkg/hostman/guestfs/guestfs.go +++ b/pkg/hostman/guestfs/guestfs.go @@ -2,8 +2,10 @@ package guestfs import ( "fmt" + "os" "os/exec" "strings" + "time" "yunion.io/x/log" "yunion.io/x/onecloud/pkg/hostman" @@ -46,9 +48,12 @@ func (p *SKVMGuestDiskPartition) Mount() bool { log.Errorf("SKVMGuestDiskPartition mount error: %s", err) return false } - if p.isReadonly() { + if p.IsReadonly() { log.Errorf("SKVMGuestDiskPartition %s is readonly, try mount as ro", p.partDev) - p.Umount() + e := p.Umount() // TODO + if e != nil { + log.Errorln(e) + } err = p.mount(true) if err != nil { log.Errorf("SKVMGuestDiskPartition mount as ro error %s", err) @@ -113,3 +118,44 @@ func (p *SKVMGuestDiskPartition) fsck() error { } return nil } + +func (p *SKVMGuestDiskPartition) Exists(sPath string, caseInsensitive bool) bool { + sPath = p.getLocalPath(sPath, caseInsensitive) + if len(sPath) > 0 { + if _, err := os.Stat(sPath); !os.IsNotExist(err) { + return true + } + } + return false +} + +func (p *SKVMGuestDiskPartition) IsMounted() bool { + if _, err := os.Stat(p.mountPath); os.IsNotExist(err) { + return false + } + err := exec.Command("mountpoint", p.mountPath).Run() + if err == nil { + return true + } else { + log.Errorln(err) + } + return false +} + +func (p *SKVMGuestDiskPartition) Umount() bool { + if p.IsMounted() { + var tries = 0 + for tries < 10 { + tries += 1 + err := exec.Command("umount", p.mountPath).Run() + if err == nil { + exec.Command("blockdev", "--flushbufs", p.partDev).Run() + os.Remove(p.mountPath) + return true + } else { + time.Sleep(time.Second * 1) + } + } + } + return false +} diff --git a/pkg/hostman/guestfs/linux.go b/pkg/hostman/guestfs/linux.go new file mode 100644 index 0000000000..11a37cfae5 --- /dev/null +++ b/pkg/hostman/guestfs/linux.go @@ -0,0 +1,417 @@ +package guestfs + +import ( + "fmt" + "path" + "strings" + "syscall" + + "yunion.io/x/jsonutils" + "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/cloudcommon/fstabutils" + "yunion.io/x/onecloud/pkg/cloudcommon/sshkeys" + "yunion.io/x/onecloud/pkg/hostman" + "yunion.io/x/onecloud/pkg/hostman/options" + "yunion.io/x/onecloud/pkg/util/seclib2" + "yunion.io/x/pkg/utils" +) + +type SLinuxRootFs struct { + *SGuestRootFsDriver +} + +func NewLinuxRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { + return &SAndroidRootFs{SGuestRootFsDriver: NewGuestRootFsDriver(part).(*SGuestRootFsDriver)} +} + +func (l *SLinuxRootFs) String() string { + return "LinuxRootFs" +} + +func (l *SLinuxRootFs) RootSignatures() []string { + return []string{"/bin", "/etc", "/boot", "/lib", "/usr"} +} + +func (l *SLinuxRootFs) DeployHost(hn, domain string, ips []string) error { + var oldHostFile string + if l.rootFs.Exists("/etc/hosts", false) { + oldhf, err := l.rootFs.FileGetContents("/etc/hosts", false) + if err != nil { + return err + } + oldHostFile = string(oldhf) + } + hf := make(hostman.HostsFile, 0) + hf.Parse(oldHostFile) + hf.Add("127.0.0.1", "localhost") + for _, ip := range ips { + hf.Add(ip, fmt.Sprintf("%s.%s", hn+domain), hn) + } + return nil +} + +func (l *SLinuxRootFs) GetLoginAccount() string { + var selUsr string + if options.HostOptions.LinuxDefaultRootUser && l.rootFs.Exists("/root", false) { + selUsr = "root" + } else { + usrs := l.rootFs.Listdir("/home", false) + for _, usr := range usrs { + if len(selUsr) == 0 || len(selUsr) > len(usr) { + selUsr = usr + } + } + if len(selUsr) > 0 && l.rootFs.Exists("/root", false) { + selUsr = "root" + } + } + return selUsr +} + +func (l *SLinuxRootFs) ChangeUserPassswd(account, gid, publicKey, password string) string { + var secret string + if err := l.rootFs.Passwd(account, password, false); err != nil { + if len(publicKey) > 0 { + secret, _ = seclib2.EncryptBase64(publicKey, password) + } else { + secret, _ = utils.EncryptAESBase64(gid, password) + } + } else { + log.Errorf("Change uer passwd error: %s", err) + } + return secret +} + +func (l *SLinuxRootFs) DeployPublicKey(selUsr string, pubkeys *sshkeys.SSHKeys) error { + var usrDir string + if selUsr == "root" { + usrDir = "/root" + } else { + usrDir = path.Join("/home", selUsr) + } + return l.rootFs.DeployAuthorizedKeys(usrDir, pubkeys, false) +} + +func (l *SLinuxRootFs) DeployYunionroot(pubkeys *sshkeys.SSHKeys) error { + l.DisableSelinux() + l.DisableCloudinit() + var yunionroot = "cloudroot" + if err := l.rootFs.UserAdd(yunionroot, false); err != nil { + return err + } + err := l.rootFs.DeployAuthorizedKeys(path.Join("/home", yunionroot), pubkeys, false) + if err != nil { + return err + } + return l.EnableUserSudo(yunionroot) +} + +func (l *SLinuxRootFs) EnableUserSudo(user string) error { + var sudoDir = "/etc/sudoers.d" + var content = fmt.Sprintf("%s ALL=(ALL) NOPASSWD:ALL\n", user) + if l.rootFs.Exists(sudoDir, false) { + filepath := path.Join(sudoDir, fmt.Sprintf("90-%s-users", user)) + err := l.rootFs.FilePutContents(filepath, content, false, false) + if err != nil { + log.Errorln(err) + return err + } + return l.rootFs.Chmod(filepath, syscall.S_IRUSR|syscall.S_IRGRP, false) + } + return nil +} + +func (l *SLinuxRootFs) DisableSelinux() { + selinuxConfig := "/etc/selinux/config" + content := `# This file controls the state of SELinux on the system. +# SELINUX= can take one of these three values: +# enforcing - SELinux security policy is enforced. +# permissive - SELinux prints warnings instead of enforcing. +# disabled - No SELinux policy is loaded. +SELINUX=disabled +# SELINUXTYPE= can take one of three two values: +# targeted - Targeted processes are protected, +# minimum - Modification of targeted policy. Only selected processes are protected. +# mls - Multi Level Security protection. +SELINUXTYPE=targeted +` + if l.rootFs.Exists(selinuxConfig, false) { + l.rootFs.FilePutContents(selinuxConfig, content, false, false) + } +} + +func (l *SLinuxRootFs) DisableCloudinit() { + cloudDir := "/etc/cloud" + cloudDisableFile := "/etc/cloud/cloud-init.disabled" + if l.rootFs.Exists(cloudDir, false) { + l.rootFs.FilePutContents(cloudDisableFile, "", false, false) + } +} + +func (l *SLinuxRootFs) DeployFstabScripts(disks []jsonutils.JSONObject) error { + fstabcont, err := l.rootFs.FileGetContents("/etc/fstab", false) + if err != nil { + return err + } + var dataDiskIdx = 0 + var rec string + var modeRwxOwner = syscall.S_IRUSR | syscall.S_IWUSR | syscall.S_IXUSR + var fstab = fstabutils.FSTabFile(string(fstabcont)) + + for i := 1; i < len(disks); i++ { + diskId, err := disks[i].GetString("disk_id") + if err != nil { + diskId = "None" + } + dev := fmt.Sprintf("UUID=%s", diskId) + if !fstab.IsExists(dev) { + fs, err := disks[i].GetString("fs") + if fs == "swap" { + rec = fmt.Sprintf("%s none %s sw 0 0", dev, fs) + } else { + mtPath, _ := disks[i].GetString("mountpoint") + if len(mtPath) == 0 { + mtPath = "/data" + if dataDiskIdx > 0 { + mtPath += fmt.Sprintf("%d", dataDiskIdx) + } + dataDiskIdx += 1 + } + rec = fmt.Sprintf("%s %s %s defaults 2 2", dev, mtPath, fs) + if !l.rootFs.Exists(mtPath, false) { + if err := l.rootFs.Mkdir(mtPath, modeRwxOwner, false); err != nil { + return err + } + } + } + fstab.AddFsrec(rec) + } + } + cf := fstab.ToConf() + return l.rootFs.FilePutContents("/etc/fstab", cf, false, false) +} + +func (l *SLinuxRootFs) DeployNetworkingScripts(nics []jsonutils.JSONObject) error { + udevPath := "/etc/udev/rules.d/" + if l.rootFs.Exists(udevPath, false) { + rules := l.rootFs.Listdir(udevPath, false) + for _, rule := range rules { + if strings.Index(rule, "persistent-net.rules") > 0 { + l.rootFs.Remove(path.Join(udevPath, rule), false) + } else if strings.Index(rule, "persistent-cd.rules") > 0 { + if err := l.rootFs.FilePutContents(path.Join(udevPath, rule), "", false, false); err != nil { + return err + } + } + } + var nicRules string + for _, nic := range nics { + nicRules += `KERNEL=="eth*", SUBSYSTEM=="net", ACTION=="add", ` + nicRules += `DRIVERS=="?*", ` + mac, _ := nic.GetString("mac") + nicRules += fmt.Sprintf(`ATTR{address}=="%s", ATTR{type}=="1", `, strings.ToLower(mac)) + idx, _ := nic.Int("index") + nicRules += fmt.Sprintf(`NAME="eth%d"\n`, idx) + } + if err := l.rootFs.FilePutContents(path.Join(udevPath, "70-persistent-net.rules"), nicRules, false, false); err != nil { + return err + } + + var usbRules string + usbRules = `SUBSYSTEM=="usb", ATTRS{idVendor}=="1d6b", ATTRS{idProduct}=="0001", ` + usbRules += `RUN+="/bin/sh -c \'echo enabled > /sys$env{DEVPATH}/../power/wakeup\'"\n` + if err := l.rootFs.FilePutContents(path.Join(udevPath, + "90-usb-tablet-remote-wakeup.rules"), usbRules, false, false); err != nil { + return err + } + } + return nil +} + +func (l *SLinuxRootFs) DeployStandbyNetworkingScripts(nics, nicsStandby []jsonutils.JSONObject) error { + var udevPath = "/etc/udev/rules.d/" + var nicRules string + for _, nic := range nicsStandby { + nicType, _ := nic.GetString("nic_type") + if !nic.Contains("nic_type") || nicType != "impi" { + nicRules += `KERNEL=="eth*", SUBSYSTEM=="net", ACTION=="add", ` + nicRules += `DRIVERS=="?*", ` + mac, _ := nic.GetString("mac") + nicRules += fmt.Sprintf(`ATTR{address}=="%s", ATTR{type}=="1", `, strings.ToLower(mac)) + idx, _ := nic.Int("index") + nicRules += fmt.Sprintf(`NAME="eth%d"\n`, idx) + } + } + if err := l.rootFs.FilePutContents(path.Join(udevPath, "70-persistent-net.rules"), nicRules, false, false); err != nil { + return err + } + return nil +} + +func (l *SLinuxRootFs) GetOs() string { + return "Linux" +} + +func (l *SLinuxRootFs) GetArch() string { + if l.rootFs.Exists("/lib64", false) && l.rootFs.Exists("/usr/lib64", false) { + return "x86_64" + } else { + return "x86" + } +} + +func (l *SLinuxRootFs) PrepareFsForTemplate() { + // clean /etc/fstab + if l.rootFs.Exists("/etc/fstab", false) { + fstabcont, _ := l.rootFs.FileGetContents("/etc/fstab", false) + fstab := fstabutils.FSTabFile(string(fstabcont)) + fstab.RemoveDevices(1) + cf := fstab.ToConf() + l.rootFs.FilePutContents("/etc/fstab", cf, false, false) + } + // rm /etc/ssh/*_key.* + if l.rootFs.Exists("/etc/ssh", false) { + for _, f := range l.rootFs.Listdir("/etc/ssh", false) { + if strings.HasSuffix(f, "_key") || strings.HasSuffix(f, "_key.pub") { + l.rootFs.Remove("/etc/ssh/"+f, false) + } + } + } + // clean cloud-init + if l.rootFs.Exists("/var/lib/cloud", false) { + l.rootFs.Cleandir("/var/lib/cloud", false, false) + } + cloudDisableFile := "/etc/cloud/cloud-init.disabled" + if l.rootFs.Exists(cloudDisableFile, false) { + l.rootFs.Remove(cloudDisableFile, false) + } + // clean /tmp /var/log /var/cache /var/spool /var/run + for _, dir := range []string{"/tmp", "/var/tmp"} { + if l.rootFs.Exists(dir, false) { + l.rootFs.Cleandir(dir, false, false) + } + } + for _, dir := range []string{"/var/log", "/var/cache", "/usr/local/var/log", "/usr/local/var/cache"} { + if l.rootFs.Exists(dir, false) { + l.rootFs.Zerofiles(dir, false) + } + } + for _, dir := range []string{"/var/spool", "/var/run", "/run", "/usr/local/var/spool", "/usr/local/var/run"} { + if l.rootFs.Exists(dir, false) { + l.rootFs.Cleandir(dir, true, true) + } + } +} + +type SDebianLikeRootFs struct { + *SLinuxRootFs +} + +func NewDebianLikeRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { + return &SDebianLikeRootFs{SLinuxRootFs: NewLinuxRootFs(part).(*SLinuxRootFs)} +} + +type SDebianRootFs struct { + *SDebianLikeRootFs +} + +func NewDebianRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { + return &SDebianRootFs{SDebianLikeRootFs: NewDebianLikeRootFs(part).(*SDebianLikeRootFs)} +} + +type SCirrosRootFs struct { + *SDebianRootFs +} + +func NewCirrosRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { + return &SCirrosRootFs{SDebianRootFs: NewDebianRootFs(part).(*SDebianRootFs)} +} + +type SCirrosNewRootFs struct { + *SDebianRootFs +} + +func NewCirrosNewRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { + return &SCirrosNewRootFs{SDebianRootFs: NewDebianRootFs(part).(*SDebianRootFs)} +} + +type SUbuntuRootFs struct { + *SDebianRootFs +} + +func NewUbuntuRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { + return &SUbuntuRootFs{SDebianRootFs: NewDebianRootFs(part).(*SDebianRootFs)} +} + +type SRedhatLikeRootFs struct { + *SLinuxRootFs +} + +func NewRedhatLikeRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { + return &SRedhatLikeRootFs{SLinuxRootFs: NewLinuxRootFs(part).(*SLinuxRootFs)} +} + +type SCentosRootFs struct { + *SRedhatLikeRootFs +} + +func NewCentosRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { + return &SCentosRootFs{SRedhatLikeRootFs: NewRedhatLikeRootFs(part).(*SRedhatLikeRootFs)} +} + +type SFedoraRootFs struct { + *SRedhatLikeRootFs +} + +func NewFedoraRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { + return &SFedoraRootFs{SRedhatLikeRootFs: NewRedhatLikeRootFs(part).(*SRedhatLikeRootFs)} +} + +type SRhelRootFs struct { + *SRedhatLikeRootFs +} + +func NewRhelRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { + return &SRhelRootFs{SRedhatLikeRootFs: NewRedhatLikeRootFs(part).(*SRedhatLikeRootFs)} +} + +type SGentooRootFs struct { + *SLinuxRootFs +} + +func NewGentooRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { + return &SGentooRootFs{SLinuxRootFs: NewLinuxRootFs(part).(*SLinuxRootFs)} +} + +type SArchLinuxRootFs struct { + *SLinuxRootFs +} + +func NewArchLinuxRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { + return &SArchLinuxRootFs{SLinuxRootFs: NewLinuxRootFs(part).(*SLinuxRootFs)} +} + +type SOpenWrtRootFs struct { + *SLinuxRootFs +} + +func NewOpenWrtRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { + return &SOpenWrtRootFs{SLinuxRootFs: NewLinuxRootFs(part).(*SLinuxRootFs)} +} + +type SCoreOsRootFs struct { + *SGuestRootFsDriver +} + +func NewCoreOsRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { + return &SCoreOsRootFs{SGuestRootFsDriver: NewGuestRootFsDriver(part).(*SGuestRootFsDriver)} +} + +func init() { + linuxFsDrivers := []newRootFsDriverFunc{ + NewLinuxRootFs, NewDebianLikeRootFs, NewDebianRootFs, NewCirrosRootFs, NewCirrosNewRootFs, + NewUbuntuRootFs, NewRedhatLikeRootFs, NewCentosRootFs, NewFedoraRootFs, NewRhelRootFs, + NewGentooRootFs, NewArchLinuxRootFs, NewOpenWrtRootFs, NewCoreOsRootFs, + } + rootfsDrivers = append(rootfsDrivers, linuxFsDrivers...) +} diff --git a/pkg/hostman/guestfs/localfs.go b/pkg/hostman/guestfs/localfs.go new file mode 100644 index 0000000000..0eca5bcb45 --- /dev/null +++ b/pkg/hostman/guestfs/localfs.go @@ -0,0 +1,323 @@ +package guestfs + +import ( + "fmt" + "io" + "io/ioutil" + "math/rand" + "os" + "os/exec" + "path" + "strings" + "syscall" + + "yunion.io/x/log" + "yunion.io/x/onecloud/pkg/cloudcommon/sshkeys" + "yunion.io/x/onecloud/pkg/hostman" +) + +type SLocalGuestFS struct { + mountPath string + readOnly bool +} + +func (f *SLocalGuestFS) IsReadonly() bool { + log.Infof("Test if read-only fs ...") + var filename = fmt.Sprint("./%f", rand.Float32()) + if err := f.FilePutContents(filename, fmt.Sprint("%f", rand.Float32()), false, false); err == nil { + f.Remove(filename, false) + return false + } else { + log.Errorf("File system is readonly: %s", err) + f.readOnly = true + return true + } +} + +func (f *SLocalGuestFS) GetReadonly() bool { + return f.readOnly +} + +func (f *SLocalGuestFS) getLocalPath(sPath string, caseInsensitive bool) string { + var fullPath = f.mountPath + pathSegs := strings.Split(sPath, "/") + for _, seg := range pathSegs { + if len(seg) > 0 { + var realSeg string + files, _ := ioutil.ReadDir(fullPath) + for _, file := range files { + var f = file.Name() + if f == seg || (caseInsensitive && (strings.ToLower(f)) == strings.ToLower(seg)) || + (seg[len(seg)-1] == '*' && strings.HasPrefix(f, seg[:len(seg)-1])) || + (caseInsensitive && strings.HasPrefix(strings.ToLower(f), + strings.ToLower(seg[:len(seg)]))) { + realSeg = f + break + } + } + if len(realSeg) > 0 { + fullPath = path.Join(fullPath, realSeg) + } else { + return "" + } + } + } + return fullPath +} + +func (f *SLocalGuestFS) Remove(path string, caseInsensitive bool) { + path = f.getLocalPath(path, caseInsensitive) + if len(path) > 0 { + os.Remove(path) + } +} + +func (f *SLocalGuestFS) Mkdir(sPath string, mode int, caseInsensitive bool) error { + segs := strings.Split(sPath, "/") + sPath = "" + pPath := f.getLocalPath("/", caseInsensitive) + for _, s := range segs { + if len(s) > 0 { + sPath = path.Join(sPath, s) + vPath := f.getLocalPath(sPath, caseInsensitive) + if len(vPath) > 0 { + if err := os.Mkdir(path.Join(pPath, s), os.FileMode(mode)); err != nil { + return err + } + pPath = f.getLocalPath(sPath, caseInsensitive) + } else { + pPath = vPath + } + } + } + return nil +} + +func (f *SLocalGuestFS) Listdir(sPath string, caseInsensitive bool) []string { + sPath = f.getLocalPath(sPath, caseInsensitive) + if len(sPath) > 0 { + files, err := ioutil.ReadDir(sPath) + if err != nil { + log.Errorln(err) + return nil + } + var res = make([]string, 0) + for _, file := range files { + res = append(res, file.Name()) + } + return res + } + return nil +} + +func (f *SLocalGuestFS) Cleandir(dir string, keepdir, caseInsensitive bool) error { + sPath := f.getLocalPath(dir, caseInsensitive) + if len(sPath) > 0 { + return hostman.Cleandir(sPath, keepdir) + } + return fmt.Errorf("No such file %s", sPath) +} + +// TODO +func (f *SLocalGuestFS) Zerofiles(dir string, caseInsensitive bool) error { + sPath := f.getLocalPath(dir, caseInsensitive) + if len(sPath) > 0 { + //写到这里了。。。 + return hostman.Zerofiles(sPath) + } + return fmt.Errorf("No such file %s", sPath) +} + +// TODO: test +func (f *SLocalGuestFS) Passwd(account, password string, caseInsensitive bool) error { + var proc = exec.Command("chroot", f.mountPath, "passwd", account) + stdin, err := proc.StdinPipe() + if err != nil { + return err + } + defer stdin.Close() + + outb, err := proc.StdoutPipe() + if err != nil { + return err + } + defer outb.Close() + + errb, err := proc.StderrPipe() + if err != nil { + return err + } + defer errb.Close() + + if err := proc.Start(); err != nil { + return err + } + io.WriteString(stdin, fmt.Sprintf("%s\n", password)) + io.WriteString(stdin, fmt.Sprintf("%s\n", password)) + stdoutPut, err := ioutil.ReadAll(outb) + if err != nil { + return err + } + stderrOutPut, err := ioutil.ReadAll(errb) + if err != nil { + return err + } + log.Infof("Passwd %s %s", stdoutPut, stderrOutPut) + return proc.Wait() +} + +func (f *SLocalGuestFS) Stat(usrDir string, caseInsensitive bool) os.FileInfo { + sPath := f.getLocalPath(usrDir, caseInsensitive) + if len(sPath) > 0 { + fileInfo, err := os.Stat(sPath) + if err != nil { + log.Errorln(err) + } + return fileInfo + } + return nil +} + +func (f *SLocalGuestFS) Exists(sPath string, caseInsensitive bool) bool { + sPath = f.getLocalPath(sPath, caseInsensitive) + if len(sPath) > 0 { + if _, err := os.Stat(sPath); !os.IsNotExist(err) { + return true + } + } + return false +} + +func (f *SLocalGuestFS) Chown(sPath string, uid, gid int, caseInsensitive bool) error { + sPath = f.getLocalPath(sPath, caseInsensitive) + if len(sPath) > 0 { + return os.Chown(sPath, uid, gid) + } + return nil +} + +func (f *SLocalGuestFS) Chmod(sPath string, mode uint32, caseInsensitive bool) error { + sPath = f.getLocalPath(sPath, caseInsensitive) + if len(sPath) > 0 { + return os.Chmod(sPath, os.FileMode(mode)) + } + return nil +} + +func (f *SLocalGuestFS) UserAdd(user string, caseInsensitive bool) error { + output, err := exec.Command("chroot", f.mountPath, "useradd", user).Output() + if err != nil { + log.Errorf("Useradd fail: %s", err) + return err + } else { + log.Infof("Useradd: %s", output) + } + return nil +} + +func (f *SLocalGuestFS) MergeAuthorizedKeys(oldKeys string, pubkeys *sshkeys.SSHKeys) string { + var allkeys = make(map[string]string, 0) + if len(oldKeys) > 0 { + for _, line := range strings.Split(oldKeys, "\n") { + line = strings.TrimSpace(line) + dat := strings.Split(line, " ") + if len(dat) > 1 { + if _, ok := allkeys[dat[1]]; !ok { + allkeys[dat[1]] = line + } + } + } + } + if len(pubkeys.DeletePublicKey) > 0 { + dat := strings.Split(pubkeys.DeletePublicKey, " ") + if len(dat) > 1 { + if _, ok := allkeys[dat[1]]; ok { + delete(allkeys, dat[1]) + } + } + } + for _, k := range []string{pubkeys.PublicKey, pubkeys.AdminPublicKey, pubkeys.ProjectPublicKey} { + if len(k) > 0 { + k = strings.TrimSpace(k) + dat := strings.Split(k, " ") + if len(dat) > 1 { + if _, ok := allkeys[dat[1]]; !ok { + allkeys[dat[1]] = k + } + } + } + } + var keys = make([]string, len(allkeys)) + for key, _ := range allkeys { + keys = append(keys, key) + } + return strings.Join(keys, "\n") +} + +func (f *SLocalGuestFS) DeployAuthorizedKeys(usrDir string, pubkeys *sshkeys.SSHKeys, replace bool) error { + usrStat := f.Stat(usrDir, false) + if usrStat != nil { + sshDir := path.Join(usrDir, ".ssh") + authFile := path.Join(sshDir, "authorized_keys") + modeRwxOwner := syscall.S_IRUSR | syscall.S_IWUSR | syscall.S_IXUSR + modeRwOwner := syscall.S_IRUSR | syscall.S_IWUSR + fStat := usrStat.Sys().(*syscall.Stat_t) + if !f.Exists(sshDir, false) { + err := f.Mkdir(sshDir, modeRwxOwner, false) + if err != nil { + return err + } + err = f.Chown(sshDir, int(fStat.Uid), int(fStat.Gid), false) + if err != nil { + return err + } + } + var oldKeys = "" + if !replace { + bOldKeys, _ := f.FileGetContents(authFile, false) + oldKeys = string(bOldKeys) + } + newKeys := f.MergeAuthorizedKeys(oldKeys, pubkeys) + err := f.FilePutContents(authFile, newKeys, false, false) + if err != nil { + return err + } + err = f.Chown(authFile, int(fStat.Uid), int(fStat.Gid), false) + if err != nil { + return err + } + return f.Chmod(authFile, uint32(modeRwOwner), false) + } + return nil +} + +func (f *SLocalGuestFS) FileGetContents(sPath string, caseInsensitive bool) ([]byte, error) { + sPath = f.getLocalPath(sPath, caseInsensitive) + if len(sPath) > 0 { + return ioutil.ReadFile(sPath) + } + return nil, fmt.Errorf("Cann't find local path") +} + +func (f *SLocalGuestFS) FilePutContents(sPath, content string, modAppend, caseInsensitive bool) error { + sFilePath := f.getLocalPath(sPath, caseInsensitive) + if len(sFilePath) > 0 { + sPath = sFilePath + } else { + dirPath := f.getLocalPath(path.Dir(sPath), caseInsensitive) + if len(dirPath) > 0 { + sPath = path.Join(dirPath, path.Base(sPath)) + } + } + if len(sPath) > 0 { + return hostman.FilePutContents(sPath, content, modAppend) + } else { + return fmt.Errorf("Cann't put content") + } +} + +func NewLocalGuestFS(mountPath string) *SLocalGuestFS { + var ret = new(SLocalGuestFS) + ret.mountPath = mountPath + return ret +} diff --git a/pkg/hostman/guestfs/macos.go b/pkg/hostman/guestfs/macos.go new file mode 100644 index 0000000000..2fa37242b7 --- /dev/null +++ b/pkg/hostman/guestfs/macos.go @@ -0,0 +1,13 @@ +package guestfs + +type SMacOSRootFs struct { + *SGuestRootFsDriver +} + +func NewMacOSRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { + return &SMacOSRootFs{SGuestRootFsDriver: NewGuestRootFsDriver(part).(*SGuestRootFsDriver)} +} + +func init() { + rootfsDrivers = append(rootfsDrivers, NewMacOSRootFs) +} diff --git a/pkg/hostman/guestfs/windows.go b/pkg/hostman/guestfs/windows.go new file mode 100644 index 0000000000..3dacebf6a2 --- /dev/null +++ b/pkg/hostman/guestfs/windows.go @@ -0,0 +1,13 @@ +package guestfs + +type SWindowsRootFs struct { + *SGuestRootFsDriver +} + +func NewWindowsRootFs(part *SKVMGuestDiskPartition) IRootFsDriver { + return &SWindowsRootFs{SGuestRootFsDriver: NewGuestRootFsDriver(part).(*SGuestRootFsDriver)} +} + +func init() { + rootfsDrivers = append(rootfsDrivers, NewWindowsRootFs) +} diff --git a/pkg/hostman/guestfs/winscripts.go b/pkg/hostman/guestfs/winscripts.go new file mode 100644 index 0000000000..342274d5a7 --- /dev/null +++ b/pkg/hostman/guestfs/winscripts.go @@ -0,0 +1,488 @@ +package guestfs + +const WinScriptChangePassword = ` + +$username = $args[0] +$passwd = $args[1] +$loghash = $args[2] +$logpath = $args[3] +Function ChangePassword($u, $p) { + $admin = [adsi]("WinNT://./$($u), user") + $succ = 0 + $tried = 0 + $max_tries = 10 + while (($succ -eq 0) -and ($tried -lt $max_tries)) { + Try { + $admin.psbase.invoke("SetPassword", $p) + $admin.psbase.CommitChanges() + $succ = 1 + } Catch { + Start-Sleep -s 1 + } + $tried = $tried + 1 + } +} +if ($username -and $passwd) { + if ($logpath) { + "starting $loghash" | Out-File $logpath -Append -Encoding Default + ChangePassword $username $passwd 2>&1 | Out-File $logpath -Append -Encoding Default + } else { + ChangePassword $username $passwd 2>&1 | Out-Null + } +} + +` + +const WinScriptMountDisk = ` + +var MTW_GLOBAL_FSO = new ActiveXObject('Scripting.FileSystemObject'); +var MTW_SCRIPT_PATH = mtw_gen_script_path(); +var MTW_DEBUG_STREAM = null; + +function mtw_create_shell() { + return new ActiveXObject('WScript.Shell'); +} + +function mtw_gen_script_path() { + var fso = MTW_GLOBAL_FSO; + var folder = fso.GetSpecialFolder(2); // TemporaryFolder + var folder_path = folder + ''; + var script_name = []; + if (!/[\\\/]$/.test(folder_path)) { + folder_path += '\\'; + } + for (var i = 0; i < 5; i++) { + script_name.push(mtw_gen_random_str(6)); + } + return folder_path + script_name.join('-'); +} + +function mtw_gen_random_str(length) { + var offset, result = []; + var charcode_base = 'A'.charCodeAt(0); + for (var i = 0; i < length; i++) { + offset = Math.floor(Math.random() * 26); + result.push(String.fromCharCode(charcode_base + offset)); + } + return result.join(''); +} + +function mtw_prepare_debug(file_path) { + var fso = new ActiveXObject('Scripting.FileSystemObject'); + var stream = fso.OpenTextFile(file_path, 8, true); // 8: ForAppending + + stream.WriteLine(''); + stream.WriteLine('================ ' + new Date() + ' ================'); + stream.WriteLine(''); + + MTW_DEBUG_STREAM = stream; +} + +function mtw_append_debug(cmd_lines, result_lines) { + var i, len, stream = MTW_DEBUG_STREAM; + + if (!stream) return; + + stream.WriteLine(''); + stream.WriteLine('---------- script:'); + for (i = 0, len = cmd_lines.length; i < len; i++) { + stream.WriteLine(cmd_lines[i]); + } + + stream.WriteLine('---------- result:'); + for (i = 0, len = result_lines.length; i < len; i++) { + stream.WriteLine(result_lines[i]); + } +} + +function mtw_execute_diskpart(cmd_lines) { + var result_lines = []; + var fso = MTW_GLOBAL_FSO; + var stream, shell, exec_cmd; + + cmd_lines.push('exit'); + stream = fso.CreateTextFile(MTW_SCRIPT_PATH, true); + for (var i = 0, len = cmd_lines.length; i < len; i++) { + stream.WriteLine(cmd_lines[i]); + } + stream.close(); + + shell = mtw_create_shell(); + exec_cmd = shell.Exec('diskpart /s ' + MTW_SCRIPT_PATH); + while (exec_cmd.Status == 0) { + WScript.Sleep(100); + } + stream = exec_cmd.StdOut; + while (!stream.AtEndOfStream) { + result_lines.push(stream.ReadLine()); + } + fso.DeleteFile(MTW_SCRIPT_PATH); + + mtw_append_debug(cmd_lines, result_lines); + + return result_lines; +} + +function mtw_get_disk_list() { + var result_lines, line, match, disk_no; + var disk_list = []; + + result_lines = mtw_execute_diskpart(['list disk']); + for (var i = 0, len = result_lines.length; i < len; i++) { + line = result_lines[i]; + /* + Disk ### Status Size Free Dyn Gpt + -------- ------------- ------- ------- --- --- + Disk 0 Online 20 GB 0 B + Disk 1 Offline 10 GB 0 B + */ + match = line.match(/\s([1-9])\s\D+\s[1-9]\d*\s+[GMK]B\s+\d+\s+[GMK]?B/); + if (match) { + disk_no = match[1]; + disk_list.push({ + 'disk_no': disk_no, + 'partition_list': mtw_get_partition_list(disk_no) + }); + } + } + + return disk_list; +} + +function mtw_get_partition_list(disk_no) { + var result_lines, line, match, partition_no; + var partition_list = []; + + result_lines = mtw_execute_diskpart([ + 'select disk=' + disk_no, + 'list partition' + ]); + for (var i = 0, len = result_lines.length; i < len; i++) { + line = result_lines[i]; + /* + Partition ### Type Size Offset + ------------- ---------------- ------- ------- + Partition 1 Primary 9 GB 1024 KB + */ + match = line.match(/\s(\d)\s\D+\s[1-9]\d*\s+[GMK]B\s+\d+\s+[GMK]?B/); + if (match) { + partition_no = match[1]; + partition_list.push({ + 'partition_no': partition_no, + 'partition_type': mtw_get_partition_type(disk_no, partition_no) + }); + } + } + + return partition_list; +} + +var MTW_PARTITION_TYPE_NODATA = 'nodata'; +var MTW_PARTITION_TYPE_INVALID = 'invalid'; +function mtw_get_partition_type(disk_no, partition_no) { + var result_lines, line, match; + var possible_type_list = []; + + result_lines = mtw_execute_diskpart([ + 'select disk=' + disk_no, + 'select partition=' + partition_no, + 'detail partition' + ]); + for (var i = 0, len = result_lines.length; i < len; i++) { + line = result_lines[i]; + /* + Partition 1 + Type : 06 + Hidden: No + Active: No + Offset in Bytes: 1048576 + + Volume ### Ltr Label Fs Type Size Status Info + ---------- --- ----------- ----- ---------- ------- --------- -------- + * Volume 3 RAW Partition 9 GB Healthy + */ + match = line.match(/:\s*([0-9a-f]{2})\b/i); + if (match) { + possible_type_list.push(match[1]); + } + } + + switch (possible_type_list.length) { + case 0: + return MTW_PARTITION_TYPE_NODATA; + case 1: + return possible_type_list[0]; + default: + break; + } + return MTW_PARTITION_TYPE_INVALID; +} + +function mtw_get_volume_list(disk_no) { + var result_lines, line, match; + var sep_line_exist = false; + var volume_list = []; + + result_lines = mtw_execute_diskpart([ + 'select disk=' + disk_no, + 'detail disk' + ]); + for (var i = 0, len = result_lines.length; i < len; i++) { + line = result_lines[i]; + /* + Red Hat VirtIO SCSI Disk Device + Disk ID: 0004B605 + Type : SCSI + Status : Online + Path : 0 + Target : 0 + LUN ID : 0 + Location Path : PCIROOT(0)#PCI(0600)#SCSI(P00T00L00) + Current Read-only State : No + Read-only : No + Boot Disk : No + Pagefile Disk : No + Hibernation File Disk : No + Crashdump Disk : No + Clustered Disk : No + + Volume ### Ltr Label Fs Type Size Status Info + ---------- --- ----------- ----- ---------- ------- --------- -------- + Volume 3 RAW Partition 9 GB Healthy + */ + if (!sep_line_exist) { + match = line.match(/-+\s+-+\s+-+\s+-+/); + if (match) { + sep_line_exist = true; + } + } else { + match = line.match(/\s(\d)\s.+\s\d+\s+[GMK]?B/); + if (match) { + volume_list.push({'volume_no': match[1]}); + } + } + } + + return volume_list; +} + +function mtw_assign_volume_letter(volume_no_set, letter_offset) { + var result_lines, line, match, i, len; + var volume_no, letter, charcode, charcode_max, letter_set; + var volume_map = {}, volume_shift_list = [], cmd_lines = []; + + result_lines = mtw_execute_diskpart(['list volume']); + for (i = 0, len = result_lines.length; i < len; i++) { + line = result_lines[i]; + /* + Volume ### Ltr Label Fs Type Size Status Info + ---------- --- ----------- ----- ---------- ------- --------- -------- + Volume 0 D CD-ROM 0 B No Media + Volume 1 ???? NTFS Partition 100 MB Healthy System + Volume 2 C NTFS Partition 19 GB Healthy Boot + Volume 3 RAW Partition 9 GB Healthy + */ + match = line.match(/\s(\d)\s+([D-Z])\s.+\d+\s+[GMK]?B/i); + if (match) { + volume_no = match[1]; + letter = match[2].toUpperCase(); + if (volume_map.hasOwnProperty(letter)) { + return false; + } + volume_map[letter] = volume_no; + } + } + + charcode = 'D'.charCodeAt(0); + charcode += letter_offset; + letter_set = String.fromCharCode(charcode); + if (volume_map.hasOwnProperty(letter_set) && volume_map[letter_set] == volume_no_set) { + return true; + } + charcode_max = 'Z'.charCodeAt(0); + while (true) { + letter = String.fromCharCode(charcode); + if (!volume_map.hasOwnProperty(letter)) { + break; + } + if (charcode >= charcode_max) { + return false; + } + volume_shift_list.push({ + 'volume_no': volume_map[letter], + 'charcode_next': charcode + 1 + }); + charcode++; + } + if (volume_shift_list.length > 0) { + volume_shift_list.sort(function(a, b) { + return b.charcode_next - a.charcode_next; + }); + for (i = 0, len = volume_shift_list.length; i < len; i++) { + volume_no = volume_shift_list[i].volume_no; + charcode = volume_shift_list[i].charcode_next; + cmd_lines.push( + 'select volume=' + volume_no, + 'assign letter=' + String.fromCharCode(charcode) + ); + } + } + cmd_lines.push( + 'select volume=' + volume_no_set, + 'assign letter=' + letter_set + ); + mtw_execute_diskpart(cmd_lines); + + return true; +} + +function mtw_wait_loop(total_ms, step_ms, callback) { + while (true) { + if (callback()) { + break; + } + if (total_ms < step_ms) { + break; + } + total_ms -= step_ms; + WScript.Sleep(step_ms); + } +} + +function mtw_get_disk_list_wait() { + var disk_list_ret = []; + + /* http://support.microsoft.com/kb/870912 */ + mtw_wait_loop(5000, 500, function() { + var i, j, disk_list, disk, partition; + disk_list = mtw_get_disk_list(); + for (i = 0; i < disk_list.length; i++) { + disk = disk_list[i]; + for (j = 0; j < disk.partition_list.length; j++) { + partition = disk.partition_list[j]; + if (partition.partition_type == MTW_PARTITION_TYPE_NODATA) { + return false; + } + } + } + disk_list_ret = disk_list; + return true; + }); + + return disk_list_ret; +} + +function mtw_get_volume_list_wait(disk_no) { + var volume_list_ret = []; + + mtw_wait_loop(5000, 500, function() { + var volume_list = mtw_get_volume_list(disk_no); + if (volume_list.length > 0) { + volume_list_ret = volume_list; + return true; + } + return false; + }); + + return volume_list_ret; +} + +function mtw_mount_disk() { + var disk_list, disk_list_mounted, disk, partition, volume_list; + var do_mount, do_create, do_delete; + var cmd_lines, letter_offset = 0; + + disk_list = mtw_get_disk_list_wait(); + disk_list_mounted = []; + for (var i = 0, len = disk_list.length; i < len; i++) { + disk = disk_list[i]; + partition = null; + do_mount = do_create = do_delete = false; + if (disk.partition_list.length == 0) { + do_mount = true; + do_create = true; + } else if (disk.partition_list.length == 1) { + partition = disk.partition_list[0]; + switch (partition.partition_type) { + case '06': // DOS 3.31+ 16-bit FAT (over 32M) + case '07': // Windows NT NTFS + do_mount = true; + break; + case '83': // Linux native partition + do_mount = true; + do_delete = true; + do_create = true; + break; + default: + break; + } + } + if (!do_mount) { + continue; + } + cmd_lines = [ + 'select disk=' + disk.disk_no, + 'online disk', + 'attributes disk clear readonly' + ]; + mtw_execute_diskpart(cmd_lines); + if (do_create) { + cmd_lines = ['select disk=' + disk.disk_no]; + if (partition && do_delete) { + cmd_lines.push( + 'select partition=' + partition.partition_no, + 'delete partition' + ); + } + cmd_lines.push('create partition primary'); + mtw_execute_diskpart(cmd_lines); + } + disk_list_mounted.push(disk); + } + + for (i = 0, len = disk_list_mounted.length; i < len; i++) { + disk = disk_list_mounted[i]; + if (i == 0) { + volume_list = mtw_get_volume_list_wait(disk.disk_no); + } else { + volume_list = mtw_get_volume_list(disk.disk_no); + } + if (volume_list.length == 1) { + mtw_assign_volume_letter(volume_list[0].volume_no, letter_offset); + letter_offset += 1; + } + } +} + +function mtw_main() { + var exec_helper, args = WScript.Arguments, debug_path = ''; + + for (var i = 0, len = args.length; i < len; i++) { + if (args(i) == '--debug') { + if (i < len) { + i += 1; + debug_path = args(i); + } + } + } + + if (debug_path) { + mtw_prepare_debug(debug_path); + } + + /* http://support.microsoft.com/kb/937252 */ + exec_helper = mtw_create_shell().Exec('diskpart'); + try { + mtw_mount_disk(); + } catch (e) { + // nothing + } + exec_helper.StdIn.WriteLine('exit'); + while (exec_helper.Status == 0) { + WScript.Sleep(100); + } +} + +mtw_main(); + +` diff --git a/pkg/hostman/guestman/guestman.go b/pkg/hostman/guestman/guestman.go index e80fe56efd..81e7887d9d 100644 --- a/pkg/hostman/guestman/guestman.go +++ b/pkg/hostman/guestman/guestman.go @@ -210,7 +210,7 @@ func (m *SGuestManager) DoDeploy(ctx context.Context, sid string, body jsonutils } // TODO publicKey := sshkeys.GetKeys(body) - deploys, _ := body.Get("deploys") + deploys, _ := body.GetArray("deploys") password, _ := body.GetString("password") resetPassword := jsonutils.QueryBoolean(body, "reset_password", false) if resetPassword && len(password) == 0 { diff --git a/pkg/hostman/storageman/core.go b/pkg/hostman/storageman/core.go index eddebcc9a3..76f5a96538 100644 --- a/pkg/hostman/storageman/core.go +++ b/pkg/hostman/storageman/core.go @@ -70,7 +70,7 @@ func (d *SBaseDisk) DeployGuestFs( log.Infof("Kvm Disk Connect Success !!") if root := kvmDisk.Mount(); root != nil { defer kvmDisk.Umount(root) - return root.DeployGuestFs(guestDesc, deployInfo) + return root.DeployGuestFs(root, guestDesc, deployInfo) } } return nil, fmt.Errorf("Kvm disk connect or mount error") diff --git a/pkg/hostman/utils.go b/pkg/hostman/utils.go index bfc5240ac2..f81c43e0c6 100644 --- a/pkg/hostman/utils.go +++ b/pkg/hostman/utils.go @@ -7,6 +7,7 @@ import ( "os" "os/exec" "path" + "regexp" "strings" "time" @@ -31,7 +32,37 @@ func CommandWithTimeout(timeout int, cmds ...string) *exec.Cmd { // file utils -func FilePutContents(filename string, context string, modAppend bool) error { +// TODO: test +func Cleandir(sPath string, keepdir bool) error { + if f, _ := os.Lstat(sPath); f == nil || f.Mode()&os.ModeSymlink == os.ModeSymlink { + return nil + } + files, _ := ioutil.ReadDir(sPath) + for _, file := range files { + fp := path.Join(sPath, file.Name()) + if f, _ := os.Lstat(fp); f.Mode()&os.ModeSymlink == os.ModeSymlink { + if !keepdir { + if err := os.Remove(fp); err != nil { + return err + } + } + } else if f.IsDir() { + Cleandir(fp, keepdir) + if !keepdir { + if err := os.Remove(fp); err != nil { + return err + } + } + } else { + if err := os.Remove(fp); err != nil { + return err + } + } + } + return nil +} + +func FilePutContents(filename string, content string, modAppend bool) error { var mode = os.O_WRONLY | os.O_CREATE if modAppend { mode = mode | os.O_APPEND @@ -41,7 +72,7 @@ func FilePutContents(filename string, context string, modAppend bool) error { return err } defer fd.Close() - _, err = fd.WriteString(context) + _, err = fd.WriteString(content) return err } @@ -157,3 +188,32 @@ func CleanFailedMountpoints() { } } } + +type HostsFile map[string][]string + +func (hf HostsFile) Parse(content string) { + lines := strings.Split(content, "\n") + for _, line := range lines { + data := regexp.MustCompile(`\s+`).Split(line, -1) + for len(data) > 0 && data[len(data)-1] == "" { + data = data[:len(data)-1] + } + if len(data) > 1 { + hf[data[0]] = data[1:] + } + } +} + +func (hf HostsFile) Add(name string, value ...string) { + hf[name] = value +} + +func (hf HostsFile) String() string { + var ret = "" + for k, v := range hf { + if len(v) > 0 { + ret += fmt.Sprintf("%s\t%s\n", k, strings.Join(v, "\t")) + } + } + return ret +}