Merge pull request #23 from rdobson/6.x-support

Adding XenServer 6.x support
This commit is contained in:
Rob Dobson 2015-05-05 10:56:49 +01:00
commit 0ab61566e2
6 changed files with 1270 additions and 60 deletions

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@ import (
type StepExport struct{} type StepExport struct{}
func downloadFile(url, filename string) (err error) { func downloadFile(url, filename string, ui packer.Ui) (err error) {
// Create the file // Create the file
fh, err := os.Create(filename) fh, err := os.Create(filename)
@ -37,7 +37,42 @@ func downloadFile(url, filename string) (err error) {
} }
defer resp.Body.Close() defer resp.Body.Close()
io.Copy(fh, resp.Body)
var progress uint
var total uint
var percentage uint
var marker_len uint
progress = uint(0)
total = uint(resp.ContentLength)
percentage = uint(0)
marker_len = uint(5)
var buffer [4096]byte
for {
n, err := resp.Body.Read(buffer[:])
if err != nil && err != io.EOF {
return err
}
progress += uint(n)
if _, write_err := fh.Write(buffer[:n]); write_err != nil {
return write_err
}
if err == io.EOF {
break
}
// Increment percentage in multiples of marker_len
cur_percentage := ((progress * 100 / total) / marker_len) * marker_len
if cur_percentage > percentage {
percentage = cur_percentage
ui.Message(fmt.Sprintf("Downloading... %d%%", percentage))
}
}
return nil return nil
} }
@ -75,7 +110,7 @@ func (StepExport) Run(state multistep.StateBag) multistep.StepAction {
export_filename := fmt.Sprintf("%s/%s.xva", config.OutputDir, config.VMName) export_filename := fmt.Sprintf("%s/%s.xva", config.OutputDir, config.VMName)
ui.Say("Getting XVA " + export_url) ui.Say("Getting XVA " + export_url)
err = downloadFile(export_url, export_filename) err = downloadFile(export_url, export_filename, ui)
if err != nil { if err != nil {
ui.Error(fmt.Sprintf("Could not download XVA: %s", err.Error())) ui.Error(fmt.Sprintf("Could not download XVA: %s", err.Error()))
return multistep.ActionHalt return multistep.ActionHalt
@ -100,24 +135,63 @@ func (StepExport) Run(state multistep.StateBag) multistep.StepAction {
return multistep.ActionHalt return multistep.ActionHalt
} }
// Basic auth in URL request is required as session token is not // Work out XenServer version
// accepted for some reason. hosts, err := client.GetHosts()
// @todo: raise with XAPI team.
disk_export_url := fmt.Sprintf("https://%s:%s@%s/export_raw_vdi?vdi=%s%s", if err != nil {
client.Username, ui.Error(fmt.Sprintf("Could not retrieve hosts in the pool: %s", err.Error()))
client.Password, return multistep.ActionHalt
client.Host, }
disk_uuid, host := hosts[0]
extrauri) host_software_versions, err := host.GetSoftwareVersion()
xs_version := host_software_versions["product_version"].(string)
if err != nil {
ui.Error(fmt.Sprintf("Could not get the software version: %s", err.Error()))
return multistep.ActionHalt
}
var disk_export_url string
// @todo: check for 6.5 SP1
if xs_version <= "6.5.0" && config.Format == "vdi_vhd" {
// Export the VHD using a Transfer VM
disk_export_url, err = disk.Expose("vhd")
if err != nil {
ui.Error(fmt.Sprintf("Failed to expose disk %s: %s", disk_uuid, err.Error()))
return multistep.ActionHalt
}
} else {
// Use the preferred direct export from XAPI
// 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%s",
client.Username,
client.Password,
client.Host,
disk_uuid,
extrauri)
}
disk_export_filename := fmt.Sprintf("%s/%s%s", config.OutputDir, disk_uuid, suffix) disk_export_filename := fmt.Sprintf("%s/%s%s", config.OutputDir, disk_uuid, suffix)
ui.Say("Getting VDI " + disk_export_url) ui.Say("Getting VDI " + disk_export_url)
err = downloadFile(disk_export_url, disk_export_filename) err = downloadFile(disk_export_url, disk_export_filename, ui)
if err != nil { if err != nil {
ui.Error(fmt.Sprintf("Could not download VDI: %s", err.Error())) ui.Error(fmt.Sprintf("Could not download VDI: %s", err.Error()))
return multistep.ActionHalt return multistep.ActionHalt
} }
// Call unexpose in case a TVM was used. The call is harmless
// if that is not the case.
disk.Unexpose()
} }
default: default:

