From 268769330b73e1c42dd94293f1ed56477daffe96 Mon Sep 17 00:00:00 2001
From: James Annesley <2913783+ja6a@users.noreply.github.com>
Date: Mon, 8 Jul 2019 17:17:37 +0100
Subject: [PATCH] 33 Fix reliability issues (#42)
* Add request run state prechecking
* Add response checking
* Add waits and retries for the different response codes. See issue https://github.com/terraform-providers/terraform-provider-skytap/issues/34
* Fail early on 422 if resource not busy otherwise retry.
* Cut Published service Update method and related structures
* Add requirement for vm to be stopped prior to interface delete
* Add environment busy check in place of vm ready check
* Add comment to clarify intention of running the environment after create
---
skytap/client.go | 330 ++++++--
skytap/client_test.go | 302 ++++++-
skytap/convert.go | 5 +
skytap/environment.go | 187 ++++-
skytap/environment_test.go | 588 +++++--------
skytap/interface.go | 114 ++-
skytap/interface_test.go | 390 ++++++---
skytap/network.go | 156 +++-
skytap/network_test.go | 224 +++--
skytap/project.go | 14 +-
skytap/published_service.go | 59 +-
skytap/published_service_test.go | 174 ++--
skytap/template.go | 4 +-
skytap/testdata/createVMResponse.json | 2 +-
.../exampleAttachInterfaceRequest.json | 3 +
.../exampleAttachInterfaceResponse.json | 21 +
.../exampleCreateInterfaceRequest.json | 3 +
.../exampleCreateInterfaceResponse.json | 16 +
skytap/testdata/exampleEnvironment.json | 349 ++++++++
.../exampleInterfaceListResponse.json | 39 +
skytap/testdata/exampleNetworkRequest.json | 8 +
skytap/testdata/exampleNetworkResponse.json | 17 +
.../examplePublishedServiceListResponse.json | 14 +
.../examplePublishedServiceRequest.json | 3 +
.../examplePublishedServiceResponse.json | 6 +
.../exampleUpdateInterfaceRequest.json | 4 +
.../exampleUpdateInterfaceResponse.json | 21 +
skytap/vm.go | 369 +++++---
skytap/vm_test.go | 792 ++++++++++++------
29 files changed, 3112 insertions(+), 1102 deletions(-)
create mode 100644 skytap/testdata/exampleAttachInterfaceRequest.json
create mode 100644 skytap/testdata/exampleAttachInterfaceResponse.json
create mode 100644 skytap/testdata/exampleCreateInterfaceRequest.json
create mode 100644 skytap/testdata/exampleCreateInterfaceResponse.json
create mode 100644 skytap/testdata/exampleEnvironment.json
create mode 100644 skytap/testdata/exampleInterfaceListResponse.json
create mode 100644 skytap/testdata/exampleNetworkRequest.json
create mode 100644 skytap/testdata/exampleNetworkResponse.json
create mode 100644 skytap/testdata/examplePublishedServiceListResponse.json
create mode 100644 skytap/testdata/examplePublishedServiceRequest.json
create mode 100644 skytap/testdata/examplePublishedServiceResponse.json
create mode 100644 skytap/testdata/exampleUpdateInterfaceRequest.json
create mode 100644 skytap/testdata/exampleUpdateInterfaceResponse.json
diff --git a/skytap/client.go b/skytap/client.go
index 42ec1bd..d9471f3 100644
--- a/skytap/client.go
+++ b/skytap/client.go
@@ -4,6 +4,7 @@ import (
"bytes"
"context"
"encoding/json"
+ "errors"
"fmt"
"io"
"io/ioutil"
@@ -23,9 +24,18 @@ const (
headerRetryAfter = "Retry-After"
defRetryAfter = 10
- defRetryCount = 30
+ defRetryCount = 60
+
+ noRunStateCheck RunStateCheckStatus = 0
+ envRunStateCheck RunStateCheckStatus = 1
+ vmRunStateCheck RunStateCheckStatus = 2
+
+ requestNotAsExpected = "request not as expected"
)
+// RunStateCheckStatus value of the run check status used to determine checking of request and response.
+type RunStateCheckStatus int
+
// Client is a client to manage and configure the skytap cloud
type Client struct {
// HTTP client to be used for communicating with the SkyTap SDK
@@ -79,7 +89,6 @@ type ListFilter struct {
// ErrorResponse is the general purpose struct to hold error data
type ErrorResponse struct {
-
// HTTP response that caused this error
Response *http.Response
@@ -92,8 +101,22 @@ type ErrorResponse struct {
// RetryAfter is sometimes returned by the server
RetryAfter *int
- // RequiresRetry indicates whether a retry is required
- RequiresRetry bool
+ // RateLimited informs Skytap is rate limiting
+ RateLimited *int
+}
+
+type environmentVMRunState struct {
+ environmentID *string
+ vmID *string
+ adapterID *string
+ environment []EnvironmentRunstate
+ vm []VMRunstate
+ diskIdentification []DiskIdentification
+ runStateCheckStatus RunStateCheckStatus
+}
+
+type responseComparator interface {
+ compareResponse(ctx context.Context, c *Client, v interface{}, state *environmentVMRunState) (string, bool)
}
// Error returns a formatted error
@@ -188,40 +211,107 @@ func (c *Client) newRequest(ctx context.Context, method, path string, body inter
return req, nil
}
-func (c *Client) do(ctx context.Context, req *http.Request, v interface{}) (*http.Response, error) {
- var resp *http.Response
- var err error
- var makeRequest = true
+func (c *Client) do(ctx context.Context, req *http.Request, v interface{}, state *environmentVMRunState, payload responseComparator) (*http.Response, error) {
+ if req.Method == http.MethodPost || req.Method == http.MethodPut || req.Method == http.MethodDelete {
+ for i := 0; i < c.retryCount; i++ {
+ err := c.checkResourceStateUntilSatisfied(ctx, req, state)
+ if err != nil {
+ return nil, err
+ }
+ resp, retry, err := c.requestPutPostDelete(ctx, req, state, payload, v)
+ if !retry || err != nil {
+ return resp, err
+ }
+ }
+ }
+ return c.request(ctx, req, v)
+}
- for i := 0; i < c.retryCount+1 && makeRequest; i++ {
- resp, err = c.hc.Do(req.WithContext(ctx))
+func (c *Client) request(ctx context.Context, req *http.Request, v interface{}) (*http.Response, error) {
+ err := logRequest(req)
+ if err != nil {
+ return nil, err
+ }
+ resp, err := c.hc.Do(req.WithContext(ctx))
+ if err != nil {
+ return nil, err
+ }
+
+ if resp.StatusCode == http.StatusOK {
+ err := readResponseBody(resp, v)
if err != nil {
- break
+ return nil, err
}
+ } else {
+ return resp, c.buildErrorResponse(resp)
+ }
- err = c.checkResponse(resp)
+ return resp, nil
+}
- if err == nil {
- errBody := readResponseBody(resp, v)
- if errBody != nil {
- break
+func (c *Client) requestPutPostDelete(ctx context.Context, req *http.Request, state *environmentVMRunState, payload responseComparator, v interface{}) (*http.Response, bool, error) {
+ var resp *http.Response
+ err := logRequest(req)
+ if err != nil {
+ return nil, false, err
+ }
+ resp, err = c.hc.Do(req.WithContext(ctx))
+ if err != nil {
+ return nil, false, err
+ }
+
+ code := resp.StatusCode
+
+ if code == http.StatusOK {
+ err = readResponseBody(resp, v)
+ if err != nil {
+ return nil, false, err
+ }
+ if payload != nil {
+ for i := 0; i < c.retryCount; i++ {
+ if message, ok := payload.compareResponse(ctx, c, v, state); !ok {
+ c.backoff("response check", fmt.Sprintf("%d", code), message, c.retryAfter)
+ } else {
+ return nil, false, nil
+ }
}
- makeRequest = false
- } else if err.(*ErrorResponse).RequiresRetry {
- seconds := *err.(*ErrorResponse).RetryAfter
- log.Printf("[INFO] retrying after %d second(s)\n", seconds)
- time.Sleep(time.Duration(seconds) * time.Second)
- } else {
- makeRequest = false
}
- errBody := resp.Body.Close()
- if errBody != nil {
- break
+ return nil, false, err
+ }
+ return c.handleError(resp, code)
+}
+
+func (c *Client) handleError(resp *http.Response, code int) (*http.Response, bool, error) {
+ var errorSpecial *ErrorResponse
+ errorSpecial = c.buildErrorResponse(resp).(*ErrorResponse)
+ retryError := ""
+ if code == http.StatusUnprocessableEntity {
+ retryError = "StatusUnprocessableEntity"
+ if !strings.Contains(*errorSpecial.Message, "busy") {
+ return resp, false, errorSpecial
+ }
+ } else if code == http.StatusConflict {
+ retryError = "StatusConflict"
+ } else if code == http.StatusLocked {
+ retryError = "StatusLocked"
+ } else if code == http.StatusTooManyRequests {
+ retryError = "StatusTooManyRequests"
+ }
+ if retryError != "" {
+ seconds := c.retryAfter
+ if errorSpecial.RetryAfter != nil {
+ seconds = *errorSpecial.RetryAfter
}
+ c.backoff("response check", fmt.Sprintf("%d", code), retryError, seconds)
+ return resp, true, nil
}
+ return resp, false, errorSpecial
+}
- return resp, err
+func (c *Client) backoff(message string, code string, codeAsString string, snooze int) {
+ log.Printf("[INFO] SDK %s (%s:%s). Retrying after %d second(s)\n", message, code, codeAsString, snooze)
+ time.Sleep(time.Duration(snooze) * time.Second)
}
func readResponseBody(resp *http.Response, v interface{}) error {
@@ -232,10 +322,27 @@ func readResponseBody(resp *http.Response, v interface{}) error {
} else {
err = json.NewDecoder(resp.Body).Decode(v)
}
+ if err != nil {
+ log.Printf("[ERROR] SDK response payload decoding: (%s)", err.Error())
+ }
+ err = resp.Body.Close()
}
return err
}
+func logRequest(req *http.Request) error {
+ log.Printf("[DEBUG] SDK request (%s), URL (%s), agent (%s)\n", req.Method, req.URL.String(), req.UserAgent())
+ if req.Body != nil {
+ body, err := ioutil.ReadAll(req.Body)
+ if err != nil {
+ return err
+ }
+ req.Body = ioutil.NopCloser(bytes.NewReader(body))
+ log.Printf("[DEBUG] SDK request body (%s)\n", strings.TrimSpace(string(body)))
+ }
+ return nil
+}
+
func (c *Client) setRequestListParameters(req *http.Request, params *ListParameters) error {
if params == nil {
params = DefaultListParameters
@@ -266,43 +373,164 @@ func (c *Client) setRequestListParameters(req *http.Request, params *ListParamet
return nil
}
-// checkResponse checks the API response for errors, and returns them if present. A response is considered an
-// error if it has a status code outside the 200 range. API error responses are expected to have either no response
-// body, or a JSON response body that maps to ErrorResponse.
-func (c *Client) checkResponse(r *http.Response) error {
- if code := r.StatusCode; code >= http.StatusOK && code <= 299 {
- return nil
- }
-
+func (c *Client) buildErrorResponse(r *http.Response) error {
errorResponse := &ErrorResponse{Response: r}
data, err := ioutil.ReadAll(r.Body)
if err == nil && len(data) > 0 {
- err := json.Unmarshal(data, errorResponse)
- if err != nil {
- errorResponse.Message = strToPtr(string(data))
- }
+ errorResponse.Message = strToPtr(string(data))
+ log.Printf("[INFO] SDK response error: (%s)", *errorResponse.Message)
}
if requestID := r.Header.Get(headerRequestID); requestID != "" {
errorResponse.RequestID = strToPtr(requestID)
}
- if code := r.StatusCode; code == http.StatusLocked ||
- code == http.StatusTooManyRequests ||
- code == http.StatusConflict ||
- (code >= http.StatusInternalServerError && code <= 599) {
- if retryAfter := r.Header.Get(headerRetryAfter); retryAfter != "" {
- val, err := strconv.Atoi(retryAfter)
- if err == nil {
- errorResponse.RetryAfter = intToPtr(val)
- } else {
- errorResponse.RetryAfter = intToPtr(c.retryAfter)
- }
+ if retryAfter := r.Header.Get(headerRetryAfter); retryAfter != "" {
+ val, err := strconv.Atoi(retryAfter)
+ if err == nil {
+ errorResponse.RetryAfter = intToPtr(val)
} else {
errorResponse.RetryAfter = intToPtr(c.retryAfter)
}
- errorResponse.RequiresRetry = true
+ } else {
+ errorResponse.RetryAfter = intToPtr(c.retryAfter)
}
return errorResponse
}
+
+func (c *Client) checkResourceStateUntilSatisfied(ctx context.Context, req *http.Request, state *environmentVMRunState) error {
+ runStateCheck := c.requiresChecking(state)
+ if runStateCheck > noRunStateCheck {
+ for i := 0; i < c.retryCount; i++ {
+ var ok bool
+ var err error
+ if runStateCheck == envRunStateCheck {
+ ok, err = c.getEnvironmentRunState(ctx, state.environmentID, state.environment)
+ } else {
+ ok, err = c.getVMRunState(ctx, state.environmentID, state.vmID, state.vm)
+ }
+ if err != nil || ok {
+ return err
+ }
+ c.backoff("pre-check loop", "", "", c.retryAfter)
+ }
+ return errors.New("timeout waiting for state")
+ }
+ return nil
+}
+
+func (c *Client) requiresChecking(state *environmentVMRunState) RunStateCheckStatus {
+ if state == nil {
+ return noRunStateCheck
+ }
+ return state.runStateCheckStatus
+}
+
+func (c *Client) getEnvironmentRunState(ctx context.Context, id *string, states []EnvironmentRunstate) (bool, error) {
+ env, err := c.Environments.Get(ctx, *id)
+ if err != nil {
+ return false, err
+ }
+ if env.Runstate == nil {
+ return false, errors.New("environment run state not set")
+ }
+ ok := c.containsEnvironmentRunState(env.Runstate, states)
+ log.Printf("[DEBUG] SDK run state of environment (%s) and require: (%s).\n",
+ *env.Runstate,
+ c.environmentsRunStatesToString(states))
+ return ok, nil
+}
+
+func (c *Client) containsEnvironmentRunState(currentState *EnvironmentRunstate, possibleStates []EnvironmentRunstate) bool {
+ for _, v := range possibleStates {
+ if v == *currentState {
+ return true
+ }
+ }
+ return false
+}
+
+func (c *Client) environmentsRunStatesToString(possibleStates []EnvironmentRunstate) string {
+ var items []string
+ for _, v := range possibleStates {
+ items = append(items, string(v))
+ }
+ return strings.Join(items, ", ")
+}
+
+func (c *Client) getVMRunState(ctx context.Context, environmentID *string, vmID *string, states []VMRunstate) (bool, error) {
+ vm, err := c.VMs.Get(ctx, *environmentID, *vmID)
+ if err != nil {
+ return false, err
+ }
+ if vm.Runstate == nil {
+ return false, errors.New("vm run state not set")
+ }
+ ok := c.containsVMRunState(vm.Runstate, states)
+ log.Printf("[INFO] SDK run state of vm (%s) and require: (%s).\n",
+ *vm.Runstate,
+ c.vMRunStatesToString(states))
+ return ok, nil
+}
+
+func (c *Client) containsVMRunState(currentState *VMRunstate, possibleStates []VMRunstate) bool {
+ for _, v := range possibleStates {
+ if v == *currentState {
+ return true
+ }
+ }
+ return false
+}
+
+func (c *Client) vMRunStatesToString(possibleStates []VMRunstate) string {
+ var items []string
+ for _, v := range possibleStates {
+ items = append(items, string(v))
+ }
+ return strings.Join(items, ", ")
+}
+
+func envRunStateNotBusy(environmentID string) *environmentVMRunState {
+ return &environmentVMRunState{
+ environmentID: strToPtr(environmentID),
+ environment: []EnvironmentRunstate{
+ EnvironmentRunstateRunning,
+ EnvironmentRunstateStopped,
+ EnvironmentRunstateSuspended,
+ EnvironmentRunstateHalted},
+ runStateCheckStatus: envRunStateCheck,
+ }
+}
+
+func envRunStateNotBusyWithVM(environmentID string, vmID string) *environmentVMRunState {
+ state := envRunStateNotBusy(environmentID)
+ state.vmID = strToPtr(vmID)
+ return state
+}
+
+func vmRunStateNotBusy(environmentID string, vmID string) *environmentVMRunState {
+ return &environmentVMRunState{
+ environmentID: strToPtr(environmentID),
+ vmID: strToPtr(vmID),
+ vm: []VMRunstate{
+ VMRunstateStopped,
+ VMRunstateHalted,
+ VMRunstateReset,
+ VMRunstateRunning,
+ VMRunstateSuspended},
+ runStateCheckStatus: vmRunStateCheck,
+ }
+}
+
+func vmRequestRunStateStopped(environmentID string, vmID string) *environmentVMRunState {
+ state := vmRunStateNotBusy(environmentID, vmID)
+ state.vm = []VMRunstate{VMRunstateStopped}
+ return state
+}
+
+func vmRunStateNotBusyWithAdapter(environmentID string, vmID string, adapterID string) *environmentVMRunState {
+ state := vmRunStateNotBusy(environmentID, vmID)
+ state.adapterID = strToPtr(adapterID)
+ return state
+}
diff --git a/skytap/client_test.go b/skytap/client_test.go
index c5d0c02..4b46efa 100644
--- a/skytap/client_test.go
+++ b/skytap/client_test.go
@@ -1,8 +1,13 @@
package skytap
import (
+ "bytes"
"context"
+ "encoding/json"
+ "fmt"
"io"
+ "io/ioutil"
+ "log"
"net/http"
"net/http/httptest"
"testing"
@@ -31,15 +36,15 @@ func createClientWithUserAgent(t *testing.T, userAgent string) (*Client, *httpte
settings := NewDefaultSettings(WithBaseURL(hs.URL), WithCredentialsProvider(NewAPITokenCredentials(user, token)), WithUserAgent(userAgent))
skytap, err := NewClient(settings)
+ assert.Nil(t, err)
skytap.retryCount = testingRetryCount
skytap.retryAfter = testingRetryAfter
- assert.Nil(t, err)
assert.NotNil(t, skytap)
return skytap, hs, &handler
}
-func TestRetryWithFailure(t *testing.T) {
+func TestGetRetryWithFailure(t *testing.T) {
requestCounter := 0
skytap, hs, handler := createClient(t)
@@ -53,12 +58,11 @@ func TestRetryWithFailure(t *testing.T) {
_, err := skytap.Projects.Get(context.Background(), 12345)
errorResponse := err.(*ErrorResponse)
- assert.Nil(t, errorResponse.RetryAfter)
assert.Equal(t, 1, requestCounter)
assert.Equal(t, http.StatusUnauthorized, errorResponse.Response.StatusCode)
}
-func TestRetryWithBusy409(t *testing.T) {
+func TestGetRetryWithBusy409(t *testing.T) {
requestCounter := 0
skytap, hs, handler := createClient(t)
skytap.retryCount = 1
@@ -75,12 +79,11 @@ func TestRetryWithBusy409(t *testing.T) {
assert.Equal(t, http.StatusConflict, errorResponse.Response.StatusCode)
assert.Equal(t, 2, *errorResponse.RetryAfter)
- assert.True(t, errorResponse.RequiresRetry)
- assert.Equal(t, 2, requestCounter)
+ assert.Equal(t, 1, requestCounter)
assert.Equal(t, 3, testingRetryCount)
}
-func TestRetryWithBusy423(t *testing.T) {
+func TestGetRetryWithBusy423(t *testing.T) {
requestCounter := 0
skytap, hs, handler := createClient(t)
skytap.retryCount = 1
@@ -97,12 +100,11 @@ func TestRetryWithBusy423(t *testing.T) {
assert.Equal(t, http.StatusLocked, errorResponse.Response.StatusCode)
assert.Equal(t, 2, *errorResponse.RetryAfter)
- assert.True(t, errorResponse.RequiresRetry)
- assert.Equal(t, 2, requestCounter)
+ assert.Equal(t, 1, requestCounter)
assert.Equal(t, 3, testingRetryCount)
}
-func TestRetryWithBusy423WithBadRetryAfter(t *testing.T) {
+func TestGetRetryWithBusy423WithBadRetryAfter(t *testing.T) {
requestCounter := 0
skytap, hs, handler := createClient(t)
defer hs.Close()
@@ -118,10 +120,10 @@ func TestRetryWithBusy423WithBadRetryAfter(t *testing.T) {
assert.Equal(t, testingRetryAfter, *errorResponse.RetryAfter)
assert.Equal(t, http.StatusLocked, errorResponse.Response.StatusCode)
- assert.Equal(t, testingRetryCount+1, requestCounter)
+ assert.Equal(t, 1, requestCounter)
}
-func TestRetryWithBusy423WithoutRetryAfter(t *testing.T) {
+func TestGetRetryWithBusy423WithoutRetryAfter(t *testing.T) {
requestCounter := 0
skytap, hs, handler := createClient(t)
defer hs.Close()
@@ -136,10 +138,10 @@ func TestRetryWithBusy423WithoutRetryAfter(t *testing.T) {
assert.Equal(t, testingRetryAfter, *errorResponse.RetryAfter)
assert.Equal(t, http.StatusLocked, errorResponse.Response.StatusCode)
- assert.Equal(t, testingRetryCount+1, requestCounter)
+ assert.Equal(t, 1, requestCounter)
}
-func TestRetryWith429(t *testing.T) {
+func TestGetRetryWith429(t *testing.T) {
requestCounter := 0
skytap, hs, handler := createClient(t)
defer hs.Close()
@@ -154,10 +156,10 @@ func TestRetryWith429(t *testing.T) {
errorResponse := err.(*ErrorResponse)
assert.Equal(t, http.StatusTooManyRequests, errorResponse.Response.StatusCode)
- assert.Equal(t, testingRetryCount+1, requestCounter)
+ assert.Equal(t, 1, requestCounter)
}
-func TestRetryWith50x(t *testing.T) {
+func TestGetRetryWith50x(t *testing.T) {
requestCounter := 0
skytap, hs, handler := createClient(t)
defer hs.Close()
@@ -172,27 +174,279 @@ func TestRetryWith50x(t *testing.T) {
errorResponse := err.(*ErrorResponse)
assert.Equal(t, http.StatusInternalServerError, errorResponse.Response.StatusCode)
- assert.Equal(t, testingRetryCount+1, requestCounter)
+ assert.Equal(t, 1, requestCounter)
+}
+
+func TestGetPreRequestRunstateNotExpecting(t *testing.T) {
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+ responseProcessed := false
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ responseProcessed = true
+ assert.Equal(t, http.MethodGet, req.Method, "Unexpected method")
+ assert.Equal(t, "/v2/projects/12345", req.URL.Path, "Unexpected path")
+ _, err := io.WriteString(rw, `{"id": "12345", "name": "test-project", "summary": "test project"}`)
+ assert.NoError(t, err)
+ }
+
+ _, err := skytap.Projects.Get(context.Background(), 12345)
+ assert.NoError(t, err)
+ assert.True(t, responseProcessed)
+}
+
+func TestPutPostPreRequestRunstateNotExpecting2(t *testing.T) {
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+ responseProcessed := 0
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ responseProcessed++
+ method := http.MethodPost
+ path := "/projects"
+ if responseProcessed >= 2 {
+ method = http.MethodPut
+ path = "/projects/12345"
+ }
+ assert.Equal(t, method, req.Method, "Unexpected method")
+ assert.Equal(t, path, req.URL.Path, "Unexpected path")
+ _, err := io.WriteString(rw, `{"id": "12345", "name": "test-project"}`)
+ assert.NoError(t, err)
+ }
+
+ project := Project{}
+ _, err := skytap.Projects.Create(context.Background(), &project)
+ assert.NoError(t, err)
+ assert.Equal(t, 2, responseProcessed)
}
-func TestRetryWith50xResolves(t *testing.T) {
+func TestPutPostPreRequestRunstate(t *testing.T) {
+ response := fmt.Sprintf(string(readTestFile(t, "VMResponse.json")), 456)
+
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
requestCounter := 0
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ log.Printf("Request: (%d)\n", requestCounter)
+ method := http.MethodGet
+ path := "/v2/configurations/123/vms/456"
+ if requestCounter == 1 {
+ method = http.MethodPost
+ path = "/v2/configurations/123/vms/456/interfaces"
+ } else if requestCounter >= 2 {
+ method = http.MethodGet
+ path = "/v2/configurations/123/vms/456/interfaces/456"
+ }
+ assert.Equal(t, path, req.URL.Path, fmt.Sprintf("Bad path: %d", requestCounter))
+ assert.Equal(t, method, req.Method, fmt.Sprintf("Bad method: %d", requestCounter))
+
+ _, err := io.WriteString(rw, response)
+ assert.NoError(t, err)
+ requestCounter++
+ }
+
+ nicType := &CreateInterfaceRequest{
+ NICType: nicTypeToPtr(NICTypeE1000),
+ }
+
+ _, err := skytap.Interfaces.Create(context.Background(), "123", "456", nicType)
+ assert.Nil(t, err)
+ assert.Equal(t, 5, requestCounter)
+}
+
+func TestPutPostPreRequestRunstate2(t *testing.T) {
+ response := fmt.Sprintf(string(readTestFile(t, "VMResponse.json")), 456)
+ var vm VM
+ err := json.Unmarshal([]byte(response), &vm)
+ assert.NoError(t, err)
+ *vm.Runstate = VMRunstateBusy
+ responseBusy, err := json.Marshal(&vm)
+
skytap, hs, handler := createClient(t)
defer hs.Close()
+ skytap.retryAfter = 1
+ requestCounter := 0
+
*handler = func(rw http.ResponseWriter, req *http.Request) {
- rw.Header().Add("Retry-After", "1")
- if requestCounter == 3 {
- io.WriteString(rw, `{"id": "12345", "name": "test-project", "summary": "test project"}`)
- } else {
- rw.WriteHeader(http.StatusInternalServerError)
+ log.Printf("Request: (%d)\n", requestCounter)
+ if requestCounter == 0 {
+ assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, string(responseBusy))
+ assert.NoError(t, err)
+ } else if requestCounter == 1 {
+ assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, response)
+ assert.NoError(t, err)
+ } else if requestCounter == 2 {
+ assert.Equal(t, "/v2/configurations/123/vms/456/interfaces", req.URL.Path, "Bad path")
+ assert.Equal(t, "POST", req.Method, "Bad method")
+ exampleInterface := fmt.Sprintf(string(readTestFile(t, "exampleCreateInterfaceResponse.json")), 456, 123)
+ _, err := io.WriteString(rw, exampleInterface)
+ assert.NoError(t, err)
+ } else if requestCounter == 3 {
+ assert.Equal(t, "/v2/configurations/123/vms/456/interfaces/nic-456", req.URL.Path, "Bad path")
+ assert.Equal(t, "GET", req.Method, "Bad method")
+ exampleInterface := fmt.Sprintf(string(readTestFile(t, "exampleCreateInterfaceResponse.json")), 456, 123)
+ _, err := io.WriteString(rw, exampleInterface)
+ assert.NoError(t, err)
}
requestCounter++
}
- _, err := skytap.Projects.Get(context.Background(), 12345)
+ nicType := &CreateInterfaceRequest{
+ NICType: nicTypeToPtr(NICTypeE1000),
+ }
+
+ _, err = skytap.Interfaces.Create(context.Background(), "123", "456", nicType)
+ assert.Nil(t, err)
+
+ assert.Equal(t, 4, requestCounter)
+}
+
+func TestGetStatus200(t *testing.T) {
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ _, err := io.WriteString(rw, string(readTestFile(t, "exampleEnvironment.json")))
+ assert.NoError(t, err)
+ }
+
+ var environment Environment
+ path := fmt.Sprintf("%s/%s", environmentBasePath, "123")
+ req, err := skytap.newRequest(context.Background(), "GET", path, nil)
+ resp, err := skytap.request(context.Background(), req, &environment)
+
+ assert.NoError(t, err)
+ assert.Equal(t, 200, resp.StatusCode)
+}
+
+func TestPutPostDelete(t *testing.T) {
+ response := fmt.Sprintf(string(readTestFile(t, "VMResponse.json")), 456)
+
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ var vmResponse VM
+ err := json.Unmarshal([]byte(response), &vmResponse)
+ assert.NoError(t, err)
+ vmResponse.Runstate = vmRunStateToPtr(VMRunstateRunning)
+ bytesRunning, err := json.Marshal(&vmResponse)
+ assert.Nil(t, err, "Bad vm")
+
+ requestCounter := 0
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ log.Printf("Request: (%d)\n", requestCounter)
+ if requestCounter == 0 {
+
+ _, err := io.WriteString(rw, string(bytesRunning))
+ assert.NoError(t, err)
+ } else if requestCounter == 1 {
+ vmResponse.Runstate = vmRunStateToPtr(VMRunstateStopped)
+ bytesStopped, err := json.Marshal(&vmResponse)
+ assert.Nil(t, err, "Bad vm")
+
+ _, err = io.WriteString(rw, string(bytesStopped))
+ assert.NoError(t, err)
+ } else if requestCounter == 2 {
+ _, err := io.WriteString(rw, response)
+ assert.NoError(t, err)
+ }
+ requestCounter++
+ }
+
+ update := &UpdateVMRequest{Runstate: vmRunStateToPtr(VMRunstateStopped)}
+ req, err := skytap.newRequest(context.Background(), http.MethodPut, "", update)
+ assert.NoError(t, err)
+
+ var vm VM
+ _, err = skytap.do(context.Background(), req, &vm, vmRunStateNotBusy("123", "456"), update)
+ assert.NoError(t, err)
+
+ assert.Equal(t, 3, requestCounter)
+}
+
+func TestOutputAndHandleError(t *testing.T) {
+ message := `{
+ "errors": [
+ "IP address conflicts with another network adapter on the network"
+ ]
+ }`
+
+ skytap, hs, _ := createClient(t)
+ defer hs.Close()
+ resp := http.Response{}
+
+ resp.Body = ioutil.NopCloser(bytes.NewBufferString(message))
+ errorSpecial := skytap.buildErrorResponse(&resp).(*ErrorResponse)
+ assert.Equal(t, message, *errorSpecial.Message, "Bad API method")
+}
+
+func TestOutputAndHandle422Error(t *testing.T) {
+ message := `{
+ "errors": [
+ "Network adapter type was not a valid choice for this operating system"
+ ]
+ }`
+
+ skytap, hs, _ := createClient(t)
+ defer hs.Close()
+ resp := http.Response{}
+
+ resp.Body = ioutil.NopCloser(bytes.NewBufferString(message))
+ _, _, err := skytap.handleError(&resp, http.StatusUnprocessableEntity)
+ errSpecial := err.(*ErrorResponse)
+ assert.Equal(t, message, *errSpecial.Message, "Bad API method")
+ assert.Error(t, errSpecial)
+}
+
+func TestOutputAndHandle422Busy(t *testing.T) {
+ message := `{
+ "errors": [
+ "The machine was busy. Try again later."
+ ]
+ }`
+
+ skytap, hs, _ := createClient(t)
+ defer hs.Close()
+ resp := http.Response{}
+
+ resp.Body = ioutil.NopCloser(bytes.NewBufferString(message))
+ _, _, err := skytap.handleError(&resp, http.StatusUnprocessableEntity)
+ assert.Nil(t, err)
+}
+
+func TestMakeTimeout(t *testing.T) {
+ var env Environment
+ err := json.Unmarshal(readTestFile(t, "exampleEnvironment.json"), &env)
+ assert.NoError(t, err)
+ env.Runstate = environmentRunStateToPtr(EnvironmentRunstateBusy)
+ b, err := json.Marshal(&env)
+ assert.Nil(t, err)
+
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ requestCounter := 0
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ log.Printf("Request: (%d)\n", requestCounter)
+ _, err = io.WriteString(rw, string(b))
+ assert.NoError(t, err)
+ requestCounter++
+ }
+ req, err := skytap.newRequest(context.Background(), http.MethodGet, "", nil)
assert.Nil(t, err)
+ err = skytap.checkResourceStateUntilSatisfied(context.Background(), req, envRunStateNotBusy(""))
+ assert.Error(t, err)
+ assert.Equal(t, testingRetryCount, requestCounter)
+ assert.Equal(t, "timeout waiting for state", err.Error())
}
func TestWithUserAgent(t *testing.T) {
diff --git a/skytap/convert.go b/skytap/convert.go
index 41f8745..691260b 100644
--- a/skytap/convert.go
+++ b/skytap/convert.go
@@ -47,3 +47,8 @@ func vmRunStateToPtr(vmRunState VMRunstate) *VMRunstate {
func nicTypeToPtr(nicType NICType) *NICType {
return &nicType
}
+
+// environmentRunStateToPtr returns a pointer to the passed EnvironmentRunstate.
+func environmentRunStateToPtr(environmentRunstate EnvironmentRunstate) *EnvironmentRunstate {
+ return &environmentRunstate
+}
diff --git a/skytap/environment.go b/skytap/environment.go
index d42cbdd..212c18e 100644
--- a/skytap/environment.go
+++ b/skytap/environment.go
@@ -3,6 +3,8 @@ package skytap
import (
"context"
"fmt"
+ "log"
+ "strings"
)
// Default URL paths
@@ -133,6 +135,7 @@ const (
EnvironmentRunstateStopped EnvironmentRunstate = "stopped"
EnvironmentRunstateSuspended EnvironmentRunstate = "suspended"
EnvironmentRunstateRunning EnvironmentRunstate = "running"
+ EnvironmentRunstateHalted EnvironmentRunstate = "halted"
EnvironmentRunstateBusy EnvironmentRunstate = "busy"
)
@@ -183,7 +186,7 @@ func (s *EnvironmentsServiceClient) List(ctx context.Context) (*EnvironmentListR
}
var environmentsListResponse EnvironmentListResult
- _, err = s.client.do(ctx, req, &environmentsListResponse.Value)
+ _, err = s.client.do(ctx, req, &environmentsListResponse.Value, nil, nil)
if err != nil {
return nil, err
}
@@ -201,7 +204,7 @@ func (s *EnvironmentsServiceClient) Get(ctx context.Context, id string) (*Enviro
}
var environment Environment
- _, err = s.client.do(ctx, req, &environment)
+ _, err = s.client.do(ctx, req, &environment, nil, nil)
if err != nil {
return nil, err
}
@@ -210,31 +213,39 @@ func (s *EnvironmentsServiceClient) Get(ctx context.Context, id string) (*Enviro
}
// Create an environment
-func (s *EnvironmentsServiceClient) Create(ctx context.Context, request *CreateEnvironmentRequest) (*Environment, error) {
- req, err := s.client.newRequest(ctx, "POST", environmentLegacyBasePath, request)
+func (s *EnvironmentsServiceClient) Create(ctx context.Context, opts *CreateEnvironmentRequest) (*Environment, error) {
+ req, err := s.client.newRequest(ctx, "POST", environmentLegacyBasePath, opts)
if err != nil {
return nil, err
}
var createdEnvironment Environment
- _, err = s.client.do(ctx, req, &createdEnvironment)
+ _, err = s.client.do(ctx, req, &createdEnvironment, nil, opts)
if err != nil {
return nil, err
}
- runstate := EnvironmentRunstateRunning
+ env, err := s.Get(ctx, *createdEnvironment.ID)
+ if err != nil {
+ return nil, err
+ }
+
+ var runstate *EnvironmentRunstate
+ if *env.VMCount > 0 {
+ runstate = environmentRunStateToPtr(EnvironmentRunstateRunning)
+ }
updateOpts := &UpdateEnvironmentRequest{
- Name: request.Name,
- Description: request.Description,
- Owner: request.Owner,
- OutboundTraffic: request.OutboundTraffic,
- Routable: request.Routable,
- SuspendOnIdle: request.SuspendOnIdle,
- SuspendAtTime: request.SuspendAtTime,
- ShutdownOnIdle: request.ShutdownOnIdle,
- ShutdownAtTime: request.ShutdownAtTime,
- Runstate: &runstate,
+ Name: opts.Name,
+ Description: opts.Description,
+ Owner: opts.Owner,
+ OutboundTraffic: opts.OutboundTraffic,
+ Routable: opts.Routable,
+ SuspendOnIdle: opts.SuspendOnIdle,
+ SuspendAtTime: opts.SuspendAtTime,
+ ShutdownOnIdle: opts.ShutdownOnIdle,
+ ShutdownAtTime: opts.ShutdownAtTime,
+ Runstate: runstate, // we are expecting the environment to start its VMs after creation
}
// update environment after creation to establish the resource information.
@@ -256,7 +267,7 @@ func (s *EnvironmentsServiceClient) Update(ctx context.Context, id string, updat
}
var environment Environment
- _, err = s.client.do(ctx, req, &environment)
+ _, err = s.client.do(ctx, req, &environment, envRunStateNotBusy(id), updateEnvironment)
if err != nil {
return nil, err
}
@@ -272,10 +283,150 @@ func (s *EnvironmentsServiceClient) Delete(ctx context.Context, id string) error
if err != nil {
return err
}
- _, err = s.client.do(ctx, req, nil)
+ _, err = s.client.do(ctx, req, nil, envRunStateNotBusy(id), nil)
if err != nil {
return err
}
return nil
}
+
+func (payload *CreateEnvironmentRequest) compareResponse(ctx context.Context, c *Client, v interface{}, state *environmentVMRunState) (string, bool) {
+ if envOriginal, ok := v.(*Environment); ok {
+ env, err := c.Environments.Get(ctx, *envOriginal.ID)
+ if err != nil {
+ return requestNotAsExpected, false
+ }
+ logEnvironmentStatus(env)
+ log.Printf("[DEBUG] SDK environment runstate after create (%s)\n", *env.Runstate)
+ if *env.Runstate != EnvironmentRunstateBusy {
+ return "", true
+ }
+ return "environment not ready", false
+ }
+ log.Printf("[ERROR] SDK environment comparison not possible on (%v)\n", v)
+ return requestNotAsExpected, false
+}
+
+func (payload *UpdateEnvironmentRequest) compareResponse(ctx context.Context, c *Client, v interface{}, state *environmentVMRunState) (string, bool) {
+ if envOriginal, ok := v.(*Environment); ok {
+ env, err := c.Environments.Get(ctx, *envOriginal.ID)
+ if err != nil {
+ return requestNotAsExpected, false
+ }
+ logEnvironmentStatus(env)
+ actual := payload.buildComparison(env)
+ if payload.string() == actual.string() {
+ return "", true
+ }
+ return "environment not ready", false
+ }
+ log.Printf("[ERROR] SDK environment comparison not possible on (%v)\n", v)
+ return requestNotAsExpected, false
+}
+
+func (payload *UpdateEnvironmentRequest) buildComparison(env *Environment) UpdateEnvironmentRequest {
+ actual := UpdateEnvironmentRequest{}
+ if payload.Name != nil {
+ actual.Name = env.Name
+ }
+ if payload.Description != nil {
+ actual.Description = env.Description
+ }
+ if payload.Owner != nil {
+ actual.Owner = env.OwnerName
+ }
+ if payload.OutboundTraffic != nil {
+ actual.OutboundTraffic = env.OutboundTraffic
+ }
+ if payload.Routable != nil {
+ actual.Routable = env.Routable
+ }
+ if payload.SuspendOnIdle != nil {
+ actual.SuspendOnIdle = env.SuspendOnIdle
+ }
+ if payload.SuspendAtTime != nil {
+ actual.SuspendAtTime = env.SuspendAtTime
+ }
+ if payload.ShutdownOnIdle != nil {
+ actual.ShutdownOnIdle = env.ShutdownOnIdle
+ }
+ if payload.ShutdownAtTime != nil {
+ actual.ShutdownAtTime = env.ShutdownAtTime
+ }
+ if payload.Runstate != nil {
+ actual.Runstate = env.Runstate
+ }
+ return actual
+}
+
+func (payload *UpdateEnvironmentRequest) string() string {
+ name := ""
+ description := ""
+ owner := ""
+ outboundTraffic := ""
+ routable := ""
+ suspendOnIdle := ""
+ suspendAtTime := ""
+ shutdownOnIdle := ""
+ shutdownAtTime := ""
+ runstate := ""
+
+ if payload.Name != nil {
+ name = *payload.Name
+ }
+ if payload.Description != nil {
+ description = *payload.Description
+ }
+ if payload.Owner != nil {
+ owner = *payload.Owner
+ }
+ if payload.OutboundTraffic != nil {
+ outboundTraffic = fmt.Sprintf("%t", *payload.OutboundTraffic)
+ }
+ if payload.Routable != nil {
+ routable = fmt.Sprintf("%t", *payload.Routable)
+ }
+ if payload.SuspendOnIdle != nil {
+ suspendOnIdle = fmt.Sprintf("%d", *payload.SuspendOnIdle)
+ }
+ if payload.SuspendAtTime != nil {
+ suspendAtTime = *payload.SuspendAtTime
+ }
+ if payload.ShutdownOnIdle != nil {
+ shutdownOnIdle = fmt.Sprintf("%d", *payload.ShutdownOnIdle)
+ }
+ if payload.ShutdownAtTime != nil {
+ shutdownAtTime = *payload.ShutdownAtTime
+ }
+ if payload.Runstate != nil {
+ runstate = string(*payload.Runstate)
+ }
+ var sb strings.Builder
+ sb.WriteString(name)
+ sb.WriteString(description)
+ sb.WriteString(owner)
+ sb.WriteString(outboundTraffic)
+ sb.WriteString(routable)
+ sb.WriteString(suspendOnIdle)
+ sb.WriteString(suspendAtTime)
+ sb.WriteString(shutdownOnIdle)
+ sb.WriteString(shutdownAtTime)
+ sb.WriteString(runstate)
+ log.Printf("[DEBUG] SDK environment payload (%s)\n", sb.String())
+ return sb.String()
+}
+
+func logEnvironmentStatus(env *Environment) {
+ if env.RateLimited != nil && *env.RateLimited {
+ log.Printf("[INFO] SDK environment rate limiting detected\n")
+ }
+ if len(env.Errors) > 0 {
+ log.Printf("[INFO] SDK environment errors detected: (%s)\n",
+ strings.Join(env.Errors, ", "))
+ }
+ if len(env.ErrorDetails) > 0 {
+ log.Printf("[INFO] SDK environment errors detected: (%s)\n",
+ strings.Join(env.ErrorDetails, ", "))
+ }
+}
diff --git a/skytap/environment_test.go b/skytap/environment_test.go
index fc4e95d..fff68e0 100644
--- a/skytap/environment_test.go
+++ b/skytap/environment_test.go
@@ -6,370 +6,22 @@ import (
"fmt"
"io"
"io/ioutil"
+ "log"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
-const exampleEnvironment = `{
- "id": "456",
- "url": "https://cloud.skytap.com/v2/configurations/456",
- "name": "No VM",
- "description": "test environment",
- "errors": [
- "error1"
- ],
- "error_details": [
- "error1 details"
- ],
- "runstate": "stopped",
- "rate_limited": false,
- "last_run": "2018/10/11 15:42:23 +0100",
- "suspend_on_idle": 1,
- "suspend_at_time": "2018/10/11 15:42:23 +0100",
- "owner_url": "https://cloud.skytap.com/v2/users/1",
- "owner_name": "Joe Bloggs",
- "owner_id": "1",
- "vm_count": 1,
- "storage": 30720,
- "network_count": 1,
- "created_at": "2018/10/11 15:42:23 +0100",
- "region": "US-West",
- "region_backend": "skytap",
- "svms": 1,
- "can_save_as_template": true,
- "can_copy": true,
- "can_delete": true,
- "can_change_state": true,
- "can_share": true,
- "can_edit": true,
- "label_count": 1,
- "label_category_count": 1,
- "can_tag": true,
- "tags": [
- {
- "id": "43894",
- "value": "tag1"
- },
- {
- "id": "43896",
- "value": "tag2"
- }
- ],
- "tag_list": "tag1,tag2",
- "alerts": [
- {
- "id":"586",
- "display_type":"informational_alert",
- "dismissable":true,
- "message":"IBM i Technology Preview Program nominations are now open. For more information see What's New.",
- "display_on_general":true,
- "display_on_login":false,
- "display_on_smartclient":false
- }
- ],
- "published_service_count": 0,
- "public_ip_count": 0,
- "auto_suspend_description": null,
- "stages": [
- {
- "delay_after_finish_seconds": 300,
- "index": 0,
- "vm_ids": [
- "123456",
- "123457"
- ]
- }
- ],
- "staged_execution": {
- "action_type": "suspend",
- "current_stage_delay_after_finish_seconds": 300,
- "current_stage_index": 1,
- "current_stage_finished_at": "2018/10/11 15:42:23 +0100",
- "vm_ids": [
- "123453",
- "123454"
- ]
- },
- "sequencing_enabled": false,
- "note_count": 1,
- "project_count_for_user": 0,
- "project_count": 0,
- "publish_set_count": 0,
- "schedule_count": 0,
- "vpn_count": 0,
- "outbound_traffic": false,
- "routable": false,
- "vms": [
- {
- "id": "36858580",
- "name": "CentOS 7 Server x64",
- "runstate": "stopped",
- "rate_limited": false,
- "hardware": {
- "cpus": 1,
- "supports_multicore": true,
- "cpus_per_socket": 1,
- "ram": 1024,
- "svms": 1,
- "guestOS": "centos-64",
- "max_cpus": 12,
- "min_ram": 256,
- "max_ram": 262144,
- "vnc_keymap": null,
- "uuid": null,
- "disks": [
- {
- "id": "disk-19861359-37668995-scsi-0-0",
- "size": 30720,
- "type": "SCSI",
- "controller": "0",
- "lun": "0"
- }
- ],
- "storage": 30720,
- "upgradable": false,
- "instance_type": null,
- "time_sync_enabled": true,
- "rtc_start_time": null,
- "copy_paste_enabled": true,
- "nested_virtualization": false,
- "architecture": "x86"
- },
- "error": false,
- "error_details": false,
- "asset_id": "1",
- "hardware_version": 11,
- "max_hardware_version": 11,
- "interfaces": [
- {
- "id": "nic-19861359-37668995-0",
- "ip": "10.0.0.1",
- "hostname": "centos7sx64",
- "mac": "00:50:56:2B:87:F5",
- "services_count": 0,
- "services": [
- {
- "id": "3389",
- "internal_port": 3389,
- "external_ip": "76.191.118.29",
- "external_port": 12345
- }
- ],
- "public_ips_count": 0,
- "public_ips": [
- {
- "1.2.3.4": "5.6.7.8"
- }
- ],
- "vm_id": "36858580",
- "vm_name": "CentOS 7 Server x64",
- "status": "Powered off",
- "network_id": "23429874",
- "network_name": "Default Network",
- "network_url": "https://cloud.skytap.com/v2/configurations/456/networks/23429874",
- "network_type": "automatic",
- "network_subnet": "10.0.0.0/24",
- "nic_type": "vmxnet3",
- "secondary_ips": [
- {
- "id": "10.0.2.2",
- "address": "10.0.2.2"
- }
- ],
- "public_ip_attachments": [
- {
- "id": 1,
- "public_ip_attachment_key": 2,
- "address": "1.2.3.4",
- "connect_type": 1,
- "hostname": "host1",
- "dns_name": "host.com",
- "public_ip_key": "5.6.7.8"
- }
- ]
- }
- ],
- "notes": [
- {
- "id": "5377708",
- "user_id": 1,
- "user": {
- "id": "1",
- "url": "https://cloud.skytap.com/v2/users/1",
- "first_name": "Joe",
- "last_name": "Bloggs",
- "login_name": "Joe.Bloggs@opencredo",
- "email": "Joe.Bloggs@opencredo.com",
- "title": "",
- "deleted": false
- },
- "created_at": "2018/10/11 15:27:45 +0100",
- "updated_at": "2018/10/11 15:27:45 +0100",
- "text": "a note"
- }
- ],
- "labels": [
- {
- "id": "43892",
- "value": "test vm",
- "label_category": "test multi",
- "label_category_id": "7704",
- "label_category_single_value": false
- }
- ],
- "credentials": [
- {
- "id": "35158632",
- "text": "user/pass"
- }
- ],
- "desktop_resizable": true,
- "local_mouse_cursor": true,
- "maintenance_lock_engaged": false,
- "region_backend": "skytap",
- "created_at": "2018/10/11 15:42:26 +0100",
- "supports_suspend": true,
- "can_change_object_state": true,
- "containers": [
- {
- "id": 1122,
- "cid": "123456789abcdefghijk123456789abcdefghijk123456789abcdefghijk",
- "name": "nginxtest1",
- "image": "nginx:latest",
- "created_at": "2016/06/16 11:58:50 -0700",
- "last_run": "2016/06/16 11:58:51 -0700",
- "can_change_state": true,
- "can_delete": true,
- "status": "running",
- "privileged": false,
- "vm_id": 111000,
- "vm_name": "Docker VM1",
- "vm_runstate": "running",
- "configuration_id": 123456
- }
- ],
- "configuration_url": "https://cloud.skytap.com/v2/configurations/456"
- }
- ],
- "networks": [
- {
- "id": "1234567",
- "url": "https://cloud.skytap.com/configurations/1111111/networks/123467",
- "name": "Network 1",
- "network_type": "automatic",
- "subnet": "10.0.0.0/24",
- "subnet_addr": "10.0.0.0",
- "subnet_size": 24,
- "gateway": "10.0.0.254",
- "primary_nameserver": "8.8.8.8",
- "secondary_nameserver": "8.8.8.9",
- "region": "US-West",
- "domain": "sampledomain.com",
- "vpn_attachments": [
- {
- "id": "111111-vpn-1234567",
- "connected": false,
- "network": {
- "id": "1111111",
- "subnet": "10.0.0.0/24",
- "network_name": "Network 1",
- "configuration_id": "1212121"
- },
- "vpn": {
- "id": "vpn-1234567",
- "name": "CorpNet",
- "enabled": true,
- "nat_enabled": true,
- "remote_subnets": "10.10.0.0/24, 10.10.1.0/24, 10.10.2.0/24, 10.10.4.0/24",
- "remote_peer_ip": "199.199.199.199",
- "can_reconnect": true
- }
- },
- {
- "id": "111111-vpn-1234555",
- "connected": false,
- "network": {
- "id": "1111111",
- "subnet": "10.0.0.0/24",
- "network_name": "Network 1",
- "configuration_id": "1212121"
- },
- "vpn": {
- "id": "vpn-1234555",
- "name": "Offsite DC",
- "enabled": true,
- "nat_enabled": true,
- "remote_subnets": "10.10.0.0/24, 10.10.1.0/24, 10.10.2.0/24, 10.10.4.0/24",
- "remote_peer_ip": "188.188.188.188",
- "can_reconnect": true
- }
- }
- ],
- "tunnelable": false,
- "tunnels": [
- {
- "id": "tunnel-123456-789011",
- "status": "not_busy",
- "error": null,
- "source_network": {
- "id": "000000",
- "url": "https://cloud.skytap.com/configurations/249424/networks/0000000",
- "name": "Network 1",
- "network_type": "automatic",
- "subnet": "10.0.0.0/24",
- "subnet_addr": "10.0.0.0",
- "subnet_size": 24,
- "gateway": "10.0.0.254",
- "primary_nameserver": null,
- "secondary_nameserver": null,
- "region": "US-West",
- "domain": "skytap.example",
- "vpn_attachments": []
- },
- "target_network": {
- "id": "111111",
- "url": "https://cloud.skytap.com/configurations/808216/networks/111111",
- "name": "Network 2",
- "network_type": "automatic",
- "subnet": "10.0.2.0/24",
- "subnet_addr": "10.0.2.0",
- "subnet_size": 24,
- "gateway": "10.0.2.254",
- "primary_nameserver": null,
- "secondary_nameserver": null,
- "region": "US-West",
- "domain": "test.net",
- "vpn_attachments": []
- }
- }
- ]
- }
- ],
- "containers_count": 0,
- "container_hosts_count": 0,
- "platform_errors": [
- "platform error1"
- ],
- "svms_by_architecture": {
- "x86": 1,
- "power": 0
- },
- "all_vms_support_suspend": true,
- "shutdown_on_idle": null,
- "shutdown_at_time": null,
- "auto_shutdown_description": "Shutting down!"
-}`
-
func TestCreateEnvironment(t *testing.T) {
skytap, hs, handler := createClient(t)
defer hs.Close()
- var createPhase = true
+ requestCounter := 0
*handler = func(rw http.ResponseWriter, req *http.Request) {
- if createPhase {
+ log.Printf("Request: (%d)\n", requestCounter)
+ if requestCounter == 0 {
if req.URL.Path != "/configurations" {
t.Error("Bad path")
}
@@ -379,9 +31,27 @@ func TestCreateEnvironment(t *testing.T) {
body, err := ioutil.ReadAll(req.Body)
assert.Nil(t, err)
assert.JSONEq(t, fmt.Sprintf(`{"template_id":%q, "project_id":%d, "description":"test environment"}`, "12345", 12345), string(body))
- io.WriteString(rw, `{"id": "456"}`)
- createPhase = false
- } else {
+ _, err = io.WriteString(rw, `{"id": "456"}`)
+ assert.NoError(t, err)
+ } else if requestCounter == 1 {
+ assert.Equal(t, "/v2/configurations/456", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, string(readTestFile(t, "exampleEnvironment.json")))
+ assert.NoError(t, err)
+ } else if requestCounter == 2 {
+ assert.Equal(t, "/v2/configurations/456", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, string(readTestFile(t, "exampleEnvironment.json")))
+ assert.NoError(t, err)
+ } else if requestCounter == 3 {
+ assert.Equal(t, "/v2/configurations/456", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, string(readTestFile(t, "exampleEnvironment.json")))
+ assert.NoError(t, err)
+ } else if requestCounter == 4 {
if req.URL.Path != "/v2/configurations/456" {
t.Error("Bad path")
}
@@ -392,8 +62,23 @@ func TestCreateEnvironment(t *testing.T) {
assert.Nil(t, err)
assert.JSONEq(t, `{"description": "test environment", "runstate":"running"}`, string(body))
- io.WriteString(rw, exampleEnvironment)
+ _, err = io.WriteString(rw, string(readTestFile(t, "exampleEnvironment.json")))
+ assert.NoError(t, err)
+
+ } else if requestCounter == 5 {
+ assert.Equal(t, "/v2/configurations/456", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ var envRunning Environment
+ err := json.Unmarshal(readTestFile(t, "exampleEnvironment.json"), &envRunning)
+ assert.NoError(t, err)
+ envRunning.Runstate = environmentRunStateToPtr(EnvironmentRunstateRunning)
+ b, err := json.Marshal(&envRunning)
+ assert.Nil(t, err)
+ _, err = io.WriteString(rw, string(b))
+ assert.NoError(t, err)
}
+ requestCounter++
}
opts := &CreateEnvironmentRequest{
@@ -408,9 +93,11 @@ func TestCreateEnvironment(t *testing.T) {
var environmentExpected Environment
- err = json.Unmarshal([]byte(exampleEnvironment), &environmentExpected)
+ err = json.Unmarshal(readTestFile(t, "exampleEnvironment.json"), &environmentExpected)
assert.Equal(t, environmentExpected, *environment)
+
+ assert.Equal(t, 6, requestCounter)
}
func TestReadEnvironment(t *testing.T) {
@@ -424,7 +111,8 @@ func TestReadEnvironment(t *testing.T) {
if req.Method != "GET" {
t.Error("Bad method")
}
- io.WriteString(rw, exampleEnvironment)
+ _, err := io.WriteString(rw, string(readTestFile(t, "exampleEnvironment.json")))
+ assert.NoError(t, err)
}
environment, err := skytap.Environments.Get(context.Background(), "456")
@@ -432,7 +120,7 @@ func TestReadEnvironment(t *testing.T) {
assert.Nil(t, err)
var environmentExpected Environment
- err = json.Unmarshal([]byte(exampleEnvironment), &environmentExpected)
+ err = json.Unmarshal(readTestFile(t, "exampleEnvironment.json"), &environmentExpected)
assert.Equal(t, environmentExpected, *environment)
}
@@ -442,24 +130,48 @@ func TestUpdateEnvironment(t *testing.T) {
defer hs.Close()
var environment Environment
- json.Unmarshal([]byte(exampleEnvironment), &environment)
+ err := json.Unmarshal(readTestFile(t, "exampleEnvironment.json"), &environment)
+ assert.NoError(t, err)
*environment.Description = "updated environment"
- bytes, err := json.Marshal(&environment)
- assert.Nil(t, err)
+ requestCounter := 0
*handler = func(rw http.ResponseWriter, req *http.Request) {
- if req.URL.Path != "/v2/configurations/456" {
- t.Error("Bad path")
- }
- if req.Method != "PUT" {
- t.Error("Bad method")
- }
- body, err := ioutil.ReadAll(req.Body)
- assert.Nil(t, err)
- assert.JSONEq(t, `{"description": "updated environment"}`, string(body))
+ log.Printf("Request: (%d)\n", requestCounter)
+ if requestCounter == 0 {
+ assert.Equal(t, "/v2/configurations/456", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, string(readTestFile(t, "exampleEnvironment.json")))
+ assert.NoError(t, err)
+ } else if requestCounter == 1 {
+ if req.URL.Path != "/v2/configurations/456" {
+ t.Error("Bad path")
+ }
+ if req.Method != "PUT" {
+ t.Error("Bad method")
+ }
+ body, err := ioutil.ReadAll(req.Body)
+ assert.Nil(t, err)
+ assert.JSONEq(t, `{"description": "updated environment"}`, string(body))
- io.WriteString(rw, string(bytes))
+ b, err := json.Marshal(&environment)
+ assert.Nil(t, err)
+ _, err = io.WriteString(rw, string(b))
+ assert.NoError(t, err)
+ } else if requestCounter == 2 {
+ assert.Equal(t, "/v2/configurations/456", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ var envRunning Environment
+ err := json.Unmarshal(readTestFile(t, "exampleEnvironment.json"), &envRunning)
+ assert.NoError(t, err)
+ envRunning.Description = strToPtr("updated environment")
+ b, err := json.Marshal(&envRunning)
+ assert.Nil(t, err)
+ _, err = io.WriteString(rw, string(b))
+ }
+ requestCounter++
}
opts := &UpdateEnvironmentRequest{
@@ -470,23 +182,38 @@ func TestUpdateEnvironment(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, environment, *environmentUpdate)
+
+ assert.Equal(t, 3, requestCounter)
}
func TestDeleteEnvironment(t *testing.T) {
skytap, hs, handler := createClient(t)
defer hs.Close()
+ requestCounter := 0
+
*handler = func(rw http.ResponseWriter, req *http.Request) {
- if req.URL.Path != "/configurations/456" {
- t.Error("Bad path")
- }
- if req.Method != "DELETE" {
- t.Error("Bad method")
+ log.Printf("Request: (%d)\n", requestCounter)
+ if requestCounter == 0 {
+ assert.Equal(t, "/v2/configurations/456", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, string(readTestFile(t, "exampleEnvironment.json")))
+ assert.NoError(t, err)
+ } else if requestCounter == 1 {
+ if req.URL.Path != "/configurations/456" {
+ t.Error("Bad path")
+ }
+ if req.Method != "DELETE" {
+ t.Error("Bad method")
+ }
}
+ requestCounter++
}
err := skytap.Environments.Delete(context.Background(), "456")
assert.Nil(t, err)
+ assert.Equal(t, 2, requestCounter)
}
func TestListEnvironments(t *testing.T) {
@@ -500,7 +227,8 @@ func TestListEnvironments(t *testing.T) {
if req.Method != "GET" {
t.Error("Bad method")
}
- io.WriteString(rw, fmt.Sprintf(`[%+v]`, exampleEnvironment))
+ _, err := io.WriteString(rw, fmt.Sprintf(`[%+v]`, string(readTestFile(t, "exampleEnvironment.json"))))
+ assert.NoError(t, err)
}
result, err := skytap.Environments.List(context.Background())
@@ -517,3 +245,107 @@ func TestListEnvironments(t *testing.T) {
assert.True(t, found)
}
+
+func TestCompareEnvironmentCreateTrue(t *testing.T) {
+ exampleEnvironment := readTestFile(t, "exampleEnvironment.json")
+
+ var environment Environment
+ err := json.Unmarshal(exampleEnvironment, &environment)
+ assert.NoError(t, err)
+ opts := CreateEnvironmentRequest{
+ TemplateID: strToPtr("12345"),
+ ProjectID: intToPtr(12345),
+ }
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ _, err := io.WriteString(rw, string(exampleEnvironment))
+ assert.NoError(t, err)
+ }
+
+ message, ok := opts.compareResponse(context.Background(), skytap, &environment, envRunStateNotBusy("123"))
+ assert.True(t, ok)
+ assert.Equal(t, "", message)
+}
+
+func TestCompareEnvironmentCreateFalse(t *testing.T) {
+ exampleEnvironment := readTestFile(t, "exampleEnvironment.json")
+
+ var environment Environment
+ err := json.Unmarshal(exampleEnvironment, &environment)
+ assert.NoError(t, err)
+ opts := CreateEnvironmentRequest{
+ TemplateID: strToPtr("12345"),
+ ProjectID: intToPtr(12345),
+ }
+
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ var envRunning Environment
+ err := json.Unmarshal(exampleEnvironment, &envRunning)
+ assert.NoError(t, err)
+ envRunning.Runstate = environmentRunStateToPtr(EnvironmentRunstateBusy)
+ b, err := json.Marshal(&envRunning)
+ assert.Nil(t, err)
+ _, err = io.WriteString(rw, string(b))
+
+ assert.NoError(t, err)
+ }
+ message, ok := opts.compareResponse(context.Background(), skytap, &environment, envRunStateNotBusy("123"))
+ assert.False(t, ok)
+ assert.Equal(t, "environment not ready", message)
+}
+
+func TestCompareEnvironmentUpdateTrue(t *testing.T) {
+ exampleEnvironment := readTestFile(t, "exampleEnvironment.json")
+
+ var environment Environment
+ err := json.Unmarshal(exampleEnvironment, &environment)
+ assert.NoError(t, err)
+ opts := UpdateEnvironmentRequest{
+ Description: strToPtr(*environment.Description),
+ }
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ _, err := io.WriteString(rw, string(exampleEnvironment))
+ assert.NoError(t, err)
+ }
+
+ message, ok := opts.compareResponse(context.Background(), skytap, &environment, envRunStateNotBusy("123"))
+ assert.True(t, ok)
+ assert.Equal(t, "", message)
+}
+
+func TestCompareEnvironmentUpdateFalse(t *testing.T) {
+ exampleEnvironment := readTestFile(t, "exampleEnvironment.json")
+
+ var environment Environment
+ err := json.Unmarshal(exampleEnvironment, &environment)
+ assert.NoError(t, err)
+ environment.Runstate = environmentRunStateToPtr(EnvironmentRunstateBusy)
+ opts := UpdateEnvironmentRequest{
+ Runstate: environmentRunStateToPtr(EnvironmentRunstateStopped),
+ }
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ var envRunning Environment
+ err := json.Unmarshal(exampleEnvironment, &envRunning)
+ assert.NoError(t, err)
+ envRunning.Runstate = environmentRunStateToPtr(EnvironmentRunstateRunning)
+ b, err := json.Marshal(&envRunning)
+ assert.Nil(t, err)
+ _, err = io.WriteString(rw, string(b))
+
+ assert.NoError(t, err)
+ }
+ message, ok := opts.compareResponse(context.Background(), skytap, &environment, envRunStateNotBusy("123"))
+ assert.False(t, ok)
+ assert.Equal(t, "environment not ready", message)
+}
diff --git a/skytap/interface.go b/skytap/interface.go
index 4d148e5..3acd559 100644
--- a/skytap/interface.go
+++ b/skytap/interface.go
@@ -1,6 +1,10 @@
package skytap
-import "context"
+import (
+ "context"
+ "fmt"
+ "log"
+)
// Default URL paths
const (
@@ -150,7 +154,7 @@ func (s *InterfacesServiceClient) List(ctx context.Context, environmentID string
}
var interfaceListResponse InterfaceListResult
- _, err = s.client.do(ctx, req, &interfaceListResponse.Value)
+ _, err = s.client.do(ctx, req, &interfaceListResponse.Value, nil, nil)
if err != nil {
return nil, err
}
@@ -169,7 +173,7 @@ func (s *InterfacesServiceClient) Get(ctx context.Context, environmentID string,
}
var networkInterface Interface
- _, err = s.client.do(ctx, req, &networkInterface)
+ _, err = s.client.do(ctx, req, &networkInterface, nil, nil)
if err != nil {
return nil, err
}
@@ -188,7 +192,7 @@ func (s *InterfacesServiceClient) Create(ctx context.Context, environmentID stri
}
var createdInterface Interface
- _, err = s.client.do(ctx, req, &createdInterface)
+ _, err = s.client.do(ctx, req, &createdInterface, vmRequestRunStateStopped(environmentID, vmID), nicType)
if err != nil {
return nil, err
}
@@ -207,7 +211,7 @@ func (s *InterfacesServiceClient) Attach(ctx context.Context, environmentID stri
}
var updatedInterface Interface
- _, err = s.client.do(ctx, req, &updatedInterface)
+ _, err = s.client.do(ctx, req, &updatedInterface, vmRunStateNotBusy(environmentID, vmID), networkID)
if err != nil {
return nil, err
}
@@ -226,7 +230,7 @@ func (s *InterfacesServiceClient) Update(ctx context.Context, environmentID stri
}
var updatedInterface Interface
- _, err = s.client.do(ctx, req, &updatedInterface)
+ _, err = s.client.do(ctx, req, &updatedInterface, vmRequestRunStateStopped(environmentID, vmID), opts)
if err != nil {
return nil, err
}
@@ -244,10 +248,106 @@ func (s *InterfacesServiceClient) Delete(ctx context.Context, environmentID stri
return err
}
- _, err = s.client.do(ctx, req, nil)
+ _, err = s.client.do(ctx, req, nil, vmRequestRunStateStopped(environmentID, vmID), nil)
if err != nil {
return err
}
return nil
}
+
+func (payload *CreateInterfaceRequest) compareResponse(ctx context.Context, c *Client, v interface{}, state *environmentVMRunState) (string, bool) {
+ if interfaceOriginal, ok := v.(*Interface); ok {
+ adapter, err := c.Interfaces.Get(ctx, *state.environmentID, *state.vmID, *interfaceOriginal.ID)
+ if err != nil {
+ return requestNotAsExpected, false
+ }
+ actual := CreateInterfaceRequest{
+ adapter.NICType,
+ }
+ if payload.string() == actual.string() {
+ return "", true
+ }
+ return "network adapter not ready", false
+ }
+ log.Printf("[ERROR] SDK interface comparison not possible on (%v)\n", v)
+ return requestNotAsExpected, false
+}
+
+func (payload *AttachInterfaceRequest) compareResponse(ctx context.Context, c *Client, v interface{}, state *environmentVMRunState) (string, bool) {
+ if interfaceOriginal, ok := v.(*Interface); ok {
+ adapter, err := c.Interfaces.Get(ctx, *state.environmentID, *state.vmID, *interfaceOriginal.ID)
+ if err != nil {
+ return requestNotAsExpected, false
+ }
+ actual := AttachInterfaceRequest{
+ adapter.NetworkID,
+ }
+ if payload.string() == actual.string() {
+ return "", true
+ }
+ return "network adapter not ready", false
+ }
+ log.Printf("[ERROR] SDK interface comparison not possible on (%v)\n", v)
+ return requestNotAsExpected, false
+}
+
+func (payload *UpdateInterfaceRequest) compareResponse(ctx context.Context, c *Client, v interface{}, state *environmentVMRunState) (string, bool) {
+ if interfaceOriginal, ok := v.(*Interface); ok {
+ adapter, err := c.Interfaces.Get(ctx, *state.environmentID, *state.vmID, *interfaceOriginal.ID)
+ if err != nil {
+ return requestNotAsExpected, false
+ }
+ actual := UpdateInterfaceRequest{
+ adapter.IP,
+ adapter.Hostname,
+ }
+ if payload.string() == actual.string() {
+ return "", true
+ }
+ return "network adapter not ready", false
+ }
+ log.Printf("[ERROR] SDK interface comparison not possible on (%v)\n", v)
+ return requestNotAsExpected, false
+}
+
+func (payload *CreateInterfaceRequest) string() string {
+ nicType := ""
+
+ if payload.NICType != nil {
+ nicType = string(*payload.NICType)
+ }
+ s := fmt.Sprintf("%s",
+ nicType)
+ log.Printf("[DEBUG] SDK create interface payload (%s)\n", s)
+ return s
+}
+
+func (payload *AttachInterfaceRequest) string() string {
+ networkID := ""
+
+ if payload.NetworkID != nil {
+ networkID = *payload.NetworkID
+ }
+ s := fmt.Sprintf("%s",
+ networkID)
+ log.Printf("[DEBUG] SDK attach interface payload (%s)\n", s)
+ return s
+}
+
+func (payload *UpdateInterfaceRequest) string() string {
+ ip := ""
+ hostname := ""
+
+ if payload.IP != nil {
+ ip = string(*payload.IP)
+ }
+ if payload.Hostname != nil {
+ hostname = string(*payload.Hostname)
+ }
+ s := fmt.Sprintf("%s%s",
+ ip,
+ hostname)
+ log.Printf("[DEBUG] SDK update interface payload (%s)\n", s)
+ return s
+}
diff --git a/skytap/interface_test.go b/skytap/interface_test.go
index 8587c77..fdeac1d 100644
--- a/skytap/interface_test.go
+++ b/skytap/interface_test.go
@@ -6,119 +6,48 @@ import (
"fmt"
"io"
"io/ioutil"
+ "log"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
-const exampleCreateInterfaceRequest = `{
- "nic_type": "e1000"
-}`
-
-const exampleAttachInterfaceRequest = `{
- "network_id": "23917287"
-}`
-
-const exampleUpdateInterfaceRequest = `{
- "ip": "10.0.0.1",
- "hostname": "updated-hostname"
-}`
-
-const exampleCreateInterfaceResponse = `{
- "id": "nic-%d",
- "ip": null,
- "hostname": null,
- "mac": "00:50:56:07:40:3F",
- "services_count": 0,
- "services": [],
- "public_ips_count": 0,
- "public_ips": [],
- "vm_id": "%d",
- "vm_name": "Windows Server 2016 Standard",
- "status": "Powered off",
- "nic_type": "e1000",
- "secondary_ips": [],
- "public_ip_attachments": []
-}`
-
-const exampleAttachInterfaceResponse = `{
- "id": "nic-20250403-38374059-4",
- "ip": "192.168.0.5",
- "hostname": "host-3",
- "mac": "00:50:56:05:3F:84",
- "services_count": 0,
- "services": [],
- "public_ips_count": 0,
- "public_ips": [],
- "vm_id": "37533321",
- "vm_name": "CentOS 6 Desktop x64",
- "status": "Powered off",
- "network_id": "23922457",
- "network_name": "tftest-network-1",
- "network_url": "https://cloud.skytap.com/v2/configurations/40071754/networks/23922457",
- "network_type": "automatic",
- "network_subnet": "192.168.0.0/16",
- "nic_type": "vmxnet3",
- "secondary_ips": [],
- "public_ip_attachments": []
-}`
-
-const exampleInterfaceListResponse = `[
- {
- "id": "nic-20246343-38367563-0",
- "ip": "192.168.0.1",
- "hostname": "wins2016s",
- "mac": "00:50:56:11:7D:D9",
- "services_count": 0,
- "services": [],
- "public_ips_count": 0,
- "public_ips": [],
- "vm_id": "37527239",
- "vm_name": "Windows Server 2016 Standard",
- "status": "Running",
- "network_id": "23917287",
- "network_name": "tftest-network-1",
- "network_url": "https://cloud.skytap.com/v2/configurations/40064014/networks/23917287",
- "network_type": "automatic",
- "network_subnet": "192.168.0.0/16",
- "nic_type": "vmxnet3",
- "secondary_ips": [],
- "public_ip_attachments": []
- },
- {
- "id": "nic-20246343-38367563-5",
- "ip": null,
- "hostname": null,
- "mac": "00:50:56:07:40:3F",
- "services_count": 0,
- "services": [],
- "public_ips_count": 0,
- "public_ips": [],
- "vm_id": "37527239",
- "vm_name": "Windows Server 2016 Standard",
- "status": "Running",
- "nic_type": "e1000",
- "secondary_ips": [],
- "public_ip_attachments": []
- }
-]`
-
func TestCreateInterface(t *testing.T) {
- exampleInterface := fmt.Sprintf(exampleCreateInterfaceResponse, 456, 123)
+ response := fmt.Sprintf(string(readTestFile(t, "VMResponse.json")), 456)
+ exampleInterface := fmt.Sprintf(string(readTestFile(t, "exampleCreateInterfaceResponse.json")), 456, 123)
skytap, hs, handler := createClient(t)
defer hs.Close()
- *handler = func(rw http.ResponseWriter, req *http.Request) {
- assert.Equal(t, "/v2/configurations/123/vms/456/interfaces", req.URL.Path, "Bad path")
- assert.Equal(t, "POST", req.Method, "Bad method")
-
- body, err := ioutil.ReadAll(req.Body)
- assert.Nil(t, err, "Bad request body")
- assert.JSONEq(t, exampleCreateInterfaceRequest, string(body), "Bad request body")
+ requestCounter := 0
- io.WriteString(rw, exampleInterface)
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ log.Printf("Request: (%d)\n", requestCounter)
+ if requestCounter == 0 {
+ assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, string(response))
+ assert.NoError(t, err)
+ } else if requestCounter == 1 {
+ assert.Equal(t, "/v2/configurations/123/vms/456/interfaces", req.URL.Path, "Bad path")
+ assert.Equal(t, "POST", req.Method, "Bad method")
+
+ body, err := ioutil.ReadAll(req.Body)
+ assert.Nil(t, err, "Bad request body")
+ assert.JSONEq(t, string(readTestFile(t, "exampleCreateInterfaceRequest.json")), string(body), "Bad request body")
+
+ _, err = io.WriteString(rw, exampleInterface)
+ assert.NoError(t, err)
+ } else if requestCounter == 2 {
+ assert.Equal(t, "/v2/configurations/123/vms/456/interfaces/nic-456", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, exampleInterface)
+ assert.NoError(t, err)
+ }
+ requestCounter++
}
nicType := &CreateInterfaceRequest{
NICType: nicTypeToPtr(NICTypeE1000),
@@ -130,23 +59,45 @@ func TestCreateInterface(t *testing.T) {
var interfaceExpected Interface
err = json.Unmarshal([]byte(exampleInterface), &interfaceExpected)
assert.Equal(t, interfaceExpected, *networkInterface, "Bad interface")
+
+ assert.Equal(t, 3, requestCounter)
}
func TestAttachInterface(t *testing.T) {
- exampleInterface := fmt.Sprintf(exampleAttachInterfaceResponse)
+ response := fmt.Sprintf(string(readTestFile(t, "VMResponse.json")), 456)
+ exampleInterface := fmt.Sprintf(string(readTestFile(t, "exampleAttachInterfaceResponse.json")))
skytap, hs, handler := createClient(t)
defer hs.Close()
- *handler = func(rw http.ResponseWriter, req *http.Request) {
- assert.Equal(t, "/v2/configurations/123/vms/456/interfaces/789", req.URL.Path, "Bad path")
- assert.Equal(t, "PUT", req.Method, "Bad method")
+ requestCounter := 0
- body, err := ioutil.ReadAll(req.Body)
- assert.Nil(t, err, "Bad request body")
- assert.JSONEq(t, exampleAttachInterfaceRequest, string(body), "Bad request body")
-
- io.WriteString(rw, exampleInterface)
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ log.Printf("Request: (%d)\n", requestCounter)
+ if requestCounter == 0 {
+ assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, string(response))
+ assert.NoError(t, err)
+ } else if requestCounter == 1 {
+ assert.Equal(t, "/v2/configurations/123/vms/456/interfaces/789", 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, string(readTestFile(t, "exampleAttachInterfaceRequest.json")), string(body), "Bad request body")
+
+ _, err = io.WriteString(rw, exampleInterface)
+ assert.NoError(t, err)
+ } else if requestCounter == 2 {
+ assert.Equal(t, "/v2/configurations/123/vms/456/interfaces/nic-20250403-38374059-4", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, exampleInterface)
+ assert.NoError(t, err)
+ }
+ requestCounter++
}
networkID := &AttachInterfaceRequest{
NetworkID: strToPtr("23917287"),
@@ -158,10 +109,12 @@ func TestAttachInterface(t *testing.T) {
var interfaceExpected Interface
err = json.Unmarshal([]byte(exampleInterface), &interfaceExpected)
assert.Equal(t, interfaceExpected, *networkInterface, "Bad interface")
+
+ assert.Equal(t, 3, requestCounter)
}
func TestReadInterface(t *testing.T) {
- exampleInterface := fmt.Sprintf(exampleCreateInterfaceResponse, 456, 123)
+ exampleInterface := fmt.Sprintf(string(readTestFile(t, "exampleCreateInterfaceResponse.json")), 456, 123)
skytap, hs, handler := createClient(t)
defer hs.Close()
@@ -170,7 +123,8 @@ func TestReadInterface(t *testing.T) {
assert.Equal(t, "/v2/configurations/123/vms/456/interfaces/789", req.URL.Path, "Bad path")
assert.Equal(t, "GET", req.Method, "Bad method")
- io.WriteString(rw, exampleInterface)
+ _, err := io.WriteString(rw, exampleInterface)
+ assert.NoError(t, err)
}
networkInterface, err := skytap.Interfaces.Get(context.Background(), "123", "456", "789")
@@ -182,29 +136,57 @@ func TestReadInterface(t *testing.T) {
}
func TestUpdateInterface(t *testing.T) {
- exampleInterface := fmt.Sprintf(exampleAttachInterfaceResponse)
+ response := fmt.Sprintf(string(readTestFile(t, "VMResponse.json")), 456)
+ exampleInterface := fmt.Sprintf(string(readTestFile(t, "exampleUpdateInterfaceResponse.json")))
skytap, hs, handler := createClient(t)
defer hs.Close()
- var networkInterface Interface
- json.Unmarshal([]byte(exampleInterface), &networkInterface)
- networkInterface.Hostname = strToPtr("updated-hostname")
-
- bytes, err := json.Marshal(&networkInterface)
- assert.Nil(t, err, "Bad interface")
+ requestCounter := 0
*handler = func(rw http.ResponseWriter, req *http.Request) {
- assert.Equal(t, "/v2/configurations/123/vms/456/interfaces/789", 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, exampleUpdateInterfaceRequest, string(body), "Bad request body")
-
- io.WriteString(rw, string(bytes))
+ log.Printf("Request: (%d)\n", requestCounter)
+ if requestCounter == 0 {
+ assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, string(response))
+ assert.NoError(t, err)
+ } else if requestCounter == 1 {
+ assert.Equal(t, "/v2/configurations/123/vms/456/interfaces/789", 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, string(readTestFile(t, "exampleUpdateInterfaceRequest.json")), string(body), "Bad request body")
+
+ _, err = io.WriteString(rw, string(readTestFile(t, "exampleUpdateInterfaceResponse.json")))
+ assert.NoError(t, err)
+ } else if requestCounter == 2 {
+ assert.Equal(t, "/v2/configurations/123/vms/456/interfaces/nic-20250403-38374059-4", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ var networkInterface Interface
+ err := json.Unmarshal([]byte(exampleInterface), &networkInterface)
+ assert.NoError(t, err)
+ networkInterface.IP = strToPtr("10.0.0.1")
+ networkInterface.Hostname = strToPtr("updated-hostname")
+ b, err := json.Marshal(&networkInterface)
+ assert.NoError(t, err)
+ _, err = io.WriteString(rw, string(b))
+ assert.NoError(t, err)
+ }
+ requestCounter++
}
+ var networkInterface Interface
+ err := json.Unmarshal([]byte(exampleInterface), &networkInterface)
+ assert.NoError(t, err)
+ networkInterface.IP = strToPtr("10.0.0.1")
+ networkInterface.Hostname = strToPtr("updated-hostname")
+ _, err = json.Marshal(&networkInterface)
+ assert.Nil(t, err, "Bad interface")
+
opts := &UpdateInterfaceRequest{
Hostname: strToPtr(*networkInterface.Hostname),
IP: strToPtr("10.0.0.1"),
@@ -213,19 +195,38 @@ func TestUpdateInterface(t *testing.T) {
assert.Nil(t, err, "Bad API method")
assert.Equal(t, networkInterface, *interfaceUpdate, "Bad interface")
+
+ assert.Equal(t, 3, requestCounter)
}
func TestDeleteInterface(t *testing.T) {
+ response := fmt.Sprintf(string(readTestFile(t, "VMResponse.json")), 456)
+
skytap, hs, handler := createClient(t)
defer hs.Close()
+ requestCounter := 0
+
*handler = func(rw http.ResponseWriter, req *http.Request) {
- assert.Equal(t, "/v2/configurations/123/vms/456/interfaces/789", req.URL.Path, "Bad path")
- assert.Equal(t, "DELETE", req.Method, "Bad method")
+ log.Printf("Request: (%d)\n", requestCounter)
+ if requestCounter == 0 {
+ assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, string(response))
+ assert.NoError(t, err)
+ } else if requestCounter == 1 {
+ log.Printf("Request: (%d)\n", requestCounter)
+ assert.Equal(t, "/v2/configurations/123/vms/456/interfaces/789", req.URL.Path, "Bad path")
+ assert.Equal(t, "DELETE", req.Method, "Bad method")
+ }
+ requestCounter++
}
err := skytap.Interfaces.Delete(context.Background(), "123", "456", "789")
assert.Nil(t, err, "Bad API method")
+
+ assert.Equal(t, 2, requestCounter)
}
func TestListInterfaces(t *testing.T) {
@@ -236,7 +237,8 @@ func TestListInterfaces(t *testing.T) {
assert.Equal(t, "/v2/configurations/123/vms/456/interfaces", req.URL.Path, "Bad path")
assert.Equal(t, "GET", req.Method, "Bad method")
- io.WriteString(rw, exampleInterfaceListResponse)
+ _, err := io.WriteString(rw, string(readTestFile(t, "exampleInterfaceListResponse.json")))
+ assert.NoError(t, err)
}
result, err := skytap.Interfaces.List(context.Background(), "123", "456")
@@ -251,3 +253,131 @@ func TestListInterfaces(t *testing.T) {
}
assert.True(t, found, "Interface not found")
}
+
+func TestCompareInterfaceCreateTrue(t *testing.T) {
+ exampleInterface := fmt.Sprintf(string(readTestFile(t, "exampleCreateInterfaceResponse.json")), 456, 123)
+
+ var adapter Interface
+ err := json.Unmarshal([]byte(exampleInterface), &adapter)
+ assert.NoError(t, err)
+ opts := CreateInterfaceRequest{
+ NICType: nicTypeToPtr(NICTypeE1000),
+ }
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ _, err := io.WriteString(rw, fmt.Sprintf(exampleInterface))
+ assert.NoError(t, err)
+ }
+ message, ok := opts.compareResponse(context.Background(), skytap, &adapter, vmRequestRunStateStopped("123", "456"))
+ assert.True(t, ok)
+ assert.Equal(t, "", message)
+}
+
+func TestCompareInterfaceCreateFalse(t *testing.T) {
+ exampleInterface := fmt.Sprintf(string(readTestFile(t, "exampleCreateInterfaceResponse.json")), 456, 123)
+
+ var adapter Interface
+ err := json.Unmarshal([]byte(exampleInterface), &adapter)
+ assert.NoError(t, err)
+ opts := CreateInterfaceRequest{
+ NICType: nicTypeToPtr(NICTypeE1000E),
+ }
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ _, err := io.WriteString(rw, fmt.Sprintf(exampleInterface))
+ assert.NoError(t, err)
+ }
+ message, ok := opts.compareResponse(context.Background(), skytap, &adapter, vmRequestRunStateStopped("123", "456"))
+ assert.False(t, ok)
+ assert.Equal(t, "network adapter not ready", message)
+}
+
+func TestCompareInterfaceAttachTrue(t *testing.T) {
+ exampleInterface := fmt.Sprintf(string(readTestFile(t, "exampleAttachInterfaceResponse.json")))
+
+ var adapter Interface
+ err := json.Unmarshal([]byte(exampleInterface), &adapter)
+ assert.NoError(t, err)
+ opts := AttachInterfaceRequest{
+ strToPtr("23917287"),
+ }
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ _, err := io.WriteString(rw, fmt.Sprintf(exampleInterface))
+ assert.NoError(t, err)
+ }
+ message, ok := opts.compareResponse(context.Background(), skytap, &adapter, vmRequestRunStateStopped("123", "456"))
+ assert.True(t, ok)
+ assert.Equal(t, "", message)
+}
+
+func TestCompareInterfaceAttachFalse(t *testing.T) {
+ exampleInterface := fmt.Sprintf(string(readTestFile(t, "exampleAttachInterfaceResponse.json")))
+
+ var adapter Interface
+ err := json.Unmarshal([]byte(exampleInterface), &adapter)
+ assert.NoError(t, err)
+ opts := AttachInterfaceRequest{
+ strToPtr("123"),
+ }
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ _, err := io.WriteString(rw, fmt.Sprintf(exampleInterface))
+ assert.NoError(t, err)
+ }
+ message, ok := opts.compareResponse(context.Background(), skytap, &adapter, vmRequestRunStateStopped("123", "456"))
+ assert.False(t, ok)
+ assert.Equal(t, "network adapter not ready", message)
+}
+
+func TestCompareInterfaceUpdateTrue(t *testing.T) {
+ exampleInterface := fmt.Sprintf(string(readTestFile(t, "exampleUpdateInterfaceResponse.json")))
+
+ var adapter Interface
+ err := json.Unmarshal([]byte(exampleInterface), &adapter)
+ assert.NoError(t, err)
+ opts := UpdateInterfaceRequest{
+ IP: strToPtr("10.0.0.1"),
+ Hostname: strToPtr("updated-hostname"),
+ }
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ _, err := io.WriteString(rw, fmt.Sprintf(exampleInterface))
+ assert.NoError(t, err)
+ }
+ message, ok := opts.compareResponse(context.Background(), skytap, &adapter, vmRequestRunStateStopped("123", "456"))
+ assert.True(t, ok)
+ assert.Equal(t, "", message)
+}
+
+func TestCompareInterfaceUpdateFalse(t *testing.T) {
+ exampleInterface := fmt.Sprintf(string(readTestFile(t, "exampleUpdateInterfaceResponse.json")))
+
+ var adapter Interface
+ err := json.Unmarshal([]byte(exampleInterface), &adapter)
+ assert.NoError(t, err)
+ opts := UpdateInterfaceRequest{
+ IP: strToPtr("10.0.0.2"),
+ Hostname: strToPtr("updated-hostname"),
+ }
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ _, err := io.WriteString(rw, fmt.Sprintf(exampleInterface))
+ assert.NoError(t, err)
+ }
+ message, ok := opts.compareResponse(context.Background(), skytap, &adapter, vmRequestRunStateStopped("123", "456"))
+ assert.False(t, ok)
+ assert.Equal(t, "network adapter not ready", message)
+}
diff --git a/skytap/network.go b/skytap/network.go
index c3d2eb8..c41e318 100644
--- a/skytap/network.go
+++ b/skytap/network.go
@@ -2,6 +2,8 @@ package skytap
import (
"context"
+ "fmt"
+ "log"
)
// Default URL paths
@@ -31,7 +33,7 @@ type Network struct {
ID *string `json:"id"`
URL *string `json:"url"`
Name *string `json:"name"`
- NetworkType *string `json:"network_type"`
+ NetworkType *NetworkType `json:"network_type"`
Subnet *string `json:"subnet"`
SubnetAddr *string `json:"subnet_addr"`
SubnetSize *int `json:"subnet_size"`
@@ -130,7 +132,7 @@ func (s *NetworksServiceClient) List(ctx context.Context, environmentID string)
}
var networkListResponse NetworkListResult
- _, err = s.client.do(ctx, req, &networkListResponse.Value)
+ _, err = s.client.do(ctx, req, &networkListResponse.Value, nil, nil)
if err != nil {
return nil, err
}
@@ -148,7 +150,7 @@ func (s *NetworksServiceClient) Get(ctx context.Context, environmentID string, i
}
var network Network
- _, err = s.client.do(ctx, req, &network)
+ _, err = s.client.do(ctx, req, &network, nil, nil)
if err != nil {
return nil, err
}
@@ -166,7 +168,7 @@ func (s *NetworksServiceClient) Create(ctx context.Context, environmentID string
}
var createdNetwork Network
- _, err = s.client.do(ctx, req, &createdNetwork)
+ _, err = s.client.do(ctx, req, &createdNetwork, envRunStateNotBusy(environmentID), opts)
if err != nil {
return nil, err
}
@@ -184,7 +186,7 @@ func (s *NetworksServiceClient) Update(ctx context.Context, environmentID string
}
var updatedNetwork Network
- _, err = s.client.do(ctx, req, &updatedNetwork)
+ _, err = s.client.do(ctx, req, &updatedNetwork, envRunStateNotBusy(environmentID), network)
if err != nil {
return nil, err
}
@@ -201,7 +203,7 @@ func (s *NetworksServiceClient) Delete(ctx context.Context, environmentID string
return err
}
- _, err = s.client.do(ctx, req, nil)
+ _, err = s.client.do(ctx, req, nil, nil, nil)
if err != nil {
return err
}
@@ -216,3 +218,145 @@ func (s *NetworksServiceClient) buildPath(environmentID string, networkID string
}
return path
}
+
+func (payload *CreateNetworkRequest) compareResponse(ctx context.Context, c *Client, v interface{}, state *environmentVMRunState) (string, bool) {
+ if networkOriginal, ok := v.(*Network); ok {
+ network, err := c.Networks.Get(ctx, *state.environmentID, *networkOriginal.ID)
+ if err != nil {
+ return requestNotAsExpected, false
+ }
+ actual := payload.buildUpdateRequestFromVM(network)
+ if payload.string() == actual.string() {
+ return "", true
+ }
+ return "network not ready", false
+ }
+ log.Printf("[ERROR] SDK network comparison not possible on (%v)\n", v)
+ return requestNotAsExpected, false
+}
+
+func (payload *UpdateNetworkRequest) compareResponse(ctx context.Context, c *Client, v interface{}, state *environmentVMRunState) (string, bool) {
+ if networkOriginal, ok := v.(*Network); ok {
+ network, err := c.Networks.Get(ctx, *state.environmentID, *networkOriginal.ID)
+ if err != nil {
+ return requestNotAsExpected, false
+ }
+ actual := payload.buildUpdateRequestFromVM(network)
+ if payload.string() == actual.string() {
+ return "", true
+ }
+ return "network not ready", false
+ }
+ log.Printf("[ERROR] SDK network comparison not possible on (%v)\n", v)
+ return requestNotAsExpected, false
+}
+
+func (payload *CreateNetworkRequest) buildUpdateRequestFromVM(network *Network) CreateNetworkRequest {
+ actual := CreateNetworkRequest{}
+ if payload.Name != nil {
+ actual.Name = network.Name
+ }
+ if payload.NetworkType != nil {
+ actual.NetworkType = network.NetworkType
+ }
+ if payload.Subnet != nil {
+ actual.Subnet = network.Subnet
+ }
+ if payload.Domain != nil {
+ actual.Domain = network.Domain
+ }
+ if payload.Gateway != nil {
+ actual.Gateway = network.Gateway
+ }
+ if payload.Tunnelable != nil {
+ actual.Tunnelable = network.Tunnelable
+ }
+ return actual
+}
+
+func (payload *UpdateNetworkRequest) buildUpdateRequestFromVM(network *Network) UpdateNetworkRequest {
+ actual := UpdateNetworkRequest{}
+ if payload.Name != nil {
+ actual.Name = network.Name
+ }
+ if payload.Subnet != nil {
+ actual.Subnet = network.Subnet
+ }
+ if payload.Domain != nil {
+ actual.Domain = network.Domain
+ }
+ if payload.Gateway != nil {
+ actual.Gateway = network.Gateway
+ }
+ if payload.Tunnelable != nil {
+ actual.Tunnelable = network.Tunnelable
+ }
+ return actual
+}
+
+func (payload *CreateNetworkRequest) string() string {
+ name := ""
+ networkType := ""
+ subnet := ""
+ domain := ""
+ gateway := ""
+ tunnelable := ""
+
+ if payload.Name != nil {
+ name = *payload.Name
+ }
+ if payload.NetworkType != nil {
+ networkType = string(*payload.NetworkType)
+ }
+ if payload.Subnet != nil {
+ subnet = *payload.Subnet
+ }
+ if payload.Domain != nil {
+ domain = *payload.Domain
+ }
+ if payload.Gateway != nil {
+ gateway = *payload.Gateway
+ }
+ if payload.Tunnelable != nil {
+ tunnelable = fmt.Sprintf("%t", *payload.Tunnelable)
+ }
+ return fmt.Sprintf("%s%s%s%s%s%s",
+ name,
+ networkType,
+ subnet,
+ domain,
+ gateway,
+ tunnelable)
+}
+
+func (payload *UpdateNetworkRequest) string() string {
+ name := ""
+ subnet := ""
+ domain := ""
+ gateway := ""
+ tunnelable := ""
+
+ if payload.Name != nil {
+ name = *payload.Name
+ }
+ if payload.Subnet != nil {
+ subnet = *payload.Subnet
+ }
+ if payload.Domain != nil {
+ domain = *payload.Domain
+ }
+ if payload.Gateway != nil {
+ gateway = *payload.Gateway
+ }
+ if payload.Tunnelable != nil {
+ tunnelable = fmt.Sprintf("%t", *payload.Tunnelable)
+ }
+ s := fmt.Sprintf("%s%s%s%s%s",
+ name,
+ subnet,
+ domain,
+ gateway,
+ tunnelable)
+ log.Printf("[DEBUG] SDK network payload (%s)\n", s)
+ return s
+}
diff --git a/skytap/network_test.go b/skytap/network_test.go
index dad84e3..e000f39 100644
--- a/skytap/network_test.go
+++ b/skytap/network_test.go
@@ -6,53 +6,47 @@ import (
"fmt"
"io"
"io/ioutil"
+ "log"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
-const exampleNetworkRequest = `{"name":
- "test network",
- "network_type": "automatic",
- "subnet": "10.0.2.0/24",
- "domain": "sampledomain.com",
- "gateway": "10.0.2.254",
- "tunnelable": true
-}`
-
-const exampleNetworkResponse = `{"id": "%d",
- "url": "https://cloud.skytap.com/v2/configurations/%d/networks/%d",
- "name": "test network",
- "network_type": "automatic",
- "subnet": "10.0.2.0/24",
- "subnet_addr": "10.0.2.0",
- "subnet_size": 24,
- "gateway": "10.0.2.254",
- "primary_nameserver": null,
- "secondary_nameserver": null,
- "region": "US-West",
- "domain": "sampledomain.com",
- "vpn_attachments": [],
- "tunnelable": true,
- "tunnels": []
-}`
-
func TestCreateNetwork(t *testing.T) {
- exampleNetwork := fmt.Sprintf(exampleNetworkResponse, 456, 123, 456)
+ exampleNetwork := fmt.Sprintf(string(readTestFile(t, "exampleNetworkResponse.json")), 456, 123, 456)
skytap, hs, handler := createClient(t)
defer hs.Close()
- *handler = func(rw http.ResponseWriter, req *http.Request) {
- assert.Equal(t, "/v2/configurations/123/networks", req.URL.Path, "Bad path")
- assert.Equal(t, "POST", req.Method, "Bad method")
+ requestCounter := 0
- body, err := ioutil.ReadAll(req.Body)
- assert.Nil(t, err, "Bad request body")
- assert.JSONEq(t, exampleNetworkRequest, string(body), "Bad request body")
-
- io.WriteString(rw, exampleNetwork)
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ log.Printf("Request: (%d)\n", requestCounter)
+ if requestCounter == 0 {
+ assert.Equal(t, "/v2/configurations/123", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, string(readTestFile(t, "exampleEnvironment.json")))
+ assert.NoError(t, err)
+ } else if requestCounter == 1 {
+ assert.Equal(t, "/v2/configurations/123/networks", req.URL.Path, "Bad path")
+ assert.Equal(t, "POST", req.Method, "Bad method")
+
+ body, err := ioutil.ReadAll(req.Body)
+ assert.Nil(t, err, "Bad request body")
+ assert.JSONEq(t, string(readTestFile(t, "exampleNetworkRequest.json")), string(body), "Bad request body")
+
+ _, err = io.WriteString(rw, exampleNetwork)
+ assert.NoError(t, err)
+ } else if requestCounter == 2 {
+ assert.Equal(t, "/v2/configurations/123/networks/456", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, exampleNetwork)
+ assert.NoError(t, err)
+ }
+ requestCounter++
}
opts := &CreateNetworkRequest{
Name: strToPtr("test network"),
@@ -69,10 +63,12 @@ func TestCreateNetwork(t *testing.T) {
var networkExpected Network
err = json.Unmarshal([]byte(exampleNetwork), &networkExpected)
assert.Equal(t, networkExpected, *network, "Bad network")
+
+ assert.Equal(t, 3, requestCounter)
}
func TestReadNetwork(t *testing.T) {
- exampleNetwork := fmt.Sprintf(exampleNetworkResponse, 456, 123, 456)
+ exampleNetwork := fmt.Sprintf(string(readTestFile(t, "exampleNetworkResponse.json")), 456, 123, 456)
skytap, hs, handler := createClient(t)
defer hs.Close()
@@ -81,7 +77,8 @@ func TestReadNetwork(t *testing.T) {
assert.Equal(t, "/v2/configurations/123/networks/456", req.URL.Path, "Bad path")
assert.Equal(t, "GET", req.Method, "Bad method")
- io.WriteString(rw, exampleNetwork)
+ _, err := io.WriteString(rw, exampleNetwork)
+ assert.NoError(t, err)
}
network, err := skytap.Networks.Get(context.Background(), "123", "456")
@@ -93,27 +90,46 @@ func TestReadNetwork(t *testing.T) {
}
func TestUpdateNetwork(t *testing.T) {
- exampleNetwork := fmt.Sprintf(exampleNetworkResponse, 456, 123, 456)
-
- skytap, hs, handler := createClient(t)
- defer hs.Close()
+ exampleNetwork := fmt.Sprintf(string(readTestFile(t, "exampleNetworkResponse.json")), 456, 123, 456)
var network Network
- json.Unmarshal([]byte(exampleNetwork), &network)
+ err := json.Unmarshal([]byte(exampleNetwork), &network)
+ assert.NoError(t, err)
*network.Name = "updated network"
-
- bytes, err := json.Marshal(&network)
+ b, err := json.Marshal(&network)
assert.Nil(t, err, "Bad network")
- *handler = func(rw http.ResponseWriter, req *http.Request) {
- assert.Equal(t, "/v2/configurations/123/networks/456", req.URL.Path, "Bad path")
- assert.Equal(t, "PUT", req.Method, "Bad method")
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
- body, err := ioutil.ReadAll(req.Body)
- assert.Nil(t, err, "Bad request body")
- assert.JSONEq(t, `{"name": "updated network"}`, string(body), "Bad request body")
+ requestCounter := 0
- io.WriteString(rw, string(bytes))
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ log.Printf("Request: (%d)\n", requestCounter)
+ if requestCounter == 0 {
+ assert.Equal(t, "/v2/configurations/123", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, string(readTestFile(t, "exampleEnvironment.json")))
+ assert.NoError(t, err)
+ } else if requestCounter == 1 {
+ assert.Equal(t, "/v2/configurations/123/networks/456", 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, `{"name": "updated network"}`, string(body), "Bad request body")
+
+ _, err = io.WriteString(rw, string(b))
+ assert.NoError(t, err)
+ } else if requestCounter == 2 {
+ assert.Equal(t, "/v2/configurations/123/networks/456", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, string(b))
+ assert.NoError(t, err)
+ }
+ requestCounter++
}
opts := &UpdateNetworkRequest{
@@ -123,23 +139,31 @@ func TestUpdateNetwork(t *testing.T) {
assert.Nil(t, err, "Bad API method")
assert.Equal(t, network, *networkUpdate, "Bad network")
+
+ assert.Equal(t, 3, requestCounter)
}
func TestDeleteNetwork(t *testing.T) {
skytap, hs, handler := createClient(t)
defer hs.Close()
+ requestCounter := 0
+
*handler = func(rw http.ResponseWriter, req *http.Request) {
+ log.Printf("Request: (%d)\n", requestCounter)
assert.Equal(t, "/v2/configurations/123/networks/456", req.URL.Path, "Bad path")
assert.Equal(t, "DELETE", req.Method, "Bad method")
+ requestCounter++
}
err := skytap.Networks.Delete(context.Background(), "123", "456")
assert.Nil(t, err, "Bad API method")
+
+ assert.Equal(t, 1, requestCounter)
}
func TestListNetworks(t *testing.T) {
- exampleNetwork := fmt.Sprintf(exampleNetworkResponse, 456, 123, 456)
+ exampleNetwork := fmt.Sprintf(string(readTestFile(t, "exampleNetworkResponse.json")), 456, 123, 456)
skytap, hs, handler := createClient(t)
defer hs.Close()
@@ -148,7 +172,8 @@ func TestListNetworks(t *testing.T) {
assert.Equal(t, "/v2/configurations/123/networks", req.URL.Path, "Bad path")
assert.Equal(t, "GET", req.Method, "Bad method")
- io.WriteString(rw, fmt.Sprintf(`[%+v]`, exampleNetwork))
+ _, err := io.WriteString(rw, fmt.Sprintf(`[%+v]`, exampleNetwork))
+ assert.NoError(t, err)
}
result, err := skytap.Networks.List(context.Background(), "123")
@@ -163,3 +188,96 @@ func TestListNetworks(t *testing.T) {
}
assert.True(t, found, "Network not found")
}
+
+func TestCompareNetworkCreateTrue(t *testing.T) {
+ exampleNetwork := fmt.Sprintf(string(readTestFile(t, "exampleNetworkResponse.json")), 456, 123, 456)
+
+ var network Network
+ err := json.Unmarshal([]byte(exampleNetwork), &network)
+ assert.NoError(t, err)
+ opts := CreateNetworkRequest{
+ Name: strToPtr("test network"),
+ Subnet: strToPtr("10.0.2.0/24"),
+ Gateway: strToPtr("10.0.2.254"),
+ Tunnelable: boolToPtr(true),
+ Domain: strToPtr("sampledomain.com"),
+ NetworkType: networkTypeToPtr(NetworkTypeAutomatic),
+ }
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ _, err := io.WriteString(rw, exampleNetwork)
+ assert.NoError(t, err)
+ }
+ message, ok := opts.compareResponse(context.Background(), skytap, &network, envRunStateNotBusy("123"))
+ assert.True(t, ok)
+ assert.Equal(t, "", message)
+}
+
+func TestCompareNetworkCreateFalse(t *testing.T) {
+ exampleNetwork := fmt.Sprintf(string(readTestFile(t, "exampleNetworkResponse.json")), 456, 123, 456)
+
+ var network Network
+ err := json.Unmarshal([]byte(exampleNetwork), &network)
+ assert.NoError(t, err)
+ opts := CreateNetworkRequest{
+ Name: strToPtr("test network2"),
+ }
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ _, err := io.WriteString(rw, exampleNetwork)
+ assert.NoError(t, err)
+ }
+ message, ok := opts.compareResponse(context.Background(), skytap, &network, envRunStateNotBusy("123"))
+ assert.False(t, ok)
+ assert.Equal(t, "network not ready", message)
+}
+
+func TestCompareNetworkUpdateTrue(t *testing.T) {
+ exampleNetwork := fmt.Sprintf(string(readTestFile(t, "exampleNetworkResponse.json")), 456, 123, 456)
+
+ var network Network
+ err := json.Unmarshal([]byte(exampleNetwork), &network)
+ assert.NoError(t, err)
+ opts := UpdateNetworkRequest{
+ Name: strToPtr("test network"),
+ Subnet: strToPtr("10.0.2.0/24"),
+ Gateway: strToPtr("10.0.2.254"),
+ Tunnelable: boolToPtr(true),
+ Domain: strToPtr("sampledomain.com"),
+ }
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ _, err := io.WriteString(rw, exampleNetwork)
+ assert.NoError(t, err)
+ }
+ message, ok := opts.compareResponse(context.Background(), skytap, &network, envRunStateNotBusy("123"))
+ assert.True(t, ok)
+ assert.Equal(t, "", message)
+}
+
+func TestCompareNetworkUpdateFalse(t *testing.T) {
+ exampleNetwork := fmt.Sprintf(string(readTestFile(t, "exampleNetworkResponse.json")), 456, 123, 456)
+
+ var network Network
+ err := json.Unmarshal([]byte(exampleNetwork), &network)
+ assert.NoError(t, err)
+ opts := UpdateNetworkRequest{
+ Name: strToPtr("test network2"),
+ }
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ _, err := io.WriteString(rw, exampleNetwork)
+ assert.NoError(t, err)
+ }
+ message, ok := opts.compareResponse(context.Background(), skytap, &network, envRunStateNotBusy("123"))
+ assert.False(t, ok)
+ assert.Equal(t, "network not ready", message)
+}
diff --git a/skytap/project.go b/skytap/project.go
index 2741f81..63c2ac9 100644
--- a/skytap/project.go
+++ b/skytap/project.go
@@ -2,6 +2,7 @@ package skytap
import (
"context"
+ "errors"
"fmt"
)
@@ -63,7 +64,7 @@ func (s *ProjectsServiceClient) List(ctx context.Context) (*ProjectListResult, e
}
var projectListResponse ProjectListResult
- _, err = s.client.do(ctx, req, &projectListResponse.Value)
+ _, err = s.client.do(ctx, req, &projectListResponse.Value, nil, nil)
if err != nil {
return nil, err
}
@@ -81,7 +82,7 @@ func (s *ProjectsServiceClient) Get(ctx context.Context, id int) (*Project, erro
}
var project Project
- _, err = s.client.do(ctx, req, &project)
+ _, err = s.client.do(ctx, req, &project, nil, nil)
if err != nil {
return nil, err
}
@@ -97,13 +98,16 @@ func (s *ProjectsServiceClient) Create(ctx context.Context, project *Project) (*
}
var createdProject Project
- _, err = s.client.do(ctx, req, &createdProject)
+ _, err = s.client.do(ctx, req, &createdProject, nil, nil)
if err != nil {
return nil, err
}
createdProject.Summary = project.Summary
+ if createdProject.ID == nil {
+ return nil, errors.New("missing project ID")
+ }
// update project after creation to establish the resource information.
updatedProject, err := s.Update(ctx, *createdProject.ID, &createdProject)
if err != nil {
@@ -123,7 +127,7 @@ func (s *ProjectsServiceClient) Update(ctx context.Context, id int, project *Pro
}
var updatedProject Project
- _, err = s.client.do(ctx, req, &updatedProject)
+ _, err = s.client.do(ctx, req, &updatedProject, nil, nil)
if err != nil {
return nil, err
}
@@ -140,7 +144,7 @@ func (s *ProjectsServiceClient) Delete(ctx context.Context, id int) error {
return err
}
- _, err = s.client.do(ctx, req, nil)
+ _, err = s.client.do(ctx, req, nil, nil, nil)
if err != nil {
return err
}
diff --git a/skytap/published_service.go b/skytap/published_service.go
index d11a8b0..b20e671 100644
--- a/skytap/published_service.go
+++ b/skytap/published_service.go
@@ -1,6 +1,10 @@
package skytap
-import "context"
+import (
+ "context"
+ "fmt"
+ "log"
+)
// Default URL paths
const (
@@ -58,7 +62,6 @@ type PublishedServicesService interface {
List(ctx context.Context, environmentID string, vmID string, nicID string) (*PublishedServiceListResult, error)
Get(ctx context.Context, environmentID string, vmID string, nicID string, id string) (*PublishedService, error)
Create(ctx context.Context, environmentID string, vmID string, nicID string, internalPort *CreatePublishedServiceRequest) (*PublishedService, error)
- Update(ctx context.Context, environmentID string, vmID string, nicID string, id string, internalPort *UpdatePublishedServiceRequest) (*PublishedService, error)
Delete(ctx context.Context, environmentID string, vmID string, nicID string, id string) error
}
@@ -81,11 +84,6 @@ type CreatePublishedServiceRequest struct {
InternalPort *int `json:"internal_port"`
}
-// UpdatePublishedServiceRequest describes the update the publishedService data
-type UpdatePublishedServiceRequest struct {
- CreatePublishedServiceRequest
-}
-
// PublishedServiceListResult is the listing request specific struct
type PublishedServiceListResult struct {
Value []PublishedService
@@ -107,7 +105,7 @@ func (s *PublishedServicesServiceClient) List(ctx context.Context, environmentID
}
var serviceListResponse PublishedServiceListResult
- _, err = s.client.do(ctx, req, &serviceListResponse.Value)
+ _, err = s.client.do(ctx, req, &serviceListResponse.Value, nil, nil)
if err != nil {
return nil, err
}
@@ -126,7 +124,7 @@ func (s *PublishedServicesServiceClient) Get(ctx context.Context, environmentID
}
var service PublishedService
- _, err = s.client.do(ctx, req, &service)
+ _, err = s.client.do(ctx, req, &service, nil, nil)
if err != nil {
return nil, err
}
@@ -145,7 +143,7 @@ func (s *PublishedServicesServiceClient) Create(ctx context.Context, environment
}
var createdService PublishedService
- _, err = s.client.do(ctx, req, &createdService)
+ _, err = s.client.do(ctx, req, &createdService, vmRunStateNotBusyWithAdapter(environmentID, vmID, nicID), internalPort)
if err != nil {
return nil, err
}
@@ -153,15 +151,6 @@ func (s *PublishedServicesServiceClient) Create(ctx context.Context, environment
return &createdService, nil
}
-// Update a publishedService
-func (s *PublishedServicesServiceClient) Update(ctx context.Context, environmentID string, vmID string, nicID string, id string, internalPort *UpdatePublishedServiceRequest) (*PublishedService, error) {
- err := s.Delete(ctx, environmentID, vmID, nicID, id)
- if err != nil {
- return nil, err
- }
- return s.Create(ctx, environmentID, vmID, nicID, &internalPort.CreatePublishedServiceRequest)
-}
-
// Delete a publishedService
func (s *PublishedServicesServiceClient) Delete(ctx context.Context, environmentID string, vmID string, nicID string, id string) error {
var builder publishedServicePathBuilderImpl
@@ -172,10 +161,40 @@ func (s *PublishedServicesServiceClient) Delete(ctx context.Context, environment
return err
}
- _, err = s.client.do(ctx, req, nil)
+ _, err = s.client.do(ctx, req, nil, nil, nil)
if err != nil {
return err
}
return nil
}
+
+func (payload *CreatePublishedServiceRequest) compareResponse(ctx context.Context, c *Client, v interface{}, state *environmentVMRunState) (string, bool) {
+ if serviceOriginal, ok := v.(*PublishedService); ok {
+ publishedService, err := c.PublishedServices.Get(ctx, *state.environmentID, *state.vmID, *state.adapterID, *serviceOriginal.ID)
+ if err != nil {
+ return requestNotAsExpected, false
+ }
+ actual := CreatePublishedServiceRequest{
+ publishedService.InternalPort,
+ }
+ if payload.string() == actual.string() {
+ return "", true
+ }
+ return "published service not ready", false
+ }
+ log.Printf("[ERROR] SDK published service comparison not possible on (%v)\n", v)
+ return requestNotAsExpected, false
+}
+
+func (payload *CreatePublishedServiceRequest) string() string {
+ internalPort := ""
+
+ if payload.InternalPort != nil {
+ internalPort = fmt.Sprintf("%d", *payload.InternalPort)
+ }
+ s := fmt.Sprintf("%s",
+ internalPort)
+ log.Printf("[DEBUG] SDK create published service payload (%s)\n", s)
+ return s
+}
diff --git a/skytap/published_service_test.go b/skytap/published_service_test.go
index 638f680..1924a8e 100644
--- a/skytap/published_service_test.go
+++ b/skytap/published_service_test.go
@@ -6,55 +6,50 @@ import (
"fmt"
"io"
"io/ioutil"
+ "log"
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
-const examplePublishedServiceRequest = `{
- "internal_port": %d
-}`
-
-const examplePublishedServiceResponse = `{
- "id": "%d",
- "internal_port": %d,
- "external_ip": "services-uswest.skytap.com",
- "external_port": 26160
-}`
-
-const examplePublishedServiceListResponse = `[
- {
- "id": "8080",
- "internal_port": 8080,
- "external_ip": "services-uswest.skytap.com",
- "external_port": 26160
- },
- {
- "id": "8081",
- "internal_port": 8081,
- "external_ip": "services-uswest.skytap.com",
- "external_port": 17785
- }
-]`
-
func TestCreateService(t *testing.T) {
+ response := fmt.Sprintf(string(readTestFile(t, "VMResponse.json")), 456)
+
port := 8080
- exampleService := fmt.Sprintf(examplePublishedServiceResponse, port, port)
+ exampleService := fmt.Sprintf(string(readTestFile(t, "examplePublishedServiceResponse.json")), port, port)
skytap, hs, handler := createClient(t)
defer hs.Close()
+ requestCounter := 0
+
*handler = func(rw http.ResponseWriter, req *http.Request) {
- assert.Equal(t, "/v2/configurations/123/vms/456/interfaces/789/services", req.URL.Path, "Bad path")
- assert.Equal(t, "POST", req.Method, "Bad method")
+ log.Printf("Request: (%d)\n", requestCounter)
+ if requestCounter == 0 {
+ assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
- body, err := ioutil.ReadAll(req.Body)
- assert.Nil(t, err, "Bad request body")
- assert.JSONEq(t, fmt.Sprintf(examplePublishedServiceRequest, port), string(body), "Bad request body")
+ _, err := io.WriteString(rw, response)
+ assert.NoError(t, err)
+ } else if requestCounter == 1 {
+ assert.Equal(t, "/v2/configurations/123/vms/456/interfaces/789/services", req.URL.Path, "Bad path")
+ assert.Equal(t, "POST", req.Method, "Bad method")
- _, err = io.WriteString(rw, exampleService)
- assert.NoError(t, err)
+ body, err := ioutil.ReadAll(req.Body)
+ assert.Nil(t, err, "Bad request body")
+ assert.JSONEq(t, fmt.Sprintf(string(readTestFile(t, "examplePublishedServiceRequest.json")), port), string(body), "Bad request body")
+
+ _, err = io.WriteString(rw, exampleService)
+ assert.NoError(t, err)
+ } else if requestCounter == 2 {
+ assert.Equal(t, "/v2/configurations/123/vms/456/interfaces/789/services/8080", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, exampleService)
+ assert.NoError(t, err)
+ }
+ requestCounter++
}
internalPort := &CreatePublishedServiceRequest{
InternalPort: intToPtr(port),
@@ -66,20 +61,26 @@ func TestCreateService(t *testing.T) {
var serviceExpected PublishedService
err = json.Unmarshal([]byte(exampleService), &serviceExpected)
assert.Equal(t, serviceExpected, *service, "Bad publishedService")
+
+ assert.Equal(t, 3, requestCounter)
}
func TestReadService(t *testing.T) {
- exampleService := fmt.Sprintf(examplePublishedServiceResponse, 8080, 8080)
+ exampleService := fmt.Sprintf(string(readTestFile(t, "examplePublishedServiceResponse.json")), 8080, 8080)
skytap, hs, handler := createClient(t)
defer hs.Close()
+ requestCounter := 0
+
*handler = func(rw http.ResponseWriter, req *http.Request) {
+ log.Printf("Request: (%d)\n", requestCounter)
assert.Equal(t, "/v2/configurations/123/vms/456/interfaces/789/services/abc", req.URL.Path, "Bad path")
assert.Equal(t, "GET", req.Method, "Bad method")
_, err := io.WriteString(rw, exampleService)
assert.NoError(t, err)
+ requestCounter++
}
service, err := skytap.PublishedServices.Get(context.Background(), "123", "456", "789", "abc")
@@ -88,73 +89,43 @@ func TestReadService(t *testing.T) {
var serviceExpected PublishedService
err = json.Unmarshal([]byte(exampleService), &serviceExpected)
assert.Equal(t, serviceExpected, *service, "Bad Interface")
-}
-
-func TestUpdateService(t *testing.T) {
- port := 8081
- exampleService := fmt.Sprintf(examplePublishedServiceResponse, port, port)
-
- skytap, hs, handler := createClient(t)
- defer hs.Close()
-
- var service PublishedService
- err := json.Unmarshal([]byte(exampleService), &service)
- assert.NoError(t, err)
-
- var deletePhase = true
-
- *handler = func(rw http.ResponseWriter, req *http.Request) {
- if deletePhase {
- assert.Equal(t, "/v2/configurations/123/vms/456/interfaces/789/services/abc", req.URL.Path, "Bad path")
- assert.Equal(t, "DELETE", req.Method, "Bad method")
- deletePhase = false
- } else {
- assert.Equal(t, "/v2/configurations/123/vms/456/interfaces/789/services", req.URL.Path, "Bad path")
- assert.Equal(t, "POST", req.Method, "Bad method")
-
- body, err := ioutil.ReadAll(req.Body)
- assert.Nil(t, err, "Bad request body")
- assert.JSONEq(t, fmt.Sprintf(examplePublishedServiceRequest, port), string(body), "Bad request body")
-
- _, err = io.WriteString(rw, exampleService)
- assert.NoError(t, err)
- }
- }
- opts := &UpdatePublishedServiceRequest{
- CreatePublishedServiceRequest{
- InternalPort: intToPtr(port),
- },
- }
- serviceUpdate, err := skytap.PublishedServices.Update(context.Background(), "123", "456", "789", "abc", opts)
- assert.Nil(t, err, "Bad API method")
-
- assert.Equal(t, service, *serviceUpdate, "Bad publishedService")
+ assert.Equal(t, 1, requestCounter)
}
func TestDeleteService(t *testing.T) {
skytap, hs, handler := createClient(t)
defer hs.Close()
+ requestCounter := 0
+
*handler = func(rw http.ResponseWriter, req *http.Request) {
+ log.Printf("Request: (%d)\n", requestCounter)
assert.Equal(t, "/v2/configurations/123/vms/456/interfaces/789/services/abc", req.URL.Path, "Bad path")
assert.Equal(t, "DELETE", req.Method, "Bad method")
+ requestCounter++
}
err := skytap.PublishedServices.Delete(context.Background(), "123", "456", "789", "abc")
assert.Nil(t, err, "Bad API method")
+
+ assert.Equal(t, 1, requestCounter)
}
func TestListServices(t *testing.T) {
skytap, hs, handler := createClient(t)
defer hs.Close()
+ requestCounter := 0
+
*handler = func(rw http.ResponseWriter, req *http.Request) {
+ log.Printf("Request: (%d)\n", requestCounter)
assert.Equal(t, "/v2/configurations/123/vms/456/interfaces/789/services", req.URL.Path, "Bad path")
assert.Equal(t, "GET", req.Method, "Bad method")
- _, err := io.WriteString(rw, examplePublishedServiceListResponse)
+ _, err := io.WriteString(rw, string(readTestFile(t, "examplePublishedServiceListResponse.json")))
assert.NoError(t, err)
+ requestCounter++
}
result, err := skytap.PublishedServices.List(context.Background(), "123", "456", "789")
@@ -168,4 +139,53 @@ func TestListServices(t *testing.T) {
}
}
assert.True(t, found, "PublishedService not found")
+
+ assert.Equal(t, 1, requestCounter)
+}
+
+func TestComparePublishedServiceCreateTrue(t *testing.T) {
+ examplePublishedService := fmt.Sprintf(string(readTestFile(t, "examplePublishedServiceResponse.json")), 789, 8080)
+
+ var service PublishedService
+ err := json.Unmarshal([]byte(examplePublishedService), &service)
+ assert.NoError(t, err)
+ opts := CreatePublishedServiceRequest{
+ InternalPort: intToPtr(8080),
+ }
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ _, err := io.WriteString(rw, string(examplePublishedService))
+ assert.NoError(t, err)
+ }
+ state := vmRunStateNotBusy("123", "456")
+ state.adapterID = strToPtr("789")
+ message, ok := opts.compareResponse(context.Background(), skytap, &service, state)
+ assert.True(t, ok)
+ assert.Equal(t, "", message)
+}
+
+func TestComparePublishedServiceCreateFalse(t *testing.T) {
+ examplePublishedService := fmt.Sprintf(string(readTestFile(t, "examplePublishedServiceResponse.json")), 789, 8080)
+
+ var service PublishedService
+ err := json.Unmarshal([]byte(examplePublishedService), &service)
+ assert.NoError(t, err)
+ opts := CreatePublishedServiceRequest{
+ InternalPort: intToPtr(8081),
+ }
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ response := fmt.Sprintf(string(readTestFile(t, "examplePublishedServiceRequest.json")), 8080)
+ _, err := io.WriteString(rw, string(response))
+ assert.NoError(t, err)
+ }
+ state := vmRunStateNotBusy("123", "456")
+ state.adapterID = strToPtr("789")
+ message, ok := opts.compareResponse(context.Background(), skytap, &service, state)
+ assert.False(t, ok)
+ assert.Equal(t, "published service not ready", message)
}
diff --git a/skytap/template.go b/skytap/template.go
index 2d3b477..1768a90 100644
--- a/skytap/template.go
+++ b/skytap/template.go
@@ -73,7 +73,7 @@ func (s *TemplatesServiceClient) List(ctx context.Context) (*TemplateListResult,
}
var templatesListResponse TemplateListResult
- _, err = s.client.do(ctx, req, &templatesListResponse.Value)
+ _, err = s.client.do(ctx, req, &templatesListResponse.Value, nil, nil)
if err != nil {
return nil, err
}
@@ -91,7 +91,7 @@ func (s *TemplatesServiceClient) Get(ctx context.Context, id string) (*Template,
}
var template Template
- _, err = s.client.do(ctx, req, &template)
+ _, err = s.client.do(ctx, req, &template, nil, nil)
if err != nil {
return nil, err
}
diff --git a/skytap/testdata/createVMResponse.json b/skytap/testdata/createVMResponse.json
index 1e6e79a..c4b4a77 100644
--- a/skytap/testdata/createVMResponse.json
+++ b/skytap/testdata/createVMResponse.json
@@ -2,7 +2,7 @@
"id": "%d",
"url": "https://cloud.skytap.com/configurations/%d",
"name": "base",
- "error": "",
+ "error": false,
"runstate": "busy",
"rate_limited": false,
"description": "used for basic understanding",
diff --git a/skytap/testdata/exampleAttachInterfaceRequest.json b/skytap/testdata/exampleAttachInterfaceRequest.json
new file mode 100644
index 0000000..50a7d44
--- /dev/null
+++ b/skytap/testdata/exampleAttachInterfaceRequest.json
@@ -0,0 +1,3 @@
+{
+ "network_id": "23917287"
+}
\ No newline at end of file
diff --git a/skytap/testdata/exampleAttachInterfaceResponse.json b/skytap/testdata/exampleAttachInterfaceResponse.json
new file mode 100644
index 0000000..ed281a8
--- /dev/null
+++ b/skytap/testdata/exampleAttachInterfaceResponse.json
@@ -0,0 +1,21 @@
+{
+ "id": "nic-20250403-38374059-4",
+ "ip": "192.168.0.5",
+ "hostname": "host-3",
+ "mac": "00:50:56:05:3F:84",
+ "services_count": 0,
+ "services": [],
+ "public_ips_count": 0,
+ "public_ips": [],
+ "vm_id": "37533321",
+ "vm_name": "CentOS 6 Desktop x64",
+ "status": "Powered off",
+ "network_id": "23917287",
+ "network_name": "tftest-network-1",
+ "network_url": "https://cloud.skytap.com/v2/configurations/40071754/networks/23922457",
+ "network_type": "automatic",
+ "network_subnet": "192.168.0.0/16",
+ "nic_type": "vmxnet3",
+ "secondary_ips": [],
+ "public_ip_attachments": []
+}
\ No newline at end of file
diff --git a/skytap/testdata/exampleCreateInterfaceRequest.json b/skytap/testdata/exampleCreateInterfaceRequest.json
new file mode 100644
index 0000000..e47e8fe
--- /dev/null
+++ b/skytap/testdata/exampleCreateInterfaceRequest.json
@@ -0,0 +1,3 @@
+{
+ "nic_type": "e1000"
+}
\ No newline at end of file
diff --git a/skytap/testdata/exampleCreateInterfaceResponse.json b/skytap/testdata/exampleCreateInterfaceResponse.json
new file mode 100644
index 0000000..ae1ecc1
--- /dev/null
+++ b/skytap/testdata/exampleCreateInterfaceResponse.json
@@ -0,0 +1,16 @@
+{
+ "id": "nic-%d",
+ "ip": null,
+ "hostname": null,
+ "mac": "00:50:56:07:40:3F",
+ "services_count": 0,
+ "services": [],
+ "public_ips_count": 0,
+ "public_ips": [],
+ "vm_id": "%d",
+ "vm_name": "Windows Server 2016 Standard",
+ "status": "Powered off",
+ "nic_type": "e1000",
+ "secondary_ips": [],
+ "public_ip_attachments": []
+}
\ No newline at end of file
diff --git a/skytap/testdata/exampleEnvironment.json b/skytap/testdata/exampleEnvironment.json
new file mode 100644
index 0000000..845457c
--- /dev/null
+++ b/skytap/testdata/exampleEnvironment.json
@@ -0,0 +1,349 @@
+{
+ "id": "456",
+ "url": "https://cloud.skytap.com/v2/configurations/456",
+ "name": "No VM",
+ "description": "test environment",
+ "errors": [
+ "error1"
+ ],
+ "error_details": [
+ "error1 details"
+ ],
+ "runstate": "stopped",
+ "rate_limited": false,
+ "last_run": "2018/10/11 15:42:23 +0100",
+ "suspend_on_idle": 1,
+ "suspend_at_time": "2018/10/11 15:42:23 +0100",
+ "owner_url": "https://cloud.skytap.com/v2/users/1",
+ "owner_name": "Joe Bloggs",
+ "owner_id": "1",
+ "vm_count": 1,
+ "storage": 30720,
+ "network_count": 1,
+ "created_at": "2018/10/11 15:42:23 +0100",
+ "region": "US-West",
+ "region_backend": "skytap",
+ "svms": 1,
+ "can_save_as_template": true,
+ "can_copy": true,
+ "can_delete": true,
+ "can_change_state": true,
+ "can_share": true,
+ "can_edit": true,
+ "label_count": 1,
+ "label_category_count": 1,
+ "can_tag": true,
+ "tags": [
+ {
+ "id": "43894",
+ "value": "tag1"
+ },
+ {
+ "id": "43896",
+ "value": "tag2"
+ }
+ ],
+ "tag_list": "tag1,tag2",
+ "alerts": [
+ {
+ "id":"586",
+ "display_type":"informational_alert",
+ "dismissable":true,
+ "message":"IBM i Technology Preview Program nominations are now open. For more information see What's New.",
+ "display_on_general":true,
+ "display_on_login":false,
+ "display_on_smartclient":false
+ }
+ ],
+ "published_service_count": 0,
+ "public_ip_count": 0,
+ "auto_suspend_description": null,
+ "stages": [
+ {
+ "delay_after_finish_seconds": 300,
+ "index": 0,
+ "vm_ids": [
+ "123456",
+ "123457"
+ ]
+ }
+ ],
+ "staged_execution": {
+ "action_type": "suspend",
+ "current_stage_delay_after_finish_seconds": 300,
+ "current_stage_index": 1,
+ "current_stage_finished_at": "2018/10/11 15:42:23 +0100",
+ "vm_ids": [
+ "123453",
+ "123454"
+ ]
+ },
+ "sequencing_enabled": false,
+ "note_count": 1,
+ "project_count_for_user": 0,
+ "project_count": 0,
+ "publish_set_count": 0,
+ "schedule_count": 0,
+ "vpn_count": 0,
+ "outbound_traffic": false,
+ "routable": false,
+ "vms": [
+ {
+ "id": "36858580",
+ "name": "CentOS 7 Server x64",
+ "runstate": "stopped",
+ "rate_limited": false,
+ "hardware": {
+ "cpus": 1,
+ "supports_multicore": true,
+ "cpus_per_socket": 1,
+ "ram": 1024,
+ "svms": 1,
+ "guestOS": "centos-64",
+ "max_cpus": 12,
+ "min_ram": 256,
+ "max_ram": 262144,
+ "vnc_keymap": null,
+ "uuid": null,
+ "disks": [
+ {
+ "id": "disk-19861359-37668995-scsi-0-0",
+ "size": 30720,
+ "type": "SCSI",
+ "controller": "0",
+ "lun": "0"
+ }
+ ],
+ "storage": 30720,
+ "upgradable": false,
+ "instance_type": null,
+ "time_sync_enabled": true,
+ "rtc_start_time": null,
+ "copy_paste_enabled": true,
+ "nested_virtualization": false,
+ "architecture": "x86"
+ },
+ "error": false,
+ "error_details": false,
+ "asset_id": "1",
+ "hardware_version": 11,
+ "max_hardware_version": 11,
+ "interfaces": [
+ {
+ "id": "nic-19861359-37668995-0",
+ "ip": "10.0.0.1",
+ "hostname": "centos7sx64",
+ "mac": "00:50:56:2B:87:F5",
+ "services_count": 0,
+ "services": [
+ {
+ "id": "3389",
+ "internal_port": 3389,
+ "external_ip": "76.191.118.29",
+ "external_port": 12345
+ }
+ ],
+ "public_ips_count": 0,
+ "public_ips": [
+ {
+ "1.2.3.4": "5.6.7.8"
+ }
+ ],
+ "vm_id": "36858580",
+ "vm_name": "CentOS 7 Server x64",
+ "status": "Powered off",
+ "network_id": "23429874",
+ "network_name": "Default Network",
+ "network_url": "https://cloud.skytap.com/v2/configurations/456/networks/23429874",
+ "network_type": "automatic",
+ "network_subnet": "10.0.0.0/24",
+ "nic_type": "vmxnet3",
+ "secondary_ips": [
+ {
+ "id": "10.0.2.2",
+ "address": "10.0.2.2"
+ }
+ ],
+ "public_ip_attachments": [
+ {
+ "id": 1,
+ "public_ip_attachment_key": 2,
+ "address": "1.2.3.4",
+ "connect_type": 1,
+ "hostname": "host1",
+ "dns_name": "host.com",
+ "public_ip_key": "5.6.7.8"
+ }
+ ]
+ }
+ ],
+ "notes": [
+ {
+ "id": "5377708",
+ "user_id": 1,
+ "user": {
+ "id": "1",
+ "url": "https://cloud.skytap.com/v2/users/1",
+ "first_name": "Joe",
+ "last_name": "Bloggs",
+ "login_name": "Joe.Bloggs@opencredo",
+ "email": "Joe.Bloggs@opencredo.com",
+ "title": "",
+ "deleted": false
+ },
+ "created_at": "2018/10/11 15:27:45 +0100",
+ "updated_at": "2018/10/11 15:27:45 +0100",
+ "text": "a note"
+ }
+ ],
+ "labels": [
+ {
+ "id": "43892",
+ "value": "test vm",
+ "label_category": "test multi",
+ "label_category_id": "7704",
+ "label_category_single_value": false
+ }
+ ],
+ "credentials": [
+ {
+ "id": "35158632",
+ "text": "user/pass"
+ }
+ ],
+ "desktop_resizable": true,
+ "local_mouse_cursor": true,
+ "maintenance_lock_engaged": false,
+ "region_backend": "skytap",
+ "created_at": "2018/10/11 15:42:26 +0100",
+ "supports_suspend": true,
+ "can_change_object_state": true,
+ "containers": [
+ {
+ "id": 1122,
+ "cid": "123456789abcdefghijk123456789abcdefghijk123456789abcdefghijk",
+ "name": "nginxtest1",
+ "image": "nginx:latest",
+ "created_at": "2016/06/16 11:58:50 -0700",
+ "last_run": "2016/06/16 11:58:51 -0700",
+ "can_change_state": true,
+ "can_delete": true,
+ "status": "running",
+ "privileged": false,
+ "vm_id": 111000,
+ "vm_name": "Docker VM1",
+ "vm_runstate": "running",
+ "configuration_id": 123456
+ }
+ ],
+ "configuration_url": "https://cloud.skytap.com/v2/configurations/456"
+ }
+ ],
+ "networks": [
+ {
+ "id": "1234567",
+ "url": "https://cloud.skytap.com/configurations/1111111/networks/123467",
+ "name": "Network 1",
+ "network_type": "automatic",
+ "subnet": "10.0.0.0/24",
+ "subnet_addr": "10.0.0.0",
+ "subnet_size": 24,
+ "gateway": "10.0.0.254",
+ "primary_nameserver": "8.8.8.8",
+ "secondary_nameserver": "8.8.8.9",
+ "region": "US-West",
+ "domain": "sampledomain.com",
+ "vpn_attachments": [
+ {
+ "id": "111111-vpn-1234567",
+ "connected": false,
+ "network": {
+ "id": "1111111",
+ "subnet": "10.0.0.0/24",
+ "network_name": "Network 1",
+ "configuration_id": "1212121"
+ },
+ "vpn": {
+ "id": "vpn-1234567",
+ "name": "CorpNet",
+ "enabled": true,
+ "nat_enabled": true,
+ "remote_subnets": "10.10.0.0/24, 10.10.1.0/24, 10.10.2.0/24, 10.10.4.0/24",
+ "remote_peer_ip": "199.199.199.199",
+ "can_reconnect": true
+ }
+ },
+ {
+ "id": "111111-vpn-1234555",
+ "connected": false,
+ "network": {
+ "id": "1111111",
+ "subnet": "10.0.0.0/24",
+ "network_name": "Network 1",
+ "configuration_id": "1212121"
+ },
+ "vpn": {
+ "id": "vpn-1234555",
+ "name": "Offsite DC",
+ "enabled": true,
+ "nat_enabled": true,
+ "remote_subnets": "10.10.0.0/24, 10.10.1.0/24, 10.10.2.0/24, 10.10.4.0/24",
+ "remote_peer_ip": "188.188.188.188",
+ "can_reconnect": true
+ }
+ }
+ ],
+ "tunnelable": false,
+ "tunnels": [
+ {
+ "id": "tunnel-123456-789011",
+ "status": "not_busy",
+ "error": null,
+ "source_network": {
+ "id": "000000",
+ "url": "https://cloud.skytap.com/configurations/249424/networks/0000000",
+ "name": "Network 1",
+ "network_type": "automatic",
+ "subnet": "10.0.0.0/24",
+ "subnet_addr": "10.0.0.0",
+ "subnet_size": 24,
+ "gateway": "10.0.0.254",
+ "primary_nameserver": null,
+ "secondary_nameserver": null,
+ "region": "US-West",
+ "domain": "skytap.example",
+ "vpn_attachments": []
+ },
+ "target_network": {
+ "id": "111111",
+ "url": "https://cloud.skytap.com/configurations/808216/networks/111111",
+ "name": "Network 2",
+ "network_type": "automatic",
+ "subnet": "10.0.2.0/24",
+ "subnet_addr": "10.0.2.0",
+ "subnet_size": 24,
+ "gateway": "10.0.2.254",
+ "primary_nameserver": null,
+ "secondary_nameserver": null,
+ "region": "US-West",
+ "domain": "test.net",
+ "vpn_attachments": []
+ }
+ }
+ ]
+ }
+ ],
+ "containers_count": 0,
+ "container_hosts_count": 0,
+ "platform_errors": [
+ "platform error1"
+ ],
+ "svms_by_architecture": {
+ "x86": 1,
+ "power": 0
+ },
+ "all_vms_support_suspend": true,
+ "shutdown_on_idle": null,
+ "shutdown_at_time": null,
+ "auto_shutdown_description": "Shutting down!"
+}
\ No newline at end of file
diff --git a/skytap/testdata/exampleInterfaceListResponse.json b/skytap/testdata/exampleInterfaceListResponse.json
new file mode 100644
index 0000000..880752c
--- /dev/null
+++ b/skytap/testdata/exampleInterfaceListResponse.json
@@ -0,0 +1,39 @@
+[
+ {
+ "id": "nic-20246343-38367563-0",
+ "ip": "192.168.0.1",
+ "hostname": "wins2016s",
+ "mac": "00:50:56:11:7D:D9",
+ "services_count": 0,
+ "services": [],
+ "public_ips_count": 0,
+ "public_ips": [],
+ "vm_id": "37527239",
+ "vm_name": "Windows Server 2016 Standard",
+ "status": "Running",
+ "network_id": "23917287",
+ "network_name": "tftest-network-1",
+ "network_url": "https://cloud.skytap.com/v2/configurations/40064014/networks/23917287",
+ "network_type": "automatic",
+ "network_subnet": "192.168.0.0/16",
+ "nic_type": "vmxnet3",
+ "secondary_ips": [],
+ "public_ip_attachments": []
+ },
+ {
+ "id": "nic-20246343-38367563-5",
+ "ip": null,
+ "hostname": null,
+ "mac": "00:50:56:07:40:3F",
+ "services_count": 0,
+ "services": [],
+ "public_ips_count": 0,
+ "public_ips": [],
+ "vm_id": "37527239",
+ "vm_name": "Windows Server 2016 Standard",
+ "status": "Running",
+ "nic_type": "e1000",
+ "secondary_ips": [],
+ "public_ip_attachments": []
+ }
+]
\ No newline at end of file
diff --git a/skytap/testdata/exampleNetworkRequest.json b/skytap/testdata/exampleNetworkRequest.json
new file mode 100644
index 0000000..5e59d92
--- /dev/null
+++ b/skytap/testdata/exampleNetworkRequest.json
@@ -0,0 +1,8 @@
+{
+ "name":"test network",
+ "network_type": "automatic",
+ "subnet": "10.0.2.0/24",
+ "domain": "sampledomain.com",
+ "gateway": "10.0.2.254",
+ "tunnelable": true
+}
\ No newline at end of file
diff --git a/skytap/testdata/exampleNetworkResponse.json b/skytap/testdata/exampleNetworkResponse.json
new file mode 100644
index 0000000..74ab822
--- /dev/null
+++ b/skytap/testdata/exampleNetworkResponse.json
@@ -0,0 +1,17 @@
+{
+ "id": "%d",
+ "url": "https://cloud.skytap.com/v2/configurations/%d/networks/%d",
+ "name": "test network",
+ "network_type": "automatic",
+ "subnet": "10.0.2.0/24",
+ "subnet_addr": "10.0.2.0",
+ "subnet_size": 24,
+ "gateway": "10.0.2.254",
+ "primary_nameserver": null,
+ "secondary_nameserver": null,
+ "region": "US-West",
+ "domain": "sampledomain.com",
+ "vpn_attachments": [],
+ "tunnelable": true,
+ "tunnels": []
+}
\ No newline at end of file
diff --git a/skytap/testdata/examplePublishedServiceListResponse.json b/skytap/testdata/examplePublishedServiceListResponse.json
new file mode 100644
index 0000000..239179d
--- /dev/null
+++ b/skytap/testdata/examplePublishedServiceListResponse.json
@@ -0,0 +1,14 @@
+[
+ {
+ "id": "8080",
+ "internal_port": 8080,
+ "external_ip": "services-uswest.skytap.com",
+ "external_port": 26160
+ },
+ {
+ "id": "8081",
+ "internal_port": 8081,
+ "external_ip": "services-uswest.skytap.com",
+ "external_port": 17785
+ }
+]
\ No newline at end of file
diff --git a/skytap/testdata/examplePublishedServiceRequest.json b/skytap/testdata/examplePublishedServiceRequest.json
new file mode 100644
index 0000000..cbef3b9
--- /dev/null
+++ b/skytap/testdata/examplePublishedServiceRequest.json
@@ -0,0 +1,3 @@
+{
+ "internal_port": %d
+}
\ No newline at end of file
diff --git a/skytap/testdata/examplePublishedServiceResponse.json b/skytap/testdata/examplePublishedServiceResponse.json
new file mode 100644
index 0000000..7a8bf13
--- /dev/null
+++ b/skytap/testdata/examplePublishedServiceResponse.json
@@ -0,0 +1,6 @@
+{
+ "id": "%d",
+ "internal_port": %d,
+ "external_ip": "services-uswest.skytap.com",
+ "external_port": 26160
+}
\ No newline at end of file
diff --git a/skytap/testdata/exampleUpdateInterfaceRequest.json b/skytap/testdata/exampleUpdateInterfaceRequest.json
new file mode 100644
index 0000000..d136c4d
--- /dev/null
+++ b/skytap/testdata/exampleUpdateInterfaceRequest.json
@@ -0,0 +1,4 @@
+{
+ "ip": "10.0.0.1",
+ "hostname": "updated-hostname"
+}
\ No newline at end of file
diff --git a/skytap/testdata/exampleUpdateInterfaceResponse.json b/skytap/testdata/exampleUpdateInterfaceResponse.json
new file mode 100644
index 0000000..d3386fb
--- /dev/null
+++ b/skytap/testdata/exampleUpdateInterfaceResponse.json
@@ -0,0 +1,21 @@
+{
+ "id": "nic-20250403-38374059-4",
+ "ip": "10.0.0.1",
+ "hostname": "updated-hostname",
+ "mac": "00:50:56:05:3F:84",
+ "services_count": 0,
+ "services": [],
+ "public_ips_count": 0,
+ "public_ips": [],
+ "vm_id": "37533321",
+ "vm_name": "CentOS 6 Desktop x64",
+ "status": "Powered off",
+ "network_id": "23922457",
+ "network_name": "tftest-network-1",
+ "network_url": "https://cloud.skytap.com/v2/configurations/40071754/networks/23922457",
+ "network_type": "automatic",
+ "network_subnet": "192.168.0.0/16",
+ "nic_type": "vmxnet3",
+ "secondary_ips": [],
+ "public_ip_attachments": []
+}
\ No newline at end of file
diff --git a/skytap/vm.go b/skytap/vm.go
index 85e0ccf..b5eef04 100644
--- a/skytap/vm.go
+++ b/skytap/vm.go
@@ -4,8 +4,8 @@ import (
"context"
"fmt"
"log"
- "net/http"
"sort"
+ "strings"
"time"
)
@@ -39,8 +39,6 @@ type VM struct {
Runstate *VMRunstate `json:"runstate"`
RateLimited *bool `json:"rate_limited"`
Hardware *Hardware `json:"hardware"`
- Error *bool `json:"error"`
- ErrorDetails *bool `json:"error_details"`
AssetID *string `json:"asset_id"`
HardwareVersion *int `json:"hardware_version"`
MaxHardwareVersion *int `json:"max_hardware_version"`
@@ -239,7 +237,7 @@ func (s *VMsServiceClient) List(ctx context.Context, environmentID string) (*VML
}
var vmListResponse VMListResult
- _, err = s.client.do(ctx, req, &vmListResponse.Value)
+ _, err = s.client.do(ctx, req, &vmListResponse.Value, nil, nil)
if err != nil {
return nil, err
}
@@ -257,7 +255,7 @@ func (s *VMsServiceClient) Get(ctx context.Context, environmentID string, id str
}
var vm VM
- _, err = s.client.do(ctx, req, &vm)
+ _, err = s.client.do(ctx, req, &vm, nil, nil)
if err != nil {
return nil, err
}
@@ -273,34 +271,17 @@ func (s *VMsServiceClient) Create(ctx context.Context, environmentID string, opt
TemplateID: opts.TemplateID,
VMID: []string{opts.VMID},
}
-
req, err := s.client.newRequest(ctx, "PUT", path, apiOpts)
+
+ var environment Environment
+ _, err = s.client.do(ctx, req, &environment, envRunStateNotBusy(environmentID), opts)
if err != nil {
return nil, err
}
- // Retry to work around 422 errors on creating a vm.
- var createdEnvironment Environment
- 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.
// It is necessary to retrieve the most recently created vm.
- createdVM, err := mostRecentVM(&createdEnvironment)
+ createdVM, err := mostRecentVM(&environment)
if err != nil {
return nil, err
}
@@ -327,7 +308,7 @@ func (s *VMsServiceClient) Delete(ctx context.Context, environmentID string, id
return err
}
- _, err = s.client.do(ctx, req, nil)
+ _, err = s.client.do(ctx, req, nil, envRunStateNotBusyWithVM(environmentID, id), nil)
if err != nil {
return err
}
@@ -357,94 +338,89 @@ func (s *VMsServiceClient) updateHardware(ctx context.Context, environmentID str
diskIdentification := opts.Hardware.UpdateDisks.DiskIdentification
opts.Hardware.UpdateDisks.DiskIdentification = nil
- currentVM, err := s.Get(ctx, environmentID, id)
+ vm, err := s.Get(ctx, environmentID, id)
if err != nil {
return nil, err
}
// if started stop
- runstate := currentVM.Runstate
- if *runstate == VMRunstateRunning {
+ runstate := *vm.Runstate
+ if runstate == VMRunstateRunning {
_, err = s.changeRunstate(ctx, environmentID, id, &UpdateVMRequest{Runstate: vmRunStateToPtr(VMRunstateStopped)})
if err != nil {
return nil, err
}
- err = s.waitForRunstate(&ctx, environmentID, id, VMRunstateStopped)
- if err != nil {
- return nil, err
- }
}
- removes := buildRemoveList(currentVM, diskIdentification)
- updates := buildUpdateList(currentVM, diskIdentification)
- addOSDiskResize(osDiskSize, currentVM, updates)
+ removes := buildRemoveList(vm, diskIdentification)
+ updates := buildUpdateList(vm, diskIdentification)
+ addOSDiskResize(osDiskSize, vm, updates)
if len(updates) > 0 {
opts.Hardware.UpdateDisks.ExistingDisks = updates
} else if len(opts.Hardware.UpdateDisks.NewDisks) == 0 {
opts.Hardware.UpdateDisks = nil
}
- requestCreate, err := s.client.newRequest(ctx, "PUT", path, opts)
- if err != nil {
- return nil, err
- }
-
- var updatedVM *VM
- _, err = s.client.do(ctx, requestCreate, updatedVM)
- if err != nil {
- return nil, err
- }
+ state := vmRequestRunStateStopped(environmentID, id)
+ state.diskIdentification = diskIdentification
+ if opts.Hardware.UpdateDisks != nil || opts.Hardware.RAM != nil || opts.Hardware.CPUs != nil || opts.Name != nil {
+ requestCreate, err := s.client.newRequest(ctx, "PUT", path, opts)
+ if err != nil {
+ return nil, err
+ }
+ _, err = s.client.do(ctx, requestCreate, &vm, state, opts)
+ if err != nil {
+ return nil, err
+ }
- // wait until not busy
- err = s.waitForRunstate(&ctx, environmentID, id, VMRunstateStopped)
- if err != nil {
- return nil, err
- }
- updatedVM, err = s.Get(ctx, environmentID, id)
- if err != nil {
- return nil, err
+ vm, err = s.Get(ctx, environmentID, id)
+ if err != nil {
+ return nil, err
+ }
}
+ matchUpExistingDisks(vm, diskIdentification, removes)
+ matchUpNewDisks(vm, diskIdentification, removes)
- matchUpExistingDisks(updatedVM, diskIdentification, removes)
- matchUpNewDisks(updatedVM, diskIdentification, removes)
-
- disks := updatedVM.Hardware.Disks
+ disks := vm.Hardware.Disks
if len(removes) > 0 {
// delete phase
opts.Hardware.CPUs = nil
opts.Hardware.RAM = nil
+ if opts.Hardware.UpdateDisks == nil {
+ opts.Hardware.UpdateDisks = &UpdateDisks{}
+ }
opts.Hardware.UpdateDisks.NewDisks = nil
opts.Hardware.UpdateDisks.ExistingDisks = removes
requestDelete, err := s.client.newRequest(ctx, "PUT", path, opts)
if err != nil {
return nil, err
}
- _, err = s.client.do(ctx, requestDelete, &updatedVM)
+ _, err = s.client.do(ctx, requestDelete, &vm, state, opts)
if err != nil {
return nil, err
}
- err = s.waitForRunstate(&ctx, environmentID, id, VMRunstateStopped)
- if err != nil {
- return nil, err
- }
- updatedVM, err = s.Get(ctx, environmentID, id)
+ vm, err = s.Get(ctx, environmentID, id)
if err != nil {
return nil, err
}
- // update new list of disks
- updateFinalDiskList(updatedVM, disks)
+ updateFinalDiskList(vm, disks)
}
// if stopped start
- if *runstate == VMRunstateRunning {
+ if runstate == VMRunstateRunning {
_, err = s.changeRunstate(ctx, environmentID, id, &UpdateVMRequest{Runstate: vmRunStateToPtr(VMRunstateRunning)})
if err != nil {
return nil, err
}
+ vm, err = s.Get(ctx, environmentID, id)
+ if err != nil {
+ return nil, err
+ }
+ updateFinalDiskList(vm, disks)
}
- return updatedVM, nil
+ return vm, nil
}
func (s *VMsServiceClient) changeRunstate(ctx context.Context, environmentID string, id string, opts *UpdateVMRequest) (*VM, error) {
@@ -458,7 +434,7 @@ func (s *VMsServiceClient) changeRunstate(ctx context.Context, environmentID str
}
var updatedVM VM
- _, err = s.client.do(ctx, requestCreate, &updatedVM)
+ _, err = s.client.do(ctx, requestCreate, &updatedVM, envRunStateNotBusyWithVM(environmentID, id), opts)
if err != nil {
return nil, err
}
@@ -500,30 +476,6 @@ func matchUpNewDisks(vm *VM, identifications []DiskIdentification, ignored map[s
}
}
-// wait for runstate
-func (s *VMsServiceClient) waitForRunstate(ctx *context.Context, environmentID string, id string, runstate VMRunstate) error {
- log.Printf("[INFO] waiting for runstate (%s)\n", string(runstate))
- var makeRequest = true
- var err error
- for i := 0; i < s.client.retryCount+1 && makeRequest; i++ {
- vm, err := s.Get(*ctx, environmentID, id)
- if err != nil {
- break
- }
-
- makeRequest = *vm.Runstate != runstate
-
- if makeRequest {
- seconds := s.client.retryAfter
- log.Printf("[INFO] waiting for %d second(s)\n", seconds)
- time.Sleep(time.Duration(seconds) * time.Second)
- } else {
- log.Printf("[INFO] runstate is now (%s)\n", string(runstate))
- }
- }
- return err
-}
-
func (s *VMsServiceClient) buildPath(legacy bool, environmentID string, vmID string) string {
var path string
if legacy {
@@ -596,10 +548,237 @@ func buildUpdateList(vm *VM, diskIDs []DiskIdentification) map[string]ExistingDi
}
func addOSDiskResize(osDiskSize *int, vm *VM, updates map[string]ExistingDisk) {
- if osDiskSize != nil {
+ if osDiskSize != nil && (*vm.Hardware.Disks[0].Size) < *osDiskSize {
updates[*vm.Hardware.Disks[0].ID] = ExistingDisk{
ID: vm.Hardware.Disks[0].ID,
Size: osDiskSize,
}
}
}
+
+func (payload *CreateVMRequest) compareResponse(ctx context.Context, c *Client, v interface{}, state *environmentVMRunState) (string, bool) {
+ env, err := c.Environments.Get(ctx, *state.environmentID)
+ if err != nil {
+ return requestNotAsExpected, false
+ }
+ logEnvironmentStatus(env)
+ if *env.Runstate != EnvironmentRunstateBusy {
+ return "", true
+ }
+ return "VM environment not ready", false
+}
+
+func (payload *UpdateVMRequest) compareResponse(ctx context.Context, c *Client, v interface{}, state *environmentVMRunState) (string, bool) {
+ vm, err := c.VMs.Get(ctx, *state.environmentID, *state.vmID)
+ if err != nil {
+ return requestNotAsExpected, false
+ }
+ logVMStatus(vm)
+ if payload.Runstate != nil && payload.Hardware == nil {
+ if *payload.Runstate == *vm.Runstate {
+ return "", true
+ }
+ return "VM not ready", false
+ }
+ actual := payload.buildComparison(vm, state.diskIdentification)
+ if payload.string() == actual.string() {
+ return "", true
+ }
+ return "VM not ready", false
+}
+
+func (payload *UpdateVMRequest) buildComparison(vm *VM, diskIdentification []DiskIdentification) *UpdateVMRequest {
+ update := &UpdateVMRequest{}
+
+ if payload.Name != nil {
+ update.Name = vm.Name
+ }
+ if payload.Runstate != nil {
+ update.Runstate = vm.Runstate
+ }
+ if payload.Hardware != nil {
+ update.Hardware = &UpdateHardware{}
+ if payload.Hardware.CPUs != nil {
+ update.Hardware.CPUs = vm.Hardware.CPUs
+ }
+ if payload.Hardware.RAM != nil {
+ update.Hardware.RAM = vm.Hardware.RAM
+ }
+ if payload.Hardware.UpdateDisks != nil {
+ update.Hardware.UpdateDisks = payload.buildDiskStructure(vm, diskIdentification)
+ }
+ }
+ if payload.Hardware.CPUs == nil && payload.Hardware.RAM == nil && payload.Hardware.UpdateDisks == nil {
+ payload.Hardware = nil
+ }
+ return update
+}
+
+func (payload *UpdateVMRequest) buildDiskStructure(vm *VM, diskIdentification []DiskIdentification) *UpdateDisks {
+ if diskIdentification == nil {
+ log.Println("[ERROR] SDK cannot compare disks because the disk identification structure is empty.")
+ return nil
+ }
+ if vm.Hardware.Disks == nil {
+ return nil
+ }
+ existing := payload.buildVMExistingDisks(vm.Hardware.Disks)
+ newDisks := payload.buildVMNewDisks(vm.Hardware.Disks, diskIdentification)
+
+ update := &UpdateDisks{}
+ if existing != nil {
+ update.ExistingDisks = existing
+ }
+ if newDisks != nil {
+ update.NewDisks = newDisks
+ }
+ if update.ExistingDisks == nil && update.NewDisks == nil {
+ return nil
+ }
+ return update
+}
+
+func (payload *UpdateVMRequest) buildVMExistingDisks(disks []Disk) map[string]ExistingDisk {
+ var existingDiskPayload map[string]ExistingDisk
+ if payload.Hardware != nil &&
+ payload.Hardware.UpdateDisks != nil &&
+ payload.Hardware.UpdateDisks.ExistingDisks != nil {
+ existingDiskPayload = payload.Hardware.UpdateDisks.ExistingDisks
+ }
+ existingDisks := make(map[string]ExistingDisk)
+ disks = disks[0:]
+ for _, disk := range disks {
+ if _, ok := existingDiskPayload[*disk.ID]; ok {
+ existingDisks[*disk.ID] = ExistingDisk{
+ ID: disk.ID,
+ Size: disk.Size,
+ }
+ }
+ }
+ for id, disk := range existingDiskPayload {
+ if disk.Size == nil {
+ existingDisks[id] = disk
+ }
+ }
+ if len(existingDisks) == 0 {
+ return nil
+ }
+ return existingDisks
+}
+
+func (payload *UpdateVMRequest) buildVMNewDisks(disks []Disk, identification []DiskIdentification) []int {
+ if payload.Hardware == nil ||
+ payload.Hardware.UpdateDisks == nil ||
+ payload.Hardware.UpdateDisks.NewDisks == nil {
+ return nil
+ }
+ newDisks := make([]int, 0)
+ disks = disks[1:]
+ for _, disk := range disks {
+ if identification == nil {
+ newDisks = append(newDisks, *disk.Size)
+ } else {
+ found := false
+ for _, diskID := range identification {
+ if diskID.ID != nil && *diskID.ID == *disk.ID {
+ found = true
+ break
+ }
+ }
+ if !found {
+ newDisks = append(newDisks, *disk.Size)
+ }
+ }
+ }
+ if len(newDisks) == 0 {
+ return nil
+ }
+ sort.Ints(payload.Hardware.UpdateDisks.NewDisks)
+ sort.Ints(newDisks)
+ return newDisks
+}
+
+func (payload *UpdateVMRequest) string() string {
+ name := ""
+ runState := ""
+ hardware := ""
+
+ if payload.Name != nil {
+ name = *payload.Name
+ }
+ if payload.Runstate != nil {
+ runState = string(*payload.Runstate)
+ }
+ if payload.Hardware != nil {
+ hardware = payload.Hardware.string()
+ }
+ s := fmt.Sprintf("%s%s%s",
+ name,
+ runState,
+ hardware)
+ log.Printf("[DEBUG] SDK update vm payload: %s", s)
+ return s
+}
+
+func (payload *UpdateHardware) string() string {
+ cpus := ""
+ ram := ""
+ updateDisks := ""
+
+ if payload.CPUs != nil {
+ cpus = fmt.Sprintf("%d", *payload.CPUs)
+ }
+ if payload.RAM != nil {
+ ram = fmt.Sprintf("%d", *payload.RAM)
+ }
+ if payload.UpdateDisks != nil {
+ updateDisks = payload.UpdateDisks.string()
+ }
+ return fmt.Sprintf("%s%s%s",
+ cpus,
+ ram,
+ updateDisks)
+}
+
+func (payload *UpdateDisks) string() string {
+ osSize := ""
+ disksExisting := ""
+ disksNew := ""
+
+ if payload.OSSize != nil {
+ osSize = fmt.Sprintf("%d", *payload.OSSize)
+ }
+ if payload.ExistingDisks != nil {
+ disks := make([]string, 0)
+ for _, disk := range payload.ExistingDisks {
+ id := ""
+ size := ""
+ if disk.ID != nil {
+ id = *disk.ID
+ }
+ if disk.Size != nil {
+ size = fmt.Sprintf("%d", *disk.Size)
+ }
+ disks = append(disks, fmt.Sprintf("%s:%s", id, size))
+ }
+ sort.Strings(disks)
+ disksExisting = strings.Join(disks, ",")
+ }
+ if payload.NewDisks != nil {
+ disks := make([]string, 0)
+ for _, disk := range payload.NewDisks {
+ disks = append(disks, fmt.Sprintf("%d", disk))
+ }
+ disksNew = strings.Join(disks, ",")
+ }
+ return fmt.Sprintf("%s%s%s",
+ osSize,
+ disksExisting,
+ disksNew)
+}
+
+func logVMStatus(vm *VM) {
+ if vm.RateLimited != nil && *vm.RateLimited {
+ log.Printf("[INFO] SDK VM rate limiting detected\n")
+ }
+}
diff --git a/skytap/vm_test.go b/skytap/vm_test.go
index 5beb440..46ee6c3 100644
--- a/skytap/vm_test.go
+++ b/skytap/vm_test.go
@@ -6,6 +6,7 @@ import (
"fmt"
"io"
"io/ioutil"
+ "log"
"net/http"
"path/filepath"
"testing"
@@ -25,57 +26,38 @@ func TestCreateVM(t *testing.T) {
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")
-
- _, err = io.WriteString(rw, response)
- assert.NoError(t, err)
- }
- 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")
-}
-
-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")
+ log.Printf("Request: (%d)\n", requestCounter)
+ if requestCounter == 0 {
+ assert.Equal(t, "/v2/configurations/123", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, 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")
+ _, err := io.WriteString(rw, string(readTestFile(t, "exampleEnvironment.json")))
+ assert.NoError(t, err)
+ } else if requestCounter == 1 {
+ assert.Equal(t, "/configurations/123", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodPut, 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)
+ } else if requestCounter == 2 {
+ assert.Equal(t, "/v2/configurations/123", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, string(readTestFile(t, "exampleEnvironment.json")))
+ assert.NoError(t, err)
+ } else if requestCounter == 3 {
+ assert.Equal(t, "/v2/configurations/123", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodPut, req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, string(readTestFile(t, "exampleEnvironment.json")))
+ assert.NoError(t, err)
}
requestCounter++
}
@@ -92,7 +74,7 @@ func TestCreateVM422(t *testing.T) {
assert.NoError(t, err)
assert.Equal(t, environment.VMs[1], *createdVM, "Bad VM")
- assert.Equal(t, 2, requestCounter)
+ assert.Equal(t, 3, requestCounter)
}
func TestCreateVMError(t *testing.T) {
@@ -102,20 +84,28 @@ func TestCreateVMError(t *testing.T) {
"%d"
]
}`, 42, 43)
- requestCounter := 0
-
skytap, hs, handler := createClient(t)
defer hs.Close()
+ requestCounter := 0
*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")
+ log.Printf("Request: (%d)\n", requestCounter)
+ if requestCounter == 0 {
+ assert.Equal(t, "/v2/configurations/123", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, string(readTestFile(t, "exampleEnvironment.json")))
+ assert.NoError(t, err)
+ } else if requestCounter == 1 {
+ 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")
+ 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)
+ rw.WriteHeader(401)
+ }
requestCounter++
}
opts := &CreateVMRequest{
@@ -127,9 +117,9 @@ func TestCreateVMError(t *testing.T) {
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)
+
+ assert.Equal(t, 2, requestCounter)
}
func TestReadVM(t *testing.T) {
@@ -138,12 +128,16 @@ func TestReadVM(t *testing.T) {
skytap, hs, handler := createClient(t)
defer hs.Close()
+ requestCounter := 0
+
*handler = func(rw http.ResponseWriter, req *http.Request) {
+ log.Printf("Request: (%d)\n", requestCounter)
assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
assert.Equal(t, "GET", req.Method, "Bad method")
_, err := io.WriteString(rw, response)
assert.NoError(t, err)
+ requestCounter++
}
vm, err := skytap.VMs.Get(context.Background(), "123", "456")
@@ -152,10 +146,11 @@ func TestReadVM(t *testing.T) {
var vmExpected VM
err = json.Unmarshal([]byte(response), &vmExpected)
assert.Equal(t, vmExpected, *vm, "Bad VM")
+
+ assert.Equal(t, 1, requestCounter)
}
-// Not testing stopping and starting.
-func TestUpdateVM(t *testing.T) {
+func TestUpdateVMModifyDisks(t *testing.T) {
response := fmt.Sprintf(string(readTestFile(t, "VMResponse.json")), 456)
skytap, hs, handler := createClient(t)
@@ -164,143 +159,184 @@ func TestUpdateVM(t *testing.T) {
var vmCurrent VM
err := json.Unmarshal([]byte(response), &vmCurrent)
assert.NoError(t, err)
- vmCurrent.Hardware.Disks = vmCurrent.Hardware.Disks[0:3]
bytesCurrent, err := json.Marshal(&vmCurrent)
assert.Nil(t, err, "Bad vm")
- var vmDisksAdded VM
- err = json.Unmarshal([]byte(response), &vmDisksAdded)
+ var vmOSSizeDiskAmendedDiskAdded VM
+ err = json.Unmarshal([]byte(response), &vmOSSizeDiskAmendedDiskAdded)
assert.NoError(t, err)
- vmDisksAdded.Hardware.Disks = vmDisksAdded.Hardware.Disks[0:3]
- vmDisksAdded.Hardware.Disks[0].Size = intToPtr(10000)
- vmDisksAdded.Hardware.Disks = append(vmDisksAdded.Hardware.Disks, *createDisk("disk-20142867-38186761-scsi-0-3", nil))
- bytesDisksAdded, err := json.Marshal(&vmDisksAdded)
- assert.Nil(t, err, "Bad vm")
-
- var vmNew VM
- err = json.Unmarshal([]byte(response), &vmNew)
- vmNew.Hardware.Disks = vmNew.Hardware.Disks[0:2]
- vmNew.Hardware.Disks[1].Name = strToPtr("1")
- vmNew.Hardware.Disks = append(vmNew.Hardware.Disks, *createDisk("disk-20142867-38186761-scsi-0-3", strToPtr("3")))
- bytesNew, err := json.Marshal(&vmNew)
+ vmOSSizeDiskAmendedDiskAdded.Hardware.Disks[0].Size = intToPtr(51201)
+ vmOSSizeDiskAmendedDiskAdded.Hardware.Disks[1].Size = intToPtr(51202)
+ vmOSSizeDiskAmendedDiskAdded.Hardware.Disks = append(vmOSSizeDiskAmendedDiskAdded.Hardware.Disks, *createDisk("disk-20142867-38186761-scsi-0-4", nil))
+ bytesDisksModified, err := json.Marshal(&vmOSSizeDiskAmendedDiskAdded)
assert.Nil(t, err, "Bad vm")
- first := true
- second := true
- third := true
- fourth := true
- fifth := true
- sixth := true
- seventh := true
+ requestCounter := 0
*handler = func(rw http.ResponseWriter, req *http.Request) {
- if first {
+ log.Printf("Request: (%d)\n", requestCounter)
+ if requestCounter == 0 {
assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
assert.Equal(t, "GET", req.Method, "Bad method")
_, err := io.WriteString(rw, string(bytesCurrent))
assert.NoError(t, err)
- first = false
- } else if second {
+ } else if requestCounter == 1 {
+ assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
+ assert.Equal(t, "GET", req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, string(bytesCurrent))
+ assert.NoError(t, err)
+ } else if requestCounter == 2 {
// add the hardware changes
assert.Equal(t, "/v2/configurations/123/vms/456", 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, `{"name": "updated vm", "hardware" : {"cpus": 12, "ram": 8192, "disks": {"new": [51200], "existing": {"disk-20142867-38186761-scsi-0-0" : {"id":"disk-20142867-38186761-scsi-0-0", "size": 10000}}}}}`, string(body), "Bad request body")
+ assert.JSONEq(t, `
+ {
+ "name": "test vm", "runstate":"stopped",
+ "hardware" :{
+ "disks": {
+ "new": [51200],
+ "existing": {
+ "disk-20142867-38186761-scsi-0-0" : {"id":"disk-20142867-38186761-scsi-0-0", "size": 51201},
+ "disk-20142867-38186761-scsi-0-1" : {"id":"disk-20142867-38186761-scsi-0-1", "size": 51202}
+ }
+ }
+ }
+ }`, string(body), "Bad request body")
+
+ _, err = io.WriteString(rw, string(bytesDisksModified))
+ assert.NoError(t, err)
+ } else if requestCounter == 3 {
+ assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
+ assert.Equal(t, "GET", req.Method, "Bad method")
- _, err = io.WriteString(rw, string(bytesCurrent))
+ _, err := io.WriteString(rw, string(bytesDisksModified))
assert.NoError(t, err)
- second = false
- } else if third {
- // wait until not busy
+ } else if requestCounter == 4 {
+ assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
+ assert.Equal(t, "GET", req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, string(bytesDisksModified))
+ assert.NoError(t, err)
+ }
+ requestCounter++
+ }
+
+ opts := createVMUpdateStructure()
+ opts.Hardware.RAM = nil
+ opts.Hardware.CPUs = nil
+ vmUpdate, err := skytap.VMs.Update(context.Background(), "123", "456", opts)
+ assert.Nil(t, err, "Bad API method")
+
+ assert.Equal(t, 5, requestCounter)
+
+ vmOSSizeDiskAmendedDiskAdded.Hardware.Disks[1].Name = strToPtr("1")
+ vmOSSizeDiskAmendedDiskAdded.Hardware.Disks[2].Name = strToPtr("2")
+ vmOSSizeDiskAmendedDiskAdded.Hardware.Disks[3].Name = strToPtr("3")
+ vmOSSizeDiskAmendedDiskAdded.Hardware.Disks[4].Name = strToPtr("4")
+ assert.Equal(t, vmOSSizeDiskAmendedDiskAdded, *vmUpdate, "Bad vm")
+
+ disks := vmUpdate.Hardware.Disks
+ assert.Equal(t, 5, len(disks), "disk length")
+
+ assert.Nil(t, disks[0].Name, "os")
+}
+
+func TestUpdateVMDeleteDisk(t *testing.T) {
+ response := fmt.Sprintf(string(readTestFile(t, "VMResponse.json")), 456)
+
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ var vmCurrent VM
+ err := json.Unmarshal([]byte(response), &vmCurrent)
+ assert.NoError(t, err)
+ bytesCurrent, err := json.Marshal(&vmCurrent)
+ assert.Nil(t, err, "Bad vm")
+
+ var vmDisksRemoved VM
+ err = json.Unmarshal([]byte(response), &vmDisksRemoved)
+ vmDisksRemoved.Hardware.Disks = vmDisksRemoved.Hardware.Disks[0:2]
+ bytesDisksModified, err := json.Marshal(&vmDisksRemoved)
+ assert.Nil(t, err, "Bad vm")
+
+ requestCounter := 0
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ log.Printf("Request: (%d)\n", requestCounter)
+ if requestCounter == 0 {
assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
assert.Equal(t, "GET", req.Method, "Bad method")
_, err := io.WriteString(rw, string(bytesCurrent))
assert.NoError(t, err)
- third = false
- } else if fourth {
- // the last retry - gives the expected count (6 currently)
+ } else if requestCounter == 1 {
assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
assert.Equal(t, "GET", req.Method, "Bad method")
- _, err := io.WriteString(rw, string(bytesDisksAdded))
+ _, err := io.WriteString(rw, string(bytesCurrent))
assert.NoError(t, err)
- fourth = false
- } else if fifth {
+ } else if requestCounter == 2 {
// delete the disks
assert.Equal(t, "/v2/configurations/123/vms/456", 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, `{"name": "updated vm", "hardware" : {"disks": {"existing": {"disk-20142867-38186761-scsi-0-2" : {"id":"disk-20142867-38186761-scsi-0-2", "size": null}}}}}`, string(body), "Bad request body")
-
- _, err = io.WriteString(rw, string(bytesDisksAdded))
+ assert.JSONEq(t, `
+ {
+ "hardware" : {
+ "disks": {
+ "existing": {
+ "disk-20142867-38186761-scsi-0-2" : {"id":"disk-20142867-38186761-scsi-0-2", "size": null},
+ "disk-20142867-38186761-scsi-0-3" : {"id":"disk-20142867-38186761-scsi-0-3", "size": null}
+ }
+ }
+ }
+ }`, string(body), "Bad request body")
+
+ _, err = io.WriteString(rw, string(bytesDisksModified))
assert.NoError(t, err)
- fifth = false
- } else if sixth {
- // wait until not busy
+ } else if requestCounter == 3 {
assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
assert.Equal(t, "GET", req.Method, "Bad method")
- _, err := io.WriteString(rw, string(bytesDisksAdded))
+ _, err := io.WriteString(rw, string(bytesDisksModified))
assert.NoError(t, err)
- sixth = false
- } else if seventh {
- // the last retry - gives the expected count (4 currently)
+ } else if requestCounter == 4 {
assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
assert.Equal(t, "GET", req.Method, "Bad method")
- _, err := io.WriteString(rw, string(bytesNew))
+ _, err := io.WriteString(rw, string(bytesDisksModified))
assert.NoError(t, err)
- fourth = false
- } else {
- seventh = false
}
+ requestCounter++
}
- diskIdentification := make([]DiskIdentification, 2)
- diskIdentification[0] = DiskIdentification{ID: strToPtr("disk-20142867-38186761-scsi-0-1"),
- Size: intToPtr(51200),
- Name: strToPtr("1")}
- diskIdentification[1] = DiskIdentification{ID: nil,
- Size: intToPtr(51200),
- Name: strToPtr("3")}
-
- opts := &UpdateVMRequest{
- Name: strToPtr("updated vm"),
- Hardware: &UpdateHardware{
- CPUs: intToPtr(12),
- RAM: intToPtr(8192),
- UpdateDisks: &UpdateDisks{
- NewDisks: []int{51200},
- DiskIdentification: diskIdentification,
- OSSize: intToPtr(10000),
- },
- },
- }
+ opts := createVMUpdateStructure()
+ opts.Name = nil
+ opts.Runstate = nil
+ opts.Hardware.RAM = nil
+ opts.Hardware.CPUs = nil
+ opts.Hardware.UpdateDisks.NewDisks = nil
+ opts.Hardware.UpdateDisks.OSSize = nil
+ opts.Hardware.UpdateDisks.DiskIdentification = opts.Hardware.UpdateDisks.DiskIdentification[0:1]
+ opts.Hardware.UpdateDisks.DiskIdentification[0].Size = intToPtr(51200)
+ delete(opts.Hardware.UpdateDisks.ExistingDisks, "disk-20142867-38186761-scsi-0-1")
+ delete(opts.Hardware.UpdateDisks.ExistingDisks, "disk-20142867-38186761-scsi-0-2")
+ delete(opts.Hardware.UpdateDisks.ExistingDisks, "disk-20142867-38186761-scsi-0-3")
vmUpdate, err := skytap.VMs.Update(context.Background(), "123", "456", opts)
assert.Nil(t, err, "Bad API method")
- assert.False(t, first)
- assert.False(t, second)
- assert.False(t, third)
- assert.False(t, fourth)
- assert.False(t, fifth)
- assert.False(t, sixth)
- assert.True(t, seventh)
+ assert.Equal(t, 5, requestCounter)
- assert.Equal(t, vmNew, *vmUpdate, "Bad vm")
+ vmDisksRemoved.Hardware.Disks[1].Name = strToPtr("1")
+ assert.Equal(t, vmDisksRemoved, *vmUpdate, "Bad vm")
disks := vmUpdate.Hardware.Disks
- assert.Equal(t, 3, len(disks), "disk length")
-
- assert.Nil(t, disks[0].Name, "os")
- assert.Equal(t, "1", *disks[1].Name, "disk name")
- assert.Equal(t, "3", *disks[2].Name, "disk name")
-
+ assert.Equal(t, 2, len(disks), "disk length")
}
// Updating cpu and ram is possible on their own
@@ -311,53 +347,42 @@ func TestUpdateCPURAMVM(t *testing.T) {
skytap, hs, handler := createClient(t)
defer hs.Close()
- var vmRunning VM
- err := json.Unmarshal([]byte(response), &vmRunning)
+ var vmExisting VM
+ err := json.Unmarshal([]byte(response), &vmExisting)
assert.NoError(t, err)
- vmRunning.Runstate = vmRunStateToPtr(VMRunstateRunning)
- bytesRunning, err := json.Marshal(&vmRunning)
+ vmExisting.Runstate = vmRunStateToPtr(VMRunstateRunning)
+ bytesExisting, err := json.Marshal(&vmExisting)
assert.Nil(t, err, "Bad vm")
- var vm VM
- err = json.Unmarshal([]byte(response), &vm)
+ var vmUpdated VM
+ err = json.Unmarshal([]byte(response), &vmUpdated)
assert.NoError(t, err)
- *vm.Name = "updated vm"
- *vm.Hardware.CPUs = 12
- *vm.Hardware.RAM = 8192
- bytes, err := json.Marshal(&vm)
+ *vmUpdated.Name = "updated vm"
+ *vmUpdated.Hardware.CPUs = 6
+ *vmUpdated.Hardware.RAM = 4096
+ vmUpdated.Runstate = vmRunStateToPtr(VMRunstateStopped)
+ bytes, err := json.Marshal(&vmUpdated)
assert.Nil(t, err, "Bad vm")
- var vmEnriched VM
- err = json.Unmarshal([]byte(response), &vmEnriched)
- assert.NoError(t, err)
- *vmEnriched.Name = "updated vm"
- *vmEnriched.Hardware.CPUs = 12
- *vmEnriched.Hardware.RAM = 8192
- vmEnriched.Hardware.Disks[1].Name = strToPtr("1")
- vmEnriched.Hardware.Disks[2].Name = strToPtr("2")
- vmEnriched.Hardware.Disks[3].Name = strToPtr("3")
- vmEnriched.Runstate = vmRunStateToPtr(VMRunstateRunning)
- bytesEnriched, err := json.Marshal(&vmEnriched)
- assert.Nil(t, err, "Bad vm")
+ vmUpdated.Runstate = vmRunStateToPtr(VMRunstateRunning)
+ bytesRunning, err := json.Marshal(&vmUpdated)
- first := true
- second := true
- third := true
- fourth := true
- fifth := true
- sixth := true
- seventh := true
- eighth := true
+ requestCounter := 0
*handler = func(rw http.ResponseWriter, req *http.Request) {
- if first {
- // get vm
+ log.Printf("Request: (%d)\n", requestCounter)
+ if requestCounter == 0 {
assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
assert.Equal(t, "GET", req.Method, "Bad method")
- _, err := io.WriteString(rw, string(bytesRunning))
+ _, err := io.WriteString(rw, string(bytesExisting))
+ assert.NoError(t, err)
+ } else if requestCounter == 1 {
+ assert.Equal(t, "/v2/configurations/123", req.URL.Path, "Bad path")
+ assert.Equal(t, "GET", req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, string(readTestFile(t, "exampleEnvironment.json")))
assert.NoError(t, err)
- first = false
- } else if second {
+ } else if requestCounter == 2 {
// turn to stopped
assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
assert.Equal(t, "PUT", req.Method, "Bad method")
@@ -368,45 +393,49 @@ func TestUpdateCPURAMVM(t *testing.T) {
_, err = io.WriteString(rw, response)
assert.NoError(t, err)
- second = false
- } else if third {
- // get vm to confirm it is stopped
+ } else if requestCounter == 3 { // confirm vm in correct state, i.e. not busy
assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
- assert.Equal(t, "GET", req.Method, "Bad method")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, response)
+ assert.NoError(t, err)
+ } else if requestCounter == 4 { // confirm vm in correct state, i.e. not busy
+ assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
_, err := io.WriteString(rw, response)
assert.NoError(t, err)
- third = false
- } else if fourth {
+ } else if requestCounter == 5 {
// update
assert.Equal(t, "/v2/configurations/123/vms/456", 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, `{"name": "updated vm", "hardware" : {"cpus": 12, "ram": 8192}}`, string(body), "Bad request body")
+ assert.JSONEq(t, `{"name": "updated vm", "hardware" : {"cpus": 6, "ram": 4096}}`, string(body), "Bad request body")
_, err = io.WriteString(rw, string(bytes))
assert.NoError(t, err)
- fourth = false
- } else if fifth {
- // wait until not busy
+ } else if requestCounter == 6 {
assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
assert.Equal(t, "GET", req.Method, "Bad method")
_, err := io.WriteString(rw, string(bytes))
assert.NoError(t, err)
- fifth = false
- } else if sixth {
- // get updated vm
+ } else if requestCounter == 7 {
assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
assert.Equal(t, "GET", req.Method, "Bad method")
- _, err := io.WriteString(rw, string(bytesEnriched))
+ _, err := io.WriteString(rw, string(bytes))
assert.NoError(t, err)
- sixth = false
- } else if seventh {
- // switch back to running
+ } else if requestCounter == 8 {
+ assert.Equal(t, "/v2/configurations/123", req.URL.Path, "Bad path")
+ assert.Equal(t, "GET", req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, string(readTestFile(t, "exampleEnvironment.json")))
+ assert.NoError(t, err)
+ } else if requestCounter == 9 {
+ // turn to running
assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
assert.Equal(t, "PUT", req.Method, "Bad method")
@@ -414,45 +443,43 @@ func TestUpdateCPURAMVM(t *testing.T) {
assert.Nil(t, err, "Bad request body")
assert.JSONEq(t, `{"runstate":"running"}`, string(body), "Bad request body")
+ _, err = io.WriteString(rw, string(bytes))
+ assert.NoError(t, err)
+ } else if requestCounter == 10 {
+ assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
+ assert.Equal(t, "GET", req.Method, "Bad method")
+
_, err = io.WriteString(rw, string(bytesRunning))
assert.NoError(t, err)
- seventh = false
- } else {
- // dont bother waiting for vm to be running
- eighth = false
- }
- }
+ } else if requestCounter == 11 {
+ assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
+ assert.Equal(t, "GET", req.Method, "Bad method")
- diskIDs := []DiskIdentification{
- {strToPtr("disk-20142867-38186761-scsi-0-1"), intToPtr(51200), strToPtr("1")},
- {strToPtr("disk-20142867-38186761-scsi-0-2"), intToPtr(51200), strToPtr("2")},
- {strToPtr("disk-20142867-38186761-scsi-0-3"), intToPtr(51200), strToPtr("3")},
+ _, err = io.WriteString(rw, string(bytesRunning))
+ assert.NoError(t, err)
+ }
+ requestCounter++
}
- opts := &UpdateVMRequest{
- Name: strToPtr(*vm.Name),
- Hardware: &UpdateHardware{
- CPUs: intToPtr(*vm.Hardware.CPUs),
- RAM: intToPtr(*vm.Hardware.RAM),
- UpdateDisks: &UpdateDisks{
- DiskIdentification: diskIDs,
- },
- },
- }
- vmUpdate, err := skytap.VMs.Update(context.Background(), "123", "456", opts)
+ opts := createVMUpdateStructure()
+ opts.Name = strToPtr("updated vm")
+ opts.Runstate = nil
+ opts.Hardware.RAM = intToPtr(4096)
+ opts.Hardware.CPUs = intToPtr(6)
+ opts.Hardware.UpdateDisks.NewDisks = nil
+ opts.Hardware.UpdateDisks.ExistingDisks = nil
+ opts.Hardware.UpdateDisks.OSSize = nil
+ opts.Hardware.UpdateDisks.DiskIdentification[0].Size = intToPtr(51200)
+ vm, err := skytap.VMs.Update(context.Background(), "123", "456", opts)
assert.Nil(t, err, "Bad API method")
- assert.False(t, first)
- assert.False(t, second)
- assert.False(t, third)
- assert.False(t, fourth)
- assert.False(t, fifth)
- assert.False(t, sixth)
- assert.False(t, seventh)
- assert.True(t, eighth)
+ assert.Equal(t, 12, requestCounter)
- assert.Equal(t, vmEnriched, *vmUpdate, "Bad vm")
- assert.Equal(t, VMRunstateRunning, *vmUpdate.Runstate, "running")
+ vmUpdated.Hardware.Disks[1].Name = strToPtr("1")
+ vmUpdated.Hardware.Disks[2].Name = strToPtr("2")
+ vmUpdated.Hardware.Disks[3].Name = strToPtr("3")
+ assert.Equal(t, vmUpdated, *vm, "Bad vm")
+ assert.Equal(t, VMRunstateRunning, *vm.Runstate, "running")
}
// Updating runstate can only be done on its own
@@ -470,16 +497,34 @@ func TestUpdateRunstateVM(t *testing.T) {
bytes, err := json.Marshal(&vm)
assert.Nil(t, err, "Bad vm")
+ requestCounter := 0
+
*handler = func(rw http.ResponseWriter, req *http.Request) {
- assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
- assert.Equal(t, "PUT", req.Method, "Bad method")
+ log.Printf("Request: (%d)\n", requestCounter)
+ if requestCounter == 0 {
+ assert.Equal(t, "/v2/configurations/123", req.URL.Path, "Bad path")
+ assert.Equal(t, "GET", req.Method, "Bad method")
- body, err := ioutil.ReadAll(req.Body)
- assert.Nil(t, err, "Bad request body")
- assert.JSONEq(t, `{"runstate":"running"}`, string(body), "Bad request body")
+ _, err := io.WriteString(rw, string(readTestFile(t, "exampleEnvironment.json")))
+ assert.NoError(t, err)
+ } else if requestCounter == 1 {
+ assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
+ assert.Equal(t, "PUT", req.Method, "Bad method")
- _, err = io.WriteString(rw, string(bytes))
- assert.NoError(t, err)
+ body, err := ioutil.ReadAll(req.Body)
+ assert.Nil(t, err, "Bad request body")
+ assert.JSONEq(t, `{"runstate":"running"}`, string(body), "Bad request body")
+
+ _, err = io.WriteString(rw, string(bytes))
+ assert.NoError(t, err)
+ } else if requestCounter == 2 {
+ assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
+ assert.Equal(t, "GET", req.Method, "Bad method")
+
+ _, err = io.WriteString(rw, string(bytes))
+ assert.NoError(t, err)
+ }
+ requestCounter++
}
opts := &UpdateVMRequest{
@@ -490,19 +535,35 @@ func TestUpdateRunstateVM(t *testing.T) {
assert.Equal(t, vm, *vmUpdate, "Bad vm")
assert.Equal(t, VMRunstateRunning, *vmUpdate.Runstate, "running")
+
+ assert.Equal(t, 3, requestCounter)
}
func TestDeleteVM(t *testing.T) {
skytap, hs, handler := createClient(t)
defer hs.Close()
+ requestCounter := 0
+
*handler = func(rw http.ResponseWriter, req *http.Request) {
- assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
- assert.Equal(t, "DELETE", req.Method, "Bad method")
+ log.Printf("Request: (%d)\n", requestCounter)
+ if requestCounter == 0 {
+ assert.Equal(t, "/v2/configurations/123", req.URL.Path, "Bad path")
+ assert.Equal(t, http.MethodGet, req.Method, "Bad method")
+
+ _, err := io.WriteString(rw, string(readTestFile(t, "exampleEnvironment.json")))
+ assert.NoError(t, err)
+ } else if requestCounter == 1 {
+ assert.Equal(t, "/v2/configurations/123/vms/456", req.URL.Path, "Bad path")
+ assert.Equal(t, "DELETE", req.Method, "Bad method")
+ }
+ requestCounter++
}
err := skytap.VMs.Delete(context.Background(), "123", "456")
assert.Nil(t, err, "Bad API method")
+
+ assert.Equal(t, 2, requestCounter)
}
func TestListVMs(t *testing.T) {
@@ -511,12 +572,16 @@ func TestListVMs(t *testing.T) {
skytap, hs, handler := createClient(t)
defer hs.Close()
+ requestCounter := 0
+
*handler = func(rw http.ResponseWriter, req *http.Request) {
+ log.Printf("Request: (%d)\n", requestCounter)
assert.Equal(t, "/v2/configurations/123/vms", req.URL.Path, "Bad path")
assert.Equal(t, "GET", req.Method, "Bad method")
_, err := io.WriteString(rw, fmt.Sprintf(`[%+v]`, response))
assert.NoError(t, err)
+ requestCounter++
}
result, err := skytap.VMs.List(context.Background(), "123")
@@ -530,6 +595,8 @@ func TestListVMs(t *testing.T) {
}
}
assert.True(t, found, "VM not found")
+
+ assert.Equal(t, 1, requestCounter)
}
func readTestFile(t *testing.T, name string) []byte {
@@ -705,8 +772,263 @@ func TestOSDiskResize(t *testing.T) {
addOSDiskResize(nil, &vm, updates)
assert.Equal(t, 1, len(updates))
- addOSDiskResize(intToPtr(10000), &vm, updates)
+ addOSDiskResize(intToPtr(51203), &vm, updates)
assert.Equal(t, 2, len(updates))
assert.Equal(t, ExistingDisk{ID: strToPtr("disk-20142867-38186761-scsi-0-0"),
- Size: intToPtr(10000)}, updates["disk-20142867-38186761-scsi-0-0"])
+ Size: intToPtr(51203)}, updates["disk-20142867-38186761-scsi-0-0"])
+}
+
+func TestCompareVMCreateTrue(t *testing.T) {
+ exampleVM := fmt.Sprintf(string(readTestFile(t, "createVMResponse.json")), 123, 123, 456)
+
+ var env Environment
+ err := json.Unmarshal([]byte(exampleVM), &env)
+ env.Runstate = environmentRunStateToPtr(EnvironmentRunstateStopped)
+ assert.NoError(t, err)
+ opts := CreateVMRequest{
+ TemplateID: "42",
+ VMID: "43",
+ }
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ bytes, err := json.Marshal(&env)
+ assert.Nil(t, err, "Bad vm")
+ _, err = io.WriteString(rw, string(bytes))
+ assert.NoError(t, err)
+ }
+ state := vmRunStateNotBusy("123", "456")
+ state.diskIdentification = buildDiskidentification()
+ message, ok := opts.compareResponse(context.Background(), skytap, &env, state)
+ assert.True(t, ok)
+ assert.Equal(t, "", message)
+}
+
+func TestCompareVMCreateFalse(t *testing.T) {
+ exampleVM := fmt.Sprintf(string(readTestFile(t, "createVMResponse.json")), 123, 123, 456)
+
+ var env Environment
+ err := json.Unmarshal([]byte(exampleVM), &env)
+ assert.NoError(t, err)
+ opts := CreateVMRequest{
+ TemplateID: "42",
+ VMID: "43",
+ }
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ _, err := io.WriteString(rw, exampleVM)
+ assert.NoError(t, err)
+ }
+ state := vmRunStateNotBusy("123", "456")
+ state.diskIdentification = buildDiskidentification()
+ message, ok := opts.compareResponse(context.Background(), skytap, &env, state)
+ assert.False(t, ok)
+ assert.Equal(t, "VM environment not ready", message)
+}
+
+func TestCompareVMUpdateRunStateTrue(t *testing.T) {
+ exampleVM := fmt.Sprintf(string(readTestFile(t, "VMResponse.json")), 456)
+
+ var vm VM
+ err := json.Unmarshal([]byte(exampleVM), &vm)
+ assert.NoError(t, err)
+ opts := &UpdateVMRequest{
+ Runstate: vmRunStateToPtr(VMRunstateStopped),
+ }
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ _, err := io.WriteString(rw, exampleVM)
+ assert.NoError(t, err)
+ }
+ state := vmRunStateNotBusy("123", "456")
+ state.diskIdentification = buildDiskidentification()
+ message, ok := opts.compareResponse(context.Background(), skytap, &vm, state)
+ assert.True(t, ok)
+ assert.Equal(t, "", message)
+}
+
+func TestCompareVMUpdateRunStateFalse(t *testing.T) {
+ exampleVM := fmt.Sprintf(string(readTestFile(t, "VMResponse.json")), 456)
+
+ var vm VM
+ err := json.Unmarshal([]byte(exampleVM), &vm)
+ vm.Runstate = vmRunStateToPtr(VMRunstateBusy)
+ assert.NoError(t, err)
+ opts := &UpdateVMRequest{
+ Runstate: vmRunStateToPtr(VMRunstateStopped),
+ }
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ bytes, err := json.Marshal(&vm)
+ assert.Nil(t, err, "Bad vm")
+ _, err = io.WriteString(rw, string(bytes))
+ assert.NoError(t, err)
+ }
+ state := vmRunStateNotBusy("123", "456")
+ state.diskIdentification = buildDiskidentification()
+ message, ok := opts.compareResponse(context.Background(), skytap, &vm, state)
+ assert.False(t, ok)
+ assert.Equal(t, "VM not ready", message)
+}
+
+func TestCompareVMUpdateTrue(t *testing.T) {
+ exampleVM := fmt.Sprintf(string(readTestFile(t, "VMResponse.json")), 456)
+
+ var vm VM
+ err := json.Unmarshal([]byte(exampleVM), &vm)
+ assert.NoError(t, err)
+ vm.Hardware.Disks = append(vm.Hardware.Disks, Disk{
+ ID: strToPtr("disk-20142867-38186761-scsi-0-4"),
+ Size: intToPtr(51200),
+ })
+ vm.Hardware.Disks[0].Size = intToPtr(51200)
+
+ existingDisks := make(map[string]ExistingDisk)
+ existingDisks["disk-20142867-38186761-scsi-0-1"] = ExistingDisk{
+ ID: strToPtr("disk-20142867-38186761-scsi-0-1"),
+ Size: intToPtr(51200),
+ }
+ existingDisks["disk-20142867-38186761-scsi-0-2"] = ExistingDisk{
+ ID: strToPtr("disk-20142867-38186761-scsi-0-2"),
+ Size: intToPtr(51200),
+ }
+ existingDisks["disk-20142867-38186761-scsi-0-3"] = ExistingDisk{
+ ID: strToPtr("disk-20142867-38186761-scsi-0-3"),
+ Size: intToPtr(51200),
+ }
+ opts := &UpdateVMRequest{
+ Name: strToPtr("test vm"),
+ Runstate: vmRunStateToPtr(VMRunstateStopped),
+ Hardware: &UpdateHardware{
+ CPUs: intToPtr(12),
+ RAM: intToPtr(8192),
+ UpdateDisks: &UpdateDisks{
+ NewDisks: []int{51200},
+ ExistingDisks: existingDisks,
+ },
+ },
+ }
+
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ bytes, err := json.Marshal(&vm)
+ assert.Nil(t, err, "Bad vm")
+ _, err = io.WriteString(rw, string(bytes))
+ assert.NoError(t, err)
+ }
+ state := vmRunStateNotBusy("123", "456")
+ state.diskIdentification = buildDiskidentification()
+ message, ok := opts.compareResponse(context.Background(), skytap, &vm, state)
+ assert.True(t, ok)
+ assert.Equal(t, "", message)
+}
+
+func TestCompareVMUpdateFalse(t *testing.T) {
+ exampleVM := fmt.Sprintf(string(readTestFile(t, "VMResponse.json")), 456)
+
+ var vm VM
+ vm.Runstate = vmRunStateToPtr(VMRunstateBusy)
+ err := json.Unmarshal([]byte(exampleVM), &vm)
+ assert.NoError(t, err)
+ opts := createVMUpdateStructure()
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ bytes, err := json.Marshal(&vm)
+ assert.Nil(t, err, "Bad vm")
+ _, err = io.WriteString(rw, string(bytes))
+ assert.NoError(t, err)
+ }
+ state := vmRunStateNotBusy("123", "456")
+ state.diskIdentification = buildDiskidentification()
+ message, ok := opts.compareResponse(context.Background(), skytap, &vm, state)
+ assert.False(t, ok)
+ assert.Equal(t, "VM not ready", message)
+}
+
+func TestCompareDiskStructureNoDisks(t *testing.T) {
+ exampleEnvironment := fmt.Sprintf(string(readTestFile(t, "createVMResponse.json")), 123, 123, 456)
+ exampleVM := fmt.Sprintf(string(readTestFile(t, "VMResponse.json")), 456)
+
+ var env Environment
+ err := json.Unmarshal([]byte(exampleEnvironment), &env)
+ assert.NoError(t, err)
+ vm := env.VMs[1]
+ vm.Hardware.Disks = nil
+ opts := createVMUpdateStructure()
+ skytap, hs, handler := createClient(t)
+ defer hs.Close()
+
+ *handler = func(rw http.ResponseWriter, req *http.Request) {
+ _, err := io.WriteString(rw, exampleVM)
+ assert.NoError(t, err)
+ }
+ state := vmRunStateNotBusy("123", "456")
+ state.diskIdentification = buildDiskidentification()
+ message, ok := opts.compareResponse(context.Background(), skytap, &vm, state)
+ assert.False(t, ok)
+ assert.Equal(t, "VM not ready", message)
+}
+
+func createVMUpdateStructure() *UpdateVMRequest {
+ diskIdentification := buildDiskidentification()
+ diskIdentification[0] = DiskIdentification{ID: strToPtr("disk-20142867-38186761-scsi-0-1"),
+ Size: intToPtr(51202),
+ Name: strToPtr("1")}
+
+ existingDisks := make(map[string]ExistingDisk)
+ existingDisks["disk-20142867-38186761-scsi-0-1"] = ExistingDisk{
+ ID: strToPtr("disk-20142867-38186761-scsi-0-1"),
+ Size: intToPtr(51200),
+ }
+ existingDisks["disk-20142867-38186761-scsi-0-2"] = ExistingDisk{
+ ID: strToPtr("disk-20142867-38186761-scsi-0-2"),
+ Size: intToPtr(51200),
+ }
+ existingDisks["disk-20142867-38186761-scsi-0-3"] = ExistingDisk{
+ ID: strToPtr("disk-20142867-38186761-scsi-0-3"),
+ Size: intToPtr(51200),
+ }
+ opts := &UpdateVMRequest{
+ Name: strToPtr("test vm"),
+ Runstate: vmRunStateToPtr(VMRunstateStopped),
+ Hardware: &UpdateHardware{
+ CPUs: intToPtr(12),
+ RAM: intToPtr(8192),
+ UpdateDisks: &UpdateDisks{
+ NewDisks: []int{51200},
+ ExistingDisks: existingDisks,
+ DiskIdentification: diskIdentification,
+ OSSize: intToPtr(51201),
+ },
+ },
+ }
+ return opts
+}
+
+func buildDiskidentification() []DiskIdentification {
+ diskIdentification := make([]DiskIdentification, 4)
+ diskIdentification[0] = DiskIdentification{ID: strToPtr("disk-20142867-38186761-scsi-0-1"),
+ Size: intToPtr(51200),
+ Name: strToPtr("1")}
+ diskIdentification[1] = DiskIdentification{ID: strToPtr("disk-20142867-38186761-scsi-0-2"),
+ Size: intToPtr(51200),
+ Name: strToPtr("2")}
+ diskIdentification[2] = DiskIdentification{ID: strToPtr("disk-20142867-38186761-scsi-0-3"),
+ Size: intToPtr(51200),
+ Name: strToPtr("3")}
+ diskIdentification[3] = DiskIdentification{ID: nil,
+ Size: intToPtr(51200),
+ Name: strToPtr("4")}
+ return diskIdentification
}