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}'`);
                }


Reply via email to