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