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

ocket8888 pushed a commit to branch 5.1.x
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/5.1.x by this push:
     new 82026c4  Disallow updating the Type of a Cache Group if it is in use 
by a (#5483) (#5517)
82026c4 is described below

commit 82026c494b68a32b256882fcdfc3a72a8e64c53a
Author: ocket8888 <[email protected]>
AuthorDate: Mon Feb 15 15:31:50 2021 -0500

    Disallow updating the Type of a Cache Group if it is in use by a (#5483) 
(#5517)
    
    Topology
    
    (cherry picked from commit 830f32e2532303ccf4f3da66d0065b54eac38277)
    
    Co-authored-by: Zach Hoffman <[email protected]>
---
 CHANGELOG.md                                       |  1 +
 .../traffic_ops_golang/cachegroup/cachegroups.go   | 74 ++++++++++++++++++++++
 .../cachegroup/cachegroups_test.go                 |  8 ++-
 3 files changed, 81 insertions(+), 2 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2826601..7ea5383 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -42,6 +42,7 @@ The format is based on [Keep a 
Changelog](http://keepachangelog.com/en/1.0.0/).
 - [#5390](https://github.com/apache/trafficcontrol/issues/5390) - Improve the 
way TO deals with delivery service server assignments
 - [#5339](https://github.com/apache/trafficcontrol/issues/5339) - Ensure 
Changelog entries for SSL key changes
 - [#5461](https://github.com/apache/trafficcontrol/issues/5461) - Fixed 
steering endpoint to be ordered consistently
+- [#5395](https://github.com/apache/trafficcontrol/issues/5395) - Added 
validation to prevent changing the Type any Cache Group that is in use by a 
Topology
 
 ### Changed
 - Refactored the Traffic Ops Go client internals so that all public methods 
have a consistent behavior/implementation
diff --git a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go 
b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
index bf80ba3..70ba7c5 100644
--- a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
+++ b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
@@ -158,6 +158,79 @@ func IsValidParentCachegroupID(id *int) bool {
        return false
 }
 
+// ValidateTypeInTopology validates cachegroup updates to ensure the type of 
the cachegroup does not change
+// if it is assigned to a topology.
+func (cg *TOCacheGroup) ValidateTypeInTopology() error {
+       userErr := fmt.Errorf("unable to check whether cachegroup %s is used in 
any topologies", *cg.Name)
+
+       // language=SQL
+       const previousTypeQuery = `
+       SELECT t.id
+       FROM cachegroup c
+       JOIN "type" t ON c."type" = t.id
+       WHERE c.id = $1
+       `
+       var previousTypeID int
+       // We only run this validation on PUT, not POST
+       if cg.ID == nil {
+               return nil
+       }
+       err := cg.ReqInfo.Tx.QueryRow(previousTypeQuery, 
*cg.ID).Scan(&previousTypeID)
+       // Cachegroup does not exist in the database yet
+       if err == sql.ErrNoRows {
+               return nil
+       }
+       if err != nil {
+               log.Errorf("%s: getting the previous type of cachegroup %s: 
%s", userErr.Error(), *cg.Name, err.Error())
+               return userErr
+       }
+       if cg.TypeID == nil || *cg.TypeID == previousTypeID {
+               return nil
+       }
+
+       // language=SQL
+       const usedInTopologyQuery = `
+       SELECT EXISTS (SELECT
+       FROM topology_cachegroup tc
+       WHERE tc.cachegroup = $1)
+       `
+       var usedInTopology bool
+       err = cg.ReqInfo.Tx.QueryRow(usedInTopologyQuery, 
*cg.Name).Scan(&usedInTopology)
+       if err != nil {
+               log.Errorf("%s: querying topology_cachegroup by cachegroup 
name: %s", userErr.Error(), err.Error())
+               return userErr
+       }
+       if !usedInTopology {
+               return nil
+       }
+
+       // language=SQL
+       const readableTypesQuery = `
+       SELECT t.id, t."name"
+       FROM "type" t
+       WHERE id = $1 OR id = $2
+       `
+       rows, err := cg.ReqInfo.Tx.Query(readableTypesQuery, previousTypeID, 
*cg.TypeID)
+       if err != nil {
+               log.Errorf("%s: querying type names: %s", userErr.Error(), 
err.Error())
+               return userErr
+       }
+       typeNameByID := map[int]string{}
+       for rows.Next() {
+               var typeID int
+               var typeName string
+               err = rows.Scan(&typeID, &typeName)
+               if err != nil {
+                       log.Errorf("%s: scanning type names: %s", 
userErr.Error(), err.Error())
+                       return userErr
+               }
+               typeNameByID[typeID] = typeName
+       }
+       log.Close(rows, "error closing rows for type names")
+
+       return fmt.Errorf("cannot change type of cachegroup %s from %s to %s 
because it is in use by a topology", *cg.Name, typeNameByID[previousTypeID], 
typeNameByID[*cg.TypeID])
+}
+
 // Validate fulfills the api.Validator interface
 func (cg TOCacheGroup) Validate() error {
        if _, err := tc.ValidateTypeID(cg.ReqInfo.Tx.Tx, cg.TypeID, 
"cachegroup"); err != nil {
@@ -196,6 +269,7 @@ func (cg TOCacheGroup) Validate() error {
                "parentCacheGroupID":          
validation.Validate(cg.ParentCachegroupID, validation.Min(1)),
                "secondaryParentCachegroupID": 
validation.Validate(cg.SecondaryParentCachegroupID, validation.Min(1)),
                "localizationMethods":         
validation.Validate(cg.LocalizationMethods, 
validation.By(tovalidate.IsPtrToSliceOfUniqueStringersICase("CZ", "DEEP_CZ", 
"GEO"))),
+               "type":                        cg.ValidateTypeInTopology(),
        }
        return util.JoinErrs(tovalidate.ToErrors(errs))
 }
diff --git a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups_test.go 
b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups_test.go
index 24ab463..0139958 100644
--- a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups_test.go
+++ b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups_test.go
@@ -203,7 +203,7 @@ func TestValidate(t *testing.T) {
        rows.AddRow("EDGE_LOC", "cachegroup")
 
        mock.ExpectBegin()
-       mock.ExpectQuery("SELECT").WillReturnRows(rows)
+       mock.ExpectQuery("SELECT\\s+name,\\s+use_in_table").WillReturnRows(rows)
        tx := db.MustBegin()
        reqInfo := api.APIInfo{Tx: tx}
 
@@ -239,6 +239,7 @@ func TestValidate(t *testing.T) {
                errors.New(`'longitude' Must be a floating point number within 
the range +-180`),
                errors.New(`'name' invalid characters found - Use alphanumeric 
. or - or _ .`),
                errors.New(`'shortName' invalid characters found - Use 
alphanumeric . or - or _ .`),
+               errors.New(`'type' unable to check whether cachegroup 
not!a!valid!cachegroup is used in any topologies`),
        })
 
        if !reflect.DeepEqual(expectedErrs, errs) {
@@ -246,7 +247,10 @@ func TestValidate(t *testing.T) {
        }
 
        rows.AddRow("EDGE_LOC", "cachegroup")
-       mock.ExpectQuery("SELECT").WillReturnRows(rows)
+       mock.ExpectQuery("SELECT\\s+name,\\s+use_in_table").WillReturnRows(rows)
+
+       rows = sqlmock.NewRows([]string{"id", "name"})
+       
mock.ExpectQuery("SELECT\\s+t\\.id\\s+FROM\\s+cachegroup").WillReturnRows(rows)
 
        //  valid name, shortName latitude, longitude
        nm = "This.is.2.a-Valid---Cachegroup."

Reply via email to