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"`
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
}
@ -84,12 +87,12 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error
}
self.config.tpl, err = packer.NewConfigTemplate()
if err != nil {
return nil, err
}
self.config.tpl.UserVars = self.config.PackerUserVars
// Set default vaules
// Set default values
if self.config.HostPortMin == 0 {
self.config.HostPortMin = 5900
@ -119,10 +122,39 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error
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 == "" {
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{
"username": &self.config.Username,
"password": &self.config.Password,
@ -146,6 +178,8 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error
"ssh_password": &self.config.SSHPassword,
"ssh_key_path": &self.config.SSHKeyPath,
"output_directory": &self.config.OutputDir,
"export_format": &self.config.ExportFormat,
"keep_instance": &self.config.KeepInstance,
}
for n, ptr := range templates {
@ -156,6 +190,8 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error
}
}
// Validation
/*
if self.config.IsoUrl == "" {
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)
if err != nil {
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)
@ -223,17 +259,23 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error
errs, errors.New("An instance name must be specified."))
}
if self.config.InstanceMemory == "" {
self.config.InstanceMemory = "1024000000"
}
if self.config.RootDiskSize == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("A root disk size must be specified."))
}
if self.config.CloneTemplate == "" {
self.config.CloneTemplate = "Other install media"
switch self.config.ExportFormat {
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 {
errs = packer.MultiErrorAppend(
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
}
// 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() {
if self.runner != nil {
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 {
defer local_conn.Close()
ssh_client_conn, err := gossh.Dial("tcp", server+":22", config)
if err != nil {
log.Printf("local ssh.Dial error: %s", err)
return err
}
defer ssh_client_conn.Close()
remote_loc := fmt.Sprintf("%s:%d", remote_dest, remote_port)
ssh_conn, err := ssh_client_conn.Dial("tcp", remote_loc)
if err != nil {
log.Printf("ssh.Dial error: %s", err)
ssh_client_conn.Close()
return err
}
defer ssh_conn.Close()
txDone := 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)
}()
go func() {
<-txDone
<-rxDone
ssh_client_conn.Close()
ssh_conn.Close()
}()
<-txDone
<-rxDone
return nil
}

View File

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

View File

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

View File

@ -6,8 +6,6 @@ import (
"fmt"
"github.com/mitchellh/multistep"
"github.com/mitchellh/packer/packer"
"log"
"math/rand"
"net"
"net/http"
)
@ -36,32 +34,18 @@ func (s *stepHTTPServer) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionContinue
}
// Find an available TCP port for our HTTP server
var httpAddr string
portRange := int(config.HTTPPortMax - config.HTTPPortMin)
for {
var err error
var offset uint = 0
s.l, httpPort = FindPort(config.HTTPPortMin, config.HTTPPortMax)
if portRange > 0 {
// Intn will panic if portRange == 0, so we do a check.
offset = uint(rand.Intn(portRange))
}
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
}
if s.l == nil || httpPort == 0 {
ui.Error("Error: unable to find free HTTP server port. Try providing a larger range [http_port_min, http_port_max]")
return multistep.ActionHalt
}
ui.Say(fmt.Sprintf("Starting HTTP server on port %d", httpPort))
// Start the HTTP server and run it in the background
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)
// 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
}
//Export the VM
switch config.ExportFormat {
case "xva":
// export the VM
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,
export_url := fmt.Sprintf("https://%s/export?vm=%s&session_id=%s",
client.Host,
disk_uuid,
instance_uuid,
client.Session.(string),
)
ui.Say("Getting " + disk_export_url)
disk_export_filename := fmt.Sprintf("%s/%s.raw", config.OutputDir, disk_uuid)
ui.Say("Downloading " + disk_uuid)
downloadFile(disk_export_url, disk_export_filename)
export_filename := fmt.Sprintf("%s/%s.xva", config.OutputDir, config.InstanceName)
ui.Say("Getting XVA " + export_url)
downloadFile(export_url, 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)

View File

@ -63,7 +63,7 @@ func (self *stepStartOnHIMN) Run(state multistep.StateBag) multistep.StepAction
log.Printf("Ref: %s", instance.Ref)
//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)
himn_iface_ip = vm_ip
return true, nil
@ -76,15 +76,13 @@ func (self *stepStartOnHIMN) Run(state multistep.StateBag) multistep.StepAction
Timeout: 100 * time.Second,
}.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()))
return multistep.ActionHalt
}
if himn_iface_ip != "" {
state.Put("himn_ssh_address", himn_iface_ip)
ui.Say("Stored VM's IP " + himn_iface_ip)
}
state.Put("himn_ssh_address", 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

View File

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