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 ef3fb6fa1737d001f211e93122ec2a61092bbeb3
Author: Dewayne Richardson <dewr...@apache.org>
AuthorDate: Mon Feb 19 13:30:02 2018 -0700

    updated to implement the CUD for statuses
---
 traffic_ops/client/status.go                      | 132 ++++++++++++
 traffic_ops/testing/api/status_test.go            | 108 ++++++++++
 traffic_ops/testing/api/v13/traffic_control.go    |   9 +-
 traffic_ops/traffic_ops_golang/status/statuses.go | 240 +++++++++++++++++++++-
 4 files changed, 484 insertions(+), 5 deletions(-)

diff --git a/traffic_ops/client/status.go b/traffic_ops/client/status.go
new file mode 100644
index 0000000..1145d86
--- /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 0000000..994c361
--- /dev/null
+++ b/traffic_ops/testing/api/status_test.go
@@ -0,0 +1,108 @@
+/*
+
+   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 TestStatuses(t *testing.T) {
+
+       CreateTestStatuses(t)
+       UpdateTestStatuses(t)
+       GetTestStatuses(t)
+       DeleteTestStatuses(t)
+
+}
+
+func CreateTestStatuses(t *testing.T) {
+       for _, status := range testData.Statuses {
+               fmt.Printf("status ---> %v\n", status)
+               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/v13/traffic_control.go 
b/traffic_ops/testing/api/v13/traffic_control.go
index 68dae1c..968b9e4 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/status/statuses.go 
b/traffic_ops/traffic_ops_golang/status/statuses.go
index a281a7d..8aca45c 100644
--- a/traffic_ops/traffic_ops_golang/status/statuses.go
+++ b/traffic_ops/traffic_ops_golang/status/statuses.go
@@ -20,12 +20,16 @@ package status
  */
 
 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
@@ -38,7 +42,32 @@ func GetRefType() *TOStatus {
        return &refType
 }
 
-func (cdn *TOStatus) Read(db *sqlx.DB, parameters map[string]string, user 
auth.CurrentUser) ([]interface{}, []error, tc.ApiErrorType) {
+//Implementation of the Identifier, Validator interface functions
+func (status *TOStatus) GetID() int {
+       return status.ID
+}
+
+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
@@ -63,17 +92,17 @@ func (cdn *TOStatus) Read(db *sqlx.DB, parameters 
map[string]string, user auth.C
        }
        defer rows.Close()
 
-       status := []interface{}{}
+       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
                }
-               status = append(status, s)
+               st = append(st, s)
        }
 
-       return status, []error{}, tc.NoError
+       return st, []error{}, tc.NoError
 }
 
 func selectQuery() string {
@@ -87,3 +116,206 @@ 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 Inserter interface
+//all implementations of Inserter 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) 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(), 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
+}

-- 
To stop receiving notification emails like this one, please contact
dang...@apache.org.

Reply via email to