github.com/vmware/go-vcloud-director/v2@v2.24.0/govcd/openapi_test.go (about)

     1  //go:build functional || openapi || ALL
     2  
     3  /*
     4   * Copyright 2020 VMware, Inc.  All rights reserved.  Licensed under the Apache v2 License.
     5   */
     6  
     7  package govcd
     8  
     9  import (
    10  	"encoding/json"
    11  	"fmt"
    12  	"net/http"
    13  	"net/url"
    14  	"regexp"
    15  	"strings"
    16  	"time"
    17  
    18  	"github.com/araddon/dateparse"
    19  	"github.com/vmware/go-vcloud-director/v2/types/v56"
    20  
    21  	. "gopkg.in/check.v1"
    22  )
    23  
    24  // Test_OpenApiRawJsonAuditTrail uses low level GET function to test out that pagination really works. It is an example
    25  // how to fetch response from multiple pages in RAW json messages without having defined as struct.
    26  func (vcd *TestVCD) Test_OpenApiRawJsonAuditTrail(check *C) {
    27  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointAuditTrail
    28  	skipOpenApiEndpointTest(vcd, check, endpoint)
    29  	apiVersion, err := vcd.client.Client.checkOpenApiEndpointCompatibility(endpoint)
    30  	check.Assert(err, IsNil)
    31  
    32  	urlRef, err := vcd.client.Client.OpenApiBuildEndpoint(endpoint)
    33  	check.Assert(err, IsNil)
    34  
    35  	// Get a timestamp after which endpoint contains at least 10 elements
    36  	filterTimeStamp := getAuditTrailTimestampWithElements(10, check, vcd, apiVersion, urlRef)
    37  
    38  	// Limit search of audits trails to the last 12 hours so that it doesn't take too long and set pageSize to be 1 result
    39  	// to force following pages
    40  	queryParams := url.Values{}
    41  	queryParams.Add("filter", "timestamp=gt="+filterTimeStamp)
    42  	queryParams.Add("pageSize", "1") // pageSize=1 to enforce internal pagination
    43  	queryParams.Add("sortDesc", "timestamp")
    44  
    45  	allResponses := []json.RawMessage{{}}
    46  	err = vcd.vdc.client.OpenApiGetAllItems(apiVersion, urlRef, queryParams, &allResponses, nil)
    47  
    48  	check.Assert(err, IsNil)
    49  	check.Assert(len(allResponses) > 1, Equals, true)
    50  
    51  	// Build a regex ant match it internally so that we are sure auditTrail events are returned in RAW json message. There
    52  	// should be the same amount of audit event IDs as total responses
    53  	auditLogUrn := regexp.MustCompile("urn:vcloud:audit:")
    54  	responseStrings := jsonRawMessagesToStrings(allResponses)
    55  	allStringResponses := `[` + strings.Join(responseStrings, ",") + `]`
    56  	matches := auditLogUrn.FindAllStringIndex(allStringResponses, -1)
    57  	check.Assert(len(matches), Equals, len(allResponses))
    58  }
    59  
    60  // Test_OpenApiInlineStructAuditTrail uses low level GET function to test out that get function can unmarshal directly
    61  // to user defined inline type
    62  func (vcd *TestVCD) Test_OpenApiInlineStructAuditTrail(check *C) {
    63  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointAuditTrail
    64  	skipOpenApiEndpointTest(vcd, check, endpoint)
    65  	apiVersion, err := vcd.client.Client.checkOpenApiEndpointCompatibility(endpoint)
    66  	check.Assert(err, IsNil)
    67  
    68  	urlRef, err := vcd.client.Client.OpenApiBuildEndpoint(endpoint)
    69  	check.Assert(err, IsNil)
    70  
    71  	// Inline type
    72  	type AuditTrail struct {
    73  		EventID      string `json:"eventId"`
    74  		Description  string `json:"description"`
    75  		OperatingOrg struct {
    76  			Name string `json:"name"`
    77  			ID   string `json:"id"`
    78  		} `json:"operatingOrg"`
    79  		User struct {
    80  			Name string `json:"name"`
    81  			ID   string `json:"id"`
    82  		} `json:"user"`
    83  		EventEntity struct {
    84  			Name string `json:"name"`
    85  			ID   string `json:"id"`
    86  		} `json:"eventEntity"`
    87  		TaskID               interface{} `json:"taskId"`
    88  		TaskCellID           string      `json:"taskCellId"`
    89  		CellID               string      `json:"cellId"`
    90  		EventType            string      `json:"eventType"`
    91  		ServiceNamespace     string      `json:"serviceNamespace"`
    92  		EventStatus          string      `json:"eventStatus"`
    93  		Timestamp            string      `json:"timestamp"`
    94  		External             bool        `json:"external"`
    95  		AdditionalProperties struct {
    96  			UserRoles                         string `json:"user.roles"`
    97  			UserSessionID                     string `json:"user.session.id"`
    98  			CurrentContextUserProxyAddress    string `json:"currentContext.user.proxyAddress"`
    99  			CurrentContextUserClientIPAddress string `json:"currentContext.user.clientIpAddress"`
   100  		} `json:"additionalProperties"`
   101  	}
   102  
   103  	allResponses := []*AuditTrail{{}}
   104  
   105  	// Define FIQL query to find events for the last 6 hours. At least login operations will already be here on test run
   106  	queryParams := url.Values{}
   107  	filterTime := time.Now().Add(-6 * time.Hour).Format(types.FiqlQueryTimestampFormat)
   108  	queryParams.Add("filter", "timestamp=gt="+filterTime)
   109  
   110  	err = vcd.vdc.client.OpenApiGetAllItems(apiVersion, urlRef, queryParams, &allResponses, nil)
   111  
   112  	check.Assert(err, IsNil)
   113  	check.Assert(len(allResponses) > 1, Equals, true)
   114  
   115  	// Check that all responses have IDs populated
   116  	for _, v := range allResponses {
   117  		check.Assert(v.EventID, NotNil)
   118  	}
   119  }
   120  
   121  // Test_OpenApiInlineStructCRUDRoles test aims to test out low level OpenAPI functions to check if all of them work as
   122  // expected. It uses a very simple "InlineRoles" endpoint which does not have bigger prerequisites and therefore is not
   123  // dependent one more deployment specific features. It also supports all of the OpenAPI CRUD endpoints so is a good
   124  // endpoint to test on
   125  // This test performs the following:
   126  // 1. Gets all available roles using "Get all endpoint"
   127  // 2.1 Uses FIQL query filtering to retrieve specific item by ID on "Get All" endpoint
   128  // 2.2 Use GET by ID endpoint to check that each of roles retrieved by get all can be found individually
   129  // 2.3 Compares retrieved struct by using "Get all" endpoint and FIQL filter with struct retrieved by using "Get By ID"
   130  // endpoint
   131  // 3. Creates a new role and verifies it is created as specified by using deep equality
   132  // 4. Updates role description
   133  // 5. Deletes created role
   134  // 6. Tests read for deleted item
   135  // 7. Create role once more using "Sync" version of POST function
   136  // 7.1 Queries TestConnection endpoint using "Sync" version of POST function to see that it handles 200OK accordingly
   137  // 8. Update role once more using "Sync" version of PUT function
   138  // 9. Delete role once again
   139  func (vcd *TestVCD) Test_OpenApiInlineStructCRUDRoles(check *C) {
   140  	endpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointRoles
   141  	apiVersion, err := vcd.client.Client.checkOpenApiEndpointCompatibility(endpoint)
   142  	check.Assert(err, IsNil)
   143  	skipOpenApiEndpointTest(vcd, check, endpoint)
   144  
   145  	// Step 1 - Get all roles
   146  	urlRef, err := vcd.client.Client.OpenApiBuildEndpoint(endpoint)
   147  	check.Assert(err, IsNil)
   148  
   149  	type InlineRoles struct {
   150  		ID          string `json:"id,omitempty"`
   151  		Name        string `json:"name"`
   152  		Description string `json:"description"`
   153  		BundleKey   string `json:"bundleKey"`
   154  		ReadOnly    bool   `json:"readOnly"`
   155  	}
   156  
   157  	allExistingRoles := []*InlineRoles{{}}
   158  	err = vcd.vdc.client.OpenApiGetAllItems(apiVersion, urlRef, nil, &allExistingRoles, nil)
   159  	check.Assert(err, IsNil)
   160  
   161  	// Step 2 - Get all roles using query filters
   162  	for _, oneRole := range allExistingRoles {
   163  		// Step 2.1 - retrieve specific role by using FIQL filter
   164  		urlRef2, err := vcd.client.Client.OpenApiBuildEndpoint(endpoint)
   165  		check.Assert(err, IsNil)
   166  
   167  		queryParams := url.Values{}
   168  		queryParams.Add("filter", "id=="+oneRole.ID)
   169  
   170  		expectOneRoleResultById := []*InlineRoles{{}}
   171  
   172  		err = vcd.vdc.client.OpenApiGetAllItems(apiVersion, urlRef2, queryParams, &expectOneRoleResultById, nil)
   173  		check.Assert(err, IsNil)
   174  		check.Assert(len(expectOneRoleResultById) == 1, Equals, true)
   175  
   176  		// Step 2.2 - retrieve specific role by using endpoint
   177  		singleRef, err := vcd.client.Client.OpenApiBuildEndpoint(endpoint + oneRole.ID)
   178  		check.Assert(err, IsNil)
   179  
   180  		oneRole := &InlineRoles{}
   181  		err = vcd.vdc.client.OpenApiGetItem(apiVersion, singleRef, nil, oneRole, nil)
   182  		check.Assert(err, IsNil)
   183  		check.Assert(oneRole, NotNil)
   184  
   185  		// Step 2.3 - compare struct retrieved by using filter and the one retrieved by exact endpoint ID
   186  		check.Assert(oneRole, DeepEquals, expectOneRoleResultById[0])
   187  
   188  	}
   189  
   190  	// Step 3 - Create a new role and ensure it is created as specified by doing deep comparison
   191  	createUrl, err := vcd.client.Client.OpenApiBuildEndpoint(endpoint)
   192  	check.Assert(err, IsNil)
   193  
   194  	newRole := &InlineRoles{
   195  		Name:        check.TestName(),
   196  		Description: "Role created by test",
   197  		// This BundleKey is being set by VCD even if it is not sent
   198  		BundleKey: types.VcloudUndefinedKey,
   199  		ReadOnly:  false,
   200  	}
   201  	newRoleResponse := &InlineRoles{}
   202  	err = vcd.client.Client.OpenApiPostItem(apiVersion, createUrl, nil, newRole, newRoleResponse, nil)
   203  	check.Assert(err, IsNil)
   204  
   205  	// Ensure supplied and created structs differ only by ID
   206  	newRole.ID = newRoleResponse.ID
   207  	check.Assert(newRoleResponse, DeepEquals, newRole)
   208  
   209  	// Step 4 - update created role (change description)
   210  	newRoleResponse.Description = "Updated description created by test"
   211  	updateUrl, err := vcd.client.Client.OpenApiBuildEndpoint(endpoint, newRoleResponse.ID)
   212  	check.Assert(err, IsNil)
   213  
   214  	updatedRoleResponse := &InlineRoles{}
   215  	err = vcd.client.Client.OpenApiPutItem(apiVersion, updateUrl, nil, newRoleResponse, updatedRoleResponse, nil)
   216  	check.Assert(err, IsNil)
   217  
   218  	// Ensure supplied and response objects are identical (update worked)
   219  	check.Assert(updatedRoleResponse, DeepEquals, newRoleResponse)
   220  
   221  	// Step 5 - delete created role
   222  	deleteUrlRef, err := vcd.client.Client.OpenApiBuildEndpoint(endpoint, newRoleResponse.ID)
   223  	check.Assert(err, IsNil)
   224  
   225  	err = vcd.client.Client.OpenApiDeleteItem(apiVersion, deleteUrlRef, nil, nil)
   226  	check.Assert(err, IsNil)
   227  
   228  	// Step 6 - try to read deleted role and expect error to contain 'ErrorEntityNotFound'
   229  	// Read is tricky - it throws an error ACCESS_TO_RESOURCE_IS_FORBIDDEN when the resource with ID does not
   230  	// exist therefore one cannot know what kind of error occurred.
   231  	lostRole := &InlineRoles{}
   232  	err = vcd.client.Client.OpenApiGetItem(apiVersion, deleteUrlRef, nil, lostRole, nil)
   233  	check.Assert(ContainsNotFound(err), Equals, true)
   234  
   235  	// Step 7 - test synchronous POST and PUT functions (because Roles is a synchronous OpenAPI endpoint)
   236  	newRole.ID = "" // unset ID as it cannot be set for creation
   237  	err = vcd.client.Client.OpenApiPostItemSync(apiVersion, createUrl, nil, newRole, newRoleResponse)
   238  	check.Assert(err, IsNil)
   239  
   240  	// Ensure supplied and created structs differ only by ID
   241  	newRole.ID = newRoleResponse.ID
   242  	check.Assert(newRoleResponse, DeepEquals, newRole)
   243  
   244  	// Step 7.1 test synchronous POST with return code 200 OK works accordingly - This is checked because OpenAPI endpoint TestConnection returns 200 instead of 201 when success
   245  	var testConnectionResult types.TestConnectionResult
   246  	testConnectionPayload := types.TestConnection{
   247  		Host:    vcd.client.Client.VCDHREF.Host,
   248  		Port:    443,
   249  		Secure:  addrOf(true),
   250  		Timeout: 10,
   251  	}
   252  
   253  	testConnectionEndpoint := types.OpenApiPathVersion1_0_0 + types.OpenApiEndpointTestConnection
   254  	apiVersionTestConnection, err := vcd.client.Client.checkOpenApiEndpointCompatibility(testConnectionEndpoint)
   255  	check.Assert(err, IsNil)
   256  
   257  	urlRefTestConnection, err := vcd.client.Client.OpenApiBuildEndpoint(testConnectionEndpoint)
   258  	check.Assert(err, IsNil)
   259  
   260  	err = vcd.client.Client.OpenApiPostItemSync(apiVersionTestConnection, urlRefTestConnection, nil, testConnectionPayload, &testConnectionResult) // This call will get a 200 OK, which is what is being tested here
   261  	check.Assert(err, IsNil)
   262  
   263  	// Step 8 - update role using synchronous PUT function
   264  	newRoleResponse.Description = "Updated description created by sync test"
   265  	updateUrl2, err := vcd.client.Client.OpenApiBuildEndpoint(endpoint, newRoleResponse.ID)
   266  	check.Assert(err, IsNil)
   267  
   268  	updatedRoleResponse2 := &InlineRoles{}
   269  	err = vcd.client.Client.OpenApiPutItem(apiVersion, updateUrl2, nil, newRoleResponse, updatedRoleResponse2, nil)
   270  	check.Assert(err, IsNil)
   271  
   272  	// Ensure supplied and response objects are identical (update worked)
   273  	check.Assert(updatedRoleResponse2, DeepEquals, newRoleResponse)
   274  
   275  	// Step 9 - delete role once again
   276  	deleteUrlRef2, err := vcd.client.Client.OpenApiBuildEndpoint(endpoint, newRoleResponse.ID)
   277  	check.Assert(err, IsNil)
   278  
   279  	err = vcd.client.Client.OpenApiDeleteItem(apiVersion, deleteUrlRef2, nil, nil)
   280  	check.Assert(err, IsNil)
   281  
   282  }
   283  
   284  func (vcd *TestVCD) Test_OpenApiTestConnection(check *C) {
   285  	// TestConnection is going to be used against the same VCD instance as the client is connected
   286  	urlTest1 := vcd.client.Client.VCDHREF
   287  	urlTest1.Path = "vcsp/lib/a0c959b4-a6dd-4a68-8042-5025f42d845e"
   288  	urlTest2 := vcd.client.Client.VCDHREF
   289  	urlTest2.Scheme = "http"
   290  	urlTest3 := vcd.client.Client.VCDHREF
   291  	urlTest3.Host = "imadethisup.io"
   292  	urlTest4 := vcd.client.Client.VCDHREF
   293  	urlTest4.Host = fmt.Sprintf("%s:666", urlTest4.Hostname()) // For testing custom port feature
   294  	tests := []struct {
   295  		SubscriptionURL  string
   296  		WantedConnection bool
   297  		WantedError      bool
   298  	}{
   299  		{
   300  			SubscriptionURL:  urlTest1.String(),
   301  			WantedConnection: true,  // it connects and it does SSL connection
   302  			WantedError:      false, //
   303  		},
   304  		{
   305  			SubscriptionURL:  urlTest2.String(),
   306  			WantedConnection: true, // it connects but it does not do SSL connection
   307  			WantedError:      true,
   308  		},
   309  		{
   310  			SubscriptionURL:  urlTest3.String(), // it doesn't do neither connection nor SSL
   311  			WantedConnection: false,
   312  			WantedError:      true,
   313  		},
   314  		{
   315  			SubscriptionURL:  urlTest4.String(), // it doesn't do neither connection nor SSL but tests custom port
   316  			WantedConnection: false,
   317  			WantedError:      true,
   318  		},
   319  	}
   320  
   321  	for _, test := range tests {
   322  		result, err := vcd.client.Client.TestConnectionWithDefaults(test.SubscriptionURL)
   323  		check.Assert(err == nil, Equals, !test.WantedError)
   324  		check.Assert(result, Equals, test.WantedConnection)
   325  	}
   326  }
   327  
   328  // getAuditTrailTimestampWithElements helps to pick good timestamp filter so that it doesn't take long time to retrieve
   329  // too many items
   330  func getAuditTrailTimestampWithElements(elementCount int, check *C, vcd *TestVCD, apiVersion string, urlRef *url.URL) string {
   331  	client := vcd.client.Client
   332  	qp := url.Values{}
   333  	qp.Add("pageSize", "128")
   334  	qp.Add("sortDesc", "timestamp") // Need to get the newest
   335  	req := client.newOpenApiRequest(apiVersion, qp, http.MethodGet, urlRef, nil, nil)
   336  
   337  	resp, err := client.Http.Do(req)
   338  	check.Assert(err, IsNil)
   339  
   340  	type AuditTrailTimestamp struct {
   341  		Timestamp string `json:"timestamp"`
   342  	}
   343  
   344  	onePageAuditTrail := make([]AuditTrailTimestamp, 1)
   345  	onePageResponse := &types.OpenApiPages{}
   346  	err = decodeBody(types.BodyTypeJSON, resp, &onePageResponse)
   347  	check.Assert(err, IsNil)
   348  
   349  	err = resp.Body.Close()
   350  	check.Assert(err, IsNil)
   351  
   352  	err = json.Unmarshal(onePageResponse.Values, &onePageAuditTrail)
   353  	check.Assert(err, IsNil)
   354  
   355  	var singleElement AuditTrailTimestamp
   356  
   357  	// Find newest element limited by provided elementCount
   358  	if len(onePageAuditTrail) < elementCount {
   359  		singleElement = onePageAuditTrail[(len(onePageAuditTrail) - 1)]
   360  	} else {
   361  		singleElement = onePageAuditTrail[(elementCount - 1)]
   362  	}
   363  
   364  	timeFormat := dateparse.MustParse(singleElement.Timestamp)
   365  
   366  	return timeFormat.Format(types.FiqlQueryTimestampFormat)
   367  }