You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@trafficcontrol.apache.org by GitBox <gi...@apache.org> on 2018/09/14 14:40:40 UTC

[GitHub] dewrich closed pull request #2803: Add Traffic Ops Golang Steering Targets

dewrich closed pull request #2803: Add Traffic Ops Golang Steering Targets
URL: https://github.com/apache/trafficcontrol/pull/2803
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/lib/go-tc/steeringtarget.go b/lib/go-tc/steeringtarget.go
index 17feaa3ca..e04c63d81 100644
--- a/lib/go-tc/steeringtarget.go
+++ b/lib/go-tc/steeringtarget.go
@@ -47,9 +47,6 @@ type SteeringTargetNullable struct {
 
 func (st SteeringTargetNullable) Validate(tx *sql.Tx) error {
 	errs := []string{}
-	if st.TargetID == nil {
-		errs = append(errs, "missing target")
-	}
 	if st.TypeID == nil {
 		errs = append(errs, "missing typeId")
 	}
diff --git a/traffic_ops/client/steeringtarget.go b/traffic_ops/client/steeringtarget.go
new file mode 100644
index 000000000..bb5c7d55a
--- /dev/null
+++ b/traffic_ops/client/steeringtarget.go
@@ -0,0 +1,96 @@
+/*
+
+   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.
+*/
+
+package client
+
+import (
+	"encoding/json"
+	"errors"
+	"net/http"
+	"strconv"
+
+	"github.com/apache/trafficcontrol/lib/go-tc"
+)
+
+func (to *Session) CreateSteeringTarget(st tc.SteeringTargetNullable) (tc.Alerts, ReqInf, error) {
+	reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss}
+	if st.DeliveryServiceID == nil {
+		return tc.Alerts{}, reqInf, errors.New("missing delivery service id")
+	}
+	reqBody, err := json.Marshal(st)
+	if err != nil {
+		return tc.Alerts{}, reqInf, err
+	}
+
+	resp := (*http.Response)(nil)
+	if resp, reqInf.RemoteAddr, err = to.request(http.MethodPost, apiBase+`/steering/`+strconv.Itoa(int(*st.DeliveryServiceID))+`/targets`, reqBody); err != nil {
+		return tc.Alerts{}, reqInf, err
+	}
+	defer resp.Body.Close()
+	alerts := tc.Alerts{}
+	err = json.NewDecoder(resp.Body).Decode(&alerts)
+	return alerts, reqInf, nil
+}
+
+func (to *Session) UpdateSteeringTarget(st tc.SteeringTargetNullable) (tc.Alerts, ReqInf, error) {
+	reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss}
+	if st.DeliveryServiceID == nil {
+		return tc.Alerts{}, reqInf, errors.New("missing delivery service id")
+	}
+	if st.TargetID == nil {
+		return tc.Alerts{}, reqInf, errors.New("missing target id")
+	}
+
+	reqBody, err := json.Marshal(st)
+	if err != nil {
+		return tc.Alerts{}, reqInf, err
+	}
+	resp := (*http.Response)(nil)
+	resp, reqInf.RemoteAddr, err = to.request(http.MethodPut, apiBase+`/steering/`+strconv.Itoa(int(*st.DeliveryServiceID))+`/targets/`+strconv.Itoa(int(*st.TargetID)), reqBody)
+	if err != nil {
+		return tc.Alerts{}, reqInf, err
+	}
+	defer resp.Body.Close()
+	alerts := tc.Alerts{}
+	err = json.NewDecoder(resp.Body).Decode(&alerts)
+	return alerts, reqInf, nil
+}
+
+func (to *Session) GetSteeringTargets(dsID int) ([]tc.SteeringTargetNullable, ReqInf, error) {
+	resp, remoteAddr, err := to.request(http.MethodGet, apiBase+`/steering/`+strconv.Itoa(dsID)+`/targets`, nil)
+	reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
+	if err != nil {
+		return nil, reqInf, err
+	}
+	defer resp.Body.Close()
+
+	data := struct {
+		Response []tc.SteeringTargetNullable `json:"response"`
+	}{}
+	err = json.NewDecoder(resp.Body).Decode(&data)
+	return data.Response, reqInf, nil
+}
+
+func (to *Session) DeleteSteeringTarget(dsID int, targetID int) (tc.Alerts, ReqInf, error) {
+	resp, remoteAddr, err := to.request(http.MethodDelete, apiBase+`/steering/`+strconv.Itoa(dsID)+`/targets/`+strconv.Itoa(targetID), nil)
+	reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
+	if err != nil {
+		return tc.Alerts{}, reqInf, err
+	}
+	defer resp.Body.Close()
+	alerts := tc.Alerts{}
+	err = json.NewDecoder(resp.Body).Decode(&alerts)
+	return alerts, reqInf, nil
+}
diff --git a/traffic_ops/testing/api/v13/steeringtargets_test.go b/traffic_ops/testing/api/v13/steeringtargets_test.go
new file mode 100644
index 000000000..5c28f62fa
--- /dev/null
+++ b/traffic_ops/testing/api/v13/steeringtargets_test.go
@@ -0,0 +1,302 @@
+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 (
+	"testing"
+
+	"github.com/apache/trafficcontrol/lib/go-log"
+	"github.com/apache/trafficcontrol/lib/go-util"
+)
+
+func TestSteeringTargets(t *testing.T) {
+	CreateTestCDNs(t)
+	CreateTestTypes(t)
+	CreateTestProfiles(t)
+	CreateTestStatuses(t)
+	CreateTestDivisions(t)
+	CreateTestRegions(t)
+	CreateTestPhysLocations(t)
+	CreateTestCacheGroups(t)
+	CreateTestServers(t)
+	CreateTestDeliveryServices(t)
+
+	CreateTestSteeringTargets(t)
+	GetTestSteeringTargets(t)
+	UpdateTestSteeringTargets(t)
+	DeleteTestSteeringTargets(t)
+
+	DeleteTestDeliveryServices(t)
+	DeleteTestServers(t)
+	DeleteTestCacheGroups(t)
+	DeleteTestPhysLocations(t)
+	DeleteTestRegions(t)
+	DeleteTestDivisions(t)
+	DeleteTestStatuses(t)
+	DeleteTestProfiles(t)
+	DeleteTestTypes(t)
+	DeleteTestCDNs(t)
+}
+
+func CreateTestSteeringTargets(t *testing.T) {
+	log.Debugln("CreateTestSteeringTargets")
+	for _, st := range testData.SteeringTargets {
+		if st.Type == nil {
+			t.Fatalf("creating steering target: test data missing type\n")
+		}
+		if st.DeliveryService == nil {
+			t.Fatalf("creating steering target: test data missing ds\n")
+		}
+		if st.Target == nil {
+			t.Fatalf("creating steering target: test data missing target\n")
+		}
+
+		{
+			respTypes, _, err := TOSession.GetTypeByName(*st.Type)
+			if err != nil {
+				t.Fatalf("creating steering target: getting type: %v\n", err)
+			} else if len(respTypes) < 1 {
+				t.Fatalf("creating steering target: getting type: not found\n")
+			}
+			st.TypeID = util.IntPtr(respTypes[0].ID)
+		}
+		{
+			respDS, _, err := TOSession.GetDeliveryServiceByXMLID(string(*st.DeliveryService))
+			if err != nil {
+				t.Fatalf("creating steering target: getting ds: %v\n", err)
+			} else if len(respDS) < 1 {
+				t.Fatalf("creating steering target: getting ds: not found\n")
+			}
+			dsID := uint64(respDS[0].ID)
+			st.DeliveryServiceID = &dsID
+		}
+		{
+			respTarget, _, err := TOSession.GetDeliveryServiceByXMLID(string(*st.Target))
+			if err != nil {
+				t.Fatalf("creating steering target: getting target ds: %v\n", err)
+			} else if len(respTarget) < 1 {
+				t.Fatalf("creating steering target: getting target ds: not found\n")
+			}
+			targetID := uint64(respTarget[0].ID)
+			st.TargetID = &targetID
+		}
+
+		resp, _, err := TOSession.CreateSteeringTarget(st)
+		log.Debugln("Response: ", resp)
+		if err != nil {
+			t.Fatalf("creating steering target: %v\n", err)
+		}
+	}
+	log.Debugln("CreateTestSteeringTargets() PASSED")
+}
+
+func UpdateTestSteeringTargets(t *testing.T) {
+	log.Debugln("UpdateTestSteeringTargets")
+
+	if len(testData.SteeringTargets) < 1 {
+		t.Fatalf("updating steering target: no steering target test data\n")
+	}
+	st := testData.SteeringTargets[0]
+	if st.DeliveryService == nil {
+		t.Fatalf("updating steering target: test data missing ds\n")
+	}
+	if st.Target == nil {
+		t.Fatalf("updating steering target: test data missing target\n")
+	}
+
+	respDS, _, err := TOSession.GetDeliveryServiceByXMLID(string(*st.DeliveryService))
+	if err != nil {
+		t.Fatalf("updating steering target: getting ds: %v\n", err)
+	}
+	if len(respDS) < 1 {
+		t.Fatalf("updating steering target: getting ds: not found\n")
+	}
+	dsID := respDS[0].ID
+
+	sts, _, err := TOSession.GetSteeringTargets(dsID)
+	if err != nil {
+		t.Fatalf("updating steering targets: getting steering target: %v\n", err)
+	}
+	if len(sts) < 1 {
+		t.Fatalf("updating steering targets: getting steering target: got 0\n")
+	}
+	st = sts[0]
+
+	expected := uint64(12345)
+	if st.Value != nil && *st.Value == expected {
+		expected += 1
+	}
+	st.Value = &expected
+
+	_, _, err = TOSession.UpdateSteeringTarget(st)
+	if err != nil {
+		t.Fatalf("updating steering targets: updating: %+v\n", err)
+	}
+
+	sts, _, err = TOSession.GetSteeringTargets(dsID)
+	if err != nil {
+		t.Fatalf("updating steering targets: getting updated steering target: %v\n", err)
+	}
+	if len(sts) < 1 {
+		t.Fatalf("updating steering targets: getting updated steering target: got 0\n")
+	}
+	actual := sts[0]
+
+	if actual.DeliveryServiceID == nil {
+		t.Fatalf("steering target update: ds id expected %v actual %v\n", dsID, nil)
+	} else if *actual.DeliveryServiceID != uint64(dsID) {
+		t.Fatalf("steering target update: ds id expected %v actual %v\n", dsID, *actual.DeliveryServiceID)
+	}
+	if actual.TargetID == nil {
+		t.Fatalf("steering target update: ds id expected %v actual %v\n", dsID, nil)
+	} else if *actual.TargetID != *st.TargetID {
+		t.Fatalf("steering target update: ds id expected %v actual %v\n", *st.TargetID, *actual.TargetID)
+	}
+	if actual.TypeID == nil {
+		t.Fatalf("steering target update: ds id expected %v actual %v\n", *st.TypeID, nil)
+	} else if *actual.TypeID != *st.TypeID {
+		t.Fatalf("steering target update: ds id expected %v actual %v\n", *st.TypeID, *actual.TypeID)
+	}
+	if actual.DeliveryService == nil {
+		t.Fatalf("steering target update: ds expected %v actual %v\n", *st.DeliveryService, nil)
+	} else if *st.DeliveryService != *actual.DeliveryService {
+		t.Fatalf("steering target update: ds name expected %v actual %v\n", *st.DeliveryService, *actual.DeliveryService)
+	}
+	if actual.Target == nil {
+		t.Fatalf("steering target update: target expected %v actual %v\n", *st.Target, nil)
+	} else if *st.Target != *actual.Target {
+		t.Fatalf("steering target update: target expected %v actual %v\n", *st.Target, *actual.Target)
+	}
+	if actual.Type == nil {
+		t.Fatalf("steering target update: type expected %v actual %v\n", *st.Type, nil)
+	} else if *st.Type != *actual.Type {
+		t.Fatalf("steering target update: type expected %v actual %v\n", *st.Type, *actual.Type)
+	}
+	if actual.Value == nil {
+		t.Fatalf("steering target update: ds expected %v actual %v\n", *st.Value, nil)
+	} else if *st.Value != *actual.Value {
+		t.Fatalf("steering target update: value expected %v actual %v\n", *st.Value, actual.Value)
+	}
+	log.Debugln("UpdateTestSteeringTargets() PASSED")
+}
+
+func GetTestSteeringTargets(t *testing.T) {
+	log.Debugln("GetTestSteeringTargets")
+
+	if len(testData.SteeringTargets) < 1 {
+		t.Fatalf("updating steering target: no steering target test data\n")
+	}
+	st := testData.SteeringTargets[0]
+	if st.DeliveryService == nil {
+		t.Fatalf("updating steering target: test data missing ds\n")
+	}
+
+	respDS, _, err := TOSession.GetDeliveryServiceByXMLID(string(*st.DeliveryService))
+	if err != nil {
+		t.Fatalf("creating steering target: getting ds: %v\n", err)
+	} else if len(respDS) < 1 {
+		t.Fatalf("steering target get: getting ds: not found\n")
+	}
+	dsID := respDS[0].ID
+
+	sts, _, err := TOSession.GetSteeringTargets(dsID)
+	if err != nil {
+		t.Fatalf("steering target get: getting steering target: %v\n", err)
+	}
+
+	if len(sts) != len(testData.SteeringTargets) {
+		t.Fatalf("steering target get: expected %v actual %v\n", len(testData.SteeringTargets), len(sts))
+	}
+
+	expected := testData.SteeringTargets[0]
+	actual := sts[0]
+
+	if actual.DeliveryServiceID == nil {
+		t.Fatalf("steering target get: ds id expected %v actual %v\n", dsID, nil)
+	} else if *actual.DeliveryServiceID != uint64(dsID) {
+		t.Fatalf("steering target get: ds id expected %v actual %v\n", dsID, *actual.DeliveryServiceID)
+	}
+	if actual.DeliveryService == nil {
+		t.Fatalf("steering target get: ds expected %v actual %v\n", expected.DeliveryService, nil)
+	} else if *expected.DeliveryService != *actual.DeliveryService {
+		t.Fatalf("steering target get: ds name expected %v actual %v\n", expected.DeliveryService, actual.DeliveryService)
+	}
+	if actual.Target == nil {
+		t.Fatalf("steering target get: target expected %v actual %v\n", expected.Target, nil)
+	} else if *expected.Target != *actual.Target {
+		t.Fatalf("steering target get: target expected %v actual %v\n", expected.Target, actual.Target)
+	}
+	if actual.Type == nil {
+		t.Fatalf("steering target get: type expected %v actual %v\n", expected.Type, nil)
+	} else if *expected.Type != *actual.Type {
+		t.Fatalf("steering target get: type expected %v actual %v\n", expected.Type, actual.Type)
+	}
+	if actual.Value == nil {
+		t.Fatalf("steering target get: ds expected %v actual %v\n", expected.Value, nil)
+	} else if *expected.Value != *actual.Value {
+		t.Fatalf("steering target get: value expected %v actual %v\n", *expected.Value, *actual.Value)
+	}
+	log.Debugln("GetTestSteeringTargets() PASSED")
+}
+
+func DeleteTestSteeringTargets(t *testing.T) {
+	log.Debugln("DeleteTestSteeringTargets")
+	dsIDs := []uint64{}
+	for _, st := range testData.SteeringTargets {
+		if st.DeliveryService == nil {
+			t.Fatalf("deleting steering target: test data missing ds\n")
+		}
+		if st.Target == nil {
+			t.Fatalf("deleting steering target: test data missing target\n")
+		}
+
+		respDS, _, err := TOSession.GetDeliveryServiceByXMLID(string(*st.DeliveryService))
+		if err != nil {
+			t.Fatalf("deleting steering target: getting ds: %v\n", err)
+		} else if len(respDS) < 1 {
+			t.Fatalf("deleting steering target: getting ds: not found\n")
+		}
+		dsID := uint64(respDS[0].ID)
+		st.DeliveryServiceID = &dsID
+
+		dsIDs = append(dsIDs, dsID)
+
+		respTarget, _, err := TOSession.GetDeliveryServiceByXMLID(string(*st.Target))
+		if err != nil {
+			t.Fatalf("deleting steering target: getting target ds: %v\n", err)
+		} else if len(respTarget) < 1 {
+			t.Fatalf("deleting steering target: getting target ds: not found\n")
+		}
+		targetID := uint64(respTarget[0].ID)
+		st.TargetID = &targetID
+
+		_, _, err = TOSession.DeleteSteeringTarget(int(*st.DeliveryServiceID), int(*st.TargetID))
+		if err != nil {
+			t.Fatalf("deleting steering target: deleting: %+v\n", err)
+		}
+	}
+
+	for _, dsID := range dsIDs {
+		sts, _, err := TOSession.GetSteeringTargets(int(dsID))
+		if err != nil {
+			t.Fatalf("deleting steering targets: getting steering target: %v\n", err)
+		}
+		if len(sts) != 0 {
+			t.Fatalf("deleting steering targets: after delete, getting steering target: expected 0 actual %+v\n", len(sts))
+		}
+	}
+	log.Debugln("DeleteTestSteeringTargets() PASSED")
+}
diff --git a/traffic_ops/testing/api/v13/tc-fixtures.json b/traffic_ops/testing/api/v13/tc-fixtures.json
index dbb53474e..1a6a58b88 100644
--- a/traffic_ops/testing/api/v13/tc-fixtures.json
+++ b/traffic_ops/testing/api/v13/tc-fixtures.json
@@ -1436,5 +1436,13 @@
             "uid": 0,
             "username": "federationuser"
         }
