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

[trafficcontrol] branch master updated: Move TO DS nonversioned logic to separate file. (#3493)

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

dangogh 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 5120f76  Move TO DS nonversioned logic to separate file. (#3493)
5120f76 is described below

commit 5120f7612748d98d7a46564e95a0053fa796517e
Author: Robert Butts <ro...@users.noreply.github.com>
AuthorDate: Mon Apr 22 21:37:57 2019 -0600

    Move TO DS nonversioned logic to separate file. (#3493)
    
    * Change TO dses to separate versions
    
    Puts all the logic in deliveryservices.go, and makes v12.go, v13.go,
    v14.go purely boilerplate.
    
    This should make it significantly easier to add new versions: you'll
    only have to copy the previous v13.go file, rename v13->v14, and
    add your logic to the main deliveryservices.go
    
    Also adds a section on adding new versions to the README.md.
    
    * Remove TO Nullable omitempty
    
    Per PR Review.
---
 lib/go-tc/deliveryservices.go                      |   22 +-
 traffic_ops/traffic_ops_golang/README.md           |  115 ++-
 ...{deliveryservicesv13.go => deliveryservices.go} |  471 +++++----
 .../deliveryservice/deliveryservicesv12.go         |  209 +---
 .../deliveryservice/deliveryservicesv13.go         | 1011 +-------------------
 .../deliveryservice/deliveryservicesv14.go         |  170 ----
 traffic_ops/traffic_ops_golang/routing/routes.go   |   10 +-
 7 files changed, 425 insertions(+), 1583 deletions(-)

diff --git a/lib/go-tc/deliveryservices.go b/lib/go-tc/deliveryservices.go
index 7bc7865..128bda3 100644
--- a/lib/go-tc/deliveryservices.go
+++ b/lib/go-tc/deliveryservices.go
@@ -78,7 +78,7 @@ type DeliveryService struct {
 }
 
 type DeliveryServiceV13 struct {
-	DeliveryServiceV12
+	DeliveryServiceV11
 	DeepCachingType   DeepCachingType `json:"deepCachingType"`
 	FQPacingRate      int             `json:"fqPacingRate,omitempty"`
 	SigningAlgorithm  string          `json:"signingAlgorithm" db:"signing_algorithm"`
@@ -87,10 +87,6 @@ type DeliveryServiceV13 struct {
 	TRResponseHeaders string          `json:"trResponseHeaders,omitempty"`
 }
 
-type DeliveryServiceV12 struct {
-	DeliveryServiceV11
-}
-
 // DeliveryService ...
 // TODO move contents to DeliveryServiceV12, fix references, and remove
 type DeliveryServiceV11 struct {
@@ -152,18 +148,18 @@ type DeliveryServiceV11 struct {
 
 type DeliveryServiceNullable struct {
 	DeliveryServiceNullableV13
-	MaxOriginConnections *int `json:"maxOriginConnections" db:"max_origin_connections"`
+	ConsistentHashRegex  *string `json:"consistentHashRegex"`
+	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"`
-	FQPacingRate        *int             `json:"fqPacingRate,omitempty"`
-	SigningAlgorithm    *string          `json:"signingAlgorithm" db:"signing_algorithm"`
-	Tenant              *string          `json:"tenant,omitempty"`
-	TRResponseHeaders   *string          `json:"trResponseHeaders,omitempty"`
-	TRRequestHeaders    *string          `json:"trRequestHeaders,omitempty"`
+	DeepCachingType   *DeepCachingType `json:"deepCachingType" db:"deep_caching_type"`
+	FQPacingRate      *int             `json:"fqPacingRate"`
+	SigningAlgorithm  *string          `json:"signingAlgorithm" db:"signing_algorithm"`
+	Tenant            *string          `json:"tenant"`
+	TRResponseHeaders *string          `json:"trResponseHeaders"`
+	TRRequestHeaders  *string          `json:"trRequestHeaders"`
 }
 
 type DeliveryServiceNullableV12 struct {
diff --git a/traffic_ops/traffic_ops_golang/README.md b/traffic_ops/traffic_ops_golang/README.md
index ac562cb..a368998 100644
--- a/traffic_ops/traffic_ops_golang/README.md
+++ b/traffic_ops/traffic_ops_golang/README.md
@@ -17,8 +17,9 @@
     under the License.
 -->
 
-Prequisites
-=======================================
+# Running
+
+## Prequisites
 
 To run `traffic_ops_golang` proxy locally the following prerequisites are needed:
 
@@ -27,8 +28,7 @@ To run `traffic_ops_golang` proxy locally the following prerequisites are needed
 * Because the Golang proxy is fronting Mojolicious Perl you need to have that service setup and running as well [TO Perl Setup Here](https://github.com/apache/trafficcontrol/blob/master/traffic_ops/INSTALL.md)
 
 
-Vendoring and Building
-=======================================
+## Vendoring and Building
 
 ### vendoring
 We treat `golang.org/x` as a part of the Go compiler so that means that we still vendor application dependencies for stability and reproducible builds.  The [govend](https://github.com/govend/govend) tool is helpful for managing dependencies.
@@ -38,8 +38,8 @@ To download the remaining `golang.org/x` dependencies you need to:
 
 `$ go get -v`
 
-Configuration
-=======================================
+## Configuration
+
 To run the Golang proxy locally the following represents a typical sequence flow.  */api/1.2* will proxy through to Mojo Perl. */api/1.3* will serve the response from the Golang proxy directly and/or interact with Postgres accordingly.
 
 **/api/1.2** routes:
@@ -50,26 +50,24 @@ To run the Golang proxy locally the following represents a typical sequence flow
 
 `TO Golang Proxy (port 8443)`<-->`TO Database (Postgres)`
 
-### cdn.conf changes
-=======================================
 
+### cdn.conf changes
 
 Copy `traffic_ops/app/conf/cdn.conf` to `$HOME/cdn.conf` so you can modify it for development purposes.
 
 `$HOME/cdn.conf`
 
-```    
+```
        "traffic_ops_golang" : {
           "port" : "443",
 ```
 
-```        
+```
        "traffic_ops_golang" : {
           "port" : "8443",
 ```
 
-### Logging
-=======================================
+## Logging
 
 By default `/var/log/traffic_ops/error.log` is configured for output, to change this modify your `$HOME/cdn.conf` for the following:
 
@@ -87,16 +85,86 @@ By default `/var/log/traffic_ops/error.log` is configured for output, to change
      }
 ```
 
-Development
-=======================================
+# Development
 
 Go is a compiled language so any local changes will require you to CTRL-C the console and re-run the `traffic_ops_golang` Go binary locally:
 
 `go build && ./traffic_ops_golang -cfg $HOME/cdn.conf -dbcfg ../app/conf/development/database.conf`
 
+## Updating a Minor Version
+
+Traffic Control implements [Semantic Versioning](https://semver.org). When adding new fields to the API, we must increase the minor version. If you're the first one adding a new field to a particular object in a particular release, you'll need to do this.
+
+The structs with no version in the name are the latest version.
+
+Most structs do not have versioning. If you are adding a field to a struct with no existing versioning. see `lib/go-tc/deliveryservices.go` for an example.
+
+1. In `lib/go-tc`, rename the old struct to be the previous minor version.
+    - For example, if you are adding a field to Delivery Service and existing minor version is 1.4 (so your new minor version is 1.5), in `lib/go-tc/deliveryservices.go` rename `type DeliveryServiceNullable struct` to `type DeliveryServiceNullableV14 struct`.
+  - Also rename any `Sanitize` and `Validate` functions to the old object.
+
+2. In `lib/go-tc`, create a new struct with an unversioned name, and anonymously embed the previous struct (that you just renamed), along with your new field.
+    - For example:
+```go
+type DeliveryServiceNullable struct {
+	DeliveryServiceNullableV14
+	MyNewField *int `json:"myNewField" db:"my_new_field"`
+}
+```
+
+3. Create a `Sanitize` function on the new struct, e.g. `func (ds *DeliveryServiceNullable) Sanitize()`, which sets your new field to a default value, if it is null.
+    - It must always be possible to create objects with previous API versions. Therefore, this step is not optional.
+    - The new `Sanitize` function must call the previous version's `Sanitize` as well, in order to sanitize all previous versions. E.g.
+```go
+  func (ds *DeliveryServiceNullable) Sanitize() {
+	ds.DeliveryServiceNullableV14.Sanitize()
+```
+
+4. Create a `Validate` function, which immediately calls the `Sanitize` function, as well as doing any other validation on your new field.
+    - `Validate` is used to `Sanitize` by the API frameworks. If a `Validate` function doesn't exist, your new field won't be checked and made valid, and may result in nil panics. Therefore, this step is not optional.
+    - For example, if your new field is a port, `Validate` should verify it is between 0 and 65535.
+    - Almost all fields can be invalid! Don't skip this step. Proper validation is essential to Traffic Control functioning properly and rejecting invalid input.
+
+    For example:
+
+```go
+func (ds *DeliveryServiceNullableV14) Validate(tx *sql.Tx) error {
+	ds.Sanitize()
+```
+
+
+5. Create a func to convert the previous version to the new latest struct. For example, `func NewDeliveryServiceNullableFromV14(ds DeliveryServiceNullableV14) DeliveryServiceNullable`. This function will typically do nothing more than create the latest object with the older version, and sanitize new fields. E.g.
+```go
+func NewDeliveryServiceNullableFromV14(ds DeliveryServiceNullableV14) DeliveryServiceNullable {
+	newDS := DeliveryServiceNullable{DeliveryServiceNullableV14: ds}
+	newDS.Sanitize()
+	return newDS
+}
+```
+
+6. In `traffic_ops/traffic_ops_golang`, copy the existing previous version file, e.g. `cp traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv1{3,4}.go`.
+    - If the object has no previous version, see `deliveryservice` for an example. The "CRUDer" version file should contain only boilerplate, no logic, and no reference to other versions except the latest. Hence, it should be possible to copy and rename, with no logic changes. The logic and latest version should all be in the main file, e.g. `deliveryservice/deliveryservices.go`.
+
+7. In the new version file, rename all instances of the previous version to the new version, e.g. `sed -i 's/v13/v14/' deliveryservicesv14.go`.
+
+8. Add the logic for your new field to the latest version file, e.g. `deliveryservice/deliveryservices.go`.
+
+9. Add your new version to `traffic_ops/traffic_ops_golang/routing/routes.go`, and add the versioned object to the previous route.
+    - The new latest route must go above the previous version. If the new version is below the old, the new version will never be routed to!
+
+    For example, Change:
+```go
+{1.4, http.MethodGet, `deliveryservices/{id}/?(\.json)?$`, api.ReadHandler(&deliveryservice.TODeliveryService{}), auth.PrivLevelReadOnly, Authenticated, nil},
+```
+
+  To:
+
+```go
+{1.5, http.MethodGet, `deliveryservices/{id}/?(\.json)?$`, api.ReadHandler(&deliveryservice.TODeliveryService{}), auth.PrivLevelReadOnly, Authenticated, nil},
+{1.4, http.MethodGet, `deliveryservices/{id}/?(\.json)?$`, api.ReadHandler(&deliveryservice.TODeliveryServiceV14{}), auth.PrivLevelReadOnly, Authenticated, nil},
+```
 
-Converting Routes to Traffic Ops Golang
-=======================================
+## Converting Routes to Traffic Ops Golang
 
 Traffic Ops is moving to Go! You can help!
 
@@ -104,11 +172,9 @@ We're in the process of migrating the Perl/Mojolicious Traffic Ops to Go. This i
 
 You'll need at least a basic understanding of Perl and Go, or be willing to learn them. You'll also need a running Traffic Ops instance, to compare the old and new routes and make sure they're identical.
 
-Converting an Endpoint
-======================
+### Converting an Endpoint
 
-Perl
-----
+#### Perl
 
 If you don't already have an endpoint in mind, open [TrafficOpsRoutes.pm](../app/lib/TrafficOpsRoutes.pm) and browse the routes. Start with `/api/` routes. We'll be moving others, like config files, but they're a bit more complex. We specifically won't be moving GUI routes (e.g. `/asns`), they'll go away when the new [Portal](https://github.com/apache/trafficcontrol/tree/master/traffic_portal) is done.
 
@@ -118,16 +184,15 @@ As you can see, this is a very simple route. It queries the database `CDN` table
 
 If you go to `/api/1.2/cdns` in a browser, you'll see Perl is also wrapping it in a `"response"` object.
 
-Go
---
+#### Go
 
 Now we need to create the Go endpoint.
 
-#### Getting a "Handle" on Routes
+##### Getting a "Handle" on Routes
 
 Open [routes.go](./routing/routes.go). Routes are defined in the `Routes` function, of the form `{version, method, path, handler}`. Notice the path can contain variables, of the form `/{var}/`. These variables will be made available to your handler.
 
-#### Creating a Handler
+##### Creating a Handler
 
 The first step is to create your handler. For an example, look at `monitoringHandler` in `monitoring.go`. Your handler arguments can be any data available to the router (the config and database, or what you can create from them). Passing the `db` or prepared `Stmt`s is common. The handler function must return a `RegexHandlerFunc`. In general, you want to return an inline function, `return func(w http.ResponseWriter, r *http.Request, p ParamMap) {...`.
 
@@ -139,7 +204,7 @@ This is the hard part, where you have to recreate the Perl response. But it's al
 
 Your handler should be in its own file, where you can create any structs and helper functions you need.
 
-#### Registering the Handler
+##### Registering the Handler
 
 Back to `routes.go`, you need to add your handler to the `Routes` function. For example, `/api/1.2/cdns` would look like `{1.2, http.MethodGet, "cdns", wrapHeaders(wrapAuth(cdnsHandler(d.DB), d.Insecure, d.TOSecret, rd.PrivLevelStmt, CdnsPrivLevel))},`.
 
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go
similarity index 87%
copy from traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go
copy to traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go
index 9d5759b..84814ff 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go
@@ -44,60 +44,32 @@ import (
 
 //we need a type alias to define functions on
 
-type TODeliveryServiceV13 struct {
+type TODeliveryService struct {
 	api.APIInfoImpl
-	tc.DeliveryServiceNullableV13
+	tc.DeliveryServiceNullable
 }
 
-func (ds *TODeliveryServiceV13) V12() *TODeliveryServiceV12 {
-	v12 := &TODeliveryServiceV12{}
-	v12.DeliveryServiceNullableV12 = ds.DeliveryServiceNullableV12
-	v12.SetInfo(ds.ReqInfo)
-	return v12
+func (ds TODeliveryService) MarshalJSON() ([]byte, error) {
+	return json.Marshal(ds.DeliveryServiceNullable)
 }
 
-func (ds TODeliveryServiceV13) MarshalJSON() ([]byte, error) {
-	return json.Marshal(ds.DeliveryServiceNullableV13)
-}
-func (ds *TODeliveryServiceV13) UnmarshalJSON(data []byte) error {
-	return json.Unmarshal(data, ds.DeliveryServiceNullableV13)
-}
-
-func (ds *TODeliveryServiceV13) APIInfo() *api.APIInfo { return ds.ReqInfo }
-
-func (ds TODeliveryServiceV13) GetKeyFieldsInfo() []api.KeyFieldInfo {
-	return ds.V12().GetKeyFieldsInfo()
+func (ds *TODeliveryService) UnmarshalJSON(data []byte) error {
+	return json.Unmarshal(data, ds.DeliveryServiceNullable)
 }
 
-//Implementation of the Identifier, Validator interface functions
-func (ds TODeliveryServiceV13) GetKeys() (map[string]interface{}, bool) {
-	return ds.V12().GetKeys()
-}
+func (ds *TODeliveryService) APIInfo() *api.APIInfo { return ds.ReqInfo }
 
-func (ds *TODeliveryServiceV13) SetKeys(keys map[string]interface{}) {
+func (ds *TODeliveryService) 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 *TODeliveryServiceV13) GetAuditName() string {
-	return ds.V12().GetAuditName()
-}
-
-func (ds *TODeliveryServiceV13) GetType() string {
-	return ds.V12().GetType()
-}
-
-func (ds *TODeliveryServiceV13) Validate() error {
-	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
-func (ds *TODeliveryServiceV13) Create() (error, error, int) {
-	return nil, nil, http.StatusNotImplemented
+func (ds *TODeliveryService) 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 CreateV13(w http.ResponseWriter, r *http.Request) {
+func Create(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)
@@ -105,7 +77,7 @@ func CreateV13(w http.ResponseWriter, r *http.Request) {
 	}
 	defer inf.Close()
 
-	ds := tc.DeliveryServiceNullableV13{}
+	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
@@ -118,18 +90,21 @@ 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
 	}
-	dsv14 := tc.NewDeliveryServiceNullableFromV13(ds)
-	dsv14, errCode, userErr, sysErr = create(inf.Tx.Tx, *inf.Config, inf.User, dsv14)
+	ds, errCode, userErr, sysErr = create(inf, 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.DeliveryServiceNullableV13{dsv14.DeliveryServiceNullableV13})
+	api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Deliveryservice creation was successful.", []tc.DeliveryServiceNullable{ds})
 }
 
 // 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.
-func create(tx *sql.Tx, cfg config.Config, user *auth.CurrentUser, ds tc.DeliveryServiceNullable) (tc.DeliveryServiceNullable, int, error, error) {
-	if authorized, err := isTenantAuthorized(user, tx, &ds.DeliveryServiceNullableV12); err != nil {
+func create(inf *api.APIInfo, ds tc.DeliveryServiceNullable) (tc.DeliveryServiceNullable, int, error, error) {
+	user := inf.User
+	tx := inf.Tx.Tx
+	cfg := inf.Config
+
+	if authorized, err := isTenantAuthorized(inf, &ds); err != nil {
 		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("checking tenant: " + err.Error())
 	} else if !authorized {
 		return tc.DeliveryServiceNullable{}, http.StatusForbidden, errors.New("not authorized on this tenant"), nil
@@ -202,7 +177,7 @@ func create(tx *sql.Tx, cfg config.Config, user *auth.CurrentUser, ds tc.Deliver
 	}
 
 	if dnssecEnabled {
-		if err := PutDNSSecKeys(tx, &cfg, *ds.XMLID, cdnName, ds.ExampleURLs); err != nil {
+		if err := PutDNSSecKeys(tx, cfg, *ds.XMLID, cdnName, ds.ExampleURLs); err != nil {
 			return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("creating DNSSEC keys: " + err.Error())
 		}
 	}
@@ -218,7 +193,7 @@ func create(tx *sql.Tx, cfg config.Config, user *auth.CurrentUser, ds tc.Deliver
 	return ds, http.StatusOK, nil, nil
 }
 
-func (ds *TODeliveryServiceV13) Read() ([]interface{}, error, error, int) {
+func (ds *TODeliveryService) Read() ([]interface{}, error, error, int) {
 	returnable := []interface{}{}
 	dses, errs, _ := readGetDeliveryServices(ds.APIInfo().Params, ds.APIInfo().Tx, ds.APIInfo().User)
 	if len(errs) > 0 {
@@ -231,134 +206,12 @@ func (ds *TODeliveryServiceV13) Read() ([]interface{}, error, error, int) {
 	}
 
 	for _, ds := range dses {
-		returnable = append(returnable, ds.DeliveryServiceNullableV13)
+		returnable = append(returnable, ds)
 	}
 	return returnable, nil, nil, http.StatusOK
 }
 
-func createDefaultRegex(tx *sql.Tx, dsID int, xmlID string) error {
-	regexStr := `.*\.` + xmlID + `\..*`
-	regexID := 0
-	if err := tx.QueryRow(`INSERT INTO regex (type, pattern) VALUES ((select id from type where name = 'HOST_REGEXP'), $1::text) RETURNING id`, regexStr).Scan(&regexID); err != nil {
-		return errors.New("insert regex: " + err.Error())
-	}
-	if _, err := tx.Exec(`INSERT INTO deliveryservice_regex (deliveryservice, regex, set_number) VALUES ($1::bigint, $2::bigint, 0)`, dsID, regexID); err != nil {
-		return errors.New("executing parameter query to insert location: " + err.Error())
-	}
-	return nil
-}
-
-func createPrimaryOrigin(tx *sql.Tx, user *auth.CurrentUser, ds tc.DeliveryServiceNullable) error {
-	if ds.OrgServerFQDN == nil {
-		return nil
-	}
-
-	protocol, fqdn, port, err := tc.ParseOrgServerFQDN(*ds.OrgServerFQDN)
-	if err != nil {
-		return fmt.Errorf("creating primary origin: %v", err)
-	}
-
-	originID := 0
-	q := `INSERT INTO origin (name, fqdn, protocol, is_primary, port, deliveryservice, tenant) VALUES ($1, $2, $3, TRUE, $4, $5, $6) RETURNING id`
-	if err := tx.QueryRow(q, ds.XMLID, fqdn, protocol, port, ds.ID, ds.TenantID).Scan(&originID); err != nil {
-		return fmt.Errorf("insert origin from '%s': %s", *ds.OrgServerFQDN, err.Error())
-	}
-
-	api.CreateChangeLogRawTx(api.ApiChange, "Created primary origin id: "+strconv.Itoa(originID)+" for delivery service: "+*ds.XMLID, user, tx)
-
-	return nil
-}
-
-func updatePrimaryOrigin(tx *sql.Tx, user *auth.CurrentUser, ds tc.DeliveryServiceNullable) error {
-	count := 0
-	q := `SELECT count(*) FROM origin WHERE deliveryservice = $1 AND is_primary`
-	if err := tx.QueryRow(q, *ds.ID).Scan(&count); err != nil {
-		return fmt.Errorf("querying existing primary origin for ds %s: %s", *ds.XMLID, err.Error())
-	}
-
-	if ds.OrgServerFQDN == nil || *ds.OrgServerFQDN == "" {
-		if count == 1 {
-			// the update is removing the existing orgServerFQDN, so the existing row needs to be deleted
-			q = `DELETE FROM origin WHERE deliveryservice = $1 AND is_primary`
-			if _, err := tx.Exec(q, *ds.ID); err != nil {
-				return fmt.Errorf("deleting primary origin for ds %s: %s", *ds.XMLID, err.Error())
-			}
-			api.CreateChangeLogRawTx(api.ApiChange, "Deleted primary origin for delivery service: "+*ds.XMLID, user, tx)
-		}
-		return nil
-	}
-
-	if count == 0 {
-		// orgServerFQDN is going from null to not null, so the primary origin needs to be created
-		return createPrimaryOrigin(tx, user, ds)
-	}
-
-	protocol, fqdn, port, err := tc.ParseOrgServerFQDN(*ds.OrgServerFQDN)
-	if err != nil {
-		return fmt.Errorf("updating primary origin: %v", err)
-	}
-
-	name := ""
-	q = `UPDATE origin SET protocol = $1, fqdn = $2, port = $3 WHERE is_primary AND deliveryservice = $4 RETURNING name`
-	if err := tx.QueryRow(q, protocol, fqdn, port, *ds.ID).Scan(&name); err != nil {
-		return fmt.Errorf("update primary origin for ds %s from '%s': %s", *ds.XMLID, *ds.OrgServerFQDN, err.Error())
-	}
-
-	api.CreateChangeLogRawTx(api.ApiChange, "Updated primary origin: "+name+" for delivery service: "+*ds.XMLID, user, tx)
-
-	return nil
-}
-
-func getOldHostName(id int, tx *sql.Tx) (string, error) {
-	q := `
-SELECT ds.xml_id, ds.protocol, type.name, ds.routing_name, cdn.domain_name
-FROM  deliveryservice as ds
-JOIN type ON ds.type = type.id
-JOIN cdn ON ds.cdn_id = cdn.id
-WHERE ds.id=$1
-`
-	xmlID := ""
-	protocol := (*int)(nil)
-	dsTypeStr := ""
-	routingName := ""
-	cdnDomain := ""
-	if err := tx.QueryRow(q, id).Scan(&xmlID, &protocol, &dsTypeStr, &routingName, &cdnDomain); err != nil {
-		return "", fmt.Errorf("querying delivery service %v host name: "+err.Error()+"\n", id)
-	}
-	dsType := tc.DSTypeFromString(dsTypeStr)
-	if dsType == tc.DSTypeInvalid {
-		return "", errors.New("getting delivery services matchlist: got invalid delivery service type '" + dsTypeStr + "'")
-	}
-	matchLists, err := GetDeliveryServicesMatchLists([]string{xmlID}, tx)
-	if err != nil {
-		return "", errors.New("getting delivery services matchlist: " + err.Error())
-	}
-	matchList, ok := matchLists[xmlID]
-	if !ok {
-		return "", errors.New("delivery service has no match lists (is your delivery service missing regexes?)")
-	}
-	host, err := getHostName(protocol, dsType, routingName, matchList, cdnDomain) // protocol defaults to 0: doesn't need to check Valid()
-	if err != nil {
-		return "", errors.New("getting hostname: " + err.Error())
-	}
-	return host, nil
-}
-
-func getTypeFromID(id int, tx *sql.Tx) (tc.DSType, error) {
-	// TODO combine with getOldHostName, to only make one query?
-	name := ""
-	if err := tx.QueryRow(`SELECT name FROM type WHERE id = $1`, id).Scan(&name); err != nil {
-		return "", fmt.Errorf("querying type ID %v: "+err.Error()+"\n", id)
-	}
-	return tc.DSTypeFromString(name), nil
-}
-
-// Update is unimplemented, needed to satisfy CRUDer, since the framework doesn't allow an update to return an array of one
-func (ds *TODeliveryServiceV13) Update() (error, error, int) {
-	return nil, nil, http.StatusNotImplemented
-}
-
-func UpdateV13(w http.ResponseWriter, r *http.Request) {
+func Update(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)
@@ -380,7 +233,7 @@ func UpdateV13(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	ds, errCode, userErr, sysErr = update(inf.Tx.Tx, *inf.Config, inf.User, &ds)
+	ds, errCode, userErr, sysErr = update(inf, &ds)
 	if userErr != nil || sysErr != nil {
 		api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
 		return
@@ -388,19 +241,24 @@ func UpdateV13(w http.ResponseWriter, r *http.Request) {
 	api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Deliveryservice update was successful.", []tc.DeliveryServiceNullable{ds})
 }
 
-func getDSType(tx *sql.Tx, xmlid string) (tc.DSType, bool, error) {
-	name := ""
-	if err := tx.QueryRow(`SELECT name FROM type WHERE id = (select type from deliveryservice where xml_id = $1)`, xmlid).Scan(&name); err != nil {
-		if err == sql.ErrNoRows {
-			return "", false, nil
-		}
-		return "", false, fmt.Errorf("querying deliveryservice type name: " + err.Error())
+func createDefaultRegex(tx *sql.Tx, dsID int, xmlID string) error {
+	regexStr := `.*\.` + xmlID + `\..*`
+	regexID := 0
+	if err := tx.QueryRow(`INSERT INTO regex (type, pattern) VALUES ((select id from type where name = 'HOST_REGEXP'), $1::text) RETURNING id`, regexStr).Scan(&regexID); err != nil {
+		return errors.New("insert regex: " + err.Error())
 	}
-	return tc.DSTypeFromString(name), true, nil
+	if _, err := tx.Exec(`INSERT INTO deliveryservice_regex (deliveryservice, regex, set_number) VALUES ($1::bigint, $2::bigint, 0)`, dsID, regexID); err != nil {
+		return errors.New("executing parameter query to insert location: " + err.Error())
+	}
+	return nil
 }
 
-func update(tx *sql.Tx, cfg config.Config, user *auth.CurrentUser, ds *tc.DeliveryServiceNullable) (tc.DeliveryServiceNullable, int, error, error) {
-	if authorized, err := isTenantAuthorized(user, tx, &ds.DeliveryServiceNullableV12); err != nil {
+func update(inf *api.APIInfo, ds *tc.DeliveryServiceNullable) (tc.DeliveryServiceNullable, int, error, error) {
+	tx := inf.Tx.Tx
+	cfg := inf.Config
+	user := inf.User
+
+	if authorized, err := isTenantAuthorized(inf, ds); err != nil {
 		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("checking tenant: " + err.Error())
 	} else if !authorized {
 		return tc.DeliveryServiceNullable{}, http.StatusForbidden, errors.New("not authorized on this tenant"), nil
@@ -522,17 +380,6 @@ func update(tx *sql.Tx, cfg config.Config, user *auth.CurrentUser, ds *tc.Delive
 	return *ds, http.StatusOK, nil, nil
 }
 
-// Delete is the DeliveryService implementation of the Deleter interface
-//all implementations of Deleter should use transactions and return the proper errorType
-func (ds *TODeliveryServiceV13) Delete() (error, error, int) {
-	return ds.V12().Delete()
-}
-
-// IsTenantAuthorized implements the Tenantable interface to ensure the user is authorized on the deliveryservice tenant
-func (ds *TODeliveryServiceV13) IsTenantAuthorized(user *auth.CurrentUser) (bool, error) {
-	return ds.V12().IsTenantAuthorized(user)
-}
-
 func readGetDeliveryServices(params map[string]string, tx *sqlx.Tx, user *auth.CurrentUser) ([]tc.DeliveryServiceNullable, []error, tc.ApiErrorType) {
 	if strings.HasSuffix(params["id"], ".json") {
 		params["id"] = params["id"][:len(params["id"])-len(".json")]
@@ -561,11 +408,14 @@ func readGetDeliveryServices(params map[string]string, tx *sqlx.Tx, user *auth.C
 	}
 
 	tenantIDs, err := tenant.GetUserTenantIDListTx(tx.Tx, user.TenantID)
+
 	if err != nil {
 		log.Errorln("received error querying for user's tenants: " + err.Error())
 		return nil, []error{tc.DBError}, tc.SystemError
 	}
+
 	where, queryValues = dbhelpers.AddTenancyCheck(where, queryValues, "ds.tenant_id", tenantIDs)
+
 	query := selectQuery() + where + orderBy
 
 	log.Debugln("generated deliveryServices query: " + query)
@@ -574,6 +424,122 @@ func readGetDeliveryServices(params map[string]string, tx *sqlx.Tx, user *auth.C
 	return GetDeliveryServices(query, queryValues, tx)
 }
 
+func getOldHostName(id int, tx *sql.Tx) (string, error) {
+	q := `
+SELECT ds.xml_id, ds.protocol, type.name, ds.routing_name, cdn.domain_name
+FROM  deliveryservice as ds
+JOIN type ON ds.type = type.id
+JOIN cdn ON ds.cdn_id = cdn.id
+WHERE ds.id=$1
+`
+	xmlID := ""
+	protocol := (*int)(nil)
+	dsTypeStr := ""
+	routingName := ""
+	cdnDomain := ""
+	if err := tx.QueryRow(q, id).Scan(&xmlID, &protocol, &dsTypeStr, &routingName, &cdnDomain); err != nil {
+		return "", fmt.Errorf("querying delivery service %v host name: "+err.Error()+"\n", id)
+	}
+	dsType := tc.DSTypeFromString(dsTypeStr)
+	if dsType == tc.DSTypeInvalid {
+		return "", errors.New("getting delivery services matchlist: got invalid delivery service type '" + dsTypeStr + "'")
+	}
+	matchLists, err := GetDeliveryServicesMatchLists([]string{xmlID}, tx)
+	if err != nil {
+		return "", errors.New("getting delivery services matchlist: " + err.Error())
+	}
+	matchList, ok := matchLists[xmlID]
+	if !ok {
+		return "", errors.New("delivery service has no match lists (is your delivery service missing regexes?)")
+	}
+	host, err := getHostName(protocol, dsType, routingName, matchList, cdnDomain) // protocol defaults to 0: doesn't need to check Valid()
+	if err != nil {
+		return "", errors.New("getting hostname: " + err.Error())
+	}
+	return host, nil
+}
+
+func getTypeFromID(id int, tx *sql.Tx) (tc.DSType, error) {
+	// TODO combine with getOldHostName, to only make one query?
+	name := ""
+	if err := tx.QueryRow(`SELECT name FROM type WHERE id = $1`, id).Scan(&name); err != nil {
+		return "", fmt.Errorf("querying type ID %v: "+err.Error()+"\n", id)
+	}
+	return tc.DSTypeFromString(name), nil
+}
+
+func updatePrimaryOrigin(tx *sql.Tx, user *auth.CurrentUser, ds tc.DeliveryServiceNullable) error {
+	count := 0
+	q := `SELECT count(*) FROM origin WHERE deliveryservice = $1 AND is_primary`
+	if err := tx.QueryRow(q, *ds.ID).Scan(&count); err != nil {
+		return fmt.Errorf("querying existing primary origin for ds %s: %s", *ds.XMLID, err.Error())
+	}
+
+	if ds.OrgServerFQDN == nil || *ds.OrgServerFQDN == "" {
+		if count == 1 {
+			// the update is removing the existing orgServerFQDN, so the existing row needs to be deleted
+			q = `DELETE FROM origin WHERE deliveryservice = $1 AND is_primary`
+			if _, err := tx.Exec(q, *ds.ID); err != nil {
+				return fmt.Errorf("deleting primary origin for ds %s: %s", *ds.XMLID, err.Error())
+			}
+			api.CreateChangeLogRawTx(api.ApiChange, "Deleted primary origin for delivery service: "+*ds.XMLID, user, tx)
+		}
+		return nil
+	}
+
+	if count == 0 {
+		// orgServerFQDN is going from null to not null, so the primary origin needs to be created
+		return createPrimaryOrigin(tx, user, ds)
+	}
+
+	protocol, fqdn, port, err := tc.ParseOrgServerFQDN(*ds.OrgServerFQDN)
+	if err != nil {
+		return fmt.Errorf("updating primary origin: %v", err)
+	}
+
+	name := ""
+	q = `UPDATE origin SET protocol = $1, fqdn = $2, port = $3 WHERE is_primary AND deliveryservice = $4 RETURNING name`
+	if err := tx.QueryRow(q, protocol, fqdn, port, *ds.ID).Scan(&name); err != nil {
+		return fmt.Errorf("update primary origin for ds %s from '%s': %s", *ds.XMLID, *ds.OrgServerFQDN, err.Error())
+	}
+
+	api.CreateChangeLogRawTx(api.ApiChange, "Updated primary origin: "+name+" for delivery service: "+*ds.XMLID, user, tx)
+
+	return nil
+}
+
+func createPrimaryOrigin(tx *sql.Tx, user *auth.CurrentUser, ds tc.DeliveryServiceNullable) error {
+	if ds.OrgServerFQDN == nil {
+		return nil
+	}
+
+	protocol, fqdn, port, err := tc.ParseOrgServerFQDN(*ds.OrgServerFQDN)
+	if err != nil {
+		return fmt.Errorf("creating primary origin: %v", err)
+	}
+
+	originID := 0
+	q := `INSERT INTO origin (name, fqdn, protocol, is_primary, port, deliveryservice, tenant) VALUES ($1, $2, $3, TRUE, $4, $5, $6) RETURNING id`
+	if err := tx.QueryRow(q, ds.XMLID, fqdn, protocol, port, ds.ID, ds.TenantID).Scan(&originID); err != nil {
+		return fmt.Errorf("insert origin from '%s': %s", *ds.OrgServerFQDN, err.Error())
+	}
+
+	api.CreateChangeLogRawTx(api.ApiChange, "Created primary origin id: "+strconv.Itoa(originID)+" for delivery service: "+*ds.XMLID, user, tx)
+
+	return nil
+}
+
+func getDSType(tx *sql.Tx, xmlid string) (tc.DSType, bool, error) {
+	name := ""
+	if err := tx.QueryRow(`SELECT name FROM type WHERE id = (select type from deliveryservice where xml_id = $1)`, xmlid).Scan(&name); err != nil {
+		if err == sql.ErrNoRows {
+			return "", false, nil
+		}
+		return "", false, fmt.Errorf("querying deliveryservice type name: " + err.Error())
+	}
+	return tc.DSTypeFromString(name), true, nil
+}
+
 func GetDeliveryServices(query string, queryValues map[string]interface{}, tx *sqlx.Tx) ([]tc.DeliveryServiceNullable, []error, tc.ApiErrorType) {
 	rows, err := tx.NamedQuery(query, queryValues)
 	if err != nil {
@@ -620,7 +586,7 @@ func GetDeliveryServices(query string, queryValues map[string]interface{}, tx *s
 	return dses, nil, tc.NoError
 }
 
-func updateSSLKeys(ds *tc.DeliveryServiceNullable, hostName string, tx *sql.Tx, cfg config.Config) error {
+func updateSSLKeys(ds *tc.DeliveryServiceNullable, hostName string, tx *sql.Tx, cfg *config.Config) error {
 	if ds.XMLID == nil {
 		return errors.New("delivery services has no XMLID!")
 	}
@@ -938,6 +904,123 @@ func GetDSSelectQuery() string {
 	return selectQuery()
 }
 
+// getTenantID returns the tenant Id of the given delivery service. Note it may return a nil id and nil error, if the tenant ID in the database is nil.
+func getTenantID(tx *sql.Tx, ds *tc.DeliveryServiceNullable) (*int, error) {
+	if ds.ID == nil && ds.XMLID == nil {
+		return nil, errors.New("delivery service has no ID or XMLID")
+	}
+	if ds.ID != nil {
+		existingID, _, err := getDSTenantIDByID(tx, *ds.ID) // ignore exists return - if the DS is new, we only need to check the user input tenant
+		return existingID, err
+	}
+	existingID, _, err := getDSTenantIDByName(tx, *ds.XMLID) // ignore exists return - if the DS is new, we only need to check the user input tenant
+	return existingID, err
+}
+
+func isTenantAuthorized(inf *api.APIInfo, ds *tc.DeliveryServiceNullable) (bool, error) {
+	tx := inf.Tx.Tx
+	user := inf.User
+
+	existingID, err := getTenantID(inf.Tx.Tx, ds)
+	if err != nil {
+		return false, errors.New("getting tenant ID: " + err.Error())
+	}
+	if ds.TenantID == nil {
+		ds.TenantID = existingID
+	}
+	if existingID != nil && existingID != ds.TenantID {
+		userAuthorizedForExistingDSTenant, err := tenant.IsResourceAuthorizedToUserTx(*existingID, user, tx)
+		if err != nil {
+			return false, errors.New("checking authorization for existing DS ID: " + err.Error())
+		}
+		if !userAuthorizedForExistingDSTenant {
+			return false, nil
+		}
+	}
+	if ds.TenantID != nil {
+		userAuthorizedForNewDSTenant, err := tenant.IsResourceAuthorizedToUserTx(*ds.TenantID, user, tx)
+		if err != nil {
+			return false, errors.New("checking authorization for new DS ID: " + err.Error())
+		}
+		if !userAuthorizedForNewDSTenant {
+			return false, nil
+		}
+	}
+	return true, nil
+}
+
+// getDSTenantIDByID returns the tenant ID, whether the delivery service exists, and any error.
+func getDSTenantIDByID(tx *sql.Tx, id int) (*int, bool, error) {
+	tenantID := (*int)(nil)
+	if err := tx.QueryRow(`SELECT tenant_id FROM deliveryservice where id = $1`, id).Scan(&tenantID); err != nil {
+		if err == sql.ErrNoRows {
+			return nil, false, nil
+		}
+		return nil, false, fmt.Errorf("querying tenant ID for delivery service ID '%v': %v", id, err)
+	}
+	return tenantID, true, nil
+}
+
+// GetDSTenantIDByIDTx returns the tenant ID, whether the delivery service exists, and any error.
+func GetDSTenantIDByIDTx(tx *sql.Tx, id int) (*int, bool, error) {
+	tenantID := (*int)(nil)
+	if err := tx.QueryRow(`SELECT tenant_id FROM deliveryservice where id = $1`, id).Scan(&tenantID); err != nil {
+		if err == sql.ErrNoRows {
+			return nil, false, nil
+		}
+		return nil, false, fmt.Errorf("querying tenant ID for delivery service ID '%v': %v", id, err)
+	}
+	return tenantID, true, nil
+}
+
+// getDSTenantIDByName returns the tenant ID, whether the delivery service exists, and any error.
+func getDSTenantIDByName(tx *sql.Tx, name string) (*int, bool, error) {
+	tenantID := (*int)(nil)
+	if err := tx.QueryRow(`SELECT tenant_id FROM deliveryservice where xml_id = $1`, name).Scan(&tenantID); err != nil {
+		if err == sql.ErrNoRows {
+			return nil, false, nil
+		}
+		return nil, false, fmt.Errorf("querying tenant ID for delivery service name '%v': %v", name, err)
+	}
+	return tenantID, true, nil
+}
+
+// GetDSTenantIDByNameTx returns the tenant ID, whether the delivery service exists, and any error.
+func GetDSTenantIDByNameTx(tx *sql.Tx, ds tc.DeliveryServiceName) (*int, bool, error) {
+	tenantID := (*int)(nil)
+	if err := tx.QueryRow(`SELECT tenant_id FROM deliveryservice where xml_id = $1`, ds).Scan(&tenantID); err != nil {
+		if err == sql.ErrNoRows {
+			return nil, false, nil
+		}
+		return nil, false, fmt.Errorf("querying tenant ID for delivery service name '%v': %v", ds, err)
+	}
+	return tenantID, true, nil
+}
+
+// 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
+}
+
+// GetXMLID loads the DeliveryService's xml_id from the database, from the ID. Returns whether the delivery service was found, and any error.
+func GetXMLID(tx *sql.Tx, id int) (string, bool, error) {
+	xmlID := ""
+	if err := tx.QueryRow(`SELECT xml_id FROM deliveryservice where id = $1`, id).Scan(&xmlID); err != nil {
+		if err == sql.ErrNoRows {
+			return "", false, nil
+		}
+		return "", false, fmt.Errorf("querying xml_id for delivery service ID '%v': %v", id, err)
+	}
+	return xmlID, true, nil
+}
+
 func selectQuery() string {
 	return `
 SELECT
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go
index b9d03fb..68d9c21 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go
@@ -20,19 +20,15 @@ package deliveryservice
  */
 
 import (
-	"database/sql"
+	"encoding/json"
 	"errors"
-	"fmt"
 	"net/http"
-	"strconv"
 
 	"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"
-	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
 
-	"github.com/jmoiron/sqlx"
 	"github.com/lib/pq"
 )
 
@@ -41,6 +37,14 @@ type TODeliveryServiceV12 struct {
 	tc.DeliveryServiceNullableV12
 }
 
+func (ds TODeliveryServiceV12) MarshalJSON() ([]byte, error) {
+	return json.Marshal(ds.DeliveryServiceNullableV12)
+}
+
+func (ds *TODeliveryServiceV12) UnmarshalJSON(data []byte) error {
+	return json.Unmarshal(data, ds.DeliveryServiceNullableV12)
+}
+
 func (v *TODeliveryServiceV12) DeleteQuery() string {
 	return `DELETE FROM deliveryservice WHERE id = :id`
 }
@@ -72,132 +76,16 @@ func (ds *TODeliveryServiceV12) GetType() string {
 	return "ds"
 }
 
-// getDSTenantIDByID returns the tenant ID, whether the delivery service exists, and any error.
-func getDSTenantIDByID(tx *sql.Tx, id int) (*int, bool, error) {
-	tenantID := (*int)(nil)
-	if err := tx.QueryRow(`SELECT tenant_id FROM deliveryservice where id = $1`, id).Scan(&tenantID); err != nil {
-		if err == sql.ErrNoRows {
-			return nil, false, nil
-		}
-		return nil, false, fmt.Errorf("querying tenant ID for delivery service ID '%v': %v", id, err)
-	}
-	return tenantID, true, nil
-}
-
-// GetDSTenantIDByIDTx returns the tenant ID, whether the delivery service exists, and any error.
-func GetDSTenantIDByIDTx(tx *sql.Tx, id int) (*int, bool, error) {
-	tenantID := (*int)(nil)
-	if err := tx.QueryRow(`SELECT tenant_id FROM deliveryservice where id = $1`, id).Scan(&tenantID); err != nil {
-		if err == sql.ErrNoRows {
-			return nil, false, nil
-		}
-		return nil, false, fmt.Errorf("querying tenant ID for delivery service ID '%v': %v", id, err)
-	}
-	return tenantID, true, nil
-}
-
-// getDSTenantIDByName returns the tenant ID, whether the delivery service exists, and any error.
-func getDSTenantIDByName(tx *sql.Tx, name string) (*int, bool, error) {
-	tenantID := (*int)(nil)
-	if err := tx.QueryRow(`SELECT tenant_id FROM deliveryservice where xml_id = $1`, name).Scan(&tenantID); err != nil {
-		if err == sql.ErrNoRows {
-			return nil, false, nil
-		}
-		return nil, false, fmt.Errorf("querying tenant ID for delivery service name '%v': %v", name, err)
-	}
-	return tenantID, true, nil
-}
-
-// GetDSTenantIDByNameTx returns the tenant ID, whether the delivery service exists, and any error.
-func GetDSTenantIDByNameTx(tx *sql.Tx, ds tc.DeliveryServiceName) (*int, bool, error) {
-	tenantID := (*int)(nil)
-	if err := tx.QueryRow(`SELECT tenant_id FROM deliveryservice where xml_id = $1`, ds).Scan(&tenantID); err != nil {
-		if err == sql.ErrNoRows {
-			return nil, false, nil
-		}
-		return nil, false, fmt.Errorf("querying tenant ID for delivery service name '%v': %v", ds, err)
-	}
-	return tenantID, true, nil
-}
-
-// GetXMLID loads the DeliveryService's xml_id from the database, from the ID. Returns whether the delivery service was found, and any error.
-
-func (ds *TODeliveryServiceV12) GetXMLID(tx *sqlx.Tx) (string, bool, error) {
-	if ds.ID == nil {
-		return "", false, errors.New("missing ID")
-	}
-	return GetXMLID(tx.Tx, *ds.ID)
-}
-
-// GetXMLID loads the DeliveryService's xml_id from the database, from the ID. Returns whether the delivery service was found, and any error.
-func GetXMLID(tx *sql.Tx, id int) (string, bool, error) {
-	xmlID := ""
-	if err := tx.QueryRow(`SELECT xml_id FROM deliveryservice where id = $1`, id).Scan(&xmlID); err != nil {
-		if err == sql.ErrNoRows {
-			return "", false, nil
-		}
-		return "", false, fmt.Errorf("querying xml_id for delivery service ID '%v': %v", id, err)
-	}
-	return xmlID, true, nil
-}
-
 // IsTenantAuthorized checks that the user is authorized for both the delivery service's existing tenant, and the new tenant they're changing it to (if different).
-
 func (ds *TODeliveryServiceV12) IsTenantAuthorized(user *auth.CurrentUser) (bool, error) {
-	return isTenantAuthorized(user, ds.ReqInfo.Tx.Tx, &ds.DeliveryServiceNullableV12)
-}
-
-// getTenantID returns the tenant Id of the given delivery service. Note it may return a nil id and nil error, if the tenant ID in the database is nil.
-func getTenantID(tx *sql.Tx, ds *tc.DeliveryServiceNullableV12) (*int, error) {
-	if ds.ID == nil && ds.XMLID == nil {
-		return nil, errors.New("delivery service has no ID or XMLID")
-	}
-	if ds.ID != nil {
-		existingID, _, err := getDSTenantIDByID(tx, *ds.ID) // ignore exists return - if the DS is new, we only need to check the user input tenant
-		return existingID, err
-	}
-	existingID, _, err := getDSTenantIDByName(tx, *ds.XMLID) // ignore exists return - if the DS is new, we only need to check the user input tenant
-	return existingID, err
-}
-
-func isTenantAuthorized(user *auth.CurrentUser, tx *sql.Tx, ds *tc.DeliveryServiceNullableV12) (bool, error) {
-	existingID, err := getTenantID(tx, ds)
-	if err != nil {
-		return false, errors.New("getting tenant ID: " + err.Error())
-	}
-	if ds.TenantID == nil {
-		ds.TenantID = existingID
-	}
-	if existingID != nil && existingID != ds.TenantID {
-		userAuthorizedForExistingDSTenant, err := tenant.IsResourceAuthorizedToUserTx(*existingID, user, tx)
-		if err != nil {
-			return false, errors.New("checking authorization for existing DS ID: " + err.Error())
-		}
-		if !userAuthorizedForExistingDSTenant {
-			return false, nil
-		}
-	}
-	if ds.TenantID != nil {
-		userAuthorizedForNewDSTenant, err := tenant.IsResourceAuthorizedToUserTx(*ds.TenantID, user, tx)
-		if err != nil {
-			return false, errors.New("checking authorization for new DS ID: " + err.Error())
-		}
-		if !userAuthorizedForNewDSTenant {
-			return false, nil
-		}
-	}
-	return true, nil
+	tcDS := tc.NewDeliveryServiceNullableFromV12(ds.DeliveryServiceNullableV12)
+	return isTenantAuthorized(ds.ReqInfo, &tcDS)
 }
 
 func (ds *TODeliveryServiceV12) Validate() error {
 	return ds.DeliveryServiceNullableV12.Validate(ds.ReqInfo.Tx.Tx)
 }
 
-// Create is unimplemented, needed to satisfy CRUDer, since the framework doesn't allow a create to return an array of one
-func (ds *TODeliveryServiceV12) Create() (error, error, int) {
-	return nil, nil, http.StatusNotImplemented
-}
-
 func CreateV12(w http.ResponseWriter, r *http.Request) {
 	inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
 	if userErr != nil || sysErr != nil {
@@ -210,13 +98,13 @@ func CreateV12(w http.ResponseWriter, r *http.Request) {
 		api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("decoding: "+err.Error()), nil)
 		return
 	}
-	dsv13 := tc.NewDeliveryServiceNullableFromV12(ds)
-	dsv13, errCode, userErr, sysErr = create(inf.Tx.Tx, *inf.Config, inf.User, dsv13)
+	tcDS := tc.NewDeliveryServiceNullableFromV12(ds)
+	tcDS, errCode, userErr, sysErr = create(inf, tcDS)
 	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.DeliveryServiceNullableV12{dsv13.DeliveryServiceNullableV12})
+	api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Deliveryservice creation was successful.", []tc.DeliveryServiceNullableV12{tcDS.DeliveryServiceNullableV12})
 }
 
 func (ds *TODeliveryServiceV12) Read() ([]interface{}, error, error, int) {
@@ -237,13 +125,36 @@ func (ds *TODeliveryServiceV12) Read() ([]interface{}, error, error, int) {
 	return returnable, nil, nil, http.StatusOK
 }
 
-//Delete is the DeliveryService implementation of the Deleter interface
-//all implementations of Deleter should use transactions and return the proper errorType
+func UpdateV12(w http.ResponseWriter, r *http.Request) {
+	inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"id"}, []string{"id"})
+	if userErr != nil || sysErr != nil {
+		api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+		return
+	}
+	defer inf.Close()
+
+	ds := tc.DeliveryServiceNullableV12{}
+	ds.ID = util.IntPtr(inf.IntParams["id"])
+	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
+	}
+	tcDS := tc.NewDeliveryServiceNullableFromV12(ds)
+	tcDS, errCode, userErr, sysErr = update(inf, &tcDS)
+	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.DeliveryServiceNullableV12{tcDS.DeliveryServiceNullableV12})
+}
+
+//Delete is the DeliveryService implementation of the Deleter interface.
 func (ds *TODeliveryServiceV12) Delete() (error, error, int) {
 	if ds.ID == nil {
 		return errors.New("missing id"), nil, http.StatusBadRequest
 	}
-	xmlID, ok, err := ds.GetXMLID(ds.ReqInfo.Tx)
+
+	xmlID, ok, err := GetXMLID(ds.ReqInfo.Tx.Tx, *ds.ID)
 	if err != nil {
 		return nil, errors.New("dsv12 delete: getting xmlid: " + err.Error()), http.StatusInternalServerError
 	} else if !ok {
@@ -278,43 +189,3 @@ 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.
-func (ds *TODeliveryServiceV12) Update() (error, error, int) {
-	return nil, nil, http.StatusNotImplemented
-}
-
-func UpdateV12(w http.ResponseWriter, r *http.Request) {
-	inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"id"}, []string{"id"})
-	if userErr != nil || sysErr != nil {
-		api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
-		return
-	}
-	defer inf.Close()
-
-	ds := tc.DeliveryServiceNullableV12{}
-	ds.ID = util.IntPtr(inf.IntParams["id"])
-	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
-	}
-	dsv13 := tc.NewDeliveryServiceNullableFromV12(ds)
-	dsv13, errCode, userErr, sysErr = update(inf.Tx.Tx, *inf.Config, inf.User, &dsv13)
-	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.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 9d5759b..6497b60 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go
@@ -20,26 +20,13 @@ package deliveryservice
  */
 
 import (
-	"database/sql"
 	"encoding/json"
 	"errors"
-	"fmt"
 	"net/http"
-	"strconv"
-	"strings"
 
-	"github.com/apache/trafficcontrol/lib/go-log"
 	"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"
-	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/config"
-	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
-	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/riaksvc"
-	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
-
-	"github.com/jmoiron/sqlx"
-	"github.com/lib/pq"
 )
 
 //we need a type alias to define functions on
@@ -49,53 +36,25 @@ type TODeliveryServiceV13 struct {
 	tc.DeliveryServiceNullableV13
 }
 
-func (ds *TODeliveryServiceV13) V12() *TODeliveryServiceV12 {
-	v12 := &TODeliveryServiceV12{}
-	v12.DeliveryServiceNullableV12 = ds.DeliveryServiceNullableV12
-	v12.SetInfo(ds.ReqInfo)
-	return v12
-}
-
 func (ds TODeliveryServiceV13) MarshalJSON() ([]byte, error) {
 	return json.Marshal(ds.DeliveryServiceNullableV13)
 }
+
 func (ds *TODeliveryServiceV13) UnmarshalJSON(data []byte) error {
 	return json.Unmarshal(data, ds.DeliveryServiceNullableV13)
 }
 
 func (ds *TODeliveryServiceV13) APIInfo() *api.APIInfo { return ds.ReqInfo }
 
-func (ds TODeliveryServiceV13) GetKeyFieldsInfo() []api.KeyFieldInfo {
-	return ds.V12().GetKeyFieldsInfo()
-}
-
-//Implementation of the Identifier, Validator interface functions
-func (ds TODeliveryServiceV13) GetKeys() (map[string]interface{}, bool) {
-	return ds.V12().GetKeys()
-}
-
 func (ds *TODeliveryServiceV13) 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 *TODeliveryServiceV13) GetAuditName() string {
-	return ds.V12().GetAuditName()
-}
-
-func (ds *TODeliveryServiceV13) GetType() string {
-	return ds.V12().GetType()
-}
-
 func (ds *TODeliveryServiceV13) Validate() error {
 	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
-func (ds *TODeliveryServiceV13) Create() (error, error, int) {
-	return nil, nil, http.StatusNotImplemented
-}
-
 // 	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 CreateV13(w http.ResponseWriter, r *http.Request) {
 	inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
@@ -118,104 +77,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
 	}
-	dsv14 := tc.NewDeliveryServiceNullableFromV13(ds)
-	dsv14, errCode, userErr, sysErr = create(inf.Tx.Tx, *inf.Config, inf.User, dsv14)
+	tcDS := tc.NewDeliveryServiceNullableFromV13(ds)
+	tcDS, errCode, userErr, sysErr = create(inf, tcDS)
 	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.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.
-func create(tx *sql.Tx, cfg config.Config, user *auth.CurrentUser, ds tc.DeliveryServiceNullable) (tc.DeliveryServiceNullable, int, error, error) {
-	if authorized, err := isTenantAuthorized(user, tx, &ds.DeliveryServiceNullableV12); err != nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("checking tenant: " + err.Error())
-	} else if !authorized {
-		return tc.DeliveryServiceNullable{}, http.StatusForbidden, errors.New("not authorized on this tenant"), nil
-	}
-
-	// TODO change DeepCachingType to implement sql.Valuer and sql.Scanner, so sqlx struct scan can be used.
-	deepCachingType := tc.DeepCachingType("").String()
-	if ds.DeepCachingType != nil {
-		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 [...]
-	if err != nil {
-		usrErr, sysErr, code := api.ParseDBError(err)
-		return tc.DeliveryServiceNullable{}, code, usrErr, sysErr
-	}
-	defer resultRows.Close()
-
-	id := 0
-	lastUpdated := tc.TimeNoMod{}
-	if !resultRows.Next() {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("no deliveryservice request inserted, no id was returned")
-	}
-	if err := resultRows.Scan(&id, &lastUpdated); err != nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("could not scan id from insert: " + err.Error())
-	}
-	if resultRows.Next() {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("too many ids returned from deliveryservice request insert")
-	}
-	ds.ID = &id
-
-	if ds.ID == nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("missing id after insert")
-	}
-	if ds.XMLID == nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("missing xml_id after insert")
-	}
-	if ds.TypeID == nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("missing type after insert")
-	}
-	dsType, err := getTypeFromID(*ds.TypeID, tx)
-	if err != nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("getting delivery service type: " + err.Error())
-	}
-	ds.Type = &dsType
-
-	if err := createDefaultRegex(tx, *ds.ID, *ds.XMLID); err != nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("creating default regex: " + err.Error())
-	}
-
-	matchlists, err := GetDeliveryServicesMatchLists([]string{*ds.XMLID}, tx)
-	if err != nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("creating DS: reading matchlists: " + err.Error())
-	}
-	if matchlist, ok := matchlists[*ds.XMLID]; !ok {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("creating DS: reading matchlists: not found")
-	} else {
-		ds.MatchList = &matchlist
-	}
-
-	cdnName, cdnDomain, dnssecEnabled, err := getCDNNameDomainDNSSecEnabled(*ds.ID, tx)
-	if err != nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("creating DS: getting CDN info: " + err.Error())
-	}
-
-	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, ds.MaxOriginConnections); err != nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("ensuring ds parameters:: " + err.Error())
-	}
-
-	if dnssecEnabled {
-		if err := PutDNSSecKeys(tx, &cfg, *ds.XMLID, cdnName, ds.ExampleURLs); err != nil {
-			return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("creating DNSSEC keys: " + err.Error())
-		}
-	}
-
-	if err := createPrimaryOrigin(tx, user, ds); err != nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("creating delivery service: " + err.Error())
-	}
-
-	ds.LastUpdated = &lastUpdated
-	if err := api.CreateChangeLogRawErr(api.ApiChange, "Created ds: "+*ds.XMLID+" id: "+strconv.Itoa(*ds.ID), user, tx); err != nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("error writing to audit log: " + err.Error())
-	}
-	return ds, http.StatusOK, nil, nil
+	api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Deliveryservice creation was successful.", []tc.DeliveryServiceNullableV13{tcDS.DeliveryServiceNullableV13})
 }
 
 func (ds *TODeliveryServiceV13) Read() ([]interface{}, error, error, int) {
@@ -236,128 +104,6 @@ func (ds *TODeliveryServiceV13) Read() ([]interface{}, error, error, int) {
 	return returnable, nil, nil, http.StatusOK
 }
 
-func createDefaultRegex(tx *sql.Tx, dsID int, xmlID string) error {
-	regexStr := `.*\.` + xmlID + `\..*`
-	regexID := 0
-	if err := tx.QueryRow(`INSERT INTO regex (type, pattern) VALUES ((select id from type where name = 'HOST_REGEXP'), $1::text) RETURNING id`, regexStr).Scan(&regexID); err != nil {
-		return errors.New("insert regex: " + err.Error())
-	}
-	if _, err := tx.Exec(`INSERT INTO deliveryservice_regex (deliveryservice, regex, set_number) VALUES ($1::bigint, $2::bigint, 0)`, dsID, regexID); err != nil {
-		return errors.New("executing parameter query to insert location: " + err.Error())
-	}
-	return nil
-}
-
-func createPrimaryOrigin(tx *sql.Tx, user *auth.CurrentUser, ds tc.DeliveryServiceNullable) error {
-	if ds.OrgServerFQDN == nil {
-		return nil
-	}
-
-	protocol, fqdn, port, err := tc.ParseOrgServerFQDN(*ds.OrgServerFQDN)
-	if err != nil {
-		return fmt.Errorf("creating primary origin: %v", err)
-	}
-
-	originID := 0
-	q := `INSERT INTO origin (name, fqdn, protocol, is_primary, port, deliveryservice, tenant) VALUES ($1, $2, $3, TRUE, $4, $5, $6) RETURNING id`
-	if err := tx.QueryRow(q, ds.XMLID, fqdn, protocol, port, ds.ID, ds.TenantID).Scan(&originID); err != nil {
-		return fmt.Errorf("insert origin from '%s': %s", *ds.OrgServerFQDN, err.Error())
-	}
-
-	api.CreateChangeLogRawTx(api.ApiChange, "Created primary origin id: "+strconv.Itoa(originID)+" for delivery service: "+*ds.XMLID, user, tx)
-
-	return nil
-}
-
-func updatePrimaryOrigin(tx *sql.Tx, user *auth.CurrentUser, ds tc.DeliveryServiceNullable) error {
-	count := 0
-	q := `SELECT count(*) FROM origin WHERE deliveryservice = $1 AND is_primary`
-	if err := tx.QueryRow(q, *ds.ID).Scan(&count); err != nil {
-		return fmt.Errorf("querying existing primary origin for ds %s: %s", *ds.XMLID, err.Error())
-	}
-
-	if ds.OrgServerFQDN == nil || *ds.OrgServerFQDN == "" {
-		if count == 1 {
-			// the update is removing the existing orgServerFQDN, so the existing row needs to be deleted
-			q = `DELETE FROM origin WHERE deliveryservice = $1 AND is_primary`
-			if _, err := tx.Exec(q, *ds.ID); err != nil {
-				return fmt.Errorf("deleting primary origin for ds %s: %s", *ds.XMLID, err.Error())
-			}
-			api.CreateChangeLogRawTx(api.ApiChange, "Deleted primary origin for delivery service: "+*ds.XMLID, user, tx)
-		}
-		return nil
-	}
-
-	if count == 0 {
-		// orgServerFQDN is going from null to not null, so the primary origin needs to be created
-		return createPrimaryOrigin(tx, user, ds)
-	}
-
-	protocol, fqdn, port, err := tc.ParseOrgServerFQDN(*ds.OrgServerFQDN)
-	if err != nil {
-		return fmt.Errorf("updating primary origin: %v", err)
-	}
-
-	name := ""
-	q = `UPDATE origin SET protocol = $1, fqdn = $2, port = $3 WHERE is_primary AND deliveryservice = $4 RETURNING name`
-	if err := tx.QueryRow(q, protocol, fqdn, port, *ds.ID).Scan(&name); err != nil {
-		return fmt.Errorf("update primary origin for ds %s from '%s': %s", *ds.XMLID, *ds.OrgServerFQDN, err.Error())
-	}
-
-	api.CreateChangeLogRawTx(api.ApiChange, "Updated primary origin: "+name+" for delivery service: "+*ds.XMLID, user, tx)
-
-	return nil
-}
-
-func getOldHostName(id int, tx *sql.Tx) (string, error) {
-	q := `
-SELECT ds.xml_id, ds.protocol, type.name, ds.routing_name, cdn.domain_name
-FROM  deliveryservice as ds
-JOIN type ON ds.type = type.id
-JOIN cdn ON ds.cdn_id = cdn.id
-WHERE ds.id=$1
-`
-	xmlID := ""
-	protocol := (*int)(nil)
-	dsTypeStr := ""
-	routingName := ""
-	cdnDomain := ""
-	if err := tx.QueryRow(q, id).Scan(&xmlID, &protocol, &dsTypeStr, &routingName, &cdnDomain); err != nil {
-		return "", fmt.Errorf("querying delivery service %v host name: "+err.Error()+"\n", id)
-	}
-	dsType := tc.DSTypeFromString(dsTypeStr)
-	if dsType == tc.DSTypeInvalid {
-		return "", errors.New("getting delivery services matchlist: got invalid delivery service type '" + dsTypeStr + "'")
-	}
-	matchLists, err := GetDeliveryServicesMatchLists([]string{xmlID}, tx)
-	if err != nil {
-		return "", errors.New("getting delivery services matchlist: " + err.Error())
-	}
-	matchList, ok := matchLists[xmlID]
-	if !ok {
-		return "", errors.New("delivery service has no match lists (is your delivery service missing regexes?)")
-	}
-	host, err := getHostName(protocol, dsType, routingName, matchList, cdnDomain) // protocol defaults to 0: doesn't need to check Valid()
-	if err != nil {
-		return "", errors.New("getting hostname: " + err.Error())
-	}
-	return host, nil
-}
-
-func getTypeFromID(id int, tx *sql.Tx) (tc.DSType, error) {
-	// TODO combine with getOldHostName, to only make one query?
-	name := ""
-	if err := tx.QueryRow(`SELECT name FROM type WHERE id = $1`, id).Scan(&name); err != nil {
-		return "", fmt.Errorf("querying type ID %v: "+err.Error()+"\n", id)
-	}
-	return tc.DSTypeFromString(name), nil
-}
-
-// Update is unimplemented, needed to satisfy CRUDer, since the framework doesn't allow an update to return an array of one
-func (ds *TODeliveryServiceV13) Update() (error, error, int) {
-	return nil, nil, http.StatusNotImplemented
-}
-
 func UpdateV13(w http.ResponseWriter, r *http.Request) {
 	inf, userErr, sysErr, errCode := api.NewInfo(r, nil, []string{"id"})
 	if userErr != nil || sysErr != nil {
@@ -380,757 +126,10 @@ func UpdateV13(w http.ResponseWriter, r *http.Request) {
 		return
 	}
 
-	ds, errCode, userErr, sysErr = update(inf.Tx.Tx, *inf.Config, inf.User, &ds)
+	ds, errCode, userErr, sysErr = update(inf, &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})
 }
-
-func getDSType(tx *sql.Tx, xmlid string) (tc.DSType, bool, error) {
-	name := ""
-	if err := tx.QueryRow(`SELECT name FROM type WHERE id = (select type from deliveryservice where xml_id = $1)`, xmlid).Scan(&name); err != nil {
-		if err == sql.ErrNoRows {
-			return "", false, nil
-		}
-		return "", false, fmt.Errorf("querying deliveryservice type name: " + err.Error())
-	}
-	return tc.DSTypeFromString(name), true, nil
-}
-
-func update(tx *sql.Tx, cfg config.Config, user *auth.CurrentUser, ds *tc.DeliveryServiceNullable) (tc.DeliveryServiceNullable, int, error, error) {
-	if authorized, err := isTenantAuthorized(user, tx, &ds.DeliveryServiceNullableV12); err != nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("checking tenant: " + err.Error())
-	} else if !authorized {
-		return tc.DeliveryServiceNullable{}, http.StatusForbidden, errors.New("not authorized on this tenant"), nil
-	}
-
-	if ds.XMLID == nil {
-		return tc.DeliveryServiceNullable{}, http.StatusBadRequest, errors.New("missing xml_id"), nil
-	}
-	if ds.ID == nil {
-		return tc.DeliveryServiceNullable{}, http.StatusBadRequest, errors.New("missing id"), nil
-	}
-
-	dsType, ok, err := getDSType(tx, *ds.XMLID)
-	if !ok {
-		return tc.DeliveryServiceNullable{}, http.StatusNotFound, errors.New("delivery service '" + *ds.XMLID + "' not found"), nil
-	}
-	if err != nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("getting delivery service type during update: " + err.Error())
-	}
-
-	// oldHostName will be used to determine if SSL Keys need updating - this will be empty if the DS doesn't have SSL keys, because DS types without SSL keys may not have regexes, and thus will fail to get a host name.
-	oldHostName := ""
-	if dsType.HasSSLKeys() {
-		oldHostName, err = getOldHostName(*ds.ID, tx)
-		if err != nil {
-			return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("getting existing delivery service hostname: " + err.Error())
-		}
-	}
-
-	// TODO change DeepCachingType to implement sql.Valuer and sql.Scanner, so sqlx struct scan can be used.
-	deepCachingType := tc.DeepCachingType("").String()
-	if ds.DeepCachingType != nil {
-		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 [...]
-
-	if err != nil {
-		usrErr, sysErr, code := api.ParseDBError(err)
-		return tc.DeliveryServiceNullable{}, code, usrErr, sysErr
-	}
-	defer resultRows.Close()
-	if !resultRows.Next() {
-		return tc.DeliveryServiceNullable{}, http.StatusNotFound, errors.New("no delivery service found with this id"), nil
-	}
-	lastUpdated := tc.TimeNoMod{}
-	if err := resultRows.Scan(&lastUpdated); err != nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("scan updating delivery service: " + err.Error())
-	}
-	if resultRows.Next() {
-		xmlID := ""
-		if ds.XMLID != nil {
-			xmlID = *ds.XMLID
-		}
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("updating delivery service " + xmlID + ": " + "this update affected too many rows: > 1")
-	}
-
-	if ds.ID == nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("missing id after update")
-	}
-	if ds.XMLID == nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("missing xml_id after update")
-	}
-	if ds.TypeID == nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("missing type after update")
-	}
-	if ds.RoutingName == nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("missing routing name after update")
-	}
-	newDSType, err := getTypeFromID(*ds.TypeID, tx)
-	if err != nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("getting delivery service type after update: " + err.Error())
-	}
-	ds.Type = &newDSType
-
-	cdnDomain, err := getCDNDomain(*ds.ID, tx) // need to get the domain again, in case it changed.
-	if err != nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("getting CDN domain after update: " + err.Error())
-	}
-
-	matchLists, err := GetDeliveryServicesMatchLists([]string{*ds.XMLID}, tx)
-	if err != nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("getting matchlists after update: " + err.Error())
-	}
-	if ml, ok := matchLists[*ds.XMLID]; !ok {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("no matchlists after update")
-	} else {
-		ds.MatchList = &ml
-	}
-
-	// newHostName will be used to determine if SSL Keys need updating - this will be empty if the DS doesn't have SSL keys, because DS types without SSL keys may not have regexes, and thus will fail to get a host name.
-	newHostName := ""
-	if dsType.HasSSLKeys() {
-		newHostName, err = getHostName(ds.Protocol, *ds.Type, *ds.RoutingName, *ds.MatchList, cdnDomain)
-		if err != nil {
-			return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("getting hostname after update: " + err.Error())
-		}
-	}
-
-	if newDSType.HasSSLKeys() && oldHostName != newHostName {
-		if err := updateSSLKeys(ds, newHostName, tx, cfg); err != nil {
-			return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("updating delivery service " + *ds.XMLID + ": updating SSL keys: " + err.Error())
-		}
-	}
-
-	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())
-	}
-
-	if err := updatePrimaryOrigin(tx, user, *ds); err != nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("updating delivery service: " + err.Error())
-	}
-
-	ds.LastUpdated = &lastUpdated
-
-	if err := api.CreateChangeLogRawErr(api.ApiChange, "Updated ds: "+*ds.XMLID+" id: "+strconv.Itoa(*ds.ID), user, tx); err != nil {
-		return tc.DeliveryServiceNullable{}, http.StatusInternalServerError, nil, errors.New("writing change log entry: " + err.Error())
-	}
-	return *ds, http.StatusOK, nil, nil
-}
-
-// Delete is the DeliveryService implementation of the Deleter interface
-//all implementations of Deleter should use transactions and return the proper errorType
-func (ds *TODeliveryServiceV13) Delete() (error, error, int) {
-	return ds.V12().Delete()
-}
-
-// IsTenantAuthorized implements the Tenantable interface to ensure the user is authorized on the deliveryservice tenant
-func (ds *TODeliveryServiceV13) IsTenantAuthorized(user *auth.CurrentUser) (bool, error) {
-	return ds.V12().IsTenantAuthorized(user)
-}
-
-func readGetDeliveryServices(params map[string]string, tx *sqlx.Tx, user *auth.CurrentUser) ([]tc.DeliveryServiceNullable, []error, tc.ApiErrorType) {
-	if strings.HasSuffix(params["id"], ".json") {
-		params["id"] = params["id"][:len(params["id"])-len(".json")]
-	}
-	if _, ok := params["orderby"]; !ok {
-		params["orderby"] = "xml_id"
-	}
-
-	// Query Parameters to Database Query column mappings
-	// see the fields mapped in the SQL query
-	queryParamsToSQLCols := map[string]dbhelpers.WhereColumnInfo{
-		"id":               dbhelpers.WhereColumnInfo{"ds.id", api.IsInt},
-		"cdn":              dbhelpers.WhereColumnInfo{"ds.cdn_id", api.IsInt},
-		"xml_id":           dbhelpers.WhereColumnInfo{"ds.xml_id", nil},
-		"xmlId":            dbhelpers.WhereColumnInfo{"ds.xml_id", nil},
-		"profile":          dbhelpers.WhereColumnInfo{"ds.profile", api.IsInt},
-		"type":             dbhelpers.WhereColumnInfo{"ds.type", api.IsInt},
-		"logsEnabled":      dbhelpers.WhereColumnInfo{"ds.logs_enabled", api.IsBool},
-		"tenant":           dbhelpers.WhereColumnInfo{"ds.tenant_id", api.IsInt},
-		"signingAlgorithm": dbhelpers.WhereColumnInfo{"ds.signing_algorithm", nil},
-	}
-
-	where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(params, queryParamsToSQLCols)
-	if len(errs) > 0 {
-		return nil, errs, tc.DataConflictError
-	}
-
-	tenantIDs, err := tenant.GetUserTenantIDListTx(tx.Tx, user.TenantID)
-	if err != nil {
-		log.Errorln("received error querying for user's tenants: " + err.Error())
-		return nil, []error{tc.DBError}, tc.SystemError
-	}
-	where, queryValues = dbhelpers.AddTenancyCheck(where, queryValues, "ds.tenant_id", tenantIDs)
-	query := selectQuery() + where + orderBy
-
-	log.Debugln("generated deliveryServices query: " + query)
-	log.Debugf("executing with values: %++v\n", queryValues)
-
-	return GetDeliveryServices(query, queryValues, tx)
-}
-
-func GetDeliveryServices(query string, queryValues map[string]interface{}, tx *sqlx.Tx) ([]tc.DeliveryServiceNullable, []error, tc.ApiErrorType) {
-	rows, err := tx.NamedQuery(query, queryValues)
-	if err != nil {
-		return nil, []error{fmt.Errorf("querying: %v", err)}, tc.SystemError
-	}
-	defer rows.Close()
-
-	dses := []tc.DeliveryServiceNullable{}
-	dsCDNDomains := map[string]string{}
-	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 [...]
-		if err != nil {
-			return nil, []error{fmt.Errorf("getting delivery services: %v", err)}, tc.SystemError
-		}
-		dsCDNDomains[*ds.XMLID] = cdnDomain
-		if ds.DeepCachingType != nil {
-			*ds.DeepCachingType = tc.DeepCachingTypeFromString(string(*ds.DeepCachingType))
-		}
-		ds.Signed = ds.SigningAlgorithm != nil && *ds.SigningAlgorithm == tc.SigningAlgorithmURLSig
-		dses = append(dses, ds)
-	}
-
-	dsNames := make([]string, len(dses), len(dses))
-	for i, ds := range dses {
-		dsNames[i] = *ds.XMLID
-	}
-
-	matchLists, err := GetDeliveryServicesMatchLists(dsNames, tx.Tx)
-	if err != nil {
-		return nil, []error{errors.New("getting delivery service matchlists: " + err.Error())}, tc.SystemError
-	}
-	for i, ds := range dses {
-		matchList, ok := matchLists[*ds.XMLID]
-		if !ok {
-			continue
-		}
-		ds.MatchList = &matchList
-		ds.ExampleURLs = MakeExampleURLs(ds.Protocol, *ds.Type, *ds.RoutingName, *ds.MatchList, dsCDNDomains[*ds.XMLID])
-		dses[i] = ds
-	}
-
-	return dses, nil, tc.NoError
-}
-
-func updateSSLKeys(ds *tc.DeliveryServiceNullable, hostName string, tx *sql.Tx, cfg config.Config) error {
-	if ds.XMLID == nil {
-		return errors.New("delivery services has no XMLID!")
-	}
-	key, ok, err := riaksvc.GetDeliveryServiceSSLKeysObj(*ds.XMLID, riaksvc.DSSSLKeyVersionLatest, tx, cfg.RiakAuthOptions, cfg.RiakPort)
-	if err != nil {
-		return errors.New("getting SSL key: " + err.Error())
-	}
-	if !ok {
-		return nil // no keys to update
-	}
-	key.DeliveryService = *ds.XMLID
-	key.Hostname = hostName
-	if err := riaksvc.PutDeliveryServiceSSLKeysObj(key, tx, cfg.RiakAuthOptions, cfg.RiakPort); err != nil {
-		return errors.New("putting updated SSL key: " + err.Error())
-	}
-	return nil
-}
-
-// getHostName gets the host name used for delivery service requests. The dsProtocol may be nil, if the delivery service type doesn't have a protocol (e.g. ANY_MAP).
-func getHostName(dsProtocol *int, dsType tc.DSType, dsRoutingName string, dsMatchList []tc.DeliveryServiceMatch, cdnDomain string) (string, error) {
-	exampleURLs := MakeExampleURLs(dsProtocol, dsType, dsRoutingName, dsMatchList, cdnDomain)
-
-	exampleURL := ""
-	if dsProtocol != nil && *dsProtocol == 2 {
-		if len(exampleURLs) < 2 {
-			return "", errors.New("missing example URLs (does your delivery service have matchsets?)")
-		}
-		exampleURL = exampleURLs[1]
-	} else {
-		if len(exampleURLs) < 1 {
-			return "", errors.New("missing example URLs (does your delivery service have matchsets?)")
-		}
-		exampleURL = exampleURLs[0]
-	}
-
-	host := strings.NewReplacer(`http://`, ``, `https://`, ``).Replace(exampleURL)
-	if dsType.IsHTTP() {
-		if firstDot := strings.Index(host, "."); firstDot == -1 {
-			host = "*" // TODO warn? error?
-		} else {
-			host = "*" + host[firstDot:]
-		}
-	}
-	return host, nil
-}
-
-func getCDNDomain(dsID int, tx *sql.Tx) (string, error) {
-	q := `SELECT cdn.domain_name from cdn where cdn.id = (SELECT ds.cdn_id from deliveryservice as ds where ds.id = $1)`
-	cdnDomain := ""
-	if err := tx.QueryRow(q, dsID).Scan(&cdnDomain); err != nil {
-		return "", fmt.Errorf("getting CDN domain for delivery service '%v': "+err.Error(), dsID)
-	}
-	return cdnDomain, nil
-}
-
-func getCDNNameDomainDNSSecEnabled(dsID int, tx *sql.Tx) (string, string, bool, error) {
-	q := `SELECT cdn.name, cdn.domain_name, cdn.dnssec_enabled from cdn where cdn.id = (SELECT ds.cdn_id from deliveryservice as ds where ds.id = $1)`
-	cdnName := ""
-	cdnDomain := ""
-	dnssecEnabled := false
-	if err := tx.QueryRow(q, dsID).Scan(&cdnName, &cdnDomain, &dnssecEnabled); err != nil {
-		return "", "", false, fmt.Errorf("getting dnssec_enabled for delivery service '%v': "+err.Error(), dsID)
-	}
-	return cdnName, cdnDomain, dnssecEnabled, nil
-}
-
-// makeExampleURLs creates the example URLs for a delivery service. The dsProtocol may be nil, if the delivery service type doesn't have a protocol (e.g. ANY_MAP).
-func MakeExampleURLs(protocol *int, dsType tc.DSType, routingName string, matchList []tc.DeliveryServiceMatch, cdnDomain string) []string {
-	examples := []string{}
-	scheme := ""
-	scheme2 := ""
-	if protocol != nil {
-		switch *protocol {
-		case 0:
-			scheme = "http"
-		case 1:
-			scheme = "https"
-		case 2:
-			fallthrough
-		case 3:
-			scheme = "http"
-			scheme2 = "https"
-		default:
-			scheme = "http"
-		}
-	} else {
-		scheme = "http"
-	}
-	dsIsDNS := dsType.IsDNS()
-	regexReplacer := strings.NewReplacer(`\`, ``, `.*`, ``, `.`, ``)
-	for _, match := range matchList {
-		if dsIsDNS || match.Type == tc.DSMatchTypeHostRegex {
-			host := regexReplacer.Replace(match.Pattern)
-			if match.SetNumber == 0 {
-				examples = append(examples, scheme+`://`+routingName+`.`+host+`.`+cdnDomain)
-				if scheme2 != "" {
-					examples = append(examples, scheme2+`://`+routingName+`.`+host+`.`+cdnDomain)
-				}
-				continue
-			}
-			examples = append(examples, scheme+`://`+match.Pattern)
-			if scheme2 != "" {
-				examples = append(examples, scheme2+`://`+match.Pattern)
-			}
-		} else if match.Type == tc.DSMatchTypePathRegex {
-			examples = append(examples, match.Pattern)
-		}
-	}
-	return examples
-}
-
-func GetDeliveryServicesMatchLists(dses []string, tx *sql.Tx) (map[string][]tc.DeliveryServiceMatch, error) {
-	// TODO move somewhere generic
-	q := `
-SELECT ds.xml_id as ds_name, t.name as type, r.pattern, COALESCE(dsr.set_number, 0)
-FROM regex as r
-JOIN deliveryservice_regex as dsr ON dsr.regex = r.id
-JOIN deliveryservice as ds on ds.id = dsr.deliveryservice
-JOIN type as t ON r.type = t.id
-WHERE ds.xml_id = ANY($1)
-ORDER BY dsr.set_number
-`
-	rows, err := tx.Query(q, pq.Array(dses))
-	if err != nil {
-		return nil, errors.New("getting delivery service regexes: " + err.Error())
-	}
-	defer rows.Close()
-
-	matches := map[string][]tc.DeliveryServiceMatch{}
-	for rows.Next() {
-		m := tc.DeliveryServiceMatch{}
-		dsName := ""
-		matchTypeStr := ""
-		if err := rows.Scan(&dsName, &matchTypeStr, &m.Pattern, &m.SetNumber); err != nil {
-			return nil, errors.New("scanning delivery service regexes: " + err.Error())
-		}
-		matchType := tc.DSMatchTypeFromString(matchTypeStr)
-		if matchType == tc.DSMatchTypeInvalid {
-			return nil, errors.New("getting delivery service regexes: got invalid delivery service match type '" + matchTypeStr + "'")
-		}
-		m.Type = matchType
-		matches[dsName] = append(matches[dsName], m)
-	}
-	return matches, nil
-}
-
-type tierType int
-
-const (
-	midTier tierType = iota
-	edgeTier
-)
-
-// 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, 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, maxOriginConns); err != nil {
-		return errors.New("creating mid header rewrite parameters: " + err.Error())
-	}
-	if err := ensureRegexRemapParams(tx, dsID, xmlID, regexRemap); err != nil {
-		return errors.New("creating mid regex remap parameters: " + err.Error())
-	}
-	if err := ensureCacheURLParams(tx, dsID, xmlID, cacheURL); err != nil {
-		return errors.New("creating mid cacheurl parameters: " + err.Error())
-	}
-	if err := ensureURLSigParams(tx, dsID, xmlID, signingAlgorithm); err != nil {
-		return errors.New("creating urlsig parameters: " + err.Error())
-	}
-	return nil
-}
-
-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 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)
-	if err != nil {
-		return err
-	}
-	if tier != midTier {
-		return createDSLocationProfileParams(tx, locationParamID, dsID)
-	}
-	profileParameterQuery := `
-INSERT INTO profile_parameter (profile, parameter)
-SELECT DISTINCT(profile), $1::bigint FROM server
-WHERE server.type IN (SELECT id from type where type.name like 'MID%' and type.use_in_table = 'server')
-AND server.cdn_id = (select cdn_id from deliveryservice where id = $2)
-ON CONFLICT DO NOTHING
-`
-	if _, err := tx.Exec(profileParameterQuery, locationParamID, dsID); err != nil {
-		return fmt.Errorf("parameter query to insert profile_parameters query '"+profileParameterQuery+"' location parameter ID '%v' delivery service ID '%v': %v", locationParamID, dsID, err)
-	}
-	return nil
-}
-
-func ensureURLSigParams(tx *sql.Tx, dsID int, xmlID string, signingAlgorithm *string) error {
-	configFile := "url_sig_" + xmlID + ".config"
-	if signingAlgorithm == nil || *signingAlgorithm != tc.SigningAlgorithmURLSig {
-		return deleteLocationParam(tx, configFile)
-	}
-	locationParamID, err := ensureLocation(tx, configFile)
-	if err != nil {
-		return err
-	}
-	return createDSLocationProfileParams(tx, locationParamID, dsID)
-}
-
-func ensureRegexRemapParams(tx *sql.Tx, dsID int, xmlID string, regexRemap *string) error {
-	configFile := "regex_remap_" + xmlID + ".config"
-	if regexRemap == nil || *regexRemap == "" {
-		return deleteLocationParam(tx, configFile)
-	}
-	locationParamID, err := ensureLocation(tx, configFile)
-	if err != nil {
-		return err
-	}
-	return createDSLocationProfileParams(tx, locationParamID, dsID)
-}
-
-func ensureCacheURLParams(tx *sql.Tx, dsID int, xmlID string, cacheURL *string) error {
-	configFile := "cacheurl_" + xmlID + ".config"
-	if cacheURL == nil || *cacheURL == "" {
-		return deleteLocationParam(tx, configFile)
-	}
-	locationParamID, err := ensureLocation(tx, configFile)
-	if err != nil {
-		return err
-	}
-	return createDSLocationProfileParams(tx, locationParamID, dsID)
-}
-
-// createDSLocationProfileParams adds the given parameter to all profiles assigned to servers which are assigned to the given delivery service.
-func createDSLocationProfileParams(tx *sql.Tx, locationParamID int, deliveryServiceID int) error {
-	profileParameterQuery := `
-INSERT INTO profile_parameter (profile, parameter)
-SELECT DISTINCT(profile), $1::bigint FROM server
-WHERE server.id IN (SELECT server from deliveryservice_server where deliveryservice = $2)
-ON CONFLICT DO NOTHING
-`
-	if _, err := tx.Exec(profileParameterQuery, locationParamID, deliveryServiceID); err != nil {
-		return errors.New("inserting profile_parameters: " + err.Error())
-	}
-	return nil
-}
-
-// ensureLocation ensures a location parameter exists for the given config file. If not, it creates one, with the same value as the 'remap.config' file parameter. Returns the ID of the location parameter.
-func ensureLocation(tx *sql.Tx, configFile string) (int, error) {
-	atsConfigLocation := ""
-	if err := tx.QueryRow(`SELECT value FROM parameter WHERE name = 'location' AND config_file = 'remap.config'`).Scan(&atsConfigLocation); err != nil {
-		if err == sql.ErrNoRows {
-			return 0, errors.New("executing parameter query for ATS config location: parameter missing (do you have a name=location config_file=remap.config parameter?")
-		}
-		return 0, errors.New("executing parameter query for ATS config location: " + err.Error())
-	}
-	atsConfigLocation = strings.TrimRight(atsConfigLocation, `/`)
-
-	locationParamID := 0
-	existingLocationErr := tx.QueryRow(`SELECT id FROM parameter WHERE name = 'location' AND config_file = $1`, configFile).Scan(&locationParamID)
-	if existingLocationErr != nil && existingLocationErr != sql.ErrNoRows {
-		return 0, errors.New("executing parameter query for existing location: " + existingLocationErr.Error())
-	}
-
-	if existingLocationErr == sql.ErrNoRows {
-		resultRows, err := tx.Query(`INSERT INTO parameter (config_file, name, value) VALUES ($1, 'location', $2) RETURNING id`, configFile, atsConfigLocation)
-		if err != nil {
-			return 0, errors.New("executing parameter query to insert location: " + err.Error())
-		}
-		defer resultRows.Close()
-		if !resultRows.Next() {
-			return 0, errors.New("parameter query to insert location didn't return id")
-		}
-		if err := resultRows.Scan(&locationParamID); err != nil {
-			return 0, errors.New("parameter query to insert location returned id scan: " + err.Error())
-		}
-		if resultRows.Next() {
-			return 0, errors.New("parameter query to insert location returned too many rows (>1)")
-		}
-	}
-	return locationParamID, nil
-}
-
-func deleteLocationParam(tx *sql.Tx, configFile string) error {
-	id := 0
-	err := tx.QueryRow(`DELETE FROM parameter WHERE name = 'location' AND config_file = $1 RETURNING id`, configFile).Scan(&id)
-	if err == sql.ErrNoRows {
-		return nil
-	}
-	if err != nil {
-		log.Errorln("deleting name=location config_file=" + configFile + " parameter: " + err.Error())
-		return errors.New("executing parameter delete: " + err.Error())
-	}
-	if _, err := tx.Exec(`DELETE FROM profile_parameter WHERE parameter = $1`, id); err != nil {
-		log.Errorf("deleting parameter name=location config_file=%v id=%v profile_parameter: %v", configFile, id, err)
-		return errors.New("executing parameter profile_parameter delete: " + err.Error())
-	}
-	return nil
-}
-
-// export the selectQuery for the 'deliveryservice' package.
-func GetDSSelectQuery() string {
-	return selectQuery()
-}
-
-func selectQuery() string {
-	return `
-SELECT
-ds.active,
-ds.anonymous_blocking_enabled,
-ds.cacheurl,
-ds.ccr_dns_ttl,
-ds.cdn_id,
-cdn.name as cdnName,
-ds.check_path,
-ds.consistent_hash_regex,
-CAST(ds.deep_caching_type AS text) as deep_caching_type,
-ds.display_name,
-ds.dns_bypass_cname,
-ds.dns_bypass_ip,
-ds.dns_bypass_ip6,
-ds.dns_bypass_ttl,
-ds.dscp,
-ds.edge_header_rewrite,
-ds.geolimit_redirect_url,
-ds.geo_limit,
-ds.geo_limit_countries,
-ds.geo_provider,
-ds.global_max_mbps,
-ds.global_max_tps,
-ds.fq_pacing_rate,
-ds.http_bypass_fqdn,
-ds.id,
-ds.info_url,
-ds.initial_dispersion,
-ds.ipv6_routing_enabled,
-ds.last_updated,
-ds.logs_enabled,
-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),
-ds.multi_site_origin,
-(SELECT o.protocol::::text || ':://' || o.fqdn || rtrim(concat('::', o.port::::text), '::')
-	FROM origin o
-	WHERE o.deliveryservice = ds.id
-	AND o.is_primary) as org_server_fqdn,
-ds.origin_shield,
-ds.profile as profileID,
-profile.name as profile_name,
-profile.description  as profile_description,
-ds.protocol,
-ds.qstring_ignore,
-ds.range_request_handling,
-ds.regex_remap,
-ds.regional_geo_blocking,
-ds.remap_text,
-ds.routing_name,
-ds.signing_algorithm,
-ds.ssl_key_version,
-ds.tenant_id,
-tenant.name,
-ds.tr_request_headers,
-ds.tr_response_headers,
-type.name,
-ds.type as type_id,
-ds.xml_id,
-cdn.domain_name as cdn_domain
-from deliveryservice as ds
-JOIN type ON ds.type = type.id
-JOIN cdn ON ds.cdn_id = cdn.id
-LEFT JOIN profile ON ds.profile = profile.id
-LEFT JOIN tenant ON ds.tenant_id = tenant.id
-`
-}
-
-func updateDSQuery() string {
-	return `
-UPDATE
-deliveryservice SET
-active=$1,
-cacheurl=$2,
-ccr_dns_ttl=$3,
-cdn_id=$4,
-check_path=$5,
-deep_caching_type=$6,
-display_name=$7,
-dns_bypass_cname=$8,
-dns_bypass_ip=$9,
-dns_bypass_ip6=$10,
-dns_bypass_ttl=$11,
-dscp=$12,
-edge_header_rewrite=$13,
-geolimit_redirect_url=$14,
-geo_limit=$15,
-geo_limit_countries=$16,
-geo_provider=$17,
-global_max_mbps=$18,
-global_max_tps=$19,
-fq_pacing_rate=$20,
-http_bypass_fqdn=$21,
-info_url=$22,
-initial_dispersion=$23,
-ipv6_routing_enabled=$24,
-logs_enabled=$25,
-long_desc=$26,
-long_desc_1=$27,
-long_desc_2=$28,
-max_dns_answers=$29,
-mid_header_rewrite=$30,
-miss_lat=$31,
-miss_long=$32,
-multi_site_origin=$33,
-origin_shield=$34,
-profile=$35,
-protocol=$36,
-qstring_ignore=$37,
-range_request_handling=$38,
-regex_remap=$39,
-regional_geo_blocking=$40,
-remap_text=$41,
-routing_name=$42,
-signing_algorithm=$43,
-ssl_key_version=$44,
-tenant_id=$45,
-tr_request_headers=$46,
-tr_response_headers=$47,
-type=$48,
-xml_id=$49,
-anonymous_blocking_enabled=$50,
-consistent_hash_regex=$51,
-max_origin_connections=$52
-WHERE id=$53
-RETURNING last_updated
-`
-}
-
-func insertQuery() string {
-	return `
-INSERT INTO deliveryservice (
-active,
-anonymous_blocking_enabled,
-cacheurl,
-ccr_dns_ttl,
-cdn_id,
-check_path,
-consistent_hash_regex,
-deep_caching_type,
-display_name,
-dns_bypass_cname,
-dns_bypass_ip,
-dns_bypass_ip6,
-dns_bypass_ttl,
-dscp,
-edge_header_rewrite,
-geolimit_redirect_url,
-geo_limit,
-geo_limit_countries,
-geo_provider,
-global_max_mbps,
-global_max_tps,
-fq_pacing_rate,
-http_bypass_fqdn,
-info_url,
-initial_dispersion,
-ipv6_routing_enabled,
-logs_enabled,
-long_desc,
-long_desc_1,
-long_desc_2,
-max_dns_answers,
-max_origin_connections,
-mid_header_rewrite,
-miss_lat,
-miss_long,
-multi_site_origin,
-origin_shield,
-profile,
-protocol,
-qstring_ignore,
-range_request_handling,
-regex_remap,
-regional_geo_blocking,
-remap_text,
-routing_name,
-signing_algorithm,
-ssl_key_version,
-tenant_id,
-tr_request_headers,
-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,$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
deleted file mode 100644
index 98f3337..0000000
--- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv14.go
+++ /dev/null
@@ -1,170 +0,0 @@
-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/routing/routes.go b/traffic_ops/traffic_ops_golang/routing/routes.go
index aeb8594..532d73c 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -389,24 +389,22 @@ 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.4, http.MethodGet, `deliveryservices/?(\.json)?$`, api.ReadHandler(&deliveryservice.TODeliveryService{}), 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.4, http.MethodGet, `deliveryservices/{id}/?(\.json)?$`, api.ReadHandler(&deliveryservice.TODeliveryService{}), 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.4, http.MethodPost, `deliveryservices/?(\.json)?$`, deliveryservice.Create, 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.4, http.MethodPut, `deliveryservices/{id}/?(\.json)?$`, deliveryservice.Update, 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},