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")
}
}