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 }