Skip to content

Commit

Permalink
Tenants rfc3339 apiv5 (#7749)
Browse files Browse the repository at this point in the history
* Created APIv5 routing for Tenants. Unfinished

* Fixed pointers and mismatched Tenant Struct type

* Reworked PUT

* Fixed broken tests and updated missing validation from cachegroups

* Rearranged imports

* Added to CHANGELOG.md

* Changed format mismatch for t.ParentName to be pointer

* Uncommented out tests for v4, resetting back to original

* Moved import

* Fixed changelog conflicts
  • Loading branch information
kdamichie authored Aug 28, 2023
1 parent 41f1fc8 commit 6ca8fda
Show file tree
Hide file tree
Showing 14 changed files with 584 additions and 65 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
- [#7628](https://github.com/apache/trafficcontrol/pull/7628) *Traffic Ops* Fixed an issue where certificate chain validation failed based on leading or trailing whitespace.
- [#7686](https://github.com/apache/trafficcontrol/pull/7686) *Traffic Ops* Fixed secured parameters being visible when role has proper permissions.
- [#7697](https://github.com/apache/trafficcontrol/pull/7697) *Traffic Ops* Fixed `iloPassword` and `xmppPassword` checking for priv-level instead of using permissions.
- [#7749](https://github.com/apache/trafficcontrol/pull/7749) *Traffic Ops* Fixes `tenants` v5 apis to respond with `RFC3339` date/time Format.

### Removed
- [#7271](https://github.com/apache/trafficcontrol/pull/7271) Removed components in `infrastructre/docker/`, not in use as cdn-in-a-box performs the same functionality.
Expand Down
4 changes: 2 additions & 2 deletions docs/source/api/v5/tenants.rst
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ Response Structure
"id": 1,
"name": "root",
"active": true,
"lastUpdated": "2018-12-10 19:11:17+00",
"lastUpdated": "2023-05-30T19:52:58.183642+00:00",
"parentId": null
}
]}
Expand Down Expand Up @@ -164,6 +164,6 @@ Response Structure
"id": 9,
"name": "test",
"active": true,
"lastUpdated": "2018-12-11 19:37:16+00",
"lastUpdated": "2023-05-30T19:52:58.183642+00:00",
"parentId": 1
}}
2 changes: 1 addition & 1 deletion docs/source/api/v5/tenants_id.rst
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ Response Structure
"id": 9,
"name": "quest",
"active": true,
"lastUpdated": "2018-12-11 20:30:54+00",
"lastUpdated": "2023-05-30T19:52:58.183642+00:00",
"parentId": 3
}}
Expand Down
40 changes: 40 additions & 0 deletions lib/go-tc/tenants.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ package tc
* under the License.
*/

import "time"

