You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by da...@apache.org on 2018/02/26 16:45:07 UTC
[incubator-trafficcontrol] 01/17: updated phys_locations to use the
CRUD interface
This is an automated email from the ASF dual-hosted git repository.
dangogh pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-trafficcontrol.git
commit 578c190cac248299d553a9e32a64f22bf68e4b78
Author: Dewayne Richardson <de...@apache.org>
AuthorDate: Tue Feb 20 13:15:12 2018 -0700
updated phys_locations to use the CRUD interface
---
traffic_ops/traffic_ops_golang/phys_locations.go | 128 --------
.../physlocation/phys_locations.go | 358 +++++++++++++++++++++
.../{ => physlocation}/phys_locations_test.go | 23 +-
traffic_ops/traffic_ops_golang/routes.go | 8 +
4 files changed, 372 insertions(+), 145 deletions(-)
diff --git a/traffic_ops/traffic_ops_golang/phys_locations.go b/traffic_ops/traffic_ops_golang/phys_locations.go
deleted file mode 100644
index b76cafb..0000000
--- a/traffic_ops/traffic_ops_golang/phys_locations.go
+++ /dev/null
@@ -1,128 +0,0 @@
-package main
-
-/*
- * 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 (
- "encoding/json"
- "fmt"
- "net/http"
-
- "github.com/apache/incubator-trafficcontrol/lib/go-log"
- "github.com/apache/incubator-trafficcontrol/lib/go-tc"
- "github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
- "github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
- "github.com/jmoiron/sqlx"
-)
-
-func physLocationsHandler(db *sqlx.DB) http.HandlerFunc {
- return func(w http.ResponseWriter, r *http.Request) {
- handleErrs := tc.GetHandleErrorsFunc(w, r)
-
- params, err := api.GetCombinedParams(r)
- if err != nil {
- log.Errorf("unable to get parameters from request: %s", err)
- handleErrs(http.StatusInternalServerError, err)
- }
-
- resp, errs, errType := getPhysLocationsResponse(params, db)
- if len(errs) > 0 {
- tc.HandleErrorsWithType(errs, errType, handleErrs)
- return
- }
-
- respBts, err := json.Marshal(resp)
- if err != nil {
- handleErrs(http.StatusInternalServerError, err)
- return
- }
-
- w.Header().Set("Content-Type", "application/json")
- fmt.Fprintf(w, "%s", respBts)
- }
-}
-
-func getPhysLocationsResponse(params map[string]string, db *sqlx.DB) (*tc.PhysLocationsResponse, []error, tc.ApiErrorType) {
- physLocations, errs, errType := getPhysLocations(params, db)
- if len(errs) > 0 {
- return nil, errs, errType
- }
- resp := tc.PhysLocationsResponse{
- Response: physLocations,
- }
- return &resp, nil, tc.NoError
-}
-
-func getPhysLocations(params map[string]string, db *sqlx.DB) ([]tc.PhysLocation, []error, tc.ApiErrorType) {
- var rows *sqlx.Rows
- var err error
-
- // Query Parameters to Database Query column mappings
- // see the fields mapped in the SQL query
- queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
- "id": dbhelpers.WhereColumnInfo{"pl.id", api.IsInt},
- "region": dbhelpers.WhereColumnInfo{"pl.region", api.IsInt},
- }
-
- where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(params, queryParamsToQueryCols)
- if len(errs) > 0 {
- return nil, errs, tc.DataConflictError
- }
-
- query := selectPhysLocationsQuery() + where + orderBy
- log.Debugln("Query is ", query)
-
- rows, err = db.NamedQuery(query, queryValues)
- if err != nil {
- return nil, []error{err}, tc.SystemError
- }
- defer rows.Close()
-
- physLocations := []tc.PhysLocation{}
- for rows.Next() {
- var s tc.PhysLocation
- if err = rows.StructScan(&s); err != nil {
- return nil, []error{fmt.Errorf("getting physLocations: %v", err)}, tc.SystemError
- }
- physLocations = append(physLocations, s)
- }
- return physLocations, nil, tc.NoError
-}
-
-func selectPhysLocationsQuery() string {
-
- query := `SELECT
-pl.address,
-pl.city,
-COALESCE(pl.comments, '') as comments,
-COALESCE(pl.email, '') as email,
-pl.id,
-pl.last_updated,
-pl.name,
-COALESCE(pl.phone, '') as phone,
-COALESCE(pl.poc, '') as poc,
-pl.region as regionid,
-r.name as region,
-pl.short_name,
-pl.state,
-pl.zip
-FROM phys_location pl
-JOIN region r ON pl.region = r.id`
- return query
-}
diff --git a/traffic_ops/traffic_ops_golang/physlocation/phys_locations.go b/traffic_ops/traffic_ops_golang/physlocation/phys_locations.go
new file mode 100644
index 0000000..bb3ada5
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/physlocation/phys_locations.go
@@ -0,0 +1,358 @@
+package physlocation
+
+/*
+ * 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"
+
+ "github.com/apache/incubator-trafficcontrol/lib/go-log"
+ "github.com/apache/incubator-trafficcontrol/lib/go-tc"
+ "github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/auth"
+ "github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
+ "github.com/jmoiron/sqlx"
+ "github.com/lib/pq"
+)
+
+//we need a type alias to define functions on
+type TOPhysLocation tc.PhysLocation
+
+//the refType is passed into the handlers where a copy of its type is used to decode the json.
+var refType = TOPhysLocation(tc.PhysLocation{})
+
+func GetRefType() *TOPhysLocation {
+ return &refType
+}
+
+//Implementation of the Identifier, Validator interface functions
+func (pl *TOPhysLocation) GetID() (int, bool) {
+ return pl.ID, true
+}
+
+func (pl *TOPhysLocation) GetAuditName() string {
+ return pl.Name
+}
+
+func (pl *TOPhysLocation) GetType() string {
+ return "physLocation"
+}
+
+func (pl *TOPhysLocation) SetID(i int) {
+ pl.ID = i
+}
+
+func (pl *TOPhysLocation) Validate(db *sqlx.DB) []error {
+ errs := []error{}
+ if len(pl.Name) < 1 {
+ errs = append(errs, errors.New(`PhysLocation 'name' is required.`))
+ }
+ return errs
+}
+
+func (pl *TOPhysLocation) Read(db *sqlx.DB, parameters map[string]string, user auth.CurrentUser) ([]interface{}, []error, tc.ApiErrorType) {
+ var rows *sqlx.Rows
+
+ // Query Parameters to Database Query column mappings
+ // see the fields mapped in the SQL query
+ queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+ "name": dbhelpers.WhereColumnInfo{"name", nil},
+ "id": dbhelpers.WhereColumnInfo{"id", nil},
+ "region": dbhelpers.WhereColumnInfo{"region", nil},
+ }
+ where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(parameters, queryParamsToQueryCols)
+ if len(errs) > 0 {
+ return nil, errs, tc.DataConflictError
+ }
+
+ query := selectPhysLocationsQuery() + where + orderBy
+ log.Debugln("Query is ", query)
+
+ rows, err := db.NamedQuery(query, queryValues)
+ if err != nil {
+ log.Errorf("Error querying PhysLocations: %v", err)
+ return nil, []error{tc.DBError}, tc.SystemError
+ }
+ defer rows.Close()
+
+ PhysLocations := []interface{}{}
+ for rows.Next() {
+ var s tc.PhysLocation
+ if err = rows.StructScan(&s); err != nil {
+ log.Errorf("error parsing PhysLocation rows: %v", err)
+ return nil, []error{tc.DBError}, tc.SystemError
+ }
+ PhysLocations = append(PhysLocations, s)
+ }
+
+ return PhysLocations, []error{}, tc.NoError
+}
+
+func selectPhysLocationsQuery() string {
+
+ query := `SELECT
+pl.address,
+pl.city,
+COALESCE(pl.comments, '') as comments,
+COALESCE(pl.email, '') as email,
+pl.id,
+pl.last_updated,
+pl.name,
+COALESCE(pl.phone, '') as phone,
+COALESCE(pl.poc, '') as poc,
+pl.region as regionid,
+r.name as region,
+pl.short_name,
+pl.state,
+pl.zip
+FROM phys_location pl
+JOIN region r ON pl.region = r.id`
+
+ return query
+}
+
+//The TOPhysLocation implementation of the Updater interface
+//all implementations of Updater should use transactions and return the proper errorType
+//ParsePQUniqueConstraintError is used to determine if a phys_location with conflicting values exists
+//if so, it will return an errorType of DataConflict and the type should be appended to the
+//generic error message returned
+func (pl *TOPhysLocation) Update(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErrorType) {
+ rollbackTransaction := true
+ tx, err := db.Beginx()
+ defer func() {
+ if tx == nil || !rollbackTransaction {
+ return
+ }
+ err := tx.Rollback()
+ if err != nil {
+ log.Errorln(errors.New("rolling back transaction: " + err.Error()))
+ }
+ }()
+
+ if err != nil {
+ log.Error.Printf("could not begin transaction: %v", err)
+ return tc.DBError, tc.SystemError
+ }
+ log.Debugf("about to run exec query: %s with phys_location: %++v", updateQuery(), pl)
+ resultRows, err := tx.NamedQuery(updateQuery(), pl)
+ if err != nil {
+ if pqErr, ok := err.(*pq.Error); ok {
+ err, eType := dbhelpers.ParsePQUniqueConstraintError(pqErr)
+ if eType == tc.DataConflictError {
+ return errors.New("a phys_location with " + err.Error()), eType
+ }
+ return err, eType
+ } else {
+ log.Errorf("received error: %++v from update execution", err)
+ return tc.DBError, tc.SystemError
+ }
+ }
+ defer resultRows.Close()
+
+ var lastUpdated tc.Time
+ rowsAffected := 0
+ for resultRows.Next() {
+ rowsAffected++
+ if err := resultRows.Scan(&lastUpdated); err != nil {
+ log.Error.Printf("could not scan lastUpdated from insert: %s\n", err)
+ return tc.DBError, tc.SystemError
+ }
+ }
+ log.Debugf("lastUpdated: %++v", lastUpdated)
+ pl.LastUpdated = lastUpdated
+ if rowsAffected != 1 {
+ if rowsAffected < 1 {
+ return errors.New("no phys_location found with this id"), tc.DataMissingError
+ } else {
+ return fmt.Errorf("this update affected too many rows: %d", rowsAffected), tc.SystemError
+ }
+ }
+ err = tx.Commit()
+ if err != nil {
+ log.Errorln("Could not commit transaction: ", err)
+ return tc.DBError, tc.SystemError
+ }
+ rollbackTransaction = false
+ return nil, tc.NoError
+}
+
+//The TOPhysLocation implementation of the Creator interface
+//all implementations of Creator should use transactions and return the proper errorType
+//ParsePQUniqueConstraintError is used to determine if a phys_location with conflicting values exists
+//if so, it will return an errorType of DataConflict and the type should be appended to the
+//generic error message returned
+//The insert sql returns the id and lastUpdated values of the newly inserted phys_location and have
+//to be added to the struct
+func (pl *TOPhysLocation) Create(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErrorType) {
+ rollbackTransaction := true
+ tx, err := db.Beginx()
+ defer func() {
+ if tx == nil || !rollbackTransaction {
+ return
+ }
+ err := tx.Rollback()
+ if err != nil {
+ log.Errorln(errors.New("rolling back transaction: " + err.Error()))
+ }
+ }()
+
+ if err != nil {
+ log.Error.Printf("could not begin transaction: %v", err)
+ return tc.DBError, tc.SystemError
+ }
+ resultRows, err := tx.NamedQuery(insertQuery(), pl)
+ if err != nil {
+ if pqErr, ok := err.(*pq.Error); ok {
+ err, eType := dbhelpers.ParsePQUniqueConstraintError(pqErr)
+ if eType == tc.DataConflictError {
+ return errors.New("a phys_location with " + err.Error()), eType
+ }
+ return err, eType
+ } else {
+ log.Errorf("received non pq error: %++v from create execution", err)
+ return tc.DBError, tc.SystemError
+ }
+ }
+ defer resultRows.Close()
+
+ var id int
+ var lastUpdated tc.Time
+ rowsAffected := 0
+ for resultRows.Next() {
+ rowsAffected++
+ if err := resultRows.Scan(&id, &lastUpdated); 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 phys_location 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 phys_location insert")
+ log.Errorln(err)
+ return tc.DBError, tc.SystemError
+ }
+ pl.SetID(id)
+ pl.LastUpdated = lastUpdated
+ err = tx.Commit()
+ if err != nil {
+ log.Errorln("Could not commit transaction: ", err)
+ return tc.DBError, tc.SystemError
+ }
+ rollbackTransaction = false
+ return nil, tc.NoError
+}
+
+//The PhysLocation implementation of the Deleter interface
+//all implementations of Deleter should use transactions and return the proper errorType
+func (pl *TOPhysLocation) Delete(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErrorType) {
+ rollbackTransaction := true
+ tx, err := db.Beginx()
+ defer func() {
+ if tx == nil || !rollbackTransaction {
+ return
+ }
+ err := tx.Rollback()
+ if err != nil {
+ log.Errorln(errors.New("rolling back transaction: " + err.Error()))
+ }
+ }()
+
+ if err != nil {
+ log.Error.Printf("could not begin transaction: %v", err)
+ return tc.DBError, tc.SystemError
+ }
+ log.Debugf("about to run exec query: %s with phys_location: %++v", deleteQuery(), pl)
+ result, err := tx.NamedExec(deleteQuery(), pl)
+ 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 phys_location with that id found"), tc.DataMissingError
+ } else {
+ return fmt.Errorf("this create affected too many rows: %d", rowsAffected), tc.SystemError
+ }
+ }
+ err = tx.Commit()
+ if err != nil {
+ log.Errorln("Could not commit transaction: ", err)
+ return tc.DBError, tc.SystemError
+ }
+ rollbackTransaction = false
+ return nil, tc.NoError
+}
+
+func updateQuery() string {
+ query := `UPDATE
+phys_location SET
+address=:address,
+city=:city,
+comments=:comments,
+email=:email,
+name=:name,
+phone=:phone,
+poc=:poc,
+region=:region,
+short_name=:short_name,
+state=:state,
+zip=:zip
+WHERE id=:id RETURNING last_updated`
+ return query
+}
+
+func insertQuery() string {
+ query := `INSERT INTO phys_location (
+address,
+city,
+comments,
+email,
+name,
+phone,
+poc,
+region,
+short_name,
+state,
+zip) VALUES (
+:address,
+:city,
+:comments,
+:email,
+:name,
+:phone,
+:poc,
+:region,
+:short_name,
+:state,
+:zip) RETURNING id,last_updated`
+ return query
+}
+
+func deleteQuery() string {
+ query := `DELETE FROM phys_location
+WHERE id=:id`
+ return query
+}
diff --git a/traffic_ops/traffic_ops_golang/phys_locations_test.go b/traffic_ops/traffic_ops_golang/physlocation/phys_locations_test.go
similarity index 81%
rename from traffic_ops/traffic_ops_golang/phys_locations_test.go
rename to traffic_ops/traffic_ops_golang/physlocation/phys_locations_test.go
index b47c69c..adc1312 100644
--- a/traffic_ops/traffic_ops_golang/phys_locations_test.go
+++ b/traffic_ops/traffic_ops_golang/physlocation/phys_locations_test.go
@@ -1,4 +1,4 @@
-package main
+package physlocation
/*
* Licensed to the Apache Software Foundation (ASF) under one
@@ -24,6 +24,7 @@ import (
"time"
"github.com/apache/incubator-trafficcontrol/lib/go-tc"
+ "github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/auth"
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/test"
"github.com/jmoiron/sqlx"
@@ -91,25 +92,13 @@ func TestGetPhysLocations(t *testing.T) {
mock.ExpectQuery("SELECT").WillReturnRows(rows)
v := map[string]string{"dsId": "1"}
- servers, errs, errType := getPhysLocations(v, db)
+ physLocations, errs, _ := refType.Read(db, v, auth.CurrentUser{})
if len(errs) > 0 {
- t.Errorf("getPhysLocations expected: no errors, actual: %v with error type: %s", err, errType.String())
+ t.Errorf("physLocation.Read expected: no errors, actual: %v", errs)
}
- if len(servers) != 2 {
- t.Errorf("getPhysLocations expected: len(servers) == 2, actual: %v", len(servers))
+ if len(physLocations) != 2 {
+ t.Errorf("physLocation.Read expected: len(physLocations) == 2, actual: %v", len(physLocations))
}
}
-
-type SortablePhysLocations []tc.PhysLocation
-
-func (s SortablePhysLocations) Len() int {
- return len(s)
-}
-func (s SortablePhysLocations) Swap(i, j int) {
- s[i], s[j] = s[j], s[i]
-}
-func (s SortablePhysLocations) Less(i, j int) bool {
- return s[i].Name < s[j].Name
-}
diff --git a/traffic_ops/traffic_ops_golang/routes.go b/traffic_ops/traffic_ops_golang/routes.go
index 317d71d..e85c470 100644
--- a/traffic_ops/traffic_ops_golang/routes.go
+++ b/traffic_ops/traffic_ops_golang/routes.go
@@ -35,6 +35,7 @@ import (
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/cdn"
dsrequest "github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/deliveryservice/request"
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/division"
+ "github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/physlocation"
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/region"
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/status"
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/systeminfo"
@@ -97,6 +98,13 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
{1.3, http.MethodPut, `deliveryservices/{xmlID}/urisignkeys$`, saveDeliveryServiceURIKeysHandler(d.DB, d.Config), auth.PrivLevelAdmin, Authenticated, nil},
{1.3, http.MethodDelete, `deliveryservices/{xmlID}/urisignkeys$`, removeDeliveryServiceURIKeysHandler(d.DB, d.Config), auth.PrivLevelAdmin, Authenticated, nil},
+ //Phys_Locations
+ {1.3, http.MethodGet, `phys_locations/?(\.json)?$`, api.ReadHandler(physlocation.GetRefType(), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
+ {1.3, http.MethodGet, `phys_locations/{id}$`, api.ReadHandler(physlocation.GetRefType(), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
+ {1.3, http.MethodPut, `phys_locations/{id}$`, api.UpdateHandler(physlocation.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
+ {1.3, http.MethodPost, `phys_locations/?$`, api.CreateHandler(physlocation.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
+ {1.3, http.MethodDelete, `phys_locations/{id}$`, api.DeleteHandler(physlocation.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
+
//Statuses
{1.3, http.MethodGet, `statuses/?(\.json)?$`, api.ReadHandler(status.GetRefType(), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
{1.3, http.MethodGet, `statuses/{id}$`, api.ReadHandler(status.GetRefType(), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
--
To stop receiving notification emails like this one, please contact
dangogh@apache.org.