+    ],
+    "steeringTargets": [
+        {
+            "deliveryService": "ds1",
+            "target": "ds2",
+            "value": 42,
+            "type": "STEERING_WEIGHT"
+        }
     ]
 }
diff --git a/traffic_ops/testing/api/v13/traffic_control.go b/traffic_ops/testing/api/v13/traffic_control.go
index ba96dcb07..625855ca3 100644
--- a/traffic_ops/testing/api/v13/traffic_control.go
+++ b/traffic_ops/testing/api/v13/traffic_control.go
@@ -42,4 +42,5 @@ type TrafficControl struct {
 	StaticDNSEntries               []tc.StaticDNSEntry                `json:"staticdnsentries"`
 	Tenants                        []tc.Tenant                        `json:"tenants"`
 	Types                          []tc.Type                          `json:"types"`
+	SteeringTargets                []tc.SteeringTargetNullable        `json:"steeringTargets"`
 }
diff --git a/traffic_ops/traffic_ops_golang/routes.go b/traffic_ops/traffic_ops_golang/routes.go
index 498f248ac..98e12f43b 100644
--- a/traffic_ops/traffic_ops_golang/routes.go
+++ b/traffic_ops/traffic_ops_golang/routes.go
@@ -63,6 +63,7 @@ import (
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/server"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/staticdnsentry"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/status"
+	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/steeringtargets"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/systeminfo"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/types"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/user"
@@ -385,6 +386,12 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) {
 		{1.1, http.MethodGet, `deliveryservices/xmlId/{name}/urlkeys/?(\.json)?$`, deliveryservice.GetURLKeysByName, auth.PrivLevelReadOnly, Authenticated, nil},
 		{1.1, http.MethodGet, `deliveryservices/{id}/urlkeys/?(\.json)?$`, deliveryservice.GetURLKeysByID, auth.PrivLevelReadOnly, Authenticated, nil},
 		{1.1, http.MethodGet, `riak/bucket/{bucket}/key/{key}/values/?(\.json)?$`, apiriak.GetBucketKey, auth.PrivLevelAdmin, Authenticated, nil},
+
+		{1.1, http.MethodGet, `steering/{deliveryservice}/targets/?(\.json)?$`, api.ReadHandler(steeringtargets.TypeFactory), auth.PrivLevelReadOnly, Authenticated, nil},
+		{1.1, http.MethodGet, `steering/{deliveryservice}/targets/{target}$`, api.ReadHandler(steeringtargets.TypeFactory), auth.PrivLevelReadOnly, Authenticated, nil},
+		{1.1, http.MethodPost, `steering/{deliveryservice}/targets/?(\.json)?$`, api.CreateHandler(steeringtargets.TypeFactory), auth.PrivLevelOperations, Authenticated, nil},
+		{1.1, http.MethodPut, `steering/{deliveryservice}/targets/{target}/?(\.json)?$`, api.UpdateHandler(steeringtargets.TypeFactory), auth.PrivLevelOperations, Authenticated, nil},
+		{1.1, http.MethodDelete, `steering/{deliveryservice}/targets/{target}/?(\.json)?$`, api.DeleteHandler(steeringtargets.TypeFactory), auth.PrivLevelOperations, Authenticated, nil},
 	}
 
 	// rawRoutes are served at the root path. These should be almost exclusively old Perl pre-API routes, which have yet to be converted in all clients. New routes should be in the versioned API path.
