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 <[email protected]>
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},

Reply via email to