Merge pull request #7 from chengsun/master

Improvements and bug fixes
This commit is contained in:
Rob Dobson 2014-12-11 18:20:13 +00:00
commit 85e3b00e5a
9 changed files with 163 additions and 114 deletions

View File

@ -61,7 +61,10 @@ type config struct {
SSHUser string `mapstructure:"ssh_username"` SSHUser string `mapstructure:"ssh_username"`
SSHKeyPath string `mapstructure:"ssh_key_path"` SSHKeyPath string `mapstructure:"ssh_key_path"`
OutputDir string `mapstructure:"output_directory"` OutputDir string `mapstructure:"output_directory"`
ExportFormat string `mapstructure:"export_format"`
KeepInstance string `mapstructure:"keep_instance"`
tpl *packer.ConfigTemplate tpl *packer.ConfigTemplate
} }
@ -84,12 +87,12 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error
} }
self.config.tpl, err = packer.NewConfigTemplate() self.config.tpl, err = packer.NewConfigTemplate()
if err != nil { if err != nil {
return nil, err return nil, err
} }
self.config.tpl.UserVars = self.config.PackerUserVars
// Set default vaules // Set default values
if self.config.HostPortMin == 0 { if self.config.HostPortMin == 0 {
self.config.HostPortMin = 5900 self.config.HostPortMin = 5900
@ -119,10 +122,39 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error
self.config.RawSSHWaitTimeout = "200m" self.config.RawSSHWaitTimeout = "200m"
} }
if self.config.InstanceMemory == "" {
self.config.InstanceMemory = "1024000000"
}
if self.config.CloneTemplate == "" {
self.config.CloneTemplate = "Other install media"
}
if self.config.OutputDir == "" { if self.config.OutputDir == "" {
self.config.OutputDir = fmt.Sprintf("output-%s", self.config.PackerBuildName) self.config.OutputDir = fmt.Sprintf("output-%s", self.config.PackerBuildName)
} }
if self.config.ExportFormat == "" {
self.config.ExportFormat = "xva"
}
if self.config.KeepInstance == "" {
self.config.KeepInstance = "never"
}
if len(self.config.PlatformArgs) == 0 {
pargs := make(map[string]string)
pargs["viridian"] = "false"
pargs["nx"] = "true"
pargs["pae"] = "true"
pargs["apic"] = "true"
pargs["timeoffset"] = "0"
pargs["acpi"] = "1"
self.config.PlatformArgs = pargs
}
// Template substitution
templates := map[string]*string{ templates := map[string]*string{
"username": &self.config.Username, "username": &self.config.Username,
"password": &self.config.Password, "password": &self.config.Password,
@ -146,6 +178,8 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error
"ssh_password": &self.config.SSHPassword, "ssh_password": &self.config.SSHPassword,
"ssh_key_path": &self.config.SSHKeyPath, "ssh_key_path": &self.config.SSHKeyPath,
"output_directory": &self.config.OutputDir, "output_directory": &self.config.OutputDir,
"export_format": &self.config.ExportFormat,
"keep_instance": &self.config.KeepInstance,
} }
for n, ptr := range templates { for n, ptr := range templates {
@ -156,6 +190,8 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error
} }
} }
// Validation
/* /*
if self.config.IsoUrl == "" { if self.config.IsoUrl == "" {
errs = packer.MultiErrorAppend( errs = packer.MultiErrorAppend(
@ -166,7 +202,7 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error
self.config.BootWait, err = time.ParseDuration(self.config.RawBootWait) self.config.BootWait, err = time.ParseDuration(self.config.RawBootWait)
if err != nil { if err != nil {
errs = packer.MultiErrorAppend( errs = packer.MultiErrorAppend(
errs, errors.New("Failed to parse boot_wait.")) errs, fmt.Errorf("Failed to parse boot_wait: %s", err))
} }
self.config.SSHWaitTimeout, err = time.ParseDuration(self.config.RawSSHWaitTimeout) self.config.SSHWaitTimeout, err = time.ParseDuration(self.config.RawSSHWaitTimeout)
@ -223,17 +259,23 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error
errs, errors.New("An instance name must be specified.")) errs, errors.New("An instance name must be specified."))
} }
if self.config.InstanceMemory == "" {
self.config.InstanceMemory = "1024000000"
}
if self.config.RootDiskSize == "" { if self.config.RootDiskSize == "" {
errs = packer.MultiErrorAppend( errs = packer.MultiErrorAppend(
errs, errors.New("A root disk size must be specified.")) errs, errors.New("A root disk size must be specified."))
} }
if self.config.CloneTemplate == "" { switch self.config.ExportFormat {
self.config.CloneTemplate = "Other install media" case "xva", "vdi_raw":
default:
errs = packer.MultiErrorAppend(
errs, errors.New("export_format must be one of 'xva', 'vdi_raw'"))
}
switch self.config.KeepInstance {
case "always", "never", "on_success":
default:
errs = packer.MultiErrorAppend(
errs, errors.New("keep_instance must be one of 'always', 'never', 'on_success'"))
} }
/* /*
@ -243,17 +285,6 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error
} }
*/ */
if len(self.config.PlatformArgs) == 0 {
pargs := make(map[string]string)
pargs["viridian"] = "false"
pargs["nx"] = "true"
pargs["pae"] = "true"
pargs["apic"] = "true"
pargs["timeoffset"] = "0"
pargs["acpi"] = "1"
self.config.PlatformArgs = pargs
}
if self.config.HTTPPortMin > self.config.HTTPPortMax { if self.config.HTTPPortMin > self.config.HTTPPortMax {
errs = packer.MultiErrorAppend( errs = packer.MultiErrorAppend(
errs, errors.New("the HTTP min port must be less than the max")) errs, errors.New("the HTTP min port must be less than the max"))
@ -392,6 +423,23 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa
return artifact, nil return artifact, nil
} }
// all steps should check config.ShouldKeepInstance first before cleaning up
func (cfg config) ShouldKeepInstance(state multistep.StateBag) bool {
switch cfg.KeepInstance {
case "always":
return true
case "never":
return false
case "on_success":
// only keep instance if build was successful
_, cancelled := state.GetOk(multistep.StateCancelled)
_, halted := state.GetOk(multistep.StateHalted)
return !(cancelled || halted)
default:
panic(fmt.Sprintf("Unknown keep_instance value '%s'", cfg.KeepInstance))
}
}
func (self *Builder) Cancel() { func (self *Builder) Cancel() {
if self.runner != nil { if self.runner != nil {
log.Println("Cancelling the step runner...") log.Println("Cancelling the step runner...")

View File

@ -0,0 +1,25 @@
package xenserver
import (
"fmt"
"log"
"net"
)
// FindPort finds and starts listening on a port in the range [portMin, portMax]
// returns the listener and the port number on success, or nil, 0 on failure
func FindPort(portMin uint, portMax uint) (net.Listener, uint) {
log.Printf("Looking for an available port between %d and %d", portMin, portMax)
for port := portMin; port <= portMax; port++ {
log.Printf("Trying port: %d", port)
l, err := net.Listen("tcp", fmt.Sprintf(":%d", port))
if err == nil {
return l, port
} else {
log.Printf("Port %d unavailable: %s", port, err.Error())
}
}
return nil, 0
}

View File

@ -84,19 +84,22 @@ func execute_ssh_cmd(cmd, host, port, username, password string) (stdout string,
} }
func forward(local_conn net.Conn, config *gossh.ClientConfig, server, remote_dest string, remote_port uint) error { func forward(local_conn net.Conn, config *gossh.ClientConfig, server, remote_dest string, remote_port uint) error {
defer local_conn.Close()
ssh_client_conn, err := gossh.Dial("tcp", server+":22", config) ssh_client_conn, err := gossh.Dial("tcp", server+":22", config)
if err != nil { if err != nil {
log.Printf("local ssh.Dial error: %s", err) log.Printf("local ssh.Dial error: %s", err)
return err return err
} }
defer ssh_client_conn.Close()
remote_loc := fmt.Sprintf("%s:%d", remote_dest, remote_port) remote_loc := fmt.Sprintf("%s:%d", remote_dest, remote_port)
ssh_conn, err := ssh_client_conn.Dial("tcp", remote_loc) ssh_conn, err := ssh_client_conn.Dial("tcp", remote_loc)
if err != nil { if err != nil {
log.Printf("ssh.Dial error: %s", err) log.Printf("ssh.Dial error: %s", err)
ssh_client_conn.Close()
return err return err
} }
defer ssh_conn.Close()
txDone := make(chan struct{}) txDone := make(chan struct{})
rxDone := make(chan struct{}) rxDone := make(chan struct{})
@ -117,12 +120,8 @@ func forward(local_conn net.Conn, config *gossh.ClientConfig, server, remote_des
close(rxDone) close(rxDone)
}() }()
go func() { <-txDone
<-txDone <-rxDone
<-rxDone
ssh_client_conn.Close()
ssh_conn.Close()
}()
return nil return nil
} }

View File

@ -214,6 +214,11 @@ func (self *stepCreateInstance) Run(state multistep.StateBag) multistep.StepActi
} }
func (self *stepCreateInstance) Cleanup(state multistep.StateBag) { func (self *stepCreateInstance) Cleanup(state multistep.StateBag) {
config := state.Get("config").(config)
if config.ShouldKeepInstance(state) {
return
}
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
if self.instance != nil { if self.instance != nil {

View File

@ -4,8 +4,6 @@ import (
"fmt" "fmt"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"log"
"net"
) )
type stepForwardPortOverSSH struct { type stepForwardPortOverSSH struct {
@ -25,32 +23,15 @@ func (self *stepForwardPortOverSSH) Run(state multistep.StateBag) multistep.Step
// Find a free local port: // Find a free local port:
log.Printf("Looking for an available port between %d and %d", l, sshHostPort := FindPort(self.HostPortMin, self.HostPortMax)
self.HostPortMin,
self.HostPortMax)
var sshHostPort uint if l == nil || sshHostPort == 0 {
var foundPort bool ui.Error("Error: unable to find free host port. Try providing a larger range [host_port_min, host_port_max]")
foundPort = false
for i := self.HostPortMin; i < self.HostPortMax; i++ {
sshHostPort = i
log.Printf("Trying port: %d", sshHostPort)
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%d", sshHostPort))
if err == nil {
l.Close()
foundPort = true
break
}
}
if !foundPort {
ui.Error("Error: unable to find free host port. Try providing a larger range")
return multistep.ActionHalt return multistep.ActionHalt
} }
l.Close()
ui.Say(fmt.Sprintf("Creating a local port forward over SSH on local port %d", sshHostPort)) ui.Say(fmt.Sprintf("Creating a local port forward over SSH on local port %d", sshHostPort))
remotePort, _ := self.RemotePort(state) remotePort, _ := self.RemotePort(state)

View File

@ -6,8 +6,6 @@ import (
"fmt" "fmt"
"github.com/mitchellh/multistep" "github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer" "github.com/mitchellh/packer/packer"
"log"
"math/rand"
"net" "net"
"net/http" "net/http"
) )
@ -36,32 +34,18 @@ func (s *stepHTTPServer) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue return multistep.ActionContinue
} }
// Find an available TCP port for our HTTP server s.l, httpPort = FindPort(config.HTTPPortMin, config.HTTPPortMax)
var httpAddr string
portRange := int(config.HTTPPortMax - config.HTTPPortMin)
for {
var err error
var offset uint = 0
if portRange > 0 { if s.l == nil || httpPort == 0 {
// Intn will panic if portRange == 0, so we do a check. ui.Error("Error: unable to find free HTTP server port. Try providing a larger range [http_port_min, http_port_max]")
offset = uint(rand.Intn(portRange)) return multistep.ActionHalt
}
httpPort = offset + config.HTTPPortMin
httpAddr = fmt.Sprintf(":%d", httpPort)
log.Printf("Trying port: %d", httpPort)
s.l, err = net.Listen("tcp", httpAddr)
if err == nil {
break
}
} }
ui.Say(fmt.Sprintf("Starting HTTP server on port %d", httpPort)) ui.Say(fmt.Sprintf("Starting HTTP server on port %d", httpPort))
// Start the HTTP server and run it in the background // Start the HTTP server and run it in the background
fileServer := http.FileServer(http.Dir(config.HTTPDir)) fileServer := http.FileServer(http.Dir(config.HTTPDir))
server := &http.Server{Addr: httpAddr, Handler: fileServer} server := &http.Server{Addr: fmt.Sprintf(":%d", httpPort), Handler: fileServer}
go server.Serve(s.l) go server.Serve(s.l)
// Save the address into the state so it can be accessed in the future // Save the address into the state so it can be accessed in the future

View File

@ -65,44 +65,52 @@ func (stepShutdownAndExport) Run(state multistep.StateBag) multistep.StepAction
return multistep.ActionHalt return multistep.ActionHalt
} }
//Export the VM switch config.ExportFormat {
case "xva":
// export the VM
export_url := fmt.Sprintf("https://%s/export?vm=%s&session_id=%s", export_url := fmt.Sprintf("https://%s/export?vm=%s&session_id=%s",
client.Host,
instance.Ref,
client.Session.(string),
)
export_filename := fmt.Sprintf("%s/%s.xva", config.OutputDir, config.InstanceName)
ui.Say("Getting metadata " + export_url)
downloadFile(export_url, export_filename)
disks, err := instance.GetDisks()
if err != nil {
ui.Error(fmt.Sprintf("Could not get VM disks: %s", err.Error()))
return multistep.ActionHalt
}
for _, disk := range disks {
disk_uuid, err := disk.GetUuid()
if err != nil {
ui.Error(fmt.Sprintf("Could not get disk with UUID '%s': %s", disk_uuid, err.Error()))
return multistep.ActionHalt
}
// Basic auth in URL request is required as session token is not
// accepted for some reason.
// @todo: raise with XAPI team.
disk_export_url := fmt.Sprintf("https://%s:%s@%s/export_raw_vdi?vdi=%s",
client.Username,
client.Password,
client.Host, client.Host,
disk_uuid, instance_uuid,
client.Session.(string),
) )
ui.Say("Getting " + disk_export_url) export_filename := fmt.Sprintf("%s/%s.xva", config.OutputDir, config.InstanceName)
disk_export_filename := fmt.Sprintf("%s/%s.raw", config.OutputDir, disk_uuid) ui.Say("Getting XVA " + export_url)
ui.Say("Downloading " + disk_uuid) downloadFile(export_url, export_filename)
downloadFile(disk_export_url, disk_export_filename)
case "vdi_raw":
// export the disks
disks, err := instance.GetDisks()
if err != nil {
ui.Error(fmt.Sprintf("Could not get VM disks: %s", err.Error()))
return multistep.ActionHalt
}
for _, disk := range disks {
disk_uuid, err := disk.GetUuid()
if err != nil {
ui.Error(fmt.Sprintf("Could not get disk with UUID '%s': %s", disk_uuid, err.Error()))
return multistep.ActionHalt
}
// Basic auth in URL request is required as session token is not
// accepted for some reason.
// @todo: raise with XAPI team.
disk_export_url := fmt.Sprintf("https://%s:%s@%s/export_raw_vdi?vdi=%s",
client.Username,
client.Password,
client.Host,
disk_uuid,
)
disk_export_filename := fmt.Sprintf("%s/%s.raw", config.OutputDir, disk_uuid)
ui.Say("Getting VDI " + disk_export_url)
downloadFile(disk_export_url, disk_export_filename)
}
default:
panic(fmt.Sprintf("Unknown export_format '%s'", config.ExportFormat))
} }
ui.Say("Download completed: " + config.OutputDir) ui.Say("Download completed: " + config.OutputDir)

