This is an automated email from the ASF dual-hosted git repository.
shamrick 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 2a43682512 Cachegroups rfc3339 apiv5 (#7605)
2a43682512 is described below
commit 2a43682512eaf0004b5ff8581fff0b6293f96b92
Author: Kurtis Michie <[email protected]>
AuthorDate: Tue Aug 1 13:14:19 2023 -0600
Cachegroups rfc3339 apiv5 (#7605)
* Changed non-rfc-datetime to rfc:3339 date formats for v5
* Added V5 rfc:339 structs to cachegroup.go and updated corresponding V5
test file to test
* Updated V5 traffic_control_test.go to test updated V5 cachegroup api
* Changed V5 Client and its subsidiaries to use new V5 api
* Changed routes.go V5 handlers to use new API functions in cachegroups.go
* Created new API functions for V5
* Applied changes for merge conflict in db_helpers.go
* Applied changes for merge conflict in db_helpers_test.go
* Arranged imports
* Rolled back "Date: ..." It is not needed
* Changelog addition
* Corrected insert statement for CreateCacheGroup function
* Implemented additional checks and missing value populators for V5 Create,
Update, and Delete API functions
* Updated V5 Get call to be appropriate. Code not done yet.
* Debugging and fixing test failures
* Fixed api test failures. Ready for review
* Fixed missing bracket from merge
* Resolved PR change requests
* Resolved PR change requests
---
CHANGELOG.md | 1 +
docs/source/api/v5/cachegroups.rst | 10 +-
docs/source/api/v5/cachegroups_id.rst | 6 +-
lib/go-tc/cachegroups.go | 108 +++-
traffic_ops/testing/api/v5/cachegroups_test.go | 20 +-
traffic_ops/testing/api/v5/cdn_locks_test.go | 2 +-
traffic_ops/testing/api/v5/traffic_control_test.go | 2 +-
.../traffic_ops_golang/cachegroup/cachegroups.go | 548 ++++++++++++++++++++-
.../traffic_ops_golang/dbhelpers/db_helpers.go | 57 +++
.../dbhelpers/db_helpers_test.go | 51 ++
traffic_ops/traffic_ops_golang/routing/routes.go | 8 +-
traffic_ops/v5-client/cachegroup.go | 12 +-
12 files changed, 792 insertions(+), 33 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b8f9a07712..48d38885cd 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -66,6 +66,7 @@ The format is based on [Keep a
Changelog](http://keepachangelog.com/en/1.0.0/).
- [#7469](https://github.com/apache/trafficcontrol/pull/7469) *Traffic Ops*
Changed logic to not report empty or missing cookies into TO error.log.
- [#7586](https://github.com/apache/trafficcontrol/pull/7586) *Traffic Ops*
Add permission to Operations Role to read from dnsseckeys endpoint.
- [#7600](https://github.com/apache/trafficcontrol/pull/7600) *t3c* changed
default go-direct command line arg to be old to avoid unexpected config changes
upon upgrade.
+- [##7605](https://github.com/apache/trafficcontrol/pull/#7605) *Traffic Ops*
Fixes `cachegroups_request_comments` v5 apis to respond with `RFC3339`
date/time Format.
- [#7621](https://github.com/apache/trafficcontrol/pull/7621) *Traffic Ops*
Use ID token for OAuth authentication, not Access Token
### Fixed
diff --git a/docs/source/api/v5/cachegroups.rst
b/docs/source/api/v5/cachegroups.rst
index 5c56b51f74..24e113a333 100644
--- a/docs/source/api/v5/cachegroups.rst
+++ b/docs/source/api/v5/cachegroups.rst
@@ -71,7 +71,7 @@ Response Structure
:fallbacks: An array of strings that are :ref:`Cache Group
names <cache-group-name>` that are registered as :ref:`cache-group-fallbacks`
for this :term:`Cache Group`\ [#fallbacks]_
:fallbackToClosest: A boolean value that defines the
:ref:`cache-group-fallback-to-closest` behavior of this :term:`Cache Group`\
[#fallbacks]_
:id: An integer that is the :ref:`cache-group-id`
of the :term:`Cache Group`
-:lastUpdated: The time and date at which this entry was last
updated in :ref:`non-rfc-datetime`
+:lastUpdated: The time and date at which this entry was last
updated in :rfc:`3339`
:latitude: A floating-point :ref:`cache-group-latitude`
for the :term:`Cache Group`
:localizationMethods: An array of
:ref:`cache-group-localization-methods` as strings
:longitude: A floating-point :ref:`cache-group-longitude`
for the :term:`Cache Group`
@@ -116,7 +116,7 @@ Response Structure
"localizationMethods": [],
"typeName": "EDGE_LOC",
"typeId": 23,
- "lastUpdated": "2018-11-07 14:45:43+00",
+ "lastUpdated": "2023-05-30T19:52:58.183642+00:00",
"fallbacks": []
}
]}
@@ -182,7 +182,7 @@ Response Structure
:fallbacks: An array of strings that are :ref:`Cache Group
names <cache-group-name>` that are registered as :ref:`cache-group-fallbacks`
for this :term:`Cache Group`\ [#fallbacks]_
:fallbackToClosest: A boolean value that defines the
:ref:`cache-group-fallback-to-closest` behavior of this :term:`Cache Group`\
[#fallbacks]_
:id: An integer that is the :ref:`cache-group-id`
of the :term:`Cache Group`
-:lastUpdated: The time and date at which this entry was last
updated in :ref:`non-rfc-datetime`
+:lastUpdated: The time and date at which this entry was last
updated in :rfc:`3339`
:latitude: A floating-point :ref:`cache-group-latitude`
for the :term:`Cache Group`
:localizationMethods: An array of
:ref:`cache-group-localization-methods` as strings
:longitude: A floating-point :ref:`cache-group-longitude`
for the :term:`Cache Group`
@@ -207,7 +207,7 @@ Response Structure
Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54
GMT; Max-Age=3600; HttpOnly
Whole-Content-Sha512:
YvZlh3rpfl3nBq6SbNVhbkt3IvckbB9amqGW2JhLxWK9K3cxjBq5J2sIHBUhrLKUhE9afpxtvaYrLRxjt1/YMQ==
X-Server-Name: traffic_ops_golang/
- Date: Wed, 07 Nov 2018 22:11:50 GMT
+ Date: Wed, 07 Nov 2018 19:46:36 GMT
Content-Length: 379
{ "alerts": [
@@ -234,7 +234,7 @@ Response Structure
],
"typeName": "EDGE_LOC",
"typeId": 23,
- "lastUpdated": "2019-12-02 22:21:08+00",
+ "lastUpdated": "2023-05-30T19:52:58.183642+00:00",
"fallbacks": []
}}
diff --git a/docs/source/api/v5/cachegroups_id.rst
b/docs/source/api/v5/cachegroups_id.rst
index eb14b0df63..3f7cdcb78d 100644
--- a/docs/source/api/v5/cachegroups_id.rst
+++ b/docs/source/api/v5/cachegroups_id.rst
@@ -84,7 +84,7 @@ Response Structure
:fallbacks: An array of strings that are :ref:`Cache Group
names <cache-group-name>` that are registered as :ref:`cache-group-fallbacks`
for this :term:`Cache Group`\ [#fallbacks]_
:fallbackToClosest: A boolean value that defines the
:ref:`cache-group-fallback-to-closest` behavior of this :term:`Cache Group`\
[#fallbacks]_
:id: An integer that is the :ref:`cache-group-id`
of the :term:`Cache Group`
-:lastUpdated: The time and date at which this entry was last
updated in :ref:`non-rfc-datetime`
+:lastUpdated: The time and date at which this entry was last
updated in :rfc:`3339`
:latitude: A floating-point :ref:`cache-group-latitude`
for the :term:`Cache Group`
:localizationMethods: An array of
:ref:`cache-group-localization-methods` as strings
:longitude: A floating-point :ref:`cache-group-longitude`
for the :term:`Cache Group`
@@ -135,7 +135,7 @@ Response Structure
],
"typeName": "EDGE_LOC",
"typeId": 23,
- "lastUpdated": "2018-11-14 19:14:28+00"
+ "lastUpdated": "2023-05-30T19:52:58.183642+00:00"
}}
@@ -181,7 +181,7 @@ Response Structure
Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54
GMT; Max-Age=3600; HttpOnly
Whole-Content-Sha512:
5jZBgO7h1eNF70J/cmlbi3Hf9KJPx+WLMblH/pSKF3FWb/10GUHIN35ZOB+lN5LZYCkmk3izGbTFkiruG8I41Q==
X-Server-Name: traffic_ops_golang/
- Date: Wed, 14 Nov 2018 20:31:04 GMT
+ Date: Wed, 14 Nov 2018 19:14:28 GMT
Content-Length: 57
{ "alerts": [
diff --git a/lib/go-tc/cachegroups.go b/lib/go-tc/cachegroups.go
index 81b05ee94e..baf01a57f2 100644
--- a/lib/go-tc/cachegroups.go
+++ b/lib/go-tc/cachegroups.go
@@ -19,7 +19,11 @@ package tc
* under the License.
*/
-import "github.com/apache/trafficcontrol/lib/go-util"
+import (
+ "time"
+
+ "github.com/apache/trafficcontrol/lib/go-util"
+)
// CacheGroupsResponse is a list of CacheGroups as a response.
type CacheGroupsResponse struct {
@@ -93,3 +97,105 @@ type CachegroupQueueUpdatesRequest struct {
CDN *CDNName `json:"cdn"`
CDNID *util.JSONIntStr `json:"cdnId"`
}
+
+// CacheGroupsResponseV50 is a list of CacheGroups as a response.
+type CacheGroupsResponseV50 struct {
+ Response []CacheGroupV50 `json:"response"`
+ Alerts
+}
+
+// CacheGroupsNullableResponseV50 is a response with a list of
CacheGroupNullables.
+// Traffic Ops API responses instead uses an interface hold a list of
+// TOCacheGroups.
+type CacheGroupsNullableResponseV50 struct {
+ Response []CacheGroupNullableV50 `json:"response"`
+ Alerts
+}
+
+// CacheGroupDetailResponseV50 is the JSON object returned for a single Cache
Group.
+type CacheGroupDetailResponseV50 struct {
+ Response CacheGroupNullableV50 `json:"response"`
+ Alerts
+}
+
+// CacheGroupV50 contains information about a given cache group in Traffic Ops.
+type CacheGroupV50 struct {
+ ID int `json:"id" db:"id"`
+ Name string `json:"name" db:"name"`
+ ShortName string `json:"shortName"
db:"short_name"`
+ Latitude float64 `json:"latitude"
db:"latitude"`
+ Longitude float64 `json:"longitude"
db:"longitude"`
+ ParentName string
`json:"parentCachegroupName" db:"parent_cachegroup_name"`
+ ParentCachegroupID int
`json:"parentCachegroupId" db:"parent_cachegroup_id"`
+ SecondaryParentName string
`json:"secondaryParentCachegroupName" db:"secondary_parent_cachegroup_name"`
+ SecondaryParentCachegroupID int
`json:"secondaryParentCachegroupId" db:"secondary_parent_cachegroup_id"`
+ FallbackToClosest bool
`json:"fallbackToClosest" db:"fallback_to_closest"`
+ LocalizationMethods []LocalizationMethod
`json:"localizationMethods" db:"localization_methods"`
+ Type string `json:"typeName"
db:"type_name"` // aliased to type_name to disambiguate struct scans due to
join on 'type' table
+ TypeID int `json:"typeId"
db:"type_id"` // aliased to type_id to disambiguate struct scans due join
on 'type' table
+ LastUpdated time.Time `json:"lastUpdated"
db:"last_updated"`
+ Fallbacks []string `json:"fallbacks"
db:"fallbacks"`
+}
+
+// CacheGroupNullableV50 contains information about a given cache group in
Traffic Ops.
+// Unlike CacheGroup, CacheGroupNullable's fields are nullable.
+type CacheGroupNullableV50 struct {
+ ID *int `json:"id" db:"id"`
+ Name *string `json:"name"
db:"name"`
+ ShortName *string `json:"shortName"
db:"short_name"`
+ Latitude *float64 `json:"latitude"
db:"latitude"`
+ Longitude *float64 `json:"longitude"
db:"longitude"`
+ ParentName *string
`json:"parentCachegroupName" db:"parent_cachegroup_name"`
+ ParentCachegroupID *int
`json:"parentCachegroupId" db:"parent_cachegroup_id"`
+ SecondaryParentName *string
`json:"secondaryParentCachegroupName" db:"secondary_parent_cachegroup_name"`
+ SecondaryParentCachegroupID *int
`json:"secondaryParentCachegroupId" db:"secondary_parent_cachegroup_id"`
+ FallbackToClosest *bool
`json:"fallbackToClosest" db:"fallback_to_closest"`
+ LocalizationMethods *[]LocalizationMethod
`json:"localizationMethods" db:"localization_methods"`
+ Type *string `json:"typeName"
db:"type_name"` // aliased to type_name to disambiguate struct scans due to
join on 'type' table
+ TypeID *int `json:"typeId"
db:"type_id"` // aliased to type_id to disambiguate struct scans due join
on 'type' table
+ LastUpdated *time.Time `json:"lastUpdated"
db:"last_updated"`
+ Fallbacks *[]string `json:"fallbacks"
db:"fallbacks"`
+}
+
+// CachegroupTrimmedNameV50 is useful when the only info about a cache group
you
+// want to return is its name.
+type CachegroupTrimmedNameV50 struct {
+ Name string `json:"name"`
+}
+
+// CachegroupQueueUpdatesRequestV50 holds info relating to the
+// cachegroups/{{ID}}/queue_update TO route.
+type CachegroupQueueUpdatesRequestV50 struct {
+ Action string `json:"action"`
+ CDN *CDNName `json:"cdn"`
+ CDNID *util.JSONIntStr `json:"cdnId"`
+}
+
+// CacheGroupsResponseV5 is the type of response from the cachegroups
+// Traffic Ops endpoint.
+// It always points to the type for the latest minor version of
CacheGroupsResponseV5x APIv5.
+type CacheGroupsResponseV5 = CacheGroupsResponseV50
+
+// CacheGroupsNullableResponseV5 is the type of response from the cachegroups
+// Traffic Ops endpoint.
+// It always points to the type for the latest minor version of
CacheGroupsNullableResponseV5x APIv5.
+type CacheGroupsNullableResponseV5 = CacheGroupsNullableResponseV50
+
+// CacheGroupDetailResponseV5 is the type of response from the cachegroups
+// Traffic Ops endpoint.
+// It always points to the type for the latest minor version of
CacheGroupDetailResponseV5x APIv5.
+type CacheGroupDetailResponseV5 = CacheGroupDetailResponseV50
+
+// CacheGroupV5 always points to the type for the latest minor version of
CacheGroupV5x APIv5.
+type CacheGroupV5 = CacheGroupV50
+
+// CacheGroupNullableV5 always points to the type for the latest minor version
of CacheGroupNullableV5x APIv5.
+type CacheGroupNullableV5 = CacheGroupNullableV50
+
+// CachegroupTrimmedNameV5 always points to the type for the latest minor
version of CachegroupTrimmedNameV5x APIv5.
+type CachegroupTrimmedNameV5 = CachegroupTrimmedNameV50
+
+// CachegroupQueueUpdatesRequestV5 is the type of request to the cachegroups
+// Traffic Ops endpoint.
+// It always points to the type for the latest minor version of
CachegroupQueueUpdatesRequestV5x APIv5.
+type CachegroupQueueUpdatesRequestV5 = CachegroupQueueUpdatesRequestV50
diff --git a/traffic_ops/testing/api/v5/cachegroups_test.go
b/traffic_ops/testing/api/v5/cachegroups_test.go
index f7a8657401..a4a8b18676 100644
--- a/traffic_ops/testing/api/v5/cachegroups_test.go
+++ b/traffic_ops/testing/api/v5/cachegroups_test.go
@@ -38,7 +38,7 @@ func TestCacheGroups(t *testing.T) {
currentTimeRFC := currentTime.Format(time.RFC1123)
tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123)
- methodTests := utils.TestCase[client.Session,
client.RequestOptions, tc.CacheGroupNullable]{
+ methodTests := utils.TestCase[client.Session,
client.RequestOptions, tc.CacheGroupNullableV5]{
"GET": {
"OK when VALID NAME parameter AND Lat/Long are
0": {
ClientSession: TOSession,
@@ -152,7 +152,7 @@ func TestCacheGroups(t *testing.T) {
"PUT": {
"OK when VALID request": {
EndpointID: GetCacheGroupId(t,
"cachegroup1"), ClientSession: TOSession,
- RequestBody: tc.CacheGroupNullable{
+ RequestBody: tc.CacheGroupNullableV5{
Latitude:
util.Ptr(17.5),
Longitude:
util.Ptr(17.5),
Name:
util.Ptr("cachegroup1"),
@@ -166,7 +166,7 @@ func TestCacheGroups(t *testing.T) {
},
"OK when updating CG with null Lat/Long": {
EndpointID: GetCacheGroupId(t,
"nullLatLongCG"), ClientSession: TOSession,
- RequestBody: tc.CacheGroupNullable{
+ RequestBody: tc.CacheGroupNullableV5{
Name:
util.Ptr("nullLatLongCG"),
ShortName: util.Ptr("null-ll"),
Type: util.Ptr("EDGE_LOC"),
@@ -177,7 +177,7 @@ func TestCacheGroups(t *testing.T) {
},
"BAD REQUEST when updating TYPE of CG in
TOPOLOGY": {
EndpointID: GetCacheGroupId(t,
"topology-edge-cg-01"), ClientSession: TOSession,
- RequestBody: tc.CacheGroupNullable{
+ RequestBody: tc.CacheGroupNullableV5{
Latitude: util.Ptr(0.0),
Longitude: util.Ptr(0.0),
Name:
util.Ptr("topology-edge-cg-01"),
@@ -190,7 +190,7 @@ func TestCacheGroups(t *testing.T) {
"PRECONDITION FAILED when updating with IMS &
IUS Headers": {
EndpointID: GetCacheGroupId(t,
"cachegroup1"), ClientSession: TOSession,
RequestOpts:
client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince:
{currentTimeRFC}}},
- RequestBody: tc.CacheGroupNullable{
+ RequestBody: tc.CacheGroupNullableV5{
Name:
util.Ptr("cachegroup1"),
ShortName:
util.Ptr("changeName"),
Type: util.Ptr("EDGE_LOC"),
@@ -201,7 +201,7 @@ func TestCacheGroups(t *testing.T) {
"PRECONDITION FAILED when updating with IFMATCH
ETAG Header": {
EndpointID: GetCacheGroupId(t,
"cachegroup1"), ClientSession: TOSession,
RequestOpts:
client.RequestOptions{Header: http.Header{rfc.IfMatch:
{rfc.ETag(currentTime)}}},
- RequestBody: tc.CacheGroupNullable{
+ RequestBody: tc.CacheGroupNullableV5{
Name:
util.Ptr("cachegroup1"),
ShortName:
util.Ptr("changeName"),
Type: util.Ptr("EDGE_LOC"),
@@ -270,7 +270,7 @@ func TestCacheGroups(t *testing.T) {
func ValidateExpectedField(field string, expected string) utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _
tc.Alerts, _ error) {
- cgResp := resp.([]tc.CacheGroupNullable)
+ cgResp := resp.([]tc.CacheGroupNullableV5)
cg := cgResp[0]
switch field {
case "Name":
@@ -287,7 +287,7 @@ func ValidateExpectedField(field string, expected string)
utils.CkReqFunc {
func ValidateResponseFields() utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _
tc.Alerts, _ error) {
- cgResp := resp.([]tc.CacheGroupNullable)
+ cgResp := resp.([]tc.CacheGroupNullableV5)
cg := cgResp[0]
assert.NotNil(t, cg.ID, "Expected response id to not be nil")
assert.NotNil(t, cg.Latitude, "Expected latitude to not be nil")
@@ -299,7 +299,7 @@ func ValidateResponseFields() utils.CkReqFunc {
func ValidatePagination(paginationParam string) utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _
tc.Alerts, _ error) {
- paginationResp := resp.([]tc.CacheGroupNullable)
+ paginationResp := resp.([]tc.CacheGroupNullableV5)
opts := client.NewRequestOptions()
opts.QueryParameters.Set("orderby", "id")
@@ -370,7 +370,7 @@ func CreateTestCacheGroups(t *testing.T) {
}
func DeleteTestCacheGroups(t *testing.T) {
- var parentlessCacheGroups []tc.CacheGroupNullable
+ var parentlessCacheGroups []tc.CacheGroupNullableV5
opts := client.NewRequestOptions()
// delete the edge caches.
diff --git a/traffic_ops/testing/api/v5/cdn_locks_test.go
b/traffic_ops/testing/api/v5/cdn_locks_test.go
index 1fd951aceb..583d22cb41 100644
--- a/traffic_ops/testing/api/v5/cdn_locks_test.go
+++ b/traffic_ops/testing/api/v5/cdn_locks_test.go
@@ -568,7 +568,7 @@ func TestCDNLocks(t *testing.T) {
}
},
"CACHE GROUP UPDATE": func(t
*testing.T) {
- cacheGroup :=
tc.CacheGroupNullable{}
+ cacheGroup :=
tc.CacheGroupNullableV5{}
err =
json.Unmarshal(dat, &cacheGroup)
assert.NoError(t, err,
"Error occurred when unmarshalling request body: %v", err)
resp, reqInf, err :=
testCase.ClientSession.UpdateCacheGroup(testCase.EndpointID(), cacheGroup,
testCase.RequestOpts)
diff --git a/traffic_ops/testing/api/v5/traffic_control_test.go
b/traffic_ops/testing/api/v5/traffic_control_test.go
index d26b9c2e64..1ed3ad0b05 100644
--- a/traffic_ops/testing/api/v5/traffic_control_test.go
+++ b/traffic_ops/testing/api/v5/traffic_control_test.go
@@ -24,7 +24,7 @@ type TrafficControl struct {
ASNs []tc.ASNV5
`json:"asns"`
CDNs []tc.CDNV5
`json:"cdns"`
CDNLocks []tc.CDNLock
`json:"cdnlocks"`
- CacheGroups
[]tc.CacheGroupNullable `json:"cachegroups"`
+ CacheGroups
[]tc.CacheGroupNullableV5 `json:"cachegroups"`
Capabilities []tc.Capability
`json:"capability"`
Coordinates []tc.CoordinateV5
`json:"coordinates"`
DeliveryServicesRegexes
[]tc.DeliveryServiceRegexesTest `json:"deliveryServicesRegexes"`
diff --git a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
index ec2484c1ce..f1d62d45b1 100644
--- a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
+++ b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
@@ -21,6 +21,7 @@ package cachegroup
import (
"database/sql"
+ "encoding/json"
"errors"
"fmt"
"net/http"
@@ -28,14 +29,13 @@ import (
"strings"
"time"
-
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/util/ims"
-
"github.com/apache/trafficcontrol/lib/go-log"
"github.com/apache/trafficcontrol/lib/go-tc"
"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
"github.com/apache/trafficcontrol/lib/go-util"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
+
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/util/ims"
validation "github.com/go-ozzo/ozzo-validation"
"github.com/jmoiron/sqlx"
@@ -47,6 +47,64 @@ type TOCacheGroup struct {
tc.CacheGroupNullable
}
+type TOCacheGroupV5 struct {
+ api.APIInfoImpl `json:"-"`
+ tc.CacheGroupNullableV5
+}
+
+// Downgrade will convert an instance of CacheGroupNullableV5 to
CacheGroupNullable.
+// Note that this function does a shallow copy of the requested and original
Cache Group structures.
+func Downgrade(cgV5 tc.CacheGroupNullableV5) TOCacheGroup {
+ var cg TOCacheGroup
+ cg.ID = util.CopyIfNotNil(cgV5.ID)
+ cg.Name = util.CopyIfNotNil(cgV5.Name)
+ cg.ShortName = util.CopyIfNotNil(cgV5.ShortName)
+ cg.Latitude = util.CopyIfNotNil(cgV5.Latitude)
+ cg.Longitude = util.CopyIfNotNil(cgV5.Longitude)
+ cg.ParentName = util.CopyIfNotNil(cgV5.ParentName)
+ cg.ParentCachegroupID = util.CopyIfNotNil(cgV5.ParentCachegroupID)
+ cg.SecondaryParentName = util.CopyIfNotNil(cgV5.SecondaryParentName)
+ cg.SecondaryParentCachegroupID =
util.CopyIfNotNil(cgV5.SecondaryParentCachegroupID)
+ cg.FallbackToClosest = util.CopyIfNotNil(cgV5.FallbackToClosest)
+ cg.LocalizationMethods = util.CopyIfNotNil(cgV5.LocalizationMethods)
+ cg.Type = util.CopyIfNotNil(cgV5.Type)
+ cg.TypeID = util.CopyIfNotNil(cgV5.TypeID)
+ if cgV5.LastUpdated != nil {
+ cg.LastUpdated = tc.TimeNoModFromTime(*cgV5.LastUpdated)
+ }
+ cg.Fallbacks = util.CopyIfNotNil(cgV5.Fallbacks)
+ return cg
+}
+
+// Upgrade will convert an instance of CacheGroupNullable to
CacheGroupNullableV5.
+// Note that this function does a shallow copy of the requested and original
Cache Group structures.
+func (cg TOCacheGroup) Upgrade() (tc.CacheGroupNullableV5, error) {
+ var cgV5 tc.CacheGroupNullableV5
+ cgV5.ID = util.CopyIfNotNil(cg.ID)
+ cgV5.Name = util.CopyIfNotNil(cg.Name)
+ cgV5.ShortName = util.CopyIfNotNil(cg.ShortName)
+ cgV5.Latitude = util.CopyIfNotNil(cg.Latitude)
+ cgV5.Longitude = util.CopyIfNotNil(cg.Longitude)
+ cgV5.ParentName = util.CopyIfNotNil(cg.ParentName)
+ cgV5.ParentCachegroupID = util.CopyIfNotNil(cg.ParentCachegroupID)
+ cgV5.SecondaryParentName = util.CopyIfNotNil(cg.SecondaryParentName)
+ cgV5.SecondaryParentCachegroupID =
util.CopyIfNotNil(cg.SecondaryParentCachegroupID)
+ cgV5.FallbackToClosest = util.CopyIfNotNil(cg.FallbackToClosest)
+ cgV5.LocalizationMethods = util.CopyIfNotNil(cg.LocalizationMethods)
+ cgV5.Type = util.CopyIfNotNil(cg.Type)
+ cgV5.TypeID = util.CopyIfNotNil(cg.TypeID)
+ if cg.LastUpdated != nil {
+ cgV5.LastUpdated = &cg.LastUpdated.Time
+ t, err := util.ConvertTimeFormat(*cgV5.LastUpdated,
time.RFC3339)
+ if err != nil {
+ return cgV5, err
+ }
+ cgV5.LastUpdated = t
+ }
+ cgV5.Fallbacks = util.CopyIfNotNil(cg.Fallbacks)
+ return cgV5, nil
+}
+
func (cg TOCacheGroup) GetKeyFieldsInfo() []api.KeyFieldInfo {
return []api.KeyFieldInfo{{Field: "id", Func: api.GetIntKey}}
}
@@ -897,3 +955,489 @@ last_updated`
func DeleteQuery() string {
return `DELETE FROM cachegroup WHERE id=$1`
}
+
+// GetCacheGroup [Version : V5] function Process the *http.Request and writes
the response. It uses getCacheGroup function.
+func GetCacheGroup(w http.ResponseWriter, r *http.Request) {
+ inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+
+ code := http.StatusOK
+ useIMS := false
+ config, e := api.GetConfig(r.Context())
+ if e == nil && config != nil {
+ useIMS = config.UseIMS
+ } else {
+ log.Warnf("Couldn't get config %v", e)
+ }
+
+ var maxTime time.Time
+ var usrErr error
+ var syErr error
+
+ var cgList []interface{}
+
+ tx := inf.Tx
+
+ cgList, maxTime, code, usrErr, syErr = getCacheGroup(tx, inf.Params,
useIMS, r.Header)
+ if code == http.StatusNotModified {
+ w.WriteHeader(code)
+ api.WriteResp(w, r, []tc.CacheGroupV5{})
+ return
+ }
+
+ if code == http.StatusBadRequest {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, usrErr,
nil)
+ return
+ }
+
+ if sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError,
nil, syErr)
+ return
+ }
+
+ if maxTime != (time.Time{}) && api.SetLastModifiedHeader(r, useIMS) {
+ api.AddLastModifiedHdr(w, maxTime)
+ }
+
+ api.WriteResp(w, r, cgList)
+}
+
+func getCacheGroup(tx *sqlx.Tx, params map[string]string, useIMS bool, header
http.Header) ([]interface{}, time.Time, int, error, error) {
+ //func getCacheGroup(tx *sqlx.Tx, params map[string]string, useIMS
bool, header http.Header) ([]tc.CacheGroupV5, time.Time, int, error, error) {
+ var runSecond bool
+ var maxTime time.Time
+ cgList := []interface{}{}
+
+ // Query Parameters to Database Query column mappings
+ queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+ "id": {Column: "cachegroup.id", Checker: api.IsInt},
+ "name": {Column: "cachegroup.name"},
+ "shortName": {Column: "cachegroup.short_name"},
+ "type": {Column: "cachegroup.type"},
+ "topology": {Column: "topology_cachegroup.topology"},
+ }
+ if _, ok := params["orderby"]; !ok {
+ params["orderby"] = "name"
+ }
+ where, orderBy, pagination, queryValues, errs :=
dbhelpers.BuildWhereAndOrderByAndPagination(params, queryParamsToQueryCols)
+ if len(errs) > 0 {
+ return nil, time.Time{}, http.StatusBadRequest,
util.JoinErrs(errs), nil
+ }
+
+ if useIMS {
+ runSecond, maxTime = ims.TryIfModifiedSinceQuery(tx, header,
queryValues, selectMaxLastUpdatedQuery(where))
+ if !runSecond {
+ log.Debugln("IMS HIT")
+ return cgList, maxTime, http.StatusNotModified, nil, nil
+ }
+ log.Debugln("IMS MISS")
+ } else {
+ log.Debugln("Non IMS request")
+ }
+ baseSelect := SelectQuery()
+ if _, ok := params["topology"]; ok {
+ baseSelect += `
+ LEFT JOIN topology_cachegroup ON cachegroup.name =
topology_cachegroup.cachegroup
+ `
+ }
+ // If the type cannot be converted to an int, return 400
+ if cgType, ok := params["type"]; ok {
+ _, err := strconv.Atoi(cgType)
+ if err != nil {
+ return nil, time.Time{}, http.StatusBadRequest, nil,
fmt.Errorf("cachegroup read: converting cachegroup type to integer " +
err.Error())
+ }
+ }
+
+ query := baseSelect + where + orderBy + pagination
+ rows, err := tx.NamedQuery(query, queryValues)
+ if err != nil {
+ return nil, time.Time{}, http.StatusInternalServerError, nil,
err
+ }
+ defer rows.Close()
+
+ for rows.Next() {
+ var cg TOCacheGroupV5
+ lms := make([]tc.LocalizationMethod, 0)
+ cgfs := make([]string, 0)
+ if err = rows.Scan(
+ &cg.ID,
+ &cg.Name,
+ &cg.ShortName,
+ &cg.Latitude,
+ &cg.Longitude,
+ pq.Array(&lms),
+ &cg.ParentCachegroupID,
+ &cg.ParentName,
+ &cg.SecondaryParentCachegroupID,
+ &cg.SecondaryParentName,
+ &cg.Type,
+ &cg.TypeID,
+ &cg.LastUpdated,
+ pq.Array(&cgfs),
+ &cg.FallbackToClosest,
+ ); err != nil {
+ return nil, time.Time{},
http.StatusInternalServerError, nil, err
+ }
+ cg.LocalizationMethods = &lms
+ cg.Fallbacks = &cgfs
+ cgList = append(cgList, cg)
+ }
+
+ return cgList, maxTime, http.StatusOK, nil, nil
+}
+
+// CreateCacheGroup [Version : V5] function creates the cache group with the
passed name.
+func CreateCacheGroup(w http.ResponseWriter, r *http.Request) {
+ inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+ tx := inf.Tx.Tx
+
+ cg, readValErr := readAndValidateJsonStruct(r)
+ if readValErr != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+ return
+ }
+
+ // check if cache group already exists
+ var exists bool
+ err := tx.QueryRow(`SELECT EXISTS(SELECT * from cachegroup where name =
$1)`, cg.Name).Scan(&exists)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
fmt.Errorf("error: %w, when checking if cache group with name %s exists", err,
*cg.Name))
+ return
+ }
+ if exists {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
fmt.Errorf("cache group name '%s' already exists.", *cg.Name), nil)
+ return
+ }
+
+ // create cache group
+ query := InsertQuery()
+
+ err = tx.QueryRow(
+ query,
+ cg.Name,
+ cg.ShortName,
+ cg.TypeID,
+ cg.ParentCachegroupID,
+ cg.SecondaryParentCachegroupID,
+ cg.FallbackToClosest,
+ ).Scan(
+ &cg.ID,
+ &cg.Type,
+ &cg.ParentName,
+ &cg.SecondaryParentName,
+ )
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
fmt.Errorf("error: %w in creating cache group with name: %s", err, *cg.Name),
nil)
+ return
+ }
+ usrErr, sysErr, code := api.ParseDBError(err)
+ api.HandleErr(w, r, tx, code, usrErr, sysErr)
+ return
+ }
+
+ dgCg := Downgrade(cg)
+ dgCg.ReqInfo = inf
+ coordinateID, err := dgCg.createCoordinate()
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
fmt.Errorf("cachegroup create: creating coord: "+err.Error()), nil)
+ return
+ }
+
+ checkLastUpdated := `UPDATE cachegroup SET coordinate=$1 WHERE id=$2
RETURNING last_updated`
+
+ err = tx.QueryRow(
+ checkLastUpdated,
+ coordinateID,
+ *cg.ID,
+ ).Scan(
+ &cg.LastUpdated,
+ )
+
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
fmt.Errorf("followup update during cachegroup create: %v", err), nil)
+ return
+ }
+
+ if err = dgCg.createLocalizationMethods(); err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
fmt.Errorf("creating cachegroup: creating localization methods: "+err.Error()),
nil)
+ return
+ }
+
+ if err = dgCg.createCacheGroupFallbacks(); err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
fmt.Errorf("creating cachegroup: creating cache group fallbacks:
"+err.Error()), nil)
+ return
+ }
+
+ cg, err = dgCg.Upgrade()
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
fmt.Errorf("converting cachegroup: converting cache group upgrade:
"+err.Error()), nil)
+ return
+ }
+
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "cache group was created.")
+ w.Header().Set("Location",
fmt.Sprintf("/api/%d.%d/cachegroups?name=%s", inf.Version.Major,
inf.Version.Minor, *cg.Name))
+ api.WriteAlertsObj(w, r, http.StatusCreated, alerts, cg)
+ return
+}
+
+// UpdateCacheGroup [Version : V5] function updates the name of the cache
group passed.
+func UpdateCacheGroup(w http.ResponseWriter, r *http.Request) {
+ inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+ tx := inf.Tx.Tx
+
+ cg, readValErr := readAndValidateJsonStruct(r)
+ if readValErr != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+ return
+ }
+
+ ID := inf.Params["id"]
+ id, err := strconv.Atoi(ID)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusUnprocessableEntity,
fmt.Errorf("update cachegroup: converted to type int: "+err.Error()), nil)
+ return
+ }
+
+ // check if the entity was already updated
+ userErr, sysErr, errCode = api.CheckIfUnModified(r.Header, inf.Tx, id,
"cachegroup")
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+
+ dgCg := Downgrade(cg)
+ dgCg.ReqInfo = inf
+
+ keyFields := dgCg.GetKeyFieldsInfo() //expecting a slice of the key
fields info which is a struct with the field name and a function to convert a
string into a {}interface of the right type. in most that will be
[{Field:"id",Func: func(s string)({}interface,error){return strconv.Atoi(s)}}]
+ // ignoring ok value -- will be checked after param processing
+
+ keys := make(map[string]interface{}) // a map of keyField to keyValue
where keyValue is an {}interface
+ for _, kf := range keyFields {
+ paramKey := inf.Params[kf.Field]
+ if paramKey == "" {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest,
errors.New("missing key: "+kf.Field), nil)
+ return
+ }
+
+ paramValue, err := kf.Func(paramKey)
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest,
errors.New("failed to parse key: "+kf.Field), nil)
+ return
+ }
+
+ if paramValue != "" {
+ // if key's value provided in params, overwrite it and
ignore that provided in JSON
+ keys[kf.Field] = paramValue
+ }
+ }
+
+ // check that all keys were properly filled in
+ dgCg.SetKeys(keys)
+ _, ok := dgCg.GetKeys()
+ if !ok {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest,
errors.New("unable to parse required keys from request body"), nil)
+ return
+ }
+
+ // check if user can modify cache group
+ userErr, sysErr, errCode =
dbhelpers.CheckIfCurrentUserCanModifyCachegroup(dgCg.ReqInfo.Tx.Tx, *dgCg.ID,
dgCg.ReqInfo.User.UserName)
+ if sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, fmt.Errorf("update cachegroup:
checking if user can modify: "+sysErr.Error()), sysErr)
+ return
+ }
+ if userErr != nil {
+ api.HandleErr(w, r, tx, errCode, fmt.Errorf("update cachegroup:
checking if user can modify: "+userErr.Error()), userErr)
+ return
+ }
+
+ coordinateID, userErr, sysErr, errCode := dgCg.handleCoordinateUpdate()
+ if sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, fmt.Errorf("update cachegroup:
updating coordinate: "+sysErr.Error()), sysErr)
+ return
+ }
+ if userErr != nil {
+ api.HandleErr(w, r, tx, errCode, fmt.Errorf("update cachegroup:
updating coordinate: "+userErr.Error()), userErr)
+ return
+ }
+
+ err = dgCg.ValidateTypeInTopology()
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusBadRequest,
fmt.Errorf("update cachegroup: validating type in topology: "+err.Error()), err)
+ return
+ }
+
+ //update cache group
+ query := UpdateQuery()
+
+ err = tx.QueryRow(
+ query,
+ dgCg.Name,
+ dgCg.ShortName,
+ coordinateID,
+ dgCg.ParentCachegroupID,
+ dgCg.SecondaryParentCachegroupID,
+ dgCg.TypeID,
+ dgCg.FallbackToClosest,
+ dgCg.ID,
+ ).Scan(
+ &dgCg.Type,
+ &dgCg.ParentName,
+ &dgCg.SecondaryParentName,
+ &dgCg.LastUpdated,
+ )
+ if err != nil {
+ if errors.Is(err, sql.ErrNoRows) {
+ api.HandleErr(w, r, tx, http.StatusNotFound,
fmt.Errorf("cache group with name: %s not found", *dgCg.Name), nil)
+ return
+ }
+ usrErr, sysErr, code := api.ParseDBError(err)
+ api.HandleErr(w, r, tx, code, usrErr, sysErr)
+ return
+ }
+
+ if err = dgCg.createLocalizationMethods(); err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
fmt.Errorf("creating cachegroup: creating localization methods: "+err.Error()),
nil)
+ return
+ }
+
+ if err = dgCg.createCacheGroupFallbacks(); err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
fmt.Errorf("creating cachegroup: creating cache group fallbacks:
"+err.Error()), nil)
+ return
+ }
+
+ cg, err = dgCg.Upgrade()
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
fmt.Errorf("converting cachegroup: converting cache group upgrade:
"+err.Error()), nil)
+ return
+ }
+
+ alerts := tc.CreateAlerts(tc.SuccessLevel, "cache group was updated")
+ api.WriteAlertsObj(w, r, http.StatusOK, alerts, cg)
+ return
+}
+
+// DeleteCacheGroup [Version : V5] function deletes the cache group passed.
+func DeleteCacheGroup(w http.ResponseWriter, r *http.Request) {
+ inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+ tx := inf.Tx.Tx
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+
+ ID := inf.Params["id"]
+ id, err := strconv.Atoi(ID)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusUnprocessableEntity,
fmt.Errorf("delete cachegroup: converted to type int: "+err.Error()), nil)
+ return
+ }
+
+ inUse, err := isUsed(inf.Tx, id)
+ if inUse {
+ api.HandleErr(w, r, tx, http.StatusBadRequest, nil, nil)
+ return
+ }
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
fmt.Errorf("cachegroup delete: checking use: "+err.Error()), nil)
+ return
+ }
+
+ coordinateID, err := dbhelpers.GetCoordinateID(inf.Tx.Tx, id)
+ if err == sql.ErrNoRows {
+ api.HandleErr(w, r, tx, http.StatusNotFound, fmt.Errorf("no
cachegroup with that id found"), nil)
+ return
+ }
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
fmt.Errorf("cachegroup delete: deleting cachegroup: "+err.Error()), nil)
+ return
+ }
+
+ // check if user can modify cache group
+ userErr, sysErr, errCode =
dbhelpers.CheckIfCurrentUserCanModifyCachegroup(inf.Tx.Tx, id,
inf.User.UserName)
+ if userErr != nil {
+ api.HandleErr(w, r, tx, errCode, fmt.Errorf("cachegroup delete:
getting coord: "+userErr.Error()), nil)
+ return
+ }
+ if sysErr != nil {
+ api.HandleErr(w, r, tx, errCode, fmt.Errorf("cachegroup delete:
getting coord: "+sysErr.Error()), nil)
+ return
+ }
+
+ if err = dbhelpers.DeleteCoordinate(inf.Tx.Tx, id, *coordinateID); err
!= nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError,
fmt.Errorf("cachegroup delete: deleting coord: "+err.Error()), nil)
+ return
+ }
+
+ res, err := tx.Exec("DELETE FROM cachegroup AS cg WHERE cg.ID=$1", ID)
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
err)
+ return
+ }
+ rowsAffected, err := res.RowsAffected()
+ if err != nil {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
fmt.Errorf("determining rows affected for delete cachegroup: %w", err))
+ return
+ }
+ if rowsAffected == 0 {
+ api.HandleErr(w, r, tx, http.StatusInternalServerError, nil,
fmt.Errorf("no rows deleted for cachegroup"))
+ return
+ }
+
+ alertMessage := fmt.Sprintf("%s was deleted.", ID)
+ alerts := tc.CreateAlerts(tc.SuccessLevel, alertMessage)
+ api.WriteAlerts(w, r, http.StatusOK, alerts)
+ return
+}
+
+// readAndValidateJsonStruct populates select missing fields and validates
JSON body
+func readAndValidateJsonStruct(r *http.Request) (tc.CacheGroupNullableV5,
error) {
+ var cg tc.CacheGroupNullableV5
+ if err := json.NewDecoder(r.Body).Decode(&cg); err != nil {
+ userErr := fmt.Errorf("error decoding POST request body into
CacheGroupV5 struct %w", err)
+ return cg, userErr
+ }
+
+ if cg.Latitude == nil {
+ cg.Latitude = util.Ptr(0.0)
+ }
+ if cg.Longitude == nil {
+ cg.Longitude = util.Ptr(0.0)
+ }
+ if cg.LocalizationMethods == nil {
+ cg.LocalizationMethods = &[]tc.LocalizationMethod{}
+ }
+ if cg.Fallbacks == nil {
+ cg.Fallbacks = &[]string{}
+ }
+ if cg.FallbackToClosest == nil {
+ fbc := true
+ cg.FallbackToClosest = &fbc
+ }
+
+ // validate JSON body
+ rule :=
validation.NewStringRule(tovalidate.IsAlphanumericUnderscoreDash, "must consist
of only alphanumeric, dash, or underscore characters")
+ errs := tovalidate.ToErrors(validation.Errors{
+ "name": validation.Validate(cg.Name, validation.Required, rule),
+ })
+ if len(errs) > 0 {
+ userErr := util.JoinErrs(errs)
+ return cg, userErr
+ }
+ return cg, nil
+}
diff --git a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
index 94f026b800..c70056279b 100644
--- a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
+++ b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
@@ -2218,6 +2218,21 @@ func ASNExists(tx *sql.Tx, id string) (bool, error) {
return true, nil
}
+// CacheGroupExists confirms whether the cache group exists, and an error (if
one occurs).
+func CacheGroupExists(tx *sql.Tx, name string) (bool, error) {
+ var count int
+ if err := tx.QueryRow("SELECT count(name) FROM cachegroup AS cg WHERE
cg.name=$1", name).Scan(&count); err != nil {
+ return false, fmt.Errorf("error getting cache group info: %w",
err)
+ }
+ if count == 0 {
+ return false, nil
+ }
+ if count != 1 {
+ return false, fmt.Errorf("getting cache group info - expected
row count: 1, actual: %d", count)
+ }
+ return true, nil
+}
+
// DivisionExists confirms whether the division exists, and an error (if one
occurs).
func DivisionExists(tx *sql.Tx, id string) (bool, error) {
var count int
@@ -2247,3 +2262,45 @@ func PhysLocationExists(tx *sql.Tx, id string) (bool,
error) {
}
return true, nil
}
+
+// GetCoordinateID obtains coordinateID, and an error (if one occurs)
+func GetCoordinateID(tx *sql.Tx, id int) (*int, error) {
+ q := `SELECT coordinate FROM cachegroup WHERE id = $1`
+
+ var coordinateID *int
+ if err := tx.QueryRow(q, id).Scan(&coordinateID); err != nil {
+ return nil, err
+ }
+
+ return coordinateID, nil
+}
+
+// DeleteCoordinate deletes coordinate by id, and an error (if one occurs)
+func DeleteCoordinate(tx *sql.Tx, cacheGroupID int, coordinateID int) error {
+ q := `UPDATE cachegroup SET coordinate = NULL WHERE id = $1`
+ result, err := tx.Exec(q, cacheGroupID)
+ if err != nil {
+ return fmt.Errorf("updating cachegroup %d coordinate to null:
%s", cacheGroupID, err.Error())
+ }
+ rowsAffected, err := result.RowsAffected()
+ if err != nil {
+ return fmt.Errorf("updating cachegroup %d coordinate to null,
getting rows affected: %s", coordinateID, err.Error())
+ }
+ if rowsAffected == 0 {
+ return fmt.Errorf("updating cachegroup %d coordinate to null,
zero rows affected", coordinateID)
+ }
+
+ q = `DELETE FROM coordinate WHERE id = $1`
+ result, err = tx.Exec(q, coordinateID)
+ if err != nil {
+ return fmt.Errorf("delete coordinate %d for cachegroup %d: %s",
coordinateID, coordinateID, err.Error())
+ }
+ rowsAffected, err = result.RowsAffected()
+ if err != nil {
+ return fmt.Errorf("delete coordinate %d for cachegroup %d,
getting rows affected: %s", coordinateID, coordinateID, err.Error())
+ }
+ if rowsAffected == 0 {
+ return fmt.Errorf("delete coordinate %d for cachegroup %d, zero
rows affected", coordinateID, coordinateID)
+ }
+ return nil
+}
diff --git a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers_test.go
b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers_test.go
index 0c9bda1586..3f9d2123dd 100644
--- a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers_test.go
+++ b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers_test.go
@@ -563,6 +563,57 @@ func TestASNExists(t *testing.T) {
}
}
+func TestCacheGroupExistsExists(t *testing.T) {
+ var testCases = []struct {
+ description string
+ name string
+ expectedError error
+ exists bool
+ }{
+ {
+ description: "Success: Get valid Cache Group",
+ name: "testCacheGroup1",
+ expectedError: nil,
+ exists: true,
+ },
+ {
+ description: "Failure: Cache Group not in DB",
+ name: "testCacheGroup2",
+ expectedError: sql.ErrNoRows,
+ exists: false,
+ },
+ }
+ for _, testCase := range testCases {
+ t.Run(testCase.description, func(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)
+ }
+ defer mockDB.Close()
+
+ db := sqlx.NewDb(mockDB, "sqlmock")
+ defer db.Close()
+
+ mock.ExpectBegin()
+ rows := sqlmock.NewRows([]string{"count"})
+ if testCase.exists {
+ rows = rows.AddRow(1)
+ }
+ mock.ExpectQuery("SELECT").WillReturnRows(rows)
+ mock.ExpectCommit()
+
+ cgExists, err := CacheGroupExists(db.MustBegin().Tx,
testCase.name)
+ if testCase.exists != cgExists {
+ t.Errorf("Expected return exists: %t, actual
%t", testCase.exists, cgExists)
+ }
+
+ if !errors.Is(err, testCase.expectedError) {
+ t.Errorf("CacheGroupExists expected: %s,
actual: %s", testCase.expectedError, err)
+ }
+ })
+ }
+}
+
func TestDivisionExists(t *testing.T) {
var testCases = []struct {
description string
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go
b/traffic_ops/traffic_ops_golang/routing/routes.go
index 1de8191a96..f62a77db47 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -177,10 +177,10 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
{Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodGet, Path: `caches/stats/?$`, Handler: cachesstats.Get,
RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions:
[]string{"CACHE-GROUP:READ", "PROFILE:READ"}, Authenticated: Authenticated,
Middlewares: nil, ID: 481320658831},
//CacheGroup: CRUD
- {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodGet, Path: `cachegroups/?$`, Handler:
api.ReadHandler(&cachegroup.TOCacheGroup{}), RequiredPrivLevel:
auth.PrivLevelReadOnly, RequiredPermissions: []string{"CACHE-GROUP:READ",
"TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 42307911031},
- {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodPut, Path: `cachegroups/{id}$`, Handler:
api.UpdateHandler(&cachegroup.TOCacheGroup{}), RequiredPrivLevel:
auth.PrivLevelOperations, RequiredPermissions: []string{"CACHE-GROUP:UPDATE",
"CACHE-GROUP:READ", "TYPE:READ"}, Authenticated: Authenticated, Middlewares:
nil, ID: 41295454631},
- {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodPost, Path: `cachegroups/?$`, Handler:
api.CreateHandler(&cachegroup.TOCacheGroup{}), RequiredPrivLevel:
auth.PrivLevelOperations, RequiredPermissions: []string{"CACHE-GROUP:CREATE",
"CACHE-GROUP:READ", "TYPE:READ"}, Authenticated: Authenticated, Middlewares:
nil, ID: 4298266531},
- {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodDelete, Path: `cachegroups/{id}$`, Handler:
api.DeleteHandler(&cachegroup.TOCacheGroup{}), RequiredPrivLevel:
auth.PrivLevelOperations, RequiredPermissions: []string{"CACHE-GROUP:DELETE",
"CACHE-GROUP:READ"}, Authenticated: Authenticated, Middlewares: nil, ID:
42786936531},
+ {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodGet, Path: `cachegroups/?$`, Handler: cachegroup.GetCacheGroup,
RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions:
[]string{"CACHE-GROUP:READ", "TYPE:READ"}, Authenticated: Authenticated,
Middlewares: nil, ID: 42307911031},
+ {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodPut, Path: `cachegroups/{id}$`, Handler:
cachegroup.UpdateCacheGroup, RequiredPrivLevel: auth.PrivLevelOperations,
RequiredPermissions: []string{"CACHE-GROUP:UPDATE", "CACHE-GROUP:READ",
"TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 41295454631},
+ {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodPost, Path: `cachegroups/?$`, Handler: cachegroup.CreateCacheGroup,
RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions:
[]string{"CACHE-GROUP:CREATE", "CACHE-GROUP:READ", "TYPE:READ"}, Authenticated:
Authenticated, Middlewares: nil, ID: 4298266531},
+ {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodDelete, Path: `cachegroups/{id}$`, Handler:
cachegroup.DeleteCacheGroup, RequiredPrivLevel: auth.PrivLevelOperations,
RequiredPermissions: []string{"CACHE-GROUP:DELETE", "CACHE-GROUP:READ"},
Authenticated: Authenticated, Middlewares: nil, ID: 42786936531},
{Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodPost, Path: `cachegroups/{id}/queue_update$`, Handler:
cachegroup.QueueUpdates, RequiredPrivLevel: auth.PrivLevelOperations,
RequiredPermissions: []string{"CACHE-GROUP:READ", "CDN:READ", "SERVER:READ",
"SERVER:QUEUE"}, Authenticated: Authenticated, Middlewares: nil, ID:
407164411031},
{Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodPost, Path: `cachegroups/{id}/deliveryservices/?$`, Handler:
cachegroup.DSPostHandlerV40, RequiredPrivLevel: auth.PrivLevelOperations,
RequiredPermissions: []string{"CACHE-GROUP:UPDATE", "DELIVERY-SERVICE:UPDATE",
"CACHE-GROUP:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated,
Middlewares: nil, ID: 452024043131},
diff --git a/traffic_ops/v5-client/cachegroup.go
b/traffic_ops/v5-client/cachegroup.go
index 0d290fa66b..d660349e71 100644
--- a/traffic_ops/v5-client/cachegroup.go
+++ b/traffic_ops/v5-client/cachegroup.go
@@ -27,8 +27,8 @@ import (
const apiCachegroups = "/cachegroups"
// CreateCacheGroup creates the given Cache Group.
-func (to *Session) CreateCacheGroup(cachegroup tc.CacheGroupNullable, opts
RequestOptions) (tc.CacheGroupDetailResponse, toclientlib.ReqInf, error) {
- var resp tc.CacheGroupDetailResponse
+func (to *Session) CreateCacheGroup(cachegroup tc.CacheGroupNullableV5, opts
RequestOptions) (tc.CacheGroupDetailResponseV5, toclientlib.ReqInf, error) {
+ var resp tc.CacheGroupDetailResponseV5
if cachegroup.TypeID == nil && cachegroup.Type != nil {
opts := NewRequestOptions()
opts.QueryParameters.Set("name", *cachegroup.Type)
@@ -78,16 +78,16 @@ func (to *Session) CreateCacheGroup(cachegroup
tc.CacheGroupNullable, opts Reque
// UpdateCacheGroup replaces the Cache Group identified by the given ID with
// the given Cache Group.
-func (to *Session) UpdateCacheGroup(id int, cachegroup tc.CacheGroupNullable,
opts RequestOptions) (tc.CacheGroupDetailResponse, toclientlib.ReqInf, error) {
+func (to *Session) UpdateCacheGroup(id int, cachegroup
tc.CacheGroupNullableV5, opts RequestOptions) (tc.CacheGroupDetailResponseV5,
toclientlib.ReqInf, error) {
route := fmt.Sprintf("%s/%d", apiCachegroups, id)
- var cachegroupResp tc.CacheGroupDetailResponse
+ var cachegroupResp tc.CacheGroupDetailResponseV5
reqInf, err := to.put(route, opts, cachegroup, &cachegroupResp)
return cachegroupResp, reqInf, err
}
// GetCacheGroups retrieves Cache Groups configured in Traffic Ops.
-func (to *Session) GetCacheGroups(opts RequestOptions)
(tc.CacheGroupsNullableResponse, toclientlib.ReqInf, error) {
- var data tc.CacheGroupsNullableResponse
+func (to *Session) GetCacheGroups(opts RequestOptions)
(tc.CacheGroupsNullableResponseV5, toclientlib.ReqInf, error) {
+ var data tc.CacheGroupsNullableResponseV5
reqInf, err := to.get(apiCachegroups, opts, &data)
return data, reqInf, err
}