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

rob 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 e3afacd  Adds max origin connections to ds api endpoints (v14) and TP 
and adds the value to hdr_rw_xml-id.config OR hdr_rw_mid_xml-id.config files 
(#3422)
e3afacd is described below

commit e3afacdbcc271ecdf0fb0effd30232096128a6b5
Author: Jeremy Mitchell <[email protected]>
AuthorDate: Tue Apr 16 11:36:56 2019 -0600

    Adds max origin connections to ds api endpoints (v14) and TP and adds the 
value to hdr_rw_xml-id.config OR hdr_rw_mid_xml-id.config files (#3422)
    
    * adds max origin connections to ds api routes and TP
    
    * adds hdr_rw_xml-id.config and hdr_rw_mid_xml-id.config to Go endpoints 
and inserts a rule for max-origin-connections IF ds.max_origin_connections > 0
    
    * adds hdr_rw_xml-id.config and hdr_rw_mid_xml-id.config to Go endpoints 
and inserts a rule for max-origin-connections IF ds.max_origin_connections > 0
    
    * formats file
    
    * godoc fix
    
    * uses the proper errors package
    
    * defines the hdr_rewrite endpoints as 1.1. to completely override the perl 
implementations
    
    * some cleanup/simplification and minor changes like using constants, go 
doc fixes, and returning a 404 when ds is not found
    
    * adds maxoriginconnections to EnsureParams to ensure that the existence of 
that value influences when location params are created for hdr rewrite files
---
 CHANGELOG.md                                       |   1 +
 docs/source/api/deliveryservices.rst               |  24 ++-
 lib/go-tc/constants.go                             |   1 +
 lib/go-tc/deliveryservices.go                      |  23 ++-
 .../20190319000000_add_max_origin_connections.sql  |  23 +++
 .../app/lib/API/Configs/ApacheTrafficServer.pm     |   2 +-
 traffic_ops/bin/traffic_ops_ort.pl                 |  13 +-
 .../testing/api/v14/deliveryservices_test.go       |   5 +-
 traffic_ops/testing/api/v14/tc-fixtures.json       |   3 +
 traffic_ops/traffic_ops_golang/ats/config.go       |  77 ++++++++
 .../traffic_ops_golang/ats/headerrewrite.go        | 202 +++++++++++++++++++++
 .../traffic_ops_golang/ats/regexrevalidate.go      |  66 -------
 .../deliveryservice/deliveryservicesv12.go         |  15 +-
 .../deliveryservice/deliveryservicesv13.go         |  58 +++---
 .../deliveryservice/deliveryservicesv14.go         | 170 +++++++++++++++++
 .../deliveryservice/request/requests_test.go       |  20 +-
 .../deliveryservice/servers/servers.go             |  31 ++--
 traffic_ops/traffic_ops_golang/routing/routes.go   |  13 ++
 traffic_ops/traffic_ops_golang/server/servers.go   |  16 +-
 .../form.deliveryService.DNS.tpl.html              |  14 ++
 .../form.deliveryService.HTTP.tpl.html             |  14 ++
 .../app/src/traffic_portal_properties.json         |   2 +
 22 files changed, 645 insertions(+), 148 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 6615ded..16f7471 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -9,6 +9,7 @@ The format is based on [Keep a 
Changelog](http://keepachangelog.com/en/1.0.0/).
   The default certificate is used whenever a client attempts an SSL handshake 
for an SNI host which does not match
   any of the other certificates.
 - Traffic Ops Golang Endpoints
+  - /api/1.4/deliveryservices `(GET,POST,PUT)`
   - /api/1.4/users `(GET,POST,PUT)`
   - /api/1.1/deliveryservices/xmlId/:xmlid/sslkeys `GET`
   - /api/1.1/deliveryservices/hostname/:hostname/sslkeys `GET`
diff --git a/docs/source/api/deliveryservices.rst 
b/docs/source/api/deliveryservices.rst
index 7cd66c5..57f73b2 100644
--- a/docs/source/api/deliveryservices.rst
+++ b/docs/source/api/deliveryservices.rst
@@ -61,9 +61,6 @@ Response Structure
 :cdnName:                  Name of the CDN to which the :term:`Delivery 
Service` belongs
 :checkPath:                The path portion of the URL to check connections to 
this :term:`Delivery Service`'s origin server
 :consistentHashRegex:      If defined, this is a regex used for the 
Pattern-Based Consistent Hashing feature. It is only applicable for HTTP and 
Steering Delivery Services
-
-       .. versionadded:: 1.5
-
 :displayName:              The display name of the :term:`Delivery Service`
 :dnsBypassCname:           Domain name to overflow requests for HTTP 
:term:`Delivery Service`\ s - bypass starts when the traffic on this 
:term:`Delivery Service` exceeds ``globalMaxMbps``, or when more than 
``globalMaxTps`` is being exceeded within the :term:`Delivery Service`\ [4]_
 :dnsBypassIp:              The IPv4 IP to use for bypass on a DNS 
:term:`Delivery Service` - bypass starts when the traffic on this 
:term:`Delivery Service` exceeds ``globalMaxMbps``, or when more than 
``globalMaxTps`` is being exceeded within the :term:`Delivery Service`\ [4]_
@@ -120,6 +117,10 @@ Response Structure
                        Use the :term:`Delivery Service` if ``pattern`` matches 
the ``xml_id`` of one of this :term:`Delivery Service`'s "Steering" target 
:term:`Delivery Service`\ s
 
 :maxDnsAnswers:    The maximum number of IPs to put in responses to A/AAAA DNS 
record requests (0 means all available)\ [4]_
+:maxOriginConnections:      The maximum number of connections allowed to the 
origin (0 means no maximum).
+
+       .. versionadded:: 1.4
+
 :midHeaderRewrite: Rewrite operations to be performed on TCP headers at the 
Edge-tier cache level - used by the Header Rewrite Apache Trafficserver plugin
 :missLat:          The latitude to use when the client cannot be found in the 
CZF or a geographic IP lookup
 :missLong:         The longitude to use when the client cannot be found in the 
CZF or a geographic IP lookup
@@ -246,6 +247,7 @@ Response Structure
                        }
                ],
                "maxDnsAnswers": null,
+               "maxOriginConnections": 0,
                "midHeaderRewrite": null,
                "missLat": 42,
                "missLong": -88,
@@ -302,9 +304,6 @@ Request Structure
 :cdnId:                    The integral, unique identifier for the CDN to 
which this :term:`Delivery Service`\ shall be assigned
 :checkPath:                The path portion of the URL which will be used to 
check connections to this :term:`Delivery Service`'s origin server
 :consistentHashRegex:      If defined, this is a regex used for the 
Pattern-Based Consistent Hashing feature. It is only applicable for HTTP and 
Steering Delivery Services
-
-       .. versionadded:: 1.5
-
 :deepCachingType:          A string describing when to do Deep Caching for 
this :term:`Delivery Service`:
 
        NEVER
@@ -351,6 +350,10 @@ Request Structure
 :longDesc1:          An optional field used when more detailed information 
that that provided by ``longDesc`` is desired
 :longDesc2:          An optional field used when even more detailed 
information that that provided by either ``longDesc`` or ``longDesc1`` is 
desired
 :maxDnsAnswers:      An optional field which, when present, specifies the 
maximum number of IPs to put in responses to A/AAAA DNS record requests - 
defaults to 0, meaning "no limit"\ [4]_
+:maxOriginConnections:      The maximum number of connections allowed to the 
origin (0 means no maximum).
+
+       .. versionadded:: 1.4
+
 :midHeaderRewrite:   An optional string containing rewrite operations to be 
performed on TCP headers at the Edge-tier cache level - used by the Header 
Rewrite Apache Trafficserver plugin
 :missLat:            The latitude to use when the client cannot be found in 
the CZF or a geographic IP lookup\ [7]_
 :missLong:           The longitude to use when the client cannot be found in 
the CZF or a geographic IP lookup\ [7]_
@@ -454,6 +457,7 @@ Request Structure
                "longDesc": "A :term:`Delivery Service` created expressly for 
API documentation examples",
                "missLat": -1,
                "missLong": -1,
+               "maxOriginConnections": 0,
                "multiSiteOrigin": false,
                "orgServerFqdn": "http://origin.infra.ciab.test";,
                "protocol": 0,
@@ -486,9 +490,6 @@ Response Structure
 :cdnName:                  Name of the CDN to which the :term:`Delivery 
Service` belongs
 :checkPath:                The path portion of the URL to check connections to 
this :term:`Delivery Service`'s origin server
 :consistentHashRegex:      If defined, this is a regex used for the 
Pattern-Based Consistent Hashing feature. It is only applicable for HTTP and 
Steering Delivery Services
-
-       .. versionadded:: 1.5
-
 :displayName:              The display name of the :term:`Delivery Service`
 :dnsBypassCname:           Domain name to overflow requests for HTTP 
:term:`Delivery Service`\ s - bypass starts when the traffic on this 
:term:`Delivery Service` exceeds ``globalMaxMbps``, or when more than 
``globalMaxTps`` is being exceeded within the :term:`Delivery Service`\ [4]_
 :dnsBypassIp:              The IPv4 IP to use for bypass on a DNS 
:term:`Delivery Service` - bypass starts when the traffic on this 
:term:`Delivery Service` exceeds ``globalMaxMbps``, or when more than 
``globalMaxTps`` is being exceeded within the :term:`Delivery Service`\ [4]_
@@ -545,6 +546,10 @@ Response Structure
                        Use the :term:`Delivery Service` if ``pattern`` matches 
the ``xml_id`` of one of this :term:`Delivery Service`'s "Steering" target 
:term:`Delivery Service`\ s
 
 :maxDnsAnswers:    The maximum number of IPs to put in responses to A/AAAA DNS 
record requests (0 means all available)\ [4]_
+:maxOriginConnections:      The maximum number of connections allowed to the 
origin (0 means no maximum).
+
+       .. versionadded:: 1.4
+
 :midHeaderRewrite: Rewrite operations to be performed on TCP headers at the 
Edge-tier cache level - used by the Header Rewrite Apache Trafficserver plugin
 :missLat:          The latitude to use when the client cannot be found in the 
CZF or a geographic IP lookup
 :missLong:         The longitude to use when the client cannot be found in the 
CZF or a geographic IP lookup
@@ -677,6 +682,7 @@ Response Structure
                                }
                        ],
                        "maxDnsAnswers": null,
