This is an automated email from the ASF dual-hosted git repository.

zrhoffman pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new 6ca8fda7f9 Tenants rfc3339 apiv5 (#7749)
6ca8fda7f9 is described below

commit 6ca8fda7f9fc3430e729a8ebd55a0f7d9f0c140c
Author: Kurtis Michie <[email protected]>
AuthorDate: Mon Aug 28 13:54:07 2023 -0600

    Tenants rfc3339 apiv5 (#7749)
    
    * 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
---
 CHANGELOG.md                                       |   1 +
 docs/source/api/v5/tenants.rst                     |   4 +-
 docs/source/api/v5/tenants_id.rst                  |   2 +-
 lib/go-tc/tenants.go                               |  40 ++
 traffic_ops/testing/api/v5/tenants_test.go         |  83 ++--
 traffic_ops/testing/api/v5/traffic_control_test.go |   2 +-
 traffic_ops/traffic_ops_golang/apitenant/tenant.go | 464 +++++++++++++++++++++
 .../traffic_ops_golang/cachegroup/cachegroups.go   |  17 +-
 traffic_ops/traffic_ops_golang/routing/routes.go   |   8 +-
 traffic_ops/v5-client/deliveryservice.go           |   2 +-
 traffic_ops/v5-client/deliveryservice_requests.go  |   2 +-
 traffic_ops/v5-client/origin.go                    |   2 +-
 traffic_ops/v5-client/tenant.go                    |  20 +-
 traffic_ops/v5-client/user.go                      |   2 +-
 14 files changed, 584 insertions(+), 65 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 8c81da6f43..6dd1d9e647 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -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.
diff --git a/docs/source/api/v5/tenants.rst b/docs/source/api/v5/tenants.rst
index 2d07db1664..8927fd46df 100644
--- a/docs/source/api/v5/tenants.rst
+++ b/docs/source/api/v5/tenants.rst
@@ -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
                }
        ]}
@@ -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
        }}
diff --git a/docs/source/api/v5/tenants_id.rst 
b/docs/source/api/v5/tenants_id.rst
index 3746a4577c..042b225ae7 100644
--- a/docs/source/api/v5/tenants_id.rst
+++ b/docs/source/api/v5/tenants_id.rst
@@ -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
        }}
 
diff --git a/lib/go-tc/tenants.go b/lib/go-tc/tenants.go
index c048a30d8c..ff27165953 100644
--- a/lib/go-tc/tenants.go
+++ b/lib/go-tc/tenants.go
@@ -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"`
@@ -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
diff --git a/traffic_ops/testing/api/v5/tenants_test.go 
b/traffic_ops/testing/api/v5/tenants_test.go
index dab8c60dc9..3c46776441 100644
--- a/traffic_ops/testing/api/v5/tenants_test.go
+++ b/traffic_ops/testing/api/v5/tenants_test.go
@@ -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"
@@ -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,
@@ -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"})),
@@ -109,11 +110,11 @@ 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})),
@@ -121,10 +122,10 @@ func TestTenants(t *testing.T) {
                                "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)),
                                },
@@ -132,22 +133,22 @@ func TestTenants(t *testing.T) {
                                        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)),
@@ -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)
                                }
@@ -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)
        }
 }
@@ -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)
        }
@@ -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))
@@ -257,11 +258,11 @@ 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)
        }
@@ -269,7 +270,7 @@ func validateTenantDescSort() utils.CkReqFunc {
 
 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")
@@ -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
        }
 }
 
@@ -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)
diff --git a/traffic_ops/testing/api/v5/traffic_control_test.go 
b/traffic_ops/testing/api/v5/traffic_control_test.go
index 1f453ae1c1..4154bb7f50 100644
--- a/traffic_ops/testing/api/v5/traffic_control_test.go
+++ b/traffic_ops/testing/api/v5/traffic_control_test.go
@@ -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"`
diff --git a/traffic_ops/traffic_ops_golang/apitenant/tenant.go 
b/traffic_ops/traffic_ops_golang/apitenant/tenant.go
index 929656490a..4047baa076 100644
--- a/traffic_ops/traffic_ops_golang/apitenant/tenant.go
+++ b/traffic_ops/traffic_ops_golang/apitenant/tenant.go
@@ -23,12 +23,14 @@ package apitenant
 
 import (
        "database/sql"
+       "encoding/json"
        "errors"
        "fmt"
        "net/http"
        "strconv"
        "time"
 
+       "github.com/apache/trafficcontrol/lib/go-log"
        "github.com/apache/trafficcontrol/lib/go-tc"
        "github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
        "github.com/apache/trafficcontrol/lib/go-util"
@@ -36,7 +38,10 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
        
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
        "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
+       
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/util/ims"
+
        validation "github.com/go-ozzo/ozzo-validation"
+       "github.com/jmoiron/sqlx"
        "github.com/lib/pq"
 )
 
