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 77464b59890693698208829f81afdd5b28f5d487 Author: Dewayne Richardson <dewr...@apache.org> AuthorDate: Mon Feb 19 14:40:38 2018 -0700 updated to use the CRUD interface --- traffic_ops/traffic_ops_golang/region/regions.go | 325 +++++++++++++++++++++ .../{ => region}/regions_test.go | 2 +- traffic_ops/traffic_ops_golang/regions.go | 122 -------- traffic_ops/traffic_ops_golang/routes.go | 12 +- 4 files changed, 334 insertions(+), 127 deletions(-) diff --git a/traffic_ops/traffic_ops_golang/region/regions.go b/traffic_ops/traffic_ops_golang/region/regions.go new file mode 100644 index 0000000..cc9ed61 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/region/regions.go @@ -0,0 +1,325 @@ +package region + +/* + * 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/api" + "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 TORegion tc.Region + +//the refType is passed into the handlers where a copy of its type is used to decode the json. +var refType = TORegion(tc.Region{}) + +func GetRefType() *TORegion { + return &refType +} + +//Implementation of the Identifier, Validator interface functions +func (region *TORegion) GetID() int { + return region.ID +} + +func (region *TORegion) GetAuditName() string { + return region.Name +} + +func (region *TORegion) GetType() string { + return "region" +} + +func (region *TORegion) SetID(i int) { + region.ID = i +} + +func (region *TORegion) Validate(db *sqlx.DB) []error { + errs := []error{} + if len(region.Name) < 1 { + errs = append(errs, errors.New(`Region 'name' is required.`)) + } + return errs +} + +func (region *TORegion) 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{ + "domainName": dbhelpers.WhereColumnInfo{"domain_name", nil}, + "dnssecEnabled": dbhelpers.WhereColumnInfo{"dnssec_enabled", nil}, + "id": dbhelpers.WhereColumnInfo{"id", api.IsInt}, + "name": dbhelpers.WhereColumnInfo{"name", nil}, + } + where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(parameters, queryParamsToQueryCols) + if len(errs) > 0 { + return nil, errs, tc.DataConflictError + } + + query := selectRegionsQuery() + where + orderBy + log.Debugln("Query is ", query) + + rows, err := db.NamedQuery(query, queryValues) + if err != nil { + log.Errorf("Error querying Regions: %v", err) + return nil, []error{tc.DBError}, tc.SystemError + } + defer rows.Close() + + Regions := []interface{}{} + for rows.Next() { + var s tc.Region + if err = rows.StructScan(&s); err != nil { + log.Errorf("error parsing Region rows: %v", err) + return nil, []error{tc.DBError}, tc.SystemError + } + Regions = append(Regions, s) + } + + return Regions, []error{}, tc.NoError +} + +func selectRegionsQuery() string { + query := `SELECT +description, +division, +id, +last_updated, +name + +FROM region r` + return query +} + +//The TORegion implementation of the Updater interface +//all implementations of Updater should use transactions and return the proper errorType +//ParsePQUniqueConstraintError is used to determine if a region 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 (region *TORegion) 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 region: %++v", updateQuery(), region) + resultRows, err := tx.NamedQuery(updateQuery(), region) + if err != nil { + if pqErr, ok := err.(*pq.Error); ok { + err, eType := dbhelpers.ParsePQUniqueConstraintError(pqErr) + if eType == tc.DataConflictError { + return errors.New("a region 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) + region.LastUpdated = lastUpdated + if rowsAffected != 1 { + if rowsAffected < 1 { + return errors.New("no region 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 TORegion implementation of the Inserter interface +//all implementations of Inserter should use transactions and return the proper errorType +//ParsePQUniqueConstraintError is used to determine if a region 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 region and have +//to be added to the struct +func (region *TORegion) Insert(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(), region) + if err != nil { + if pqErr, ok := err.(*pq.Error); ok { + err, eType := dbhelpers.ParsePQUniqueConstraintError(pqErr) + if eType == tc.DataConflictError { + return errors.New("a region 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 region 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 region insert") + log.Errorln(err) + return tc.DBError, tc.SystemError + } + region.SetID(id) + region.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 Region implementation of the Deleter interface +//all implementations of Deleter should use transactions and return the proper errorType +func (region *TORegion) 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 region: %++v", deleteQuery(), region) + result, err := tx.NamedExec(deleteQuery(), region) + 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 region 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 +region SET +description=:description, +division=:division, +name=:name +WHERE id=:id RETURNING last_updated` + return query +} + +func insertQuery() string { + query := `INSERT INTO region ( +description, +division, +name) VALUES ( +:description, +:division, +:name) RETURNING id,last_updated` + return query +} + +func deleteQuery() string { + query := `DELETE FROM region +WHERE id=:id` + return query +} diff --git a/traffic_ops/traffic_ops_golang/regions_test.go b/traffic_ops/traffic_ops_golang/region/regions_test.go similarity index 99% rename from traffic_ops/traffic_ops_golang/regions_test.go rename to traffic_ops/traffic_ops_golang/region/regions_test.go index 1b18d75..b4fac20 100644 --- a/traffic_ops/traffic_ops_golang/regions_test.go +++ b/traffic_ops/traffic_ops_golang/region/regions_test.go @@ -1,4 +1,4 @@ -package main +package region /* * Licensed to the Apache Software Foundation (ASF) under one diff --git a/traffic_ops/traffic_ops_golang/regions.go b/traffic_ops/traffic_ops_golang/regions.go deleted file mode 100644 index 66e06ef..0000000 --- a/traffic_ops/traffic_ops_golang/regions.go +++ /dev/null @@ -1,122 +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 regionsHandler(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 := getRegionsResponse(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 getRegionsResponse(params map[string]string, db *sqlx.DB) (*tc.RegionsResponse, []error, tc.ApiErrorType) { - regions, errs, errType := getRegions(params, db) - if len(errs) > 0 { - return nil, errs, errType - } - - resp := tc.RegionsResponse{ - Response: regions, - } - return &resp, nil, tc.NoError -} - -func getRegions(params map[string]string, db *sqlx.DB) ([]tc.Region, []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{ - "division": dbhelpers.WhereColumnInfo{"d.id", api.IsInt}, - "id": dbhelpers.WhereColumnInfo{"r.id", api.IsInt}, - "name": dbhelpers.WhereColumnInfo{"r.name", nil}, - } - - where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(params, queryParamsToQueryCols) - if len(errs) > 0 { - return nil, errs, tc.DataConflictError - } - - query := selectRegionsQuery() + 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() - - regions := []tc.Region{} - for rows.Next() { - var s tc.Region - if err = rows.StructScan(&s); err != nil { - return nil, []error{fmt.Errorf("getting regions: %v", err)}, tc.SystemError - } - regions = append(regions, s) - } - return regions, nil, tc.NoError -} - -func selectRegionsQuery() string { - - query := `SELECT -r.division, -d.name as divisionname, -r.id, -r.last_updated, -r.name - -FROM region r -JOIN division d ON r.division = d.id` - return query -} diff --git a/traffic_ops/traffic_ops_golang/routes.go b/traffic_ops/traffic_ops_golang/routes.go index b59d92b..a61c015 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/region" "github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/status" "github.com/basho/riak-go-client" ) @@ -110,13 +111,16 @@ func Routes(d ServerData) ([]Route, http.Handler, error) { //HwInfo {1.2, http.MethodGet, `hwinfo-wip/?(\.json)?$`, hwInfoHandler(d.DB), auth.PrivLevelReadOnly, Authenticated, nil}, + //Regions + {1.3, http.MethodGet, `regions/?(\.json)?$`, api.ReadHandler(region.GetRefType(), d.DB), auth.PrivLevelReadOnly, Authenticated, nil}, + {1.3, http.MethodGet, `regions/{id}$`, api.ReadHandler(region.GetRefType(), d.DB), auth.PrivLevelReadOnly, Authenticated, nil}, + {1.3, http.MethodPut, `regions/{id}$`, api.UpdateHandler(region.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil}, + {1.3, http.MethodPost, `regions/?$`, api.CreateHandler(region.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil}, + {1.3, http.MethodDelete, `regions/{id}$`, api.DeleteHandler(region.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil}, + //Parameters {1.3, http.MethodGet, `parameters/?(\.json)?$`, parametersHandler(d.DB), auth.PrivLevelReadOnly, Authenticated, nil}, - //Regions - {1.2, http.MethodGet, `regions/?(\.json)?$`, regionsHandler(d.DB), auth.PrivLevelReadOnly, Authenticated, nil}, - {1.2, http.MethodGet, `regions/{id}$`, regionsHandler(d.DB), auth.PrivLevelReadOnly, Authenticated, nil}, - //Servers // explicitly passed to legacy system until fully implemented. Auth handled by legacy system. {1.2, http.MethodGet, `servers/checks$`, handlerToFunc(proxyHandler), 0, NoAuth, []Middleware{}}, -- To stop receiving notification emails like this one, please contact dang...@apache.org.