vm/gce: windows support

Support custom pre-created images.
Support non-root user.
Use dir instead of pwd on windows.
Don't use sudo on windows.
This commit is contained in:
Dmitry Vyukov 2017-09-27 17:07:31 +02:00
parent fd98417f4d
commit 9fc15c7ea3
5 changed files with 69 additions and 49 deletions

View File

@ -27,7 +27,8 @@ type Config struct {
Kernel_Src string // kernel source directory
Tag string // arbitrary optional tag that is saved along with crash reports (e.g. branch/commit)
Image string // linux image for VMs
Sshkey string // root ssh key for the image (may be empty for some VM types)
Sshkey string // ssh key for the image (may be empty for some VM types)
Ssh_User string // ssh user ("root" by default)
Hub_Client string
Hub_Addr string
@ -81,6 +82,7 @@ func LoadFile(filename string) (*Config, error) {
func DefaultValues() *Config {
return &Config{
Ssh_User: "root",
Cover: true,
Reproduce: true,
Sandbox: "setuid",
@ -271,7 +273,8 @@ func CreateVMEnv(cfg *Config, debug bool) *vm.Env {
Arch: cfg.TargetVMArch,
Workdir: cfg.Workdir,
Image: cfg.Image,
Sshkey: cfg.Sshkey,
SshKey: cfg.Sshkey,
SshUser: cfg.Ssh_User,
Debug: debug,
Config: cfg.VM,
}

View File

@ -39,16 +39,17 @@ type Config struct {
Count int // number of VMs to use
Machine_Type string // GCE machine type (e.g. "n1-highcpu-2")
GCS_Path string // GCS path to upload image
GCE_Image string // Pre-created GCE image to use
}
type Pool struct {
env *vmimpl.Env
cfg *Config
GCE *gce.Context
gceImage string
env *vmimpl.Env
cfg *Config
GCE *gce.Context
}
type instance struct {
env *vmimpl.Env
cfg *Config
GCE *gce.Context
debug bool
@ -84,9 +85,12 @@ func ctor(env *vmimpl.Env) (vmimpl.Pool, error) {
if cfg.Machine_Type == "" {
return nil, fmt.Errorf("machine_type parameter is empty")
}
if cfg.GCS_Path == "" {
if cfg.GCE_Image == "" && cfg.GCS_Path == "" {
return nil, fmt.Errorf("gcs_path parameter is empty")
}
if cfg.GCE_Image != "" && env.Image != "" {
return nil, fmt.Errorf("both image and gce_image are specified")
}
GCE, err := gce.NewContext()
if err != nil {
@ -95,24 +99,25 @@ func ctor(env *vmimpl.Env) (vmimpl.Pool, error) {
Logf(0, "GCE initialized: running on %v, internal IP %v, project %v, zone %v",
GCE.Instance, GCE.InternalIP, GCE.ProjectID, GCE.ZoneID)
gcsImage := filepath.Join(cfg.GCS_Path, env.Name+"-image.tar.gz")
Logf(0, "uploading image to %v...", gcsImage)
if err := uploadImageToGCS(env.Image, gcsImage); err != nil {
return nil, err
}
gceImage := env.Name
Logf(0, "creating GCE image %v...", gceImage)
if err := GCE.DeleteImage(gceImage); err != nil {
return nil, fmt.Errorf("failed to delete GCE image: %v", err)
}
if err := GCE.CreateImage(gceImage, gcsImage); err != nil {
return nil, fmt.Errorf("failed to create GCE image: %v", err)
if cfg.GCE_Image == "" {
cfg.GCE_Image = env.Name
gcsImage := filepath.Join(cfg.GCS_Path, env.Name+"-image.tar.gz")
Logf(0, "uploading image to %v...", gcsImage)
if err := uploadImageToGCS(env.Image, gcsImage); err != nil {
return nil, err
}
Logf(0, "creating GCE image %v...", cfg.GCE_Image)
if err := GCE.DeleteImage(cfg.GCE_Image); err != nil {
return nil, fmt.Errorf("failed to delete GCE image: %v", err)
}
if err := GCE.CreateImage(cfg.GCE_Image, gcsImage); err != nil {
return nil, fmt.Errorf("failed to create GCE image: %v", err)
}
}
pool := &Pool{
cfg: cfg,
env: env,
GCE: GCE,
gceImage: gceImage,
cfg: cfg,
env: env,
GCE: GCE,
}
return pool, nil
}
@ -139,7 +144,7 @@ func (pool *Pool) Create(workdir string, index int) (vmimpl.Instance, error) {
return nil, err
}
Logf(0, "creating instance: %v", name)
ip, err := pool.GCE.CreateInstance(name, pool.cfg.Machine_Type, pool.gceImage, string(gceKeyPub))
ip, err := pool.GCE.CreateInstance(name, pool.cfg.Machine_Type, pool.cfg.GCE_Image, string(gceKeyPub))
if err != nil {
return nil, err
}
@ -150,19 +155,20 @@ func (pool *Pool) Create(workdir string, index int) (vmimpl.Instance, error) {
pool.GCE.DeleteInstance(name, true)
}
}()
sshKey := pool.env.Sshkey
sshUser := "root"
sshKey := pool.env.SshKey
sshUser := pool.env.SshUser
if sshKey == "" {
// Assuming image supports GCE ssh fanciness.
sshKey = gceKey
sshUser = "syzkaller"
}
Logf(0, "wait instance to boot: %v (%v)", name, ip)
if err := waitInstanceBoot(pool.env.Debug, ip, sshKey, sshUser); err != nil {
if err := pool.waitInstanceBoot(ip, sshKey, sshUser); err != nil {
return nil, err
}
ok = true
inst := &instance{
env: pool.env,
cfg: pool.cfg,
debug: pool.env.Debug,
GCE: pool.GCE,
@ -272,8 +278,10 @@ func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command strin
sshRpipe.Close()
return nil, nil, err
}
if inst.sshUser != "root" {
command = fmt.Sprintf("sudo bash -c '%v'", command)
if inst.env.OS == "linux" {
if inst.sshUser != "root" {
command = fmt.Sprintf("sudo bash -c '%v'", command)
}
}
args := append(sshArgs(inst.debug, inst.sshKey, "-p", 22), inst.sshUser+"@"+inst.name, command)
ssh := exec.Command("ssh", args...)
@ -334,13 +342,17 @@ func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command strin
return merger.Output, errc, nil
}
func waitInstanceBoot(debug bool, ip, sshKey, sshUser string) error {
func (pool *Pool) waitInstanceBoot(ip, sshKey, sshUser string) error {
pwd := "pwd"
if pool.env.OS == "windows" {
pwd = "dir"
}
for i := 0; i < 100; i++ {
if !vmimpl.SleepInterruptible(5 * time.Second) {
return fmt.Errorf("shutdown in progress")
}
args := append(sshArgs(debug, sshKey, "-p", 22), sshUser+"@"+ip, "pwd")
if _, err := runCmd(debug, "ssh", args...); err == nil {
args := append(sshArgs(pool.env.Debug, sshKey, "-p", 22), sshUser+"@"+ip, pwd)
if _, err := runCmd(pool.env.Debug, "ssh", args...); err == nil {
return nil
}
}
@ -425,7 +437,7 @@ func sshArgs(debug bool, sshKey, portArg string, port int) []string {
"-o", "BatchMode=yes",
"-o", "IdentitiesOnly=yes",
"-o", "StrictHostKeyChecking=no",
"-o", "ConnectTimeout=5",
"-o", "ConnectTimeout=10",
}
if debug {
args = append(args, "-v")

View File

@ -54,8 +54,8 @@ func ctor(env *vmimpl.Env) (vmimpl.Pool, error) {
return nil, fmt.Errorf("config param target_dir is empty")
}
// sshkey is optional
if env.Sshkey != "" && !osutil.IsExist(env.Sshkey) {
return nil, fmt.Errorf("ssh key '%v' does not exist", env.Sshkey)
if env.SshKey != "" && !osutil.IsExist(env.SshKey) {
return nil, fmt.Errorf("ssh key '%v' does not exist", env.SshKey)
}
if env.Debug {
cfg.Targets = cfg.Targets[:1]
@ -74,10 +74,10 @@ func (pool *Pool) Count() int {
func (pool *Pool) Create(workdir string, index int) (vmimpl.Instance, error) {
inst := &instance{
cfg: pool.cfg,
target: pool.cfg.Targets[index],
target: pool.env.SshUser + "@" + pool.cfg.Targets[index],
closed: make(chan bool),
debug: pool.env.Debug,
sshkey: pool.env.Sshkey,
sshkey: pool.env.SshKey,
}
closeInst := inst
defer func() {
@ -120,7 +120,7 @@ func (inst *instance) ssh(command string) ([]byte, error) {
return nil, err
}
args := append(inst.sshArgs("-p"), "root@"+inst.target, command)
args := append(inst.sshArgs("-p"), inst.target, command)
if inst.debug {
Logf(0, "running command: ssh %#v", args)
}
@ -230,7 +230,7 @@ func (inst *instance) Copy(hostSrc string) (string, error) {
baseName := filepath.Base(hostSrc)
vmDst := filepath.Join(inst.cfg.Target_Dir, baseName)
inst.ssh("pkill -9 '" + baseName + "'; rm -f '" + vmDst + "'")
args := append(inst.sshArgs("-P"), hostSrc, "root@"+inst.target+":"+vmDst)
args := append(inst.sshArgs("-P"), hostSrc, inst.target+":"+vmDst)
cmd := exec.Command("scp", args...)
if inst.debug {
Logf(0, "running command: scp %#v", args)
@ -257,7 +257,7 @@ func (inst *instance) Copy(hostSrc string) (string, error) {
}
func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command string) (<-chan []byte, <-chan error, error) {
args := append(inst.sshArgs("-p"), "root@"+inst.target)
args := append(inst.sshArgs("-p"), inst.target)
dmesg, err := vmimpl.OpenRemoteConsole("ssh", args...)
if err != nil {
return nil, nil, err
@ -275,7 +275,7 @@ func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command strin
proxy := fmt.Sprintf("%v:127.0.0.1:%v", inst.port, inst.port)
args = append(args, "-R", proxy)
}
args = append(args, "root@"+inst.target, "cd "+inst.cfg.Target_Dir+" && exec "+command)
args = append(args, inst.target, "cd "+inst.cfg.Target_Dir+" && exec "+command)
Logf(0, "running command: ssh %#v", args)
if inst.debug {
Logf(0, "running command: ssh %#v", args)

View File

@ -51,6 +51,7 @@ type instance struct {
debug bool
workdir string
sshkey string
sshuser string
port int
rpipe io.ReadCloser
wpipe io.WriteCloser
@ -118,8 +119,8 @@ func ctor(env *vmimpl.Env) (vmimpl.Pool, error) {
if !osutil.IsExist(env.Image) {
return nil, fmt.Errorf("image file '%v' does not exist", env.Image)
}
if !osutil.IsExist(env.Sshkey) {
return nil, fmt.Errorf("ssh key '%v' does not exist", env.Sshkey)
if !osutil.IsExist(env.SshKey) {
return nil, fmt.Errorf("ssh key '%v' does not exist", env.SshKey)
}
}
if cfg.Cpu <= 0 || cfg.Cpu > 1024 {
@ -142,9 +143,11 @@ func (pool *Pool) Count() int {
}
func (pool *Pool) Create(workdir string, index int) (vmimpl.Instance, error) {
sshkey := pool.env.Sshkey
sshkey := pool.env.SshKey
sshuser := pool.env.SshUser
if pool.env.Image == "9p" {
sshkey = filepath.Join(workdir, "key")
sshuser = "root"
keygen := exec.Command("ssh-keygen", "-t", "rsa", "-b", "2048", "-N", "", "-C", "", "-f", sshkey)
if out, err := keygen.CombinedOutput(); err != nil {
return nil, fmt.Errorf("failed to execute ssh-keygen: %v\n%s", err, out)
@ -156,7 +159,7 @@ func (pool *Pool) Create(workdir string, index int) (vmimpl.Instance, error) {
}
for i := 0; ; i++ {
inst, err := pool.ctor(workdir, sshkey, index)
inst, err := pool.ctor(workdir, sshkey, sshuser, index)
if err == nil {
return inst, nil
}
@ -167,13 +170,14 @@ func (pool *Pool) Create(workdir string, index int) (vmimpl.Instance, error) {
}
}
func (pool *Pool) ctor(workdir, sshkey string, index int) (vmimpl.Instance, error) {
func (pool *Pool) ctor(workdir, sshkey, sshuser string, index int) (vmimpl.Instance, error) {
inst := &instance{
cfg: pool.cfg,
image: pool.env.Image,
debug: pool.env.Debug,
workdir: workdir,
sshkey: sshkey,
sshuser: sshuser,
}
closeInst := inst
defer func() {
@ -383,7 +387,7 @@ func (inst *instance) Copy(hostSrc string) (string, error) {
basePath = "/tmp"
}
vmDst := filepath.Join(basePath, filepath.Base(hostSrc))
args := append(inst.sshArgs("-P"), hostSrc, "root@localhost:"+vmDst)
args := append(inst.sshArgs("-P"), hostSrc, inst.sshuser+"@localhost:"+vmDst)
cmd := exec.Command("scp", args...)
if inst.debug {
Logf(0, "running command: scp %#v", args)
@ -416,7 +420,7 @@ func (inst *instance) Run(timeout time.Duration, stop <-chan bool, command strin
}
inst.merger.Add("ssh", rpipe)
args := append(inst.sshArgs("-p"), "root@localhost", command)
args := append(inst.sshArgs("-p"), inst.sshuser+"@localhost", command)
if inst.debug {
Logf(0, "running command: ssh %#v", args)
}

View File

@ -50,7 +50,8 @@ type Env struct {
Arch string // target arch
Workdir string
Image string
Sshkey string
SshKey string
SshUser string
Debug bool
Config []byte // json-serialized VM-type-specific config
}