This is an automated email from the ASF dual-hosted git repository.
srijeet0406 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 3aaf465079 Override all Delivery Service TTLs in a CDN's Snapshot with
a given value (#7143)
3aaf465079 is described below
commit 3aaf4650795f64c3562639a08a88f4428fea961b
Author: Zach Hoffman <[email protected]>
AuthorDate: Tue Oct 18 17:26:41 2022 +0000
Override all Delivery Service TTLs in a CDN's Snapshot with a given value
(#7143)
* CDN TTLOverride field
* Override all Delivery Service TTLs in a CDN's Snapshot with given value
* Only use TTLOverride field in API version 4.1 and up
* Key fields
* Correct validation message
* Use math.Min() for comparisons
* Remove duplicate word
* Use abbreviation for `CDN`, not glossary
* Refactor version-sensitive formatted query strings
* Reword
* Use Math.Min() in more places
---
CHANGELOG.md | 1 +
docs/source/api/v4/cdns.rst | 8 +-
docs/source/api/v5/cdns.rst | 8 +-
lib/go-tc/cdns.go | 8 ++
lib/go-tc/crconfig.go | 3 +
...22101700451329_add_ttl_override_to_cdn.down.sql | 19 +++++
...2022101700451329_add_ttl_override_to_cdn.up.sql | 19 +++++
traffic_ops/traffic_ops_golang/api/api.go | 8 ++
traffic_ops/traffic_ops_golang/cdn/cdns.go | 66 ++++++++++++---
traffic_ops/traffic_ops_golang/cdn/cdns_test.go | 16 ++--
.../traffic_ops_golang/crconfig/crconfig.go | 4 +-
.../traffic_ops_golang/crconfig/deliveryservice.go | 48 +++++++++--
.../crconfig/deliveryservice_test.go | 95 ++++++++++++++++------
traffic_ops/traffic_ops_golang/crconfig/servers.go | 10 ++-
.../traffic_ops_golang/crconfig/servers_test.go | 19 +++--
.../src/common/modules/form/cdn/form.cdn.tpl.html | 13 +++
traffic_portal/test/integration/Data/cdn.ts | 24 +++++-
.../test/integration/PageObjects/CDNPage.po.ts | 19 ++++-
18 files changed, 314 insertions(+), 74 deletions(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 313b5e3f8b..1007e96db9 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,6 +17,7 @@ The format is based on [Keep a
Changelog](http://keepachangelog.com/en/1.0.0/).
- [#6033](https://github.com/apache/trafficcontrol/issues/6033) *Traffic Ops,
Traffic Portal* Added ability to assign multiple server capabilities to a
server.
- [#7032](https://github.com/apache/trafficcontrol/issues/7032) *Cache Config*
Add t3c-apply flag to use local ATS version for config generation rather than
Server package Parameter, to allow managing the ATS OS package via external
tools. See 'man t3c-apply' and 'man t3c-generate' for details.
- [#7097](https://github.com/apache/trafficcontrol/issues/7097) *Traffic Ops,
Traffic Portal, t3c* Added the `regional` field to Delivery Services, which
affects whether `maxOriginConnections` should be per Cache Group
+- [#2388](https://github.com/apache/trafficcontrol/issues/2388) *Trafic Ops,
Traffic Portal* Added the `TTLOverride` field to CDNs, which lets you override
all TTLs in all Delivery Services of a CDN's snapshot with a single value
### Changed
- [#7063](https://github.com/apache/trafficcontrol/pull/7063) *Traffic Ops*
Python client now uses Traffic Ops API 4.1 by default.
diff --git a/docs/source/api/v4/cdns.rst b/docs/source/api/v4/cdns.rst
index 8e13605df5..9726b9c439 100644
--- a/docs/source/api/v4/cdns.rst
+++ b/docs/source/api/v4/cdns.rst
@@ -67,6 +67,7 @@ Response Structure
:id: The integral, unique identifier for the CDN
:lastUpdated: Date and time when the CDN was last modified in
:ref:`non-rfc-datetime`
:name: The name of the CDN
+:ttlOverride: A :abbr:`TTL (Time To Live)` value, in seconds, that, if set,
overrides all set TTL values on :term:`Delivery Services` in this :abbr:`CDN
(Content Delivery Network)`
.. code-block:: http
:caption: Response Example
@@ -89,7 +90,8 @@ Response Structure
"domainName": "-",
"id": 1,
"lastUpdated": "2018-11-14 18:21:06+00",
- "name": "ALL"
+ "name": "ALL",
+ "ttlOverride": 60
},
{
"dnssecEnabled": false,
@@ -115,6 +117,7 @@ Request Structure
:dnssecEnabled: If ``true``, this CDN will use DNSSEC, if ``false`` it will not
:domainName: The top-level domain (TLD) belonging to the new CDN
:name: Name of the new CDN
+:ttlOverride: A :abbr:`TTL (Time To Live)` value, in seconds, that, if set,
overrides all set TTL values on :term:`Delivery Services` in this :abbr:`CDN
(Content Delivery Network)`
.. code-block:: http
:caption: Request Structure
@@ -135,6 +138,7 @@ Response Structure
:domainName: The top-level domain (TLD) assigned to the newly created CDN
:id: An integral, unique identifier for the newly created CDN
:name: The newly created CDN's name
+:ttlOverride: A :abbr:`TTL (Time To Live)` value, in seconds, that, if set,
overrides all set TTL values on :term:`Delivery Services` in this :abbr:`CDN
(Content Delivery Network)`
.. code-block:: http
@@ -163,5 +167,5 @@ Response Structure
"domainName": "quest",
"id": 3,
"lastUpdated": "2018-11-14 20:49:28+00",
- "name": "test"
+ "name": "test",
}}
diff --git a/docs/source/api/v5/cdns.rst b/docs/source/api/v5/cdns.rst
index d76df84255..5791859b19 100644
--- a/docs/source/api/v5/cdns.rst
+++ b/docs/source/api/v5/cdns.rst
@@ -67,6 +67,7 @@ Response Structure
:id: The integral, unique identifier for the CDN
:lastUpdated: Date and time when the CDN was last modified in
:ref:`non-rfc-datetime`
:name: The name of the CDN
+:ttlOverride: A :abbr:`TTL (Time To Live)` value, in seconds, that, if set,
overrides all set TTL values on :term:`Delivery Services` in this :abbr:`CDN
(Content Delivery Network)`
.. code-block:: http
:caption: Response Example
@@ -89,7 +90,8 @@ Response Structure
"domainName": "-",
"id": 1,
"lastUpdated": "2018-11-14 18:21:06+00",
- "name": "ALL"
+ "name": "ALL",
+ "ttlOverride": 60
},
{
"dnssecEnabled": false,
@@ -115,6 +117,7 @@ Request Structure
:dnssecEnabled: If ``true``, this CDN will use DNSSEC, if ``false`` it will not
:domainName: The top-level domain (TLD) belonging to the new CDN
:name: Name of the new CDN
+:ttlOverride: A :abbr:`TTL (Time To Live)` value, in seconds, that, if set,
overrides all set TTL values on :term:`Delivery Services` in this :abbr:`CDN
(Content Delivery Network)`
.. code-block:: http
:caption: Request Structure
@@ -135,6 +138,7 @@ Response Structure
:domainName: The top-level domain (TLD) assigned to the newly created CDN
:id: An integral, unique identifier for the newly created CDN
:name: The newly created CDN's name
+:ttlOverride: A :abbr:`TTL (Time To Live)` value, in seconds, that, if set,
overrides all set TTL values on :term:`Delivery Services` in this :abbr:`CDN
(Content Delivery Network)`
.. code-block:: http
@@ -163,5 +167,5 @@ Response Structure
"domainName": "quest",
"id": 3,
"lastUpdated": "2018-11-14 20:49:28+00",
- "name": "test"
+ "name": "test",
}}
diff --git a/lib/go-tc/cdns.go b/lib/go-tc/cdns.go
index 9a377bb472..8fe0f70f4c 100644
--- a/lib/go-tc/cdns.go
+++ b/lib/go-tc/cdns.go
@@ -71,6 +71,10 @@ type CDN struct {
//
// required: true
Name string `json:"name" db:"name"`
+
+ // TTLOverride
+ //
+ TTLOverride int `json:"ttlOverride,omitempty" db:"ttl_override"`
}
// CDNNullable is identical to CDN except that its fields are reference values,
@@ -102,6 +106,10 @@ type CDNNullable struct {
//
// required: true
Name *string `json:"name" db:"name"`
+
+ // TTLOverride
+ //
+ TTLOverride *int `json:"ttlOverride,omitempty" db:"ttl_override"`
}
// CDNSSLKeysResponse is the structure of the Traffic Ops API's response to
diff --git a/lib/go-tc/crconfig.go b/lib/go-tc/crconfig.go
index eaa226a5f2..291d8d8505 100644
--- a/lib/go-tc/crconfig.go
+++ b/lib/go-tc/crconfig.go
@@ -58,6 +58,9 @@ type CRConfigConfig struct {
ZoneManagerThreadpoolScale *string
`json:"zonemanager.threadpool.scale,omitempty"`
}
+const CrConfigTTLSOA = "tld.ttls.SOA"
+const CrConfigTTLNS = "tld.ttls.NS"
+
// CRConfigTTL defines the Time-To-Live (TTL) of various record types served by
// Traffic Router.
//
diff --git
a/traffic_ops/app/db/migrations/2022101700451329_add_ttl_override_to_cdn.down.sql
b/traffic_ops/app/db/migrations/2022101700451329_add_ttl_override_to_cdn.down.sql
new file mode 100644
index 0000000000..1640cc53b6
--- /dev/null
+++
b/traffic_ops/app/db/migrations/2022101700451329_add_ttl_override_to_cdn.down.sql
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you 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.
+ */
+
+ALTER TABLE public.cdn
+DROP COLUMN ttl_override;
diff --git
a/traffic_ops/app/db/migrations/2022101700451329_add_ttl_override_to_cdn.up.sql
b/traffic_ops/app/db/migrations/2022101700451329_add_ttl_override_to_cdn.up.sql
new file mode 100644
index 0000000000..99d1db790d
--- /dev/null
+++
b/traffic_ops/app/db/migrations/2022101700451329_add_ttl_override_to_cdn.up.sql
@@ -0,0 +1,19 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with this
+ * work for additional information regarding copyright ownership. The ASF
+ * licenses this file to you 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.
+ */
+
+ALTER TABLE public.cdn
+ADD COLUMN ttl_override BIGINT DEFAULT NULL;
diff --git a/traffic_ops/traffic_ops_golang/api/api.go
b/traffic_ops/traffic_ops_golang/api/api.go
index 0ca5d1df9c..d1e8a9d783 100644
--- a/traffic_ops/traffic_ops_golang/api/api.go
+++ b/traffic_ops/traffic_ops_golang/api/api.go
@@ -799,6 +799,14 @@ type Version struct {
Minor uint64
}
+func (v *Version) LessThan(otherVersion *Version) bool {
+ return v.Major < otherVersion.Major || (v.Major == otherVersion.Major
&& v.Minor < otherVersion.Minor)
+}
+
+func (v *Version) GreaterThanOrEqualTo(otherVersion *Version) bool {
+ return !v.LessThan(otherVersion)
+}
+
// GetRequestedAPIVersion returns a pointer to the requested API Version from
the request if it exists or returns nil otherwise.
func GetRequestedAPIVersion(path string) *Version {
pathParts := strings.Split(path, "/")
diff --git a/traffic_ops/traffic_ops_golang/cdn/cdns.go
b/traffic_ops/traffic_ops_golang/cdn/cdns.go
index 51a357a96d..9b0e44954d 100644
--- a/traffic_ops/traffic_ops_golang/cdn/cdns.go
+++ b/traffic_ops/traffic_ops_golang/cdn/cdns.go
@@ -20,6 +20,7 @@ package cdn
*/
import (
+ "fmt"
"net/http"
"strconv"
"strings"
@@ -53,18 +54,22 @@ func (v *TOCDN) SelectMaxLastUpdatedQuery(where, orderBy,
pagination, tableName
}
func (v *TOCDN) SetLastUpdated(t tc.TimeNoMod) { v.LastUpdated = &t }
-func (v *TOCDN) InsertQuery() string { return insertQuery() }
+func (v *TOCDN) InsertQuery() string { return
insertQuery(v.APIInfo().Version) }
func (v *TOCDN) NewReadObj() interface{} { return &tc.CDNNullable{} }
-func (v *TOCDN) SelectQuery() string { return selectQuery() }
+func (v *TOCDN) SelectQuery() string { return
selectQuery(v.APIInfo().Version) }
func (v *TOCDN) ParamColumns() map[string]dbhelpers.WhereColumnInfo {
- return map[string]dbhelpers.WhereColumnInfo{
+ columnInfo := map[string]dbhelpers.WhereColumnInfo{
"domainName": dbhelpers.WhereColumnInfo{Column:
"domain_name"},
"dnssecEnabled": dbhelpers.WhereColumnInfo{Column:
"dnssec_enabled"},
"id": dbhelpers.WhereColumnInfo{Column: "id",
Checker: api.IsInt},
"name": dbhelpers.WhereColumnInfo{Column: "name"},
}
+ if v.APIInfo().Version.GreaterThanOrEqualTo(&api.Version{Major: 4,
Minor: 1}) {
+ columnInfo["ttlOverride"] = dbhelpers.WhereColumnInfo{Column:
"ttl_override", Checker: api.IsInt}
+ }
+ return columnInfo
}
-func (v *TOCDN) UpdateQuery() string { return updateQuery() }
+func (v *TOCDN) UpdateQuery() string { return updateQuery(v.APIInfo().Version)
}
func (v *TOCDN) DeleteQuery() string { return deleteQuery() }
func (cdn TOCDN) GetKeyFieldsInfo() []api.KeyFieldInfo {
@@ -128,11 +133,17 @@ func (cdn TOCDN) Validate() (error, error) {
"name": validation.Validate(cdn.Name,
validation.Required, validName),
"domainName": validation.Validate(cdn.DomainName,
validation.Required, validDomainName),
}
+ if cdn.APIInfo().Version.GreaterThanOrEqualTo(&api.Version{Major: 4,
Minor: 1}) {
+ errs["ttlOverride"] = validation.Validate(cdn.TTLOverride,
validation.By(tovalidate.IsGreaterThanZero))
+ }
return util.JoinErrs(tovalidate.ToErrors(errs)), nil
}
func (cdn *TOCDN) Create() (error, error, int) {
*cdn.DomainName = strings.ToLower(*cdn.DomainName)
+ if cdn.APIInfo().Version.LessThan(&api.Version{Major: 4, Minor: 1}) {
+ cdn.TTLOverride = nil
+ }
return api.GenericCreate(cdn)
}
@@ -149,6 +160,9 @@ func (cdn *TOCDN) Update(h http.Header) (error, error, int)
{
}
}
*cdn.DomainName = strings.ToLower(*cdn.DomainName)
+ if cdn.APIInfo().Version.LessThan(&api.Version{Major: 4, Minor: 1}) {
+ cdn.TTLOverride = nil
+ }
return api.GenericUpdate(h, cdn)
}
@@ -162,37 +176,65 @@ func (cdn *TOCDN) Delete() (error, error, int) {
return api.GenericDelete(cdn)
}
-func selectQuery() string {
+func formatQueryByAPIVersion(apiVersion *api.Version, minimumAPIVersion
*api.Version, queryFormatString string, columnStrs []string,
lowAPIVersionColumnStrs []string) string {
+ if apiVersion.LessThan(&api.Version{Major: 4, Minor: 1}) {
+ for index, _ := range columnStrs {
+ columnStrs[index] = lowAPIVersionColumnStrs[index]
+ }
+ }
+ columnStrArgs := make([]interface{}, len(columnStrs))
+ for index, _ := range columnStrs {
+ columnStrArgs[index] = columnStrs[index]
+ }
+ query := fmt.Sprintf(queryFormatString, columnStrArgs...)
+ return query
+}
+
+func selectQuery(apiVersion *api.Version) string {
query := `SELECT
dnssec_enabled,
domain_name,
id,
last_updated,
+%s
name
FROM cdn c`
- return query
+ return formatQueryByAPIVersion(apiVersion, &api.Version{Major: 4,
Minor: 1}, query, []string{`
+ ttl_override,
+`}, []string{``})
}
-func updateQuery() string {
+func updateQuery(apiVersion *api.Version) string {
query := `UPDATE
cdn SET
dnssec_enabled=:dnssec_enabled,
domain_name=:domain_name,
name=:name
+%s
WHERE id=:id RETURNING last_updated`
- return query
+ return formatQueryByAPIVersion(apiVersion, &api.Version{Major: 4,
Minor: 1}, query, []string{`,
+ttl_override=:ttl_override
+`}, []string{``})
}
-func insertQuery() string {
+func insertQuery(apiVersion *api.Version) string {
query := `INSERT INTO cdn (
dnssec_enabled,
domain_name,
-name) VALUES (
+name
+%s
+) VALUES (
:dnssec_enabled,
:domain_name,
-:name) RETURNING id,last_updated`
- return query
+:name
+%s
+) RETURNING id,last_updated`
+ return formatQueryByAPIVersion(apiVersion, &api.Version{Major: 4,
Minor: 1}, query, []string{`,
+ttl_override
+`, `,
+:ttl_override
+`}, []string{``, ``})
}
func deleteQuery() string {
diff --git a/traffic_ops/traffic_ops_golang/cdn/cdns_test.go
b/traffic_ops/traffic_ops_golang/cdn/cdns_test.go
index baa10197c3..e4a3296251 100644
--- a/traffic_ops/traffic_ops_golang/cdn/cdns_test.go
+++ b/traffic_ops/traffic_ops_golang/cdn/cdns_test.go
@@ -42,6 +42,7 @@ func getTestCDNs() []tc.CDN {
ID: 1,
Name: "cdn1",
LastUpdated: tc.TimeNoMod{Time: time.Now()},
+ TTLOverride: 50,
}
cdns = append(cdns, testCDN)
@@ -75,13 +76,14 @@ func TestReadCDNs(t *testing.T) {
ts.ID,
ts.LastUpdated,
ts.Name,
+ ts.TTLOverride,
)
}
mock.ExpectBegin()
mock.ExpectQuery("SELECT").WillReturnRows(rows)
mock.ExpectCommit()
- reqInfo := api.APIInfo{Tx: db.MustBegin(), Params:
map[string]string{"dsId": "1"}}
+ reqInfo := api.APIInfo{Tx: db.MustBegin(), Params:
map[string]string{"dsId": "1"}, Version: &api.Version{Major: 5, Minor: 0}}
obj := TOCDN{
api.APIInfoImpl{ReqInfo: &reqInfo},
tc.CDNNullable{},
@@ -97,13 +99,14 @@ func TestReadCDNs(t *testing.T) {
}
func TestFuncs(t *testing.T) {
- if strings.Index(selectQuery(), "SELECT") != 0 {
+ apiVersion := &api.Version{Major: 4, Minor: 1}
+ if strings.Index(selectQuery(apiVersion), "SELECT") != 0 {
t.Errorf("expected selectQuery to start with SELECT")
}
- if strings.Index(insertQuery(), "INSERT") != 0 {
+ if strings.Index(insertQuery(apiVersion), "INSERT") != 0 {
t.Errorf("expected insertQuery to start with INSERT")
}
- if strings.Index(updateQuery(), "UPDATE") != 0 {
+ if strings.Index(updateQuery(apiVersion), "UPDATE") != 0 {
t.Errorf("expected updateQuery to start with UPDATE")
}
if strings.Index(deleteQuery(), "DELETE") != 0 {
@@ -135,7 +138,8 @@ func TestInterfaces(t *testing.T) {
func TestValidate(t *testing.T) {
// invalid name, empty domainname
n := "not_a_valid_cdn"
- c := TOCDN{CDNNullable: tc.CDNNullable{Name: &n}}
+ reqInfo := api.APIInfo{Tx: nil, Params: map[string]string{"dsId": "1"},
Version: &api.Version{Major: 5, Minor: 0}}
+ c := TOCDN{CDNNullable: tc.CDNNullable{Name: &n}, APIInfoImpl:
api.APIInfoImpl{ReqInfo: &reqInfo}}
err, _ := c.Validate()
errs := util.JoinErrsStr(test.SortErrors(test.SplitErrors(err)))
@@ -151,7 +155,7 @@ func TestValidate(t *testing.T) {
// name, domainname both valid
n = "This.is.2.a-Valid---CDNNAME."
d := `awesome-cdn.example.net`
- c = TOCDN{CDNNullable: tc.CDNNullable{Name: &n, DomainName: &d}}
+ c = TOCDN{CDNNullable: tc.CDNNullable{Name: &n, DomainName: &d},
APIInfoImpl: api.APIInfoImpl{ReqInfo: &reqInfo}}
err, _ = c.Validate()
if err != nil {
t.Errorf("expected nil, got %s", err)
diff --git a/traffic_ops/traffic_ops_golang/crconfig/crconfig.go
b/traffic_ops/traffic_ops_golang/crconfig/crconfig.go
index 456feaef76..fc1ac53004 100644
--- a/traffic_ops/traffic_ops_golang/crconfig/crconfig.go
+++ b/traffic_ops/traffic_ops_golang/crconfig/crconfig.go
@@ -35,7 +35,7 @@ func Make(tx *sql.Tx, cdn, user, toHost, toVersion string,
useClientReqHost bool
crc := tc.CRConfig{}
err := error(nil)
- cdnDomain, dnssecEnabled, err := getCDNInfo(cdn, tx)
+ cdnDomain, dnssecEnabled, ttlOverride, err := getCDNInfo(cdn, tx)
if err != nil {
return nil, errors.New("Error getting CDN info: " + err.Error())
}
@@ -50,7 +50,7 @@ func Make(tx *sql.Tx, cdn, user, toHost, toVersion string,
useClientReqHost bool
if crc.EdgeLocations, crc.RouterLocations, err = makeLocations(cdn,
tx); err != nil {
return nil, errors.New("Error getting Edge Locations: " +
err.Error())
}
- if crc.DeliveryServices, err = makeDSes(cdn, cdnDomain, tx); err != nil
{
+ if crc.DeliveryServices, err = makeDSes(cdn, cdnDomain, ttlOverride,
tx); err != nil {
return nil, errors.New("Error getting Delivery Services: " +
err.Error())
}
if crc.Topologies, err = topology.MakeTopologies(tx); err != nil {
diff --git a/traffic_ops/traffic_ops_golang/crconfig/deliveryservice.go
b/traffic_ops/traffic_ops_golang/crconfig/deliveryservice.go
index 91b556f449..dfd45ebaa7 100644
--- a/traffic_ops/traffic_ops_golang/crconfig/deliveryservice.go
+++ b/traffic_ops/traffic_ops_golang/crconfig/deliveryservice.go
@@ -23,6 +23,7 @@ import (
"database/sql"
"errors"
"fmt"
+ "math"
"strconv"
"strings"
"time"
@@ -43,14 +44,25 @@ const DefaultTLDTTLNS = 3600 * time.Second
const GeoProviderMaxmindStr = "maxmindGeolocationService"
const GeoProviderNeustarStr = "neustarGeolocationService"
-func makeDSes(cdn string, domain string, tx *sql.Tx)
(map[string]tc.CRConfigDeliveryService, error) {
+func makeDSes(cdn string, domain string, ttlOverride int, tx *sql.Tx)
(map[string]tc.CRConfigDeliveryService, error) {
dses := map[string]tc.CRConfigDeliveryService{}
admin := CDNSOAAdmin
- expireSecondsStr := strconv.Itoa(int(CDNSOAExpire / time.Second))
- minimumSecondsStr := strconv.Itoa(int(CDNSOAMinimum / time.Second))
- refreshSecondsStr := strconv.Itoa(int(CDNSOARefresh / time.Second))
- retrySecondsStr := strconv.Itoa(int(CDNSOARetry / time.Second))
+ expireSeconds := int(CDNSOAExpire / time.Second)
+ minimumSeconds := int(CDNSOAMinimum / time.Second)
+ refreshSeconds := int(CDNSOARefresh / time.Second)
+ retrySeconds := int(CDNSOARetry / time.Second)
+ if ttlOverride > 0 {
+ expireSeconds = int(math.Min(float64(ttlOverride),
float64(expireSeconds)))
+ minimumSeconds = int(math.Min(float64(ttlOverride),
float64(minimumSeconds)))
+ refreshSeconds = int(math.Min(float64(ttlOverride),
float64(refreshSeconds)))
+ retrySeconds = int(math.Min(float64(ttlOverride),
float64(retrySeconds)))
+ }
+
+ expireSecondsStr := strconv.Itoa(expireSeconds)
+ minimumSecondsStr := strconv.Itoa(minimumSeconds)
+ refreshSecondsStr := strconv.Itoa(refreshSeconds)
+ retrySecondsStr := strconv.Itoa(retrySeconds)
cdnSOA := &tc.SOA{
Admin: &admin,
ExpireSeconds: &expireSecondsStr,
@@ -76,6 +88,8 @@ func makeDSes(cdn string, domain string, tx *sql.Tx)
(map[string]tc.CRConfigDeli
return nil, errors.New("getting deliveryservice parameters: " +
err.Error())
}
+ ttlOverrideDuration := time.Duration(ttlOverride) * time.Second
+
dsParams, err := getDSParams(serverParams)
if err != nil {
return nil, errors.New("getting deliveryservice server
parameters: " + err.Error())
@@ -213,6 +227,10 @@ AND d.active = true
return nil, errors.New("scanning deliveryservice: " +
err.Error())
}
+ if ttlOverride > 0 && ds.TTL != nil {
+ *ds.TTL = int(math.Min(float64(ttlOverride),
float64(*ds.TTL)))
+ }
+
// TODO prevent (lat XOR lon) in the Tx and UI
if missLat.Valid && missLon.Valid {
ds.MissLocation =
&tc.CRConfigLatitudeLongitudeShort{Lat: missLat.Float64, Lon: missLon.Float64}
@@ -313,7 +331,13 @@ AND d.active = true
}
}
}
+ if ttlOverride > 0 && ttlOverrideDuration < nsSeconds {
+ nsSeconds = ttlOverrideDuration
+ }
nsSecondsStr := strconv.Itoa(int(nsSeconds / time.Second))
+ if ttlOverride > 0 && ttlOverrideDuration < soaSeconds {
+ soaSeconds = ttlOverrideDuration
+ }
soaSecondsStr := strconv.Itoa(int(soaSeconds / time.Second))
ttlStr := ""
if ds.TTL != nil {
@@ -336,6 +360,9 @@ AND d.active = true
}
if dnsBypassTTL.Valid {
i := int(dnsBypassTTL.Int64)
+ if ttlOverride > 0 {
+ i = int(math.Min(float64(ttlOverride),
float64(i)))
+ }
bypassDest.TTL = &i
}
if dnsBypassCName.Valid && dnsBypassCName.String != "" {
@@ -411,6 +438,13 @@ AND d.active = true
}
ds.StaticDNSEntries =
staticDNSEntries[tc.DeliveryServiceName(xmlID)]
+ if ttlOverride > 0 {
+ for xmlID := range staticDNSEntries {
+ for index := range staticDNSEntries[xmlID] {
+ staticDNSEntries[xmlID][index].TTL =
ttlOverride
+ }
+ }
+ }
dses[xmlID] = ds
}
@@ -534,8 +568,8 @@ func getDSParams(serverParams map[string]map[string]string)
(map[string]string,
"tld.soa.minimum": struct{}{},
"tld.soa.refresh": struct{}{},
"tld.soa.retry": struct{}{},
- "tld.ttls.SOA": struct{}{},
- "tld.ttls.NS": struct{}{},
+ tc.CrConfigTTLSOA: struct{}{},
+ tc.CrConfigTTLNS: struct{}{},
"LogRequestHeaders": struct{}{},
}
dsParams := map[string]string{}
diff --git a/traffic_ops/traffic_ops_golang/crconfig/deliveryservice_test.go
b/traffic_ops/traffic_ops_golang/crconfig/deliveryservice_test.go
index fe0744e77e..2e5b9c9ab2 100644
--- a/traffic_ops/traffic_ops_golang/crconfig/deliveryservice_test.go
+++ b/traffic_ops/traffic_ops_golang/crconfig/deliveryservice_test.go
@@ -22,6 +22,7 @@ package crconfig
import (
"context"
"encoding/json"
+ "math"
"reflect"
"strconv"
"strings"
@@ -35,19 +36,45 @@ import (
"gopkg.in/DATA-DOG/go-sqlmock.v1"
)
-func randDS() tc.CRConfigDeliveryService {
+func randDS(ttlOverride int) tc.CRConfigDeliveryService {
// truePtr := true
falseStrPtr := "false"
// numStr := "42"
ttlAdmin := "traffic_ops"
- ttlExpire := "604800"
- ttlMinimum := "30"
- ttlRefresh := "28800"
- ttlRetry := "7200"
- ttl := util.IntPtr(test.RandInt())
- ttlStr := strconv.Itoa(*ttl)
- ttlNS := "3600"
- ttlSOA := "86400"
+ ttlExpire := 604800
+ ttlMinimum := 30
+ ttlRefresh := 28800
+ ttlRetry := 7200
+ if ttlOverride > 0 {
+ ttlExpire = int(math.Min(float64(ttlOverride),
float64(ttlExpire)))
+ ttlMinimum = int(math.Min(float64(ttlOverride),
float64(ttlMinimum)))
+ ttlRefresh = int(math.Min(float64(ttlOverride),
float64(ttlRefresh)))
+ ttlRetry = int(math.Min(float64(ttlOverride),
float64(ttlRetry)))
+ }
+
+ ttlExpireStr := strconv.Itoa(ttlExpire)
+ ttlMinimumStr := strconv.Itoa(ttlMinimum)
+ ttlRefreshStr := strconv.Itoa(ttlRefresh)
+ ttlRetryStr := strconv.Itoa(ttlRetry)
+
+ var ttl *int
+ var ttlStr string
+ var ttlNS, ttlSOA string
+ var staticDNSTTL int
+
+ if ttlOverride > 0 {
+ ttl = util.Ptr(ttlOverride)
+ ttlStr = strconv.Itoa(*ttl)
+ ttlNS = ttlStr
+ ttlSOA = ttlStr
+ staticDNSTTL = ttlOverride
+ } else {
+ ttl = util.IntPtr(test.RandInt())
+ ttlStr = strconv.Itoa(*ttl)
+ ttlNS = "3600"
+ ttlSOA = "86400"
+ staticDNSTTL = test.RandInt()
+ }
geoProviderStr := GeoProviderMaxmindStr
ecsEnabled := false
return tc.CRConfigDeliveryService{
@@ -76,10 +103,10 @@ func randDS() tc.CRConfigDeliveryService {
RequiredCapabilities: test.RandStrArray(),
Soa: &tc.SOA{
Admin: &ttlAdmin,
- ExpireSeconds: &ttlExpire,
- MinimumSeconds: &ttlMinimum,
- RefreshSeconds: &ttlRefresh,
- RetrySeconds: &ttlRetry,
+ ExpireSeconds: &ttlExpireStr,
+ MinimumSeconds: &ttlMinimumStr,
+ RefreshSeconds: &ttlRefreshStr,
+ RetrySeconds: &ttlRetryStr,
},
SSLEnabled: false,
EcsEnabled: &ecsEnabled,
@@ -110,7 +137,7 @@ func randDS() tc.CRConfigDeliveryService {
StaticDNSEntries: []tc.CRConfigStaticDNSEntry{
tc.CRConfigStaticDNSEntry{
Name: test.RandStr(),
- TTL: test.RandInt(),
+ TTL: staticDNSTTL,
Type: test.RandStr(),
Value: test.RandStr(),
},
@@ -118,10 +145,10 @@ func randDS() tc.CRConfigDeliveryService {
}
}
-func ExpectedMakeDSes() map[string]tc.CRConfigDeliveryService {
+func ExpectedMakeDSes(ttlOverride int) map[string]tc.CRConfigDeliveryService {
return map[string]tc.CRConfigDeliveryService{
- "ds1": randDS(),
- "ds2": randDS(),
+ "ds1": randDS(ttlOverride),
+ "ds2": randDS(ttlOverride),
}
}
@@ -211,7 +238,11 @@ func TestMakeDSesGeoLimit(t *testing.T) {
cdn := "mycdn"
domain := "mycdn.invalid"
- expected := ExpectedMakeDSes()
+ ttlOverride := 0
+ if test.RandBool() {
+ ttlOverride = 1 + test.RandIntn(200)
+ }
+ expected := ExpectedMakeDSes(ttlOverride)
delete(expected, "ds2")
expectedDS := expected["ds1"]
geoEnabled := make([]tc.CRConfigGeoEnabled, 0)
@@ -245,7 +276,7 @@ func TestMakeDSesGeoLimit(t *testing.T) {
}
defer tx.Commit()
- actual, err := makeDSes(cdn, domain, tx)
+ actual, err := makeDSes(cdn, domain, ttlOverride, tx)
if err != nil {
t.Fatalf("makeDSes expected: nil error, actual: %v", err)
}
@@ -281,7 +312,11 @@ func TestMakeDSes(t *testing.T) {
cdn := "mycdn"
domain := "mycdn.invalid"
- expected := ExpectedMakeDSes()
+ ttlOverride := 0
+ if test.RandBool() {
+ ttlOverride = 1 + test.RandIntn(200)
+ }
+ expected := ExpectedMakeDSes(ttlOverride)
expectedParams := ExpectedGetServerProfileParams(expected)
expectedDSParams, err := getDSParams(expectedParams)
if err != nil {
@@ -305,7 +340,7 @@ func TestMakeDSes(t *testing.T) {
}
defer tx.Commit()
- actual, err := makeDSes(cdn, domain, tx)
+ actual, err := makeDSes(cdn, domain, ttlOverride, tx)
if err != nil {
t.Fatalf("makeDSes expected: nil error, actual: %v", err)
}
@@ -358,7 +393,11 @@ func TestGetServerProfileParams(t *testing.T) {
cdn := "mycdn"
- expectedMakeDSes := ExpectedMakeDSes()
+ ttlOverride := 0
+ if test.RandBool() {
+ ttlOverride = 1 + test.RandIntn(200)
+ }
+ expectedMakeDSes := ExpectedMakeDSes(ttlOverride)
expected := ExpectedGetServerProfileParams(expectedMakeDSes)
mock.ExpectBegin()
@@ -442,7 +481,11 @@ func TestGetDSRegexesDomains(t *testing.T) {
cdn := "mycdn"
domain := "mycdn.invalid"
- expectedMakeDSes := ExpectedMakeDSes()
+ ttlOverride := 0
+ if test.RandBool() {
+ ttlOverride = 1 + test.RandIntn(200)
+ }
+ expectedMakeDSes := ExpectedMakeDSes(ttlOverride)
expectedServerProfileParams :=
ExpectedGetServerProfileParams(expectedMakeDSes)
expectedDSParams, err := getDSParams(expectedServerProfileParams)
if err != nil {
@@ -511,7 +554,11 @@ func TestGetStaticDNSEntries(t *testing.T) {
cdn := "mycdn"
- expectedMakeDSes := ExpectedMakeDSes()
+ ttlOverride := 0
+ if test.RandBool() {
+ ttlOverride = 1 + test.RandIntn(200)
+ }
+ expectedMakeDSes := ExpectedMakeDSes(ttlOverride)
expected := ExpectedGetStaticDNSEntries(expectedMakeDSes)
mock.ExpectBegin()
diff --git a/traffic_ops/traffic_ops_golang/crconfig/servers.go
b/traffic_ops/traffic_ops_golang/crconfig/servers.go
index 28f17b8195..16b52ab285 100644
--- a/traffic_ops/traffic_ops_golang/crconfig/servers.go
+++ b/traffic_ops/traffic_ops_golang/crconfig/servers.go
@@ -405,13 +405,15 @@ and (st.name = 'REPORTED' or st.name = 'ONLINE' or
st.name = 'ADMIN_DOWN')
}
// getCDNInfo returns the CDN domain, and whether DNSSec is enabled
-func getCDNInfo(cdn string, tx *sql.Tx) (string, bool, error) {
+func getCDNInfo(cdn string, tx *sql.Tx) (string, bool, int, error) {
domain := ""
dnssec := false
- if err := tx.QueryRow(`select domain_name, dnssec_enabled from cdn
where name = $1`, cdn).Scan(&domain, &dnssec); err != nil {
- return "", false, errors.New("Error querying CDN domain name: "
+ err.Error())
+ ttlOverride := sql.NullInt64{}
+ err := tx.QueryRow(`select domain_name, dnssec_enabled, ttl_override
from cdn where name = $1`, cdn).Scan(&domain, &dnssec, &ttlOverride)
+ if err != nil {
+ err = errors.New("Error querying CDN domain name: " +
err.Error())
}
- return domain, dnssec, nil
+ return domain, dnssec, int(ttlOverride.Int64), err
}
// getCDNNameFromID returns the CDN name given the ID, false if the no CDN
with the given ID exists, and an error if the database query fails.
diff --git a/traffic_ops/traffic_ops_golang/crconfig/servers_test.go
b/traffic_ops/traffic_ops_golang/crconfig/servers_test.go
index 117596a483..3a2577b54c 100644
--- a/traffic_ops/traffic_ops_golang/crconfig/servers_test.go
+++ b/traffic_ops/traffic_ops_golang/crconfig/servers_test.go
@@ -540,13 +540,13 @@ func TestGetServerDSes(t *testing.T) {
}
}
-func ExpectedGetCDNInfo() (string, bool) {
- return test.RandStr(), test.RandBool()
+func ExpectedGetCDNInfo() (string, bool, int) {
+ return test.RandStr(), test.RandBool(), test.RandInt()
}
-func MockGetCDNInfo(mock sqlmock.Sqlmock, expectedDomain string,
expectedDNSSECEnabled bool, cdn string) {
- rows := sqlmock.NewRows([]string{"domain_name", "dnssec_enabled"})
- rows = rows.AddRow(expectedDomain, expectedDNSSECEnabled)
+func MockGetCDNInfo(mock sqlmock.Sqlmock, expectedDomain string,
expectedDNSSECEnabled bool, expectedTTLOverride int, cdn string) {
+ rows := sqlmock.NewRows([]string{"domain_name", "dnssec_enabled",
"ttl_override"})
+ rows = rows.AddRow(expectedDomain, expectedDNSSECEnabled,
expectedTTLOverride)
mock.ExpectQuery("select").WithArgs(cdn).WillReturnRows(rows)
}
@@ -560,8 +560,8 @@ func TestGetCDNInfo(t *testing.T) {
cdn := "mycdn"
mock.ExpectBegin()
- expectedDomain, expectedDNSSECEnabled := ExpectedGetCDNInfo()
- MockGetCDNInfo(mock, expectedDomain, expectedDNSSECEnabled, cdn)
+ expectedDomain, expectedDNSSECEnabled, expectedTTLOverride :=
ExpectedGetCDNInfo()
+ MockGetCDNInfo(mock, expectedDomain, expectedDNSSECEnabled,
expectedTTLOverride, cdn)
mock.ExpectCommit()
dbCtx, cancelTx := context.WithTimeout(context.TODO(), 10*time.Second)
@@ -572,7 +572,7 @@ func TestGetCDNInfo(t *testing.T) {
}
defer tx.Commit()
- actualDomain, actualDNSSECEnabled, err := getCDNInfo(cdn, tx)
+ actualDomain, actualDNSSECEnabled, actualTTLOverride, err :=
getCDNInfo(cdn, tx)
if err != nil {
t.Fatalf("getCDNInfo expected: nil error, actual: %v", err)
}
@@ -583,6 +583,9 @@ func TestGetCDNInfo(t *testing.T) {
if expectedDNSSECEnabled != actualDNSSECEnabled {
t.Errorf("getCDNInfo expected: %v, actual: %v",
expectedDNSSECEnabled, actualDNSSECEnabled)
}
+ if expectedTTLOverride != actualTTLOverride {
+ t.Errorf("getCDNInfo expected: %v, actual: %v",
expectedTTLOverride, actualTTLOverride)
+ }
}
func ExpectedGetCDNNameFromID() string {
diff --git a/traffic_portal/app/src/common/modules/form/cdn/form.cdn.tpl.html
b/traffic_portal/app/src/common/modules/form/cdn/form.cdn.tpl.html
index 14c08530ee..e8ec59d2c6 100644
--- a/traffic_portal/app/src/common/modules/form/cdn/form.cdn.tpl.html
+++ b/traffic_portal/app/src/common/modules/form/cdn/form.cdn.tpl.html
@@ -82,6 +82,19 @@ under the License.
<small class="input-error"
ng-show="hasPropertyError(cdnForm.dnssecEnabled, 'required')">Required</small>
</div>
</div>
+ <div class="form-group" ng-class="{'has-error':
hasError(cdnForm.ttlOverride), 'has-feedback': hasError(cdnForm.ttlOverride)}">
+ <label class="has-tooltip control-label col-md-2 col-sm-2
col-xs-12">TTL Override<div class="helptooltip">
+ <div class="helptext">Setting the <abbr
title="Time To Live">TTL</abbr> Override for this CDN overrides every TTL value
of every Delivery Service in the CDN's Snapshot with this value.
+ <br><br>
+ Operators may find this feature useful for
temporarily lowering the TTL values in order to perform a Traffic Router
upgrade.
+ </div>
+ </div>
+ </label>
+ <div class="col-md-10 col-sm-10 col-xs-12">
+ <input name="ttlOverride" type="number"
class="form-control" ng-model="cdn.ttlOverride" step="1" min="1">
+ <span ng-show="hasError(cdnForm.ttlOverride)"
class="form-control-feedback"><i class="fa fa-times"></i></span>
+ </div>
+ </div>
<div class="modal-footer">
<button type="button" class="btn btn-danger"
ng-show="!settings.isNew" ng-click="confirmDelete(cdn)">Delete</button>
<button type="button" class="btn btn-success"
ng-disabled="cdnForm.$pristine || cdnForm.$invalid"
ng-click="save(cdn)">{{settings.saveLabel}}</button>
diff --git a/traffic_portal/test/integration/Data/cdn.ts
b/traffic_portal/test/integration/Data/cdn.ts
index be41e689db..369a37ea78 100644
--- a/traffic_portal/test/integration/Data/cdn.ts
+++ b/traffic_portal/test/integration/Data/cdn.ts
@@ -68,12 +68,18 @@ export const cdns = {
NewName: "",
validationMessage: "Cleared CDN server
updates"
},
+ {
+ description: "update cdn ttl override",
+ Name: "TPCDN2",
+ NewName: "",
+ validationMessage: "cdn was updated."
+ },
{
description: "update cdn name",
Name: "TPCDN1",
NewName: "TPNewCDN1",
validationMessage: "cdn was updated."
- }
+ },
],
remove: [
{
@@ -120,12 +126,18 @@ export const cdns = {
NewName: "",
validationMessage: "missing required
Permissions: SERVER:QUEUE"
},
+ {
+ description: "update cdn ttl override",
+ Name: "TPCDN2",
+ NewName: "",
+ validationMessage: "missing required
Permissions: CDN:UPDATE"
+ },
{
description: "update cdn name",
Name: "TPCDN2",
NewName: "TPNewCDN2",
validationMessage: "missing required
Permissions: CDN:UPDATE"
- }
+ },
],
remove: [
{
@@ -185,12 +197,18 @@ export const cdns = {
NewName: "",
validationMessage: "Cleared CDN server
updates"
},
+ {
+ description: "update cdn ttl override",
+ Name: "TPCDN4",
+ NewName: "",
+ validationMessage: "cdn was updated."
+ },
{
description: "update cdn name",
Name: "TPCDN3",
NewName: "TPNewCDN3",
validationMessage: "cdn was updated."
- }
+ },
],
remove: [
{
diff --git a/traffic_portal/test/integration/PageObjects/CDNPage.po.ts
b/traffic_portal/test/integration/PageObjects/CDNPage.po.ts
index 40891936ce..dfc01bd33b 100644
--- a/traffic_portal/test/integration/PageObjects/CDNPage.po.ts
+++ b/traffic_portal/test/integration/PageObjects/CDNPage.po.ts
@@ -17,7 +17,7 @@
* under the License.
*/
import { browser, by, element } from "protractor";
-import { randomize } from "../config";
+import { randomize, twoNumberRandomize } from "../config";
import { SideNavigationPage } from "./SideNavigationPage.po";
interface CDN {
@@ -25,6 +25,7 @@ interface CDN {
DNSSEC: string;
Domain: string;
Name: string;
+ TTLOverride?: string;
validationMessage?: string;
}
@@ -38,6 +39,7 @@ interface UpdateCDN {
export class CDNPage extends SideNavigationPage {
private readonly txtCDNName = element(by.name("name"));
+ private readonly txtCDNTTLOverride = element(by.name("ttlOverride"));
/**
* Navigates the browser to the CDNs table page.
@@ -58,11 +60,15 @@ export class CDNPage extends SideNavigationPage {
await this.openCDNsPage();
await element(by.buttonText("More")).click();
await element(by.linkText("Create New CDN")).click();
- await Promise.all([
+ const actions = [
this.txtCDNName.sendKeys(cdn.Name + randomize),
element(by.name("domainName")).sendKeys(cdn.Domain),
- element(by.name("dnssecEnabled")).sendKeys(cdn.DNSSEC)
- ]);
+ element(by.name("dnssecEnabled")).sendKeys(cdn.DNSSEC),
+ ];
+ if (cdn.TTLOverride !== undefined) {
+
actions.push(element(this.txtCDNTTLOverride).sendKeys(cdn.TTLOverride));
+ }
+ await Promise.all(actions);
await this.ClickCreate();
return this.GetOutputMessage().then(v => cdn.validationMessage
=== v);
}
@@ -125,6 +131,11 @@ export class CDNPage extends SideNavigationPage {
await this.txtCDNName.sendKeys(cdn.NewName +
randomize);
await this.ClickUpdate();
break;
+ case "update cdn ttl override":
+ await this.txtCDNTTLOverride.clear();
+ await
this.txtCDNTTLOverride.sendKeys(twoNumberRandomize);
+ await this.ClickUpdate();
+ break;
default:
throw new Error(`unhandleable description:
'${cdn.description}'`);
}