dewrich closed pull request #2557: Cachegroups localization policy
URL: https://github.com/apache/trafficcontrol/pull/2557
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/lib/go-tc/crconfig.go b/lib/go-tc/crconfig.go
index 18133bf0c..1cf10d5de 100644
--- a/lib/go-tc/crconfig.go
+++ b/lib/go-tc/crconfig.go
@@ -151,18 +151,16 @@ type CRConfigDispersion struct {
        Shuffled bool `json:"shuffled,string"`
 }
 
-
 type CRConfigBackupLocations struct {
        FallbackToClosest bool     `json:"fallbackToClosest,string"`
        List              []string `json:"list,omitempty"`
-
 }
 
 type CRConfigLatitudeLongitude struct {
-       Lat               float64                 `json:"latitude"`
-       Lon               float64                 `json:"longitude"`
-       BackupLocations   CRConfigBackupLocations 
`json:"backupLocations,omitempty"`
-
+       Lat                 float64                 `json:"latitude"`
+       Lon                 float64                 `json:"longitude"`
+       BackupLocations     CRConfigBackupLocations 
`json:"backupLocations,omitempty"`
+       LocalizationMethods []LocalizationMethod    `json:"localizationMethods"`
 }
 
 type CRConfigLatitudeLongitudeShort struct {
diff --git a/lib/go-tc/enum.go b/lib/go-tc/enum.go
index 7b2aee03d..4ab943f1c 100644
--- a/lib/go-tc/enum.go
+++ b/lib/go-tc/enum.go
@@ -29,8 +29,10 @@ package tc
  */
 
 import (
+       "database/sql/driver"
        "encoding/json"
        "errors"
+       "fmt"
        "strconv"
        "strings"
 )
@@ -186,6 +188,83 @@ func CacheStatusFromString(s string) CacheStatus {
        }
 }
 