View File

@ -17,6 +17,11 @@ func (self *StepFindVdi) Run(state multistep.StateBag) multistep.StepAction {
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(xsclient.XenAPIClient) client := state.Get("client").(xsclient.XenAPIClient)
// Ignore if VdiName is not specified
if self.VdiName == "" {
return multistep.ActionContinue
}
vdis, err := client.GetVdiByNameLabel(self.VdiName) vdis, err := client.GetVdiByNameLabel(self.VdiName)
switch { switch {

View File

@ -11,7 +11,7 @@ import (
) )
type StepUploadVdi struct { type StepUploadVdi struct {
VdiName string VdiNameFunc func() string
ImagePathFunc func() string ImagePathFunc func() string
VdiUuidKey string VdiUuidKey string
} }
@ -22,12 +22,13 @@ func (self *StepUploadVdi) Run(state multistep.StateBag) multistep.StepAction {
client := state.Get("client").(xsclient.XenAPIClient) client := state.Get("client").(xsclient.XenAPIClient)
imagePath := self.ImagePathFunc() imagePath := self.ImagePathFunc()
vdiName := self.VdiNameFunc()
if imagePath == "" { if imagePath == "" {
// skip if no disk image to attach // skip if no disk image to attach
return multistep.ActionContinue return multistep.ActionContinue
} }
ui.Say(fmt.Sprintf("Step: Upload VDI '%s'", self.VdiName)) ui.Say(fmt.Sprintf("Step: Upload VDI '%s'", vdiName))
// Create VDI for the image // Create VDI for the image
sr, err := config.GetSR(client) sr, err := config.GetSR(client)
@ -52,15 +53,15 @@ func (self *StepUploadVdi) Run(state multistep.StateBag) multistep.StepAction {
fileLength := fstat.Size() fileLength := fstat.Size()
// Create the VDI // Create the VDI
vdi, err := sr.CreateVdi(self.VdiName, fileLength) vdi, err := sr.CreateVdi(vdiName, fileLength)
if err != nil { if err != nil {
ui.Error(fmt.Sprintf("Unable to create VDI '%s': %s", self.VdiName, err.Error())) ui.Error(fmt.Sprintf("Unable to create VDI '%s': %s", vdiName, err.Error()))
return multistep.ActionHalt return multistep.ActionHalt
} }
vdiUuid, err := vdi.GetUuid() vdiUuid, err := vdi.GetUuid()
if err != nil { if err != nil {
ui.Error(fmt.Sprintf("Unable to get UUID of VDI '%s': %s", self.VdiName, err.Error())) ui.Error(fmt.Sprintf("Unable to get UUID of VDI '%s': %s", vdiName, err.Error()))
return multistep.ActionHalt return multistep.ActionHalt
} }
state.Put(self.VdiUuidKey, vdiUuid) state.Put(self.VdiUuidKey, vdiUuid)
@ -83,6 +84,8 @@ func (self *StepUploadVdi) Cleanup(state multistep.StateBag) {
ui := state.Get("ui").(packer.Ui) ui := state.Get("ui").(packer.Ui)
client := state.Get("client").(xsclient.XenAPIClient) client := state.Get("client").(xsclient.XenAPIClient)
vdiName := self.VdiNameFunc()
if config.ShouldKeepVM(state) { if config.ShouldKeepVM(state) {
return return
} }
@ -119,7 +122,7 @@ func (self *StepUploadVdi) Cleanup(state multistep.StateBag) {
ui.Error(fmt.Sprintf("Can't destroy VDI '%s': %s", vdiUuid, err.Error())) ui.Error(fmt.Sprintf("Can't destroy VDI '%s': %s", vdiUuid, err.Error()))
return return
} }
ui.Say(fmt.Sprintf("Destroyed VDI '%s'", self.VdiName)) ui.Say(fmt.Sprintf("Destroyed VDI '%s'", vdiName))
state.Put(self.VdiUuidKey, "") state.Put(self.VdiUuidKey, "")
} }

View File

@ -27,6 +27,7 @@ type config struct {
ISOChecksumType string `mapstructure:"iso_checksum_type"` ISOChecksumType string `mapstructure:"iso_checksum_type"`
ISOUrls []string `mapstructure:"iso_urls"` ISOUrls []string `mapstructure:"iso_urls"`
ISOUrl string `mapstructure:"iso_url"` ISOUrl string `mapstructure:"iso_url"`
ISOName string `mapstructure:"iso_name"`
PlatformArgs map[string]string `mapstructure:"platform_args"` PlatformArgs map[string]string `mapstructure:"platform_args"`
@ -95,6 +96,7 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error
"iso_checksum": &self.config.ISOChecksum, "iso_checksum": &self.config.ISOChecksum,
"iso_checksum_type": &self.config.ISOChecksumType, "iso_checksum_type": &self.config.ISOChecksumType,
"iso_url": &self.config.ISOUrl, "iso_url": &self.config.ISOUrl,
"iso_name": &self.config.ISOName,
"install_timeout": &self.config.RawInstallTimeout, "install_timeout": &self.config.RawInstallTimeout,
} }
for i := range self.config.ISOUrls { for i := range self.config.ISOUrls {
@ -117,46 +119,55 @@ func (self *Builder) Prepare(raws ...interface{}) (params []string, retErr error
errs, fmt.Errorf("Failed to parse install_timeout: %s", err)) errs, fmt.Errorf("Failed to parse install_timeout: %s", err))
} }
if self.config.ISOChecksumType == "" { if self.config.ISOName == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("The iso_checksum_type must be specified."))
} else {
self.config.ISOChecksumType = strings.ToLower(self.config.ISOChecksumType)
if self.config.ISOChecksumType != "none" {
if self.config.ISOChecksum == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("Due to the file size being large, an iso_checksum is required."))
} else {
self.config.ISOChecksum = strings.ToLower(self.config.ISOChecksum)
}
if hash := common.HashForType(self.config.ISOChecksumType); hash == nil { // If ISO name is not specified, assume a URL and checksum has been provided.
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Unsupported checksum type: %s", self.config.ISOChecksumType))
}
} if self.config.ISOChecksumType == "" {
} errs = packer.MultiErrorAppend(
errs, errors.New("The iso_checksum_type must be specified."))
} else {
self.config.ISOChecksumType = strings.ToLower(self.config.ISOChecksumType)
if self.config.ISOChecksumType != "none" {
if self.config.ISOChecksum == "" {
errs = packer.MultiErrorAppend(
errs, errors.New("Due to the file size being large, an iso_checksum is required."))
} else {
self.config.ISOChecksum = strings.ToLower(self.config.ISOChecksum)
}
if len(self.config.ISOUrls) == 0 { if hash := common.HashForType(self.config.ISOChecksumType); hash == nil {
if self.config.ISOUrl == "" { errs = packer.MultiErrorAppend(
errs = packer.MultiErrorAppend( errs, fmt.Errorf("Unsupported checksum type: %s", self.config.ISOChecksumType))
errs, errors.New("One of iso_url or iso_urls must be specified.")) }
} else {
self.config.ISOUrls = []string{self.config.ISOUrl}
}
} else if self.config.ISOUrl != "" {
errs = packer.MultiErrorAppend(
errs, errors.New("Only one of iso_url or iso_urls may be specified."))
}
for i, url := range self.config.ISOUrls { }
self.config.ISOUrls[i], err = common.DownloadableURL(url) }
if err != nil {
errs = packer.MultiErrorAppend( if len(self.config.ISOUrls) == 0 {
errs, fmt.Errorf("Failed to parse iso_urls[%d]: %s", i, err)) if self.config.ISOUrl == "" {
} errs = packer.MultiErrorAppend(
} errs, errors.New("One of iso_url or iso_urls must be specified."))
} else {
self.config.ISOUrls = []string{self.config.ISOUrl}
}
} else if self.config.ISOUrl != "" {
errs = packer.MultiErrorAppend(
errs, errors.New("Only one of iso_url or iso_urls may be specified."))
}
for i, url := range self.config.ISOUrls {
self.config.ISOUrls[i], err = common.DownloadableURL(url)
if err != nil {
errs = packer.MultiErrorAppend(
errs, fmt.Errorf("Failed to parse iso_urls[%d]: %s", i, err))
}
}
} else {
// An ISO name has been provided. It should be attached from an available SR.
}
if len(errs.Errors) > 0 { if len(errs.Errors) > 0 {
retErr = errors.New(errs.Error()) retErr = errors.New(errs.Error())
@ -190,7 +201,7 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa
httpReqChan := make(chan string, 1) httpReqChan := make(chan string, 1)
//Build the steps //Build the steps
steps := []multistep.Step{ download_steps := []multistep.Step{
&common.StepDownload{ &common.StepDownload{
Checksum: self.config.ISOChecksum, Checksum: self.config.ISOChecksum,
ChecksumType: self.config.ISOChecksumType, ChecksumType: self.config.ISOChecksumType,
@ -198,6 +209,9 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa
ResultKey: "iso_path", ResultKey: "iso_path",
Url: self.config.ISOUrls, Url: self.config.ISOUrls,
}, },
}
steps := []multistep.Step{
&xscommon.StepPrepareOutputDir{ &xscommon.StepPrepareOutputDir{
Force: self.config.PackerForce, Force: self.config.PackerForce,
Path: self.config.OutputDir, Path: self.config.OutputDir,
@ -209,7 +223,9 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa
Chan: httpReqChan, Chan: httpReqChan,
}, },
&xscommon.StepUploadVdi{ &xscommon.StepUploadVdi{
VdiName: "Packer-floppy-disk", VdiNameFunc: func() string {
return "Packer-floppy-disk"
},
ImagePathFunc: func() string { ImagePathFunc: func() string {
if floppyPath, ok := state.GetOk("floppy_path"); ok { if floppyPath, ok := state.GetOk("floppy_path"); ok {
return floppyPath.(string) return floppyPath.(string)
@ -219,9 +235,17 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa
VdiUuidKey: "floppy_vdi_uuid", VdiUuidKey: "floppy_vdi_uuid",
}, },
&xscommon.StepUploadVdi{ &xscommon.StepUploadVdi{
VdiName: path.Base(self.config.ISOUrls[0]), VdiNameFunc: func() string {
if len(self.config.ISOUrls) > 0 {
return path.Base(self.config.ISOUrls[0])
}
return ""
},
ImagePathFunc: func() string { ImagePathFunc: func() string {
return state.Get("iso_path").(string) if isoPath, ok := state.GetOk("iso_path"); ok {
return isoPath.(string)
}
return ""
}, },
VdiUuidKey: "iso_vdi_uuid", VdiUuidKey: "iso_vdi_uuid",
}, },
@ -229,6 +253,10 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa
VdiName: self.config.ToolsIsoName, VdiName: self.config.ToolsIsoName,
VdiUuidKey: "tools_vdi_uuid", VdiUuidKey: "tools_vdi_uuid",
}, },
&xscommon.StepFindVdi{
VdiName: self.config.ISOName,
VdiUuidKey: "isoname_vdi_uuid",
},
new(stepCreateInstance), new(stepCreateInstance),
&xscommon.StepAttachVdi{ &xscommon.StepAttachVdi{
VdiUuidKey: "floppy_vdi_uuid", VdiUuidKey: "floppy_vdi_uuid",
@ -238,6 +266,10 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa
VdiUuidKey: "iso_vdi_uuid", VdiUuidKey: "iso_vdi_uuid",
VdiType: xsclient.CD, VdiType: xsclient.CD,
}, },
&xscommon.StepAttachVdi{
VdiUuidKey: "isoname_vdi_uuid",
VdiType: xsclient.CD,
},
&xscommon.StepAttachVdi{ &xscommon.StepAttachVdi{
VdiUuidKey: "tools_vdi_uuid", VdiUuidKey: "tools_vdi_uuid",
VdiType: xsclient.CD, VdiType: xsclient.CD,
@ -293,6 +325,11 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa
new(xscommon.StepExport), new(xscommon.StepExport),
} }
if self.config.ISOName == "" {
steps = append(download_steps, steps...)
}
self.runner = &multistep.BasicRunner{Steps: steps} self.runner = &multistep.BasicRunner{Steps: steps}
self.runner.Run(state) self.runner.Run(state)

View File

@ -127,7 +127,9 @@ func (self *Builder) Run(ui packer.Ui, hook packer.Hook, cache packer.Cache) (pa
}, },
new(xscommon.StepHTTPServer), new(xscommon.StepHTTPServer),
&xscommon.StepUploadVdi{ &xscommon.StepUploadVdi{
VdiName: "Packer-floppy-disk", VdiNameFunc: func() string {
return "Packer-floppy-disk"
},
ImagePathFunc: func() string { ImagePathFunc: func() string {
if floppyPath, ok := state.GetOk("floppy_path"); ok { if floppyPath, ok := state.GetOk("floppy_path"); ok {
return floppyPath.(string) return floppyPath.(string)