You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ra...@apache.org on 2018/08/09 18:54:14 UTC
[trafficcontrol] 01/10: Handlers and Types for
cdns/:name/federations
This is an automated email from the ASF dual-hosted git repository.
rawlin pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git
commit 41e9fd7e5c579e5a8fb2d5cc2d938c90d8abba3a
Author: moltzaum <ma...@moltzau.net>
AuthorDate: Sat Jul 14 20:18:57 2018 -0600
Handlers and Types for cdns/:name/federations
This is the majority of the infrastructure, it mostly just doesn't include the client and the tests.
---
lib/go-tc/v13/federations.go | 75 ++++++
.../cdnfederation/cdnfederations.go | 295 +++++++++++++++++++++
traffic_ops/traffic_ops_golang/routes.go | 9 +
3 files changed, 379 insertions(+)
diff --git a/lib/go-tc/v13/federations.go b/lib/go-tc/v13/federations.go
new file mode 100644
index 0000000..d2d67d3
--- /dev/null
+++ b/lib/go-tc/v13/federations.go
@@ -0,0 +1,75 @@
+package v13
+
+/*
+ * 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.
+ */
+
+// Other endpoints define their own alert. Why not just use tc.Alert?
+type FederationAlert struct {
+ Level string `json:"level"`
+ Text string `json:"text"`
+}
+
+type CDNFederationResponse struct {
+ Response []CDNFederation `json:"response"`
+}
+
+type CreateCDNFederationResponse struct {
+ Response CDNFederation `json:"response"`
+ Alerts []FederationAlert `json:"alerts"`
+}
+
+type UpdateCDNFederationResponse struct {
+ Reponse CDNFederation `json:"response"`
+ Alerts []FederationAlert `json:"alerts"`
+}
+
+type DeleteCDNFederationResponse struct {
+ Alerts []FederationAlert `json:"alerts"`
+}
+
+type CDNFederation struct {
+ ID int `json:"id"`
+ CName string `json:"cname"`
+ Ttl int `json:"ttl"`
+ Description string `json:"description"`
+ *DeliveryServiceIDs `json:"deliveryService,omitempty"`
+}
+
+type CDNFederationNullable struct {
+ ID *int `json:"id" db:"id"`
+ CName *string `json:"cname" db:"cname"`
+ Ttl *int `json:"ttl" db:"ttl"`
+ Description *string `json:"description" db:"description"`
+
+ //omitempty only works with primitive types and pointers
+ *DeliveryServiceIDsNullable `json:"deliveryService,omitempty"`
+
+ //Extra datapoint for ease in Read function (cannot Scan after StructScan)
+ Name *string `json:"-" db:"cdn_name"`
+}
+
+type DeliveryServiceIDs struct {
+ ID int `json:"id,omitempty"`
+ XmlId string `json:"xmlId,omitempty"`
+}
+
+type DeliveryServiceIDsNullable struct {
+ ID *int `json:"id,omitempty" db:"ds_id"`
+ XmlId *string `json:"xmlId,omitempty" db:"xml_id"`
+}
diff --git a/traffic_ops/traffic_ops_golang/cdnfederation/cdnfederations.go b/traffic_ops/traffic_ops_golang/cdnfederation/cdnfederations.go
new file mode 100644
index 0000000..75b6494
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/cdnfederation/cdnfederations.go
@@ -0,0 +1,295 @@
+package cdnfederation
+
+import (
+ "errors"
+ "fmt"
+ "strconv"
+ "strings"
+
+ "github.com/apache/trafficcontrol/lib/go-log"
+ "github.com/apache/trafficcontrol/lib/go-tc"
+ "github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
+ "github.com/apache/trafficcontrol/lib/go-tc/v13"
+ "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/go-ozzo/ozzo-validation"
+ "github.com/lib/pq"
+)
+
+//we need a type alias to define functions on
+type TOCDNFederation struct {
+ ReqInfo *api.APIInfo `json:"-"`
+ v13.CDNFederationNullable
+}
+
+//Used for all CRUD routes
+func GetTypeSingleton() api.CRUDFactory {
+ return func(reqInfo *api.APIInfo) api.CRUDer {
+ toReturn := TOCDNFederation{reqInfo, v13.CDNFederationNullable{}}
+ return &toReturn
+ }
+}
+
+//Fufills `Identifier' interface
+func (fed TOCDNFederation) GetKeyFieldsInfo() []api.KeyFieldInfo {
+ return []api.KeyFieldInfo{{Field: "id", Func: api.GetIntKey}}
+}
+
+//Fufills `Identifier' interface
+func (fed TOCDNFederation) GetKeys() (map[string]interface{}, bool) {
+ if fed.ID == nil {
+ return map[string]interface{}{"id": 0}, false
+ }
+ return map[string]interface{}{"id": *fed.ID}, true
+}
+
+//Fufills `Identifier' interface
+func (fed TOCDNFederation) GetAuditName() string {
+ if fed.CName != nil {
+ return *fed.CName
+ }
+ if fed.ID != nil {
+ return strconv.Itoa(*fed.ID)
+ }
+ return "unknown"
+}
+
+//Fufills `Identifier' interface
+func (fed TOCDNFederation) GetType() string {
+ return "cdnfederation"
+}
+
+//Fufills `Create' interface
+func (fed *TOCDNFederation) SetKeys(keys map[string]interface{}) {
+ i, _ := keys["id"].(int) //non-panicking type assertion
+ fed.ID = &i
+}
+
+//Fulfills `Validate' interface
+func (fed *TOCDNFederation) Validate() error {
+
+ noSpaces := validation.NewStringRule(tovalidate.NoSpaces, "cannot contain spaces")
+ endsWithDot := validation.NewStringRule(
+ func(str string) bool {
+ return strings.HasSuffix(str, ".")
+ }, "must end with a period")
+
+ //cname regex: (^\S*\.$), ttl regex: (^\d+$)
+ validateErrs := validation.Errors{
+ "cname": validation.Validate(fed.CName, validation.Required, endsWithDot, noSpaces),
+ "ttl": validation.Validate(fed.Ttl, validation.Required),
+ }
+ return util.JoinErrs(tovalidate.ToErrors(validateErrs))
+}
+
+//This separates out errors depending on whether or not some constraint prevented
+//the operation from occuring.
+func parseQueryError(parseErr error, method string) (error, tc.ApiErrorType) {
+ if pqErr, ok := parseErr.(*pq.Error); ok {
+ err, eType := dbhelpers.ParsePQUniqueConstraintError(pqErr)
+ if eType == tc.DataConflictError {
+ return errors.New("a federation with " + err.Error()), eType
+ }
+ return err, eType
+ } else {
+ log.Errorf("received error: %++v from %s execution", parseErr, method)
+ return tc.DBError, tc.SystemError
+ }
+}
+
+//fed.ReqInfo.Params["name"] is not used on creation, rather the cdn name
+//is connected when the federations/:id/deliveryservice links a federation
+//Note: cdns and deliveryservies have a 1-1 relationship
+func (fed *TOCDNFederation) Create() (error, tc.ApiErrorType) {
+
+ if fed.ReqInfo.User.PrivLevel < auth.PrivLevelAdmin {
+ return errors.New("Forbidden"), tc.ForbiddenError
+ }
+
+ // Boilerplate code below
+ resultRows, err := fed.ReqInfo.Tx.NamedQuery(insertQuery(), fed)
+ if err != nil {
+ return parseQueryError(err, "create")
+ }
+ defer resultRows.Close()
+
+ var id int
+ rowsAffected := 0
+ for resultRows.Next() {
+ rowsAffected++
+ if err = resultRows.Scan(&id); err != nil {
+ log.Error.Printf("could not scan id from insert: %s\n", err)
+ return tc.DBError, tc.SystemError
+ }
+ }
+ if rowsAffected == 0 {
+ err = errors.New("no federation was inserted, no id was returned")
+ log.Errorln(err)
+ return tc.DBError, tc.SystemError
+ } else if rowsAffected > 1 {
+ err = errors.New("too many ids returned from fed insert")
+ log.Errorln(err)
+ return tc.DBError, tc.SystemError
+ }
+ fed.SetKeys(map[string]interface{}{"id": id})
+
+ return nil, tc.NoError
+}
+
+//Concerning efficiency, maybe it would be better to conditionally run different queries?
+//This way the handler wouldn't be doing a full outer join for every query.
+func (fed *TOCDNFederation) Read(parameters map[string]string) ([]interface{}, []error, tc.ApiErrorType) {
+
+ _, ok_id := parameters["id"]
+ queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+ //db tag symbol from query
+ "id": dbhelpers.WhereColumnInfo{Column: "federation.id", Checker: api.IsInt},
+ "cname": dbhelpers.WhereColumnInfo{Column: "cname", Checker: nil},
+ "ttl": dbhelpers.WhereColumnInfo{Column: "ttl", Checker: api.IsInt},
+ "description": dbhelpers.WhereColumnInfo{Column: "description", Checker: nil},
+ "xmlId": dbhelpers.WhereColumnInfo{Column: "xml_id", Checker: nil},
+ "ds_id": dbhelpers.WhereColumnInfo{Column: "deliveryservice.id", Checker: api.IsInt},
+
+ //"name": dbhelpers.WhereColumnInfo{Column: "cdn.name", Checker: nil}, //would narrow scope of search
+ "cdn_name": dbhelpers.WhereColumnInfo{Column: "cdn.name", Checker: nil}, //used to verify cdn name exists
+ }
+
+ where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(parameters, queryParamsToQueryCols)
+ if len(errs) > 0 {
+ return nil, errs, tc.DataConflictError
+ }
+
+ query := selectQuery() + where + orderBy
+ log.Debugln("Query is ", query)
+
+ rows, err := fed.ReqInfo.Tx.NamedQuery(query, queryValues)
+ if err != nil {
+ log.Errorf("Error querying federations: %v", err)
+ return nil, []error{tc.DBError}, tc.SystemError
+ }
+ defer rows.Close()
+
+ cdnFound := false
+ federations := []interface{}{}
+ for rows.Next() {
+ var fed v13.CDNFederationNullable
+ if err = rows.StructScan(&fed); err != nil {
+ log.Errorf("error parsing federation rows: %v", err)
+ return nil, []error{tc.DBError}, tc.SystemError
+ }
+
+ //if we have an id, the cdn name is fooable
+ if ok_id || fed.Name != nil && *fed.Name == parameters["name"] {
+ cdnFound = true
+
+ //if we are getting by id, there may not be an attached deliveryservice
+ //DeliveryServiceIDsNullable will not be nil itself, due to the struct scan
+ if ok_id && fed.DeliveryServiceIDsNullable.ID == nil {
+ fed.DeliveryServiceIDsNullable = nil
+ }
+
+ //Never return cnd and deliveryService only information. Just federation data.
+ if fed.ID != nil {
+ federations = append(federations, fed)
+ }
+ }
+ }
+
+ if !cdnFound {
+ return nil, []error{errors.New("Resource not found.")}, tc.DataMissingError
+ }
+
+ return federations, []error{}, tc.NoError
+}
+
+func (fed *TOCDNFederation) Update() (error, tc.ApiErrorType) {
+
+ if fed.ReqInfo.User.PrivLevel < auth.PrivLevelAdmin {
+ return errors.New("Forbidden"), tc.ForbiddenError
+ }
+
+ result, err := fed.ReqInfo.Tx.NamedExec(updateQuery(), fed)
+ if err != nil {
+ return parseQueryError(err, "update")
+ }
+
+ rowsAffected, err := result.RowsAffected()
+ if err != nil {
+ return tc.DBError, tc.SystemError
+ }
+
+ if rowsAffected != 1 {
+ if rowsAffected < 1 {
+ return errors.New("no federation found with this id"), tc.DataMissingError
+ } else {
+ return fmt.Errorf("this update affected too many rows: %d", rowsAffected), tc.SystemError
+ }
+ }
+ return nil, tc.NoError
+}
+
+//In the perl version, :name is ignored. It is not even verified whether or not
+//:name is a real cdn that exists. This mimicks the perl behavior.
+func (fed *TOCDNFederation) Delete() (error, tc.ApiErrorType) {
+
+ if fed.ReqInfo.User.PrivLevel < auth.PrivLevelAdmin {
+ return errors.New("Forbidden"), tc.ForbiddenError
+ }
+
+ log.Debugf("about to run exec query: %s with federation: %++v", deleteQuery(), fed)
+ result, err := fed.ReqInfo.Tx.NamedExec(deleteQuery(), fed)
+ if err != nil {
+ log.Errorf("received error: %++v from delete execution", err)
+ return tc.DBError, tc.SystemError
+ }
+ rowsAffected, err := result.RowsAffected()
+ if err != nil {
+ return tc.DBError, tc.SystemError
+ }
+ if rowsAffected != 1 {
+ if rowsAffected < 1 {
+ return errors.New("no federation with that id found"), tc.DataMissingError
+ } else {
+ return fmt.Errorf("this delete affected too many rows: %d", rowsAffected), tc.SystemError
+ }
+ }
+ return nil, tc.NoError
+}
+
+//Full outer joins are used so that we can get a federation without an attached deliveryservice (fetching by id)
+//and so that we can retrieve all cdn names to determine if the requested cdn exists.
+func selectQuery() string {
+ return `
+ SELECT federation.id as id, cname, ttl, description, deliveryservice.id as ds_id, xml_id, cdn.name as cdn_name FROM federation
+ FULL OUTER JOIN federation_deliveryservice as fd ON federation.id = fd.federation
+ FULL OUTER JOIN deliveryservice ON deliveryservice.id = fd.deliveryservice
+ FULL OUTER JOIN cdn ON cdn.id = cdn_id`
+}
+
+func updateQuery() string {
+ return `
+ UPDATE federation SET
+ cname=:cname
+ ttl=:ttl
+ description=:description
+ WHERE id=:id`
+}
+
+func insertQuery() string {
+ return `
+ INSERT INTO federation (
+ cname,
+ ttl,
+ description
+ ) VALUES (
+ :cname,
+ :ttl,
+ :description
+ ) RETURNING id`
+}
+
+func deleteQuery() string {
+ return `DELETE FROM federation WHERE id = :id`
+}
diff --git a/traffic_ops/traffic_ops_golang/routes.go b/traffic_ops/traffic_ops_golang/routes.go
index 2f12491..a177b8e 100644
--- a/traffic_ops/traffic_ops_golang/routes.go
+++ b/traffic_ops/traffic_ops_golang/routes.go
@@ -40,6 +40,7 @@ import (
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/cachegroup"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/cdn"
+ "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/cdnfederation"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/coordinate"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/crconfig"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/deliveryservice"
@@ -291,6 +292,14 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) {
{1.3, http.MethodPut, `deliveryservices/{xmlID}/urisignkeys$`, saveDeliveryServiceURIKeysHandler, auth.PrivLevelAdmin, Authenticated, nil},
{1.3, http.MethodDelete, `deliveryservices/{xmlID}/urisignkeys$`, removeDeliveryServiceURIKeysHandler, auth.PrivLevelAdmin, Authenticated, nil},
+ // Federations by CDN (the actual table for `federation')
+ //Should this be 1.2 or 1.1?
+ {1.2, http.MethodGet, `cdns/{name}/federations/?(\.json)?$`, api.ReadHandler(cdnfederation.GetTypeSingleton()), auth.PrivLevelReadOnly, Authenticated, nil},
+ {1.2, http.MethodGet, `cdns/{name}/federations/{id}$`, api.ReadHandler(cdnfederation.GetTypeSingleton()), auth.PrivLevelReadOnly, Authenticated, nil},
+ {1.2, http.MethodPost, `cdns/{name}/federations/?(\.json)?$`, api.CreateHandler(cdnfederation.GetTypeSingleton()), auth.PrivLevelOperations, Authenticated, nil},
+ {1.2, http.MethodPut, `cdns/{name}/federations/?(\.json)?$`, api.UpdateHandler(cdnfederation.GetTypeSingleton()), auth.PrivLevelOperations, Authenticated, nil},
+ {1.2, http.MethodDelete, `cdns/{name}/federations/{id}$`, api.DeleteHandler(cdnfederation.GetTypeSingleton()), auth.PrivLevelOperations, Authenticated, nil},
+
//Origins
{1.3, http.MethodGet, `origins/?(\.json)?$`, api.ReadHandler(origin.GetTypeSingleton()), auth.PrivLevelReadOnly, Authenticated, nil},
{1.3, http.MethodGet, `origins/?$`, api.ReadHandler(origin.GetTypeSingleton()), auth.PrivLevelReadOnly, Authenticated, nil},