diff --git a/traffic_ops/traffic_ops_golang/steeringtargets/steeringtargets.go b/traffic_ops/traffic_ops_golang/steeringtargets/steeringtargets.go
new file mode 100644
index 000000000..9642983e8
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/steeringtargets/steeringtargets.go
@@ -0,0 +1,337 @@
+package steeringtargets
+
+/*
+ * 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 (
+	"errors"
+	"fmt"
+	"net/http"
+	"strconv"
+
+	"github.com/apache/trafficcontrol/lib/go-tc"
+	"github.com/apache/trafficcontrol/lib/go-util"
+	"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/dbhelpers"
+	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
+
+	"github.com/jmoiron/sqlx"
+)
+
+type TOSteeringTargetV11 struct {
+	ReqInfo *api.APIInfo `json:"-"`
+	tc.SteeringTargetNullable
+	DSTenantID  *int          `json:"-" db:"tenant"`
+	LastUpdated *tc.TimeNoMod `json:"-" db:"last_updated"`
+}
+
+func TypeFactory(inf *api.APIInfo) api.CRUDer {
+	return &TOSteeringTargetV11{ReqInfo: inf}
+}
+
+func (st TOSteeringTargetV11) GetKeyFieldsInfo() []api.KeyFieldInfo {
+	return []api.KeyFieldInfo{
+		{"deliveryservice", api.GetIntKey},
+		{"target", api.GetIntKey},
+	}
+}
+
+func (st TOSteeringTargetV11) GetKeys() (map[string]interface{}, bool) {
+	keys := map[string]interface{}{}
+	valid := true
+	if st.DeliveryServiceID == nil {
+		keys["deliveryservice"] = 0
+		valid = false
+	} else {
+		keys["deliveryservice"] = int(*st.DeliveryServiceID)
+	}
+	if st.TargetID == nil {
+		keys["target"] = 0
+		valid = false
+	} else {
+		keys["target"] = int(*st.TargetID)
+	}
+	return keys, valid
+}
+
+func (st *TOSteeringTargetV11) SetKeys(keys map[string]interface{}) {
+	dsI, _ := keys["deliveryservice"].(int) //this utilizes the non panicking type assertion, if the thrown away ok variable is false i will be the zero of the type, 0 here.
+	ds := uint64(dsI)
+	st.DeliveryServiceID = &ds
+	targetI, _ := keys["target"].(int) //this utilizes the non panicking type assertion, if the thrown away ok variable is false i will be the zero of the type, 0 here.
+	target := uint64(targetI)
+	st.TargetID = &target
+}
+
+func (st TOSteeringTargetV11) GetAuditName() string {
+	if st.DeliveryService != nil && st.Target != nil {
+		return string(*st.DeliveryService) + `-` + string(*st.Target)
+	}
+	if st.DeliveryServiceID != nil && st.TargetID != nil {
+		return strconv.FormatUint(*st.DeliveryServiceID, 10) + `-` + strconv.FormatUint(*st.TargetID, 10)
+	}
+	return "unknown"
+}
+
+func (st TOSteeringTargetV11) GetType() string {
+	return "steeringtarget"
+}
+
+func (st TOSteeringTargetV11) Validate() error {
+	return st.SteeringTargetNullable.Validate(st.ReqInfo.Tx.Tx)
+}
+
+func (st *TOSteeringTargetV11) Read() ([]interface{}, error, error, int) {
+	steeringTargets, userErr, sysErr, errCode := read(st.ReqInfo.Tx, st.ReqInfo.Params, st.ReqInfo.User)
+	if userErr != nil || sysErr != nil {
+		return nil, userErr, sysErr, errCode
+	}
+	iSteeringTargets := make([]interface{}, len(steeringTargets), len(steeringTargets))
+	for i, steeringTarget := range steeringTargets {
+		iSteeringTargets[i] = steeringTarget
+	}
+	return iSteeringTargets, nil, nil, http.StatusOK
+}
+
+func read(tx *sqlx.Tx, parameters map[string]string, user *auth.CurrentUser) ([]tc.SteeringTargetNullable, error, error, int) {
+	queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+		"deliveryservice": dbhelpers.WhereColumnInfo{"st.deliveryservice", api.IsInt},
+		"target":          dbhelpers.WhereColumnInfo{"st.target", api.IsInt},
+	}
+	where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(parameters, queryParamsToQueryCols)
+	if len(errs) > 0 {
+		return nil, nil, util.JoinErrs(errs), http.StatusBadRequest
+	}
+	query := selectQuery() + where + orderBy
+
+	userTenants, err := tenant.GetUserTenantListTx(*user, tx.Tx)
+	if err != nil {
+		return nil, nil, errors.New("getting user tenant list: " + err.Error()), http.StatusInternalServerError
+	}
+
+	rows, err := tx.NamedQuery(query, queryValues)
+	if err != nil {
+		return nil, nil, errors.New("steering targets querying: " + err.Error()), http.StatusInternalServerError
+	}
+	defer rows.Close()
+
+	steeringTargets := []TOSteeringTargetV11{}
+	for rows.Next() {
+		s := TOSteeringTargetV11{}
+		if err = rows.StructScan(&s); err != nil {
+			return nil, nil, errors.New("steering targets parsing: " + err.Error()), http.StatusInternalServerError
+		}
+		steeringTargets = append(steeringTargets, s)
+	}
+
+	tenantMap := map[int]struct{}{}
+	for _, ten := range userTenants {
+		if ten.ID == nil {
+			return nil, nil, errors.New("user tenant with nil ID"), http.StatusInternalServerError
+		}
+		tenantMap[*ten.ID] = struct{}{}
+	}
+
+	filteredTargets := []tc.SteeringTargetNullable{}
+	for _, tr := range steeringTargets {
+		if tr.DSTenantID == nil {
+			filteredTargets = append(filteredTargets, tr.SteeringTargetNullable)
+			continue
+		}
+		if _, ok := tenantMap[int(*tr.DSTenantID)]; ok {
+			filteredTargets = append(filteredTargets, tr.SteeringTargetNullable)
+			continue
+		}
+	}
+	return filteredTargets, nil, nil, http.StatusOK
+}
+
+func (st *TOSteeringTargetV11) Create() (error, error, int) {
+	dsIDInt, err := strconv.Atoi(st.ReqInfo.Params["deliveryservice"])
+	if err != nil {
+		return errors.New("delivery service ID must be an integer"), nil, http.StatusBadRequest
+	}
+	dsID := uint64(dsIDInt)
+	st.DeliveryServiceID = &dsID
+
+	// target can't be in the Validate func, because it's in the parameters of PUT, not the body (but it is in the body in the POST here).
+	if st.TargetID == nil {
+		return errors.New("missing target"), nil, http.StatusBadRequest
+	}
+
+	if userErr, sysErr, errCode := tenant.CheckID(st.ReqInfo.Tx.Tx, st.ReqInfo.User, int(*st.DeliveryServiceID)); userErr != nil || sysErr != nil {
+		return userErr, sysErr, errCode
+	}
+
+	rows, err := st.ReqInfo.Tx.NamedQuery(insertQuery(), st)
+	if err != nil {
+		return api.ParseDBErr(err, st.GetType())
+	}
+	defer rows.Close()
+
+	rowsAffected := 0
+	for rows.Next() {
+		rowsAffected++
+		if err = rows.StructScan(&st); err != nil {
+			return nil, errors.New("steering target create scanning: " + err.Error()), http.StatusInternalServerError
+		}
+	}
+	if rowsAffected == 0 {
+		return nil, errors.New("no " + st.GetType() + " was inserted, no id was returned"), http.StatusInternalServerError
+	} else if rowsAffected > 1 {
+		return nil, errors.New("too many ids returned from steering target insert"), http.StatusInternalServerError
+	}
+	return nil, nil, http.StatusOK
+}
+
+func (st *TOSteeringTargetV11) Update() (error, error, int) {
+	dsIDInt, err := strconv.Atoi(st.ReqInfo.Params["deliveryservice"])
+	if err != nil {
+		return errors.New("delivery service ID must be an integer"), nil, http.StatusBadRequest
+	}
+	dsID := uint64(dsIDInt)
+	// TODO determine if the CRUDer automatically does this
+	st.DeliveryServiceID = &dsID
+
+	targetIDInt, err := strconv.Atoi(st.ReqInfo.Params["target"])
+	if err != nil {
+		return errors.New("target ID must be an integer"), nil, http.StatusBadRequest
+	}
+	targetID := uint64(targetIDInt)
+	st.TargetID = &targetID
+
+	if userErr, sysErr, errCode := tenant.CheckID(st.ReqInfo.Tx.Tx, st.ReqInfo.User, int(*st.DeliveryServiceID)); userErr != nil || sysErr != nil {
+		return userErr, sysErr, errCode
+	}
+
+	rows, err := st.ReqInfo.Tx.NamedQuery(updateQuery(), st)
+	if err != nil {
+		return api.ParseDBErr(err, st.GetType())
+	}
+	defer rows.Close()
+
+	lastUpdated := tc.TimeNoMod{}
+	rowsAffected := 0
+	for rows.Next() {
+		rowsAffected++
+		if err = rows.StructScan(&st); err != nil {
+			return nil, errors.New("steering target update scanning: " + err.Error()), http.StatusInternalServerError
+		}
+	}
+	st.LastUpdated = &lastUpdated
+	if rowsAffected != 1 {
+		if rowsAffected < 1 {
+			return nil, nil, http.StatusNotFound
+		}
+		return nil, errors.New("too many ids returned from steering target update"), http.StatusInternalServerError
+	}
+	return nil, nil, http.StatusOK
+}
+
+func (st *TOSteeringTargetV11) Delete() (error, error, int) {
+	if userErr, sysErr, errCode := tenant.CheckID(st.ReqInfo.Tx.Tx, st.ReqInfo.User, int(*st.DeliveryServiceID)); userErr != nil || sysErr != nil {
+		return userErr, sysErr, errCode
+	}
+
+	result, err := st.ReqInfo.Tx.NamedExec(deleteQuery(), st)
+	if err != nil {
+		return nil, errors.New("steering target delete exec: " + err.Error()), http.StatusInternalServerError
+	}
+	rowsAffected, err := result.RowsAffected()
+	if err != nil {
+		return nil, errors.New("steering target delete exec getting rows affected: " + err.Error()), http.StatusInternalServerError
+	}
+
+	if rowsAffected < 1 {
+		return nil, nil, http.StatusNotFound
+	} else if rowsAffected != 1 {
+		return nil, fmt.Errorf("this create affected too many rows: %d", rowsAffected), http.StatusInternalServerError
+	}
+	return nil, nil, http.StatusOK
+}
+
+func selectQuery() string {
+	return `
+SELECT
+  st.deliveryservice,
+  ds.xml_id as deliveryservice_name,
+  CAST(ds.tenant_id AS INTEGER) as tenant,
+  st.target,
+  dst.xml_id AS target_name,
+  st.type as type_id,
+  tp.name as type_name,
+  st.value
+FROM steering_target AS st
+JOIN deliveryservice AS ds ON st.deliveryservice = ds.id
+JOIN deliveryservice AS dst ON st.target = dst.id
+JOIN type AS tp ON tp.id = st.type
+`
+}
+
+func insertQuery() string {
+	return `
+WITH st AS (
+  INSERT INTO steering_target (deliveryservice, target, value, type)
+  VALUES (:deliveryservice, :target, :value, :type_id)
+  RETURNING deliveryservice, target, value, type
+)
+SELECT
+  st.deliveryservice,
+  ds.xml_id as deliveryservice_name,
+  st.target,
+  dst.xml_id AS target_name,
+  st.type as type_id,
+  tp.name as type_name,
+  st.value
+FROM st
+JOIN deliveryservice AS ds ON st.deliveryservice = ds.id
+JOIN deliveryservice AS dst ON st.target = dst.id
+JOIN type AS tp ON tp.id = st.type
+`
+}
+
+func updateQuery() string {
+	return `
+WITH st as (
+  UPDATE steering_target SET
+    value = :value,
+    type = :type_id
+  WHERE deliveryservice = :deliveryservice AND target = :target
+  RETURNING deliveryservice, target, value, type, last_updated
+)
+SELECT
+  st.deliveryservice,
+  ds.xml_id as deliveryservice_name,
+  st.target,
+  dst.xml_id AS target_name,
+  st.type as type_id,
+  tp.name as type_name,
+  st.value,
+  st.last_updated
+FROM st
+JOIN deliveryservice AS ds ON st.deliveryservice = ds.id
+JOIN deliveryservice AS dst ON st.target = dst.id
+JOIN type AS tp ON tp.id = st.type
+`
+}
+
+func deleteQuery() string {
+	return `DELETE FROM steering_target WHERE deliveryservice = :deliveryservice AND target = :target`
+}
diff --git a/traffic_ops/traffic_ops_golang/tenant/tenancy.go b/traffic_ops/traffic_ops_golang/tenant/tenancy.go
index c897f2d9e..e57a4dfa2 100644
--- a/traffic_ops/traffic_ops_golang/tenant/tenancy.go
+++ b/traffic_ops/traffic_ops_golang/tenant/tenancy.go
@@ -110,6 +110,32 @@ func CheckID(tx *sql.Tx, user *auth.CurrentUser, dsID int) (error, error, int) {
 	return nil, nil, http.StatusOK
 }
 
+// GetUserTenantListTx returns a Tenant list that the specified user has access to.
+// NOTE: This method does not use the use_tenancy parameter and if this method is being used
+// to control tenancy the parameter must be checked. The method IsResourceAuthorizedToUser checks the use_tenancy parameter
+// and should be used for this purpose in most cases.
+func GetUserTenantListTx(user auth.CurrentUser, tx *sql.Tx) ([]tc.TenantNullable, error) {
+	query := `WITH RECURSIVE q AS (SELECT id, name, active, parent_id, last_updated FROM tenant WHERE id = $1
+	UNION SELECT t.id, t.name, t.active, t.parent_id, t.last_updated  FROM tenant t JOIN q ON q.id = t.parent_id)
+	SELECT id, name, active, parent_id, last_updated FROM q;`
+
+	rows, err := tx.Query(query, user.TenantID)
+	if err != nil {
+		return nil, errors.New("querying user tenant list: " + err.Error())
+	}
+	defer rows.Close()
+
+	tenants := []tc.TenantNullable{}
+	for rows.Next() {
+		t := tc.TenantNullable{}
+		if err := rows.Scan(&t.ID, &t.Name, &t.Active, &t.ParentID, &t.LastUpdated); err != nil {
+			return nil, err
+		}
+		tenants = append(tenants, t)
+	}
+	return tenants, nil
+}
+
 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


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services