@@ -336,3 +341,462 @@ func deleteQuery() string {
 WHERE id=:id`
        return query
 }
+
+func selectMaxLastUpdatedQuery(where string) string {
+       tableName := "tenant"
+       return `SELECT max(t) from (
+               SELECT max(last_updated) as t from ` + tableName + ` q ` + 
where +
+               ` UNION ALL
+       select max(last_updated) as t from last_deleted l where l.table_name='` 
+ tableName + `') as res`
+}
+
+// CreateTenant [Version : V5] function Process the *http.Request and creates 
new tenant
+func CreateTenant(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+
+       tx := inf.Tx.Tx
+
+       defer r.Body.Close()
+
+       tenant, readValErr := readAndValidateJsonStruct(r)
+       if readValErr != nil {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+               return
+       }
+
+       // Check if tenant is tenable
+       authorized, err := isTenantAuthorizedV5(&tenant, inf.User, tx)
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("checking tenant authorized: "+err.Error()))
+               return
+       }
+       if !authorized {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusForbidden, 
errors.New("not authorized on this tenant"), nil)
+               return
+       }
+
+       resultRows, err := inf.Tx.NamedQuery(insertQuery(), tenant)
+       if err != nil {
+               userErr, sysErr, errCode = api.ParseDBError(err)
+               api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+               return
+       }
+       defer resultRows.Close()
+
+       var id int
+       lastUpdated := time.Time{}
+       rowsAffected := 0
+       for resultRows.Next() {
+               rowsAffected++
+               if err := resultRows.Scan(&id, &lastUpdated); err != nil {
+                       api.HandleErr(w, r, tx, http.StatusInternalServerError, 
nil, errors.New("tenant create scanning: "+err.Error()))
+                       return
+               }
+       }
+
+       if rowsAffected == 0 {
+               api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, 
errors.New("tenant create: no tenant was inserted, no id was returned"))
+               return
+       } else if rowsAffected > 1 {
+               api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, 
errors.New("too many ids returned from tenant insert"))
+               return
+       }
+       tenant.ID = &id
+       tenant.LastUpdated = &lastUpdated
+
+       alerts := tc.CreateAlerts(tc.SuccessLevel, "tenant was created.")
+       api.WriteAlertsObj(w, r, http.StatusOK, alerts, tenant)
+       changeLogMsg := fmt.Sprintf("TENANT: %s, ID: %d, ACTION: Created 
tenant", *tenant.Name, *tenant.ID)
+       api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx)
+       return
+}
+
+// GetTenant [Version : V5] function Process the *http.Request and writes the 
response. It uses getTenant function.
+func GetTenant(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+       tx := inf.Tx.Tx
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+
+       user, _ := auth.GetCurrentUser(r.Context())
+       tenantID := user.TenantID
+       if tenantID == auth.TenantIDInvalid {
+               return
+       }
+
+       code := http.StatusOK
+       useIMS := false
+       config, e := api.GetConfig(r.Context())
+       if e == nil && config != nil {
+               useIMS = config.UseIMS
+       } else {
+               log.Warnf("Couldn't get config %v", e)
+       }
+
+       var maxTime *time.Time
+       var usrErr error
+       var syErr error
+
+       var tenants []tc.TenantV5
+
+       api.DefaultSort(inf, "name")
+       tenants, usrErr, syErr, code, maxTime = getTenants(inf.Tx, inf.Params, 
useIMS, r.Header, tenantID)
+       if usrErr != nil {
+               api.HandleErr(w, r, tx, code, fmt.Errorf("read tenant: get 
tenant: "+usrErr.Error()), nil)
+       }
+       if syErr != nil {
+               api.HandleErr(w, r, tx, code, nil, fmt.Errorf("read tenant: get 
tenant: "+syErr.Error()))
+       }
+       if maxTime != nil && api.SetLastModifiedHeader(r, useIMS) {
+               api.AddLastModifiedHdr(w, *maxTime)
+               w.WriteHeader(http.StatusNotModified)
+               return
+       }
+
+       tenantNames := map[int]*string{}
+       for _, it := range tenants {
+               tenantNames[*it.ID] = it.Name
+       }
+       for _, it := range tenants {
+               if it.ParentID == nil || tenantNames[*it.ParentID] == nil {
+                       // root tenant has no parent
+                       continue
+               }
+               p := *tenantNames[*it.ParentID]
+               it.ParentName = &p // copy
+       }
+       api.WriteResp(w, r, tenants)
+}
+
+func getTenants(tx *sqlx.Tx, params map[string]string, useIMS bool, header 
http.Header, id int) ([]tc.TenantV5, error, error, int, *time.Time) {
+       tenants := make([]tc.TenantV5, 0)
+       code := http.StatusOK
+
+       var maxTime time.Time
+       var runSecond bool
+
+       // Query Parameters to Database Query column mappings
+       queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+               "active":      {Column: "q.active", Checker: nil},
+               "name":        {Column: "q.name", Checker: nil},
+               "parent_name": {Column: "p.name", Checker: nil},
+               "id":          {Column: "q.id", Checker: api.IsInt},
+               "parent_id":   {Column: "q.parent_id", Checker: api.IsInt},
+       }
+       if _, ok := params["orderby"]; !ok {
+               params["orderby"] = "name"
+       }
+
+       where, orderBy, pagination, queryValues, errs := 
dbhelpers.BuildWhereAndOrderByAndPagination(params, queryParamsToQueryCols)
+       if len(errs) > 0 {
+               return nil, util.JoinErrs(errs), nil, http.StatusBadRequest, nil
+       }
+
+       if useIMS {
+               runSecond, maxTime = ims.TryIfModifiedSinceQuery(tx, header, 
queryValues, selectMaxLastUpdatedQuery(where))
+               if !runSecond {
+                       log.Debugln("IMS HIT")
+                       code = http.StatusNotModified
+                       return tenants, nil, nil, code, &maxTime
+               }
+               log.Debugln("IMS MISS")
+       } else {
+               log.Debugln("Non IMS request")
+       }
+
+       // Case where we need to run the second query
+       query := selectQuery(id) + where + orderBy + pagination
+       rows, err := tx.NamedQuery(query, queryValues)
+       if err != nil {
+               return nil, nil, err, http.StatusInternalServerError, nil
+       }
+       defer rows.Close()
+
+       for rows.Next() {
+               var t tc.TenantV5
+
+               if err = rows.Scan(
+                       &t.ID,
+                       &t.Name,
+                       &t.Active,
+                       &t.ParentID,
+                       &t.LastUpdated,
+                       &t.ParentName,
+               ); err != nil {
+                       return nil, nil, err, http.StatusInternalServerError, 
nil
+               }
+               tenants = append(tenants, t)
+       }
+
+       return tenants, nil, nil, http.StatusOK, nil
+}
+
+// UpdateTenant [Version : V5] function Process the *http.Request and updates 
the tenant.
+func UpdateTenant(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+
+       tx := inf.Tx.Tx
+
+       defer r.Body.Close()
+       var tenant tc.TenantV5
+
+       tenant, readValErr := readAndValidateJsonStruct(r)
+       if readValErr != nil {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+               return
+       }
+
+       if id, ok := inf.Params["id"]; !ok {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, 
errors.New("missing key: id"), nil)
+               return
+       } else {
+               idNum, err := strconv.Atoi(id)
+               if err != nil {
+                       api.HandleErr(w, r, tx, http.StatusBadRequest, 
errors.New("couldn't convert ID into a numeric value: "+err.Error()), nil)
+                       return
+               }
+               tenant.ID = &idNum
+
+               existingLastUpdated, found, err := api.GetLastUpdated(inf.Tx, 
idNum, "tenant")
+               if err == nil && found == false {
+                       api.HandleErr(w, r, tx, http.StatusNotFound, 
errors.New("no tenant found with this id"), nil)
+                       return
+               }
+               if err != nil {
+                       api.HandleErr(w, r, tx, http.StatusNotFound, nil, err)
+                       return
+               }
+               if !api.IsUnmodified(r.Header, *existingLastUpdated) {
+                       api.HandleErr(w, r, tx, http.StatusPreconditionFailed, 
api.ResourceModifiedError, nil)
+                       return
+               }
+
+               // Check if tenant is tenable
+               authorized, err := isTenantAuthorizedV5(&tenant, inf.User, tx)
+               if err != nil {
+                       api.HandleErr(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, errors.New("checking tenant authorized: 
"+err.Error()))
+                       return
+               }
+               if !authorized {
+                       api.HandleErr(w, r, inf.Tx.Tx, http.StatusForbidden, 
errors.New("not authorized on this tenant"), nil)
+                       return
+               }
+
+               //Check if tenant is updatable
+               userErr, sysErr, statusCode := isUpdatableV5(&tenant, inf.Tx)
+               if userErr != nil || sysErr != nil {
+                       api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, 
sysErr)
+                       return
+               }
+
+               rows, err := inf.Tx.NamedQuery(updateQuery(), tenant)
+               if err != nil {
+                       userErr, sysErr, errCode = api.ParseDBError(err)
+                       api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+                       return
+               }
+               defer rows.Close()
+
+               if !rows.Next() {
+                       api.HandleErr(w, r, tx, http.StatusNotFound, 
errors.New("no tenant found with this id"), nil)
+                       return
+               }
+               lastUpdated := time.Time{}
+               if err := rows.Scan(&lastUpdated); err != nil {
+                       api.HandleErr(w, r, tx, http.StatusInternalServerError, 
nil, errors.New("scanning lastUpdated from tenant insert: "+err.Error()))
+                       return
+               }
+               tenant.LastUpdated = &lastUpdated
+               if rows.Next() {
+                       api.HandleErr(w, r, tx, http.StatusInternalServerError, 
nil, errors.New("tenant update affected too many rows: >1"))
+                       return
+               }
+
+               alerts := tc.CreateAlerts(tc.SuccessLevel, "tenant was 
updated.")
+               api.WriteAlertsObj(w, r, http.StatusOK, alerts, tenant)
+               changeLogMsg := fmt.Sprintf("TENANT: %s, ID: %d, ACTION: 
Updated tenant", *tenant.Name, *tenant.ID)
+               api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, 
tx)
+               return
+       }
+}
+
+// DeleteTenant [Version : V5] function deletes the tenant passed.
+func DeleteTenant(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+       tx := inf.Tx.Tx
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+
+       ID := inf.Params["id"]
+       id, err := strconv.Atoi(ID)
+       if err != nil {
+               api.HandleErr(w, r, tx, http.StatusUnprocessableEntity, 
fmt.Errorf("delete cachegroup: converted to type int: "+err.Error()), nil)
+               return
+       }
+
+       useIMS := false
+       var tenantV5 []tc.TenantV5
+
+       tenantV5, usrErr, syErr, code, maxTime := getTenants(inf.Tx, 
inf.Params, useIMS, r.Header, id)
+       if userErr != nil {
+               api.HandleErr(w, r, tx, code, fmt.Errorf("delete tenant: get 
tenant: "+usrErr.Error()), nil)
+       }
+       if sysErr != nil {
+               api.HandleErr(w, r, tx, code, nil, fmt.Errorf("delete tenant: 
get tenant: "+syErr.Error()))
+       }
+       if maxTime != nil && api.SetLastModifiedHeader(r, useIMS) {
+               api.AddLastModifiedHdr(w, *maxTime)
+               w.WriteHeader(http.StatusNotModified)
+               return
+       }
+
+       // Check if tenant is tenable
+       authorized, err := isTenantAuthorizedV5(&tenantV5[0], inf.User, tx)
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("checking tenant authorized: "+err.Error()))
+               return
+       }
+       if !authorized {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusForbidden, 
errors.New("not authorized on this tenant"), nil)
+               return
+       }
+
+       res, err := tx.Exec("DELETE FROM tenant AS t WHERE t.ID=$1", id)
+       if err != nil {
+               usrErr, syErr, code := parseDeleteErr(err, id, tx)
+               api.HandleErr(w, r, tx, code, usrErr, syErr)
+               return
+       }
+       rowsAffected, err := res.RowsAffected()
+       if err != nil {
+               api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, 
fmt.Errorf("determining rows affected for delete cachegroup: %w", err))
+               return
+       }
+       if rowsAffected == 0 {
+               api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, 
fmt.Errorf("no rows deleted for cachegroup"))
+               return
+       }
+
+       alertMessage := fmt.Sprint("tenant was deleted.")
+       alerts := tc.CreateAlerts(tc.SuccessLevel, alertMessage)
+       api.WriteAlerts(w, r, http.StatusOK, alerts)
+       changeLogMsg := fmt.Sprintf("TENANT: ID: %d, ACTION: Deleted tenant", 
id)
+       api.CreateChangeLogRawTx(api.ApiChange, changeLogMsg, inf.User, tx)
+       return
+}
+
+// IsTenantAuthorized implements the Tenantable interface for TOTenant
+// returns true if the user has access on this tenant and on the ParentID if 
changed.
+func isTenantAuthorizedV5(tenantV5 *tc.TenantV5, user *auth.CurrentUser, tx 
*sql.Tx) (bool, error) {
+       var ok = false
+       var err error
+
+       if tenantV5 == nil {
+               // should never happen
+               return ok, err
+       }
+
+       if tenantV5.ID != nil && *tenantV5.ID != 0 {
+               // modifying an existing tenant
+               ok, err = tenant.IsResourceAuthorizedToUserTx(*tenantV5.ID, 
user, tx)
+               if !ok || err != nil {
+                       return ok, err
+               }
+
+               if tenantV5.ParentID == nil || *tenantV5.ParentID == 0 {
+                       // not changing parent
+                       return ok, err
+               }
+
+               // get current parentID to check if it's being changed
+               var parentID int
+               // If it's the root tenant, don't check for parent
+               if tenantV5.Name != nil && *tenantV5.Name != rootName {
+                       err = tx.QueryRow(`SELECT parent_id FROM tenant WHERE 
id = ` + strconv.Itoa(*tenantV5.ID)).Scan(&parentID)
+                       if err != nil {
+                               return false, err
+                       }
+                       if parentID == *tenantV5.ParentID {
+                               // parent not being changed
+                               return ok, err
+                       }
+               }
+       }
+
+       // creating new tenant -- must specify a parent
+       if tenantV5.ParentID == nil || *tenantV5.ParentID == 0 {
+               return false, err
+       }
+
+       return tenant.IsResourceAuthorizedToUserTx(*tenantV5.ParentID, user, tx)
+}
+
+func isUpdatableV5(tenantV5 *tc.TenantV5, tx *sqlx.Tx) (error, error, int) {
+       if tenantV5.Name != nil && *tenantV5.Name == rootName {
+               return errors.New("trying to change the root tenant, which is 
immutable"), nil, http.StatusBadRequest
+       }
+
+       // Perform SelectQuery
+       vals := []tc.TenantNullable{}
+       query := selectQuery(*tenantV5.ID)
+       rows, err := tx.Queryx(query)
+       if err != nil {
+               return nil, errors.New("querying tenant: " + err.Error()), 
http.StatusInternalServerError
+       }
+       defer rows.Close()
+
+       for rows.Next() {
+               var v tc.TenantNullable
+               if err = rows.StructScan(&v); err != nil {
+                       return nil, errors.New("scanning tenant: " + 
err.Error()), http.StatusInternalServerError
+               }
+               vals = append(vals, v)
+       }
+
+       // Ensure the new desired ParentID does not exist in the susequent list 
of Children
+       for _, val := range vals {
+               if *tenantV5.ParentID == *val.ID {
+                       return errors.New("trying to set existing child as new 
parent"), nil, http.StatusBadRequest
+               }
+       }
+       return nil, nil, http.StatusOK
+}
+
+// readAndValidateJsonStruct populates select missing fields and validates 
JSON body
+func readAndValidateJsonStruct(r *http.Request) (tc.TenantV5, error) {
+       var ten tc.TenantV5
+       if err := json.NewDecoder(r.Body).Decode(&ten); err != nil {
+               userErr := fmt.Errorf("error decoding POST request body into 
TenantV5 struct %w", err)
+               return ten, userErr
+       }
+
+       // validate JSON body
+       rule := 
validation.NewStringRule(tovalidate.IsAlphanumericUnderscoreDash, "must consist 
of only alphanumeric, dash, or underscore characters")
+       errs := tovalidate.ToErrors(validation.Errors{
+               "name":       validation.Validate(ten.Name, 
validation.Required, rule),
+               "active":     validation.Validate(ten.Active), // only validate 
it's boolean
+               "parentId":   validation.Validate(ten.ParentID, 
validation.Required, validation.Min(1)),
+               "parentName": nil,
+       })
+       if len(errs) > 0 {
+               userErr := util.JoinErrs(errs)
+               return ten, userErr
+       }
+       return ten, nil
+}
diff --git a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go 
b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
index 38ab868732..c507e97318 100644
--- a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
+++ b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
@@ -1008,7 +1008,6 @@ func GetCacheGroup(w http.ResponseWriter, r 
*http.Request) {
 }
 
 func getCacheGroup(tx *sqlx.Tx, params map[string]string, useIMS bool, header 
http.Header) ([]interface{}, time.Time, int, error, error) {
-       //func getCacheGroup(tx *sqlx.Tx, params map[string]string, useIMS 
bool, header http.Header) ([]tc.CacheGroupV5, time.Time, int, error, error) {
        var runSecond bool
        var maxTime time.Time
        cgList := []interface{}{}
@@ -1154,6 +1153,12 @@ func CreateCacheGroup(w http.ResponseWriter, r 
*http.Request) {
                return
        }
 
+       err = dgCg.ValidateTypeInTopology()
+       if err != nil {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, 
fmt.Errorf("update cachegroup: validating type in topology: "+err.Error()), err)
+               return
+       }
+
        checkLastUpdated := `UPDATE cachegroup SET coordinate=$1 WHERE id=$2 
RETURNING last_updated`
 
        err = tx.QueryRow(
@@ -1433,8 +1438,16 @@ func readAndValidateJsonStruct(r *http.Request) 
(tc.CacheGroupNullableV5, error)
 
        // validate JSON body
        rule := 
validation.NewStringRule(tovalidate.IsAlphanumericUnderscoreDash, "must consist 
of only alphanumeric, dash, or underscore characters")
+       latitudeErr := "Must be a floating point number within the range +-90"
+       longitudeErr := "Must be a floating point number within the range +-180"
        errs := tovalidate.ToErrors(validation.Errors{
-               "name": validation.Validate(cg.Name, validation.Required, rule),
+               "name":                        validation.Validate(cg.Name, 
validation.Required, rule),
+               "shortName":                   
validation.Validate(cg.ShortName, validation.Required, rule),
+               "latitude":                    validation.Validate(cg.Latitude, 
validation.Min(-90.0).Error(latitudeErr), 
validation.Max(90.0).Error(latitudeErr)),
+               "longitude":                   
validation.Validate(cg.Longitude, validation.Min(-180.0).Error(longitudeErr), 
validation.Max(180.0).Error(longitudeErr)),
+               "parentCacheGroupID":          
validation.Validate(cg.ParentCachegroupID, validation.Min(1)),
+               "secondaryParentCachegroupID": 
validation.Validate(cg.SecondaryParentCachegroupID, validation.Min(1)),
+               "localizationMethods":         
validation.Validate(cg.LocalizationMethods, 
validation.By(tovalidate.IsPtrToSliceOfUniqueStringersICase("CZ", "DEEP_CZ", 
"GEO"))),
        })
        if len(errs) > 0 {
                userErr := util.JoinErrs(errs)
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go 
b/traffic_ops/traffic_ops_golang/routing/routes.go
index 45a178f67e..0355a22164 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -445,10 +445,10 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
                {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodDelete, Path: `profileparameters/{profileId}/{parameterId}$`, 
Handler: profileparameter.DeleteProfileParameter, RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:UPDATE", 
"PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: 
nil, ID: 42483952931},
 
                //Tenants
-               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodGet, Path: `tenants/?$`, Handler: 
api.ReadHandler(&apitenant.TOTenant{}), RequiredPrivLevel: 
auth.PrivLevelReadOnly, RequiredPermissions: []string{"TENANT:READ"}, 
Authenticated: Authenticated, Middlewares: nil, ID: 467796781431},
-               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodPut, Path: `tenants/{id}$`, Handler: 
api.UpdateHandler(&apitenant.TOTenant{}), RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: []string{"TENANT:UPDATE", 
"TENANT:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 
409413147831},
-               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodPost, Path: `tenants/?$`, Handler: 
api.CreateHandler(&apitenant.TOTenant{}), RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: []string{"TENANT:CREATE", 
"TENANT:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 
41724801331},
-               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodDelete, Path: `tenants/{id}$`, Handler: 
api.DeleteHandler(&apitenant.TOTenant{}), RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: []string{"TENANT:DELETE", 
"TENANT:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 
41636555831},
+               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodGet, Path: `tenants/?$`, Handler: apitenant.GetTenant, 
RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: 
[]string{"TENANT:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 
467796781431},
+               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodPut, Path: `tenants/{id}$`, Handler: apitenant.UpdateTenant, 
RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: 
[]string{"TENANT:UPDATE", "TENANT:READ"}, Authenticated: Authenticated, 
Middlewares: nil, ID: 409413147831},
+               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodPost, Path: `tenants/?$`, Handler: apitenant.CreateTenant, 
RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: 
[]string{"TENANT:CREATE", "TENANT:READ"}, Authenticated: Authenticated, 
Middlewares: nil, ID: 41724801331},
+               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodDelete, Path: `tenants/{id}$`, Handler: apitenant.DeleteTenant, 
RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: 
[]string{"TENANT:DELETE", "TENANT:READ"}, Authenticated: Authenticated, 
Middlewares: nil, ID: 41636555831},
 
                //CRConfig
                {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodGet, Path: `cdns/{cdn}/snapshot/?$`, Handler: 
crconfig.SnapshotGetHandler, RequiredPrivLevel: auth.PrivLevelReadOnly, 
RequiredPermissions: []string{"CDN-SNAPSHOT:READ"}, Authenticated: 
Authenticated, Middlewares: nil, ID: 495727369531},
diff --git a/traffic_ops/v5-client/deliveryservice.go 
b/traffic_ops/v5-client/deliveryservice.go
index 2a669d585a..b0e2ec2702 100644
--- a/traffic_ops/v5-client/deliveryservice.go
+++ b/traffic_ops/v5-client/deliveryservice.go
@@ -184,7 +184,7 @@ func (to *Session) CreateDeliveryService(ds 
tc.DeliveryServiceV5, opts RequestOp
                if len(ten.Response) == 0 {
                        return resp, reqInf, fmt.Errorf("no Tenant named '%s'", 
*ds.Tenant)
                }
-               ds.TenantID = ten.Response[0].ID
+               ds.TenantID = *ten.Response[0].ID
        }
 
        reqInf, err := to.post(apiDeliveryServices, RequestOptions{Header: 
opts.Header}, ds, &resp)
diff --git a/traffic_ops/v5-client/deliveryservice_requests.go 
b/traffic_ops/v5-client/deliveryservice_requests.go
index 0198cb98c0..7336fde031 100644
--- a/traffic_ops/v5-client/deliveryservice_requests.go
+++ b/traffic_ops/v5-client/deliveryservice_requests.go
@@ -102,7 +102,7 @@ func (to *Session) CreateDeliveryServiceRequest(dsr 
tc.DeliveryServiceRequestV5,
                if err != nil || len(ten.Response) == 0 {
                        return resp, reqInf, fmt.Errorf("no Tenant named '%s'", 
*ds.Tenant)
                }
-               ds.TenantID = ten.Response[0].ID
+               ds.TenantID = *ten.Response[0].ID
        }
 
        reqInf, err := to.post(apiDSRequests, opts, dsr, &resp)
diff --git a/traffic_ops/v5-client/origin.go b/traffic_ops/v5-client/origin.go
index c5f050ed88..0964b43c15 100644
--- a/traffic_ops/v5-client/origin.go
+++ b/traffic_ops/v5-client/origin.go
@@ -94,7 +94,7 @@ func (to *Session) originIDs(origin *tc.Origin) error {
                if len(tenant.Response) == 0 {
                        return fmt.Errorf("no Tenant with name '%s'", 
*origin.Tenant)
                }
-               origin.TenantID = &tenant.Response[0].ID
+               origin.TenantID = tenant.Response[0].ID
        }
 
        return nil
diff --git a/traffic_ops/v5-client/tenant.go b/traffic_ops/v5-client/tenant.go
index a7fddab8d8..049b256408 100644
--- a/traffic_ops/v5-client/tenant.go
+++ b/traffic_ops/v5-client/tenant.go
@@ -29,35 +29,35 @@ const apiTenants = "/tenants"
 const apiTenantID = apiTenants + "/%d"
 
 // GetTenants retrieves all Tenants stored in Traffic Ops.
-func (to *Session) GetTenants(opts RequestOptions) (tc.GetTenantsResponse, 
toclientlib.ReqInf, error) {
-       var data tc.GetTenantsResponse
+func (to *Session) GetTenants(opts RequestOptions) (tc.GetTenantsResponseV5, 
toclientlib.ReqInf, error) {
+       var data tc.GetTenantsResponseV5
        reqInf, err := to.get(apiTenants, opts, &data)
        return data, reqInf, err
 }
 
 // CreateTenant creates the Tenant it's passed.
-func (to *Session) CreateTenant(t tc.Tenant, opts RequestOptions) 
(tc.TenantResponse, toclientlib.ReqInf, error) {
-       if t.ParentID == 0 && t.ParentName != "" {
+func (to *Session) CreateTenant(t tc.TenantV5, opts RequestOptions) 
(tc.TenantResponseV5, toclientlib.ReqInf, error) {
+       if (t.ParentID == nil || *t.ParentID == 0) && (t.ParentName != nil || 
*t.ParentName != "") {
                parentOpts := NewRequestOptions()
-               parentOpts.QueryParameters.Set("name", t.ParentName)
+               parentOpts.QueryParameters.Set("name", *t.ParentName)
                tenant, reqInf, err := to.GetTenants(parentOpts)
                if err != nil {
-                       return tc.TenantResponse{Alerts: tenant.Alerts}, 
reqInf, err
+                       return tc.TenantResponseV5{Alerts: tenant.Alerts}, 
reqInf, err
                }
                if len(tenant.Response) < 1 {
-                       return tc.TenantResponse{Alerts: tenant.Alerts}, 
reqInf, fmt.Errorf("no Tenant could be found for Parent Tenant '%s'", 
t.ParentName)
+                       return tc.TenantResponseV5{Alerts: tenant.Alerts}, 
reqInf, fmt.Errorf("no Tenant could be found for Parent Tenant '%s'", 
*t.ParentName)
                }
                t.ParentID = tenant.Response[0].ID
        }
 
-       var data tc.TenantResponse
+       var data tc.TenantResponseV5
        reqInf, err := to.post(apiTenants, opts, t, &data)
        return data, reqInf, err
 }
 
 // UpdateTenant replaces the Tenant identified by 'id' with the one provided.
-func (to *Session) UpdateTenant(id int, t tc.Tenant, opts RequestOptions) 
(tc.TenantResponse, toclientlib.ReqInf, error) {
-       var data tc.TenantResponse
+func (to *Session) UpdateTenant(id int, t tc.TenantV5, opts RequestOptions) 
(tc.TenantResponseV5, toclientlib.ReqInf, error) {
+       var data tc.TenantResponseV5
        reqInf, err := to.put(fmt.Sprintf(apiTenantID, id), opts, t, &data)
        return data, reqInf, err
 }
diff --git a/traffic_ops/v5-client/user.go b/traffic_ops/v5-client/user.go
index 38b46be17c..2cfb8a6d13 100644
--- a/traffic_ops/v5-client/user.go
+++ b/traffic_ops/v5-client/user.go
@@ -62,7 +62,7 @@ func (to *Session) CreateUser(user tc.UserV4, opts 
RequestOptions) (tc.CreateUse
                if len(tenant.Response) < 1 {
                        return tc.CreateUserResponseV4{Alerts: tenant.Alerts}, 
toclientlib.ReqInf{}, fmt.Errorf("no such Tenant: '%s'", *user.Tenant)
                }
-               user.TenantID = tenant.Response[0].ID
+               user.TenantID = *tenant.Response[0].ID
        }
 
        if user.Role != "" {


Reply via email to