View File

@ -63,7 +63,7 @@ func (self *stepStartOnHIMN) Run(state multistep.StateBag) multistep.StepAction
log.Printf("Ref: %s", instance.Ref) log.Printf("Ref: %s", instance.Ref)
//Check for instance.Ref in map //Check for instance.Ref in map
if vm_ip, ok := ips[himn_vif.Ref]; ok { if vm_ip, ok := ips[himn_vif.Ref]; ok && vm_ip != "" {
ui.Say("Found the VM's IP: " + vm_ip) ui.Say("Found the VM's IP: " + vm_ip)
himn_iface_ip = vm_ip himn_iface_ip = vm_ip
return true, nil return true, nil
@ -76,15 +76,13 @@ func (self *stepStartOnHIMN) Run(state multistep.StateBag) multistep.StepAction
Timeout: 100 * time.Second, Timeout: 100 * time.Second,
}.Wait(state) }.Wait(state)
if err != nil || himn_iface_ip == "" { if err != nil {
ui.Error(fmt.Sprintf("Unable to find an IP on the Host-internal management interface: %s", err.Error())) ui.Error(fmt.Sprintf("Unable to find an IP on the Host-internal management interface: %s", err.Error()))
return multistep.ActionHalt return multistep.ActionHalt
} }
if himn_iface_ip != "" { state.Put("himn_ssh_address", himn_iface_ip)
state.Put("himn_ssh_address", himn_iface_ip) ui.Say("Stored VM's IP " + himn_iface_ip)
ui.Say("Stored VM's IP " + himn_iface_ip)
}
// Wait for the VM to boot, and check we can ping this interface // Wait for the VM to boot, and check we can ping this interface

View File

@ -17,6 +17,7 @@
[ [
"<tab><wait>", "<tab><wait>",
" ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/centos6-ks.cfg<enter>" " ks=http://{{ .HTTPIP }}:{{ .HTTPPort }}/centos6-ks.cfg<enter>"
] ],
"keep_instance": "always"
}] }]
} }