You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by de...@apache.org on 2018/07/05 20:40:10 UTC

[trafficcontrol] branch master updated (67d4df0 -> 2cf3457)

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

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


    from 67d4df0  stop profiling loop when profiling is switched off
     new 4cbd06b  Add TO Go users/id/deliveryservices
     new 2cf3457  Add TO Go deliveryservice/users tests

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 lib/go-tc/deliveryservices.go                      |  30 +++
 lib/go-tc/users.go                                 |  45 ++++
 traffic_ops/client/dsuser.go                       |  60 +++++
 traffic_ops/client/v13/dsuser.go                   |  60 +++++
 traffic_ops/client/{ => v13}/user.go               |   4 +-
 .../testing/api/v13/userdeliveryservices_test.go   | 224 ++++++++++++++++++
 traffic_ops/traffic_ops_golang/routes.go           |   5 +-
 .../traffic_ops_golang/user/deliveryservices.go    | 255 +++++++++++++++++++++
 8 files changed, 680 insertions(+), 3 deletions(-)
 create mode 100644 traffic_ops/client/dsuser.go
 create mode 100644 traffic_ops/client/v13/dsuser.go
 copy traffic_ops/client/{ => v13}/user.go (98%)
 create mode 100644 traffic_ops/testing/api/v13/userdeliveryservices_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/user/deliveryservices.go


[trafficcontrol] 02/02: Add TO Go deliveryservice/users tests

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 2cf3457989a525493a03429a23912d7d01137d8c
Author: Robert Butts <ro...@apache.org>
AuthorDate: Thu Jun 28 13:43:22 2018 -0600

    Add TO Go deliveryservice/users tests
---
 lib/go-tc/users.go                                 |  45 +++++
 traffic_ops/client/dsuser.go                       |  60 ++++++
 traffic_ops/client/v13/dsuser.go                   |  60 ++++++
 traffic_ops/client/v13/user.go                     |  46 +++++
 .../testing/api/v13/userdeliveryservices_test.go   | 224 +++++++++++++++++++++
 .../traffic_ops_golang/user/deliveryservices.go    |   9 +-
 6 files changed, 441 insertions(+), 3 deletions(-)

diff --git a/lib/go-tc/users.go b/lib/go-tc/users.go
index 954d399..ea56df6 100644
--- a/lib/go-tc/users.go
+++ b/lib/go-tc/users.go
@@ -19,6 +19,10 @@ package tc
  * under the License.
  */
 
