diff --git a/skytap/vm.go b/skytap/vm.go index bcad22f..85e0ccf 100644 --- a/skytap/vm.go +++ b/skytap/vm.go @@ -4,6 +4,7 @@ import ( "context" "fmt" "log" + "net/http" "sort" "time" ) @@ -278,10 +279,23 @@ func (s *VMsServiceClient) Create(ctx context.Context, environmentID string, opt return nil, err } + // Retry to work around 422 errors on creating a vm. var createdEnvironment Environment - _, err = s.client.do(ctx, req, &createdEnvironment) - if err != nil { - return nil, err + var makeRequest = true + for i := 0; i < s.client.retryCount+1 && makeRequest; i++ { + _, err = s.client.do(ctx, req, &createdEnvironment) + if err == nil { + log.Printf("[INFO] VM created\n") + makeRequest = false + } else { + errorResponse := err.(*ErrorResponse) + if http.StatusUnprocessableEntity == errorResponse.Response.StatusCode { + log.Printf("[INFO] 422 error received: waiting for %d second(s)\n", s.client.retryAfter) + time.Sleep(time.Duration(s.client.retryAfter) * time.Second) + } else { + return nil, err + } + } } // The create method returns an environment. The ID of the VM is not specified. diff --git a/skytap/vm_test.go b/skytap/vm_test.go index 7f26850..5beb440 100644 --- a/skytap/vm_test.go +++ b/skytap/vm_test.go @@ -50,6 +50,88 @@ func TestCreateVM(t *testing.T) { assert.Equal(t, environment.VMs[1], *createdVM, "Bad VM") } +func TestCreateVM422(t *testing.T) { + request := fmt.Sprintf(`{ + "template_id": "%d", + "vm_ids": [ + "%d" + ] + }`, 42, 43) + response := fmt.Sprintf(string(readTestFile(t, "createVMResponse.json")), 123, 123, 456) + requestCounter := 0 + + skytap, hs, handler := createClient(t) + defer hs.Close() + + *handler = func(rw http.ResponseWriter, req *http.Request) { + assert.Equal(t, "/configurations/123", req.URL.Path, "Bad path") + assert.Equal(t, "PUT", req.Method, "Bad method") + + body, err := ioutil.ReadAll(req.Body) + assert.Nil(t, err, "Bad request body") + assert.JSONEq(t, request, string(body), "Bad request body") + + if requestCounter == 0 { + rw.WriteHeader(http.StatusUnprocessableEntity) + } else { + _, err = io.WriteString(rw, response) + assert.NoError(t, err) + } + requestCounter++ + } + opts := &CreateVMRequest{ + TemplateID: "42", + VMID: "43", + } + + createdVM, err := skytap.VMs.Create(context.Background(), "123", opts) + assert.Nil(t, err, "Bad API method") + + var environment Environment + err = json.Unmarshal([]byte(response), &environment) + assert.NoError(t, err) + assert.Equal(t, environment.VMs[1], *createdVM, "Bad VM") + + assert.Equal(t, 2, requestCounter) +} + +func TestCreateVMError(t *testing.T) { + request := fmt.Sprintf(`{ + "template_id": "%d", + "vm_ids": [ + "%d" + ] + }`, 42, 43) + requestCounter := 0 + + skytap, hs, handler := createClient(t) + defer hs.Close() + + *handler = func(rw http.ResponseWriter, req *http.Request) { + assert.Equal(t, "/configurations/123", req.URL.Path, "Bad path") + assert.Equal(t, "PUT", req.Method, "Bad method") + + body, err := ioutil.ReadAll(req.Body) + assert.Nil(t, err, "Bad request body") + assert.JSONEq(t, request, string(body), "Bad request body") + + rw.WriteHeader(401) + requestCounter++ + } + opts := &CreateVMRequest{ + TemplateID: "42", + VMID: "43", + } + + createdVM, err := skytap.VMs.Create(context.Background(), "123", opts) + assert.Nil(t, createdVM, "Bad API method") + errorResponse := err.(*ErrorResponse) + + assert.Nil(t, errorResponse.RetryAfter) + assert.Equal(t, 1, requestCounter) + assert.Equal(t, http.StatusUnauthorized, errorResponse.Response.StatusCode) +} + func TestReadVM(t *testing.T) { response := fmt.Sprintf(string(readTestFile(t, "VMResponse.json")), 456)