You are viewing a plain text version of this content. The canonical link for it is here.
Posted to issues@trafficcontrol.apache.org by GitBox <gi...@apache.org> on 2018/02/22 23:09:59 UTC

[GitHub] dangogh closed pull request #1895: Updated /regions to use the CRUD interface

dangogh closed pull request #1895: Updated /regions to use the CRUD interface 
URL: https://github.com/apache/incubator-trafficcontrol/pull/1895
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/lib/go-tc/regions.go b/lib/go-tc/regions.go
index 9c4edc3f2f..d32556cd11 100644
--- a/lib/go-tc/regions.go
+++ b/lib/go-tc/regions.go
@@ -24,7 +24,7 @@ type RegionsResponse struct {
 }
 
 type Region struct {
-	DivisionName string `json:"divisionName" db:"divisionname"`
+	DivisionName string `json:"divisionName"`
 	Division     int    `json:"division" db:"division"`
 	ID           int    `json:"id" db:"id"`
 	LastUpdated  Time   `json:"lastUpdated" db:"last_updated"`
diff --git a/traffic_ops/client/region.go b/traffic_ops/client/region.go
new file mode 100644
index 0000000000..f3e39f16b0
--- /dev/null
+++ b/traffic_ops/client/region.go
@@ -0,0 +1,132 @@
+/*
+
+   Licensed 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.
+*/
+
+package client
+
+import (
+	"encoding/json"
+	"fmt"
+	"net"
+	"net/http"
+
+	"github.com/apache/incubator-trafficcontrol/lib/go-tc"
+)
+
+const (
+	API_v13_REGIONS = "/api/1.3/regions"
+)
+
+// Create a Region
+func (to *Session) CreateRegion(region tc.Region) (tc.Alerts, ReqInf, error) {
+
+	var remoteAddr net.Addr
+	reqBody, err := json.Marshal(region)
+	reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
+	if err != nil {
+		return tc.Alerts{}, reqInf, err
+	}
+	resp, remoteAddr, err := to.request(http.MethodPost, API_v13_REGIONS, reqBody)
+	if err != nil {
+		return tc.Alerts{}, reqInf, err
+	}
+	defer resp.Body.Close()
+	var alerts tc.Alerts
+	err = json.NewDecoder(resp.Body).Decode(&alerts)
+	return alerts, reqInf, nil
+}
+
+// Update a Region by ID
+func (to *Session) UpdateRegionByID(id int, region tc.Region) (tc.Alerts, ReqInf, error) {
+
+	var remoteAddr net.Addr
+	reqBody, err := json.Marshal(region)
+	reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
+	if err != nil {
+		return tc.Alerts{}, reqInf, err
+	}
+	route := fmt.Sprintf("%s/%d", API_v13_REGIONS, id)
+	resp, remoteAddr, err := to.request(http.MethodPut, route, reqBody)
+	if err != nil {
+		return tc.Alerts{}, reqInf, err
+	}
+	defer resp.Body.Close()
+	var alerts tc.Alerts
+	err = json.NewDecoder(resp.Body).Decode(&alerts)
+	return alerts, reqInf, nil
+}
+
+// Returns a list of regions
+func (to *Session) GetRegions() ([]tc.Region, ReqInf, error) {
+	resp, remoteAddr, err := to.request(http.MethodGet, API_v13_REGIONS, nil)
+	reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
+	if err != nil {
+		return nil, reqInf, err
+	}
+	defer resp.Body.Close()
+
+	var data tc.RegionsResponse
+	err = json.NewDecoder(resp.Body).Decode(&data)
+	return data.Response, reqInf, nil
+}
+
+// GET a Region by the Region id
+func (to *Session) GetRegionByID(id int) ([]tc.Region, ReqInf, error) {
+	route := fmt.Sprintf("%s/%d", API_v13_REGIONS, id)
+	resp, remoteAddr, err := to.request(http.MethodGet, route, nil)
+	reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
+	if err != nil {
+		return nil, reqInf, err
+	}
+	defer resp.Body.Close()
+
+	var data tc.RegionsResponse
+	if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
+		return nil, reqInf, err
+	}
+
+	return data.Response, reqInf, nil
+}
+
+// GET a Region by the Region name
+func (to *Session) GetRegionByName(name string) ([]tc.Region, ReqInf, error) {
+	url := fmt.Sprintf("%s?name=%s", API_v13_REGIONS, name)
+	resp, remoteAddr, err := to.request(http.MethodGet, url, nil)
+	reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
+	if err != nil {
+		return nil, reqInf, err
+	}
+	defer resp.Body.Close()
+
+	var data tc.RegionsResponse
+	if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
+		return nil, reqInf, err
+	}
+
+	return data.Response, reqInf, nil
+}
+
+// DELETE a Region by ID
+func (to *Session) DeleteRegionByID(id int) (tc.Alerts, ReqInf, error) {
+	route := fmt.Sprintf("%s/%d", API_v13_REGIONS, id)
+	resp, remoteAddr, err := to.request(http.MethodDelete, route, nil)
+	reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
+	if err != nil {
+		return tc.Alerts{}, reqInf, err
+	}
+	defer resp.Body.Close()
+	var alerts tc.Alerts
+	err = json.NewDecoder(resp.Body).Decode(&alerts)
+	return alerts, reqInf, nil
+}
diff --git a/traffic_ops/testing/api/region_test.go b/traffic_ops/testing/api/region_test.go
new file mode 100644
index 0000000000..2d0d78a51e
--- /dev/null
+++ b/traffic_ops/testing/api/region_test.go
@@ -0,0 +1,107 @@
+/*
+
+   Licensed 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.
+*/
+
+package api
+
+import (
+	"fmt"
+	"testing"
+
+	"github.com/apache/incubator-trafficcontrol/lib/go-log"
+	"github.com/apache/incubator-trafficcontrol/lib/go-tc"
+)
+
+func TestRegions(t *testing.T) {
+
+	CreateTestRegions(t)
+	UpdateTestRegions(t)
+	GetTestRegions(t)
+	DeleteTestRegions(t)
+
+}
+
+func CreateTestRegions(t *testing.T) {
+	for _, region := range testData.Regions {
+		fmt.Printf("region ---> %v\n", region)
+		resp, _, err := TOSession.CreateRegion(region)
+		log.Debugln("Response: ", resp)
+		if err != nil {
+			t.Errorf("could not CREATE region: %v\n", err)
+		}
+	}
+}
+
+func UpdateTestRegions(t *testing.T) {
+
+	firstRegion := testData.Regions[0]
+	// Retrieve the Region by region so we can get the id for the Update
+	resp, _, err := TOSession.GetRegionByName(firstRegion.Name)
+	if err != nil {
+		t.Errorf("cannot GET Region by region: %v - %v\n", firstRegion.Name, err)
+	}
+	remoteRegion := resp[0]
+	expectedRegion := "OFFLINE-TEST"
+	remoteRegion.Name = expectedRegion
+	var alert tc.Alerts
+	alert, _, err = TOSession.UpdateRegionByID(remoteRegion.ID, remoteRegion)
+	if err != nil {
+		t.Errorf("cannot UPDATE Region by id: %v - %v\n", err, alert)
+	}
+
+	// Retrieve the Region to check region got updated
+	resp, _, err = TOSession.GetRegionByID(remoteRegion.ID)
+	if err != nil {
+		t.Errorf("cannot GET Region by region: %v - %v\n", firstRegion.Name, err)
+	}
+	respRegion := resp[0]
+	if respRegion.Name != expectedRegion {
+		t.Errorf("results do not match actual: %s, expected: %s\n", respRegion.Name, expectedRegion)
+	}
+
+}
+
+func GetTestRegions(t *testing.T) {
+	for _, region := range testData.Regions {
+		resp, _, err := TOSession.GetRegionByName(region.Name)
+		if err != nil {
+			t.Errorf("cannot GET Region by region: %v - %v\n", err, resp)
+		}
+	}
+}
+
+func DeleteTestRegions(t *testing.T) {
+
+	region := testData.Regions[1]
+	// Retrieve the Region by name so we can get the id
+	resp, _, err := TOSession.GetRegionByName(region.Name)
+	if err != nil {
+		t.Errorf("cannot GET Region by name: %v - %v\n", region.Name, err)
+	}
+	respRegion := resp[0]
+
+	delResp, _, err := TOSession.DeleteRegionByID(respRegion.ID)
+	if err != nil {
+		t.Errorf("cannot DELETE Region by region: %v - %v\n", err, delResp)
+	}
+
+	// Retrieve the Region to see if it got deleted
+	regionResp, _, err := TOSession.GetRegionByName(region.Name)
+	if err != nil {
+		t.Errorf("error deleting Region region: %s\n", err.Error())
+	}
+	if len(regionResp) > 0 {
+		t.Errorf("expected Region : %s to be deleted\n", region.Name)
+	}
+}
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 0000000000..52d881d054
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/region/regions.go
@@ -0,0 +1,320 @@
+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, bool) {
+	return region.ID, true
+}
+
+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{
+		"name":     dbhelpers.WhereColumnInfo{"r.name", nil},
+		"division": dbhelpers.WhereColumnInfo{"r.division", nil},
+		"id":       dbhelpers.WhereColumnInfo{"r.id", api.IsInt},
+	}
+	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 := 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 selectQuery() 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
+}
+
+//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
+		}
+		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
+		}
+		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 Creator interface
+//all implementations of Creator 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) 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(), 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
+		}
+		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
+	}
+	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 {
+		return errors.New("no region with that id found"), tc.DataMissingError
+	}
+	if rowsAffected > 1 {
+		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
+division=:division,
+name=:name
+WHERE id=:id RETURNING last_updated`
+	return query
+}
+
+func insertQuery() string {
+	query := `INSERT INTO region (
+division,
+name) VALUES (
+: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 65%
rename from traffic_ops/traffic_ops_golang/regions_test.go
rename to traffic_ops/traffic_ops_golang/region/regions_test.go
index 1b18d75c0b..0a9f28feb9 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
@@ -24,6 +24,8 @@ import (
 	"time"
 
 	"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/test"
 	"github.com/jmoiron/sqlx"
 
@@ -47,7 +49,7 @@ func getTestRegions() []tc.Region {
 	return regions
 }
 
-func TestGetRegions(t *testing.T) {
+func TestReadRegions(t *testing.T) {
 	mockDB, mock, err := sqlmock.New()
 	if err != nil {
 		t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
@@ -57,13 +59,14 @@ func TestGetRegions(t *testing.T) {
 	db := sqlx.NewDb(mockDB, "sqlmock")
 	defer db.Close()
 
-	testCase := getTestRegions()
+	refType := GetRefType()
+
+	testRegions := getTestRegions()
 	cols := test.ColsFromStructByTag("db", tc.Region{})
 	rows := sqlmock.NewRows(cols)
 
-	for _, ts := range testCase {
+	for _, ts := range testRegions {
 		rows = rows.AddRow(
-			ts.DivisionName,
 			ts.Division,
 			ts.ID,
 			ts.LastUpdated,
@@ -73,25 +76,33 @@ func TestGetRegions(t *testing.T) {
 	mock.ExpectQuery("SELECT").WillReturnRows(rows)
 	v := map[string]string{"dsId": "1"}
 
-	servers, errs, errType := getRegions(v, db)
+	regions, errs, _ := refType.Read(db, v, auth.CurrentUser{})
 	if len(errs) > 0 {
-		t.Errorf("getRegions expected: no errors, actual: %v with error type: %s", errs, errType.String())
+		t.Errorf("region.Read expected: no errors, actual: %v", errs)
 	}
 
-	if len(servers) != 2 {
-		t.Errorf("getRegions expected: len(servers) == 1, actual: %v", len(servers))
+	if len(regions) != 2 {
+		t.Errorf("region.Read expected: len(regions) == 2, actual: %v", len(regions))
 	}
-
 }
 
-type SortableRegions []tc.Region
+func TestInterfaces(t *testing.T) {
+	var i interface{}
+	i = &TORegion{}
 
-func (s SortableRegions) Len() int {
-	return len(s)
-}
-func (s SortableRegions) Swap(i, j int) {
-	s[i], s[j] = s[j], s[i]
-}
-func (s SortableRegions) Less(i, j int) bool {
-	return s[i].Name < s[j].Name
+	if _, ok := i.(api.Creator); !ok {
+		t.Errorf("Region must be Creator")
+	}
+	if _, ok := i.(api.Reader); !ok {
+		t.Errorf("Region must be Reader")
+	}
+	if _, ok := i.(api.Updater); !ok {
+		t.Errorf("Region must be Updater")
+	}
+	if _, ok := i.(api.Deleter); !ok {
+		t.Errorf("Region must be Deleter")
+	}
+	if _, ok := i.(api.Identifier); !ok {
+		t.Errorf("Region must be Identifier")
+	}
 }
diff --git a/traffic_ops/traffic_ops_golang/regions.go b/traffic_ops/traffic_ops_golang/regions.go
deleted file mode 100644
index 66e06ef97e..0000000000
--- 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 b59d92b3a6..a61c015f19 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{}},


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
users@infra.apache.org


With regards,
Apache Git Services