This is an automated email from the ASF dual-hosted git repository.

mitchell852 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new bf5dfc7  TO: Cachegroup Bugfixes (#3450)
bf5dfc7 is described below

commit bf5dfc7467b485b8e168728ce0d0b1dd865d967c
Author: Matthew Allen Moltzau <[email protected]>
AuthorDate: Fri May 17 12:30:32 2019 -0600

    TO: Cachegroup Bugfixes (#3450)
    
    * Fix Cachegroups Bugs
    
    Affects Response (thus should affect documentation)
    * Made joined fields work for cachegroup (POST and PUT)
        - parentCachegroupName
        - secondaryParentCachegroupName
        - typeName
    * Made POST include lastUpdated in the response
        (it was mentioned in the documentation beforehand)
    * Made GET coalesce localization_methods to an empty array
    * Made POST and PUT have default values for array data
    * Made PUT have default value for fallback_to_closest to mimic POST's 
behavior
    * Renamed the cg type to cachegroup
    
    Fixes Response Codes
    * Made sure 404 works for cachegroup PUT
    * Fixed bug where delete did not recognize a 404 error
    * Fixed ISE when posting twice (apache#3090)
    * Fixed ISE on cachegroup delete when it has servers attached (Fixes 
apache#3401)
    
    Internal changes
    * Removed use of NamedQuery for cachegroup Update and Create
    * Removed use of NamedExec for cachegroup Delete
    * Removed dead code from handleCoordinateUpdate
    
    * Documentation Updates for Cachegroup
    
    * Check response to make sure certain fields aren't null
---
 docs/source/api/cachegroups.rst                    |  17 +-
 docs/source/api/cachegroups_id.rst                 |  21 +-
 traffic_ops/testing/api/v14/cachegroups_test.go    |  42 +++-
 traffic_ops/traffic_ops_golang/api/api.go          |   5 +-
 .../traffic_ops_golang/cachegroup/cachegroups.go   | 272 +++++++++++----------
 .../cachegroup/cachegroups_test.go                 |  16 +-
 6 files changed, 218 insertions(+), 155 deletions(-)

diff --git a/docs/source/api/cachegroups.rst b/docs/source/api/cachegroups.rst
index d07aac0..c64b6d7 100644
--- a/docs/source/api/cachegroups.rst
+++ b/docs/source/api/cachegroups.rst
@@ -91,8 +91,8 @@ Response Structure
                        "parentCachegroupId": 6,
                        "secondaryParentCachegroupName": null,
                        "secondaryParentCachegroupId": null,
-                       "fallbackToClosest": null,
-                       "localizationMethods": null,
+                       "fallbackToClosest": [],
+                       "localizationMethods": [],
                        "typeName": "EDGE_LOC",
                        "typeId": 23,
                        "lastUpdated": "2018-11-07 14:45:43+00"
@@ -115,8 +115,9 @@ Request Structure
        .. note:: The default value of ``fallbackToClosest`` is 'true', and if 
it is 'null' Traffic Control components will still interpret it as 'true'.
 
 :latitude:                    An optional field which, if present, will define 
the latitude for the :term:`Cache Group` to ISO-standard double specification\ 
[1]_
-:localizationMethods:         Array of enabled localization methods (as 
strings)
 :longitude:                   An optional field which, if present, will define 
the longitude for the :term:`Cache Group` to ISO-standard double specification\ 
[1]_
+:localizationMethods:         Array of enabled localization methods (as 
strings)
+:fallbacks:                   Array of fallback server hostnames.
 :name:                        The name of the :term:`Cache Group`
 :parentCachegroupId:          An optional field which, if present, should be 
an integral, unique identifier for this :term:`Cache Group`\ 's primary parent
 :secondaryParentCachegroupId: An optional field which, if present, should be 
an integral, unique identifier for this :term:`Cache Group`\ 's secondary parent
@@ -148,6 +149,7 @@ Request Structure
                "latitude": 0,
                "longitude": 0,
                "localizationMethods": [],
+               "fallbacks": [],
                "name": "test",
                "parentCachegroupId": 7,
                "shortName": "test",
@@ -161,6 +163,8 @@ Response Structure
 :lastUpdated:                   The time and date at which this entry was last 
updated in ISO format
 :latitude:                      Latitude for the :term:`Cache Group`
 :longitude:                     Longitude for the :term:`Cache Group`
+:localizationMethods:           Array of enabled localization methods (as 
strings)
+:fallbacks:                     Array of fallback server hostnames
 :name:                          The name of the :term:`Cache Group` entry
 :parentCachegroupId:            ID of this :term:`Cache Group`\ 's parent 
:term:`Cache Group` (if any)
 :parentCachegroupName:          Name of this :term:`Cache Group`\ 's parent 
:term:`Cache Group` (if any)
@@ -188,7 +192,7 @@ Response Structure
 
        { "alerts": [
                {
-                       "text": "cg was created.",
+                       "text": "cachegroup was created.",
                        "level": "success"
                }
        ],
@@ -198,13 +202,14 @@ Response Structure
                "shortName": "test",
                "latitude": 0,
                "longitude": 0,
-               "parentCachegroupName": null,
+               "parentCachegroupName": "CDN_in_a_Box_Mid",
                "parentCachegroupId": 7,
                "secondaryParentCachegroupName": null,
                "secondaryParentCachegroupId": null,
                "fallbackToClosest": false,
                "localizationMethods": [],
-               "typeName": null,
+               "fallbacks": [],
+               "typeName": "EDGE_LOC",
                "typeId": 23,
                "lastUpdated": "2018-11-07 22:11:50+00"
        }}
diff --git a/docs/source/api/cachegroups_id.rst 
b/docs/source/api/cachegroups_id.rst
index f79b715..e38490f 100644
--- a/docs/source/api/cachegroups_id.rst
+++ b/docs/source/api/cachegroups_id.rst
@@ -89,7 +89,7 @@ Response Structure
                        "parentCachegroupId": 6,
                        "secondaryParentCachegroupName": null,
                        "secondaryParentCachegroupId": null,
-                       "fallbackToClosest": null,
+                       "fallbackToClosest": [],
                        "localizationMethods": [
                                "DEEP_CZ",
                                "CZ"
@@ -197,7 +197,7 @@ Response Structure
 
        { "alerts": [
                {
-                       "text": "cg was updated.",
+                       "text": "cachegroup was updated.",
                        "level": "success"
                }
        ],
@@ -211,11 +211,11 @@ Response Structure
                "parentCachegroupId": null,
                "secondaryParentCachegroupName": null,
                "secondaryParentCachegroupId": null,
-               "fallbackToClosest": null,
+               "fallbackToClosest": [],
                "localizationMethods": [
                        "GEO"
                ],
-               "typeName": null,
+               "typeName": "EDGE_LOC",
                "typeId": 23,
                "lastUpdated": "2018-11-14 19:14:28+00"
        }}
@@ -240,9 +240,18 @@ Request Structure
        | ID           | The integral, unique identifier of a :term:`Cache 
Group` to be deleted |
        
+--------------+------------------------------------------------------------------------+
 
+.. code-block:: http
+       :caption: Request Example
+
+       DELETE /api/1.4/cachegroups/42 HTTP/1.1
+       Host: trafficops.infra.ciab.test
+       User-Agent: curl/7.47.0
+       Accept: */*
+       Cookie: mojolicious=...
+
 Response Structure
 ------------------
-.. code block:: http
+.. code-block:: http
        :caption: Response Example
 
        HTTP/1.1 200 OK
@@ -259,7 +268,7 @@ Response Structure
 
        { "alerts": [
                {
-                       "text": "cg was deleted.",
+                       "text": "cachegroup was deleted.",
                        "level": "success"
                }
        ]}
diff --git a/traffic_ops/testing/api/v14/cachegroups_test.go 
b/traffic_ops/testing/api/v14/cachegroups_test.go
index a5fa580..3c8dec4 100644
--- a/traffic_ops/testing/api/v14/cachegroups_test.go
+++ b/traffic_ops/testing/api/v14/cachegroups_test.go
@@ -32,11 +32,34 @@ func TestCacheGroups(t *testing.T) {
 }
 
 func CreateTestCacheGroups(t *testing.T) {
+
+       var err error
+       var resp *tc.CacheGroupDetailResponse
+
        for _, cg := range testData.CacheGroups {
-               _, _, err := TOSession.CreateCacheGroupNullable(cg)
+
+               resp, _, err = TOSession.CreateCacheGroupNullable(cg)
                if err != nil {
                        t.Errorf("could not CREATE cachegroups: %v, request: 
%v\n", err, cg)
                }
+
+               // Testing 'join' fields during create
+               if cg.ParentName != nil && resp.Response.ParentName == nil {
+                       t.Errorf("Parent cachegroup is null in response when it 
should have a value\n")
+               }
+               if cg.SecondaryParentName != nil && 
resp.Response.SecondaryParentName == nil {
+                       t.Error("Secondary parent cachegroup is null in 
response when it should have a value\n")
+               }
+               if cg.Type != nil && resp.Response.Type == nil {
+                       t.Error("Type is null in response when it should have a 
value\n")
+               }
+               if resp.Response.LocalizationMethods == nil {
+                       t.Errorf("Localization methods are null\n")
+               }
+               if resp.Response.Fallbacks == nil {
+                       t.Errorf("Fallbacks are null\n")
+               }
+
        }
 }
 
@@ -71,6 +94,23 @@ func UpdateTestCacheGroups(t *testing.T) {
                t.Errorf("cannot UPDATE CacheGroup by id: %v - %v\n", err, 
updResp)
        }
 
+       // Check response to make sure fields aren't null
+       if cg.ParentName != nil && updResp.Response.ParentName == nil {
+               t.Errorf("Parent cachegroup is null in response when it should 
have a value\n")
+       }
+       if cg.SecondaryParentName != nil && 
updResp.Response.SecondaryParentName == nil {
+               t.Error("Secondary parent cachegroup is null in response when 
it should have a value\n")
+       }
+       if cg.Type != nil && updResp.Response.Type == nil {
+               t.Error("Type is null in response when it should have a 
value\n")
+       }
+       if updResp.Response.LocalizationMethods == nil {
+               t.Errorf("Localization methods are null\n")
+       }
+       if updResp.Response.Fallbacks == nil {
+               t.Errorf("Fallbacks are null\n")
+       }
+
        // Retrieve the CacheGroup to check CacheGroup name got updated
        resp, _, err = TOSession.GetCacheGroupNullableByID(*cg.ID)
        if err != nil {
diff --git a/traffic_ops/traffic_ops_golang/api/api.go 
b/traffic_ops/traffic_ops_golang/api/api.go
index b103fb9..2251f13 100644
--- a/traffic_ops/traffic_ops_golang/api/api.go
+++ b/traffic_ops/traffic_ops_golang/api/api.go
@@ -507,7 +507,7 @@ func parseUniqueConstraint(err *pq.Error) (error, error, 
int) {
        if match == nil {
                return nil, nil, http.StatusOK
        }
-       return fmt.Errorf("%s %s already exists.", match[1], match[2]), nil, 
http.StatusBadRequest
+       return fmt.Errorf("%v %s '%s' already exists.", err.Table, match[1], 
match[2]), nil, http.StatusBadRequest
 }
 
 // parses pq errors for ON DELETE RESTRICT fk constraint violations
@@ -548,7 +548,8 @@ func ParseDBError(ierr error) (error, error, int) {
 
        err, ok := ierr.(*pq.Error)
        if !ok {
-               return nil, errors.New("database returned non pq error: " + 
err.Error()), http.StatusInternalServerError
+               log.Errorf("a non-pq error was given")
+               return nil, ierr, http.StatusInternalServerError
        }
 
        if usrErr, sysErr, errCode := parseNotNullConstraint(err); errCode != 
http.StatusOK {
diff --git a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go 
b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
index fd4b106..87b519a 100644
--- a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
+++ b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
@@ -78,7 +78,7 @@ func (cg TOCacheGroup) GetAuditName() string {
 }
 
 func (cg TOCacheGroup) GetType() string {
-       return "cg"
+       return "cachegroup"
 }
 
 func (cg *TOCacheGroup) SetID(i int) {
@@ -193,16 +193,16 @@ func (cg TOCacheGroup) Validate() error {
 }
 
 //The TOCacheGroup implementation of the Creator interface
-//all implementations of Creator should use transactions and return the proper 
errorType
-//ParsePQUniqueConstraintError is used to determine if a cachegroup 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 
cachegroup and have
 //to be added to the struct
 func (cg *TOCacheGroup) Create() (error, error, int) {
-       coordinateID, err := cg.createCoordinate()
-       if err != nil {
-               return nil, errors.New("cg create: creating coord:" + 
err.Error()), http.StatusInternalServerError
+
+       if cg.LocalizationMethods == nil {
+               cg.LocalizationMethods = &[]tc.LocalizationMethod{}
+       }
+
+       if cg.Fallbacks == nil {
+               cg.Fallbacks = &[]string{}
        }
 
        if cg.FallbackToClosest == nil {
@@ -210,45 +210,46 @@ func (cg *TOCacheGroup) Create() (error, error, int) {
                cg.FallbackToClosest = &fbc
        }
 
-       resultRows, err := cg.ReqInfo.Tx.Tx.Query(
-               insertQuery(),
+       err := cg.ReqInfo.Tx.Tx.QueryRow(
+               InsertQuery(),
                cg.Name,
                cg.ShortName,
-               coordinateID,
                cg.TypeID,
                cg.ParentCachegroupID,
                cg.SecondaryParentCachegroupID,
                cg.FallbackToClosest,
+       ).Scan(
+               &cg.ID,
+               &cg.Type,
+               &cg.ParentName,
+               &cg.SecondaryParentName,
        )
        if err != nil {
                return api.ParseDBError(err)
        }
-       defer resultRows.Close()
 
-       var id int
-       var lastUpdated tc.TimeNoMod
-       rowsAffected := 0
-       for resultRows.Next() {
-               rowsAffected++
-               if err := resultRows.Scan(&id, &lastUpdated); err != nil {
-                       return nil, errors.New("cg create scanning: " + 
err.Error()), http.StatusInternalServerError
-               }
+       coordinateID, err := cg.createCoordinate()
+       if err != nil {
+               return nil, errors.New("cachegroup create: creating coord:" + 
err.Error()), http.StatusInternalServerError
        }
-       if rowsAffected == 0 {
-               return nil, errors.New("cg create: no rows returned"), 
http.StatusInternalServerError
-       } else if rowsAffected > 1 {
-               return nil, errors.New("cg create: multiple rows returned"), 
http.StatusInternalServerError
+
+       err = cg.ReqInfo.Tx.Tx.QueryRow(
+               `UPDATE cachegroup SET coordinate=$1 WHERE id=$2 RETURNING 
last_updated`,
+               coordinateID,
+               cg.ID,
+       ).Scan(&cg.LastUpdated)
+       if err != nil {
+               return nil, fmt.Errorf("followup update during cachegroup 
create: %v", err), http.StatusInternalServerError
        }
-       cg.SetID(id)
+
        if err = cg.createLocalizationMethods(); err != nil {
-               return nil, errors.New("cg create: creating localization 
methods: " + err.Error()), http.StatusInternalServerError
+               return nil, errors.New("creating cachegroup: creating 
localization methods: " + err.Error()), http.StatusInternalServerError
        }
 
        if err = cg.createCacheGroupFallbacks(); err != nil {
-               return nil, errors.New("cg create: creating cache group 
fallbacks: " + err.Error()), http.StatusInternalServerError
+               return nil, errors.New("creating cachegroup: creating cache 
group fallbacks: " + err.Error()), http.StatusInternalServerError
        }
 
-       cg.LastUpdated = &lastUpdated
        return nil, nil, http.StatusOK
 }
 
@@ -325,7 +326,7 @@ func (cg *TOCacheGroup) createCoordinate() (*int, error) {
        if cg.Latitude != nil && cg.Longitude != nil {
                q := `INSERT INTO coordinate (name, latitude, longitude) VALUES 
($1, $2, $3) RETURNING id`
                if err := cg.ReqInfo.Tx.Tx.QueryRow(q, 
tc.CachegroupCoordinateNamePrefix+*cg.Name, *cg.Latitude, 
*cg.Longitude).Scan(&coordinateID); err != nil {
-                       return nil, fmt.Errorf("insert coordinate for cg '%s': 
%s", *cg.Name, err.Error())
+                       return nil, err
                }
        }
        return coordinateID, nil
@@ -336,14 +337,14 @@ func (cg *TOCacheGroup) updateCoordinate() error {
                q := `UPDATE coordinate SET name = $1, latitude = $2, longitude 
= $3 WHERE id = (SELECT coordinate FROM cachegroup WHERE id = $4)`
                result, err := cg.ReqInfo.Tx.Tx.Exec(q, 
tc.CachegroupCoordinateNamePrefix+*cg.Name, *cg.Latitude, *cg.Longitude, *cg.ID)
                if err != nil {
-                       return fmt.Errorf("update coordinate for cg '%s': %s", 
*cg.Name, err.Error())
+                       return fmt.Errorf("updating coordinate for cachegroup 
'%s': %s", *cg.Name, err.Error())
                }
                rowsAffected, err := result.RowsAffected()
                if err != nil {
-                       return fmt.Errorf("update coordinate for cg '%s', 
getting rows affected: %s", *cg.Name, err.Error())
+                       return fmt.Errorf("updating coordinate for cachegroup 
'%s', getting rows affected: %s", *cg.Name, err.Error())
                }
                if rowsAffected == 0 {
-                       return fmt.Errorf("update coordinate for cg '%s', zero 
rows affected", *cg.Name)
+                       return fmt.Errorf("updating coordinate for cachegroup 
'%s', zero rows affected", *cg.Name)
                }
        }
        return nil
@@ -353,27 +354,27 @@ func (cg *TOCacheGroup) deleteCoordinate(coordinateID 
int) error {
        q := `UPDATE cachegroup SET coordinate = NULL WHERE id = $1`
        result, err := cg.ReqInfo.Tx.Tx.Exec(q, *cg.ID)
        if err != nil {
-               return fmt.Errorf("updating cg %d coordinate to null: %s", 
*cg.ID, err.Error())
+               return fmt.Errorf("updating cachegroup %d coordinate to null: 
%s", *cg.ID, err.Error())
        }
        rowsAffected, err := result.RowsAffected()
        if err != nil {
-               return fmt.Errorf("updating cg %d coordinate to null, getting 
rows affected: %s", *cg.ID, err.Error())
+               return fmt.Errorf("updating cachegroup %d coordinate to null, 
getting rows affected: %s", *cg.ID, err.Error())
        }
        if rowsAffected == 0 {
-               return fmt.Errorf("updating cg %d coordinate to null, zero rows 
affected", *cg.ID)
+               return fmt.Errorf("updating cachegroup %d coordinate to null, 
zero rows affected", *cg.ID)
        }
 
        q = `DELETE FROM coordinate WHERE id = $1`
        result, err = cg.ReqInfo.Tx.Tx.Exec(q, coordinateID)
        if err != nil {
-               return fmt.Errorf("delete coordinate %d for cg %d: %s", 
coordinateID, *cg.ID, err.Error())
+               return fmt.Errorf("delete coordinate %d for cachegroup %d: %s", 
coordinateID, *cg.ID, err.Error())
        }
        rowsAffected, err = result.RowsAffected()
        if err != nil {
-               return fmt.Errorf("delete coordinate %d for cg %d, getting rows 
affected: %s", coordinateID, *cg.ID, err.Error())
+               return fmt.Errorf("delete coordinate %d for cachegroup %d, 
getting rows affected: %s", coordinateID, *cg.ID, err.Error())
        }
        if rowsAffected == 0 {
-               return fmt.Errorf("delete coordinate %d for cg %d, zero rows 
affected", coordinateID, *cg.ID)
+               return fmt.Errorf("delete coordinate %d for cachegroup %d, zero 
rows affected", coordinateID, *cg.ID)
        }
        return nil
 }
@@ -392,12 +393,10 @@ func (cg *TOCacheGroup) Read() ([]interface{}, error, 
error, int) {
                return nil, util.JoinErrs(errs), nil, http.StatusBadRequest
        }
 
-       query := selectQuery() + where + orderBy
-       log.Debugln("Query is ", query)
-
+       query := SelectQuery() + where + orderBy
        rows, err := cg.ReqInfo.Tx.NamedQuery(query, queryValues)
        if err != nil {
-               return nil, nil, errors.New("cg read: querying: " + 
err.Error()), http.StatusInternalServerError
+               return nil, nil, errors.New("cachegroup read: querying: " + 
err.Error()), http.StatusInternalServerError
        }
        defer rows.Close()
 
@@ -423,7 +422,7 @@ func (cg *TOCacheGroup) Read() ([]interface{}, error, 
error, int) {
                        pq.Array(&cgfs),
                        &s.FallbackToClosest,
                ); err != nil {
-                       return nil, nil, errors.New("cg read: scanning: " + 
err.Error()), http.StatusInternalServerError
+                       return nil, nil, errors.New("cachegroup read: scanning: 
" + err.Error()), http.StatusInternalServerError
                }
                s.LocalizationMethods = &lms
                s.Fallbacks = &cgfs
@@ -434,18 +433,28 @@ func (cg *TOCacheGroup) Read() ([]interface{}, error, 
error, int) {
 }
 
 //The TOCacheGroup implementation of the Updater interface
-//all implementations of Updater should use transactions and return the proper 
errorType
-//ParsePQUniqueConstraintError is used to determine if a cachegroup 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 (cg *TOCacheGroup) Update() (error, error, int) {
+
+       if cg.LocalizationMethods == nil {
+               cg.LocalizationMethods = &[]tc.LocalizationMethod{}
+       }
+
+       if cg.Fallbacks == nil {
+               cg.Fallbacks = &[]string{}
+       }
+
+       if cg.FallbackToClosest == nil {
+               fbc := true
+               cg.FallbackToClosest = &fbc
+       }
+
        coordinateID, err, errType := cg.handleCoordinateUpdate()
        if err != nil {
                return api.TypeErrToAPIErr(err, errType)
        }
 
-       resultRows, err := cg.ReqInfo.Tx.Tx.Query(
-               updateQuery(),
+       err = cg.ReqInfo.Tx.Tx.QueryRow(
+               UpdateQuery(),
                cg.Name,
                cg.ShortName,
                coordinateID,
@@ -454,67 +463,65 @@ func (cg *TOCacheGroup) Update() (error, error, int) {
                cg.TypeID,
                cg.FallbackToClosest,
                cg.ID,
+       ).Scan(
+               &cg.Type,
+               &cg.ParentName,
+               &cg.SecondaryParentName,
+               &cg.LastUpdated,
        )
        if err != nil {
                return api.ParseDBError(err)
        }
-       defer resultRows.Close()
 
-       var lastUpdated tc.TimeNoMod
-       rowsAffected := 0
-       for resultRows.Next() {
-               rowsAffected++
-               if err := resultRows.Scan(&lastUpdated); err != nil {
-                       return nil, errors.New("cg update: scanning: " + 
err.Error()), http.StatusInternalServerError
-               }
-       }
-       log.Debugf("lastUpdated: %++v", lastUpdated)
-       cg.LastUpdated = &lastUpdated
-       if rowsAffected != 1 {
-               if rowsAffected < 1 {
-                       return nil, nil, http.StatusNotFound
-               } else {
-                       return nil, errors.New("cg update: affected multiple 
rows"), http.StatusInternalServerError
-               }
-       }
        if err = cg.createLocalizationMethods(); err != nil {
-               return nil, errors.New("cg update: creating localization 
methods: " + err.Error()), http.StatusInternalServerError
+               return nil, errors.New("cachegroup update: creating 
localization methods: " + err.Error()), http.StatusInternalServerError
        }
 
        if err = cg.createCacheGroupFallbacks(); err != nil {
-               return nil, errors.New("cg create: creating cache group 
fallbacks: " + err.Error()), http.StatusInternalServerError
+               return nil, errors.New("cachegroup update: creating cache group 
fallbacks: " + err.Error()), http.StatusInternalServerError
        }
 
        return nil, nil, http.StatusOK
 }
 
+// TODO: Remove tc.ApiErrorType (see #3349)
 func (cg *TOCacheGroup) handleCoordinateUpdate() (*int, error, 
tc.ApiErrorType) {
+
        coordinateID, err := cg.getCoordinateID()
+
+       // This is not a logic error. Because the coordinate id is recieved 
from the
+       // cachegroup table, not being able to find the coordinate is 
equivalent to
+       // not being able to find the cachegroup.
+       if err == sql.ErrNoRows {
+               return nil, fmt.Errorf("no cachegroup with id %d found", 
*cg.ID), tc.DataMissingError
+       }
        if err != nil {
-               if err == sql.ErrNoRows {
-                       return nil, fmt.Errorf("no cg with id %d found", 
*cg.ID), tc.DataMissingError
-               }
-               log.Errorf("updating cg %d got error when querying coordinate: 
%s\n", *cg.ID, err)
                return nil, tc.DBError, tc.SystemError
        }
-       if coordinateID == nil && cg.Latitude != nil && cg.Longitude != nil {
-               newCoordinateID, err := cg.createCoordinate()
-               if err != nil {
-                       log.Errorf("updating cg %d: %s\n", *cg.ID, err)
-                       return nil, tc.DBError, tc.SystemError
-               }
-               coordinateID = newCoordinateID
-       } else if coordinateID != nil && (cg.Latitude == nil || cg.Longitude == 
nil) {
+
+       // If partial coordinate information is given or the coordinate 
information is wholly
+       // nullified, it is invalid and we zero the reference to the coordinate 
in the database.
+       // For now I am also nullifying both the longitude and latitude if one 
of them is nil
+       // because a non-nil returned value would have no meaning.
+       //
+       // TODO: Find references that talk about longitude and latidude as 
required fields
+       //      - Making longitude and latitude required would prevent this odd 
case
+       //      - Longitude and latitude probably aren't required for 
versioning reasons
+       //      - We've recently had a discussion on versioning that may be 
related
+       // TODO: In the meantime should an error be returned for partial 
coordinate information?
+       //      - Probably not
+       //
+       if cg.Latitude == nil || cg.Longitude == nil {
                if err = cg.deleteCoordinate(*coordinateID); err != nil {
-                       log.Errorf("updating cg %d: %s\n", *cg.ID, err)
-                       return nil, tc.DBError, tc.SystemError
-               }
-               coordinateID = nil
-       } else {
-               if err = cg.updateCoordinate(); err != nil {
-                       log.Errorf("updating cg %d: %s\n", *cg.ID, err)
                        return nil, tc.DBError, tc.SystemError
                }
+               cg.Latitude = nil
+               cg.Longitude = nil
+               return nil, nil, tc.NoError
+       }
+
+       if err = cg.updateCoordinate(); err != nil {
+               return nil, tc.DBError, tc.SystemError
        }
        return coordinateID, nil, tc.NoError
 }
@@ -532,74 +539,73 @@ func (cg *TOCacheGroup) getCoordinateID() (*int, error) {
 //all implementations of Deleter should use transactions and return the proper 
errorType
 func (cg *TOCacheGroup) Delete() (error, error, int) {
        inUse, err := isUsed(cg.ReqInfo.Tx, *cg.ID)
-       if err != nil {
-               return nil, errors.New("cg delete: checking use: " + 
err.Error()), http.StatusInternalServerError
+       if inUse {
+               return err, nil, http.StatusBadRequest
        }
-       if inUse == true {
-               return errors.New("cannot delete cachegroup in use"), nil, 
http.StatusInternalServerError
+       if err != nil {
+               return nil, errors.New("cachegroup delete: checking use: " + 
err.Error()), http.StatusInternalServerError
        }
 
        coordinateID, err := cg.getCoordinateID()
+       if err == sql.ErrNoRows {
+               return errors.New("no cachegroup with that id found"), nil, 
http.StatusNotFound
+       }
        if err != nil {
-               if err == sql.ErrNoRows {
-                       return nil, nil, http.StatusNotFound
-               }
-               return nil, errors.New("cg delete: getting coord: " + 
err.Error()), http.StatusInternalServerError
+               return nil, errors.New("cachegroup delete: getting coord: " + 
err.Error()), http.StatusInternalServerError
        }
 
-       if coordinateID != nil {
-               if err = cg.deleteCoordinate(*coordinateID); err != nil {
-                       return nil, errors.New("cg delete: deleting coord: " + 
err.Error()), http.StatusInternalServerError
-               }
+       if err = cg.deleteCoordinate(*coordinateID); err != nil {
+               return nil, errors.New("cachegroup delete: deleting coord: " + 
err.Error()), http.StatusInternalServerError
        }
 
-       result, err := cg.ReqInfo.Tx.NamedExec(deleteQuery(), cg)
+       result, err := cg.ReqInfo.Tx.Exec(DeleteQuery(), *cg.ID)
        if err != nil {
-               return nil, errors.New("cg delete querying: " + err.Error()), 
http.StatusInternalServerError
+               return nil, errors.New("cachegroup delete: " + err.Error()), 
http.StatusInternalServerError
        }
+
        rowsAffected, err := result.RowsAffected()
        if err != nil {
-               return nil, errors.New("cg delete getting rows affected: " + 
err.Error()), http.StatusInternalServerError
+               return nil, fmt.Errorf("getting rows affected: %v", err), 
http.StatusInternalServerError
        }
+
+       // In the zero case, either err != nil occurs (from the Exec) or we got 
sql.ErrNoRows above
        if rowsAffected != 1 {
-               if rowsAffected < 1 {
-                       return nil, nil, http.StatusNotFound
-               } else {
-                       return nil, errors.New("cg delete affected multiple 
rows"), http.StatusInternalServerError
-               }
+               return nil, errors.New("cachegroup delete affected multiple 
rows"), http.StatusInternalServerError
        }
 
        return nil, nil, http.StatusOK
 }
 
-// insert query
-func insertQuery() string {
-       query := `INSERT INTO cachegroup (
+func InsertQuery() string {
+       return `INSERT INTO cachegroup (
 name,
 short_name,
-coordinate,
 type,
 parent_cachegroup_id,
 secondary_parent_cachegroup_id,
 fallback_to_closest
-) VALUES($1,$2,$3,$4,$5,$6,$7)
-RETURNING id,last_updated`
-       return query
+) VALUES($1,$2,$3,$4,$5,$6)
+RETURNING
+id,
+(SELECT name FROM type WHERE cachegroup.type = type.id),
+(SELECT name FROM cachegroup parent
+       WHERE cachegroup.parent_cachegroup_id = parent.id),
+(SELECT name FROM cachegroup secondary_parent
+       WHERE cachegroup.secondary_parent_cachegroup_id = secondary_parent.id)`
 }
 
-// select query
-func selectQuery() string {
+func SelectQuery() string {
        // the 'type_name' and 'type_id' aliases on the 'type.name'
        // and cachegroup.type' fields are needed
        // to disambiguate the struct scan, see also the
        // tc.CacheGroupNullable struct 'db' metadata
-       query := `SELECT
+       return `SELECT
 cachegroup.id,
 cachegroup.name,
 cachegroup.short_name,
 coordinate.latitude,
 coordinate.longitude,
-(SELECT array_agg(CAST(method as text)) AS localization_methods FROM 
cachegroup_localization_method clm WHERE clm.cachegroup = cachegroup.id),
+(SELECT COALESCE(array_agg(CAST(method as text)), '{}') AS 
localization_methods FROM cachegroup_localization_method clm WHERE 
clm.cachegroup = cachegroup.id),
 cachegroup.parent_cachegroup_id,
 cgp.name AS parent_cachegroup_name,
 cachegroup.secondary_parent_cachegroup_id,
@@ -607,28 +613,26 @@ cgs.name AS secondary_parent_cachegroup_name,
 type.name AS type_name,
 cachegroup.type AS type_id,
 cachegroup.last_updated,
-(SELECT coalesce(array_agg(CAST(cg2.name as text) ORDER BY cgf.set_order ASC), 
'{}') AS fallbacks FROM cachegroup cg2 INNER JOIN cachegroup_fallbacks cgf ON 
cgf.backup_cg = cg2.id WHERE cgf.primary_cg = cachegroup.id),
+(SELECT COALESCE(array_agg(CAST(cg2.name as text) ORDER BY cgf.set_order ASC), 
'{}') AS fallbacks FROM cachegroup cg2 INNER JOIN cachegroup_fallbacks cgf ON 
cgf.backup_cg = cg2.id WHERE cgf.primary_cg = cachegroup.id),
 cachegroup.fallback_to_closest
 FROM cachegroup
 LEFT JOIN coordinate ON coordinate.id = cachegroup.coordinate
 INNER JOIN type ON cachegroup.type = type.id
 LEFT JOIN cachegroup AS cgp ON cachegroup.parent_cachegroup_id = cgp.id
 LEFT JOIN cachegroup AS cgs ON cachegroup.secondary_parent_cachegroup_id = 
cgs.id`
-       return query
 }
 
+// unused?
 // select type name so checks are based on name instead of id
 func selectTypeNameQuery() string {
-       query := `SELECT name FROM type WHERE id = $1;`
-       return query
+       return `SELECT name FROM type WHERE id = $1;`
 }
 
-// update query
-func updateQuery() string {
+func UpdateQuery() string {
        // to disambiguate struct scans, the named
        // parameter 'type_id' is an alias to cachegroup.type
        //see also the tc.CacheGroupNullable struct 'db' metadata
-       query := `UPDATE
+       return `UPDATE
 cachegroup SET
 name=$1,
 short_name=$2,
@@ -637,12 +641,16 @@ parent_cachegroup_id=$4,
 secondary_parent_cachegroup_id=$5,
 type=$6,
 fallback_to_closest=$7
-WHERE id=$8 RETURNING last_updated`
-       return query
+WHERE id=$8
+RETURNING
+(SELECT name FROM type WHERE cachegroup.type = type.id),
+(SELECT name FROM cachegroup parent
+       WHERE cachegroup.parent_cachegroup_id = parent.id),
+(SELECT name FROM cachegroup secondary_parent
+       WHERE cachegroup.secondary_parent_cachegroup_id = secondary_parent.id),
+last_updated`
 }
 
-//delete query
-func deleteQuery() string {
-       query := `DELETE FROM cachegroup WHERE id=:id`
-       return query
+func DeleteQuery() string {
+       return `DELETE FROM cachegroup WHERE id=$1`
 }
diff --git a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups_test.go 
b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups_test.go
index 972605c..480e39f 100644
--- a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups_test.go
+++ b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups_test.go
@@ -153,17 +153,17 @@ func TestReadCacheGroups(t *testing.T) {
 }
 
 func TestFuncs(t *testing.T) {
-       if strings.Index(selectQuery(), "SELECT") != 0 {
-               t.Errorf("expected selectQuery to start with SELECT")
+       if strings.Index(SelectQuery(), "SELECT") != 0 {
+               t.Errorf("expected SelectQuery to start with SELECT")
        }
-       if strings.Index(insertQuery(), "INSERT") != 0 {
-               t.Errorf("expected insertQuery to start with INSERT")
+       if strings.Index(InsertQuery(), "INSERT") != 0 {
+               t.Errorf("expected InsertQuery to start with INSERT")
        }
-       if strings.Index(updateQuery(), "UPDATE") != 0 {
-               t.Errorf("expected updateQuery to start with UPDATE")
+       if strings.Index(UpdateQuery(), "UPDATE") != 0 {
+               t.Errorf("expected UpdateQuery to start with UPDATE")
        }
-       if strings.Index(deleteQuery(), "DELETE") != 0 {
-               t.Errorf("expected deleteQuery to start with DELETE")
+       if strings.Index(DeleteQuery(), "DELETE") != 0 {
+               t.Errorf("expected DeleteQuery to start with DELETE")
        }
 }
 

Reply via email to