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.