You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by da...@apache.org on 2019/01/16 20:05:11 UTC

[trafficcontrol] branch master updated: Fix Traffic Ops Tenancy and Activity Bugs, Fix TO API Test Framework to work with Tenancy (#3163)

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

dangogh 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 b8cc607  Fix Traffic Ops Tenancy and Activity Bugs, Fix TO API Test Framework to work with Tenancy (#3163)
b8cc607 is described below

commit b8cc6076a4b004894f693d5998b7974876274b89
Author: Robert Butts <ro...@users.noreply.github.com>
AuthorDate: Wed Jan 16 13:05:05 2019 -0700

    Fix Traffic Ops Tenancy and Activity Bugs, Fix TO API Test Framework to work with Tenancy (#3163)
    
    * Fix TO Tenancy for inactive parents
    
    - Fix TO Tenancy to prevent access when parent tenants are inactive.
    
    - Add TO API Tests to verify tenancy, verify access is disabled when
      parents or self are inactive.
    
    - Fix TO API Test user panic on test failure.
    
    - Fix TO API Test tenants to automatically delete children before
      parents, rather than hard-coding.
    
    - Fix TO API Test parameters race condition, fixed to delete all
      parameters not only first name+file.
    
    - Fix TO API Tests to enable Tenancy on all tests (by creating
      Parameters, which creates the use_tenancy=true param).
      The TO API Tests were completely broken with tenancy, due
      to the above bugs. With the bugs fixed, the tests pass
      with tenancy enabled.
    
    Fixes #2732
    
    * Change TO client GetDSSN? to use common func
    
    As requested in PR review.
---
 traffic_ops/client/asn.go                          |  32 ----
 traffic_ops/client/deliveryserviceserver.go        |  47 +++++
 traffic_ops/testing/api/v14/cachegroups_test.go    |   2 +-
 .../api/v14/cachegroupsdeliveryservices_test.go    |   7 +-
 traffic_ops/testing/api/v14/cdn_domains_test.go    |   2 +-
 traffic_ops/testing/api/v14/cdnfederations_test.go |   2 +-
 traffic_ops/testing/api/v14/cdns_test.go           |   2 +-
 traffic_ops/testing/api/v14/coordinates_test.go    |   2 +-
 traffic_ops/testing/api/v14/crconfig_test.go       |   2 +-
 .../v14/deliveryservice_request_comments_test.go   |   2 +-
 .../api/v14/deliveryservice_requests_test.go       |  10 +-
 .../testing/api/v14/deliveryservicematches_test.go |   2 +-
 .../testing/api/v14/deliveryservices_test.go       |   2 +-
 .../testing/api/v14/deliveryserviceservers_test.go |   2 +-
 .../api/v14/deliveryservicesideligible_test.go     |   2 +-
 traffic_ops/testing/api/v14/divisions_test.go      |   2 +-
 traffic_ops/testing/api/v14/federations_test.go    |   2 +-
 traffic_ops/testing/api/v14/origins_test.go        |   2 +-
 traffic_ops/testing/api/v14/parameters_test.go     |  10 +-
 traffic_ops/testing/api/v14/phys_locations_test.go |   2 +-
 traffic_ops/testing/api/v14/regions_test.go        |   2 +-
 traffic_ops/testing/api/v14/roles_test.go          |   2 +-
 traffic_ops/testing/api/v14/servers_test.go        |   2 +-
 .../testing/api/v14/staticdnsentries_test.go       |   2 +-
 traffic_ops/testing/api/v14/statuses_test.go       |   2 +-
 traffic_ops/testing/api/v14/steering_test.go       |   2 +-
 .../testing/api/v14/steeringtargets_test.go        |   2 +-
 traffic_ops/testing/api/v14/tc-fixtures.json       | 119 ++++++++++++
 traffic_ops/testing/api/v14/tenants_test.go        | 202 +++++++++++++++++++--
 traffic_ops/testing/api/v14/types_test.go          |   2 +-
 traffic_ops/testing/api/v14/user_test.go           |   7 +-
 .../testing/api/v14/userdeliveryservices_test.go   |   2 +-
 traffic_ops/traffic_ops_golang/tenant/tenancy.go   |  68 ++++++-
 33 files changed, 455 insertions(+), 95 deletions(-)

diff --git a/traffic_ops/client/asn.go b/traffic_ops/client/asn.go
index 89204c7..2a38c65 100644
--- a/traffic_ops/client/asn.go
+++ b/traffic_ops/client/asn.go
@@ -17,11 +17,9 @@ package client
 
 import (
 	"encoding/json"
-	"errors"
 	"fmt"
 	"net"
 	"net/http"
-	"strconv"
 
 	"github.com/apache/trafficcontrol/lib/go-tc"
 )
@@ -132,33 +130,3 @@ func (to *Session) DeleteASNByASN(asn int) (tc.Alerts, ReqInf, error) {
 	err = json.NewDecoder(resp.Body).Decode(&alerts)
 	return alerts, reqInf, nil
 }
-
-func (to *Session) DeleteDeliveryServiceServer(dsID int, serverID int) (tc.Alerts, ReqInf, error) {
-	route := apiBase + `/deliveryservice_server/` + strconv.Itoa(dsID) + "/" + strconv.Itoa(serverID)
-	reqResp, remoteAddr, err := to.request(http.MethodDelete, route, nil)
-	reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
-	if err != nil {
-		return tc.Alerts{}, reqInf, errors.New("requesting from Traffic Ops: " + err.Error())
-	}
-	defer reqResp.Body.Close()
-	resp := tc.Alerts{}
-	if err = json.NewDecoder(reqResp.Body).Decode(&resp); err != nil {
-		return tc.Alerts{}, reqInf, errors.New("decoding response: " + err.Error())
-	}
-	return resp, reqInf, nil
-}
-
-func (to *Session) GetDeliveryServiceServers() (tc.DeliveryServiceServerResponse, ReqInf, error) {
-	route := apiBase + `/deliveryserviceserver`
-	reqResp, remoteAddr, err := to.request(http.MethodGet, route, nil)
-	reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
-	if err != nil {
-		return tc.DeliveryServiceServerResponse{}, reqInf, errors.New("requesting from Traffic Ops: " + err.Error())
-	}
-	defer reqResp.Body.Close()
-	resp := tc.DeliveryServiceServerResponse{}
-	if err = json.NewDecoder(reqResp.Body).Decode(&resp); err != nil {
-		return tc.DeliveryServiceServerResponse{}, reqInf, errors.New("decoding response: " + err.Error())
-	}
-	return resp, reqInf, nil
-}
diff --git a/traffic_ops/client/deliveryserviceserver.go b/traffic_ops/client/deliveryserviceserver.go
index bff05fd..642e5fd 100644
--- a/traffic_ops/client/deliveryserviceserver.go
+++ b/traffic_ops/client/deliveryserviceserver.go
@@ -17,6 +17,10 @@ package client
 
 import (
 	"encoding/json"
+	"errors"
+	"net/http"
+	"net/url"
+	"strconv"
 
 	"github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/lib/go-util"
@@ -42,3 +46,46 @@ func (to *Session) CreateDeliveryServiceServers(dsID int, serverIDs []int, repla
 	}
 	return &resp.Response, nil
 }
+
+func (to *Session) DeleteDeliveryServiceServer(dsID int, serverID int) (tc.Alerts, ReqInf, error) {
+	route := apiBase + `/deliveryservice_server/` + strconv.Itoa(dsID) + "/" + strconv.Itoa(serverID)
+	reqResp, remoteAddr, err := to.request(http.MethodDelete, route, nil)
+	reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
+	if err != nil {
+		return tc.Alerts{}, reqInf, errors.New("requesting from Traffic Ops: " + err.Error())
+	}
+	defer reqResp.Body.Close()
+	resp := tc.Alerts{}
+	if err = json.NewDecoder(reqResp.Body).Decode(&resp); err != nil {
+		return tc.Alerts{}, reqInf, errors.New("decoding response: " + err.Error())
+	}
+	return resp, reqInf, nil
+}
+
+// GetDeliveryServiceServers gets all delivery service servers, with the default API limit.
+func (to *Session) GetDeliveryServiceServers() (tc.DeliveryServiceServerResponse, ReqInf, error) {
+	return to.getDeliveryServiceServers(url.Values{})
+}
+
+// GetDeliveryServiceServersN gets all delivery service servers, with a limit of n.
+func (to *Session) GetDeliveryServiceServersN(n int) (tc.DeliveryServiceServerResponse, ReqInf, error) {
+	return to.getDeliveryServiceServers(url.Values{"limit": []string{strconv.Itoa(n)}})
+}
+
+func (to *Session) getDeliveryServiceServers(urlQuery url.Values) (tc.DeliveryServiceServerResponse, ReqInf, error) {
+	route := apiBase + `/deliveryserviceserver`
+	if qry := urlQuery.Encode(); qry != "" {
+		route += `?` + qry
+	}
+	reqResp, remoteAddr, err := to.request(http.MethodGet, route, nil)
+	reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
+	if err != nil {
+		return tc.DeliveryServiceServerResponse{}, reqInf, errors.New("requesting from Traffic Ops: " + err.Error())
+	}
+	defer reqResp.Body.Close()
+	resp := tc.DeliveryServiceServerResponse{}
+	if err = json.NewDecoder(reqResp.Body).Decode(&resp); err != nil {
+		return tc.DeliveryServiceServerResponse{}, reqInf, errors.New("decoding response: " + err.Error())
+	}
+	return resp, reqInf, nil
+}
diff --git a/traffic_ops/testing/api/v14/cachegroups_test.go b/traffic_ops/testing/api/v14/cachegroups_test.go
index 626d352..7f94c73 100644
--- a/traffic_ops/testing/api/v14/cachegroups_test.go
+++ b/traffic_ops/testing/api/v14/cachegroups_test.go
@@ -25,7 +25,7 @@ import (
 )
 
 func TestCacheGroups(t *testing.T) {
-	WithObjs(t, []TCObj{Types, CacheGroups}, func() {
+	WithObjs(t, []TCObj{Types, Parameters, CacheGroups}, func() {
 		GetTestCacheGroups(t)
 		CheckCacheGroupsAuthentication(t)
 		UpdateTestCacheGroups(t)
diff --git a/traffic_ops/testing/api/v14/cachegroupsdeliveryservices_test.go b/traffic_ops/testing/api/v14/cachegroupsdeliveryservices_test.go
index dd91730..428c9ce 100644
--- a/traffic_ops/testing/api/v14/cachegroupsdeliveryservices_test.go
+++ b/traffic_ops/testing/api/v14/cachegroupsdeliveryservices_test.go
@@ -16,12 +16,13 @@ package v14
 */
 
 import (
-	"github.com/apache/trafficcontrol/lib/go-log"
 	"testing"
+
+	"github.com/apache/trafficcontrol/lib/go-log"
 )
 
 func TestDeliveryServicesCachegroups(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Types, Tenants, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices}, func() {
+	WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices}, func() {
 		CreateTestCachegroupsDeliveryServices(t)
 		DeleteTestCachegroupsDeliveryServices(t)
 	})
@@ -100,7 +101,7 @@ func CreateTestCachegroupsDeliveryServices(t *testing.T) {
 func DeleteTestCachegroupsDeliveryServices(t *testing.T) {
 	log.Debugln("DeleteTestCachegroupsDeliveryServices")
 
-	dss, _, err := TOSession.GetDeliveryServiceServers()
+	dss, _, err := TOSession.GetDeliveryServiceServersN(1000000)
 	if err != nil {
 		t.Errorf("cannot GET DeliveryServiceServers: %v\n", err)
 	}
diff --git a/traffic_ops/testing/api/v14/cdn_domains_test.go b/traffic_ops/testing/api/v14/cdn_domains_test.go
index 6206c1f..76e5881 100644
--- a/traffic_ops/testing/api/v14/cdn_domains_test.go
+++ b/traffic_ops/testing/api/v14/cdn_domains_test.go
@@ -30,7 +30,7 @@ func GetTestDomains(t *testing.T) {
 }
 
 func TestDomains(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Types, Profiles, Statuses}, func() {
+	WithObjs(t, []TCObj{CDNs, Types, Parameters, Profiles, Statuses}, func() {
 		GetTestDomains(t)
 	})
 }
diff --git a/traffic_ops/testing/api/v14/cdnfederations_test.go b/traffic_ops/testing/api/v14/cdnfederations_test.go
index 11718bd..aad93b7 100644
--- a/traffic_ops/testing/api/v14/cdnfederations_test.go
+++ b/traffic_ops/testing/api/v14/cdnfederations_test.go
@@ -25,7 +25,7 @@ import (
 var fedIDs []int
 
 func TestCDNFederations(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Types, Tenants, DeliveryServices, CDNFederations}, func() {
+	WithObjs(t, []TCObj{CDNs, Types, Parameters, Tenants, DeliveryServices, CDNFederations}, func() {
 		UpdateTestCDNFederations(t)
 		GetTestCDNFederations(t)
 	})
diff --git a/traffic_ops/testing/api/v14/cdns_test.go b/traffic_ops/testing/api/v14/cdns_test.go
index 8271deb..46a8be7 100644
--- a/traffic_ops/testing/api/v14/cdns_test.go
+++ b/traffic_ops/testing/api/v14/cdns_test.go
@@ -23,7 +23,7 @@ import (
 )
 
 func TestCDNs(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs}, func() {
+	WithObjs(t, []TCObj{CDNs, Parameters}, func() {
 		UpdateTestCDNs(t)
 		GetTestCDNs(t)
 	})
diff --git a/traffic_ops/testing/api/v14/coordinates_test.go b/traffic_ops/testing/api/v14/coordinates_test.go
index 0e66f29..0ddbe5b 100644
--- a/traffic_ops/testing/api/v14/coordinates_test.go
+++ b/traffic_ops/testing/api/v14/coordinates_test.go
@@ -23,7 +23,7 @@ import (
 )
 
 func TestCoordinates(t *testing.T) {
-	WithObjs(t, []TCObj{Coordinates}, func() {
+	WithObjs(t, []TCObj{Parameters, Coordinates}, func() {
 		GetTestCoordinates(t)
 		UpdateTestCoordinates(t)
 	})
diff --git a/traffic_ops/testing/api/v14/crconfig_test.go b/traffic_ops/testing/api/v14/crconfig_test.go
index 7fa6af3..e10a4e5 100644
--- a/traffic_ops/testing/api/v14/crconfig_test.go
+++ b/traffic_ops/testing/api/v14/crconfig_test.go
@@ -25,7 +25,7 @@ import (
 )
 
 func TestCRConfig(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Types, Tenants, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices}, func() {
+	WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices}, func() {
 		UpdateTestCRConfigSnapshot(t)
 	})
 }
diff --git a/traffic_ops/testing/api/v14/deliveryservice_request_comments_test.go b/traffic_ops/testing/api/v14/deliveryservice_request_comments_test.go
index 6e77a2f..df6ab48 100644
--- a/traffic_ops/testing/api/v14/deliveryservice_request_comments_test.go
+++ b/traffic_ops/testing/api/v14/deliveryservice_request_comments_test.go
@@ -22,7 +22,7 @@ import (
 )
 
 func TestDeliveryServiceRequestComments(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Types, Tenants, DeliveryServiceRequests, DeliveryServiceRequestComments}, func() {
+	WithObjs(t, []TCObj{CDNs, Types, Parameters, Tenants, DeliveryServiceRequests, DeliveryServiceRequestComments}, func() {
 		UpdateTestDeliveryServiceRequestComments(t)
 		GetTestDeliveryServiceRequestComments(t)
 	})
diff --git a/traffic_ops/testing/api/v14/deliveryservice_requests_test.go b/traffic_ops/testing/api/v14/deliveryservice_requests_test.go
index 6538912..dce58a4 100644
--- a/traffic_ops/testing/api/v14/deliveryservice_requests_test.go
+++ b/traffic_ops/testing/api/v14/deliveryservice_requests_test.go
@@ -32,7 +32,7 @@ const (
 )
 
 func TestDeliveryServiceRequests(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Types, Tenants, DeliveryServiceRequests}, func() {
+	WithObjs(t, []TCObj{CDNs, Types, Parameters, Tenants, DeliveryServiceRequests}, func() {
 		GetTestDeliveryServiceRequests(t)
 		UpdateTestDeliveryServiceRequests(t)
 	})
@@ -51,7 +51,7 @@ func CreateTestDeliveryServiceRequests(t *testing.T) {
 }
 
 func TestDeliveryServiceRequestRequired(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Types, Tenants}, func() {
+	WithObjs(t, []TCObj{CDNs, Types, Parameters, Tenants}, func() {
 		dsr := testData.DeliveryServiceRequests[dsrRequired]
 		alerts, _, err := TOSession.CreateDeliveryServiceRequest(dsr)
 		if err != nil {
@@ -65,7 +65,7 @@ func TestDeliveryServiceRequestRequired(t *testing.T) {
 }
 
 func TestDeliveryServiceRequestRules(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Types, Tenants}, func() {
+	WithObjs(t, []TCObj{CDNs, Types, Parameters, Tenants}, func() {
 		routingName := strings.Repeat("X", 1) + "." + strings.Repeat("X", 48)
 		// Test the xmlId length and form
 		XMLID := "X " + strings.Repeat("X", 46)
@@ -114,7 +114,7 @@ func TestDeliveryServiceRequestTypeFields(t *testing.T) {
 }
 
 func TestDeliveryServiceRequestBad(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Types, Tenants}, func() {
+	WithObjs(t, []TCObj{CDNs, Types, Parameters, Tenants}, func() {
 		// try to create non-draft/submitted
 		src := testData.DeliveryServiceRequests[dsrDraft]
 		s, err := tc.RequestStatusFromString("pending")
@@ -136,7 +136,7 @@ func TestDeliveryServiceRequestBad(t *testing.T) {
 
 // TestDeliveryServiceRequestWorkflow tests that transitions of Status are
 func TestDeliveryServiceRequestWorkflow(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Types, Tenants}, func() {
+	WithObjs(t, []TCObj{CDNs, Types, Parameters, Tenants}, func() {
 		// test empty request table
 		dsrs, _, err := TOSession.GetDeliveryServiceRequests()
 		if err != nil {
diff --git a/traffic_ops/testing/api/v14/deliveryservicematches_test.go b/traffic_ops/testing/api/v14/deliveryservicematches_test.go
index b7b041b..214d0ac 100644
--- a/traffic_ops/testing/api/v14/deliveryservicematches_test.go
+++ b/traffic_ops/testing/api/v14/deliveryservicematches_test.go
@@ -22,7 +22,7 @@ import (
 )
 
 func TestDeliveryServiceMatches(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Types, Tenants, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices}, func() {
+	WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices}, func() {
 		GetTestDeliveryServiceMatches(t)
 	})
 }
diff --git a/traffic_ops/testing/api/v14/deliveryservices_test.go b/traffic_ops/testing/api/v14/deliveryservices_test.go
index 4f6a7fd..f80fe90 100644
--- a/traffic_ops/testing/api/v14/deliveryservices_test.go
+++ b/traffic_ops/testing/api/v14/deliveryservices_test.go
@@ -24,7 +24,7 @@ import (
 )
 
 func TestDeliveryServices(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Types, Tenants, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices}, func() {
+	WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices}, func() {
 		UpdateTestDeliveryServices(t)
 		UpdateNullableTestDeliveryServices(t)
 		GetTestDeliveryServices(t)
diff --git a/traffic_ops/testing/api/v14/deliveryserviceservers_test.go b/traffic_ops/testing/api/v14/deliveryserviceservers_test.go
index a3dc4b5..1d7899e 100644
--- a/traffic_ops/testing/api/v14/deliveryserviceservers_test.go
+++ b/traffic_ops/testing/api/v14/deliveryserviceservers_test.go
@@ -22,7 +22,7 @@ import (
 )
 
 func TestDeliveryServiceServers(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Types, Tenants, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices}, func() {
+	WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices}, func() {
 		DeleteTestDeliveryServiceServers(t)
 	})
 }
diff --git a/traffic_ops/testing/api/v14/deliveryservicesideligible_test.go b/traffic_ops/testing/api/v14/deliveryservicesideligible_test.go
index 499c247..a1baa42 100644
--- a/traffic_ops/testing/api/v14/deliveryservicesideligible_test.go
+++ b/traffic_ops/testing/api/v14/deliveryservicesideligible_test.go
@@ -20,7 +20,7 @@ import (
 )
 
 func TestDeliveryServicesEligible(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Types, Tenants, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices}, func() {
+	WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices}, func() {
 		GetTestDeliveryServicesEligible(t)
 	})
 }
diff --git a/traffic_ops/testing/api/v14/divisions_test.go b/traffic_ops/testing/api/v14/divisions_test.go
index 9705c87..d071ac7 100644
--- a/traffic_ops/testing/api/v14/divisions_test.go
+++ b/traffic_ops/testing/api/v14/divisions_test.go
@@ -23,7 +23,7 @@ import (
 )
 
 func TestDivisions(t *testing.T) {
-	WithObjs(t, []TCObj{Divisions}, func() {
+	WithObjs(t, []TCObj{Parameters, Divisions}, func() {
 		UpdateTestDivisions(t)
 		GetTestDivisions(t)
 	})
diff --git a/traffic_ops/testing/api/v14/federations_test.go b/traffic_ops/testing/api/v14/federations_test.go
index 77be998..ddb6e6b 100644
--- a/traffic_ops/testing/api/v14/federations_test.go
+++ b/traffic_ops/testing/api/v14/federations_test.go
@@ -22,7 +22,7 @@ import (
 )
 
 func TestFederations(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Types, Tenants, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, DeliveryServices, UsersDeliveryServices, CDNFederations}, func() {
+	WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, DeliveryServices, UsersDeliveryServices, CDNFederations}, func() {
 		PostTestFederationsDeliveryServices(t)
 		GetTestFederations(t)
 	})
diff --git a/traffic_ops/testing/api/v14/origins_test.go b/traffic_ops/testing/api/v14/origins_test.go
index efb431f..6381292 100644
--- a/traffic_ops/testing/api/v14/origins_test.go
+++ b/traffic_ops/testing/api/v14/origins_test.go
@@ -22,7 +22,7 @@ import (
 )
 
 func TestOrigins(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Types, Tenants, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices, Coordinates, Origins}, func() {
+	WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices, Coordinates, Origins}, func() {
 		UpdateTestOrigins(t)
 		GetTestOrigins(t)
 	})
diff --git a/traffic_ops/testing/api/v14/parameters_test.go b/traffic_ops/testing/api/v14/parameters_test.go
index 0d8b0f5..cb423d3 100644
--- a/traffic_ops/testing/api/v14/parameters_test.go
+++ b/traffic_ops/testing/api/v14/parameters_test.go
@@ -114,9 +114,15 @@ func DeleteTestParameter(t *testing.T, pl tc.Parameter) {
 	if err != nil {
 		t.Errorf("cannot GET Parameter by name: %v - %v\n", pl.Name, err)
 	}
-	if len(resp) > 0 {
-		respParameter := resp[0]
 
+	if len(resp) == 0 {
+		// TODO This fails for the ProfileParameters test; determine a way to check this, even for ProfileParameters
+		// t.Errorf("DeleteTestParameter got no params for %+v %+v\n", pl.Name, pl.ConfigFile)
+	} else if len(resp) > 1 {
+		// TODO figure out why this happens, and be more precise about deleting things where created.
+		// t.Errorf("DeleteTestParameter params for %+v %+v expected 1, actual %+v\n", pl.Name, pl.ConfigFile, len(resp))
+	}
+	for _, respParameter := range resp {
 		delResp, _, err := TOSession.DeleteParameterByID(respParameter.ID)
 		if err != nil {
 			t.Errorf("cannot DELETE Parameter by name: %v - %v\n", err, delResp)
diff --git a/traffic_ops/testing/api/v14/phys_locations_test.go b/traffic_ops/testing/api/v14/phys_locations_test.go
index 70845b1..07b901a 100644
--- a/traffic_ops/testing/api/v14/phys_locations_test.go
+++ b/traffic_ops/testing/api/v14/phys_locations_test.go
@@ -23,7 +23,7 @@ import (
 )
 
 func TestPhysLocations(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Divisions, Regions, PhysLocations}, func() {
+	WithObjs(t, []TCObj{CDNs, Parameters, Divisions, Regions, PhysLocations}, func() {
 		UpdateTestPhysLocations(t)
 		GetTestPhysLocations(t)
 	})
diff --git a/traffic_ops/testing/api/v14/regions_test.go b/traffic_ops/testing/api/v14/regions_test.go
index 29970ef..ae59531 100644
--- a/traffic_ops/testing/api/v14/regions_test.go
+++ b/traffic_ops/testing/api/v14/regions_test.go
@@ -23,7 +23,7 @@ import (
 )
 
 func TestRegions(t *testing.T) {
-	WithObjs(t, []TCObj{Divisions, Regions}, func() {
+	WithObjs(t, []TCObj{Parameters, Divisions, Regions}, func() {
 		UpdateTestRegions(t)
 		GetTestRegions(t)
 		GetTestRegionsByNamePath(t)
diff --git a/traffic_ops/testing/api/v14/roles_test.go b/traffic_ops/testing/api/v14/roles_test.go
index 4ba720f..dd62832 100644
--- a/traffic_ops/testing/api/v14/roles_test.go
+++ b/traffic_ops/testing/api/v14/roles_test.go
@@ -31,7 +31,7 @@ const (
 )
 
 func TestRoles(t *testing.T) {
-	WithObjs(t, []TCObj{Roles}, func() {
+	WithObjs(t, []TCObj{Parameters, Roles}, func() {
 		UpdateTestRoles(t)
 		GetTestRoles(t)
 	})
diff --git a/traffic_ops/testing/api/v14/servers_test.go b/traffic_ops/testing/api/v14/servers_test.go
index 022bfeb..92cf516 100644
--- a/traffic_ops/testing/api/v14/servers_test.go
+++ b/traffic_ops/testing/api/v14/servers_test.go
@@ -23,7 +23,7 @@ import (
 )
 
 func TestServers(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Types, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers}, func() {
+	WithObjs(t, []TCObj{CDNs, Types, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers}, func() {
 		UpdateTestServers(t)
 		GetTestServers(t)
 	})
diff --git a/traffic_ops/testing/api/v14/staticdnsentries_test.go b/traffic_ops/testing/api/v14/staticdnsentries_test.go
index 53da338..547c70e 100644
--- a/traffic_ops/testing/api/v14/staticdnsentries_test.go
+++ b/traffic_ops/testing/api/v14/staticdnsentries_test.go
@@ -24,7 +24,7 @@ import (
 )
 
 func TestStaticDNSEntries(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Types, Tenants, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices, StaticDNSEntries}, func() {
+	WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices, StaticDNSEntries}, func() {
 		GetTestStaticDNSEntries(t)
 		UpdateTestStaticDNSEntries(t)
 		UpdateTestStaticDNSEntriesInvalidAddress(t)
diff --git a/traffic_ops/testing/api/v14/statuses_test.go b/traffic_ops/testing/api/v14/statuses_test.go
index 5040f29..c7af98e 100644
--- a/traffic_ops/testing/api/v14/statuses_test.go
+++ b/traffic_ops/testing/api/v14/statuses_test.go
@@ -23,7 +23,7 @@ import (
 )
 
 func TestStatuses(t *testing.T) {
-	WithObjs(t, []TCObj{Statuses}, func() {
+	WithObjs(t, []TCObj{Parameters, Statuses}, func() {
 		UpdateTestStatuses(t)
 		GetTestStatuses(t)
 	})
diff --git a/traffic_ops/testing/api/v14/steering_test.go b/traffic_ops/testing/api/v14/steering_test.go
index a2529a8..1cef8f8 100644
--- a/traffic_ops/testing/api/v14/steering_test.go
+++ b/traffic_ops/testing/api/v14/steering_test.go
@@ -22,7 +22,7 @@ import (
 )
 
 func TestSteering(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Types, Tenants, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices, SteeringTargets}, func() {
+	WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices, SteeringTargets}, func() {
 		GetTestSteering(t)
 	})
 }
diff --git a/traffic_ops/testing/api/v14/steeringtargets_test.go b/traffic_ops/testing/api/v14/steeringtargets_test.go
index 1167933..d93ef36 100644
--- a/traffic_ops/testing/api/v14/steeringtargets_test.go
+++ b/traffic_ops/testing/api/v14/steeringtargets_test.go
@@ -23,7 +23,7 @@ import (
 )
 
 func TestSteeringTargets(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Types, Tenants, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices, SteeringTargets}, func() {
+	WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices, SteeringTargets}, func() {
 		GetTestSteeringTargets(t)
 		UpdateTestSteeringTargets(t)
 	})
diff --git a/traffic_ops/testing/api/v14/tc-fixtures.json b/traffic_ops/testing/api/v14/tc-fixtures.json
index 110ed92..bd88f2c 100644
--- a/traffic_ops/testing/api/v14/tc-fixtures.json
+++ b/traffic_ops/testing/api/v14/tc-fixtures.json
@@ -361,6 +361,73 @@
             "type": "HTTP_LIVE",
             "xmlId": "ds2",
             "anonymousBlockingEnabled": true
+        },
+        {
+            "active": true,
+            "cdnName": "cdn1",
+            "cacheurl": "cacheUrl3",
+            "ccrDnsTtl": 3600,
+            "cdnName": "cdn1",
+            "checkPath": "",
+            "deepCachingType": "NEVER",
+            "displayName": "d s 1",
+            "dnsBypassCname": null,
+            "dnsBypassIp": "",
+            "dnsBypassIp6": "",
+            "dnsBypassTtl": 30,
+            "dscp": 40,
+            "edgeHeaderRewrite": "edgeRewrite3",
+            "exampleURLs": [
+                "http://ccr.ds3.example.net",
+                "https://ccr.ds3x.example.net"
+            ],
+            "fqPacingRate": 0,
+            "geoLimit": 0,
+            "geoLimitCountries": "",
+            "geoLimitRedirectURL": null,
+            "geoProvider": 0,
+            "globalMaxMbps": 0,
+            "globalMaxTps": 0,
+            "httpBypassFqdn": "",
+            "infoUrl": "TBD",
+            "initialDispersion": 1,
+            "ipv6RoutingEnabled": true,
+            "lastUpdated": "2018-04-06 16:48:51+00",
+            "logsEnabled": false,
+            "longDesc": "d s 3",
+            "longDesc1": "ds3",
+            "longDesc2": "ds3",
+            "matchList": [
+                {
+                    "pattern": ".*\\.ds3\\..*",
+                    "setNumber": 0,
+                    "type": "HOST_REGEXP"
+                }
+            ],
+            "maxDnsAnswers": 0,
+            "midHeaderRewrite": "midRewrite3",
+            "missLat": 41.881944,
+            "missLong": -87.627778,
+            "multiSiteOrigin": false,
+            "orgServerFqdn": "http://origin.ds3.example.net",
+            "originShield": null,
+            "profileDescription": null,
+            "profileName": null,
+            "protocol": 2,
+            "qstringIgnore": 1,
+            "rangeRequestHandling": 0,
+            "regexRemap": "regexRemap3",
+            "regionalGeoBlocking": false,
+            "remapText": "@plugin=tslua.so @pparam=/opt/trafficserver/etc/trafficserver/ds3plugin.lua",
+            "routingName": "ccr-ds3",
+            "signed": false,
+            "signingAlgorithm": "url_sig",
+            "sslKeyVersion": 2,
+            "tenant": "tenant3",
+            "tenantName": "tenant3",
+            "type": "HTTP_LIVE",
+            "xmlId": "ds3",
+            "anonymousBlockingEnabled": true
         }
     ],
     "divisions": [
@@ -1568,6 +1635,16 @@
             "active": false,
             "name": "tenant2",
             "parentName": "tenant1"
+        },
+        {
+            "active": true,
+            "name": "tenant3",
+            "parentName": "tenant2"
+        },
+        {
+            "active": true,
+            "name": "tenant4",
+            "parentName": "root"
         }
     ],
     "types": [
@@ -1829,6 +1906,48 @@
             "tenant": "tenant1",
             "uid": 0,
             "username": "readonlyuser"
+        },
+        {
+            "addressLine1": "address of admin",
+            "addressLine2": "",
+            "city": "Anywhere",
+            "company": "Comcast",
+            "country": "USA",
+            "email": "tenant3user@example.com",
+            "fullName": "Fred the admin",
+            "gid": 0,
+            "localPasswd": "pa$$word",
+            "confirmLocalPasswd": "pa$$word",
+            "newUser": false,
+            "phoneNumber": "810-555-9876",
+            "postalCode": "55443",
+            "publicSshKey": "",
+            "role": 4,
+            "stateOrProvince": "LA",
+            "tenant": "tenant3",
+            "uid": 0,
+            "username": "tenant3user"
+        },
+        {
+            "addressLine1": "address of admin",
+            "addressLine2": "",
+            "city": "Anywhere",
+            "company": "Comcast",
+            "country": "USA",
+            "email": "tenant4user@example.com",
+            "fullName": "Fred the admin",
+            "gid": 0,
+            "localPasswd": "pa$$word",
+            "confirmLocalPasswd": "pa$$word",
+            "newUser": false,
+            "phoneNumber": "810-555-9876",
+            "postalCode": "55443",
+            "publicSshKey": "",
+            "role": 4,
+            "stateOrProvince": "LA",
+            "tenant": "tenant4",
+            "uid": 0,
+            "username": "tenant4user"
         }
     ],
     "steeringTargets": [
diff --git a/traffic_ops/testing/api/v14/tenants_test.go b/traffic_ops/testing/api/v14/tenants_test.go
index ab106c9..2cfa14d 100644
--- a/traffic_ops/testing/api/v14/tenants_test.go
+++ b/traffic_ops/testing/api/v14/tenants_test.go
@@ -19,12 +19,14 @@ import (
 	"strconv"
 	"strings"
 	"testing"
+	"time"
 
-	tc "github.com/apache/trafficcontrol/lib/go-tc"
+	"github.com/apache/trafficcontrol/lib/go-tc"
+	"github.com/apache/trafficcontrol/traffic_ops/client"
 )
 
 func TestTenants(t *testing.T) {
-	WithObjs(t, []TCObj{Tenants}, func() {
+	WithObjs(t, []TCObj{Parameters, Tenants}, func() {
 		GetTestTenants(t)
 		UpdateTestTenants(t)
 	})
@@ -110,27 +112,195 @@ func DeleteTestTenants(t *testing.T) {
 	if err != nil {
 		t.Errorf("cannot GET Tenant by name: %v - %v\n", t1, err)
 	}
+	expectedChildDeleteErrMsg := `Tenant '` + strconv.Itoa(tenant1.ID) + `' has child tenants. Please update these child tenants and retry.`
+	if _, err := TOSession.DeleteTenant(strconv.Itoa(tenant1.ID)); err == nil {
+		t.Fatalf("%s has child tenants -- should not be able to delete", t1)
+	} else if !strings.Contains(err.Error(), expectedChildDeleteErrMsg) {
+		t.Errorf("expected error: %s;  got %s", expectedChildDeleteErrMsg, err.Error())
+	}
+
+	deletedTenants := map[string]struct{}{}
+	for {
+		initLenDeleted := len(deletedTenants)
+		for _, tn := range testData.Tenants {
+			if _, ok := deletedTenants[tn.Name]; ok {
+				continue
+			}
+
+			hasParent := false
+			for _, otherTenant := range testData.Tenants {
+				if _, ok := deletedTenants[otherTenant.Name]; ok {
+					continue
+				}
+				if otherTenant.ParentName == tn.Name {
+					hasParent = true
+					break
+				}
+			}
+			if hasParent {
+				continue
+			}
+
+			toTenant, _, err := TOSession.TenantByName(tn.Name)
+			if err != nil {
+				t.Fatalf("getting tenant %s: %v", tn.Name, err)
+			}
+			if _, err = TOSession.DeleteTenant(strconv.Itoa(toTenant.ID)); err != nil {
+				t.Fatalf("deleting tenant %s: %v", toTenant.Name, err)
+			}
+			deletedTenants[tn.Name] = struct{}{}
+
+		}
+		if len(deletedTenants) == len(testData.Tenants) {
+			break
+		}
+		if len(deletedTenants) == initLenDeleted {
+			t.Fatalf("could not delete tenants: not tenant without an existing child found (cycle?)")
+		}
+	}
+}
+
+func TestTenantsActive(t *testing.T) {
+	CreateTestCDNs(t)
+	CreateTestTypes(t)
+	CreateTestTenants(t)
+	CreateTestParameters(t)
+	CreateTestProfiles(t)
+	CreateTestStatuses(t)
+	CreateTestDivisions(t)
+	CreateTestRegions(t)
+	CreateTestPhysLocations(t)
+	CreateTestCacheGroups(t)
+	CreateTestServers(t)
+	CreateTestDeliveryServices(t)
+	CreateTestUsers(t)
+
+	UpdateTestTenantsActive(t)
+
+	ForceDeleteTestUsers(t)
+	DeleteTestDeliveryServices(t)
+	DeleteTestServers(t)
+	DeleteTestCacheGroups(t)
+	DeleteTestPhysLocations(t)
+	DeleteTestRegions(t)
+	DeleteTestDivisions(t)
+	DeleteTestStatuses(t)
+	DeleteTestProfiles(t)
+	DeleteTestParameters(t)
+	DeleteTestTenants(t)
+	DeleteTestTypes(t)
+	DeleteTestCDNs(t)
+}
+
+func UpdateTestTenantsActive(t *testing.T) {
+	originalTenants, _, err := TOSession.Tenants()
+	if err != nil {
+		t.Fatalf("getting tenants error expected: nil, actual: %+v", err)
+	}
+
+	setTenantActive(t, "tenant1", true)
+	setTenantActive(t, "tenant2", true)
+	setTenantActive(t, "tenant3", false)
+
+	// ds3 has tenant3. Even though tenant3 is inactive, we should still be able to get it, because our user is tenant1, which is active.
+	dses, _, err := TOSession.GetDeliveryServiceByXMLID("ds3")
+	if err != nil {
+		t.Fatalf("failed to get delivery service, when the DS's tenant was inactive (even though our user's tenant was active)")
+	} else if len(dses) != 1 {
+		t.Errorf("admin user getting delivery service ds3 with tenant3, expected: ds, actual: empty")
+	}
 
-	_, err = TOSession.DeleteTenant(strconv.Itoa(tenant1.ID))
-	if err == nil {
-		t.Errorf("%s has child tenants -- should not be able to delete", t1)
+	setTenantActive(t, "tenant1", true)
+	setTenantActive(t, "tenant2", false)
+	setTenantActive(t, "tenant3", true)
+
+	// ds3 has tenant3. Even though tenant3's parent, tenant2, is inactive, we should still be able to get it, because our user is tenant1, which is active.
+	_, _, err = TOSession.GetDeliveryServiceByXMLID("ds3")
+	if err != nil {
+		t.Fatalf("failed to get delivery service, when a parent tenant was inactive (even though our user's tenant was active)")
 	}
-	expected := `Tenant '` + strconv.Itoa(tenant1.ID) + `' has child tenants. Please update these child tenants and retry.`
-	if !strings.Contains(err.Error(), expected) {
-		t.Errorf("expected error: %s;  got %s", expected, err.Error())
+
+	toReqTimeout := time.Second * time.Duration(Config.Default.Session.TimeoutInSecs)
+	tenant3Session, _, err := client.LoginWithAgent(TOSession.URL, "tenant3user", "pa$$word", true, "to-api-v14-client-tests/tenant3user", true, toReqTimeout)
+	if err != nil {
+		t.Fatalf("failed to get log in with tenant3user: " + err.Error())
+	}
+
+	tenant4Session, _, err := client.LoginWithAgent(TOSession.URL, "tenant4user", "pa$$word", true, "to-api-v14-client-tests/tenant4user", true, toReqTimeout)
+	if err != nil {
+		t.Fatalf("failed to get log in with tenant4user: " + err.Error())
 	}
 
-	t2 := "tenant2"
-	tenant2, _, err := TOSession.TenantByName(t2)
-	_, err = TOSession.DeleteTenant(strconv.Itoa(tenant2.ID))
+	// tenant3user with tenant3 has no access to ds3 with tenant3 when parent tenant2 is inactive
+	dses, _, err = tenant3Session.GetDeliveryServiceByXMLID("ds3")
+	for _, ds := range dses {
+		t.Errorf("tenant3user got delivery service %+v with tenant3 but tenant3 parent tenant2 is inactive, expected: no ds", ds.XMLID)
+	}
+
+	setTenantActive(t, "tenant1", true)
+	setTenantActive(t, "tenant2", true)
+	setTenantActive(t, "tenant3", false)
+
+	// tenant3user with tenant3 has no access to ds3 with tenant3 when tenant3 is inactive
+	dses, _, err = tenant3Session.GetDeliveryServiceByXMLID("ds3")
+	for _, ds := range dses {
+		t.Errorf("tenant3user got delivery service %+v with tenant3 but tenant3 is inactive, expected: no ds", ds.XMLID)
+	}
+
+	setTenantActive(t, "tenant1", true)
+	setTenantActive(t, "tenant2", true)
+	setTenantActive(t, "tenant3", true)
+
+	// tenant3user with tenant3 has access to ds3 with tenant3
+	dses, _, err = tenant3Session.GetDeliveryServiceByXMLID("ds3")
 	if err != nil {
-		t.Errorf("error deleting tenant %s: %v", t2, err)
+		t.Errorf("tenant3user getting delivery service ds3 error expected: nil, actual: %+v", err)
+	} else if len(dses) == 0 {
+		t.Errorf("tenant3user getting delivery service ds3 with tenant3, expected: ds, actual: empty")
 	}
 
-	// Now should be able to delete t1
-	tenant1, _, err = TOSession.TenantByName(t1)
-	_, err = TOSession.DeleteTenant(strconv.Itoa(tenant1.ID))
+	// 1. ds2 has tenant2.
+	// 2. tenant3user has tenant3.
+	// 3. tenant2 is not a child of tenant3 (tenant3 is a child of tenant2)
+	// 4. Therefore, tenant3user should not have access to ds2
+	dses, _, _ = tenant3Session.GetDeliveryServiceByXMLID("ds2")
+	for _, ds := range dses {
+		t.Errorf("tenant3user got delivery service %+v with tenant2, expected: no ds", ds.XMLID)
+	}
+
+	// 1. ds1 has tenant1.
+	// 2. tenant4user has tenant4.
+	// 3. tenant1 is not a child of tenant4 (tenant4 is unrelated to tenant1)
+	// 4. Therefore, tenant4user should not have access to ds1
+	dses, _, _ = tenant4Session.GetDeliveryServiceByXMLID("ds1")
+	for _, ds := range dses {
+		t.Errorf("tenant4user got delivery service %+v with tenant1, expected: no ds", ds.XMLID)
+	}
+
+	setTenantActive(t, "tenant3", false)
+	dses, _, _ = tenant3Session.GetDeliveryServiceByXMLID("ds3")
+	for _, ds := range dses {
+		t.Errorf("tenant3user was inactive, but got delivery service %+v with tenant3, expected: no ds", ds.XMLID)
+	}
+
+	for _, tn := range originalTenants {
+		if tn.Name == "root" {
+			continue
+		}
+		if _, err := TOSession.UpdateTenant(strconv.Itoa(tn.ID), &tn); err != nil {
+			t.Fatalf("restoring original tenants: " + err.Error())
+		}
+	}
+}
+
+func setTenantActive(t *testing.T, name string, active bool) {
+	tn, _, err := TOSession.TenantByName(name)
+	if err != nil {
+		t.Fatalf("cannot GET Tenant by name: %s - %v\n", name, err)
+	}
+	tn.Active = active
+	_, err = TOSession.UpdateTenant(strconv.Itoa(tn.ID), tn)
 	if err != nil {
-		t.Errorf("error deleting tenant %s: %v", t1, err)
+		t.Fatalf("cannot UPDATE Tenant by id: %v\n", err)
 	}
 }
diff --git a/traffic_ops/testing/api/v14/types_test.go b/traffic_ops/testing/api/v14/types_test.go
index 9119e15..e242990 100644
--- a/traffic_ops/testing/api/v14/types_test.go
+++ b/traffic_ops/testing/api/v14/types_test.go
@@ -23,7 +23,7 @@ import (
 )
 
 func TestTypes(t *testing.T) {
-	WithObjs(t, []TCObj{Types}, func() {
+	WithObjs(t, []TCObj{Parameters, Types}, func() {
 		UpdateTestTypes(t)
 		GetTestTypes(t)
 	})
diff --git a/traffic_ops/testing/api/v14/user_test.go b/traffic_ops/testing/api/v14/user_test.go
index e66364a..3692a03 100644
--- a/traffic_ops/testing/api/v14/user_test.go
+++ b/traffic_ops/testing/api/v14/user_test.go
@@ -23,7 +23,7 @@ import (
 )
 
 func TestUsers(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Types, Tenants, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, DeliveryServices, Users}, func() {
+	WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, DeliveryServices, Users}, func() {
 		UpdateTestUsers(t)
 		GetTestUsers(t)
 		GetTestUserCurrent(t)
@@ -36,11 +36,10 @@ func CreateTestUsers(t *testing.T) {
 	for _, user := range testData.Users {
 
 		resp, _, err := TOSession.CreateUser(&user)
-		log.Debugln("Response: ", resp.Alerts)
-
 		if err != nil {
-			t.Errorf("could not CREATE user: %v\n", err)
+			t.Fatalf("could not CREATE user: %v\n", err)
 		}
+		log.Debugln("Response: ", resp.Alerts)
 	}
 }
 
diff --git a/traffic_ops/testing/api/v14/userdeliveryservices_test.go b/traffic_ops/testing/api/v14/userdeliveryservices_test.go
index be56183..86e1551 100644
--- a/traffic_ops/testing/api/v14/userdeliveryservices_test.go
+++ b/traffic_ops/testing/api/v14/userdeliveryservices_test.go
@@ -21,7 +21,7 @@ import (
 )
 
 func TestUserDeliveryServices(t *testing.T) {
-	WithObjs(t, []TCObj{CDNs, Types, Tenants, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, DeliveryServices, UsersDeliveryServices}, func() {
+	WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, DeliveryServices, UsersDeliveryServices}, func() {
 		GetTestUsersDeliveryServices(t)
 	})
 }
diff --git a/traffic_ops/traffic_ops_golang/tenant/tenancy.go b/traffic_ops/traffic_ops_golang/tenant/tenancy.go
index e57a4df..4aee850 100644
--- a/traffic_ops/traffic_ops_golang/tenant/tenancy.go
+++ b/traffic_ops/traffic_ops_golang/tenant/tenancy.go
@@ -136,11 +136,28 @@ func GetUserTenantListTx(user auth.CurrentUser, tx *sql.Tx) ([]tc.TenantNullable
 	return tenants, nil
 }
 
+// GetUserTenantIDListTx returns a list of tenant IDs accessible to the given tenant.
+// Note: If the given tenant or any of its parents are inactive, no IDs will be returned. If child tenants are needed even if the current tenant is inactive, use GetUserTenantListTx instead.
 func GetUserTenantIDListTx(tx *sql.Tx, userTenantID int) ([]int, error) {
 	query := `
-WITH RECURSIVE q AS (SELECT id, name, active, parent_id FROM tenant WHERE id = $1
-UNION SELECT t.id, t.name, t.active, t.parent_id  FROM tenant t JOIN q ON q.id = t.parent_id)
-SELECT id FROM q;
+WITH RECURSIVE
+user_tenant_id as (select $1::bigint as v),
+user_tenant_parents AS (
+  SELECT active, parent_id FROM tenant WHERE id = (select v from user_tenant_id)
+  UNION
+  SELECT t.active, t.parent_id FROM TENANT t JOIN user_tenant_parents ON user_tenant_parents.parent_id = t.id
+),
+user_tenant_active AS (
+  SELECT bool_and(active) as v FROM user_tenant_parents
+),
+user_tenant_children AS (
+  SELECT id, name, active, parent_id
+  FROM tenant WHERE id = (SELECT v FROM user_tenant_id) AND (SELECT v FROM user_tenant_active)
+  UNION
+  SELECT t.id, t.name, t.active, t.parent_id
+  FROM tenant t JOIN user_tenant_children ON user_tenant_children.id = t.parent_id
+)
+SELECT id FROM user_tenant_children;
 `
 	rows, err := tx.Query(query, userTenantID)
 	if err != nil {
@@ -171,13 +188,46 @@ func IsTenancyEnabledTx(tx *sql.Tx) (bool, error) {
 }
 
 // IsResourceAuthorizedToUserTx returns a boolean value describing if the user has access to the provided resource tenant id and an error
-// if use_tenancy is set to false (0 in the db) this method will return true allowing access.
+// If use_tenancy is set to false (0 in the db) this method will return true allowing access.
+// If the user tenant is inactive (or any of its parent tenants are inactive), false will be returned.
 func IsResourceAuthorizedToUserTx(resourceTenantID int, user *auth.CurrentUser, tx *sql.Tx) (bool, error) {
-	// $1 is the user tenant ID and $2 is the resource tenant ID
-	query := `WITH RECURSIVE q AS (SELECT id, active FROM tenant WHERE id = $1
-	UNION SELECT t.id, t.active FROM TENANT t JOIN q ON q.id = t.parent_id),
-	tenancy AS (SELECT COALESCE(value::boolean,FALSE) AS value FROM parameter WHERE name = 'use_tenancy' AND config_file = 'global' UNION ALL SELECT FALSE FETCH FIRST 1 ROW ONLY)
-	SELECT id, active, tenancy.value AS use_tenancy FROM tenancy, q WHERE id = $2 UNION ALL SELECT -1, false, tenancy.value AS use_tenancy FROM tenancy FETCH FIRST 1 ROW ONLY;`
+	query := `
+WITH RECURSIVE
+user_tenant_id as (select $1::bigint as v),
+resource_tenant_id as (select $2::bigint as v),
+user_tenant_parents AS (
+  SELECT active, parent_id FROM tenant WHERE id = (select v from user_tenant_id)
+  UNION
+  SELECT t.active, t.parent_id FROM TENANT t JOIN user_tenant_parents ON user_tenant_parents.parent_id = t.id
+),
+q AS (
+  SELECT id, active FROM tenant WHERE id = (select v from user_tenant_id)
+  UNION
+  SELECT t.id, t.active FROM TENANT t JOIN q ON q.id = t.parent_id
+),
+tenancy AS (
+  SELECT
+    COALESCE(value::boolean,FALSE) AS value
+  FROM
+    parameter
+  WHERE
+    name = 'use_tenancy'
+    AND config_file = 'global'
+  UNION ALL SELECT FALSE
+  FETCH FIRST 1 ROW ONLY
+)
+SELECT
+  id,
+  (select bool_and(active) from user_tenant_parents) as active,
+  tenancy.value AS use_tenancy
+FROM
+  tenancy,
+  q
+WHERE
+  id = (select v from resource_tenant_id)
+UNION ALL SELECT -1, false, tenancy.value AS use_tenancy FROM tenancy
+FETCH FIRST 1 ROW ONLY;
+`
 
 	var tenantID int
 	var active bool