+import (
+	"time"
+)
+
 // UsersResponse ...
 type UsersResponse struct {
 	Response []User `json:"response"`
@@ -45,3 +49,44 @@ type UserCredentials struct {
 	Username string `json:"u"`
 	Password string `json:"p"`
 }
+
+// TODO reconcile APIUser and User
+
+type APIUser struct {
+	AddressLine1     *string    `json:"addressLine1", db:"address_line1"`
+	AddressLine2     *string    `json:"addressLine2" db:"address_line2"`
+	City             *string    `json:"city" db:"city"`
+	Company          *string    `json:"company,omitempty" db:"company"`
+	Country          *string    `json:"country" db:"country"`
+	Email            *string    `json:"email" db:"email"`
+	FullName         *string    `json:"fullName" db:"full_name"`
+	GID              *int       `json:"gid" db:"gid"`
+	ID               *int       `json:"id" db:"id"`
+	LastUpdated      *time.Time `json:"lastUpdated" db:"last_updated"`
+	NewUser          *bool      `json:"newUser" db:"new_user"`
+	PhoneNumber      *string    `json:"phoneNumber" db:"phone_number"`
+	PostalCode       *string    `json:"postalCode" db:"postal_code"`
+	PublicSSHKey     *string    `json:"publicSshKey" db:"public_ssh_key"`
+	RegistrationSent *time.Time `json:"registrationSent" db:"registration_sent"`
+	Role             *int       `json:"role" db:"role"`
+	RoleName         *string    `json:"rolename"`
+	StateOrProvince  *string    `json:"stateOrProvince" db:"state_or_province"`
+	Tenant           *string    `json:"tenant"`
+	TenantID         *int       `json:"tenantId" db:"tenant_id"`
+	UID              *int       `json:"uid" db:"uid"`
+	UserName         *string    `json:"username" db:"username"`
+}
+
+type APIUserPost struct {
+	APIUser
+	ConfirmLocalPassword *string `json:"confirmLocalPassword" db:"confirm_local_passwd"`
+	LocalPassword        *string `json:"localPassword" db:"local_passwd"`
+}
+
+type APIUsersResponse struct {
+	Response []APIUser `json:"response"`
+}
+
+type UserDeliveryServiceDeleteResponse struct {
+	Alerts []Alert `json:"alerts"`
+}
diff --git a/traffic_ops/client/dsuser.go b/traffic_ops/client/dsuser.go
new file mode 100644
index 0000000..1a5a184
--- /dev/null
+++ b/traffic_ops/client/dsuser.go
@@ -0,0 +1,60 @@
+package client
+
+/*
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+import (
+	"encoding/json"
+	"strconv"
+
+	tc "github.com/apache/trafficcontrol/lib/go-tc"
+)
+
+// GetUserDeliveryServices gets the delivery services associated with the given user.
+func (to *Session) GetUserDeliveryServices(userID int) (*tc.UserDeliveryServicesResponse, ReqInf, error) {
+	uri := apiBase + `/users/` + strconv.Itoa(userID) + `/deliveryservices`
+	resp := tc.UserDeliveryServicesResponse{}
+	reqInf, err := get(to, uri, &resp)
+	if err != nil {
+		return nil, reqInf, err
+	}
+	return &resp, reqInf, nil
+}
+
+// SetUserDeliveryService associates the given delivery services with the given user.
+func (to *Session) SetDeliveryServiceUser(userID int, dses []int, replace bool) (*tc.UserDeliveryServicePostResponse, error) {
+	uri := apiBase + `/deliveryservice_user`
+	ds := tc.DeliveryServiceUserPost{UserID: &userID, DeliveryServices: &dses, Replace: &replace}
+	jsonReq, err := json.Marshal(ds)
+	if err != nil {
+		return nil, err
+	}
+	resp := tc.UserDeliveryServicePostResponse{}
+	err = post(to, uri, jsonReq, &resp)
+	if err != nil {
+		return nil, err
+	}
+	return &resp, nil
+}
+
+// DeleteDeliveryServiceUser deletes the association between the given delivery service and user
+func (to *Session) DeleteDeliveryServiceUser(userID int, dsID int) (*tc.UserDeliveryServiceDeleteResponse, error) {
+	uri := apiBase + `/deliveryservice_user/` + strconv.Itoa(dsID) + `/` + strconv.Itoa(userID)
+	resp := tc.UserDeliveryServiceDeleteResponse{}
+	if err := del(to, uri, &resp); err != nil {
+		return nil, err
+	}
+	return &resp, nil
+}
diff --git a/traffic_ops/client/v13/dsuser.go b/traffic_ops/client/v13/dsuser.go
new file mode 100644
index 0000000..01bde1b
--- /dev/null
+++ b/traffic_ops/client/v13/dsuser.go
@@ -0,0 +1,60 @@
+package v13
+
+/*
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+import (
+	"encoding/json"
+	"strconv"
+
+	tc "github.com/apache/trafficcontrol/lib/go-tc"
+)
+
+// GetUserDeliveryServices gets the delivery services associated with the given user.
+func (to *Session) GetUserDeliveryServices(userID int) (*tc.UserDeliveryServicesResponse, ReqInf, error) {
+	uri := apiBase + `/users/` + strconv.Itoa(userID) + `/deliveryservices`
+	resp := tc.UserDeliveryServicesResponse{}
+	reqInf, err := get(to, uri, &resp)
+	if err != nil {
+		return nil, reqInf, err
+	}
+	return &resp, reqInf, nil
+}
+
+// SetUserDeliveryService associates the given delivery services with the given user.
+func (to *Session) SetDeliveryServiceUser(userID int, dses []int, replace bool) (*tc.UserDeliveryServicePostResponse, error) {
+	uri := apiBase + `/deliveryservice_user`
+	ds := tc.DeliveryServiceUserPost{UserID: &userID, DeliveryServices: &dses, Replace: &replace}
+	jsonReq, err := json.Marshal(ds)
+	if err != nil {
+		return nil, err
+	}
+	resp := tc.UserDeliveryServicePostResponse{}
+	err = post(to, uri, jsonReq, &resp)
+	if err != nil {
+		return nil, err
+	}
+	return &resp, nil
+}
+
+// DeleteDeliveryServiceUser deletes the association between the given delivery service and user
+func (to *Session) DeleteDeliveryServiceUser(userID int, dsID int) (*tc.UserDeliveryServiceDeleteResponse, error) {
+	uri := apiBase + `/deliveryservice_user/` + strconv.Itoa(dsID) + `/` + strconv.Itoa(userID)
+	resp := tc.UserDeliveryServiceDeleteResponse{}
+	if err := del(to, uri, &resp); err != nil {
+		return nil, err
+	}
+	return &resp, nil
+}
diff --git a/traffic_ops/client/v13/user.go b/traffic_ops/client/v13/user.go
new file mode 100644
index 0000000..ac8ff5b
--- /dev/null
+++ b/traffic_ops/client/v13/user.go
@@ -0,0 +1,46 @@
+package v13
+
+/*
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+import (
+	"encoding/json"
+
+	tc "github.com/apache/trafficcontrol/lib/go-tc"
+)
+
+// Users gets an array of Users.
+// Deprecated: use GetUsers
+func (to *Session) Users() ([]tc.User, error) {
+	us, _, err := to.GetUsers()
+	return us, err
+}
+
+func (to *Session) GetUsers() ([]tc.User, ReqInf, error) {
+	url := "/api/1.2/users.json"
+	resp, remoteAddr, err := to.request("GET", url, nil)
+	reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
+	if err != nil {
+		return nil, reqInf, err
+	}
+	defer resp.Body.Close()
+
+	var data tc.UsersResponse
+	if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
+		return nil, reqInf, err
+	}
+
+	return data.Response, reqInf, nil
+}
diff --git a/traffic_ops/testing/api/v13/userdeliveryservices_test.go b/traffic_ops/testing/api/v13/userdeliveryservices_test.go
new file mode 100644
index 0000000..e6acf4f
--- /dev/null
+++ b/traffic_ops/testing/api/v13/userdeliveryservices_test.go
@@ -0,0 +1,224 @@
+package v13
+
+/*
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+import (
+	"github.com/apache/trafficcontrol/lib/go-log"
+	"testing"
+)
+
+func TestUsersDeliveryServices(t *testing.T) {
+	CreateTestCDNs(t)
+	CreateTestTypes(t)
+	CreateTestProfiles(t)
+	CreateTestStatuses(t)
+	CreateTestDivisions(t)
+	CreateTestRegions(t)
+	CreateTestPhysLocations(t)
+	CreateTestCacheGroups(t)
+	CreateTestServers(t)
+	CreateTestDeliveryServices(t)
+
+	CreateTestUsersDeliveryServices(t)
+	GetTestUsersDeliveryServices(t)
+	DeleteTestUsersDeliveryServices(t)
+
+	DeleteTestDeliveryServices(t)
+	DeleteTestServers(t)
+	DeleteTestCacheGroups(t)
+	DeleteTestPhysLocations(t)
+	DeleteTestRegions(t)
+	DeleteTestDivisions(t)
+	DeleteTestStatuses(t)
+	DeleteTestProfiles(t)
+	DeleteTestTypes(t)
+	DeleteTestCDNs(t)
+}
+
+const TestUsersDeliveryServicesUser = "admin" // TODO make dynamic
+
+func CreateTestUsersDeliveryServices(t *testing.T) {
+	log.Debugln("CreateTestUsersDeliveryServices")
+
+	dses, _, err := TOSession.GetDeliveryServices()
+	if err != nil {
+		t.Fatalf("cannot GET DeliveryServices: %v - %v\n", err, dses)
+	}
+	if len(dses) == 0 {
+		t.Fatalf("no delivery services, must have at least 1 ds to test users_deliveryservices\n")
+	}
+	users, _, err := TOSession.GetUsers()
+	if err != nil {
+		t.Fatalf("cannot GET users: %v\n", err)
+	}
+	if len(users) == 0 {
+		t.Fatalf("no users, must have at least 1 user to test users_deliveryservices\n")
+	}
+
+	dsIDs := []int{}
+	for _, ds := range dses {
+		dsIDs = append(dsIDs, ds.ID)
+	}
+
+	userID := 0
+	foundUser := false
+	for _, user := range users {
+		if user.Username == TestUsersDeliveryServicesUser {
+			userID = user.ID
+			foundUser = true
+			break
+		}
+	}
+	if !foundUser {
+		t.Fatalf("get users expected: %v actual: missing\n", TestUsersDeliveryServicesUser)
+	}
+
+	_, err = TOSession.SetDeliveryServiceUser(userID, dsIDs, true)
+	if err != nil {
+		t.Fatalf("failed to set delivery service users: " + err.Error())
+	}
+
+	userDSes, _, err := TOSession.GetUserDeliveryServices(userID)
+	if err != nil {
+		t.Fatalf("get user delivery services returned error: " + err.Error())
+	}
+
+	if len(userDSes.Response) != len(dsIDs) {
+		t.Fatalf("get user delivery services expected %v actual %v\n", len(dsIDs), len(userDSes.Response))
+	}
+
+	actualDSIDMap := map[int]struct{}{}
+	for _, userDS := range userDSes.Response {
+		if userDS.ID == nil {
+			t.Fatalf("get user delivery services returned a DS with a nil ID\n")
+		}
+		actualDSIDMap[*userDS.ID] = struct{}{}
+	}
+	for _, dsID := range dsIDs {
+		if _, ok := actualDSIDMap[dsID]; !ok {
+			t.Fatalf("get user delivery services expected %v actual %v\n", dsID, "missing")
+		}
+	}
+}
+
+func GetTestUsersDeliveryServices(t *testing.T) {
+	log.Debugln("GetTestUsersDeliveryServices")
+
+	dses, _, err := TOSession.GetDeliveryServices()
+	if err != nil {
+		t.Fatalf("cannot GET DeliveryServices: %v - %v\n", err, dses)
+	}
+	if len(dses) == 0 {
+		t.Fatalf("no delivery services, must have at least 1 ds to test users_deliveryservices\n")
+	}
+	users, _, err := TOSession.GetUsers()
+	if err != nil {
+		t.Fatalf("cannot GET users: %v\n", err)
+	}
+	if len(users) == 0 {
+		t.Fatalf("no users, must have at least 1 user to test users_deliveryservices\n")
+	}
+
+	dsIDs := []int64{}
+	for _, ds := range dses {
+		dsIDs = append(dsIDs, int64(ds.ID))
+	}
+
+	userID := 0
+	foundUser := false
+	for _, user := range users {
+		if user.Username == TestUsersDeliveryServicesUser {
+			userID = user.ID
+			foundUser = true
+			break
+		}
+	}
+	if !foundUser {
+		t.Fatalf("get users expected: %v actual: missing\n", TestUsersDeliveryServicesUser)
+	}
+
+	userDSes, _, err := TOSession.GetUserDeliveryServices(userID)
+	if err != nil {
+		t.Fatalf("get user delivery services returned error: " + err.Error() + "\n")
+	}
+
+	if len(userDSes.Response) != len(dsIDs) {
+		t.Fatalf("get user delivery services expected %v actual %v\n", len(dsIDs), len(userDSes.Response))
+	}
+
+	actualDSIDMap := map[int]struct{}{}
+	for _, userDS := range userDSes.Response {
+		if userDS.ID == nil {
+			t.Fatalf("get user delivery services returned a DS with a nil ID\n")
+		}
+		actualDSIDMap[*userDS.ID] = struct{}{}
+	}
+	for _, dsID := range dsIDs {
+		if _, ok := actualDSIDMap[int(dsID)]; !ok {
+			t.Fatalf("get user delivery services expected %v actual %v\n", dsID, "missing")
+		}
+	}
+}
+
+func DeleteTestUsersDeliveryServices(t *testing.T) {
+	log.Debugln("DeleteTestUsersDeliveryServices")
+
+	users, _, err := TOSession.GetUsers()
+	if err != nil {
+		t.Fatalf("cannot GET users: %v\n", err)
+	}
+	if len(users) == 0 {
+		t.Fatalf("no users, must have at least 1 user to test users_deliveryservices\n")
+	}
+	userID := 0
+	foundUser := false
+	for _, user := range users {
+		if user.Username == TestUsersDeliveryServicesUser {
+			userID = user.ID
+			foundUser = true
+			break
+		}
+	}
+	if !foundUser {
+		t.Fatalf("get users expected: %v actual: missing\n", TestUsersDeliveryServicesUser)
+	}
+
+	dses, _, err := TOSession.GetUserDeliveryServices(userID)
+	if err != nil {
+		t.Fatalf("get user delivery services returned error: " + err.Error())
+	}
+	if len(dses.Response) == 0 {
+		t.Fatalf("get user delivery services expected %v actual %v\n", ">0", "0")
+	}
+
+	for _, ds := range dses.Response {
+		if ds.ID == nil {
+			t.Fatalf("get user delivery services returned ds with nil ID\n")
+		}
+		_, err := TOSession.DeleteDeliveryServiceUser(userID, *ds.ID)
+		if err != nil {
+			t.Fatalf("delete user delivery service returned error: " + err.Error())
+		}
+	}
+
+	dses, _, err = TOSession.GetUserDeliveryServices(userID)
+	if err != nil {
+		t.Fatalf("get user delivery services returned error: " + err.Error())
+	}
+	if len(dses.Response) != 0 {
+		t.Fatalf("get user delivery services after deleting expected %v actual %v\n", "0", len(dses.Response))
+	}
+}
diff --git a/traffic_ops/traffic_ops_golang/user/deliveryservices.go b/traffic_ops/traffic_ops_golang/user/deliveryservices.go
index 509bd57..fbde590 100644
--- a/traffic_ops/traffic_ops_golang/user/deliveryservices.go
+++ b/traffic_ops/traffic_ops_golang/user/deliveryservices.go
@@ -94,7 +94,7 @@ func filterAuthorized(db *sqlx.DB, dses []tc.DeliveryServiceNullableV13, user *a
 		if ds.TenantID == nil {
 			continue
 		}
-		authorized, err := tenant.IsResourceAuthorizedToUser(*ds.TenantID, *user, db)
+		authorized, err := tenant.IsResourceAuthorizedToUser(*ds.TenantID, user, db)
 		if err != nil {
 			return nil, errors.New("checking delivery service tenancy authorization: " + err.Error())
 		}
@@ -112,7 +112,7 @@ func filterAvailableAuthorized(db *sqlx.DB, dses []tc.UserAvailableDS, user *aut
 		if ds.TenantID == nil {
 			continue
 		}
-		authorized, err := tenant.IsResourceAuthorizedToUser(*ds.TenantID, *user, db)
+		authorized, err := tenant.IsResourceAuthorizedToUser(*ds.TenantID, user, db)
 		if err != nil {
 			return nil, errors.New("checking delivery service tenancy authorization: " + err.Error())
 		}
@@ -163,7 +163,10 @@ ds.mid_header_rewrite,
 COALESCE(ds.miss_lat, 0.0),
 COALESCE(ds.miss_long, 0.0),
 ds.multi_site_origin,
-ds.org_server_fqdn,
+(SELECT o.protocol::text || '://' || o.fqdn || rtrim(concat(':', o.port::text), ':')
+  FROM origin o
+  WHERE o.deliveryservice = ds.id
+  AND o.is_primary) as org_server_fqdn,
 ds.origin_shield,
 ds.profile as profileID,
 profile.name as profile_name,


[trafficcontrol] 01/02: Add TO Go users/id/deliveryservices

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

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

commit 4cbd06bf04ada5dcfb5c74fb2c71bc93095e58c2
Author: Robert Butts <ro...@apache.org>
AuthorDate: Sun May 27 12:19:51 2018 -0600

    Add TO Go users/id/deliveryservices
---
 lib/go-tc/deliveryservices.go                      |  30 +++
 traffic_ops/traffic_ops_golang/routes.go           |   5 +-
 .../traffic_ops_golang/user/deliveryservices.go    | 252 +++++++++++++++++++++
 3 files changed, 286 insertions(+), 1 deletion(-)

diff --git a/lib/go-tc/deliveryservices.go b/lib/go-tc/deliveryservices.go
index 50983e8..7cd1324 100644
--- a/lib/go-tc/deliveryservices.go
+++ b/lib/go-tc/deliveryservices.go
@@ -544,6 +544,36 @@ type DeliveryServiceRouting struct {
 	RegionalDenied    int     `json:"regionalDenied"`
 }
 
+type UserAvailableDS struct {
+	ID          *int    `json:"id" db:"id"`
+	DisplayName *string `json:"displayName" db:"display_name"`
+	XMLID       *string `json:"xmlId" db:"xml_id"`
+	TenantID    *int    `json:"-"` // tenant is necessary to check authorization, but not serialized
+}
+
+type DeliveryServiceUserPost struct {
+	UserID           *int   `json:"userId"`
+	DeliveryServices *[]int `json:"deliveryServices"`
+	Replace          *bool  `json:"replace"`
+}
+
+type UserDeliveryServicePostResponse struct {
+	Alerts   []Alert                 `json:"alerts"`
+	Response DeliveryServiceUserPost `json:"response"`
+}
+
+type UserDeliveryServicesResponseV13 struct {
+	Response []DeliveryServiceV13 `json:"response"`
+}
+
+type UserDeliveryServicesResponseV12 struct {
+	Response []DeliveryServiceV13 `json:"response"`
+}
+
+type UserDeliveryServicesResponse struct {
+	Response []DeliveryServiceNullableV13 `json:"response"`
+}
+
 type DSServerIDs struct {
 	DeliveryServiceID *int  `json:"dsId", db:"deliveryservice"`
 	ServerIDs         []int `json:"servers"`
diff --git a/traffic_ops/traffic_ops_golang/routes.go b/traffic_ops/traffic_ops_golang/routes.go
index 33acff1..60670a3 100644
--- a/traffic_ops/traffic_ops_golang/routes.go
+++ b/traffic_ops/traffic_ops_golang/routes.go
@@ -48,7 +48,6 @@ import (
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/deliveryservicesregexes"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/division"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/hwinfo"
-	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/origin"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/parameter"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/physlocation"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/ping"
@@ -62,7 +61,9 @@ import (
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/systeminfo"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/types"
+	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/user"
 
+	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/origin"
 	"github.com/basho/riak-go-client"
 	"github.com/jmoiron/sqlx"
 )
@@ -140,6 +141,8 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) {
 		{1.1, http.MethodGet, `hwinfo-wip/?(\.json)?$`, hwinfo.HWInfoHandler(d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
 
 		//Login
+		{1.1, http.MethodGet, `users/{id}/deliveryservices/?(\.json)?$`, user.GetDSes(d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
+		{1.1, http.MethodGet, `user/{id}/deliveryservices/available/?(\.json)?$`, user.GetAvailableDSes(d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
 		{1.2, http.MethodPost, `user/login/?$`, wrapAccessLog(d.Secrets[0], auth.LoginHandler(d.DB, d.Config)), 0, NoAuth, nil}, {1.3, http.MethodPost, `user/login/?$`, auth.LoginHandler(d.DB, d.Config), 0, NoAuth, nil},
 
 		//Parameter: CRUD
diff --git a/traffic_ops/traffic_ops_golang/user/deliveryservices.go b/traffic_ops/traffic_ops_golang/user/deliveryservices.go
new file mode 100644
index 0000000..509bd57
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/user/deliveryservices.go
@@ -0,0 +1,252 @@
+package user
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+	"database/sql"
+	"errors"
+	"net/http"
+
+	"github.com/apache/trafficcontrol/lib/go-tc"
+	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
+	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
+
+	"github.com/jmoiron/sqlx"
+)
+
+func GetDSes(db *sqlx.DB) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		user, err := auth.GetCurrentUser(r.Context())
+		if err != nil {
+			api.HandleErr(w, r, http.StatusInternalServerError, nil, errors.New("getting current user: "+err.Error()))
+			return
+		}
+		_, intParams, userErr, sysErr, errCode := api.AllParams(r, []string{"id"}, []string{"id"})
+		if userErr != nil || sysErr != nil {
+			api.HandleErr(w, r, errCode, userErr, sysErr)
+			return
+		}
+		dsUserID := intParams["id"]
+		dses, err := getUserDSes(db.DB, dsUserID)
+		if err != nil {
+			api.HandleErr(w, r, http.StatusInternalServerError, nil, errors.New("getting user delivery services: "+err.Error()))
+			return
+		}
+
+		dses, err = filterAuthorized(db, dses, user)
+		if err != nil {
+			api.HandleErr(w, r, http.StatusInternalServerError, nil, errors.New("filtering user-authorized delivery services: "+err.Error()))
+			return
+		}
+		api.WriteResp(w, r, dses)
+	}
+}
+
+func GetAvailableDSes(db *sqlx.DB) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		user, err := auth.GetCurrentUser(r.Context())
+		if err != nil {
+			api.HandleErr(w, r, http.StatusInternalServerError, nil, errors.New("getting current user: "+err.Error()))
+			return
+		}
+		_, intParams, userErr, sysErr, errCode := api.AllParams(r, []string{"id"}, []string{"id"})
+		if userErr != nil || sysErr != nil {
+			api.HandleErr(w, r, errCode, userErr, sysErr)
+			return
+		}
+		dsUserID := intParams["id"]
+		dses, err := getUserAvailableDSes(db.DB, dsUserID)
+		if err != nil {
+			api.HandleErr(w, r, http.StatusInternalServerError, nil, errors.New("getting user delivery services: "+err.Error()))
+			return
+		}
+
+		dses, err = filterAvailableAuthorized(db, dses, user)
+		if err != nil {
+			api.HandleErr(w, r, http.StatusInternalServerError, nil, errors.New("filtering user-authorized delivery services: "+err.Error()))
+			return
+		}
+		api.WriteResp(w, r, dses)
+	}
+}
+
+func filterAuthorized(db *sqlx.DB, dses []tc.DeliveryServiceNullableV13, user *auth.CurrentUser) ([]tc.DeliveryServiceNullableV13, error) {
+	authorizedDSes := []tc.DeliveryServiceNullableV13{}
+	for _, ds := range dses {
+		if ds.TenantID == nil {
+			continue
+		}
+		authorized, err := tenant.IsResourceAuthorizedToUser(*ds.TenantID, *user, db)
+		if err != nil {
+			return nil, errors.New("checking delivery service tenancy authorization: " + err.Error())
+		}
+		if !authorized {
+			continue // TODO determine if this is correct - Perl appears to return an error if any DS on the user is unauthorized to the current user
+		}
+		authorizedDSes = append(authorizedDSes, ds)
+	}
+	return authorizedDSes, nil
+}
+
+func filterAvailableAuthorized(db *sqlx.DB, dses []tc.UserAvailableDS, user *auth.CurrentUser) ([]tc.UserAvailableDS, error) {
+	authorizedDSes := []tc.UserAvailableDS{}
+	for _, ds := range dses {
+		if ds.TenantID == nil {
+			continue
+		}
+		authorized, err := tenant.IsResourceAuthorizedToUser(*ds.TenantID, *user, db)
+		if err != nil {
+			return nil, errors.New("checking delivery service tenancy authorization: " + err.Error())
+		}
+		if !authorized {
+			continue // TODO determine if this is correct - Perl appears to return an error if any DS on the user is unauthorized to the current user
+		}
+		authorizedDSes = append(authorizedDSes, ds)
+	}
+	return authorizedDSes, nil
+}
+
+func getUserDSes(db *sql.DB, userID int) ([]tc.DeliveryServiceNullableV13, error) {
+	q := `
+SELECT
+ds.active,
+ds.cacheurl,
+ds.ccr_dns_ttl,
+ds.cdn_id,
+cdn.name as cdnName,
+ds.check_path,
+ds.deep_caching_type,
+ds.display_name,
+ds.dns_bypass_cname,
+ds.dns_bypass_ip,
+ds.dns_bypass_ip6,
+ds.dns_bypass_ttl,
+ds.dscp,
+ds.edge_header_rewrite,
+ds.geolimit_redirect_url,
+ds.geo_limit,
+ds.geo_limit_countries,
+ds.geo_provider,
+ds.global_max_mbps,
+ds.global_max_tps,
+ds.fq_pacing_rate,
+ds.http_bypass_fqdn,
+ds.id,
+ds.info_url,
+ds.initial_dispersion,
+ds.ipv6_routing_enabled,
+ds.last_updated,
+ds.logs_enabled,
+ds.long_desc,
+ds.long_desc_1,
+ds.long_desc_2,
+ds.max_dns_answers,
+ds.mid_header_rewrite,
+COALESCE(ds.miss_lat, 0.0),
+COALESCE(ds.miss_long, 0.0),
+ds.multi_site_origin,
+ds.org_server_fqdn,
+ds.origin_shield,
+ds.profile as profileID,
+profile.name as profile_name,
+profile.description  as profile_description,
+ds.protocol,
+ds.qstring_ignore,
+ds.range_request_handling,
+ds.regex_remap,
+ds.regional_geo_blocking,
+ds.remap_text,
+ds.routing_name,
+ds.signing_algorithm,
+ds.ssl_key_version,
+ds.tenant_id,
+tenant.name,
+ds.tr_request_headers,
+ds.tr_response_headers,
+type.name,
+ds.type as type_id,
+ds.xml_id
+FROM deliveryservice as ds
+JOIN type ON ds.type = type.id
+JOIN cdn ON ds.cdn_id = cdn.id
+JOIN deliveryservice_tmuser dsu ON ds.id = dsu.deliveryservice
+LEFT JOIN profile ON ds.profile = profile.id
+LEFT JOIN tenant ON ds.tenant_id = tenant.id
+WHERE dsu.tm_user_id = $1
+`
+	rows, err := db.Query(q, userID)
+	if err != nil {
+		return nil, errors.New("querying user delivery services: " + err.Error())
+	}
+	defer rows.Close()
+	dses := []tc.DeliveryServiceNullableV13{}
+	for rows.Next() {
+		ds := tc.DeliveryServiceNullableV13{}
+		deepCachingTypeStr := ""
+		err := rows.Scan(&ds.Active, &ds.CacheURL, &ds.CCRDNSTTL, &ds.CDNID, &ds.CDNName, &ds.CheckPath, &deepCachingTypeStr, &ds.DisplayName, &ds.DNSBypassCNAME, &ds.DNSBypassIP, &ds.DNSBypassIP6, &ds.DNSBypassTTL, &ds.DSCP, &ds.EdgeHeaderRewrite, &ds.GeoLimitRedirectURL, &ds.GeoLimit, &ds.GeoLimitCountries, &ds.GeoProvider, &ds.GlobalMaxMBPS, &ds.GlobalMaxTPS, &ds.FQPacingRate, &ds.HTTPBypassFQDN, &ds.ID, &ds.InfoURL, &ds.InitialDispersion, &ds.IPV6RoutingEnabled, &ds.LastUpdated, &ds.LogsEn [...]
+		if err != nil {
+			return nil, errors.New("scanning user delivery services : " + err.Error())
+		}
+		deepCachingType := tc.DeepCachingTypeFromString(deepCachingTypeStr)
+		ds.DeepCachingType = &deepCachingType
+		dses = append(dses, ds)
+	}
+	return dses, nil
+}
+
+func getUserAvailableDSes(db *sql.DB, userID int) ([]tc.UserAvailableDS, error) {
+	q := `
+SELECT
+ds.id,
+ds.display_name,
+ds.xml_id,
+ds.tenant_id
+FROM deliveryservice as ds
+JOIN deliveryservice_tmuser dsu ON ds.id = dsu.deliveryservice
+WHERE dsu.tm_user_id = $1
+`
+	rows, err := db.Query(q, userID)
+	if err != nil {
+		return nil, errors.New("querying user available delivery services: " + err.Error())
+	}
+	defer rows.Close()
+	dses := []tc.UserAvailableDS{}
+	for rows.Next() {
+		ds := tc.UserAvailableDS{}
+		err := rows.Scan(&ds.ID, &ds.DisplayName, &ds.XMLID, &ds.TenantID)
+		if err != nil {
+			return nil, errors.New("scanning user available delivery services : " + err.Error())
+		}
+		dses = append(dses, ds)
+	}
+	return dses, nil
+}
+
+func getUserTenantIDByID(db *sql.DB, id int) (*int, bool, error) {
+	tenantID := (*int)(nil)
+	if err := db.QueryRow(`SELECT tenant_id FROM tm_user WHERE id = $1`, id).Scan(&tenantID); err != nil {
+		if err == sql.ErrNoRows {
+			return nil, false, nil
+		}
+		return nil, false, errors.New("querying user: " + err.Error())
+	}
+	return tenantID, true, nil
+}