From 4efd1029a2cd8e0f20d482f6663a846139c7095b Mon Sep 17 00:00:00 2001 From: Cheng Sun Date: Wed, 31 Dec 2014 15:21:15 +0000 Subject: [PATCH] Abstract out http PUT --- builder/xenserver/common/http_upload.go | 115 ++++++++++++++++++++ builder/xenserver/common/step_upload_vdi.go | 85 +-------------- 2 files changed, 119 insertions(+), 81 deletions(-) create mode 100644 builder/xenserver/common/http_upload.go diff --git a/builder/xenserver/common/http_upload.go b/builder/xenserver/common/http_upload.go new file mode 100644 index 0000000..57443ac --- /dev/null +++ b/builder/xenserver/common/http_upload.go @@ -0,0 +1,115 @@ +package common + +import ( + "crypto/tls" + "fmt" + "github.com/mitchellh/multistep" + "github.com/mitchellh/packer/packer" + "log" + "net/http" + "net/url" + "os" + "time" +) + +func appendQuery(urlstring, k, v string) (string, error) { + u, err := url.Parse(urlstring) + if err != nil { + return "", fmt.Errorf("Unable to parse URL '%s': %s", urlstring, err.Error()) + } + m := u.Query() + m.Add(k, v) + u.RawQuery = m.Encode() + return u.String(), err +} + +func httpUpload(import_url string, fh *os.File, state multistep.StateBag) error { + ui := state.Get("ui").(packer.Ui) + client := state.Get("client").(XenAPIClient) + + task, err := client.CreateTask() + if err != nil { + return fmt.Errorf("Unable to create task: %s", err.Error()) + } + defer task.Destroy() + + import_task_url, err := appendQuery(import_url, "task_id", task.Ref) + if err != nil { + return err + } + + // Get file length + fstat, err := fh.Stat() + if err != nil { + return fmt.Errorf("Unable to stat '%s': %s", fh.Name(), err.Error()) + } + fileLength := fstat.Size() + + // Define a new transport which allows self-signed certs + tr := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + } + + // Create a client + httpClient := &http.Client{Transport: tr} + + // Create request and download file + request, err := http.NewRequest("PUT", import_task_url, fh) + request.ContentLength = fileLength + + ui.Say(fmt.Sprintf("PUT '%s'", import_task_url)) + + resp, err := httpClient.Do(request) // Do closes fh for us, according to docs + if err != nil { + return err + } + + if resp.StatusCode != 200 { + return fmt.Errorf("PUT request got non-200 status code: %s", resp.Status) + } + + logIteration := 0 + err = InterruptibleWait{ + Predicate: func() (bool, error) { + status, err := task.GetStatus() + if err != nil { + return false, fmt.Errorf("Failed to get task status: %s", err.Error()) + } + switch status { + case Pending: + progress, err := task.GetProgress() + if err != nil { + return false, fmt.Errorf("Failed to get progress: %s", err.Error()) + } + logIteration = logIteration + 1 + if logIteration%5 == 0 { + log.Printf("Upload %.0f%% complete", progress*100) + } + return false, nil + case Success: + return true, nil + case Failure: + errorInfo, err := task.GetErrorInfo() + if err != nil { + errorInfo = []string{fmt.Sprintf("furthermore, failed to get error info: %s", err.Error())} + } + return false, fmt.Errorf("Task failed: %s", errorInfo) + case Cancelling, Cancelled: + return false, fmt.Errorf("Task cancelled") + default: + return false, fmt.Errorf("Unknown task status %v", status) + } + }, + PredicateInterval: 1 * time.Second, + Timeout: 24 * time.Hour, + }.Wait(state) + + resp.Body.Close() + + if err != nil { + return fmt.Errorf("Error uploading: %s", err.Error()) + } + + log.Printf("Upload complete") + return nil +} diff --git a/builder/xenserver/common/step_upload_vdi.go b/builder/xenserver/common/step_upload_vdi.go index adc3807..6de6d92 100644 --- a/builder/xenserver/common/step_upload_vdi.go +++ b/builder/xenserver/common/step_upload_vdi.go @@ -1,12 +1,10 @@ package common import ( - "crypto/tls" "fmt" "github.com/mitchellh/multistep" "github.com/mitchellh/packer/packer" "log" - "net/http" "os" "time" ) @@ -37,7 +35,7 @@ func (self *StepUploadVdi) Run(state multistep.StateBag) multistep.StepAction { return multistep.ActionHalt } - // Open the file for reading (NB: putFile closes the file for us) + // Open the file for reading (NB: httpUpload closes the file for us) fh, err := os.Open(imagePath) if err != nil { ui.Error(fmt.Sprintf("Unable to open disk image '%s': %s", imagePath, err.Error())) @@ -66,91 +64,16 @@ func (self *StepUploadVdi) Run(state multistep.StateBag) multistep.StepAction { } state.Put(self.VdiUuidKey, vdiUuid) - task, err := client.CreateTask() - if err != nil { - ui.Error(fmt.Sprintf("Unable to create task: %s", err.Error())) - return multistep.ActionHalt - } - defer task.Destroy() - - import_url := fmt.Sprintf("https://%s/import_raw_vdi?vdi=%s&session_id=%s&task_id=%s", + err = httpUpload(fmt.Sprintf("https://%s/import_raw_vdi?vdi=%s&session_id=%s", client.Host, vdi.Ref, client.Session.(string), - task.Ref, - ) - - // Define a new transport which allows self-signed certs - tr := &http.Transport{ - TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, - } - - // Create a client - httpClient := &http.Client{Transport: tr} - - // Create request and download file - request, err := http.NewRequest("PUT", import_url, fh) - request.ContentLength = fileLength - - ui.Say(fmt.Sprintf("PUT disk image '%s'", import_url)) - - resp, err := httpClient.Do(request) // Do closes fh for us, according to docs + ), fh, state) if err != nil { - ui.Error(fmt.Sprintf("Unable to upload disk image: %s", err.Error())) + ui.Error(fmt.Sprintf("Unable to upload VDI: %s", err.Error())) return multistep.ActionHalt } - if resp.StatusCode != 200 { - ui.Error(fmt.Sprintf("Unable to upload disk image: PUT request got non-200 status code: %s", resp.Status)) - return multistep.ActionHalt - } - - logIteration := 0 - err = InterruptibleWait{ - Predicate: func() (bool, error) { - status, err := task.GetStatus() - if err != nil { - return false, fmt.Errorf("Failed to get task status: %s", err.Error()) - } - switch status { - case Pending: - progress, err := task.GetProgress() - if err != nil { - return false, fmt.Errorf("Failed to get progress: %s", err.Error()) - } - logIteration = logIteration + 1 - if logIteration%5 == 0 { - log.Printf("Upload %.0f%% complete", progress*100) - } - return false, nil - case Success: - return true, nil - case Failure: - errorInfo, err := task.GetErrorInfo() - if err != nil { - errorInfo = []string{fmt.Sprintf("furthermore, failed to get error info: %s", err.Error())} - } - return false, fmt.Errorf("Task failed: %s", errorInfo) - case Cancelling, Cancelled: - return false, fmt.Errorf("Task cancelled") - default: - return false, fmt.Errorf("Unknown task status %v", status) - } - }, - PredicateInterval: 1 * time.Second, - Timeout: 24 * time.Hour, - }.Wait(state) - - resp.Body.Close() - - if err != nil { - ui.Error(fmt.Sprintf("Error uploading: %s", err.Error())) - - return multistep.ActionHalt - } - - log.Printf("Upload complete") - return multistep.ActionContinue }