+// LocalizationMethod represents an enabled localization method for a 
cachegroup. The string values of this type should match the Traffic Ops values.
+type LocalizationMethod string
+
+const (
+       LocalizationMethodCZ      = LocalizationMethod("CZ")
+       LocalizationMethodDeepCZ  = LocalizationMethod("DEEP_CZ")
+       LocalizationMethodGeo     = LocalizationMethod("GEO")
+       LocalizationMethodInvalid = LocalizationMethod("INVALID")
+)
+
+// String returns a string representation of this localization method
+func (m LocalizationMethod) String() string {
+       switch m {
+       case LocalizationMethodCZ:
+               return string(m)
+       case LocalizationMethodDeepCZ:
+               return string(m)
+       case LocalizationMethodGeo:
+               return string(m)
+       default:
+               return "INVALID"
+       }
+}
+
+func LocalizationMethodFromString(s string) LocalizationMethod {
+       switch strings.ToLower(s) {
+       case "cz":
+               return LocalizationMethodCZ
+       case "deep_cz":
+               return LocalizationMethodDeepCZ
+       case "geo":
+               return LocalizationMethodGeo
+       default:
+               return LocalizationMethodInvalid
+       }
+}
+
+func (m *LocalizationMethod) UnmarshalJSON(data []byte) error {
+       if string(data) == "null" {
+               return errors.New("LocalizationMethod cannot be null")
+       }
+       s, err := strconv.Unquote(string(data))
+       if err != nil {
+               return errors.New(string(data) + " JSON not quoted")
+       }
+       *m = LocalizationMethodFromString(s)
+       if *m == LocalizationMethodInvalid {
+               return errors.New(s + " is not a LocalizationMethod")
+       }
+       return nil
+}
+
+func (m LocalizationMethod) MarshalJSON() ([]byte, error) {
+       return json.Marshal(m.String())
+}
+
+func (m *LocalizationMethod) Scan(value interface{}) error {
+       if value == nil {
+               return errors.New("LocalizationMethod cannot be null")
+       }
+       sv, err := driver.String.ConvertValue(value)
+       if err != nil {
+               return errors.New("failed to scan LocalizationMethod: " + 
err.Error())
+       }
+
+       switch v := sv.(type) {
+       case []byte:
+               *m = LocalizationMethodFromString(string(v))
+               if *m == LocalizationMethodInvalid {
+                       return errors.New(string(v) + " is not a valid 
LocalizationMethod")
+               }
+               return nil
+       default:
+               return fmt.Errorf("failed to scan LocalizationMethod, 
unsupported input type: %T", value)
+       }
+}
+
 // DeepCachingType represents a Delivery Service's deep caching type. The 
string values of this type should match the Traffic Ops values.
 type DeepCachingType string
 
diff --git a/lib/go-tc/tovalidate/rules.go b/lib/go-tc/tovalidate/rules.go
index 940424c99..f5d055b73 100644
--- a/lib/go-tc/tovalidate/rules.go
+++ b/lib/go-tc/tovalidate/rules.go
@@ -15,6 +15,7 @@ package tovalidate
 import (
        "errors"
        "fmt"
+       "reflect"
        "strings"
 )
 
@@ -51,6 +52,52 @@ func IsOneOfStringICase(set ...string) func(string) bool {
        }
 }
 
+// IsPtrToSliceOfUniqueStringersICase returns a validator function which 
returns an error if the argument is a non-nil
+// pointer to a slice of Stringers whose String() values are not in the set of 
strings or there are duplicate strings
+func IsPtrToSliceOfUniqueStringersICase(set ...string) func(interface{}) error 
{
+       lowcased := make(map[string]bool, len(set))
+       for _, s := range set {
+               lowcased[strings.ToLower(s)] = true
+       }
+       return func(slicePtr interface{}) error {
+
+               rv := reflect.ValueOf(slicePtr)
+               if rv.Kind() != reflect.Ptr {
+                       return fmt.Errorf("%T is not a pointer", slicePtr)
+               }
+
+               if rv.IsNil() {
+                       return nil
+               }
+
+               slice := rv.Elem()
+               if slice.Kind() != reflect.Slice {
+                       return fmt.Errorf("%T is not a slice", slicePtr)
+               }
+
+               seen := make(map[string]bool, len(set))
+
+               l := slice.Len()
+               for i := 0; i < l; i++ {
+                       if item := slice.Index(i).Interface(); item != nil {
+                               s, ok := item.(fmt.Stringer)
+                               if !ok {
+                                       return fmt.Errorf("%T is not a pointer 
to a slice of Stringers", slicePtr)
+                               }
+                               lc := strings.ToLower(s.String())
+                               if !lowcased[lc] {
+                                       return fmt.Errorf("'%s' is not one of 
%v", lc, set)
+                               }
+                               if _, ok := seen[lc]; ok {
+                                       return fmt.Errorf("duplicate value 
found: '%s'", lc)
+                               }
+                               seen[lc] = true
+                       }
+               }
+               return nil
+       }
+}
+
 func IsGreaterThanZero(value interface{}) error {
        switch v := value.(type) {
        case *int:
diff --git a/lib/go-tc/v13/cachegroups.go b/lib/go-tc/v13/cachegroups.go
index 6eb7c0fdf..c2fce59b2 100644
--- a/lib/go-tc/v13/cachegroups.go
+++ b/lib/go-tc/v13/cachegroups.go
@@ -41,35 +41,37 @@ type CacheGroupDetailResponse struct {
 
 // CacheGroup contains information about a given Cachegroup in Traffic Ops.
 type CacheGroup 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"`
-       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                 tc.TimeNoMod `json:"lastUpdated" 
db:"last_updated"`
+       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         []tc.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                 tc.TimeNoMod            `json:"lastUpdated" 
db:"last_updated"`
 }
 
 type CacheGroupNullable 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"`
-       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                 *tc.TimeNoMod `json:"lastUpdated" 
db:"last_updated"`
+       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         *[]tc.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                 *tc.TimeNoMod            
`json:"lastUpdated" db:"last_updated"`
 }
 
 type CachegroupTrimmedName struct {
diff --git 
a/traffic_ops/app/db/migrations/20180806000000_add_cachegroup_localization_method.sql
 
b/traffic_ops/app/db/migrations/20180806000000_add_cachegroup_localization_method.sql
new file mode 100644
index 000000000..da1983223
--- /dev/null
+++ 
b/traffic_ops/app/db/migrations/20180806000000_add_cachegroup_localization_method.sql
@@ -0,0 +1,33 @@
+/*
+       Licensed under the Apache License, Version 2.0 (the "License");
+       you may not use this file except in compliance with the License.
+       You may obtain a copy of the License at
+
+           http://www.apache.org/licenses/LICENSE-2.0
+
+       Unless required by applicable law or agreed to in writing, software
+       distributed under the License is distributed on an "AS IS" BASIS,
+       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+       See the License for the specific language governing permissions and
+       limitations under the License.
+*/
+
+-- +goose Up
+-- SQL in section 'Up' is executed when this migration is applied
+
+CREATE TYPE localization_method AS ENUM ('CZ', 'DEEP_CZ', 'GEO');
+
+CREATE TABLE cachegroup_localization_method (
+    cachegroup bigint NOT NULL REFERENCES cachegroup(id) ON DELETE CASCADE,
+    method localization_method NOT NULL,
+    UNIQUE(cachegroup, method)
+);
+
+CREATE INDEX cachegroup_localization_method_cachegroup_fkey ON 
cachegroup_localization_method USING btree (cachegroup);
+
+-- +goose Down
+-- SQL section 'Down' is executed when this migration is rolled back
+
+DROP TABLE IF EXISTS cachegroup_localization_method;
+
+DROP TYPE IF EXISTS localization_method;
diff --git a/traffic_ops/testing/api/v13/cachegroups_test.go 
b/traffic_ops/testing/api/v13/cachegroups_test.go
index 9b6a0c1bb..9891756a4 100644
--- a/traffic_ops/testing/api/v13/cachegroups_test.go
+++ b/traffic_ops/testing/api/v13/cachegroups_test.go
@@ -17,9 +17,11 @@ package v13
 
 import (
        "fmt"
+       "reflect"
        "testing"
 
        "github.com/apache/trafficcontrol/lib/go-log"
+       "github.com/apache/trafficcontrol/lib/go-tc"
        "github.com/apache/trafficcontrol/lib/go-tc/v13"
        "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils"
 )
@@ -157,6 +159,26 @@ func UpdateTestCacheGroups(t *testing.T) {
                failed = true
        }
 
+       // test localizationMethods
+       expectedMethods := []tc.LocalizationMethod{tc.LocalizationMethodGeo}
+       cg.LocalizationMethods = &expectedMethods
+       updResp, _, err = TOSession.UpdateCacheGroupNullableByID(*cg.ID, cg)
+       if err != nil {
+               t.Errorf("cannot UPDATE CacheGroup by id: %v - %v\n", err, 
updResp)
+               failed = true
+       }
+
+       resp, _, err = TOSession.GetCacheGroupNullableByID(*cg.ID)
+       if err != nil {
+               t.Errorf("cannot GET CacheGroup by id: '%d', %v\n", *cg.ID, err)
+               failed = true
+       }
+       cg = resp[0]
+       if !reflect.DeepEqual(expectedMethods, *cg.LocalizationMethods) {
+               t.Errorf("failed to update localizationMethods (expected = %v, 
actual = %v\n", expectedMethods, *cg.LocalizationMethods)
+               failed = true
+       }
+
        if !failed {
                log.Debugln("UpdateTestCacheGroups() PASSED: ")
        }
diff --git a/traffic_ops/testing/api/v13/tc-fixtures.json 
b/traffic_ops/testing/api/v13/tc-fixtures.json
index e1b221289..e9cdfd96e 100644
--- a/traffic_ops/testing/api/v13/tc-fixtures.json
+++ b/traffic_ops/testing/api/v13/tc-fixtures.json
@@ -38,6 +38,11 @@
             "parentCachegroupName": "parentCachegroup",
             "secondaryParentCachegroupName": "secondaryCachegroup",
             "shortName": "cg1",
+            "localizationMethods": [
+                "CZ",
+                "DEEP_CZ",
+                "GEO"
+            ],
             "typeName": "EDGE_LOC"
         },
         {
diff --git a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go 
b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
index 23a315417..d9e7ab4e6 100644
--- a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
+++ b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups.go
@@ -175,6 +175,7 @@ func (cg TOCacheGroup) Validate() error {
                "longitude":                   
validation.Validate(cg.Longitude, validation.Min(-180.0).Error(longitudeErr), 
validation.Max(180.0).Error(longitudeErr)),
                "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"))),
        }
        return util.JoinErrs(tovalidate.ToErrors(errs))
 }
@@ -236,10 +237,30 @@ func (cg *TOCacheGroup) Create() (error, tc.ApiErrorType) 
{
                return tc.DBError, tc.SystemError
        }
        cg.SetID(id)
+       if err = cg.createLocalizationMethods(); err != nil {
+               log.Errorln("creating cachegroup: " + err.Error())
+               return tc.DBError, tc.SystemError
+       }
        cg.LastUpdated = &lastUpdated
        return nil, tc.NoError
 }
 
+func (cg *TOCacheGroup) createLocalizationMethods() error {
+       q := `DELETE FROM cachegroup_localization_method where cachegroup = $1`
+       if _, err := cg.ReqInfo.Tx.Exec(q, *cg.ID); err != nil {
+               return fmt.Errorf("unable to delete 
cachegroup_localization_methods for cachegroup %d: %s", *cg.ID, err.Error())
+       }
+       if cg.LocalizationMethods != nil {
+               q = `INSERT INTO cachegroup_localization_method (cachegroup, 
method) VALUES ($1, $2)`
+               for _, method := range *cg.LocalizationMethods {
+                       if _, err := cg.ReqInfo.Tx.Exec(q, *cg.ID, 
method.String()); err != nil {
+                               return fmt.Errorf("unable to insert 
cachegroup_localization_methods for cachegroup %d: %s", *cg.ID, err.Error())
+                       }
+               }
+       }
+       return nil
+}
+
 func (cg *TOCacheGroup) createCoordinate() (*int, error) {
        var coordinateID *int
        if cg.Latitude != nil && cg.Longitude != nil {
@@ -322,17 +343,33 @@ func (cg *TOCacheGroup) Read(parameters 
map[string]string) ([]interface{}, []err
        }
        defer rows.Close()
 
-       CacheGroups := []interface{}{}
+       cacheGroups := []interface{}{}
        for rows.Next() {
                var s TOCacheGroup
-               if err = rows.StructScan(&s); err != nil {
+               lms := make([]tc.LocalizationMethod, 0)
+               if err = rows.Scan(
+                       &s.ID,
+                       &s.Name,
+                       &s.ShortName,
+                       &s.Latitude,
+                       &s.Longitude,
+                       pq.Array(&lms),
+                       &s.ParentCachegroupID,
+                       &s.ParentName,
+                       &s.SecondaryParentCachegroupID,
+                       &s.SecondaryParentName,
+                       &s.Type,
+                       &s.TypeID,
+                       &s.LastUpdated,
+               ); err != nil {
                        log.Errorf("error parsing CacheGroup rows: %v", err)
                        return nil, []error{tc.DBError}, tc.SystemError
                }
-               CacheGroups = append(CacheGroups, s)
+               s.LocalizationMethods = &lms
+               cacheGroups = append(cacheGroups, s)
        }
 
-       return CacheGroups, []error{}, tc.NoError
+       return cacheGroups, []error{}, tc.NoError
 }
 
 //The TOCacheGroup implementation of the Updater interface
@@ -389,6 +426,10 @@ func (cg *TOCacheGroup) Update() (error, tc.ApiErrorType) {
                        return fmt.Errorf("this update affected too many rows: 
%d", rowsAffected), tc.SystemError
                }
        }
+       if err = cg.createLocalizationMethods(); err != nil {
+               log.Errorln("updating cachegroup: " + err.Error())
+               return tc.DBError, tc.SystemError
+       }
        return nil, tc.NoError
 }
 
@@ -507,6 +548,7 @@ 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),
 cachegroup.parent_cachegroup_id,
 cgp.name AS parent_cachegroup_name,
 cachegroup.secondary_parent_cachegroup_id,
diff --git a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups_test.go 
b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups_test.go
index 645f990e3..2335f705e 100644
--- a/traffic_ops/traffic_ops_golang/cachegroup/cachegroups_test.go
+++ b/traffic_ops/traffic_ops_golang/cachegroup/cachegroups_test.go
@@ -46,6 +46,11 @@ func getTestCacheGroups() []v13.CacheGroup {
                Longitude:                   90.7,
                ParentCachegroupID:          2,
                SecondaryParentCachegroupID: 2,
+               LocalizationMethods: []tc.LocalizationMethod{
+                       tc.LocalizationMethodDeepCZ,
+                       tc.LocalizationMethodCZ,
+                       tc.LocalizationMethodGeo,
+               },
                Type:        "EDGE_LOC",
                TypeID:      6,
                LastUpdated: tc.TimeNoMod{Time: time.Now()},
@@ -60,6 +65,11 @@ func getTestCacheGroups() []v13.CacheGroup {
                Longitude:                   90.7,
                ParentCachegroupID:          1,
                SecondaryParentCachegroupID: 1,
+               LocalizationMethods: []tc.LocalizationMethod{
+                       tc.LocalizationMethodDeepCZ,
+                       tc.LocalizationMethodCZ,
+                       tc.LocalizationMethodGeo,
+               },
                Type:        "MID_LOC",
                TypeID:      7,
                LastUpdated: tc.TimeNoMod{Time: time.Now()},
@@ -80,8 +90,21 @@ func TestReadCacheGroups(t *testing.T) {
        defer db.Close()
 
        testCGs := getTestCacheGroups()
-       cols := test.ColsFromStructByTag("db", v13.CacheGroup{})
-       rows := sqlmock.NewRows(cols)
+       rows := sqlmock.NewRows([]string{
+               "id",
+               "name",
+               "short_name",
+               "latitude",
+               "longitude",
+               "localization_methods",
+               "parent_cachegroup_id",
+               "parent_cachegroup_name",
+               "secondary_parent_cachegroup_id",
+               "secondary_parent_cachegroup_name",
+               "type_name",
+               "type_id",
+               "last_updated",
+       })
 
        for _, ts := range testCGs {
                rows = rows.AddRow(
@@ -90,11 +113,11 @@ func TestReadCacheGroups(t *testing.T) {
                        ts.ShortName,
                        ts.Latitude,
                        ts.Longitude,
-                       ts.ParentName,
+                       []byte("{DEEP_CZ,CZ,GEO}"),
                        ts.ParentCachegroupID,
-                       ts.SecondaryParentName,
+                       ts.ParentName,
                        ts.SecondaryParentCachegroupID,
-                       ts.FallbackToClosest,
+                       ts.SecondaryParentName,
                        ts.Type,
                        ts.TypeID,
                        ts.LastUpdated,
@@ -177,22 +200,26 @@ func TestValidate(t *testing.T) {
        sn := "not!a!valid!shortname"
        la := -190.0
        lo := -190.0
+       lm := []tc.LocalizationMethod{tc.LocalizationMethodGeo, 
tc.LocalizationMethodInvalid}
        ty := "EDGE_LOC"
        ti := 6
        lu := tc.TimeNoMod{Time: time.Now()}
-       c := TOCacheGroup{ReqInfo: &reqInfo, CacheGroupNullable: 
v13.CacheGroupNullable{ID: &id,
-               Name:        &nm,
-               ShortName:   &sn,
-               Latitude:    &la,
-               Longitude:   &lo,
-               Type:        &ty,
-               TypeID:      &ti,
-               LastUpdated: &lu,
+       c := TOCacheGroup{ReqInfo: &reqInfo, CacheGroupNullable: 
v13.CacheGroupNullable{
+               ID:                  &id,
+               Name:                &nm,
+               ShortName:           &sn,
+               Latitude:            &la,
+               Longitude:           &lo,
+               LocalizationMethods: &lm,
+               Type:                &ty,
+               TypeID:              &ti,
+               LastUpdated:         &lu,
        }}
        errs := 
util.JoinErrsStr(test.SortErrors(test.SplitErrors(c.Validate())))
 
        expectedErrs := util.JoinErrsStr([]error{
                errors.New(`'latitude' Must be a floating point number within 
the range +-90`),
+               errors.New(`'localizationMethods' 'invalid' is not one of [CZ 
DEEP_CZ GEO]`),
                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 _ .`),
@@ -210,14 +237,17 @@ func TestValidate(t *testing.T) {
        sn = `awesome-cachegroup`
        la = 90.0
        lo = 90.0
-       c = TOCacheGroup{ReqInfo: &reqInfo, CacheGroupNullable: 
v13.CacheGroupNullable{ID: &id,
-               Name:        &nm,
-               ShortName:   &sn,
-               Latitude:    &la,
-               Longitude:   &lo,
-               Type:        &ty,
-               TypeID:      &ti,
-               LastUpdated: &lu,
+       lm = []tc.LocalizationMethod{tc.LocalizationMethodGeo, 
tc.LocalizationMethodCZ, tc.LocalizationMethodDeepCZ}
+       c = TOCacheGroup{ReqInfo: &reqInfo, CacheGroupNullable: 
v13.CacheGroupNullable{
+               ID:                  &id,
+               Name:                &nm,
+               ShortName:           &sn,
+               Latitude:            &la,
+               Longitude:           &lo,
+               LocalizationMethods: &lm,
+               Type:                &ty,
+               TypeID:              &ti,
+               LastUpdated:         &lu,
        }}
        err = c.Validate()
        if err != nil {
diff --git a/traffic_ops/traffic_ops_golang/crconfig/edgelocations.go 
b/traffic_ops/traffic_ops_golang/crconfig/edgelocations.go
index 364d33608..64c216bca 100644
--- a/traffic_ops/traffic_ops_golang/crconfig/edgelocations.go
+++ b/traffic_ops/traffic_ops_golang/crconfig/edgelocations.go
@@ -24,6 +24,7 @@ import (
        "errors"
 
        "github.com/apache/trafficcontrol/lib/go-tc"
+       "github.com/lib/pq"
 )
 
 func makeLocations(cdn string, db *sql.DB) 
(map[string]tc.CRConfigLatitudeLongitude, 
map[string]tc.CRConfigLatitudeLongitude, error) {
@@ -32,7 +33,9 @@ func makeLocations(cdn string, db *sql.DB) 
(map[string]tc.CRConfigLatitudeLongit
 
        // TODO test whether it's faster to do a single query, joining lat/lon 
into servers
        q := `
-select cg.name, cg.id, t.name as type, co.latitude, co.longitude, 
cg.fallback_to_closest from cachegroup as cg
+select cg.name, cg.id, t.name as type, co.latitude, co.longitude, 
cg.fallback_to_closest,
+(SELECT array_agg(method::text) as localization_methods FROM 
cachegroup_localization_method WHERE cachegroup = cg.id)
+from cachegroup as cg
 left join coordinate as co on co.id = cg.coordinate
 inner join server as s on s.cachegroup = cg.id
 inner join type as t on t.id = s.type
@@ -54,9 +57,13 @@ and (st.name = 'REPORTED' or st.name = 'ONLINE' or st.name = 
'ADMIN_DOWN')
                ttype := ""
                var fallbackToClosest *bool
                latlon := tc.CRConfigLatitudeLongitude{}
-               if err := rows.Scan(&cachegroup, &primaryCacheID, &ttype, 
&latlon.Lat, &latlon.Lon, &fallbackToClosest); err != nil {
+               if err := rows.Scan(&cachegroup, &primaryCacheID, &ttype, 
&latlon.Lat, &latlon.Lon, &fallbackToClosest, 
pq.Array(&latlon.LocalizationMethods)); err != nil {
                        return nil, nil, errors.New("Error scanning cachegroup: 
" + err.Error())
                }
+               if len(latlon.LocalizationMethods) == 0 {
+                       // to keep current default behavior when 
localizationMethods is unset/empty, enable all current localization methods
+                       latlon.LocalizationMethods = 
[]tc.LocalizationMethod{tc.LocalizationMethodGeo, tc.LocalizationMethodCZ, 
tc.LocalizationMethodDeepCZ}
+               }
                if ttype == RouterTypeName {
                        routerLocs[cachegroup] = latlon
                } else {
diff --git a/traffic_ops/traffic_ops_golang/crconfig/edgelocations_test.go 
b/traffic_ops/traffic_ops_golang/crconfig/edgelocations_test.go
index b0cd674dc..3465fb710 100644
--- a/traffic_ops/traffic_ops_golang/crconfig/edgelocations_test.go
+++ b/traffic_ops/traffic_ops_golang/crconfig/edgelocations_test.go
@@ -30,22 +30,38 @@ import (
 
 func ExpectedMakeLocations() (map[string]tc.CRConfigLatitudeLongitude, 
map[string]tc.CRConfigLatitudeLongitude) {
        return map[string]tc.CRConfigLatitudeLongitude{
-                       "cache0": tc.CRConfigLatitudeLongitude{Lat: 
*randFloat64(), Lon: *randFloat64()},
-                       "cache1": tc.CRConfigLatitudeLongitude{Lat: 
*randFloat64(), Lon: *randFloat64()},
+                       "cache0": tc.CRConfigLatitudeLongitude{
+                               Lat:                 *randFloat64(),
+                               Lon:                 *randFloat64(),
+                               LocalizationMethods: 
[]tc.LocalizationMethod{tc.LocalizationMethodCZ},
+                       },
+                       "cache1": tc.CRConfigLatitudeLongitude{
+                               Lat:                 *randFloat64(),
+                               Lon:                 *randFloat64(),
+                               LocalizationMethods: 
[]tc.LocalizationMethod{tc.LocalizationMethodCZ},
+                       },
                },
                map[string]tc.CRConfigLatitudeLongitude{
-                       "router0": tc.CRConfigLatitudeLongitude{Lat: 
*randFloat64(), Lon: *randFloat64()},
-                       "router1": tc.CRConfigLatitudeLongitude{Lat: 
*randFloat64(), Lon: *randFloat64()},
+                       "router0": tc.CRConfigLatitudeLongitude{
+                               Lat:                 *randFloat64(),
+                               Lon:                 *randFloat64(),
+                               LocalizationMethods: 
[]tc.LocalizationMethod{tc.LocalizationMethodGeo, tc.LocalizationMethodCZ, 
tc.LocalizationMethodDeepCZ},
+                       },
+                       "router1": tc.CRConfigLatitudeLongitude{
+                               Lat:                 *randFloat64(),
+                               Lon:                 *randFloat64(),
+                               LocalizationMethods: 
[]tc.LocalizationMethod{tc.LocalizationMethodGeo, tc.LocalizationMethodCZ, 
tc.LocalizationMethodDeepCZ},
+                       },
                }
 }
 
 func MockMakeLocations(mock sqlmock.Sqlmock, expectedEdgeLocs 
map[string]tc.CRConfigLatitudeLongitude, expectedRouterLocs 
map[string]tc.CRConfigLatitudeLongitude, cdn string) {
-       rows := sqlmock.NewRows([]string{"name", "id", "type", "latitude", 
"longitude", "fallback_to_closest"})
+       rows := sqlmock.NewRows([]string{"name", "id", "type", "latitude", 
"longitude", "fallback_to_closest", "localization_methods"})
        for s, l := range expectedEdgeLocs {
-               rows = rows.AddRow(s, 1, EdgeTypePrefix, l.Lat, l.Lon, false)
+               rows = rows.AddRow(s, 1, EdgeTypePrefix, l.Lat, l.Lon, false, 
[]byte("{CZ}"))
        }
        for s, l := range expectedRouterLocs {
-               rows = rows.AddRow(s, 1, RouterTypeName, l.Lat, l.Lon, false)
+               rows = rows.AddRow(s, 1, RouterTypeName, l.Lat, l.Lon, false, 
nil)
        }
        mock.ExpectQuery("select").WithArgs(cdn).WillReturnRows(rows)
 


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[email protected]


With regards,
Apache Git Services

Reply via email to