// GetTenantsResponse is the response for a request for a group of tenants.
type GetTenantsResponse struct {
Response []Tenant `json:"response"`
Expand Down Expand Up @@ -72,3 +74,41 @@ type TenantAlert struct {
Level string `json:"level"`
Text string `json:"text"`
}

// GetTenantsResponseV50 is the response for a request for a group of tenants.
type GetTenantsResponseV50 struct {
Response []TenantV5 `json:"response"`
Alerts
}

// TenantResponseV50 is the type of response from Traffic Ops to a PUT, POST,
// or DELETE request made to its /tenants.
type TenantResponseV50 struct {
Response TenantV5 `json:"response"`
Alerts
}

// A TenantV50 is a scope that can be applied to groups of users to limit their
// Delivery Service-related actions to specific sets of similarly "Tenanted"
// Delivery Services.
type TenantV50 struct {
ID *int `json:"id" db:"id"`
Name *string `json:"name" db:"name"`
Active *bool `json:"active" db:"active"`
LastUpdated *time.Time `json:"lastUpdated" db:"last_updated"`
ParentID *int `json:"parentId" db:"parent_id"`
ParentName *string `json:"parentName,omitempty" db:"parent_name"`
}

// GetTenantsResponseV5 is the type of response from the tenants
// Traffic Ops endpoint.
// It always points to the type for the latest minor version of GetTenantsResponseV5x APIv5.
type GetTenantsResponseV5 = GetTenantsResponseV50

// TenantResponseV5 is the type of response from Traffic Ops to a PUT, POST,
// // or DELETE request made to its /tenants.
// It always points to the type for the latest minor version of TenantResponseV5x APIv5.
type TenantResponseV5 = TenantResponseV50

// TenantV5 always points to the type for the latest minor version of TenantV5x APIv5.
type TenantV5 = TenantV50
83 changes: 42 additions & 41 deletions traffic_ops/testing/api/v5/tenants_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (

"github.com/apache/trafficcontrol/lib/go-rfc"
"github.com/apache/trafficcontrol/lib/go-tc"
"github.com/apache/trafficcontrol/lib/go-util"
"github.com/apache/trafficcontrol/lib/go-util/assert"
"github.com/apache/trafficcontrol/traffic_ops/testing/api/utils"
"github.com/apache/trafficcontrol/traffic_ops/toclientlib"
Expand All @@ -38,7 +39,7 @@ func TestTenants(t *testing.T) {
currentTimeRFC := currentTime.Format(time.RFC1123)
tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123)

methodTests := utils.TestCase[client.Session, client.RequestOptions, tc.Tenant]{
methodTests := utils.TestCase[client.Session, client.RequestOptions, tc.TenantV5]{
"GET": {
"NOT MODIFIED when NO CHANGES made": {
ClientSession: TOSession,
Expand Down Expand Up @@ -95,11 +96,11 @@ func TestTenants(t *testing.T) {
"POST": {
"OK when VALID request": {
ClientSession: TOSession,
RequestBody: tc.Tenant{
Active: true,
Name: "tenant5",
ParentName: "root",
ParentID: GetTenantID(t, "root")(),
RequestBody: tc.TenantV5{
Active: util.Ptr(true),
Name: util.Ptr("tenant5"),
ParentName: util.Ptr("root"),
ParentID: util.Ptr(GetTenantID(t, "root")()),
},
Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
validateTenantCreateUpdateFields(map[string]interface{}{"Name": "tenant5"})),
Expand All @@ -109,45 +110,45 @@ func TestTenants(t *testing.T) {
"OK when VALID request": {
EndpointID: GetTenantID(t, "tenant4"),
ClientSession: TOSession,
RequestBody: tc.Tenant{
Active: false,
Name: "newname",
ParentName: "root",
ParentID: GetTenantID(t, "root")(),
RequestBody: tc.TenantV5{
Active: util.Ptr(false),
Name: util.Ptr("newname"),
ParentName: util.Ptr("root"),
ParentID: util.Ptr(GetTenantID(t, "root")()),
},
Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
validateTenantCreateUpdateFields(map[string]interface{}{"Name": "newname", "Active": false})),
},
"BAD REQUEST when ROOT TENANT": {
EndpointID: GetTenantID(t, "root"),
ClientSession: TOSession,
RequestBody: tc.Tenant{
Active: false,
Name: "tenant1",
ParentName: "root",
RequestBody: tc.TenantV5{
Active: util.Ptr(false),
Name: util.Ptr("tenant1"),
ParentName: util.Ptr("root"),
},
Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
},
"PRECONDITION FAILED when updating with IMS & IUS Headers": {
EndpointID: GetTenantID(t, "tenant2"),
ClientSession: TOSession,
RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}},
RequestBody: tc.Tenant{
Active: false,
Name: "tenant2",
ParentName: "root",
ParentID: GetTenantID(t, "root")(),
RequestBody: tc.TenantV5{
Active: util.Ptr(false),
Name: util.Ptr("tenant2"),
ParentName: util.Ptr("root"),
ParentID: util.Ptr(GetTenantID(t, "root")()),
},
Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)),
},
"PRECONDITION FAILED when updating with IFMATCH ETAG Header": {
EndpointID: GetTenantID(t, "tenant2"),
ClientSession: TOSession,
RequestBody: tc.Tenant{
Active: false,
Name: "tenant2",
ParentName: "root",
ParentID: GetTenantID(t, "root")(),
RequestBody: tc.TenantV5{
Active: util.Ptr(false),
Name: util.Ptr("tenant2"),
ParentName: util.Ptr("root"),
ParentID: util.Ptr(GetTenantID(t, "root")()),
},
RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}},
Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)),
Expand Down Expand Up @@ -204,16 +205,16 @@ func TestTenants(t *testing.T) {
func validateTenantFields(expectedResp map[string]interface{}) utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) {
assert.RequireNotNil(t, resp, "Expected Tenant response to not be nil.")
tenantResp := resp.([]tc.Tenant)
tenantResp := resp.([]tc.TenantV5)
for field, expected := range expectedResp {
for _, tenant := range tenantResp {
switch field {
case "Active":
assert.Equal(t, expected, tenant.Active, "Expected Active to be %v, but got %b", expected, tenant.Active)
assert.Equal(t, expected, *tenant.Active, "Expected Active to be %v, but got %b", expected, tenant.Active)
case "Name":
assert.Equal(t, expected, tenant.Name, "Expected Name to be %v, but got %s", expected, tenant.Name)
assert.Equal(t, expected, *tenant.Name, "Expected Name to be %v, but got %s", expected, tenant.Name)
case "ParentName":
assert.Equal(t, expected, tenant.ParentName, "Expected ParentName to be %v, but got %s", expected, tenant.ParentName)
assert.Equal(t, expected, *tenant.ParentName, "Expected ParentName to be %v, but got %s", expected, tenant.ParentName)
default:
t.Errorf("Expected field: %v, does not exist in response", field)
}
Expand All @@ -225,8 +226,8 @@ func validateTenantFields(expectedResp map[string]interface{}) utils.CkReqFunc {
func validateTenantCreateUpdateFields(expectedResp map[string]interface{}) utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) {
assert.RequireNotNil(t, resp, "Expected Tenant response to not be nil.")
tenantResp := resp.(tc.Tenant)
tenants := []tc.Tenant{tenantResp}
tenantResp := resp.(tc.TenantV5)
tenants := []tc.TenantV5{tenantResp}
validateTenantFields(expectedResp)(t, toclientlib.ReqInf{}, tenants, tc.Alerts{}, nil)
}
}
Expand All @@ -235,9 +236,9 @@ func validateTenantSort() utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) {
assert.RequireNotNil(t, resp, "Expected Tenant response to not be nil.")
var tenants []string
tenantResp := resp.([]tc.Tenant)
tenantResp := resp.([]tc.TenantV5)
for _, tenant := range tenantResp {
tenants = append(tenants, tenant.Name)
tenants = append(tenants, *tenant.Name)
}
assert.Equal(t, true, sort.StringsAreSorted(tenants), "List is not sorted by their names: %v", tenants)
}
Expand All @@ -246,7 +247,7 @@ func validateTenantSort() utils.CkReqFunc {
func validateTenantDescSort() utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) {
assert.RequireNotNil(t, resp, "Expected Tenant response to not be nil.")
tenantDescResp := resp.([]tc.Tenant)
tenantDescResp := resp.([]tc.TenantV5)
var descSortedList []string
var ascSortedList []string
assert.RequireGreaterOrEqual(t, len(tenantDescResp), 2, "Need at least 2 Tenants in Traffic Ops to test desc sort, found: %d", len(tenantDescResp))
Expand All @@ -257,19 +258,19 @@ func validateTenantDescSort() utils.CkReqFunc {
assert.RequireEqual(t, len(tenantsAscResp.Response), len(tenantDescResp), "Expected descending order response length: %v, to match ascending order response length %v", len(tenantsAscResp.Response), len(tenantDescResp))
// Insert Tenant names to the front of a new list, so they are now reversed to be in ascending order.
for _, tenant := range tenantDescResp {
descSortedList = append([]string{tenant.Name}, descSortedList...)
descSortedList = append([]string{*tenant.Name}, descSortedList...)
}
// Insert Tenant names by appending to a new list, so they stay in ascending order.
for _, tenant := range tenantsAscResp.Response {
ascSortedList = append(ascSortedList, tenant.Name)
ascSortedList = append(ascSortedList, *tenant.Name)
}
assert.Exactly(t, ascSortedList, descSortedList, "Tenant responses are not equal after reversal: %v - %v", ascSortedList, descSortedList)
}
}

func validateTenantPagination(paginationParam string) utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) {
paginationResp := resp.([]tc.Tenant)
paginationResp := resp.([]tc.TenantV5)

opts := client.NewRequestOptions()
opts.QueryParameters.Set("orderby", "id")
Expand All @@ -296,7 +297,7 @@ func GetTenantID(t *testing.T, name string) func() int {
tenants, _, err := TOSession.GetTenants(opts)
assert.RequireNoError(t, err, "Get Tenants Request failed with error:", err)
assert.RequireEqual(t, 1, len(tenants.Response), "Expected response object length 1, but got %d", len(tenants.Response))
return tenants.Response[0].ID
return *tenants.Response[0].ID
}
}

Expand All @@ -314,13 +315,13 @@ func DeleteTestTenants(t *testing.T) {
assert.NoError(t, err, "Cannot get Tenants: %v - alerts: %+v", err, tenants.Alerts)

for _, tenant := range tenants.Response {
if tenant.Name == "root" {
if *tenant.Name == "root" {
continue
}
alerts, _, err := TOSession.DeleteTenant(tenant.ID, client.RequestOptions{})
alerts, _, err := TOSession.DeleteTenant(*tenant.ID, client.RequestOptions{})
assert.NoError(t, err, "Unexpected error deleting Tenant '%s' (#%d): %v - alerts: %+v", tenant.Name, tenant.ID, err, alerts.Alerts)
// Retrieve the Tenant to see if it got deleted
opts.QueryParameters.Set("id", strconv.Itoa(tenant.ID))
opts.QueryParameters.Set("id", strconv.Itoa(*tenant.ID))
getTenants, _, err := TOSession.GetTenants(opts)
assert.NoError(t, err, "Error getting Tenant '%s' after deletion: %v - alerts: %+v", tenant.Name, err, getTenants.Alerts)
assert.Equal(t, 0, len(getTenants.Response), "Expected Tenant '%s' to be deleted, but it was found in Traffic Ops", tenant.Name)
Expand Down
2 changes: 1 addition & 1 deletion traffic_ops/testing/api/v5/traffic_control_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ type TrafficControl struct {
Statuses []tc.StatusV5 `json:"statuses"`
StaticDNSEntries []tc.StaticDNSEntryV5 `json:"staticdnsentries"`
StatsSummaries []tc.StatsSummaryV5 `json:"statsSummaries"`
Tenants []tc.Tenant `json:"tenants"`
Tenants []tc.TenantV5 `json:"tenants"`
ServerCheckExtensions []tc.ServerCheckExtensionNullable `json:"servercheck_extensions"`
Topologies []tc.TopologyV5 `json:"topologies"`
Types []tc.TypeV5 `json:"types"`
Expand Down
Loading

0 comments on commit 6ca8fda

Please sign in to comment.