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/09/14 14:40:46 UTC

[trafficcontrol] 01/03: Add TO Go steering/id/targets

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 8ba7d24303d2b420d5059654f83a7154ff7a0703
Author: Robert Butts <ro...@apache.org>
AuthorDate: Tue Jul 10 08:29:21 2018 -0600

    Add TO Go steering/id/targets
---
 lib/go-tc/steeringtarget.go                        |   3 -
 traffic_ops/traffic_ops_golang/routes.go           |   7 +
 .../steeringtargets/steeringtargets.go             | 337 +++++++++++++++++++++
 traffic_ops/traffic_ops_golang/tenant/tenancy.go   |  26 ++
 4 files changed, 370 insertions(+), 3 deletions(-)

diff --git a/lib/go-tc/steeringtarget.go b/lib/go-tc/steeringtarget.go
index 17feaa3..e04c63d 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/traffic_ops_golang/routes.go b/traffic_ops/traffic_ops_golang/routes.go
index f49164e..f9c2c84 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"
@@ -386,6 +387,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 0000000..9642983
--- /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 c897f2d..e57a4df 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