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 18:26:10 UTC

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

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

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/traffic_ops/client/status.go b/traffic_ops/client/status.go
new file mode 100644
index 0000000000..1145d8631c
--- /dev/null
+++ b/traffic_ops/client/status.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_STATUSES = "/api/1.3/statuses"
+)
+
+// Create a Status
+func (to *Session) CreateStatus(status tc.Status) (tc.Alerts, ReqInf, error) {
+
+	var remoteAddr net.Addr
+	reqBody, err := json.Marshal(status)
+	reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
+	if err != nil {
+		return tc.Alerts{}, reqInf, err
+	}
+	resp, remoteAddr, err := to.request(http.MethodPost, API_v13_STATUSES, 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 Status by ID
+func (to *Session) UpdateStatusByID(id int, status tc.Status) (tc.Alerts, ReqInf, error) {
+
+	var remoteAddr net.Addr
+	reqBody, err := json.Marshal(status)
+	reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
+	if err != nil {
+		return tc.Alerts{}, reqInf, err
+	}
+	route := fmt.Sprintf("%s/%d", API_v13_STATUSES, 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 Statuses
+func (to *Session) GetStatuses() ([]tc.Status, ReqInf, error) {
+	resp, remoteAddr, err := to.request(http.MethodGet, API_v13_STATUSES, nil)
+	reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
+	if err != nil {
+		return nil, reqInf, err
+	}
+	defer resp.Body.Close()
+
+	var data tc.StatusesResponse
+	err = json.NewDecoder(resp.Body).Decode(&data)
+	return data.Response, reqInf, nil
+}
+
+// GET a Status by the Status id
+func (to *Session) GetStatusByID(id int) ([]tc.Status, ReqInf, error) {
+	route := fmt.Sprintf("%s/%d", API_v13_STATUSES, 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.StatusesResponse
+	if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
+		return nil, reqInf, err
+	}
+
+	return data.Response, reqInf, nil
+}
+
+// GET a Status by the Status name
+func (to *Session) GetStatusByName(name string) ([]tc.Status, ReqInf, error) {
+	url := fmt.Sprintf("%s?name=%s", API_v13_STATUSES, 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.StatusesResponse
+	if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
+		return nil, reqInf, err
+	}
+
+	return data.Response, reqInf, nil
+}
+
+// DELETE a Status by id
+func (to *Session) DeleteStatusByID(id int) (tc.Alerts, ReqInf, error) {
+	route := fmt.Sprintf("%s/%d", API_v13_STATUSES, 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/status_test.go b/traffic_ops/testing/api/status_test.go
new file mode 100644
index 0000000000..a2ae9fce05
--- /dev/null
+++ b/traffic_ops/testing/api/status_test.go
@@ -0,0 +1,106 @@
+/*
+
+   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 (
+	"testing"
+
+	"github.com/apache/incubator-trafficcontrol/lib/go-log"
+	"github.com/apache/incubator-trafficcontrol/lib/go-tc"
+)
+
+func TestStatuses(t *testing.T) {
+
+	CreateTestStatuses(t)
+	UpdateTestStatuses(t)
+	GetTestStatuses(t)
+	DeleteTestStatuses(t)
+
+}
+
+func CreateTestStatuses(t *testing.T) {
+	for _, status := range testData.Statuses {
+		resp, _, err := TOSession.CreateStatus(status)
+		log.Debugln("Response: ", resp)
+		if err != nil {
+			t.Errorf("could not CREATE statuss: %v\n", err)
+		}
+	}
+
+}
+
+func UpdateTestStatuses(t *testing.T) {
+
+	firstStatus := testData.Statuses[0]
+	// Retrieve the Status by status so we can get the id for the Update
+	resp, _, err := TOSession.GetStatusByName(firstStatus.Name)
+	if err != nil {
+		t.Errorf("cannot GET Status by status: %v - %v\n", firstStatus.Name, err)
+	}
+	remoteStatus := resp[0]
+	expectedStatus := "OFFLINE-TEST"
+	remoteStatus.Name = expectedStatus
+	var alert tc.Alerts
+	alert, _, err = TOSession.UpdateStatusByID(remoteStatus.ID, remoteStatus)
+	if err != nil {
+		t.Errorf("cannot UPDATE Status by id: %v - %v\n", err, alert)
+	}
+
+	// Retrieve the Status to check status got updated
+	resp, _, err = TOSession.GetStatusByID(remoteStatus.ID)
+	if err != nil {
+		t.Errorf("cannot GET Status by status: %v - %v\n", firstStatus.Name, err)
+	}
+	respStatus := resp[0]
+	if respStatus.Name != expectedStatus {
+		t.Errorf("results do not match actual: %s, expected: %s\n", respStatus.Name, expectedStatus)
+	}
+
+}
+
+func GetTestStatuses(t *testing.T) {
+	for _, status := range testData.Statuses {
+		resp, _, err := TOSession.GetStatusByName(status.Name)
+		if err != nil {
+			t.Errorf("cannot GET Status by status: %v - %v\n", err, resp)
+		}
+	}
+}
+
+func DeleteTestStatuses(t *testing.T) {
+
+	status := testData.Statuses[1]
+	// Retrieve the Status by name so we can get the id
+	resp, _, err := TOSession.GetStatusByName(status.Name)
+	if err != nil {
+		t.Errorf("cannot GET Status by name: %v - %v\n", status.Name, err)
+	}
+	respStatus := resp[0]
+
+	delResp, _, err := TOSession.DeleteStatusByID(respStatus.ID)
+	if err != nil {
+		t.Errorf("cannot DELETE Status by status: %v - %v\n", err, delResp)
+	}
+
+	// Retrieve the Status to see if it got deleted
+	statusResp, _, err := TOSession.GetStatusByName(status.Name)
+	if err != nil {
+		t.Errorf("error deleting Status status: %s\n", err.Error())
+	}
+	if len(statusResp) > 0 {
+		t.Errorf("expected Status status: %s to be deleted\n", status.Name)
+	}
+}
diff --git a/traffic_ops/testing/api/tc-fixtures.json b/traffic_ops/testing/api/tc-fixtures.json
index 4154fa075d..2d3a65b6d8 100644
--- a/traffic_ops/testing/api/tc-fixtures.json
+++ b/traffic_ops/testing/api/tc-fixtures.json
@@ -7,8 +7,14 @@
         },
         {
             "asn": 2222,
-            "cachegroup": "cachegroup2",
-            "cachegroupId": 2
+            "cachegroupId": 2,
+            "cachegroupName": "cachegroup1",
+            "name": "asn1"
+        },
+        {
+            "asn": 3333,
+            "cachegroupName": "cachegroup3",
+            "name": "asn2"
         }
     ],
     "cachegroups": [
@@ -90,6 +96,32 @@
             "name": "region2"
         }
     ],
+    "statuses": [
+        {
+            "name": "OFFLINE",
+            "description": "Edge: Puts server in CCR config file in this state, but CCR will never route traffic to it. Mid: Server will not be included in parent.config files for its edge caches"
+        },
+        {
+            "name": "ONLINE",
+            "description": "Edge: Puts server in CCR config file in this state, and CCR will always route traffic to it. Mid: Server will be included in parent.config files for its edges"
+        },
+        {
+            "name": "REPORTED",
+            "description": "Edge: Puts server in CCR config file in this state, and CCR will adhere to the health protocol. Mid: N/A for now"
+        },
+        {
+            "name": "ADMIN_DOWN",
+            "description": "Temporary down. Edge: XMPP client will send status OFFLINE to CCR, otherwise similar to REPORTED. Mid: Server will not be included in parent.config files for its edge caches"
+        },
+        {
+            "name": "CCR_IGNORE",
+            "description": "Edge: 12M will not include caches in this state in CCR config files. Mid: N/A for now"
+        },
+        {
+            "name": "PRE_PROD",
+            "description": "Pre Production. Not active in any configuration."
+        }
+    ],
     "tenants": [
         {
             "active": true,
diff --git a/traffic_ops/testing/api/v13/traffic_control.go b/traffic_ops/testing/api/v13/traffic_control.go
index 68dae1c2c8..968b9e4aa4 100644
--- a/traffic_ops/testing/api/v13/traffic_control.go
+++ b/traffic_ops/testing/api/v13/traffic_control.go
@@ -19,7 +19,14 @@ import tcapi "github.com/apache/incubator-trafficcontrol/lib/go-tc"
 
 // TrafficControl - maps to the tc-fixtures.json file
 type TrafficControl struct {
+	ASNs                    []tc.ASN                       `json:"asns"`
 	CDNs                    []tcapi.CDN                    `json:"cdns"`
-	DeliveryServices        []tcapi.DeliveryService        `json:"deliveryservices"`
+	Cachegroups             []tc.CacheGroup                `json:"cachegroups"`
 	DeliveryServiceRequests []tcapi.DeliveryServiceRequest `json:"deliveryServiceRequests"`
+	DeliveryServices        []tc.DeliveryService           `json:"deliveryservices"`
+	DeliveryServices        []tcapi.DeliveryService        `json:"deliveryservices"`
+	Divisions               []tc.Division                  `json:"divisions"`
+	Regions                 []tc.Region                    `json:"regions"`
+	Statuses                []tc.Status                    `json:"statuses"`
+	Tenants                 []tc.Tenant                    `json:"tenants"`
 }
diff --git a/traffic_ops/traffic_ops_golang/routes.go b/traffic_ops/traffic_ops_golang/routes.go
index 58f2a54fc2..b59d92b3a6 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/status"
 	"github.com/basho/riak-go-client"
 )
 
@@ -94,6 +95,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},
 
+		//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},
+		{1.3, http.MethodPut, `statuses/{id}$`, api.UpdateHandler(status.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
+		{1.3, http.MethodPost, `statuses/?$`, api.CreateHandler(status.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
+		{1.3, http.MethodDelete, `statuses/{id}$`, api.DeleteHandler(status.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
+
 		//Divisions
 		{1.2, http.MethodGet, `divisions/?(\.json)?$`, api.ReadHandler(division.GetRefType(), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
 		{1.3, http.MethodGet, `divisions/{id}$`, api.ReadHandler(division.GetRefType(), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
@@ -125,9 +133,7 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
 		{1.2, http.MethodGet, `deliveryservices-wip/xmlId/{xmlID}/sslkeys$`, getDeliveryServiceSSLKeysByXMLIDHandler(d.DB, d.Config), auth.PrivLevelAdmin, Authenticated, nil},
 		{1.2, http.MethodGet, `deliveryservices-wip/hostname/{hostName}/sslkeys$`, getDeliveryServiceSSLKeysByHostNameHandler(d.DB, d.Config), auth.PrivLevelAdmin, Authenticated, nil},
 		{1.2, http.MethodPost, `deliveryservices-wip/hostname/{hostName}/sslkeys/add$`, addDeliveryServiceSSLKeysHandler(d.DB, d.Config), auth.PrivLevelAdmin, Authenticated, nil},
-		//Statuses
-		{1.2, http.MethodGet, `statuses/?(\.json)?$`, statusesHandler(d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
-		{1.2, http.MethodGet, `statuses/{id}$`, statusesHandler(d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
+
 		//System
 		{1.2, http.MethodGet, `system/info/?(\.json)?$`, systemInfoHandler(d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
 
diff --git a/traffic_ops/traffic_ops_golang/status/statuses.go b/traffic_ops/traffic_ops_golang/status/statuses.go
new file mode 100644
index 0000000000..d273e03c35
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/status/statuses.go
@@ -0,0 +1,321 @@
+package status
+
+/*
+ * 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 TOStatus tc.Status
+
+//the refType is passed into the handlers where a copy of its type is used to decode the json.
+var refType = TOStatus(tc.Status{})
+
+func GetRefType() *TOStatus {
+	return &refType
+}
+
+//Implementation of the Identifier, Validator interface functions
+func (status *TOStatus) GetID() (int, bool) {
+	return status.ID, true
+}
+
+func (status *TOStatus) GetAuditName() string {
+	return status.Name
+}
+
+func (status *TOStatus) GetType() string {
+	return "status"
+}
+
+func (status *TOStatus) SetID(i int) {
+	status.ID = i
+}
+
+func (status *TOStatus) Validate(db *sqlx.DB) []error {
+	errs := []error{}
+	if len(status.Name) < 1 {
+		errs = append(errs, errors.New(`Status 'name' is required.`))
+	}
+	return errs
+}
+
+func (status *TOStatus) 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{
+		"id":          dbhelpers.WhereColumnInfo{"id", api.IsInt},
+		"description": dbhelpers.WhereColumnInfo{"description", nil},
+		"name":        dbhelpers.WhereColumnInfo{"name", nil},
+	}
+	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 Status: %v", err)
+		return nil, []error{tc.DBError}, tc.SystemError
+	}
+	defer rows.Close()
+
+	st := []interface{}{}
+	for rows.Next() {
+		var s tc.Status
+		if err = rows.StructScan(&s); err != nil {
+			log.Errorf("error parsing Status rows: %v", err)
+			return nil, []error{tc.DBError}, tc.SystemError
+		}
+		st = append(st, s)
+	}
+
+	return st, []error{}, tc.NoError
+}
+
+func selectQuery() string {
+
+	query := `SELECT
+description,
+id,
+last_updated,
+name 
+
+FROM status s`
+	return query
+}
+
+//The TOStatus implementation of the Updater interface
+//all implementations of Updater should use transactions and return the proper errorType
+//ParsePQUniqueConstraintError is used to determine if a status 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 (status *TOStatus) 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 status: %++v", updateQuery(), status)
+	resultRows, err := tx.NamedQuery(updateQuery(), status)
+	if err != nil {
+		if pqErr, ok := err.(*pq.Error); ok {
+			err, eType := dbhelpers.ParsePQUniqueConstraintError(pqErr)
+			if eType == tc.DataConflictError {
+				return errors.New("a status 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)
+	status.LastUpdated = lastUpdated
+	if rowsAffected != 1 {
+		if rowsAffected < 1 {
+			return errors.New("no status 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 TOStatus implementation of the Creator interface
+//all implementations of Creator should use transactions and return the proper errorType
+//ParsePQUniqueConstraintError is used to determine if a status 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 status and have
+//to be added to the struct
+func (status *TOStatus) 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(), status)
+	if err != nil {
+		if pqErr, ok := err.(*pq.Error); ok {
+			err, eType := dbhelpers.ParsePQUniqueConstraintError(pqErr)
+			if eType == tc.DataConflictError {
+				return errors.New("a status 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 status 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 status insert")
+		log.Errorln(err)
+		return tc.DBError, tc.SystemError
+	}
+	status.SetID(id)
+	status.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 Status implementation of the Deleter interface
+//all implementations of Deleter should use transactions and return the proper errorType
+func (status *TOStatus) 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 status: %++v", deleteQuery(), status)
+	result, err := tx.NamedExec(deleteQuery(), status)
+	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 status 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
+status SET
+name=:name,
+description=:description
+WHERE id=:id RETURNING last_updated`
+	return query
+}
+
+func insertQuery() string {
+	query := `INSERT INTO status (
+name,
+description) VALUES (
+:name,
+:description) RETURNING id,last_updated`
+	return query
+}
+
+func deleteQuery() string {
+	query := `DELETE FROM status
+WHERE id=:id`
+	return query
+}
diff --git a/traffic_ops/traffic_ops_golang/statuses_test.go b/traffic_ops/traffic_ops_golang/status/statuses_test.go
similarity index 66%
rename from traffic_ops/traffic_ops_golang/statuses_test.go
rename to traffic_ops/traffic_ops_golang/status/statuses_test.go
index 6be62e6036..ca37fa412a 100644
--- a/traffic_ops/traffic_ops_golang/statuses_test.go
+++ b/traffic_ops/traffic_ops_golang/status/statuses_test.go
@@ -1,4 +1,4 @@
-package main
+package status
 
 /*
  * 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"
 
@@ -48,7 +50,7 @@ func getTestStatuses() []tc.Status {
 	return cdns
 }
 
-func TestGetStatus(t *testing.T) {
+func TestReadStatuses(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)
@@ -58,13 +60,13 @@ func TestGetStatus(t *testing.T) {
 	db := sqlx.NewDb(mockDB, "sqlmock")
 	defer db.Close()
 
-	testStatus := getTestStatuses()
+	refType := GetRefType()
+
+	testStatuses := getTestStatuses()
 	cols := test.ColsFromStructByTag("db", tc.Status{})
 	rows := sqlmock.NewRows(cols)
 
-	//TODO: drichardson - build helper to add these Rows from the struct values
-	//                    or by CSV if types get in the way
-	for _, ts := range testStatus {
+	for _, ts := range testStatuses {
 		rows = rows.AddRow(
 			ts.Description,
 			ts.ID,
@@ -75,25 +77,33 @@ func TestGetStatus(t *testing.T) {
 	mock.ExpectQuery("SELECT").WillReturnRows(rows)
 	v := map[string]string{"dsId": "1"}
 
-	servers, errs, errType := getStatuses(v, db)
+	statuses, errs, _ := refType.Read(db, v, auth.CurrentUser{})
 	if len(errs) > 0 {
-		t.Errorf("getStatus expected: no errors, actual: %v with error type: %s", errs, errType.String())
+		t.Errorf("status.Read expected: no errors, actual: %v", errs)
 	}
 
-	if len(servers) != 2 {
-		t.Errorf("getStatus expected: len(servers) == 1, actual: %v", len(servers))
+	if len(statuses) != 2 {
+		t.Errorf("status.Read expected: len(statuses) == 2, actual: %v", len(statuses))
 	}
-
 }
 
-type SortableStatuses []tc.Status
+func TestInterfaces(t *testing.T) {
+	var i interface{}
+	i = &TOStatus{}
 
-func (s SortableStatuses) Len() int {
-	return len(s)
-}
-func (s SortableStatuses) Swap(i, j int) {
-	s[i], s[j] = s[j], s[i]
-}
-func (s SortableStatuses) Less(i, j int) bool {
-	return s[i].Name < s[j].Name
+	if _, ok := i.(api.Creator); !ok {
+		t.Errorf("Status must be Creator")
+	}
+	if _, ok := i.(api.Reader); !ok {
+		t.Errorf("Status must be Reader")
+	}
+	if _, ok := i.(api.Updater); !ok {
+		t.Errorf("Status must be Updater")
+	}
+	if _, ok := i.(api.Deleter); !ok {
+		t.Errorf("Status must be Deleter")
+	}
+	if _, ok := i.(api.Identifier); !ok {
+		t.Errorf("Status must be Identifier")
+	}
 }
diff --git a/traffic_ops/traffic_ops_golang/statuses.go b/traffic_ops/traffic_ops_golang/statuses.go
deleted file mode 100644
index 03d38ff13e..0000000000
--- a/traffic_ops/traffic_ops_golang/statuses.go
+++ /dev/null
@@ -1,120 +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 statusesHandler(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 := getStatusesResponse(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 getStatusesResponse(params map[string]string, db *sqlx.DB) (*tc.StatusesResponse, []error, tc.ApiErrorType) {
-	cdns, errs, errType := getStatuses(params, db)
-	if len(errs) > 0 {
-		return nil, errs, errType
-	}
-
-	resp := tc.StatusesResponse{
-		Response: cdns,
-	}
-	return &resp, nil, tc.NoError
-}
-
-func getStatuses(params map[string]string, db *sqlx.DB) ([]tc.Status, []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
-	queryParamsToSQLCols := map[string]dbhelpers.WhereColumnInfo{
-		"id":          dbhelpers.WhereColumnInfo{"id", api.IsInt},
-		"name":        dbhelpers.WhereColumnInfo{"name", nil},
-		"description": dbhelpers.WhereColumnInfo{"description", nil},
-	}
-
-	where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(params, queryParamsToSQLCols)
-	if len(errs) > 0 {
-		return nil, errs, tc.DataConflictError
-	}
-
-	query := selectStatusesQuery() + 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()
-
-	statuses := []tc.Status{}
-	for rows.Next() {
-		var s tc.Status
-		if err = rows.StructScan(&s); err != nil {
-			return nil, []error{fmt.Errorf("getting statuses: %v", err)}, tc.SystemError
-		}
-		statuses = append(statuses, s)
-	}
-	return statuses, nil, tc.NoError
-}
-
-func selectStatusesQuery() string {
-
-	query := `SELECT
-description,
-id,
-last_updated,
-name 
-
-FROM status c`
-	return query
-}


 

----------------------------------------------------------------
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