Skip to content
This repository was archived by the owner on Nov 24, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
- Added an optional readiness check service to cdn-in-a-box that exits successfully when it is able to get a `200 OK` from all delivery services
- [Flexible Topologies (in progress)](https://github.com/apache/trafficcontrol/blob/master/blueprints/flexible-topologies.md)
- Traffic Ops: Added an API 3.0 endpoint, /api/3.0/topologies, to create, read, update and delete flexible topologies.
- Traffic Ops: Added support for `topology` query parameter to `GET /api/3.0/cachegroups` to return all cachegroups used in the given topology.
- Traffic Portal: Added the ability to create, read, update and delete flexible topologies.

### Fixed
Expand Down
2 changes: 2 additions & 0 deletions docs/source/api/v3/cachegroups.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Request Structure
+-----------+----------+--------------------------------------------------------------------------------------------------------------------------+
| type | no | Return only :term:`Cache Groups` that are of the :ref:`cache-group-type` identified by this integral, unique identifier |
+-----------+----------+--------------------------------------------------------------------------------------------------------------------------+
| topology | no | Return only :term:`Cache Groups` that are used in the :term:`Topology` identified by this unique identifier |
+-----------+----------+--------------------------------------------------------------------------------------------------------------------------+
| orderby | no | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` array |
+-----------+----------+--------------------------------------------------------------------------------------------------------------------------+
| sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") |
Expand Down
25 changes: 24 additions & 1 deletion traffic_ops/client/cachegroup.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"net/http"
"net/url"

"github.com/apache/trafficcontrol/lib/go-log"
"github.com/apache/trafficcontrol/lib/go-tc"
)

Expand Down Expand Up @@ -156,7 +157,7 @@ func (to *Session) GetCacheGroupNullableByName(name string) ([]tc.CacheGroupNull
return data.Response, reqInf, nil
}

// GET a CacheGroup by the CacheGroup name.
// GET a CacheGroup by the CacheGroup short name.
func (to *Session) GetCacheGroupNullableByShortName(shortName string) ([]tc.CacheGroupNullable, ReqInf, error) {
route := fmt.Sprintf("%s?shortName=%s", API_CACHEGROUPS, url.QueryEscape(shortName))
resp, remoteAddr, err := to.request(http.MethodGet, route, nil)
Expand Down Expand Up @@ -189,3 +190,25 @@ func (to *Session) DeleteCacheGroupByID(id int) (tc.Alerts, ReqInf, error) {
}
return alerts, reqInf, nil
}

// GetCacheGroupsByQueryParams gets cache groups by the given query parameters.
func (to *Session) GetCacheGroupsByQueryParams(qparams url.Values) ([]tc.CacheGroupNullable, ReqInf, error) {
route := API_CACHEGROUPS
if len(qparams) > 0 {
route += "?" + qparams.Encode()
}

resp, remoteAddr, err := to.request(http.MethodGet, route, nil)
reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: remoteAddr}
if err != nil {
return nil, reqInf, err
}
defer log.Close(resp.Body, "unable to close cachegroups response body")

var data tc.CacheGroupsNullableResponse
if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
return nil, reqInf, err
}

return data.Response, reqInf, nil
}
37 changes: 35 additions & 2 deletions traffic_ops/testing/api/v3/cachegroups_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,16 +17,18 @@ package v3

import (
"fmt"
"net/url"
"reflect"
"testing"

"github.com/apache/trafficcontrol/lib/go-tc"
)

func TestCacheGroups(t *testing.T) {
WithObjs(t, []TCObj{Types, Parameters, CacheGroups}, func() {
WithObjs(t, []TCObj{Types, Parameters, CacheGroups, Topologies}, func() {
GetTestCacheGroupsByName(t)
GetTestCacheGroupsByShortName(t)
GetTestCacheGroupsByTopology(t)
CheckCacheGroupsAuthentication(t)
UpdateTestCacheGroups(t)
})
Expand Down Expand Up @@ -70,16 +72,47 @@ func GetTestCacheGroupsByName(t *testing.T) {
if err != nil {
t.Errorf("cannot GET CacheGroup by name: %v - %v", err, resp)
}
if *resp[0].Name != *cg.Name {
t.Errorf("name expected: %s, actual: %s", *cg.Name, *resp[0].Name)
}
}
}

func GetTestCacheGroupsByShortName(t *testing.T) {
for _, cg := range testData.CacheGroups {
resp, _, err := TOSession.GetCacheGroupNullableByName(*cg.ShortName)
resp, _, err := TOSession.GetCacheGroupNullableByShortName(*cg.ShortName)
if err != nil {
t.Errorf("cannot GET CacheGroup by shortName: %v - %v", err, resp)
}
if *resp[0].ShortName != *cg.ShortName {
t.Errorf("short name expected: %s, actual: %s", *cg.ShortName, *resp[0].ShortName)
}
}
}

func GetTestCacheGroupsByTopology(t *testing.T) {
for _, top := range testData.Topologies {
qparams := url.Values{}
qparams.Set("topology", top.Name)
Comment thread
rawlinp marked this conversation as resolved.
resp, _, err := TOSession.GetCacheGroupsByQueryParams(qparams)
if err != nil {
t.Errorf("cannot GET CacheGroups by topology: %v - %v", err, resp)
}
expectedCGs := topologyCachegroups(top)
for _, cg := range resp {
if _, exists := expectedCGs[*cg.Name]; !exists {
t.Errorf("GET cachegroups by topology - expected one of: %v, actual: %s", expectedCGs, *cg.Name)
}
}
}
}

func topologyCachegroups(top tc.Topology) map[string]struct{} {
res := make(map[string]struct{})
for _, node := range top.Nodes {
res[node.Cachegroup] = struct{}{}
}
return res
}

func UpdateTestCacheGroups(t *testing.T) {
Expand Down
20 changes: 14 additions & 6 deletions traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,23 +88,28 @@ func (cg *TOCacheGroup) SetID(i int) {
// Is the cachegroup being used?
func isUsed(tx *sqlx.Tx, ID int) (bool, error) {

var usedByTopology bool
var usedByServer bool
var usedByParent bool
var usedBySecondaryParent bool
var usedByASN bool

query := `SELECT
(SELECT id FROM topology_cachegroup WHERE topology_cachegroup.cachegroup = (SELECT name FROM cachegroup WHERE id = $1) LIMIT 1) IS NOT NULL,
(SELECT id FROM server WHERE server.cachegroup = $1 LIMIT 1) IS NOT NULL,
(SELECT id FROM cachegroup WHERE cachegroup.parent_cachegroup_id = $1 LIMIT 1) IS NOT NULL,
(SELECT id FROM cachegroup WHERE cachegroup.secondary_parent_cachegroup_id = $1 LIMIT 1) IS NOT NULL,
(SELECT id FROM asn WHERE cachegroup = $1 LIMIT 1) IS NOT NULL;`

err := tx.QueryRow(query, ID).Scan(&usedByServer, &usedByParent, &usedBySecondaryParent, &usedByASN)
err := tx.QueryRow(query, ID).Scan(&usedByTopology, &usedByServer, &usedByParent, &usedBySecondaryParent, &usedByASN)
if err != nil {
log.Errorf("received error: %++v from query execution", err)
return false, err
}
//Only return the immediate error
if usedByTopology {
return true, errors.New("cachegroup is in use by one or more topologies")
}
if usedByServer {
return true, errors.New("cachegroup is in use by one or more servers")
}
Expand Down Expand Up @@ -423,10 +428,11 @@ func (cg *TOCacheGroup) Read() ([]interface{}, error, error, int) {
// Query Parameters to Database Query column mappings
// see the fields mapped in the SQL query
queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
"id": dbhelpers.WhereColumnInfo{"cachegroup.id", api.IsInt},
"name": dbhelpers.WhereColumnInfo{"cachegroup.name", nil},
"shortName": dbhelpers.WhereColumnInfo{"cachegroup.short_name", nil},
"type": dbhelpers.WhereColumnInfo{"cachegroup.type", nil},
"id": {"cachegroup.id", api.IsInt},
"name": {"cachegroup.name", nil},
"shortName": {"cachegroup.short_name", nil},
"type": {"cachegroup.type", nil},
"topology": {"topology_cachegroup.topology", nil},
}
where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(cg.ReqInfo.Params, queryParamsToQueryCols)
if len(errs) > 0 {
Expand Down Expand Up @@ -658,7 +664,9 @@ 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`
LEFT JOIN cachegroup AS cgs ON cachegroup.secondary_parent_cachegroup_id = cgs.id
LEFT JOIN topology_cachegroup ON cachegroup.name = topology_cachegroup.cachegroup
`
}

func multipleCacheGroupsWhere() string {
Expand Down