You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ro...@apache.org on 2019/04/16 17:37:02 UTC

[trafficcontrol] branch master updated: 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)

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 <mi...@users.noreply.github.com>
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,