+                       "maxOriginConnections": 0,
                        "midHeaderRewrite": null,
                        "missLat": -1,
                        "missLong": -1,
diff --git a/lib/go-tc/constants.go b/lib/go-tc/constants.go
index 9f7cee8..539f139 100644
--- a/lib/go-tc/constants.go
+++ b/lib/go-tc/constants.go
@@ -32,6 +32,7 @@ const ApplicationJson = "application/json"
 const Gzip = "gzip"
 const ContentType = "Content-Type"
 const ContentEncoding = "Content-Encoding"
+const ContentTypeTextPlain = "text/plain"
 
 type AlertLevel int
 
diff --git a/lib/go-tc/deliveryservices.go b/lib/go-tc/deliveryservices.go
index cb3fdd3..7bc7865 100644
--- a/lib/go-tc/deliveryservices.go
+++ b/lib/go-tc/deliveryservices.go
@@ -73,6 +73,11 @@ type DeleteDeliveryServiceResponse struct {
 }
 
 type DeliveryService struct {
+       DeliveryServiceV13
+       MaxOriginConnections int `json:"maxOriginConnections" 
db:"max_origin_connections"`
+}
+
+type DeliveryServiceV13 struct {
        DeliveryServiceV12
        DeepCachingType   DeepCachingType `json:"deepCachingType"`
        FQPacingRate      int             `json:"fqPacingRate,omitempty"`
@@ -146,6 +151,11 @@ type DeliveryServiceV11 struct {
 }
 
 type DeliveryServiceNullable struct {
+       DeliveryServiceNullableV13
+       MaxOriginConnections *int `json:"maxOriginConnections" 
db:"max_origin_connections"`
+}
+
+type DeliveryServiceNullableV13 struct {
        DeliveryServiceNullableV12
        ConsistentHashRegex *string          
`json:"consistentHashRegex,omitempty"`
        DeepCachingType     *DeepCachingType `json:"deepCachingType" 
db:"deep_caching_type"`
@@ -225,7 +235,15 @@ type DeliveryServiceNullableV11 struct {
 
 // NewDeliveryServiceNullableFromV12 creates a new V13 DS from a V12 DS, 
filling new fields with appropriate defaults.
 func NewDeliveryServiceNullableFromV12(ds DeliveryServiceNullableV12) 
DeliveryServiceNullable {
-       newDS := DeliveryServiceNullable{DeliveryServiceNullableV12: ds}
+       newDSv13 := DeliveryServiceNullableV13{DeliveryServiceNullableV12: ds}
+       newDS := DeliveryServiceNullable{DeliveryServiceNullableV13: newDSv13}
+       newDS.Sanitize()
+       return newDS
+}
+
+// NewDeliveryServiceNullableFromV13 creates a new V14 DS from a V13 DS, 
filling new fields with appropriate defaults.
+func NewDeliveryServiceNullableFromV13(ds DeliveryServiceNullableV13) 
DeliveryServiceNullable {
+       newDS := DeliveryServiceNullable{DeliveryServiceNullableV13: ds}
        newDS.Sanitize()
        return newDS
 }
@@ -399,6 +417,9 @@ func (ds *DeliveryServiceNullable) Sanitize() {
        if !ds.Signed && ds.SigningAlgorithm != nil && *ds.SigningAlgorithm == 
signedAlgorithm {
                ds.Signed = true
        }
+       if ds.MaxOriginConnections == nil || *ds.MaxOriginConnections < 0 {
+               ds.MaxOriginConnections = util.IntPtr(0)
+       }
        if ds.DeepCachingType == nil {
                s := DeepCachingType("")
                ds.DeepCachingType = &s
diff --git 
a/traffic_ops/app/db/migrations/20190319000000_add_max_origin_connections.sql 
b/traffic_ops/app/db/migrations/20190319000000_add_max_origin_connections.sql
new file mode 100644
index 0000000..784d87b
--- /dev/null
+++ 
b/traffic_ops/app/db/migrations/20190319000000_add_max_origin_connections.sql
@@ -0,0 +1,23 @@
+/*
+
+    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
+ALTER TABLE deliveryservice ADD COLUMN max_origin_connections bigint NOT NULL 
DEFAULT 0 CHECK (max_origin_connections >= 0);
+
+-- +goose Down
+-- SQL section 'Down' is executed when this migration is rolled back
+ALTER TABLE deliveryservice DROP COLUMN max_origin_connections;
+
diff --git a/traffic_ops/app/lib/API/Configs/ApacheTrafficServer.pm 
b/traffic_ops/app/lib/API/Configs/ApacheTrafficServer.pm
index e4a286c..b456396 100755
--- a/traffic_ops/app/lib/API/Configs/ApacheTrafficServer.pm
+++ b/traffic_ops/app/lib/API/Configs/ApacheTrafficServer.pm
@@ -153,7 +153,7 @@ sub get_config_metadata {
                        delete $config_file_obj->{$config_file}->{'apiUri'};
                }
                else {
-                       $config_file_obj->{$config_file}->{'apiUri'} = 
"/api/1.2/" . $scope . "/" . $scope_id . "/configfiles/ats/" . $config_file;
+                       $config_file_obj->{$config_file}->{'apiUri'} = 
"/api/1.4/" . $scope . "/" . $scope_id . "/configfiles/ats/" . $config_file;
                }
                $config_file_obj->{$config_file}->{'scope'} = $scope;
        }
diff --git a/traffic_ops/bin/traffic_ops_ort.pl 
b/traffic_ops/bin/traffic_ops_ort.pl
index e62eb80..47a2a1f 100755
--- a/traffic_ops/bin/traffic_ops_ort.pl
+++ b/traffic_ops/bin/traffic_ops_ort.pl
@@ -797,7 +797,7 @@ sub get_update_status {
 
        ##Some versions of Traffic Ops had the 1.3 API but did not have the 
use_reval_pending field.  If this field is not present, exit.
        if ( !defined( $upd_json->[0]->{'use_reval_pending'} ) ) {
-               my $info_uri = "/api/1.2/system/info.json";
+               my $info_uri = "/api/1.4/system/info.json";
                my $info_ref = &lwp_get($info_uri);
                if ($info_ref eq '404') {
                        ( $log_level >> $ERROR ) && printf("ERROR Unable to get 
status of use_reval_pending parameter.  Stopping.\n");
@@ -856,7 +856,7 @@ sub check_revalidate_state {
                        ( $log_level >> $ERROR ) && print "ERROR Traffic Ops is 
signaling that no revalidations are waiting to be applied.\n";
                }
 
-               my $stj = &lwp_get("/api/1.2/statuses");
+               my $stj = &lwp_get("/api/1.4/statuses");
                if ( $stj =~ m/^\d{3}$/ ) {
                        ( $log_level >> $ERROR ) && print "Statuses URL: $uri 
returned $stj! Skipping creation of status file.\n";
                }
@@ -973,8 +973,7 @@ sub check_syncds_state {
                else {
                        ( $log_level >> $ERROR ) && print "ERROR Traffic Ops is 
signaling that no update is waiting to be applied.\n";
                }
-
-               my $stj = &lwp_get("/api/1.2/statuses");
+               my $stj = &lwp_get("/api/1.4/statuses");
                if ( $stj =~ m/^\d{3}$/ ) {
                        ( $log_level >> $ERROR ) && print "Statuses URL: $uri 
returned $stj! Skipping creation of status file.\n";
                }
@@ -1784,7 +1783,7 @@ sub get_cfg_file_list {
        my $cfg_files;
        my $profile_name;
        my $cdn_name;
-       my $uri = "/api/1.2/servers/$host_name/configfiles/ats";
+       my $uri = "/api/1.4/servers/$host_name/configfiles/ats";
 
        my $result = &lwp_get($uri);
 
@@ -1895,7 +1894,7 @@ sub get_header_comment {
        my $to_host = shift;
        my $toolname;
 
-       my $uri    = "/api/1.2/system/info.json";
+       my $uri    = "/api/1.4/system/info.json";
        my $result = &lwp_get($uri);
 
        my $result_ref = decode_json($result);
@@ -2983,7 +2982,7 @@ sub adv_processing_ssl {
        my @db_file_lines = @{ $_[0] };
        if (@db_file_lines > 1) { #header line is always present, so look for 2 
lines or more
                ( $log_level >> $DEBUG ) && print "DEBUG Entering advanced 
processing for ssl_multicert.config.\n";
-               my $uri = "/api/1.2/cdns/name/$my_cdn_name/sslkeys.json";
+               my $uri = "/api/1.4/cdns/name/$my_cdn_name/sslkeys.json";
                my $result = &lwp_get($uri);
                if ( $result =~ m/^\d{3}$/ ) {
                        if ( $script_mode == $REPORT ) {
diff --git a/traffic_ops/testing/api/v14/deliveryservices_test.go 
b/traffic_ops/testing/api/v14/deliveryservices_test.go
index 3aa6e0e..eec83e1 100644
--- a/traffic_ops/testing/api/v14/deliveryservices_test.go
+++ b/traffic_ops/testing/api/v14/deliveryservices_test.go
@@ -87,8 +87,10 @@ func UpdateTestDeliveryServices(t *testing.T) {
 
        updatedLongDesc := "something different"
        updatedMaxDNSAnswers := 164598
+       updatedMaxOriginConnections := 100
        remoteDS.LongDesc = updatedLongDesc
        remoteDS.MaxDNSAnswers = updatedMaxDNSAnswers
+       remoteDS.MaxOriginConnections = updatedMaxOriginConnections
        remoteDS.MatchList = nil // verify that this field is optional in a PUT 
request, doesn't cause nil dereference panic
 
        if updateResp, err := 
TOSession.UpdateDeliveryService(strconv.Itoa(remoteDS.ID), &remoteDS); err != 
nil {
@@ -104,9 +106,10 @@ func UpdateTestDeliveryServices(t *testing.T) {
                t.Errorf("cannot GET Delivery Service by ID: %v - nil\n", 
remoteDS.XMLID)
        }
 
-       if resp.LongDesc != updatedLongDesc || resp.MaxDNSAnswers != 
updatedMaxDNSAnswers {
+       if resp.LongDesc != updatedLongDesc || resp.MaxDNSAnswers != 
updatedMaxDNSAnswers || resp.MaxOriginConnections != 
updatedMaxOriginConnections {
                t.Errorf("results do not match actual: %s, expected: %s\n", 
resp.LongDesc, updatedLongDesc)
                t.Errorf("results do not match actual: %v, expected: %v\n", 
resp.MaxDNSAnswers, updatedMaxDNSAnswers)
+               t.Errorf("results do not match actual: %v, expected: %v\n", 
resp.MaxOriginConnections, updatedMaxOriginConnections)
        }
 }
 
diff --git a/traffic_ops/testing/api/v14/tc-fixtures.json 
b/traffic_ops/testing/api/v14/tc-fixtures.json
index eb7be84..6fe45e4 100644
--- a/traffic_ops/testing/api/v14/tc-fixtures.json
+++ b/traffic_ops/testing/api/v14/tc-fixtures.json
@@ -338,6 +338,7 @@
                 }
             ],
             "maxDnsAnswers": 0,
+            "maxOriginConnections": -1,
             "midHeaderRewrite": "midRewrite2",
             "missLat": 41.881944,
             "missLong": -87.627778,
@@ -405,6 +406,7 @@
                 }
             ],
             "maxDnsAnswers": 0,
+            "maxOriginConnections": 0,
             "midHeaderRewrite": "midRewrite3",
             "missLat": 41.881944,
             "missLong": -87.627778,
@@ -462,6 +464,7 @@
             "longDesc2": "",
             "matchList": [],
             "maxDnsAnswers": 0,
+            "maxOriginConnections": 1,
             "midHeaderRewrite": "",
             "missLat": 41.881944,
             "missLong": -87.627778,
diff --git a/traffic_ops/traffic_ops_golang/ats/config.go 
b/traffic_ops/traffic_ops_golang/ats/config.go
index ea6a521..06225d4 100644
--- a/traffic_ops/traffic_ops_golang/ats/config.go
+++ b/traffic_ops/traffic_ops_golang/ats/config.go
@@ -1,5 +1,16 @@
 package ats
 
+import (
+       "database/sql"
+       "errors"
+       "fmt"
+       "net/http"
+       "strconv"
+       "time"
+
+       
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
+)
+
 /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
@@ -27,6 +38,72 @@ const CacheUrlPrefix = "cacheurl_"
 
 const RemapFile = "remap.config"
 
+const HeaderCommentDateFormat = "Mon Jan 2 15:04:05 MST 2006"
+
 func GetConfigFile(prefix string, xmlId string) string {
        return prefix + xmlId + configSuffix
 }
+
+func GetNameVersionString(tx *sql.Tx) (string, error) {
+       qry := `
+SELECT
+  p.name,
+  p.value
+FROM
+  parameter p
+WHERE
+  (p.name = 'tm.toolname' OR p.name = 'tm.url') AND p.config_file = 'global'
+`
+       rows, err := tx.Query(qry)
+       if err != nil {
+               return "", errors.New("querying: " + err.Error())
+       }
+       defer rows.Close()
+       toolName := ""
+       url := ""
+       for rows.Next() {
+               name := ""
+               val := ""
+               if err := rows.Scan(&name, &val); err != nil {
+                       return "", errors.New("scanning: " + err.Error())
+               }
+               if name == "tm.toolname" {
+                       toolName = val
+               } else if name == "tm.url" {
+                       url = val
+               }
+       }
+       return toolName + " (" + url + ")", nil
+}
+
+// getCDNNameFromNameOrID returns the CDN name from a parameter which may be 
the name or ID.
+// This also checks and verifies the existence of the given CDN, and returns 
an appropriate user error if it doesn't exist.
+// Returns the name, any user error, any system error, and any error code.
+func getCDNNameFromNameOrID(tx *sql.Tx, cdnNameOrID string) (string, error, 
error, int) {
+       if cdnID, err := strconv.Atoi(cdnNameOrID); err == nil {
+               cdnName, ok, err := dbhelpers.GetCDNNameFromID(tx, int64(cdnID))
+               if err != nil {
+                       return "", nil, fmt.Errorf("getting CDN name from id 
%v: %v", cdnID, err), http.StatusInternalServerError
+               } else if !ok {
+                       return "", errors.New("cdn not found"), nil, 
http.StatusNotFound
+               }
+               return string(cdnName), nil, nil, http.StatusOK
+       }
+
+       cdnName := cdnNameOrID
+       if ok, err := dbhelpers.CDNExists(cdnName, tx); err != nil {
+               return "", nil, fmt.Errorf("checking CDN name '%v' existence: 
%v", cdnName, err), http.StatusInternalServerError
+       } else if !ok {
+               return "", errors.New("cdn not found"), nil, http.StatusNotFound
+       }
+       return cdnName, nil, nil, http.StatusOK
+}
+
+func headerComment(tx *sql.Tx, name string) (string, error) {
+       nameVersionStr, err := GetNameVersionString(tx)
+       if err != nil {
+               return "", errors.New("getting name version string: " + 
err.Error())
+       }
+       return "# DO NOT EDIT - Generated for " + name + " by " + 
nameVersionStr + " on " + time.Now().Format(HeaderCommentDateFormat) + "\n", nil
+}
+
diff --git a/traffic_ops/traffic_ops_golang/ats/headerrewrite.go 
b/traffic_ops/traffic_ops_golang/ats/headerrewrite.go
new file mode 100644
index 0000000..9c60437
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/ats/headerrewrite.go
@@ -0,0 +1,202 @@
+package ats
+
+/*
+ * 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.
+ */
+
+import (
+       "errors"
+       "github.com/apache/trafficcontrol/lib/go-tc"
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+       
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/deliveryservice"
+       "github.com/jmoiron/sqlx"
+       "math"
+       "net/http"
+       "regexp"
+       "strconv"
+)
+
+func GetEdgeHeaderRewriteDotConfig(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, errCode := api.NewInfo(r, 
[]string{"cdn-name-or-id"}, nil)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+
+       cdnName, userErr, sysErr, errCode := getCDNNameFromNameOrID(inf.Tx.Tx, 
inf.Params["cdn-name-or-id"])
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+               return
+       }
+
+       text, err := headerComment(inf.Tx.Tx, "CDN "+cdnName)
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("getting hdr_rw_xml-id.config text: "+err.Error()))
+               return
+       }
+
+       ds, err := getDeliveryService(inf.Tx, inf.Params["xml-id"])
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, nil, 
errors.New("getting hdr_rw_mid_xml-id.config text: "+err.Error()))
+               return
+       }
+
+       maxOriginConnections := *ds.MaxOriginConnections
+
+       dsType, err := deliveryservice.GetDeliveryServiceType(*ds.ID, inf.Tx.Tx)
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("getting hdr_rw_xml-id.config text: "+err.Error()))
+               return
+       }
+       usesMids := dsType.UsesMidCache()
+
+       // write a header rewrite rule if maxOriginConnections > 0 and the ds 
does NOT use mids
+       if maxOriginConnections > 0 && !usesMids {
+               dsOnlineEdgeCount, err := getOnlineDSEdgeCount(inf.Tx, *ds.ID)
+               if err != nil {
+                       api.HandleErr(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, errors.New("getting ds server count: 
"+err.Error()))
+                       return
+               }
+               maxOriginConnectionsPerEdge := 
int(math.Round(float64(maxOriginConnections) / float64(dsOnlineEdgeCount)))
+               text += "cond %{REMAP_PSEUDO_HOOK}\nset-config 
proxy.config.http.origin_max_connections " + 
strconv.Itoa(maxOriginConnectionsPerEdge)
+               if ds.EdgeHeaderRewrite == nil {
+                       text += " [L]"
+               } else {
+                       text += "\n"
+               }
+       }
+
+       // write the contents of ds.EdgeHeaderRewrite to hdr_rw_xml-id.config 
replacing any instances of __RETURN__ (surrounded by spaces or not) with \n
+       if ds.EdgeHeaderRewrite != nil {
+               var re = regexp.MustCompile(`\s*__RETURN__\s*`)
+               text += re.ReplaceAllString(*ds.EdgeHeaderRewrite, "\n")
+       }
+
+       text += "\n"
+
+       w.Header().Set(tc.ContentType, tc.ContentTypeTextPlain)
+       w.Write([]byte(text))
+}
+
+func GetMidHeaderRewriteDotConfig(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, errCode := api.NewInfo(r, 
[]string{"cdn-name-or-id"}, nil)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+
+       cdnName, userErr, sysErr, errCode := getCDNNameFromNameOrID(inf.Tx.Tx, 
inf.Params["cdn-name-or-id"])
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+               return
+       }
+
+       text, err := headerComment(inf.Tx.Tx, "CDN "+cdnName)
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("getting hdr_rw_mid_xml-id.config text: "+err.Error()))
+               return
+       }
+
+       ds, err := getDeliveryService(inf.Tx, inf.Params["xml-id"])
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, nil, 
errors.New("getting hdr_rw_mid_xml-id.config text: "+err.Error()))
+               return
+       }
+
+       maxOriginConnections := *ds.MaxOriginConnections
+
+       dsType, err := deliveryservice.GetDeliveryServiceType(*ds.ID, inf.Tx.Tx)
+       if err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("getting hdr_rw_mid_xml-id.config text: "+err.Error()))
+               return
+       }
+       usesMids := dsType.UsesMidCache()
+
+       // write a header rewrite rule if maxOriginConnections > 0 and the ds 
DOES use mids
+       if maxOriginConnections > 0 && usesMids {
+               dsOnlineMidCount, err := getOnlineDSMidCount(inf.Tx, *ds.ID)
+               if err != nil {
+                       api.HandleErr(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, errors.New("getting ds server count: 
"+err.Error()))
+                       return
+               }
+               maxOriginConnectionsPerMid := 
int(math.Round(float64(maxOriginConnections) / float64(dsOnlineMidCount)))
+               text += "cond %{REMAP_PSEUDO_HOOK}\nset-config 
proxy.config.http.origin_max_connections " + 
strconv.Itoa(maxOriginConnectionsPerMid)
+               if ds.MidHeaderRewrite == nil {
+                       text += " [L]"
+               } else {
+                       text += "\n"
+               }
+       }
+
+       // write the contents of ds.MidHeaderRewrite to 
hdr_rw_mid_xml-id.config replacing any instances of __RETURN__ (surrounded by 
spaces or not) with \n
+       if ds.MidHeaderRewrite != nil {
+               var re = regexp.MustCompile(`\s*__RETURN__\s*`)
+               text += re.ReplaceAllString(*ds.MidHeaderRewrite, "\n")
+       }
+
+       text += "\n"
+
+       w.Header().Set(tc.ContentType, tc.ContentTypeTextPlain)
+       w.Write([]byte(text))
+}
+
+func getDeliveryService(tx *sqlx.Tx, xmlId string) 
(tc.DeliveryServiceNullable, error) {
+       qry := `SELECT id, cdn_id, max_origin_connections, edge_header_rewrite, 
mid_header_rewrite FROM deliveryservice WHERE xml_id = $1`
+       ds := tc.DeliveryServiceNullable{}
+       if err := tx.QueryRow(qry, xmlId).Scan(&ds.ID, &ds.CDNID, 
&ds.MaxOriginConnections, &ds.EdgeHeaderRewrite, &ds.MidHeaderRewrite); err != 
nil {
+               return tc.DeliveryServiceNullable{}, err
+       }
+       return ds, nil
+}
+// getOnlineDSEdgeCount gets the count of online or reported edges assigned to 
a delivery service
+func getOnlineDSEdgeCount(tx *sqlx.Tx, dsID int) (int, error) {
+       count := 0
+       qry := `SELECT count(1)
+               FROM deliveryservice_server 
+               JOIN server ON deliveryservice_server.server = server.id 
+               JOIN status ON server.status = status.id
+               WHERE deliveryservice_server.deliveryservice = $1 AND 
status.name IN ('REPORTED', 'ONLINE')`
+       if err := tx.QueryRow(qry, dsID).Scan(&count); err != nil {
+               return 0, err
+       }
+       return count, nil
+}
+
+// getOnlineDSMidCount gets the count of online or reported mids employed by 
the delivery service
+// 1. get the cache groups of the edges assigned to the ds
+// 2. get the parent cachegroups for those cachegroups (found in 1)
+// 3. get the servers that belong to those cachegroups that are a) mids and b) 
online/reported
+func getOnlineDSMidCount(tx *sqlx.Tx, dsID int) (int, error) {
+       count := 0
+       qry := `SELECT COUNT(1)
+FROM server AS s 
+JOIN type AS t ON s.type = t.id
+JOIN status AS st ON s.status = st.id
+WHERE t.name = 'MID' AND st.name IN ('ONLINE', 'REPORTED') AND s.cachegroup IN 
(
+    SELECT cg.parent_cachegroup_id FROM cachegroup AS cg 
+    WHERE cg.id IN (
+        SELECT s.cachegroup FROM server AS s 
+        WHERE s.id IN (
+            SELECT server FROM deliveryservice_server WHERE deliveryservice = 
$1)))`
+       if err := tx.QueryRow(qry, dsID).Scan(&count); err != nil {
+               return 0, err
+       }
+       return count, nil
+}
diff --git a/traffic_ops/traffic_ops_golang/ats/regexrevalidate.go 
b/traffic_ops/traffic_ops_golang/ats/regexrevalidate.go
index 5cfe985..1dc424d 100644
--- a/traffic_ops/traffic_ops_golang/ats/regexrevalidate.go
+++ b/traffic_ops/traffic_ops_golang/ats/regexrevalidate.go
@@ -22,7 +22,6 @@ package ats
 import (
        "database/sql"
        "errors"
-       "fmt"
        "net/http"
        "sort"
        "strconv"
@@ -31,12 +30,10 @@ import (
 
        "github.com/apache/trafficcontrol/lib/go-log"
        "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
-       
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
 )
 
 const DefaultMaxRevalDurationDays = 90
 const JobKeywordPurge = "PURGE"
-const HeaderCommentDateFormat = "Mon Jan 2 15:04:05 MST 2006"
 
 func GetRegexRevalidateDotConfig(w http.ResponseWriter, r *http.Request) {
        inf, userErr, sysErr, errCode := api.NewInfo(r, 
[]string{"cdn-name-or-id"}, nil)
@@ -61,29 +58,6 @@ func GetRegexRevalidateDotConfig(w http.ResponseWriter, r 
*http.Request) {
        w.Write([]byte(regexRevalTxt))
 }
 
-// getCDNNameFromNameOrID returns the CDN name from a parameter which may be 
the name or ID.
-// This also checks and verifies the existence of the given CDN, and returns 
an appropriate user error if it doesn't exist.
-// Returns the name, any user error, any system error, and any error code.
-func getCDNNameFromNameOrID(tx *sql.Tx, cdnNameOrID string) (string, error, 
error, int) {
-       if cdnID, err := strconv.Atoi(cdnNameOrID); err == nil {
-               cdnName, ok, err := dbhelpers.GetCDNNameFromID(tx, int64(cdnID))
-               if err != nil {
-                       return "", nil, fmt.Errorf("getting CDN name from id 
%v: %v", cdnID, err), http.StatusInternalServerError
-               } else if !ok {
-                       return "", errors.New("cdn not found"), nil, 
http.StatusNotFound
-               }
-               return string(cdnName), nil, nil, http.StatusOK
-       }
-
-       cdnName := cdnNameOrID
-       if ok, err := dbhelpers.CDNExists(cdnName, tx); err != nil {
-               return "", nil, fmt.Errorf("checking CDN name '%v' existence: 
%v", cdnName, err), http.StatusInternalServerError
-       } else if !ok {
-               return "", errors.New("cdn not found"), nil, http.StatusNotFound
-       }
-       return cdnName, nil, nil, http.StatusOK
-}
-
 func getRegexRevalidate(tx *sql.Tx, cdnName string) (string, error) {
        maxDays, ok, err := getMaxDays(tx)
        if err != nil {
@@ -207,43 +181,3 @@ func getMaxDays(tx *sql.Tx) (int64, bool, error) {
        }
        return days, true, nil
 }
-
-func headerComment(tx *sql.Tx, name string) (string, error) {
-       nameVersionStr, err := GetNameVersionString(tx)
-       if err != nil {
-               return "", errors.New("getting name version string: " + 
err.Error())
-       }
-       return "# DO NOT EDIT - Generated for " + name + " by " + 
nameVersionStr + " on " + time.Now().Format(HeaderCommentDateFormat) + "\n", nil
-}
-
-func GetNameVersionString(tx *sql.Tx) (string, error) {
-       qry := `
-SELECT
-  p.name,
-  p.value
-FROM
-  parameter p
-WHERE
-  (p.name = 'tm.toolname' OR p.name = 'tm.url') AND p.config_file = 'global'
-`
-       rows, err := tx.Query(qry)
-       if err != nil {
-               return "", errors.New("querying: " + err.Error())
-       }
-       defer rows.Close()
-       toolName := ""
-       url := ""
-       for rows.Next() {
-               name := ""
-               val := ""
-               if err := rows.Scan(&name, &val); err != nil {
-                       return "", errors.New("scanning: " + err.Error())
-               }
-               if name == "tm.toolname" {
-                       toolName = val
-               } else if name == "tm.url" {
-                       url = val
-               }
-       }
-       return toolName + " (" + url + ")", nil
-}
diff --git 
a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go
index 2e6e732..ad112da 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go
@@ -24,6 +24,7 @@ import (
        "errors"
        "fmt"
        "net/http"
+       "strconv"
 
        "github.com/apache/trafficcontrol/lib/go-tc"
        "github.com/apache/trafficcontrol/lib/go-util"
@@ -282,7 +283,7 @@ func (ds *TODeliveryServiceV12) Delete() (error, error, 
int) {
        return nil, nil, http.StatusOK
 }
 
-// Update is unimplemented, needed to satisfy CRUDer, since the framework 
doesn't allow an update to return an array of one
+// Update is unimplemented, needed to satisfy CRUDer, since the framework 
doesn't allow an update to return an array of one.
 func (ds *TODeliveryServiceV12) Update() (error, error, int) {
        return nil, nil, http.StatusNotImplemented
 }
@@ -309,3 +310,15 @@ func UpdateV12(w http.ResponseWriter, r *http.Request) {
        }
        api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Deliveryservice update 
was successful.", 
[]tc.DeliveryServiceNullableV12{dsv13.DeliveryServiceNullableV12})
 }
+
+// GetDeliveryServiceType returns the type of the deliveryservice.
+func GetDeliveryServiceType(dsID int, tx *sql.Tx) (tc.DSType, error) {
+       var dsType tc.DSType
+       if err := tx.QueryRow(`SELECT t.name FROM deliveryservice as ds JOIN 
type t ON ds.type = t.id WHERE ds.id=$1`, dsID).Scan(&dsType); err != nil {
+               if err == sql.ErrNoRows {
+                       return tc.DSTypeInvalid, errors.New("a deliveryservice 
with id '" + strconv.Itoa(dsID) + "' was not found")
+               }
+               return tc.DSTypeInvalid, errors.New("querying type from 
delivery service: " + err.Error())
+       }
+       return dsType, nil
+}
diff --git 
a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go
index 056c934..e81a980 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go
@@ -46,7 +46,7 @@ import (
 
 type TODeliveryServiceV13 struct {
        api.APIInfoImpl
-       tc.DeliveryServiceNullable
+       tc.DeliveryServiceNullableV13
 }
 
 func (ds *TODeliveryServiceV13) V12() *TODeliveryServiceV12 {
@@ -57,10 +57,10 @@ func (ds *TODeliveryServiceV13) V12() *TODeliveryServiceV12 
{
 }
 
 func (ds TODeliveryServiceV13) MarshalJSON() ([]byte, error) {
-       return json.Marshal(ds.DeliveryServiceNullable)
+       return json.Marshal(ds.DeliveryServiceNullableV13)
 }
 func (ds *TODeliveryServiceV13) UnmarshalJSON(data []byte) error {
-       return json.Unmarshal(data, ds.DeliveryServiceNullable)
+       return json.Unmarshal(data, ds.DeliveryServiceNullableV13)
 }
 
 func (ds *TODeliveryServiceV13) APIInfo() *api.APIInfo { return ds.ReqInfo }
@@ -88,7 +88,7 @@ func (ds *TODeliveryServiceV13) GetType() string {
 }
 
 func (ds *TODeliveryServiceV13) Validate() error {
-       return ds.DeliveryServiceNullable.Validate(ds.APIInfo().Tx.Tx)
+       return ds.DeliveryServiceNullableV13.Validate(ds.APIInfo().Tx.Tx)
 }
 
 // Create is unimplemented, needed to satisfy CRUDer, since the framework 
doesn't allow a create to return an array of one
@@ -105,7 +105,7 @@ func CreateV13(w http.ResponseWriter, r *http.Request) {
        }
        defer inf.Close()
 
-       ds := tc.DeliveryServiceNullable{}
+       ds := tc.DeliveryServiceNullableV13{}
        if err := api.Parse(r.Body, inf.Tx.Tx, &ds); err != nil {
                api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, 
errors.New("decoding: "+err.Error()), nil)
                return
@@ -118,12 +118,13 @@ func CreateV13(w http.ResponseWriter, r *http.Request) {
                api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, 
errors.New("invalid request: "+err.Error()), nil)
                return
        }
-       ds, errCode, userErr, sysErr = create(inf.Tx.Tx, *inf.Config, inf.User, 
ds)
+       dsv14 := tc.NewDeliveryServiceNullableFromV13(ds)
+       dsv14, errCode, userErr, sysErr = create(inf.Tx.Tx, *inf.Config, 
inf.User, dsv14)
        if userErr != nil || sysErr != nil {
                api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
                return
        }
-       api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Deliveryservice creation 
was successful.", []tc.DeliveryServiceNullable{ds})
+       api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Deliveryservice creation 
was successful.", 
[]tc.DeliveryServiceNullableV13{dsv14.DeliveryServiceNullableV13})
 }
 
 // create creates the given ds in the database, and returns the DS with its id 
and other fields created on insert set. On error, the HTTP status code, user 
error, and system error are returned. The status code SHOULD NOT be used, if 
both errors are nil.
@@ -140,7 +141,7 @@ func create(tx *sql.Tx, cfg config.Config, user 
*auth.CurrentUser, ds tc.Deliver
                deepCachingType = ds.DeepCachingType.String() // necessary, 
because DeepCachingType's default needs to insert the string, not "", and Query 
doesn't call .String().
        }
 
-       resultRows, err := tx.Query(insertQuery(), &ds.Active, 
&ds.AnonymousBlockingEnabled, &ds.CacheURL, &ds.CCRDNSTTL, &ds.CDNID, 
&ds.CheckPath, &ds.ConsistentHashRegex, &deepCachingType, &ds.DisplayName, 
&ds.DNSBypassCNAME, &ds.DNSBypassIP, &ds.DNSBypassIP6, &ds.DNSBypassTTL, 
&ds.DSCP, &ds.EdgeHeaderRewrite, &ds.GeoLimitRedirectURL, &ds.GeoLimit, 
&ds.GeoLimitCountries, &ds.GeoProvider, &ds.GlobalMaxMBPS, &ds.GlobalMaxTPS, 
&ds.FQPacingRate, &ds.HTTPBypassFQDN, &ds.InfoURL, &ds.InitialDispers [...]
+       resultRows, err := tx.Query(insertQuery(), &ds.Active, 
&ds.AnonymousBlockingEnabled, &ds.CacheURL, &ds.CCRDNSTTL, &ds.CDNID, 
&ds.CheckPath, &ds.ConsistentHashRegex, &deepCachingType, &ds.DisplayName, 
&ds.DNSBypassCNAME, &ds.DNSBypassIP, &ds.DNSBypassIP6, &ds.DNSBypassTTL, 
&ds.DSCP, &ds.EdgeHeaderRewrite, &ds.GeoLimitRedirectURL, &ds.GeoLimit, 
&ds.GeoLimitCountries, &ds.GeoProvider, &ds.GlobalMaxMBPS, &ds.GlobalMaxTPS, 
&ds.FQPacingRate, &ds.HTTPBypassFQDN, &ds.InfoURL, &ds.InitialDispers [...]
        if err != nil {
                usrErr, sysErr, code := api.ParseDBError(err)
                return tc.DeliveryServiceNullable{}, code, usrErr, sysErr
@@ -196,7 +197,7 @@ func create(tx *sql.Tx, cfg config.Config, user 
*auth.CurrentUser, ds tc.Deliver
 
        ds.ExampleURLs = MakeExampleURLs(ds.Protocol, *ds.Type, 
*ds.RoutingName, *ds.MatchList, cdnDomain)
 
-       if err := EnsureParams(tx, *ds.ID, *ds.XMLID, ds.EdgeHeaderRewrite, 
ds.MidHeaderRewrite, ds.RegexRemap, ds.CacheURL, ds.SigningAlgorithm, dsType); 
err != nil {
+       if err := EnsureParams(tx, *ds.ID, *ds.XMLID, ds.EdgeHeaderRewrite, 
ds.MidHeaderRewrite, ds.RegexRemap, ds.CacheURL, ds.SigningAlgorithm, dsType, 
ds.MaxOriginConnections); err != nil {
                return tc.DeliveryServiceNullable{}, 
http.StatusInternalServerError, nil, errors.New("ensuring ds parameters:: " + 
err.Error())
        }
 
@@ -230,7 +231,7 @@ func (ds *TODeliveryServiceV13) Read() ([]interface{}, 
error, error, int) {
        }
 
        for _, ds := range dses {
-               returnable = append(returnable, ds)
+               returnable = append(returnable, ds.DeliveryServiceNullableV13)
        }
        return returnable, nil, nil, http.StatusOK
 }
@@ -435,7 +436,7 @@ func update(tx *sql.Tx, cfg config.Config, user 
*auth.CurrentUser, ds *tc.Delive
                deepCachingType = ds.DeepCachingType.String() // necessary, 
because DeepCachingType's default needs to insert the string, not "", and Query 
doesn't call .String().
        }
 
-       resultRows, err := tx.Query(updateDSQuery(), &ds.Active, &ds.CacheURL, 
&ds.CCRDNSTTL, &ds.CDNID, &ds.CheckPath, &deepCachingType, &ds.DisplayName, 
&ds.DNSBypassCNAME, &ds.DNSBypassIP, &ds.DNSBypassIP6, &ds.DNSBypassTTL, 
&ds.DSCP, &ds.EdgeHeaderRewrite, &ds.GeoLimitRedirectURL, &ds.GeoLimit, 
&ds.GeoLimitCountries, &ds.GeoProvider, &ds.GlobalMaxMBPS, &ds.GlobalMaxTPS, 
&ds.FQPacingRate, &ds.HTTPBypassFQDN, &ds.InfoURL, &ds.InitialDispersion, 
&ds.IPV6RoutingEnabled, &ds.LogsEnabled, &ds.Lon [...]
+       resultRows, err := tx.Query(updateDSQuery(), &ds.Active, &ds.CacheURL, 
&ds.CCRDNSTTL, &ds.CDNID, &ds.CheckPath, &deepCachingType, &ds.DisplayName, 
&ds.DNSBypassCNAME, &ds.DNSBypassIP, &ds.DNSBypassIP6, &ds.DNSBypassTTL, 
&ds.DSCP, &ds.EdgeHeaderRewrite, &ds.GeoLimitRedirectURL, &ds.GeoLimit, 
&ds.GeoLimitCountries, &ds.GeoProvider, &ds.GlobalMaxMBPS, &ds.GlobalMaxTPS, 
&ds.FQPacingRate, &ds.HTTPBypassFQDN, &ds.InfoURL, &ds.InitialDispersion, 
&ds.IPV6RoutingEnabled, &ds.LogsEnabled, &ds.Lon [...]
 
        if err != nil {
                usrErr, sysErr, code := api.ParseDBError(err)
@@ -505,7 +506,7 @@ func update(tx *sql.Tx, cfg config.Config, user 
*auth.CurrentUser, ds *tc.Delive
                }
        }
 
-       if err := EnsureParams(tx, *ds.ID, *ds.XMLID, ds.EdgeHeaderRewrite, 
ds.MidHeaderRewrite, ds.RegexRemap, ds.CacheURL, ds.SigningAlgorithm, dsType); 
err != nil {
+       if err := EnsureParams(tx, *ds.ID, *ds.XMLID, ds.EdgeHeaderRewrite, 
ds.MidHeaderRewrite, ds.RegexRemap, ds.CacheURL, ds.SigningAlgorithm, 
newDSType, ds.MaxOriginConnections); err != nil {
                return tc.DeliveryServiceNullable{}, 
http.StatusInternalServerError, nil, errors.New("ensuring ds parameters:: " + 
err.Error())
        }
 
@@ -594,7 +595,7 @@ func GetDeliveryServices(query string, queryValues 
map[string]interface{}, tx *s
        for rows.Next() {
                ds := tc.DeliveryServiceNullable{}
                cdnDomain := ""
-               err := rows.Scan(&ds.Active, &ds.AnonymousBlockingEnabled, 
&ds.CacheURL, &ds.CCRDNSTTL, &ds.CDNID, &ds.CDNName, &ds.CheckPath, 
&ds.ConsistentHashRegex, &ds.DeepCachingType, &ds.DisplayName, 
&ds.DNSBypassCNAME, &ds.DNSBypassIP, &ds.DNSBypassIP6, &ds.DNSBypassTTL, 
&ds.DSCP, &ds.EdgeHeaderRewrite, &ds.GeoLimitRedirectURL, &ds.GeoLimit, 
&ds.GeoLimitCountries, &ds.GeoProvider, &ds.GlobalMaxMBPS, &ds.GlobalMaxTPS, 
&ds.FQPacingRate, &ds.HTTPBypassFQDN, &ds.ID, &ds.InfoURL, &ds.InitialDispersi 
[...]
+               err := rows.Scan(&ds.Active, &ds.AnonymousBlockingEnabled, 
&ds.CacheURL, &ds.CCRDNSTTL, &ds.CDNID, &ds.CDNName, &ds.CheckPath, 
&ds.ConsistentHashRegex, &ds.DeepCachingType, &ds.DisplayName, 
&ds.DNSBypassCNAME, &ds.DNSBypassIP, &ds.DNSBypassIP6, &ds.DNSBypassTTL, 
&ds.DSCP, &ds.EdgeHeaderRewrite, &ds.GeoLimitRedirectURL, &ds.GeoLimit, 
&ds.GeoLimitCountries, &ds.GeoProvider, &ds.GlobalMaxMBPS, &ds.GlobalMaxTPS, 
&ds.FQPacingRate, &ds.HTTPBypassFQDN, &ds.ID, &ds.InfoURL, &ds.InitialDispersi 
[...]
                if err != nil {
                        return nil, []error{fmt.Errorf("getting delivery 
services: %v", err)}, tc.SystemError
                }
@@ -784,11 +785,11 @@ const (
 
 // EnsureParams ensures the given delivery service's necessary parameters 
exist on profiles of servers assigned to the delivery service.
 // Note the edgeHeaderRewrite, midHeaderRewrite, regexRemap, and cacheURL may 
be nil, if the delivery service does not have those values.
-func EnsureParams(tx *sql.Tx, dsID int, xmlID string, edgeHeaderRewrite 
*string, midHeaderRewrite *string, regexRemap *string, cacheURL *string, 
signingAlgorithm *string, dsType tc.DSType) error {
-       if err := ensureHeaderRewriteParams(tx, dsID, xmlID, edgeHeaderRewrite, 
edgeTier, dsType); err != nil {
+func EnsureParams(tx *sql.Tx, dsID int, xmlID string, edgeHeaderRewrite 
*string, midHeaderRewrite *string, regexRemap *string, cacheURL *string, 
signingAlgorithm *string, dsType tc.DSType, maxOriginConns *int) error {
+       if err := ensureHeaderRewriteParams(tx, dsID, xmlID, edgeHeaderRewrite, 
edgeTier, dsType, maxOriginConns); err != nil {
                return errors.New("creating edge header rewrite parameters: " + 
err.Error())
        }
-       if err := ensureHeaderRewriteParams(tx, dsID, xmlID, midHeaderRewrite, 
midTier, dsType); err != nil {
+       if err := ensureHeaderRewriteParams(tx, dsID, xmlID, midHeaderRewrite, 
midTier, dsType, maxOriginConns); err != nil {
                return errors.New("creating mid header rewrite parameters: " + 
err.Error())
        }
        if err := ensureRegexRemapParams(tx, dsID, xmlID, regexRemap); err != 
nil {
@@ -803,15 +804,19 @@ func EnsureParams(tx *sql.Tx, dsID int, xmlID string, 
edgeHeaderRewrite *string,
        return nil
 }
 
-func ensureHeaderRewriteParams(tx *sql.Tx, dsID int, xmlID string, hdrRW 
*string, tier tierType, dsType tc.DSType) error {
-       if tier == midTier && dsType.IsLive() && !dsType.IsNational() {
-               return nil // live local DSes don't get remap rules
-       }
+func ensureHeaderRewriteParams(tx *sql.Tx, dsID int, xmlID string, hdrRW 
*string, tier tierType, dsType tc.DSType, maxOriginConns *int) error {
        configFile := "hdr_rw_" + xmlID + ".config"
        if tier == midTier {
                configFile = "hdr_rw_mid_" + xmlID + ".config"
        }
-       if hdrRW == nil || *hdrRW == "" {
+
+       if tier == midTier && dsType.IsLive() && !dsType.IsNational() {
+               // live local DSes don't get header rewrite rules on the mid so 
cleanup any location params related to mids
+               return deleteLocationParam(tx, configFile)
+       }
+
+       hasMaxOriginConns := *maxOriginConns > 0 && ((tier == midTier) == 
dsType.UsesMidCache())
+       if (hdrRW == nil || *hdrRW == "") && !hasMaxOriginConns {
                return deleteLocationParam(tx, configFile)
        }
        locationParamID, err := ensureLocation(tx, configFile)
@@ -937,7 +942,7 @@ func deleteLocationParam(tx *sql.Tx, configFile string) 
error {
        return nil
 }
 
-// export the selectQuery for the 'servers' package.
+// export the selectQuery for the 'deliveryservice' package.
 func GetDSSelectQuery() string {
        return selectQuery()
 }
@@ -979,6 +984,7 @@ ds.long_desc,
 ds.long_desc_1,
 ds.long_desc_2,
 ds.max_dns_answers,
+ds.max_origin_connections,
 ds.mid_header_rewrite,
 COALESCE(ds.miss_lat, 0.0),
 COALESCE(ds.miss_long, 0.0),
@@ -1070,8 +1076,9 @@ tr_response_headers=$47,
 type=$48,
 xml_id=$49,
 anonymous_blocking_enabled=$50,
-consistent_hash_regex=$51
-WHERE id=$52
+consistent_hash_regex=$51,
+max_origin_connections=$52
+WHERE id=$53
 RETURNING last_updated
 `
 }
@@ -1110,6 +1117,7 @@ long_desc,
 long_desc_1,
 long_desc_2,
 max_dns_answers,
+max_origin_connections,
 mid_header_rewrite,
 miss_lat,
 miss_long,
@@ -1131,7 +1139,7 @@ tr_response_headers,
 type,
 xml_id
 )
-VALUES 
($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$50,$51)
+VALUES 
($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$50,$51,$52)
 RETURNING id, last_updated
 `
 }
diff --git 
a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv14.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv14.go
new file mode 100644
index 0000000..98f3337
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv14.go
@@ -0,0 +1,170 @@
+package deliveryservice
+
+/*
+ * 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.
+ */
+
+import (
+       "encoding/json"
+       "errors"
+       "net/http"
+
+       "github.com/apache/trafficcontrol/lib/go-tc"
+       "github.com/apache/trafficcontrol/lib/go-util"
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+       "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
+)
+
+//we need a type alias to define functions on
+
+type TODeliveryServiceV14 struct {
+       api.APIInfoImpl
+       tc.DeliveryServiceNullable
+}
+
+func (ds *TODeliveryServiceV14) V13() *TODeliveryServiceV13 {
+       v13 := &TODeliveryServiceV13{}
+       v13.DeliveryServiceNullableV13 = ds.DeliveryServiceNullableV13
+       v13.SetInfo(ds.ReqInfo)
+       return v13
+}
+
+func (ds TODeliveryServiceV14) MarshalJSON() ([]byte, error) {
+       return json.Marshal(ds.DeliveryServiceNullable)
+}
+
+func (ds *TODeliveryServiceV14) UnmarshalJSON(data []byte) error {
+       return json.Unmarshal(data, ds.DeliveryServiceNullable)
+}
+
+func (ds *TODeliveryServiceV14) APIInfo() *api.APIInfo { return ds.ReqInfo }
+
+func (ds TODeliveryServiceV14) GetKeyFieldsInfo() []api.KeyFieldInfo {
+       return ds.V13().GetKeyFieldsInfo()
+}
+
+//Implementation of the Identifier, Validator interface functions
+func (ds TODeliveryServiceV14) GetKeys() (map[string]interface{}, bool) {
+       return ds.V13().GetKeys()
+}
+
+func (ds *TODeliveryServiceV14) SetKeys(keys map[string]interface{}) {
+       i, _ := keys["id"].(int) //this utilizes the non panicking type 
assertion, if the thrown away ok variable is false i will be the zero of the 
type, 0 here.
+       ds.ID = &i
+}
+
+func (ds *TODeliveryServiceV14) GetAuditName() string {
+       return ds.V13().GetAuditName()
+}
+
+func (ds *TODeliveryServiceV14) GetType() string {
+       return ds.V13().GetType()
+}
+
+func (ds *TODeliveryServiceV14) Validate() error {
+       return ds.DeliveryServiceNullable.Validate(ds.APIInfo().Tx.Tx)
+}
+
+//     TODO allow users to post names (type, cdn, etc) and get the IDs from 
the names. This isn't trivial to do in a single query, without dynamically 
building the entire insert query, and ideally inserting would be one query. But 
it'd be much more convenient for users. Alternatively, remove IDs from the 
database entirely and use real candidate keys.
+func CreateV14(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+
+       ds := tc.DeliveryServiceNullable{}
+       if err := api.Parse(r.Body, inf.Tx.Tx, &ds); err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, 
errors.New("decoding: "+err.Error()), nil)
+               return
+       }
+
+       if ds.RoutingName == nil || *ds.RoutingName == "" {
+               ds.RoutingName = util.StrPtr("cdn")
+       }
+       if err := ds.Validate(inf.Tx.Tx); err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, 
errors.New("invalid request: "+err.Error()), nil)
+               return
+       }
+       ds, errCode, userErr, sysErr = create(inf.Tx.Tx, *inf.Config, inf.User, 
ds)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+               return
+       }
+       api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Deliveryservice creation 
was successful.", []tc.DeliveryServiceNullable{ds})
+}
+
+func (ds *TODeliveryServiceV14) Read() ([]interface{}, error, error, int) {
+       returnable := []interface{}{}
+       dses, errs, _ := readGetDeliveryServices(ds.APIInfo().Params, 
ds.APIInfo().Tx, ds.APIInfo().User)
+       if len(errs) > 0 {
+               for _, err := range errs {
+                       if err.Error() == `id cannot parse to integer` { // 
TODO create const for string
+                               return nil, errors.New("Resource not found."), 
nil, http.StatusNotFound //matches perl response
+                       }
+               }
+               return nil, nil, errors.New("reading dses: " + 
util.JoinErrsStr(errs)), http.StatusInternalServerError
+       }
+
+       for _, ds := range dses {
+               returnable = append(returnable, ds)
+       }
+       return returnable, nil, nil, http.StatusOK
+}
+
+func UpdateV14(w http.ResponseWriter, r *http.Request) {
+       inf, userErr, sysErr, errCode := api.NewInfo(r, nil, []string{"id"})
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+               return
+       }
+       defer inf.Close()
+
+       id := inf.IntParams["id"]
+
+       ds := tc.DeliveryServiceNullable{}
+       if err := json.NewDecoder(r.Body).Decode(&ds); err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, 
errors.New("malformed JSON: "+err.Error()), nil)
+               return
+       }
+       ds.ID = &id
+
+       if err := ds.Validate(inf.Tx.Tx); err != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, 
errors.New("invalid request: "+err.Error()), nil)
+               return
+       }
+
+       ds, errCode, userErr, sysErr = update(inf.Tx.Tx, *inf.Config, inf.User, 
&ds)
+       if userErr != nil || sysErr != nil {
+               api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+               return
+       }
+       api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Deliveryservice update 
was successful.", []tc.DeliveryServiceNullable{ds})
+}
+
+// Delete is the DeliveryService implementation of the Deleter interface
+//all implementations of Deleter should use transactions and return the proper 
errorType
+func (ds *TODeliveryServiceV14) Delete() (error, error, int) {
+       return ds.V13().Delete()
+}
+
+// IsTenantAuthorized implements the Tenantable interface to ensure the user 
is authorized on the deliveryservice tenant
+func (ds *TODeliveryServiceV14) IsTenantAuthorized(user *auth.CurrentUser) 
(bool, error) {
+       return ds.V13().IsTenantAuthorized(user)
+}
diff --git 
a/traffic_ops/traffic_ops_golang/deliveryservice/request/requests_test.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/request/requests_test.go
index 2ddd489..c745399 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/request/requests_test.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/request/requests_test.go
@@ -62,15 +62,17 @@ func TestGetDeliveryServiceRequest(t *testing.T) {
                ChangeType: &u,
                Status:     &st,
                DeliveryService: &tc.DeliveryServiceNullable{
-                       DeliveryServiceNullableV12: 
tc.DeliveryServiceNullableV12{
-                               DeliveryServiceNullableV11: 
tc.DeliveryServiceNullableV11{
-                                       XMLID:       &s,
-                                       CDNID:       &i,
-                                       LogsEnabled: &b,
-                                       DSCP:        nil,
-                                       GeoLimit:    &i,
-                                       Active:      &b,
-                                       TypeID:      &i,
+                       DeliveryServiceNullableV13: 
tc.DeliveryServiceNullableV13{
+                               DeliveryServiceNullableV12: 
tc.DeliveryServiceNullableV12{
+                                       DeliveryServiceNullableV11: 
tc.DeliveryServiceNullableV11{
+                                               XMLID:       &s,
+                                               CDNID:       &i,
+                                               LogsEnabled: &b,
+                                               DSCP:        nil,
+                                               GeoLimit:    &i,
+                                               Active:      &b,
+                                               TypeID:      &i,
+                                       },
                                },
                        },
                },
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go
index 2a57ff1..2a508ab 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go
@@ -295,7 +295,7 @@ func GetReplaceHandler(w http.ResponseWriter, r 
*http.Request) {
                respServers = append(respServers, server)
        }
 
-       if err := deliveryservice.EnsureParams(inf.Tx.Tx, *dsId, ds.Name, 
ds.EdgeHeaderRewrite, ds.MidHeaderRewrite, ds.RegexRemap, ds.CacheURL, 
ds.SigningAlgorithm, ds.Type); err != nil {
+       if err := deliveryservice.EnsureParams(inf.Tx.Tx, *dsId, ds.Name, 
ds.EdgeHeaderRewrite, ds.MidHeaderRewrite, ds.RegexRemap, ds.CacheURL, 
ds.SigningAlgorithm, ds.Type, ds.MaxOriginConnections); err != nil {
                api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("deliveryservice_server replace ensuring ds parameters: 
"+err.Error()))
                return
        }
@@ -361,7 +361,7 @@ func GetCreateHandler(w http.ResponseWriter, r 
*http.Request) {
                return
        }
 
-       if err := deliveryservice.EnsureParams(inf.Tx.Tx, ds.ID, ds.Name, 
ds.EdgeHeaderRewrite, ds.MidHeaderRewrite, ds.RegexRemap, ds.CacheURL, 
ds.SigningAlgorithm, ds.Type); err != nil {
+       if err := deliveryservice.EnsureParams(inf.Tx.Tx, ds.ID, ds.Name, 
ds.EdgeHeaderRewrite, ds.MidHeaderRewrite, ds.RegexRemap, ds.CacheURL, 
ds.SigningAlgorithm, ds.Type, ds.MaxOriginConnections); err != nil {
                api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, 
nil, errors.New("deliveryservice_server replace ensuring ds parameters: 
"+err.Error()))
                return
        }
@@ -564,14 +564,15 @@ func updateQuery() string {
 }
 
 type DSInfo struct {
-       ID                int
-       Name              string
-       Type              tc.DSType
-       EdgeHeaderRewrite *string
-       MidHeaderRewrite  *string
-       RegexRemap        *string
-       SigningAlgorithm  *string
-       CacheURL          *string
+       ID                   int
+       Name                 string
+       Type                 tc.DSType
+       EdgeHeaderRewrite    *string
+       MidHeaderRewrite     *string
+       RegexRemap           *string
+       SigningAlgorithm     *string
+       CacheURL             *string
+       MaxOriginConnections *int
 }
 
 // GetDSInfo loads the DeliveryService fields needed by Delivery Service 
Servers from the database, from the ID. Returns the data, whether the delivery 
service was found, and any error.
@@ -584,7 +585,8 @@ SELECT
   ds.mid_header_rewrite,
   ds.regex_remap,
   ds.signing_algorithm,
-  ds.cacheurl
+  ds.cacheurl,
+  ds.max_origin_connections
 FROM
   deliveryservice ds
   JOIN type tp ON ds.type = tp.id
@@ -592,7 +594,7 @@ WHERE
   ds.id = $1
 `
        di := DSInfo{ID: id}
-       if err := tx.QueryRow(qry, id).Scan(&di.Name, &di.Type, 
&di.EdgeHeaderRewrite, &di.MidHeaderRewrite, &di.RegexRemap, 
&di.SigningAlgorithm, &di.CacheURL); err != nil {
+       if err := tx.QueryRow(qry, id).Scan(&di.Name, &di.Type, 
&di.EdgeHeaderRewrite, &di.MidHeaderRewrite, &di.RegexRemap, 
&di.SigningAlgorithm, &di.CacheURL, &di.MaxOriginConnections); err != nil {
                if err == sql.ErrNoRows {
                        return DSInfo{}, false, nil
                }
@@ -612,7 +614,8 @@ SELECT
   ds.mid_header_rewrite,
   ds.regex_remap,
   ds.signing_algorithm,
-  ds.cacheurl
+  ds.cacheurl,
+  ds.max_origin_connections
 FROM
   deliveryservice ds
   JOIN type tp ON ds.type = tp.id
@@ -620,7 +623,7 @@ WHERE
   ds.xml_id = $1
 `
        di := DSInfo{Name: dsName}
-       if err := tx.QueryRow(qry, dsName).Scan(&di.ID, &di.Type, 
&di.EdgeHeaderRewrite, &di.MidHeaderRewrite, &di.RegexRemap, 
&di.SigningAlgorithm, &di.CacheURL); err != nil {
+       if err := tx.QueryRow(qry, dsName).Scan(&di.ID, &di.Type, 
&di.EdgeHeaderRewrite, &di.MidHeaderRewrite, &di.RegexRemap, 
&di.SigningAlgorithm, &di.CacheURL, &di.MaxOriginConnections); err != nil {
                if err == sql.ErrNoRows {
                        return DSInfo{}, false, nil
                }
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go 
b/traffic_ops/traffic_ops_golang/routing/routes.go
index 3b52b13..999ce4a 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -378,7 +378,10 @@ func Routes(d ServerData) ([]Route, []RawRoute, 
http.Handler, error) {
                {1.1, http.MethodPut, `cdns/{id}/snapshot/?$`, 
crconfig.SnapshotHandler, auth.PrivLevelOperations, Authenticated, nil},
                {1.1, http.MethodPut, `snapshot/{cdn}/?$`, 
crconfig.SnapshotHandler, auth.PrivLevelOperations, Authenticated, nil},
 
+               // ATS config files
                {1.1, http.MethodGet, 
`cdns/{cdn-name-or-id}/configfiles/ats/regex_revalidate.config/?(\.json)?$`, 
ats.GetRegexRevalidateDotConfig, auth.PrivLevelOperations, Authenticated, nil},
+               {1.1, http.MethodGet, 
`cdns/{cdn-name-or-id}/configfiles/ats/hdr_rw_mid_{xml-id}.config/?(\.json)?$`, 
ats.GetMidHeaderRewriteDotConfig, auth.PrivLevelOperations, Authenticated, nil},
+               {1.1, http.MethodGet, 
`cdns/{cdn-name-or-id}/configfiles/ats/hdr_rw_{xml-id}.config/?(\.json)?$`, 
ats.GetEdgeHeaderRewriteDotConfig, auth.PrivLevelOperations, Authenticated, 
nil},
 
                // Federations
                {1.4, http.MethodGet, `federations/all/?(\.json)?$`, 
federations.GetAll, auth.PrivLevelAdmin, Authenticated, nil},
@@ -386,16 +389,26 @@ func Routes(d ServerData) ([]Route, []RawRoute, 
http.Handler, error) {
                {1.1, http.MethodPost, 
`federations/{id}/deliveryservices?(\.json)?$`, federations.PostDSes, 
auth.PrivLevelAdmin, Authenticated, nil},
 
                ////DeliveryServices
+               {1.4, http.MethodGet, `deliveryservices/?(\.json)?$`, 
api.ReadHandler(&deliveryservice.TODeliveryServiceV14{}), 
auth.PrivLevelReadOnly, Authenticated, nil},
                {1.3, http.MethodGet, `deliveryservices/?(\.json)?$`, 
api.ReadHandler(&deliveryservice.TODeliveryServiceV13{}), 
auth.PrivLevelReadOnly, Authenticated, nil},
                {1.1, http.MethodGet, `deliveryservices/?(\.json)?$`, 
api.ReadHandler(&deliveryservice.TODeliveryServiceV12{}), 
auth.PrivLevelReadOnly, Authenticated, nil},
+
+               {1.4, http.MethodGet, `deliveryservices/{id}/?(\.json)?$`, 
api.ReadHandler(&deliveryservice.TODeliveryServiceV14{}), 
auth.PrivLevelReadOnly, Authenticated, nil},
                {1.3, http.MethodGet, `deliveryservices/{id}/?(\.json)?$`, 
api.ReadHandler(&deliveryservice.TODeliveryServiceV13{}), 
auth.PrivLevelReadOnly, Authenticated, nil},
                {1.1, http.MethodGet, `deliveryservices/{id}/?(\.json)?$`, 
api.ReadHandler(&deliveryservice.TODeliveryServiceV12{}), 
auth.PrivLevelReadOnly, Authenticated, nil},
+
+               {1.4, http.MethodPost, `deliveryservices/?(\.json)?$`, 
deliveryservice.CreateV14, auth.PrivLevelOperations, Authenticated, nil},
                {1.3, http.MethodPost, `deliveryservices/?(\.json)?$`, 
deliveryservice.CreateV13, auth.PrivLevelOperations, Authenticated, nil},
                {1.1, http.MethodPost, `deliveryservices/?(\.json)?$`, 
deliveryservice.CreateV12, auth.PrivLevelOperations, Authenticated, nil},
+
+               {1.4, http.MethodPut, `deliveryservices/{id}/?(\.json)?$`, 
deliveryservice.UpdateV14, auth.PrivLevelOperations, Authenticated, nil},
                {1.3, http.MethodPut, `deliveryservices/{id}/?(\.json)?$`, 
deliveryservice.UpdateV13, auth.PrivLevelOperations, Authenticated, nil},
                {1.1, http.MethodPut, `deliveryservices/{id}/?(\.json)?$`, 
deliveryservice.UpdateV12, auth.PrivLevelOperations, Authenticated, nil},
+
+               {1.4, http.MethodDelete, `deliveryservices/{id}/?(\.json)?$`, 
api.DeleteHandler(&deliveryservice.TODeliveryServiceV14{}), 
auth.PrivLevelOperations, Authenticated, nil},
                {1.3, http.MethodDelete, `deliveryservices/{id}/?(\.json)?$`, 
api.DeleteHandler(&deliveryservice.TODeliveryServiceV13{}), 
auth.PrivLevelOperations, Authenticated, nil},
                {1.1, http.MethodDelete, `deliveryservices/{id}/?(\.json)?$`, 
api.DeleteHandler(&deliveryservice.TODeliveryServiceV12{}), 
auth.PrivLevelOperations, Authenticated, nil},
+
                {1.1, http.MethodGet, 
`deliveryservices/{id}/servers/eligible/?(\.json)?$`, 
deliveryservice.GetServersEligible, auth.PrivLevelReadOnly, Authenticated, nil},
 
                {1.1, http.MethodGet, 
`deliveryservices/xmlId/{xmlid}/sslkeys$`, deliveryservice.GetSSLKeysByXMLID, 
auth.PrivLevelAdmin, Authenticated, nil},
diff --git a/traffic_ops/traffic_ops_golang/server/servers.go 
b/traffic_ops/traffic_ops_golang/server/servers.go
index b05cf9b..7c564ca 100644
--- a/traffic_ops/traffic_ops_golang/server/servers.go
+++ b/traffic_ops/traffic_ops_golang/server/servers.go
@@ -20,7 +20,6 @@ package server
  */
 
 import (
-       "database/sql"
        "errors"
        "fmt"
        "net/http"
@@ -35,6 +34,7 @@ import (
        "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
        
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
 
+       
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/deliveryservice"
        "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
        "github.com/go-ozzo/ozzo-validation"
        "github.com/go-ozzo/ozzo-validation/is"
@@ -192,18 +192,6 @@ func (server *TOServer) Read() ([]interface{}, error, 
error, int) {
        return returnable, nil, nil, http.StatusOK
 }
 
-// getDeliveryServiceType returns the type of the deliveryservice
-func getDeliveryServiceType(dsID int, tx *sql.Tx) (tc.DSType, error) {
-       var dsType tc.DSType
-       if err := tx.QueryRow(`SELECT t.name FROM deliveryservice as ds JOIN 
type t ON ds.type = t.id WHERE ds.id=$1`, dsID).Scan(&dsType); err != nil {
-               if err == sql.ErrNoRows {
-                       return tc.DSTypeInvalid, errors.New("a deliveryservice 
with id '" + strconv.Itoa(dsID) + "' was not found")
-               }
-               return tc.DSTypeInvalid, errors.New("querying type from 
delivery service: " + err.Error())
-       }
-       return dsType, nil
-}
-
 func getServers(params map[string]string, tx *sqlx.Tx, user *auth.CurrentUser) 
([]tc.ServerNullable, []error, tc.ApiErrorType) {
        // Query Parameters to Database Query column mappings
        // see the fields mapped in the SQL query
@@ -237,7 +225,7 @@ func getServers(params map[string]string, tx *sqlx.Tx, user 
*auth.CurrentUser) (
 FULL OUTER JOIN deliveryservice_server dss ON dss.server = s.id
 `
                // depending on ds type, also need to add mids
-               dsType, err := getDeliveryServiceType(dsID, tx.Tx)
+               dsType, err := deliveryservice.GetDeliveryServiceType(dsID, 
tx.Tx)
                if err != nil {
                        return nil, []error{err}, tc.DataConflictError
                }
diff --git 
a/traffic_portal/app/src/common/modules/form/deliveryService/form.deliveryService.DNS.tpl.html
 
b/traffic_portal/app/src/common/modules/form/deliveryService/form.deliveryService.DNS.tpl.html
index 310dc04..a4eac81 100644
--- 
a/traffic_portal/app/src/common/modules/form/deliveryService/form.deliveryService.DNS.tpl.html
+++ 
b/traffic_portal/app/src/common/modules/form/deliveryService/form.deliveryService.DNS.tpl.html
@@ -828,6 +828,20 @@ under the License.
                     </div>
                 </div>
 
+                <div class="form-group" ng-class="{'has-error': 
hasError(deliveryServiceForm.maxOriginConnections), 'has-feedback': 
hasError(deliveryServiceForm.maxOriginConnections)}">
+                    <label class="has-tooltip control-label col-md-2 col-sm-2 
col-xs-12" for="maxOriginConnections">Max Origin Connections<div 
class="helptooltip">
+                        <div class="helptext">
+                            The maximum number of connections allowed to the 
origin. Enter <code>0</code> to indicate no maximum.
+                        </div>
+                    </div>
+                    </label>
+                    <div class="col-md-10 col-sm-10 col-xs-12">
+                        <input id="maxOriginConnections" 
name="maxOriginConnections" type="number" class="form-control" 
ng-model="deliveryService.maxOriginConnections" step="1" min="0">
+                        <small class="input-diff" ng-if="settings.isRequest" 
ng-show="open() && deliveryService.maxOriginConnections != 
dsCurrent.maxOriginConnections">Current Value: [ 
{{dsCurrent.maxOriginConnections}} ]</small>
+                        <span 
ng-show="hasError(deliveryServiceForm.maxOriginConnections)" 
class="form-control-feedback"><i class="fa fa-times"></i></span>
+                    </div>
+                </div>
+
             </div>
 
             <div class="modal-footer">
diff --git 
a/traffic_portal/app/src/common/modules/form/deliveryService/form.deliveryService.HTTP.tpl.html
 
b/traffic_portal/app/src/common/modules/form/deliveryService/form.deliveryService.HTTP.tpl.html
index 550fe67..a038615 100644
--- 
a/traffic_portal/app/src/common/modules/form/deliveryService/form.deliveryService.HTTP.tpl.html
+++ 
b/traffic_portal/app/src/common/modules/form/deliveryService/form.deliveryService.HTTP.tpl.html
@@ -856,6 +856,20 @@ under the License.
                     </div>
                 </div>
 
+                <div class="form-group" ng-class="{'has-error': 
hasError(deliveryServiceForm.maxOriginConnections), 'has-feedback': 
hasError(deliveryServiceForm.maxOriginConnections)}">
+                    <label class="has-tooltip control-label col-md-2 col-sm-2 
col-xs-12" for="maxOriginConnections">Max Origin Connections<div 
class="helptooltip">
+                        <div class="helptext">
+                            The maximum number of connections allowed to the 
origin. Enter <code>0</code> to indicate no maximum.
+                        </div>
+                    </div>
+                    </label>
+                    <div class="col-md-10 col-sm-10 col-xs-12">
+                        <input id="maxOriginConnections" 
name="maxOriginConnections" type="number" class="form-control" 
ng-model="deliveryService.maxOriginConnections" step="1" min="0">
+                        <small class="input-diff" ng-if="settings.isRequest" 
ng-show="open() && deliveryService.maxOriginConnections != 
dsCurrent.maxOriginConnections">Current Value: [ 
{{dsCurrent.maxOriginConnections}} ]</small>
+                        <span 
ng-show="hasError(deliveryServiceForm.maxOriginConnections)" 
class="form-control-feedback"><i class="fa fa-times"></i></span>
+                    </div>
+                </div>
+
                 <div class="form-group" ng-class="{'has-error': 
hasError(deliveryServiceForm.consistentHashRegex), 'has-feedback': 
hasError(deliveryServiceForm.consistentHashRegex)}">
                     <label class="has-tooltip control-label col-md-2 col-sm-2 
col-xs-12" for="consistentHashRegex">Consistent Hash Regex<div 
class="helptooltip">
                         <div class="helptext">
diff --git a/traffic_portal/app/src/traffic_portal_properties.json 
b/traffic_portal/app/src/traffic_portal_properties.json
index ad493e2..635d109 100644
--- a/traffic_portal/app/src/traffic_portal_properties.json
+++ b/traffic_portal/app/src/traffic_portal_properties.json
@@ -122,6 +122,7 @@
           "ipv6RoutingEnabled": true,
           "rangeRequestHandling": 0,
           "qstringIgnore": 0,
+          "maxOriginConnections": 0,
           "multiSiteOrigin": false,
           "logsEnabled": false,
           "geoProvider": 0,
@@ -140,6 +141,7 @@
           "ipv6RoutingEnabled": true,
           "rangeRequestHandling": 0,
           "qstringIgnore": 0,
+          "maxOriginConnections": 0,
           "multiSiteOrigin": false,
           "logsEnabled": false,
           "initialDispersion": 1,

Reply via email to