You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by de...@apache.org on 2018/05/30 16:12:55 UTC

[incubator-trafficcontrol] branch master updated (38d2a1d -> 0164fb8)

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

dewrich pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-trafficcontrol.git.


    from 38d2a1d  add the ability to filter servers by parent cachegroup
     new 38cbfca  Vendor miekg/dns
     new 75a949d  Add TO Go deliveryservices routes
     new 0164fb8  Add TO Go Deliveryservices Integration Tests

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .rat-excludes                                      |    1 +
 LICENSE                                            |    4 +
 lib/go-tc/deliveryservice_requests.go              |   52 +-
 lib/go-tc/deliveryservice_ssl_keys.go              |   26 +
 lib/go-tc/deliveryservices.go                      |  255 +-
 licenses/BSD-miekg-dns                             |   32 +
 traffic_ops/app/lib/TrafficOpsRoutes.pm            |    5 -
 traffic_ops/client/v13/deliveryservice.go          |  253 ++
 .../client/v13/deliveryservice_endpoints.go        |   66 +
 traffic_ops/client/v13/endpoints.go                |   18 +
 traffic_ops/client/v13/tenant.go                   |   86 +
 traffic_ops/client/v13/tenant_endpoints.go         |   25 +
 traffic_ops/client/v13/util.go                     |   65 +
 .../testing/api/v13/deliveryservices_test.go       |  225 ++
 traffic_ops/testing/api/v13/tc-fixtures.json       |  147 +-
 traffic_ops/traffic_ops_golang/api/change_log.go   |    7 +-
 .../traffic_ops_golang/api/shared_handlers.go      |   55 +-
 traffic_ops/traffic_ops_golang/crconfig/handler.go |    8 +-
 .../traffic_ops_golang/dbhelpers/db_helpers.go     |   26 +
 .../deliveryservice/deliveryservices.go            |  406 ---
 .../deliveryservice/deliveryservices_test.go       |   54 -
 .../deliveryservice/deliveryservicesv12.go         |  431 +++
 .../deliveryservice/deliveryservicesv13.go         | 1144 +++++++
 .../traffic_ops_golang/deliveryservice/dnssec.go   |  206 ++
 .../deliveryservice/handlers_test.go               |    2 +-
 .../deliveryservice/request/requests_test.go       |   36 +-
 .../deliveryservice/request/validate.go            |    7 +-
 .../traffic_ops_golang/deliveryservices_keys.go    |  107 +-
 traffic_ops/traffic_ops_golang/riaksvc/dsutil.go   |  140 +
 .../traffic_ops_golang/riaksvc/riak_services.go    |  110 +-
 .../riaksvc/riak_services_test.go                  |    6 +-
 traffic_ops/traffic_ops_golang/routes.go           |   26 +-
 traffic_ops/traffic_ops_golang/urisigning.go       |    6 +-
 .../vendor/github.com/miekg/dns/.codecov.yml       |    8 +
 .../vendor/github.com/miekg/dns/.travis.yml        |   20 +
 .../vendor/github.com/miekg/dns/AUTHORS            |    1 +
 .../vendor/github.com/miekg/dns/CONTRIBUTORS       |   10 +
 .../vendor/github.com/miekg/dns/COPYRIGHT          |    9 +
 .../vendor/github.com/miekg/dns/Gopkg.lock         |   21 +
 .../vendor/github.com/miekg/dns/Gopkg.toml         |   26 +
 .../vendor/github.com/miekg/dns/LICENSE            |   32 +
 .../vendor/github.com/miekg/dns/Makefile.fuzz      |   33 +
 .../vendor/github.com/miekg/dns/Makefile.release   |   52 +
 .../vendor/github.com/miekg/dns/README.md          |  168 +
 .../vendor/github.com/miekg/dns/client.go          |  503 +++
 .../vendor/github.com/miekg/dns/client_test.go     |  590 ++++
 .../vendor/github.com/miekg/dns/clientconfig.go    |  139 +
 .../github.com/miekg/dns/clientconfig_test.go      |  181 +
 .../github.com/miekg/dns/compress_generate.go      |  188 +
 .../vendor/github.com/miekg/dns/dane.go            |   43 +
 .../vendor/github.com/miekg/dns/defaults.go        |  288 ++
 .../vendor/github.com/miekg/dns/dns.go             |  107 +
 .../vendor/github.com/miekg/dns/dns_bench_test.go  |  230 ++
 .../vendor/github.com/miekg/dns/dns_test.go        |  320 ++
 .../vendor/github.com/miekg/dns/dnssec.go          |  784 +++++
 .../vendor/github.com/miekg/dns/dnssec_keygen.go   |  178 +
 .../vendor/github.com/miekg/dns/dnssec_keyscan.go  |  297 ++
 .../vendor/github.com/miekg/dns/dnssec_privkey.go  |   93 +
 .../vendor/github.com/miekg/dns/dnssec_test.go     |  841 +++++
 .../vendor/github.com/miekg/dns/dnsutil/util.go    |   83 +
 .../github.com/miekg/dns/dnsutil/util_test.go      |  130 +
 .../vendor/github.com/miekg/dns/doc.go             |  272 ++
 .../vendor/github.com/miekg/dns/dyn_test.go        |    3 +
 .../vendor/github.com/miekg/dns/edns.go            |  627 ++++
 .../vendor/github.com/miekg/dns/edns_test.go       |   68 +
 .../vendor/github.com/miekg/dns/example_test.go    |  146 +
 .../vendor/github.com/miekg/dns/format.go          |   87 +
 .../vendor/github.com/miekg/dns/fuzz.go            |   23 +
 .../vendor/github.com/miekg/dns/generate.go        |  159 +
 .../vendor/github.com/miekg/dns/issue_test.go      |   62 +
 .../vendor/github.com/miekg/dns/labels.go          |  191 ++
 .../vendor/github.com/miekg/dns/labels_test.go     |  201 ++
 .../vendor/github.com/miekg/dns/leak_test.go       |   71 +
 .../vendor/github.com/miekg/dns/length_test.go     |  174 +
 .../vendor/github.com/miekg/dns/msg.go             | 1177 +++++++
 .../vendor/github.com/miekg/dns/msg_generate.go    |  348 ++
 .../vendor/github.com/miekg/dns/msg_helpers.go     |  637 ++++
 .../vendor/github.com/miekg/dns/msg_test.go        |  124 +
 .../vendor/github.com/miekg/dns/nsecx.go           |  106 +
 .../vendor/github.com/miekg/dns/nsecx_test.go      |  133 +
 .../vendor/github.com/miekg/dns/parse_test.go      | 1465 ++++++++
 .../vendor/github.com/miekg/dns/privaterr.go       |  149 +
 .../vendor/github.com/miekg/dns/privaterr_test.go  |  166 +
 .../vendor/github.com/miekg/dns/rawmsg.go          |   49 +
 .../vendor/github.com/miekg/dns/remote_test.go     |   19 +
 .../vendor/github.com/miekg/dns/reverse.go         |   38 +
 .../vendor/github.com/miekg/dns/rr_test.go         |    7 +
 .../vendor/github.com/miekg/dns/sanitize.go        |   84 +
 .../vendor/github.com/miekg/dns/sanitize_test.go   |   75 +
 .../vendor/github.com/miekg/dns/scan.go            | 1007 ++++++
 .../vendor/github.com/miekg/dns/scan_rr.go         | 2199 ++++++++++++
 .../vendor/github.com/miekg/dns/scan_test.go       |   48 +
 .../vendor/github.com/miekg/dns/scanner.go         |   56 +
 .../vendor/github.com/miekg/dns/server.go          |  724 ++++
 .../vendor/github.com/miekg/dns/server_test.go     |  792 +++++
 .../vendor/github.com/miekg/dns/sig0.go            |  218 ++
 .../vendor/github.com/miekg/dns/sig0_test.go       |   89 +
 .../vendor/github.com/miekg/dns/singleinflight.go  |   57 +
 .../vendor/github.com/miekg/dns/smimea.go          |   47 +
 .../vendor/github.com/miekg/dns/tlsa.go            |   47 +
 .../vendor/github.com/miekg/dns/tsig.go            |  386 +++
 .../vendor/github.com/miekg/dns/tsig_test.go       |   52 +
 .../vendor/github.com/miekg/dns/types.go           | 1381 ++++++++
 .../vendor/github.com/miekg/dns/types_generate.go  |  272 ++
 .../vendor/github.com/miekg/dns/types_test.go      |   74 +
 .../vendor/github.com/miekg/dns/udp.go             |  102 +
 .../vendor/github.com/miekg/dns/udp_test.go        |  140 +
 .../vendor/github.com/miekg/dns/udp_windows.go     |   37 +
 .../vendor/github.com/miekg/dns/update.go          |  106 +
 .../vendor/github.com/miekg/dns/update_test.go     |  139 +
 .../vendor/github.com/miekg/dns/version.go         |   15 +
 .../vendor/github.com/miekg/dns/version_test.go    |   10 +
 .../vendor/github.com/miekg/dns/xfr.go             |  260 ++
 .../vendor/github.com/miekg/dns/zcompress.go       |  118 +
 .../vendor/github.com/miekg/dns/zmsg.go            | 3615 ++++++++++++++++++++
 .../vendor/github.com/miekg/dns/ztypes.go          |  863 +++++
 116 files changed, 28160 insertions(+), 767 deletions(-)
 create mode 100644 licenses/BSD-miekg-dns
 create mode 100644 traffic_ops/client/v13/deliveryservice.go
 create mode 100644 traffic_ops/client/v13/deliveryservice_endpoints.go
 create mode 100644 traffic_ops/client/v13/endpoints.go
 create mode 100644 traffic_ops/client/v13/tenant.go
 create mode 100644 traffic_ops/client/v13/tenant_endpoints.go
 create mode 100644 traffic_ops/client/v13/util.go
 create mode 100644 traffic_ops/testing/api/v13/deliveryservices_test.go
 delete mode 100644 traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go
 delete mode 100644 traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go
 create mode 100644 traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go
 create mode 100644 traffic_ops/traffic_ops_golang/deliveryservice/dnssec.go
 create mode 100644 traffic_ops/traffic_ops_golang/riaksvc/dsutil.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/.codecov.yml
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/.travis.yml
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/AUTHORS
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/CONTRIBUTORS
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/COPYRIGHT
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/Gopkg.lock
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/Gopkg.toml
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/LICENSE
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/Makefile.fuzz
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/Makefile.release
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/README.md
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/client.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/client_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/clientconfig.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/clientconfig_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/compress_generate.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dane.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/defaults.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dns.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dns_bench_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dns_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnssec.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnssec_keygen.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnssec_keyscan.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnssec_privkey.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnssec_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnsutil/util.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnsutil/util_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/doc.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dyn_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/edns.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/edns_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/example_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/format.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/fuzz.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/generate.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/issue_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/labels.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/labels_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/leak_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/length_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/msg.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/msg_generate.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/msg_helpers.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/msg_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/nsecx.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/nsecx_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/parse_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/privaterr.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/privaterr_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/rawmsg.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/remote_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/reverse.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/rr_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/sanitize.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/sanitize_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/scan.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/scan_rr.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/scan_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/scanner.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/server.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/server_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/sig0.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/sig0_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/singleinflight.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/smimea.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/tlsa.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/tsig.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/tsig_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/types.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/types_generate.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/types_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/udp.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/udp_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/udp_windows.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/update.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/update_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/version.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/version_test.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/xfr.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/zcompress.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/zmsg.go
 create mode 100644 traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/ztypes.go

-- 
To stop receiving notification emails like this one, please contact
dewrich@apache.org.

[incubator-trafficcontrol] 02/03: Add TO Go deliveryservices routes

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dewrich pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-trafficcontrol.git

commit 75a949dedb31337ab6aba666daebeae705af97cb
Author: Robert Butts <ro...@apache.org>
AuthorDate: Thu Apr 12 08:29:30 2018 -0600

    Add TO Go deliveryservices routes
---
 .rat-excludes                                      |    1 +
 lib/go-tc/deliveryservice_requests.go              |   52 +-
 lib/go-tc/deliveryservice_ssl_keys.go              |   26 +
 lib/go-tc/deliveryservices.go                      |  255 +++--
 traffic_ops/app/lib/TrafficOpsRoutes.pm            |    5 -
 traffic_ops/traffic_ops_golang/api/change_log.go   |    7 +-
 .../traffic_ops_golang/api/shared_handlers.go      |   53 +-
 traffic_ops/traffic_ops_golang/crconfig/handler.go |    8 +-
 .../traffic_ops_golang/dbhelpers/db_helpers.go     |   26 +
 .../deliveryservice/deliveryservices.go            |  406 -------
 .../deliveryservice/deliveryservicesv12.go         |  433 ++++++++
 .../deliveryservice/deliveryservicesv13.go         | 1119 ++++++++++++++++++++
 .../traffic_ops_golang/deliveryservice/dnssec.go   |  206 ++++
 .../deliveryservice/request/requests_test.go       |   16 +-
 .../deliveryservice/request/validate.go            |    7 +-
 .../traffic_ops_golang/deliveryservices_keys.go    |  107 +-
 traffic_ops/traffic_ops_golang/riaksvc/dsutil.go   |  140 +++
 .../traffic_ops_golang/riaksvc/riak_services.go    |  110 +-
 traffic_ops/traffic_ops_golang/routes.go           |   26 +-
 traffic_ops/traffic_ops_golang/urisigning.go       |    6 +-
 20 files changed, 2320 insertions(+), 689 deletions(-)

diff --git a/.rat-excludes b/.rat-excludes
index 44a7533..e461914 100644
--- a/.rat-excludes
+++ b/.rat-excludes
@@ -70,3 +70,4 @@ asn1-ber.v1(?:                       MIT. Properly documented in LICENSE ){0}
 bytefmt(?:                           Apache 2.0. Properly documented in LICENSE ){0}
 siphash(?:                           CC0. Properly documented in LICENSE ){0}
 bbolt(?:                             MIT. Properly documented in LICENSE ){0}
+miekg(?:                             BSD 3-clause. Properly documented in LICENSE ){0}
diff --git a/lib/go-tc/deliveryservice_requests.go b/lib/go-tc/deliveryservice_requests.go
index 7b2a4cf..9aae4bd 100644
--- a/lib/go-tc/deliveryservice_requests.go
+++ b/lib/go-tc/deliveryservice_requests.go
@@ -32,37 +32,37 @@ type IDNoMod int
 // DeliveryServiceRequest is used as part of the workflow to create,
 // modify, or delete a delivery service.
 type DeliveryServiceRequest struct {
-	AssigneeID      int             `json:"assigneeId,omitempty"`
-	Assignee        string          `json:"assignee,omitempty"`
-	AuthorID        IDNoMod         `json:"authorId"`
-	Author          string          `json:"author"`
-	ChangeType      string          `json:"changeType"`
-	CreatedAt       *TimeNoMod      `json:"createdAt"`
-	ID              int             `json:"id"`
-	LastEditedBy    string          `json:"lastEditedBy,omitempty"`
-	LastEditedByID  IDNoMod         `json:"lastEditedById,omitempty"`
-	LastUpdated     *TimeNoMod      `json:"lastUpdated"`
-	DeliveryService DeliveryService `json:"deliveryService"`
-	Status          RequestStatus   `json:"status"`
-	XMLID           string          `json:"-" db:"xml_id"`
+	AssigneeID      int                `json:"assigneeId,omitempty"`
+	Assignee        string             `json:"assignee,omitempty"`
+	AuthorID        IDNoMod            `json:"authorId"`
+	Author          string             `json:"author"`
+	ChangeType      string             `json:"changeType"`
+	CreatedAt       *TimeNoMod         `json:"createdAt"`
+	ID              int                `json:"id"`
+	LastEditedBy    string             `json:"lastEditedBy,omitempty"`
+	LastEditedByID  IDNoMod            `json:"lastEditedById,omitempty"`
+	LastUpdated     *TimeNoMod         `json:"lastUpdated"`
+	DeliveryService DeliveryServiceV13 `json:"deliveryService"` // TODO version DeliveryServiceRequest
+	Status          RequestStatus      `json:"status"`
+	XMLID           string             `json:"-" db:"xml_id"`
 }
 
 // DeliveryServiceRequestNullable is used as part of the workflow to create,
 // modify, or delete a delivery service.
 type DeliveryServiceRequestNullable struct {
-	AssigneeID      *int                     `json:"assigneeId,omitempty" db:"assignee_id"`
-	Assignee        *string                  `json:"assignee,omitempty"`
-	AuthorID        *IDNoMod                 `json:"authorId" db:"author_id"`
-	Author          *string                  `json:"author"`
-	ChangeType      *string                  `json:"changeType" db:"change_type"`
-	CreatedAt       *TimeNoMod               `json:"createdAt" db:"created_at"`
-	ID              *int                     `json:"id" db:"id"`
-	LastEditedBy    *string                  `json:"lastEditedBy"`
-	LastEditedByID  *IDNoMod                 `json:"lastEditedById" db:"last_edited_by_id"`
-	LastUpdated     *TimeNoMod               `json:"lastUpdated" db:"last_updated"`
-	DeliveryService *DeliveryServiceNullable `json:"deliveryService" db:"deliveryservice"`
-	Status          *RequestStatus           `json:"status" db:"status"`
-	XMLID           *string                  `json:"-" db:"xml_id"`
+	AssigneeID      *int                        `json:"assigneeId,omitempty" db:"assignee_id"`
+	Assignee        *string                     `json:"assignee,omitempty"`
+	AuthorID        *IDNoMod                    `json:"authorId" db:"author_id"`
+	Author          *string                     `json:"author"`
+	ChangeType      *string                     `json:"changeType" db:"change_type"`
+	CreatedAt       *TimeNoMod                  `json:"createdAt" db:"created_at"`
+	ID              *int                        `json:"id" db:"id"`
+	LastEditedBy    *string                     `json:"lastEditedBy"`
+	LastEditedByID  *IDNoMod                    `json:"lastEditedById" db:"last_edited_by_id"`
+	LastUpdated     *TimeNoMod                  `json:"lastUpdated" db:"last_updated"`
+	DeliveryService *DeliveryServiceNullableV13 `json:"deliveryService" db:"deliveryservice"` // TODO version DeliveryServiceRequest
+	Status          *RequestStatus              `json:"status" db:"status"`
+	XMLID           *string                     `json:"-" db:"xml_id"`
 }
 
 // UnmarshalJSON implements the json.Unmarshaller interface to suppress unmarshalling for IDNoMod
diff --git a/lib/go-tc/deliveryservice_ssl_keys.go b/lib/go-tc/deliveryservice_ssl_keys.go
index 9f47f70..830bebe 100644
--- a/lib/go-tc/deliveryservice_ssl_keys.go
+++ b/lib/go-tc/deliveryservice_ssl_keys.go
@@ -84,3 +84,29 @@ func (v *DeliveryServiceSSLKeys) UnmarshalJSON(b []byte) (err error) {
 	}
 	return err
 }
+
+// DNSSECKeys is the DNSSEC keys object stored in Riak. The map key strings are both DeliveryServiceNames and CDNNames.
+type DNSSECKeys map[string]DNSSECKeySet
+
+type DNSSECKeySet struct {
+	ZSK []DNSSECKey `json:"zsk"`
+	KSK []DNSSECKey `json:"ksk"`
+}
+
+type DNSSECKey struct {
+	InceptionDateUnix  int64 `json:"inceptionDate"`
+	ExpirationDateUnix int64 `json:"expirationDate"`
+	Name               string `json:"name"`
+	TTLSeconds         uint64 `json:"ttl,string"`
+	Status             string `json:"status"`
+	EffectiveDateUnix  int64 `json:"effectiveDate"`
+	Public             string `json:"public"`
+	Private            string `json:"private"`
+	DSRecord           *DNSSECKeyDSRecord `json:"dsRecord,omitempty"`
+}
+
+type DNSSECKeyDSRecord struct {
+	Algorithm  int64  `json:"algorithm,string"`
+	DigestType int64  `json:"digestType,string"`
+	Digest     string `json:"digest"`
+}
diff --git a/lib/go-tc/deliveryservices.go b/lib/go-tc/deliveryservices.go
index fa3bbf1..7990998 100644
--- a/lib/go-tc/deliveryservices.go
+++ b/lib/go-tc/deliveryservices.go
@@ -56,126 +56,153 @@ type DeleteDeliveryServiceResponse struct {
 }
 
 // DeliveryService ...
+// TODO move contents to DeliveryServiceV12, fix references, and remove
 type DeliveryService struct {
-	Active                   bool                   `json:"active"`
-	AnonymousBlockingEnabled bool                   `json:"anonymousBlockingEnabled"`
-	CacheURL                 string                 `json:"cacheurl"`
-	CCRDNSTTL                int                    `json:"ccrDnsTtl"`
-	CDNID                    int                    `json:"cdnId"`
-	CDNName                  string                 `json:"cdnName"`
-	CheckPath                string                 `json:"checkPath"`
-	DeepCachingType          DeepCachingType        `json:"deepCachingType"`
-	DisplayName              string                 `json:"displayName"`
-	DNSBypassCname           string                 `json:"dnsBypassCname"`
-	DNSBypassIP              string                 `json:"dnsBypassIp"`
-	DNSBypassIP6             string                 `json:"dnsBypassIp6"`
-	DNSBypassTTL             int                    `json:"dnsBypassTtl"`
-	DSCP                     int                    `json:"dscp"`
-	EdgeHeaderRewrite        string                 `json:"edgeHeaderRewrite"`
-	ExampleURLs              []string               `json:"exampleURLs"`
-	GeoLimit                 int                    `json:"geoLimit"`
-	FQPacingRate             int                    `json:"fqPacingRate"`
-	GeoProvider              int                    `json:"geoProvider"`
-	GlobalMaxMBPS            int                    `json:"globalMaxMbps"`
-	GlobalMaxTPS             int                    `json:"globalMaxTps"`
-	HTTPBypassFQDN           string                 `json:"httpBypassFqdn"`
-	ID                       int                    `json:"id"`
-	InfoURL                  string                 `json:"infoUrl"`
-	InitialDispersion        float32                `json:"initialDispersion"`
-	IPV6RoutingEnabled       bool                   `json:"ipv6RoutingEnabled"`
-	LastUpdated              *TimeNoMod             `json:"lastUpdated" db:"last_updated"`
-	LogsEnabled              bool                   `json:"logsEnabled"`
-	LongDesc                 string                 `json:"longDesc"`
-	LongDesc1                string                 `json:"longDesc1"`
-	LongDesc2                string                 `json:"longDesc2"`
-	MatchList                []DeliveryServiceMatch `json:"matchList,omitempty"`
-	MaxDNSAnswers            int                    `json:"maxDnsAnswers"`
-	MidHeaderRewrite         string                 `json:"midHeaderRewrite"`
-	MissLat                  float64                `json:"missLat"`
-	MissLong                 float64                `json:"missLong"`
-	MultiSiteOrigin          bool                   `json:"multiSiteOrigin"`
-	OrgServerFQDN            string                 `json:"orgServerFqdn"`
-	ProfileDesc              string                 `json:"profileDescription"`
-	ProfileID                int                    `json:"profileId,omitempty"`
-	ProfileName              string                 `json:"profileName"`
-	Protocol                 int                    `json:"protocol"`
-	QStringIgnore            int                    `json:"qstringIgnore"`
-	RangeRequestHandling     int                    `json:"rangeRequestHandling"`
-	RegexRemap               string                 `json:"regexRemap"`
-	RegionalGeoBlocking      bool                   `json:"regionalGeoBlocking"`
-	RemapText                string                 `json:"remapText"`
-	RoutingName              string                 `json:"routingName"`
-	SigningAlgorithm         string                 `json:"signingAlgorithm" db:"signing_algorithm"`
-	TypeID                   int                    `json:"typeId"`
-	Type                     string                 `json:"type"`
-	TRResponseHeaders        string                 `json:"trResponseHeaders"`
-	TenantID                 int                    `json:"tenantId,omitempty"`
-	XMLID                    string                 `json:"xmlId"`
+	Active               bool                   `json:"active"`
+	CacheURL             string                 `json:"cacheurl"`
+	CCRDNSTTL            int                    `json:"ccrDnsTtl"`
+	CDNID                int                    `json:"cdnId"`
+	CDNName              string                 `json:"cdnName"`
+	CheckPath            string                 `json:"checkPath"`
+	DeepCachingType      DeepCachingType        `json:"deepCachingType"`
+	DisplayName          string                 `json:"displayName"`
+	DNSBypassCname       string                 `json:"dnsBypassCname"`
+	DNSBypassIP          string                 `json:"dnsBypassIp"`
+	DNSBypassIP6         string                 `json:"dnsBypassIp6"`
+	DNSBypassTTL         int                    `json:"dnsBypassTtl"`
+	DSCP                 int                    `json:"dscp"`
+	EdgeHeaderRewrite    string                 `json:"edgeHeaderRewrite"`
+	ExampleURLs          []string               `json:"exampleURLs"`
+	GeoLimit             int                    `json:"geoLimit"`
+	FQPacingRate         int                    `json:"fqPacingRate"`
+	GeoProvider          int                    `json:"geoProvider"`
+	GlobalMaxMBPS        int                    `json:"globalMaxMbps"`
+	GlobalMaxTPS         int                    `json:"globalMaxTps"`
+	HTTPBypassFQDN       string                 `json:"httpBypassFqdn"`
+	ID                   int                    `json:"id"`
+	InfoURL              string                 `json:"infoUrl"`
+	InitialDispersion    float32                `json:"initialDispersion"`
+	IPV6RoutingEnabled   bool                   `json:"ipv6RoutingEnabled"`
+	LastUpdated          *TimeNoMod             `json:"lastUpdated" db:"last_updated"`
+	LogsEnabled          bool                   `json:"logsEnabled"`
+	LongDesc             string                 `json:"longDesc"`
+	LongDesc1            string                 `json:"longDesc1"`
+	LongDesc2            string                 `json:"longDesc2"`
+	MatchList            []DeliveryServiceMatch `json:"matchList,omitempty"`
+	MaxDNSAnswers        int                    `json:"maxDnsAnswers"`
+	MidHeaderRewrite     string                 `json:"midHeaderRewrite"`
+	MissLat              float64                `json:"missLat"`
+	MissLong             float64                `json:"missLong"`
+	MultiSiteOrigin      bool                   `json:"multiSiteOrigin"`
+	OrgServerFQDN        string                 `json:"orgServerFqdn"`
+	ProfileDesc          string                 `json:"profileDescription"`
+	ProfileID            int                    `json:"profileId,omitempty"`
+	ProfileName          string                 `json:"profileName"`
+	Protocol             int                    `json:"protocol"`
+	QStringIgnore        int                    `json:"qstringIgnore"`
+	RangeRequestHandling int                    `json:"rangeRequestHandling"`
+	RegexRemap           string                 `json:"regexRemap"`
+	RegionalGeoBlocking  bool                   `json:"regionalGeoBlocking"`
+	RemapText            string                 `json:"remapText"`
+	RoutingName          string                 `json:"routingName"`
+	SigningAlgorithm     string                 `json:"signingAlgorithm" db:"signing_algorithm"`
+	TypeID               int                    `json:"typeId"`
+	Type                 string                 `json:"type"`
+	TRResponseHeaders    string                 `json:"trResponseHeaders"`
+	TenantID             int                    `json:"tenantId,omitempty"`
+	XMLID                string                 `json:"xmlId"`
+}
+
+type DeliveryServiceV12 struct {
+	DeliveryService
+}
+
+type DeliveryServiceV13 struct {
+	DeliveryServiceV12
+	AnonymousBlockingEnabled bool            `json:"anonymousBlockingEnabled"`
+	DeepCachingType          DeepCachingType `json:"deepCachingType"`
+	FQPacingRate             int             `json:"fqPacingRate,omitempty"`
+	SigningAlgorithm         string          `json:"signingAlgorithm" db:"signing_algorithm"`
+	TenantName               string          `json:"tenantName,omitempty"`
+	TRRequestHeaders         string          `json:"trRequestHeaders,omitempty"`
+	TRResponseHeaders        string          `json:"trResponseHeaders,omitempty"`
 }
 
 // DeliveryServiceNullable - a version of the deliveryservice that allows for all fields to be null
+// TODO move contents to DeliveryServiceNullableV12, fix references, and remove
 type DeliveryServiceNullable struct {
 	// NOTE: the db: struct tags are used for testing to map to their equivalent database column (if there is one)
 	//
-	Active                   *bool                   `json:"active" db:"active"`
-	AnonymousBlockingEnabled *bool                   `json:"anonymousBlockingEnabled" db:"anonymous_blocking_enabled"`
-	CacheURL                 *string                 `json:"cacheurl" db:"cacheurl"`
-	CCRDNSTTL                *int                    `json:"ccrDnsTtl" db:"ccr_dns_ttl"`
-	CDNID                    *int                    `json:"cdnId" db:"cdn_id"`
-	CDNName                  *string                 `json:"cdnName"`
-	CheckPath                *string                 `json:"checkPath" db:"check_path"`
-	DeepCachingType          *DeepCachingType        `json:"deepCachingType" db:"deep_caching_type"`
-	DisplayName              *string                 `json:"displayName" db:"display_name"`
-	DNSBypassCNAME           *string                 `json:"dnsBypassCname" db:"dns_bypass_cname"`
-	DNSBypassIP              *string                 `json:"dnsBypassIp" db:"dns_bypass_ip"`
-	DNSBypassIP6             *string                 `json:"dnsBypassIp6" db:"dns_bypass_ip6"`
-	DNSBypassTTL             *int                    `json:"dnsBypassTtl" db:"dns_bypass_ttl"`
-	DSCP                     *int                    `json:"dscp" db:"dscp"`
-	EdgeHeaderRewrite        *string                 `json:"edgeHeaderRewrite" db:"edge_header_rewrite"`
-	FQPacingRate             *int                    `json:"fqPacingRate" db:"fq_pacing_rate"`
-	GeoLimit                 *int                    `json:"geoLimit" db:"geo_limit"`
-	GeoLimitCountries        *string                 `json:"geoLimitCountries" db:"geo_limit_countries"`
-	GeoLimitRedirectURL      *string                 `json:"geoLimitRedirectURL" db:"geolimit_redirect_url"`
-	GeoProvider              *int                    `json:"geoProvider" db:"geo_provider"`
-	GlobalMaxMBPS            *int                    `json:"globalMaxMbps" db:"global_max_mbps"`
-	GlobalMaxTPS             *int                    `json:"globalMaxTps" db:"global_max_tps"`
-	HTTPBypassFQDN           *string                 `json:"httpBypassFqdn" db:"http_bypass_fqdn"`
-	ID                       *int                    `json:"id" db:"id"`
-	InfoURL                  *string                 `json:"infoUrl" db:"info_url"`
-	InitialDispersion        *int                    `json:"initialDispersion" db:"initial_dispersion"`
-	IPV6RoutingEnabled       *bool                   `json:"ipv6RoutingEnabled" db:"ipv6_routing_enabled"`
-	LastUpdated              *TimeNoMod              `json:"lastUpdated" db:"last_updated"`
-	LogsEnabled              *bool                   `json:"logsEnabled" db:"logs_enabled"`
-	LongDesc                 *string                 `json:"longDesc" db:"long_desc"`
-	LongDesc1                *string                 `json:"longDesc1" db:"long_desc_1"`
-	LongDesc2                *string                 `json:"longDesc2" db:"long_desc_2"`
-	MatchList                *[]DeliveryServiceMatch `json:"matchList"`
-	MaxDNSAnswers            *int                    `json:"maxDnsAnswers" db:"max_dns_answers"`
-	MidHeaderRewrite         *string                 `json:"midHeaderRewrite" db:"mid_header_rewrite"`
-	MissLat                  *float64                `json:"missLat" db:"miss_lat"`
-	MissLong                 *float64                `json:"missLong" db:"miss_long"`
-	MultiSiteOrigin          *bool                   `json:"multiSiteOrigin" db:"multi_site_origin"`
-	MultiSiteOriginAlgorithm *int                    `json:"multiSiteOriginAlgorithm" db:"multi_site_origin_algorithm"`
-	OriginShield             *string                 `json:"originShield" db:"origin_shield"`
-	OrgServerFQDN            *string                 `json:"orgServerFqdn" db:"org_server_fqdn"`
-	ProfileDesc              *string                 `json:"profileDescription"`
-	ProfileID                *int                    `json:"profileId" db:"profile"`
-	ProfileName              *string                 `json:"profileName"`
-	Protocol                 *int                    `json:"protocol" db:"protocol"`
-	QStringIgnore            *int                    `json:"qstringIgnore" db:"qstring_ignore"`
-	RangeRequestHandling     *int                    `json:"rangeRequestHandling" db:"range_request_handling"`
-	RegexRemap               *string                 `json:"regexRemap" db:"regex_remap"`
-	RegionalGeoBlocking      *bool                   `json:"regionalGeoBlocking" db:"regional_geo_blocking"`
-	RemapText                *string                 `json:"remapText" db:"remap_text"`
-	RoutingName              *string                 `json:"routingName" db:"routing_name"`
-	SigningAlgorithm         *string                 `json:"signingAlgorithm" db:"signing_algorithm"`
-	SSLKeyVersion            *int                    `json:"sslKeyVersion" db:"ssl_key_version"`
-	TRRequestHeaders         *string                 `json:"trRequestHeaders" db:"tr_request_headers"`
-	TRResponseHeaders        *string                 `json:"trResponseHeaders" db:"tr_response_headers"`
-	TenantID                 *int                    `json:"tenantId" db:"tenant_id"`
-	TypeName                 *string                 `json:"typeName"`
-	TypeID                   *int                    `json:"typeId" db:"type"`
-	XMLID                    *string                 `json:"xmlId" db:"xml_id"`
+	Active               *bool                   `json:"active" db:"active"`
+	CacheURL             *string                 `json:"cacheurl" db:"cacheurl"`
+	CCRDNSTTL            *int                    `json:"ccrDnsTtl" db:"ccr_dns_ttl"`
+	CDNID                *int                    `json:"cdnId" db:"cdn_id"`
+	CDNName              *string                 `json:"cdnName"`
+	CheckPath            *string                 `json:"checkPath" db:"check_path"`
+	DisplayName          *string                 `json:"displayName" db:"display_name"`
+	DNSBypassCNAME       *string                 `json:"dnsBypassCname" db:"dns_bypass_cname"`
+	DNSBypassIP          *string                 `json:"dnsBypassIp" db:"dns_bypass_ip"`
+	DNSBypassIP6         *string                 `json:"dnsBypassIp6" db:"dns_bypass_ip6"`
+	DNSBypassTTL         *int                    `json:"dnsBypassTtl" db:"dns_bypass_ttl"`
+	DSCP                 *int                    `json:"dscp" db:"dscp"`
+	EdgeHeaderRewrite    *string                 `json:"edgeHeaderRewrite" db:"edge_header_rewrite"`
+	FQPacingRate         *int                    `json:"fqPacingRate" db:"fq_pacing_rate"`
+	GeoLimit             *int                    `json:"geoLimit" db:"geo_limit"`
+	GeoLimitCountries    *string                 `json:"geoLimitCountries" db:"geo_limit_countries"`
+	GeoLimitRedirectURL  *string                 `json:"geoLimitRedirectURL" db:"geolimit_redirect_url"`
+	GeoProvider          *int                    `json:"geoProvider" db:"geo_provider"`
+	GlobalMaxMBPS        *int                    `json:"globalMaxMbps" db:"global_max_mbps"`
+	GlobalMaxTPS         *int                    `json:"globalMaxTps" db:"global_max_tps"`
+	HTTPBypassFQDN       *string                 `json:"httpBypassFqdn" db:"http_bypass_fqdn"`
+	ID                   *int                    `json:"id" db:"id"`
+	InfoURL              *string                 `json:"infoUrl" db:"info_url"`
+	InitialDispersion    *int                    `json:"initialDispersion" db:"initial_dispersion"`
+	IPV6RoutingEnabled   *bool                   `json:"ipv6RoutingEnabled" db:"ipv6_routing_enabled"`
+	LastUpdated          *TimeNoMod              `json:"lastUpdated" db:"last_updated"`
+	LogsEnabled          *bool                   `json:"logsEnabled" db:"logs_enabled"`
+	LongDesc             *string                 `json:"longDesc" db:"long_desc"`
+	LongDesc1            *string                 `json:"longDesc1" db:"long_desc_1"`
+	LongDesc2            *string                 `json:"longDesc2" db:"long_desc_2"`
+	MatchList            *[]DeliveryServiceMatch `json:"matchList"`
+	MaxDNSAnswers        *int                    `json:"maxDnsAnswers" db:"max_dns_answers"`
+	MidHeaderRewrite     *string                 `json:"midHeaderRewrite" db:"mid_header_rewrite"`
+	MissLat              *float64                `json:"missLat" db:"miss_lat"`
+	MissLong             *float64                `json:"missLong" db:"miss_long"`
+	MultiSiteOrigin      *bool                   `json:"multiSiteOrigin" db:"multi_site_origin"`
+	OriginShield         *string                 `json:"originShield" db:"origin_shield"`
+	OrgServerFQDN        *string                 `json:"orgServerFqdn" db:"org_server_fqdn"`
+	ProfileDesc          *string                 `json:"profileDescription"`
+	ProfileID            *int                    `json:"profileId" db:"profile"`
+	ProfileName          *string                 `json:"profileName"`
+	Protocol             *int                    `json:"protocol" db:"protocol"`
+	QStringIgnore        *int                    `json:"qstringIgnore" db:"qstring_ignore"`
+	RangeRequestHandling *int                    `json:"rangeRequestHandling" db:"range_request_handling"`
+	RegexRemap           *string                 `json:"regexRemap" db:"regex_remap"`
+	RegionalGeoBlocking  *bool                   `json:"regionalGeoBlocking" db:"regional_geo_blocking"`
+	RemapText            *string                 `json:"remapText" db:"remap_text"`
+	RoutingName          *string                 `json:"routingName" db:"routing_name"`
+	Signed               bool                    `json:"signed"`
+	SSLKeyVersion        *int                    `json:"sslKeyVersion" db:"ssl_key_version"`
+	TenantID             *int                    `json:"tenantId" db:"tenant_id"`
+	Type                 *string                 `json:"type"`
+	TypeID               *int                    `json:"typeId" db:"type"`
+	XMLID                *string                 `json:"xmlId" db:"xml_id"`
+	ExampleURLs          []string                `json:"exampleURLs"`
+}
+
+type DeliveryServiceNullableV12 struct {
+	DeliveryServiceNullable
+}
+
+type DeliveryServiceNullableV13 struct {
+	DeliveryServiceNullableV12
+	AnonymousBlockingEnabled *bool            `json:"anonymousBlockingEnabled" db:"anonymous_blocking_enabled"`
+	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"`
 }
 
 // Value implements the driver.Valuer interface
@@ -282,5 +309,3 @@ type DeliveryServiceRouting struct {
 	RegionalAlternate int     `json:"regionalAlternate"`
 	RegionalDenied    int     `json:"regionalDenied"`
 }
-
-
diff --git a/traffic_ops/app/lib/TrafficOpsRoutes.pm b/traffic_ops/app/lib/TrafficOpsRoutes.pm
index 5c944b9..9ed303c 100644
--- a/traffic_ops/app/lib/TrafficOpsRoutes.pm
+++ b/traffic_ops/app/lib/TrafficOpsRoutes.pm
@@ -501,12 +501,7 @@ sub api_routes {
 
 	# -- DELIVERYSERVICES
 	# -- DELIVERYSERVICES: CRUD
-	$r->get("/api/$version/deliveryservices")->over( authenticated => 1, not_ldap => 1 )->to( 'Deliveryservice#index', namespace => $namespace );
-	$r->get( "/api/$version/deliveryservices/:id" => [ id => qr/\d+/ ] )->over( authenticated => 1, not_ldap => 1 )->to( 'Deliveryservice#show', namespace => $namespace );
-	$r->post("/api/$version/deliveryservices")->over( authenticated => 1, not_ldap => 1 )->to( 'Deliveryservice#create', namespace => $namespace );
-	$r->put("/api/$version/deliveryservices/:id" => [ id => qr/\d+/ ] )->over( authenticated => 1, not_ldap => 1 )->to( 'Deliveryservice#update', namespace => $namespace );
 	$r->put("/api/$version/deliveryservices/:id/safe" => [ id => qr/\d+/ ] )->over( authenticated => 1, not_ldap => 1 )->to( 'Deliveryservice#safe_update', namespace => $namespace );
-	$r->delete("/api/$version/deliveryservices/:id" => [ id => qr/\d+/ ] )->over( authenticated => 1, not_ldap => 1 )->to( 'Deliveryservice#delete', namespace => $namespace );
 
 	# get all delivery services associated with a server (from deliveryservice_server table)
 	$r->get( "/api/$version/servers/:id/deliveryservices" => [ id => qr/\d+/ ] )->over( authenticated => 1, not_ldap => 1 )->to( 'Deliveryservice#get_deliveryservices_by_serverId', namespace => $namespace );
diff --git a/traffic_ops/traffic_ops_golang/api/change_log.go b/traffic_ops/traffic_ops_golang/api/change_log.go
index 1b30429..5cbede6 100644
--- a/traffic_ops/traffic_ops_golang/api/change_log.go
+++ b/traffic_ops/traffic_ops_golang/api/change_log.go
@@ -85,9 +85,8 @@ func CreateChangeLogMsg(level string, user auth.CurrentUser, db *sqlx.DB, msg st
 	return nil
 }
 
-func CreateChangeLogRaw(level string, message string, user auth.CurrentUser, db *sql.DB) error {
-	if _, err := db.Exec(`INSERT INTO log (level, message, tm_user) VALUES ($1, $2, $3)`, level, message, user.ID); err != nil {
-		return fmt.Errorf("inserting change log level '%v' message '%v' user '%v': %v", level, message, user.ID, err)
+func CreateChangeLogRaw(level string, msg string, user auth.CurrentUser, db *sql.DB) {
+	if _, err := db.Exec(`INSERT INTO log (level, message, tm_user) VALUES ($1, $2, $3)`, level, msg, user.ID); err != nil {
+		log.Errorln("Inserting change log level '" + level + "' message '" + msg + "' user '" + user.UserName + "': " + err.Error())
 	}
-	return nil
 }
diff --git a/traffic_ops/traffic_ops_golang/api/shared_handlers.go b/traffic_ops/traffic_ops_golang/api/shared_handlers.go
index 408bab8..9ec3cb9 100644
--- a/traffic_ops/traffic_ops_golang/api/shared_handlers.go
+++ b/traffic_ops/traffic_ops_golang/api/shared_handlers.go
@@ -103,14 +103,9 @@ func GetCombinedParams(r *http.Request) (map[string]string, error) {
 //      we lose the ability to unmarshal the struct if a struct implementing the interface is passed in,
 //      because when when it is de-referenced it is a pointer to an interface. A new copy is created so that
 //      there are no issues with concurrent goroutines
-func decodeAndValidateRequestBody(r *http.Request, v Validator, db *sqlx.DB) (interface{}, []error) {
-	typ := reflect.TypeOf(v)
-	if typ.Kind() == reflect.Ptr {
-		typ = typ.Elem()
-	}
-	payload := reflect.New(typ).Interface()
+func decodeAndValidateRequestBody(r *http.Request, v Validator, db *sqlx.DB, user auth.CurrentUser) (interface{}, []error) {
+	payload := reflect.Indirect(reflect.ValueOf(v)).Addr().Interface() // does a shallow copy v's internal struct members
 	defer r.Body.Close()
-
 	if err := json.NewDecoder(r.Body).Decode(payload); err != nil {
 		return nil, []error{err}
 	}
@@ -175,17 +170,6 @@ func UpdateHandler(typeRef Updater, db *sqlx.DB) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		//create error function with ResponseWriter and Request
 		handleErrs := tc.GetHandleErrorsFunc(w, r)
-		//create local instance of the shared typeRef pointer
-		//no operations should be made on the typeRef
-		//decode the body and validate the request struct
-		decoded, errs := decodeAndValidateRequestBody(r, typeRef, db)
-		if len(errs) > 0 {
-			handleErrs(http.StatusBadRequest, errs...)
-			return
-		}
-		u := decoded.(Updater)
-		//now we have a validated local object to update
-
 		//collect path parameters and user from context
 		ctx := r.Context()
 		params, err := GetCombinedParams(r)
@@ -201,11 +185,23 @@ func UpdateHandler(typeRef Updater, db *sqlx.DB) http.HandlerFunc {
 			return
 		}
 
+		//create local instance of the shared typeRef pointer
+		//no operations should be made on the typeRef
+		//decode the body and validate the request struct
+		decoded, errs := decodeAndValidateRequestBody(r, typeRef, db, *user)
+		if len(errs) > 0 {
+			handleErrs(http.StatusBadRequest, errs...)
+			return
+		}
+		u := decoded.(Updater)
+		//now we have a validated local object to update
+
 		keyFields := u.GetKeyFieldsInfo() //expecting a slice of the key fields info which is a struct with the field name and a function to convert a string into a {}interface of the right type. in most that will be [{Field:"id",Func: func(s string)({}interface,error){return strconv.Atoi(s)}}]
 		keys, ok := u.GetKeys()           // a map of keyField to keyValue where keyValue is an {}interface
 		if !ok {
 			log.Errorf("unable to parse keys from request: %++v", u)
 			handleErrs(http.StatusBadRequest, errors.New("unable to parse required keys from request body"))
+			return // TODO verify?
 		}
 		for _, keyFieldInfo := range keyFields {
 			paramKey := params[keyFieldInfo.Field]
@@ -219,6 +215,7 @@ func UpdateHandler(typeRef Updater, db *sqlx.DB) http.HandlerFunc {
 			if err != nil {
 				log.Errorf("failed to parse key %s: %s", keyFieldInfo.Field, err)
 				handleErrs(http.StatusBadRequest, errors.New("failed to parse key: "+keyFieldInfo.Field))
+				return
 			}
 
 			if paramValue != keys[keyFieldInfo.Field] {
@@ -252,7 +249,7 @@ func UpdateHandler(typeRef Updater, db *sqlx.DB) http.HandlerFunc {
 		resp := struct {
 			Response interface{} `json:"response"`
 			tc.Alerts
-		}{u, tc.CreateAlerts(tc.SuccessLevel, u.GetType()+" was updated.")}
+		}{[]interface{}{u}, tc.CreateAlerts(tc.SuccessLevel, u.GetType()+" was updated.")}
 
 		respBts, err := json.Marshal(resp)
 		if err != nil {
@@ -361,8 +358,16 @@ func CreateHandler(typeRef Creator, db *sqlx.DB) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		handleErrs := tc.GetHandleErrorsFunc(w, r)
 
+		ctx := r.Context()
+		user, err := auth.GetCurrentUser(ctx)
+		if err != nil {
+			log.Errorf("unable to retrieve current user from context: %s", err)
+			handleErrs(http.StatusInternalServerError, err)
+			return
+		}
+
 		//decode the body and validate the request struct
-		decoded, errs := decodeAndValidateRequestBody(r, typeRef, db)
+		decoded, errs := decodeAndValidateRequestBody(r, typeRef, db, *user)
 		if len(errs) > 0 {
 			handleErrs(http.StatusBadRequest, errs...)
 			return
@@ -371,14 +376,6 @@ func CreateHandler(typeRef Creator, db *sqlx.DB) http.HandlerFunc {
 		log.Debugf("%++v", i)
 		//now we have a validated local object to insert
 
-		ctx := r.Context()
-		user, err := auth.GetCurrentUser(ctx)
-		if err != nil {
-			log.Errorf("unable to retrieve current user from context: %s", err)
-			handleErrs(http.StatusInternalServerError, err)
-			return
-		}
-
 		// if the object has tenancy enabled, check that user is able to access the tenant
 		if t, ok := i.(Tenantable); ok {
 			authorized, err := t.IsTenantAuthorized(*user, db)
diff --git a/traffic_ops/traffic_ops_golang/crconfig/handler.go b/traffic_ops/traffic_ops_golang/crconfig/handler.go
index 6822b60..6dfd461 100644
--- a/traffic_ops/traffic_ops_golang/crconfig/handler.go
+++ b/traffic_ops/traffic_ops_golang/crconfig/handler.go
@@ -193,9 +193,7 @@ func SnapshotHandler(db *sqlx.DB, cfg config.Config) http.HandlerFunc {
 			handleErrs(http.StatusInternalServerError, err)
 			return
 		}
-		if err := api.CreateChangeLogRaw(api.ApiChange, "Snapshot of CRConfig performed for "+cdn, *user, db.DB); err != nil {
-			log.Errorln("creating CRConfig snapshot changelog: " + err.Error())
-		}
+		api.CreateChangeLogRaw(api.ApiChange, "Snapshot of CRConfig performed for "+cdn, *user, db.DB)
 		w.WriteHeader(http.StatusOK) // TODO change to 204 No Content in new version
 	}
 }
@@ -240,9 +238,7 @@ func SnapshotOldGUIHandler(db *sqlx.DB, cfg config.Config) http.HandlerFunc {
 			writePerlHTMLErr(w, r, err)
 			return
 		}
-		if err := api.CreateChangeLogRaw(api.ApiChange, "Snapshot of CRConfig performed for "+cdn, *user, db.DB); err != nil {
-			log.Errorln("creating CRConfig snapshot changelog: " + err.Error())
-		}
+		api.CreateChangeLogRaw(api.ApiChange, "Snapshot of CRConfig performed for "+cdn, *user, db.DB)
 		http.Redirect(w, r, "/tools/flash_and_close/"+url.PathEscape("Successfully wrote the CRConfig.json!"), http.StatusFound)
 	}
 }
diff --git a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
index 0c8774b..3349504 100644
--- a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
+++ b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
@@ -20,12 +20,14 @@ package dbhelpers
  */
 
 import (
+	"database/sql"
 	"errors"
 	"strings"
 
 	"github.com/apache/incubator-trafficcontrol/lib/go-log"
 	"github.com/apache/incubator-trafficcontrol/lib/go-tc"
 
+	"github.com/jmoiron/sqlx"
 	"github.com/lib/pq"
 )
 
@@ -118,3 +120,27 @@ func ParsePQUniqueConstraintError(err *pq.Error) (error, tc.ApiErrorType) {
 	log.Error.Printf("failed to parse unique constraint from pq error: %v", err)
 	return tc.DBError, tc.SystemError
 }
+
+// FinishTx commits the transaction if commit is true when it's called, otherwise it rolls back the transaction. This is designed to be called in a defer.
+func FinishTx(tx *sql.Tx, commit *bool) {
+	if tx == nil {
+		return
+	}
+	if !*commit {
+		tx.Rollback()
+		return
+	}
+	tx.Commit()
+}
+
+// FinishTxX commits the transaction if commit is true when it's called, otherwise it rolls back the transaction. This is designed to be called in a defer.
+func FinishTxX(tx *sqlx.Tx, commit *bool) {
+	if tx == nil {
+		return
+	}
+	if !*commit {
+		tx.Rollback()
+		return
+	}
+	tx.Commit()
+}
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go
deleted file mode 100644
index 9df72a9..0000000
--- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go
+++ /dev/null
@@ -1,406 +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 (
-	"errors"
-	"fmt"
-	"regexp"
-	"strings"
-
-	"github.com/apache/incubator-trafficcontrol/lib/go-log"
-	"github.com/apache/incubator-trafficcontrol/lib/go-tc"
-	"github.com/asaskevich/govalidator"
-	validation "github.com/go-ozzo/ozzo-validation"
-	"github.com/go-ozzo/ozzo-validation/is"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
-	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/auth"
-	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
-	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
-	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/tovalidate"
-	"github.com/jmoiron/sqlx"
-	"github.com/lib/pq"
-)
-
-//we need a type alias to define functions on
-type TODeliveryService tc.DeliveryServiceNullable
-
-//the refType is passed into the handlers where a copy of its type is used to decode the json.
-var refType = TODeliveryService(tc.DeliveryServiceNullable{})
-
-func GetRefType() *TODeliveryService {
-	return &refType
-}
-
-func (ds TODeliveryService) GetKeyFieldsInfo() []api.KeyFieldInfo {
-	return []api.KeyFieldInfo{{"id", api.GetIntKey}}
-}
-
-//Implementation of the Identifier, Validator interface functions
-func (ds TODeliveryService) GetKeys() (map[string]interface{}, bool) {
-	if ds.ID == nil {
-		return map[string]interface{}{"id": 0}, false
-	}
-	return map[string]interface{}{"id": *ds.ID}, true
-}
-
-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 *TODeliveryService) GetAuditName() string {
-	if ds.XMLID != nil {
-		return *ds.XMLID
-	}
-	return ""
-}
-
-func (ds *TODeliveryService) GetType() string {
-	return "ds"
-}
-
-func Validate(db *sqlx.DB, ds *tc.DeliveryServiceNullable) []error {
-	if ds == nil {
-		return []error{}
-	}
-	tods := TODeliveryService(*ds)
-	return tods.Validate(db)
-}
-
-func (ds *TODeliveryService) Validate(db *sqlx.DB) []error {
-
-	// Custom Examples:
-	// Just add isCIDR as a parameter to Validate()
-	// isCIDR := validation.NewStringRule(govalidator.IsCIDR, "must be a valid CIDR address")
-	isHost := validation.NewStringRule(govalidator.IsHost, "must be a valid hostname")
-	noPeriods := validation.NewStringRule(tovalidate.NoPeriods, "cannot contain periods")
-	noSpaces := validation.NewStringRule(tovalidate.NoSpaces, "cannot contain spaces")
-	neverOrAlways := validation.NewStringRule(tovalidate.IsOneOfStringICase("NEVER", "ALWAYS"),
-		"must be one of 'NEVER' or 'ALWAYS'")
-
-	// Validate that the required fields are sent first to prevent panics below
-	errs := validation.Errors{
-		"active":                   validation.Validate(ds.Active, validation.NotNil),
-		"anonymousBlockingEnabled": validation.Validate(ds.AnonymousBlockingEnabled, validation.NotNil),
-		"cdnId":                    validation.Validate(ds.CDNID, validation.Required),
-		"displayName":              validation.Validate(ds.DisplayName, validation.Required, validation.Length(1, 48)),
-		"deepCachingType":          validation.Validate(neverOrAlways),
-		"dnsBypassIp":              validation.Validate(ds.DNSBypassIP, is.IP),
-		"dnsBypassIp6":             validation.Validate(ds.DNSBypassIP6, is.IPv6),
-		"dscp":                     validation.Validate(ds.DSCP, validation.NotNil, validation.Min(0)),
-		"geoLimit":                 validation.Validate(ds.GeoLimit, validation.NotNil),
-		"geoProvider":              validation.Validate(ds.GeoProvider, validation.NotNil),
-		"infoUrl":                  validation.Validate(ds.InfoURL, is.URL),
-		"logsEnabled":              validation.Validate(ds.LogsEnabled, validation.NotNil),
-		"orgServerFqdn":            validation.Validate(ds.OrgServerFQDN, is.URL),
-		"regionalGeoBlocking":      validation.Validate(ds.RegionalGeoBlocking, validation.NotNil),
-		"routingName":              validation.Validate(ds.RoutingName, isHost, noPeriods, validation.Length(1, 48)),
-		"typeId":                   validation.Validate(ds.TypeID, validation.Required, validation.Min(1)),
-		"xmlId":                    validation.Validate(ds.XMLID, noSpaces, noPeriods, validation.Length(1, 48)),
-	}
-
-	if errs != nil {
-		return tovalidate.ToErrors(errs)
-	}
-
-	errsResponse := ds.validateTypeFields(db)
-	if errsResponse != nil {
-		return errsResponse
-	}
-
-	return nil
-}
-
-func (ds *TODeliveryService) validateTypeFields(db *sqlx.DB) []error {
-	fmt.Printf("validateTypeFields\n")
-	// Validate the TypeName related fields below
-	var typeName string
-	var err error
-	DNSRegexType := "^DNS.*$"
-	HTTPRegexType := "^HTTP.*$"
-	SteeringRegexType := "^STEERING.*$"
-
-	if db != nil && ds.TypeID != nil {
-		typeID := *ds.TypeID
-		typeName, err = getTypeName(db, typeID)
-		if err != nil {
-			return []error{err}
-		}
-	}
-
-	if typeName != "" {
-		errs := validation.Errors{
-			"initialDispersion": validation.Validate(ds.InitialDispersion,
-				validation.By(requiredIfMatchesTypeName([]string{DNSRegexType, HTTPRegexType}, typeName))),
-			"ipv6RoutingEnabled": validation.Validate(ds.IPV6RoutingEnabled,
-				validation.By(requiredIfMatchesTypeName([]string{SteeringRegexType, DNSRegexType, HTTPRegexType}, typeName))),
-			"missLat": validation.Validate(ds.MissLat,
-				validation.By(requiredIfMatchesTypeName([]string{DNSRegexType, HTTPRegexType}, typeName))),
-			"missLong": validation.Validate(ds.MissLong,
-				validation.By(requiredIfMatchesTypeName([]string{DNSRegexType, HTTPRegexType}, typeName))),
-			"multiSiteOrigin": validation.Validate(ds.MultiSiteOrigin,
-				validation.By(requiredIfMatchesTypeName([]string{DNSRegexType, HTTPRegexType}, typeName))),
-			"orgServerFqdn": validation.Validate(ds.OrgServerFQDN,
-				validation.By(requiredIfMatchesTypeName([]string{DNSRegexType, HTTPRegexType}, typeName))),
-			"protocol": validation.Validate(ds.Protocol,
-				validation.By(requiredIfMatchesTypeName([]string{SteeringRegexType, DNSRegexType, HTTPRegexType}, typeName))),
-			"qstringIgnore": validation.Validate(ds.QStringIgnore,
-				validation.By(requiredIfMatchesTypeName([]string{DNSRegexType, HTTPRegexType}, typeName))),
-			"rangeRequestHandling": validation.Validate(ds.RangeRequestHandling,
-				validation.By(requiredIfMatchesTypeName([]string{DNSRegexType, HTTPRegexType}, typeName))),
-		}
-		return tovalidate.ToErrors(errs)
-	}
-	return nil
-}
-
-func requiredIfMatchesTypeName(patterns []string, typeName string) func(interface{}) error {
-	return func(value interface{}) error {
-
-		pattern := strings.Join(patterns, "|")
-		var err error
-		var match bool
-		if typeName != "" {
-			match, err = regexp.MatchString(pattern, typeName)
-			if match {
-				return fmt.Errorf("is required if type is '%s'", typeName)
-			}
-		}
-		return err
-	}
-}
-
-// TODO: drichardson - refactor to the types.go once implemented.
-func getTypeName(db *sqlx.DB, typeID int) (string, error) {
-
-	query := `SELECT name from type where id=$1`
-
-	var rows *sqlx.Rows
-	var err error
-
-	rows, err = db.Queryx(query, typeID)
-	if err != nil {
-		return "", err
-	}
-	defer rows.Close()
-
-	typeResults := []tc.Type{}
-	for rows.Next() {
-		var s tc.Type
-		if err = rows.StructScan(&s); err != nil {
-			return "", fmt.Errorf("getting Type: %v", err)
-		}
-		typeResults = append(typeResults, s)
-	}
-
-	typeName := typeResults[0].Name
-	return typeName, err
-}
-
-//The TODeliveryService implementation of the Updater interface
-//all implementations of Updater should use transactions and return the proper errorType
-//ParsePQUniqueConstraintError is used to determine if a delivery service with conflicting values exists
-//if so, it will return an errorType of DataConflict and the type should be appended to the
-//generic error message returned
-func (ds *TODeliveryService) Update(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErrorType) {
-	tx, err := db.Beginx()
-	defer func() {
-		if tx == nil {
-			return
-		}
-		if err != nil {
-			tx.Rollback()
-			return
-		}
-		tx.Commit()
-	}()
-
-	if err != nil {
-		log.Error.Printf("could not begin transaction: %v", err)
-		return tc.DBError, tc.SystemError
-	}
-	log.Debugf("about to run exec query: %s with ds: %++v", updateDSQuery(), ds)
-	resultRows, err := tx.NamedQuery(updateDSQuery(), ds)
-	if err != nil {
-		if err, ok := err.(*pq.Error); ok {
-			err, eType := dbhelpers.ParsePQUniqueConstraintError(err)
-			if eType == tc.DataConflictError {
-				return errors.New("a delivery service with " + err.Error()), eType
-			}
-			return err, eType
-		}
-		log.Errorf("received error: %++v from update execution", err)
-		return tc.DBError, tc.SystemError
-	}
-	var lastUpdated tc.TimeNoMod
-	rowsAffected := 0
-	for resultRows.Next() {
-		rowsAffected++
-		if err := resultRows.Scan(&lastUpdated); err != nil {
-			log.Error.Printf("could not scan lastUpdated from insert: %s\n", err)
-			return tc.DBError, tc.SystemError
-		}
-	}
-	log.Debugf("lastUpdated: %++v", lastUpdated)
-	ds.LastUpdated = &lastUpdated
-	if rowsAffected != 1 {
-		if rowsAffected < 1 {
-			return errors.New("no delivery service found with this id"), tc.DataMissingError
-		}
-		return fmt.Errorf("this update affected too many rows: %d", rowsAffected), tc.SystemError
-	}
-	return nil, tc.NoError
-}
-
-// Create implements the Creator interface.
-//all implementations of Creator should use transactions and return the proper errorType
-//ParsePQUniqueConstraintError is used to determine if a ds with conflicting values exists
-//if so, it will return an errorType of DataConflict and the type should be appended to the
-//generic error message returned
-//The insert sql returns the id and lastUpdated values of the newly inserted ds and have
-//to be added to the struct
-func (ds *TODeliveryService) Create(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErrorType) {
-	tx, err := db.Beginx()
-	defer func() {
-		if tx == nil {
-			return
-		}
-		if err != nil {
-			tx.Rollback()
-			return
-		}
-		tx.Commit()
-	}()
-
-	if err != nil {
-		log.Error.Printf("could not begin transaction: %v", err)
-		return tc.DBError, tc.SystemError
-	}
-	fmt.Printf("ds ---> %v\n", ds)
-	resultRows, err := tx.NamedQuery(insertDSQuery(), ds)
-	if err != nil {
-		if pqerr, ok := err.(*pq.Error); ok {
-			err, eType := dbhelpers.ParsePQUniqueConstraintError(pqerr)
-			return errors.New("a delivery service with " + err.Error()), eType
-		}
-		log.Errorf("received non pq error: %++v from create execution", err)
-		return tc.DBError, tc.SystemError
-	}
-	var id int
-	var lastUpdated tc.TimeNoMod
-	rowsAffected := 0
-	for resultRows.Next() {
-		rowsAffected++
-		if err := resultRows.Scan(&id, &lastUpdated); err != nil {
-			log.Error.Printf("could not scan id from insert: %s\n", err)
-			return tc.DBError, tc.SystemError
-		}
-	}
-	if rowsAffected == 0 {
-		err = errors.New("no delivery service was inserted, no id was returned")
-		log.Errorln(err)
-		return tc.DBError, tc.SystemError
-	} else if rowsAffected > 1 {
-		err = errors.New("too many ids returned from delivery service insert")
-		log.Errorln(err)
-		return tc.DBError, tc.SystemError
-	}
-	ds.SetKeys(map[string]interface{}{"id": id})
-	ds.LastUpdated = &lastUpdated
-	return nil, tc.NoError
-}
-
-//The DeliveryService implementation of the Deleter interface
-//all implementations of Deleter should use transactions and return the proper errorType
-func (ds *TODeliveryService) Delete(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErrorType) {
-	tx, err := db.Beginx()
-	defer func() {
-		if tx == nil {
-			return
-		}
-		if err != nil {
-			tx.Rollback()
-			return
-		}
-		tx.Commit()
-	}()
-
-	if err != nil {
-		log.Error.Printf("could not begin transaction: %v", err)
-		return tc.DBError, tc.SystemError
-	}
-	log.Debugf("about to run exec query: %s with Delivery Service: %++v", deleteDSQuery(), ds)
-	result, err := tx.NamedExec(deleteDSQuery(), ds)
-	if err != nil {
-		log.Errorf("received error: %++v from delete execution", err)
-		return tc.DBError, tc.SystemError
-	}
-	rowsAffected, err := result.RowsAffected()
-	if err != nil {
-		return tc.DBError, tc.SystemError
-	}
-	if rowsAffected != 1 {
-		if rowsAffected < 1 {
-			return errors.New("no delivery service with that id found"), tc.DataMissingError
-		}
-		return fmt.Errorf("this create affected too many rows: %d", rowsAffected), tc.SystemError
-	}
-	return nil, tc.NoError
-}
-
-// IsTenantAuthorized implements the Tenantable interface to ensure the user is authorized on the deliveryservice tenant
-func (ds *TODeliveryService) IsTenantAuthorized(user auth.CurrentUser, db *sqlx.DB) (bool, error) {
-	if ds.TenantID == nil {
-		log.Debugf("tenantID is nil")
-		return false, errors.New("tenantID is nil")
-	}
-	return tenant.IsResourceAuthorizedToUser(*ds.TenantID, user, db)
-}
-
-//TODO: drichardson - plumb these out!
-func updateDSQuery() string {
-	query := `UPDATE
-cdn SET
-dnssec_enabled=:dnssec_enabled,
-domain_name=:domain_name,
-name=:name
-WHERE id=:id RETURNING last_updated`
-	return query
-}
-
-//TODO: drichardson - plumb these out!
-func insertDSQuery() string {
-	query := `INSERT INTO deliveryservice (
-dnssec_enabled,
-domain_name,
-name) VALUES (
-:dnssec_enabled,
-:domain_name,
-:name) RETURNING id,last_updated`
-	return query
-}
-
-func deleteDSQuery() string {
-	query := `DELETE FROM deliveryservice
-WHERE id=:id`
-	return query
-}
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go
new file mode 100644
index 0000000..c5c7ca8
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go
@@ -0,0 +1,433 @@
+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 (
+	"database/sql"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"regexp"
+	"strings"
+
+	"github.com/apache/incubator-trafficcontrol/lib/go-log"
+	"github.com/apache/incubator-trafficcontrol/lib/go-tc"
+	"github.com/apache/incubator-trafficcontrol/lib/go-util"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/auth"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/config"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/tovalidate"
+
+	"github.com/asaskevich/govalidator"
+	"github.com/go-ozzo/ozzo-validation"
+	"github.com/jmoiron/sqlx"
+)
+
+type TODeliveryServiceV12 struct {
+	tc.DeliveryServiceNullableV12
+	Cfg config.Config
+	DB  *sqlx.DB
+}
+
+func GetRefTypeV12(cfg config.Config, db *sqlx.DB) *TODeliveryServiceV12 {
+	return &TODeliveryServiceV12{Cfg: cfg, DB: db}
+}
+
+func (ds TODeliveryServiceV12) GetKeyFieldsInfo() []api.KeyFieldInfo {
+	return []api.KeyFieldInfo{{"id", api.GetIntKey}}
+}
+
+func (ds TODeliveryServiceV12) GetKeys() (map[string]interface{}, bool) {
+	if ds.ID == nil {
+		return map[string]interface{}{"id": 0}, false
+	}
+	return map[string]interface{}{"id": *ds.ID}, true
+}
+
+func (ds *TODeliveryServiceV12) 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 *TODeliveryServiceV12) GetAuditName() string {
+	if ds.XMLID != nil {
+		return *ds.XMLID
+	}
+	return ""
+}
+
+func (ds *TODeliveryServiceV12) GetType() string {
+	return "ds"
+}
+
+func ValidateV12(db *sqlx.DB, ds *tc.DeliveryServiceNullableV12) []error {
+	if ds == nil {
+		return []error{}
+	}
+	tods := TODeliveryServiceV12{DB: db} // TODO pass config?
+	return tods.Validate(db)
+}
+
+func (ds *TODeliveryServiceV12) Sanitize(db *sqlx.DB) {
+	sanitizeV12(&ds.DeliveryServiceNullableV12)
+}
+
+func sanitizeV12(ds *tc.DeliveryServiceNullableV12) {
+	if ds.GeoLimitCountries != nil {
+		*ds.GeoLimitCountries = strings.ToUpper(strings.Replace(*ds.GeoLimitCountries, " ", "", -1))
+	}
+	if ds.ProfileID != nil && *ds.ProfileID == -1 {
+		ds.ProfileID = nil
+	}
+	if ds.EdgeHeaderRewrite != nil && strings.TrimSpace(*ds.EdgeHeaderRewrite) == "" {
+		ds.EdgeHeaderRewrite = nil
+	}
+	if ds.MidHeaderRewrite != nil && strings.TrimSpace(*ds.MidHeaderRewrite) == "" {
+		ds.MidHeaderRewrite = nil
+	}
+}
+
+// getDSTenantIDByID returns the tenant ID, whether the delivery service exists, and any error.
+// Note the id may be nil, even if true is returned, if the delivery service exists but its tenant_id field is null.
+func getDSTenantIDByID(db *sql.DB, id int) (*int, bool, error) {
+	tenantID := (*int)(nil)
+	if err := db.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.
+// Note the id may be nil, even if true is returned, if the delivery service exists but its tenant_id field is null.
+func getDSTenantIDByName(db *sql.DB, name string) (*int, bool, error) {
+	tenantID := (*int)(nil)
+	if err := db.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
+}
+
+// LoadXMLID 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) LoadXMLID(db *sqlx.DB) (bool, error) {
+	if ds.ID == nil {
+		return false, errors.New("missing ID")
+	}
+
+	xmlID := ""
+	if err := db.QueryRow(`SELECT xml_id FROM deliveryservice where id = $1`, ds.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", *ds.ID, err)
+	}
+	ds.XMLID = &xmlID
+	return 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, db *sqlx.DB) (bool, error) {
+	return isTenantAuthorized(user, db, &ds.DeliveryServiceNullableV12)
+}
+
+func isTenantAuthorized(user auth.CurrentUser, db *sqlx.DB, ds *tc.DeliveryServiceNullableV12) (bool, error) {
+	if ds.ID == nil && ds.XMLID == nil {
+		return false, errors.New("delivery service has no ID or XMLID")
+	}
+
+	existingID, err := (*int)(nil), error(nil)
+	if ds.ID != nil {
+		existingID, _, err = getDSTenantIDByID(db.DB, *ds.ID) // ignore exists return - if the DS is new, we only need to check the user input tenant
+	} else {
+		existingID, _, err = getDSTenantIDByName(db.DB, *ds.XMLID) // ignore exists return - if the DS is new, we only need to check the user input tenant
+	}
+	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.IsResourceAuthorizedToUser(*existingID, user, db)
+		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.IsResourceAuthorizedToUser(*ds.TenantID, user, db)
+		if err != nil {
+			return false, errors.New("checking authorization for new DS ID: " + err.Error())
+		}
+		if !userAuthorizedForNewDSTenant {
+			return false, nil
+		}
+	}
+	return true, nil
+}
+
+func (ds *TODeliveryServiceV12) Validate(db *sqlx.DB) []error {
+	return validateV12(db, &ds.DeliveryServiceNullableV12)
+}
+
+func validateV12(db *sqlx.DB, ds *tc.DeliveryServiceNullableV12) []error {
+	sanitizeV12(ds)
+	// Custom Examples:
+	// Just add isCIDR as a parameter to Validate()
+	// isCIDR := validation.NewStringRule(govalidator.IsCIDR, "must be a valid CIDR address")
+	isDNSName := validation.NewStringRule(govalidator.IsDNSName, "must be a valid hostname")
+	noPeriods := validation.NewStringRule(tovalidate.NoPeriods, "cannot contain periods")
+	noSpaces := validation.NewStringRule(tovalidate.NoSpaces, "cannot contain spaces")
+
+	// Validate that the required fields are sent first to prevent panics below
+	errs := validation.Errors{
+		"active":              validation.Validate(ds.Active, validation.NotNil),
+		"cdnId":               validation.Validate(ds.CDNID, validation.Required),
+		"displayName":         validation.Validate(ds.DisplayName, validation.Required, validation.Length(1, 48)),
+		"dscp":                validation.Validate(ds.DSCP, validation.NotNil, validation.Min(0)),
+		"geoLimit":            validation.Validate(ds.GeoLimit, validation.NotNil),
+		"geoProvider":         validation.Validate(ds.GeoProvider, validation.NotNil),
+		"logsEnabled":         validation.Validate(ds.LogsEnabled, validation.NotNil),
+		"regionalGeoBlocking": validation.Validate(ds.RegionalGeoBlocking, validation.NotNil),
+		"routingName":         validation.Validate(ds.RoutingName, isDNSName, noPeriods, validation.Length(1, 48)),
+		"typeId":              validation.Validate(ds.TypeID, validation.Required, validation.Min(1)),
+		"xmlId":               validation.Validate(ds.XMLID, noSpaces, noPeriods, validation.Length(1, 48)),
+	}
+	if errs != nil {
+		return tovalidate.ToErrors(errs)
+	}
+	errsResponse := validateTypeFields(db, ds)
+	if errsResponse != nil {
+		return errsResponse
+	}
+
+	return nil
+}
+
+func validateTypeFields(db *sqlx.DB, ds *tc.DeliveryServiceNullableV12) []error {
+	fmt.Printf("validateTypeFields\n")
+	// Validate the TypeName related fields below
+	var typeName string
+	var err error
+	DNSRegexType := "^DNS.*$"
+	HTTPRegexType := "^HTTP.*$"
+	SteeringRegexType := "^STEERING.*$"
+
+	if db != nil && ds == nil || ds.TypeID != nil {
+		typeID := *ds.TypeID
+		typeName, err = getTypeName(db, typeID)
+		if err != nil {
+			return []error{err}
+		}
+	}
+
+	if typeName != "" {
+		errs := validation.Errors{
+			"initialDispersion": validation.Validate(ds.InitialDispersion,
+				validation.By(requiredIfMatchesTypeName([]string{DNSRegexType, HTTPRegexType}, typeName))),
+			"ipv6RoutingEnabled": validation.Validate(ds.IPV6RoutingEnabled,
+				validation.By(requiredIfMatchesTypeName([]string{SteeringRegexType, DNSRegexType, HTTPRegexType}, typeName))),
+			"missLat": validation.Validate(ds.MissLat,
+				validation.By(requiredIfMatchesTypeName([]string{DNSRegexType, HTTPRegexType}, typeName))),
+			"missLong": validation.Validate(ds.MissLong,
+				validation.By(requiredIfMatchesTypeName([]string{DNSRegexType, HTTPRegexType}, typeName))),
+			"multiSiteOrigin": validation.Validate(ds.MultiSiteOrigin,
+				validation.By(requiredIfMatchesTypeName([]string{DNSRegexType, HTTPRegexType}, typeName))),
+			"orgServerFqdn": validation.Validate(ds.OrgServerFQDN,
+				validation.By(requiredIfMatchesTypeName([]string{DNSRegexType, HTTPRegexType}, typeName))),
+			"protocol": validation.Validate(ds.Protocol,
+				validation.By(requiredIfMatchesTypeName([]string{SteeringRegexType, DNSRegexType, HTTPRegexType}, typeName))),
+			"qstringIgnore": validation.Validate(ds.QStringIgnore,
+				validation.By(requiredIfMatchesTypeName([]string{DNSRegexType, HTTPRegexType}, typeName))),
+			"rangeRequestHandling": validation.Validate(ds.RangeRequestHandling,
+				validation.By(requiredIfMatchesTypeName([]string{DNSRegexType, HTTPRegexType}, typeName))),
+		}
+		return tovalidate.ToErrors(errs)
+	}
+	return nil
+}
+
+func requiredIfMatchesTypeName(patterns []string, typeName string) func(interface{}) error {
+	return func(value interface{}) error {
+
+		pattern := strings.Join(patterns, "|")
+		var err error
+		var match bool
+		if typeName != "" {
+			match, err = regexp.MatchString(pattern, typeName)
+			if match {
+				return fmt.Errorf("is required if type is '%s'", typeName)
+			}
+		}
+		return err
+	}
+}
+
+// TODO: drichardson - refactor to the types.go once implemented.
+func getTypeName(db *sqlx.DB, typeID int) (string, error) {
+
+	query := `SELECT name from type where id=$1`
+
+	var rows *sqlx.Rows
+	var err error
+
+	rows, err = db.Queryx(query, typeID)
+	if err != nil {
+		return "", err
+	}
+	defer rows.Close()
+
+	typeResults := []tc.Type{}
+	for rows.Next() {
+		var s tc.Type
+		if err = rows.StructScan(&s); err != nil {
+			return "", fmt.Errorf("getting Type: %v", err)
+		}
+		typeResults = append(typeResults, s)
+	}
+
+	typeName := typeResults[0].Name
+	return typeName, err
+}
+
+func CreateV12(db *sqlx.DB, cfg config.Config) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		defer r.Body.Close()
+		user, err := auth.GetCurrentUser(r.Context())
+		if err != nil {
+			api.HandleErr(w, r, http.StatusInternalServerError, nil, errors.New("getting user: "+err.Error()))
+			return
+		}
+
+		ds := tc.DeliveryServiceNullableV12{}
+		if err := json.NewDecoder(r.Body).Decode(&ds); err != nil {
+			api.HandleErr(w, r, http.StatusBadRequest, errors.New("malformed JSON: "+err.Error()), nil)
+			return
+		}
+
+		if errs := validateV12(db, &ds); len(errs) > 0 {
+			api.HandleErr(w, r, http.StatusBadRequest, errors.New("invalid request: "+util.JoinErrs(errs).Error()), nil)
+			return
+		}
+
+		dsv13 := tc.DeliveryServiceNullableV13{DeliveryServiceNullableV12: tc.DeliveryServiceNullableV12(ds)}
+
+		if authorized, err := isTenantAuthorized(*user, db, &ds); err != nil {
+			api.HandleErr(w, r, http.StatusInternalServerError, nil, errors.New("checking tenant: "+err.Error()))
+			return
+		} else if !authorized {
+			api.HandleErr(w, r, http.StatusForbidden, errors.New("not authorized on this tenant"), nil)
+			return
+		}
+
+		dsv13, errCode, userErr, sysErr := create(db.DB, cfg, user, dsv13)
+		if userErr != nil || sysErr != nil {
+			api.HandleErr(w, r, errCode, userErr, sysErr)
+			return
+		}
+		api.WriteResp(w, r, []tc.DeliveryServiceNullableV12{dsv13.DeliveryServiceNullableV12})
+	}
+}
+
+func (ds *TODeliveryServiceV12) Read(db *sqlx.DB, params map[string]string, user auth.CurrentUser) ([]interface{}, []error, tc.ApiErrorType) {
+	returnable := []interface{}{}
+	dses, errs, errType := readGetDeliveryServices(params, db)
+	if len(errs) > 0 {
+		for _, err := range errs {
+			if err.Error() == `id cannot parse to integer` {
+				return nil, []error{errors.New("Resource not found.")}, tc.DataMissingError //matches perl response
+			}
+		}
+		return nil, errs, errType
+	}
+
+	dses, err := filterAuthorized(dses, user, db)
+	if err != nil {
+		log.Errorln("Checking tenancy: " + err.Error())
+		return nil, []error{errors.New("Error checking tenancy.")}, tc.SystemError
+	}
+
+	for _, ds := range dses {
+		returnable = append(returnable, ds.DeliveryServiceNullableV12)
+	}
+	return returnable, nil, tc.NoError
+}
+
+func (ds *TODeliveryServiceV12) Delete(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErrorType) {
+	v13 := &TODeliveryServiceV13{
+		Cfg: ds.Cfg,
+		DB:  ds.DB,
+		DeliveryServiceNullableV13: tc.DeliveryServiceNullableV13{
+			DeliveryServiceNullableV12: ds.DeliveryServiceNullableV12,
+		},
+	}
+	err, errType := v13.Delete(db, user)
+	ds.DeliveryServiceNullableV12 = v13.DeliveryServiceNullableV12 // TODO avoid copy
+	return err, errType
+}
+
+func UpdateV12(db *sqlx.DB, cfg config.Config) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		defer r.Body.Close()
+		user, err := auth.GetCurrentUser(r.Context())
+		if err != nil {
+			api.HandleErr(w, r, http.StatusInternalServerError, nil, errors.New("getting user: "+err.Error()))
+			return
+		}
+
+		ds := tc.DeliveryServiceNullableV12{}
+		if err := json.NewDecoder(r.Body).Decode(&ds); err != nil {
+			api.HandleErr(w, r, http.StatusBadRequest, errors.New("malformed JSON: "+err.Error()), nil)
+			return
+		}
+
+		if errs := validateV12(db, &ds); len(errs) > 0 {
+			api.HandleErr(w, r, http.StatusBadRequest, errors.New("invalid request: "+util.JoinErrs(errs).Error()), nil)
+			return
+		}
+
+		dsv13 := tc.DeliveryServiceNullableV13{DeliveryServiceNullableV12: tc.DeliveryServiceNullableV12(ds)}
+
+		if authorized, err := isTenantAuthorized(*user, db, &ds); err != nil {
+			api.HandleErr(w, r, http.StatusInternalServerError, nil, errors.New("checking tenant: "+err.Error()))
+			return
+		} else if !authorized {
+			api.HandleErr(w, r, http.StatusForbidden, errors.New("not authorized on this tenant"), nil)
+			return
+		}
+
+		dsv13, errCode, userErr, sysErr := update(db.DB, cfg, *user, &dsv13)
+		if userErr != nil || sysErr != nil {
+			api.HandleErr(w, r, errCode, userErr, sysErr)
+			return
+		}
+		api.WriteResp(w, r, []tc.DeliveryServiceNullableV12{dsv13.DeliveryServiceNullableV12})
+	}
+}
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go
new file mode 100644
index 0000000..b8fab5f
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go
@@ -0,0 +1,1119 @@
+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 (
+	"database/sql"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"net/http"
+	"strconv"
+	"strings"
+
+	"github.com/apache/incubator-trafficcontrol/lib/go-log"
+	"github.com/apache/incubator-trafficcontrol/lib/go-tc"
+	"github.com/apache/incubator-trafficcontrol/lib/go-util"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/auth"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/config"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/riaksvc"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/tovalidate"
+
+	"github.com/go-ozzo/ozzo-validation"
+	"github.com/jmoiron/sqlx"
+	"github.com/lib/pq"
+)
+
+//we need a type alias to define functions on
+type TODeliveryServiceV13 struct {
+	tc.DeliveryServiceNullableV13
+	Cfg config.Config
+	DB  *sqlx.DB
+}
+
+func (ds *TODeliveryServiceV13) V12() *TODeliveryServiceV12 {
+	return &TODeliveryServiceV12{DeliveryServiceNullableV12: ds.DeliveryServiceNullableV12, DB: ds.DB, Cfg: ds.Cfg}
+}
+
+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 GetRefTypeV13(cfg config.Config, db *sqlx.DB) *TODeliveryServiceV13 {
+	return &TODeliveryServiceV13{Cfg: cfg, DB: db}
+}
+
+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{}) {
+	ds.V12().SetKeys(keys)
+}
+
+func (ds *TODeliveryServiceV13) GetAuditName() string {
+	return ds.V12().GetAuditName()
+}
+
+func (ds *TODeliveryServiceV13) GetType() string {
+	return ds.V12().GetType()
+}
+
+func ValidateV13(db *sqlx.DB, ds *tc.DeliveryServiceNullableV13) []error {
+	if ds == nil {
+		return []error{}
+	}
+	tods := TODeliveryServiceV13{DeliveryServiceNullableV13: *ds, DB: db} // TODO set Cfg?
+	return tods.Validate(db)
+}
+
+func (ds *TODeliveryServiceV13) Sanitize(db *sqlx.DB) { sanitizeV13(&ds.DeliveryServiceNullableV13) }
+
+func sanitizeV13(ds *tc.DeliveryServiceNullableV13) {
+	sanitizeV12(&ds.DeliveryServiceNullableV12)
+	signedAlgorithm := "url_sig"
+	if ds.Signed && (ds.SigningAlgorithm == nil || *ds.SigningAlgorithm == "") {
+		ds.SigningAlgorithm = &signedAlgorithm
+	}
+	if !ds.Signed && ds.SigningAlgorithm != nil && *ds.SigningAlgorithm == signedAlgorithm {
+		ds.Signed = true
+	}
+	if ds.DeepCachingType == nil {
+		s := tc.DeepCachingType("")
+		ds.DeepCachingType = &s
+	}
+	*ds.DeepCachingType = tc.DeepCachingTypeFromString(string(*ds.DeepCachingType))
+}
+
+func (ds *TODeliveryServiceV13) Validate(db *sqlx.DB) []error {
+	return validateV13(db, &ds.DeliveryServiceNullableV13)
+}
+
+func validateV13(db *sqlx.DB, ds *tc.DeliveryServiceNullableV13) []error {
+	sanitizeV13(ds)
+	neverOrAlways := validation.NewStringRule(tovalidate.IsOneOfStringICase("NEVER", "ALWAYS"),
+		"must be one of 'NEVER' or 'ALWAYS'")
+	errs := tovalidate.ToErrors(validation.Errors{
+		"deepCachingType": validation.Validate(ds.DeepCachingType, neverOrAlways),
+	})
+	oldErrs := validateV12(db, &ds.DeliveryServiceNullableV12)
+	return append(errs, oldErrs...)
+}
+
+// Create implements the Creator interface.
+//all implementations of Creator should use transactions and return the proper errorType
+//ParsePQUniqueConstraintError is used to determine if a ds with conflicting values exists
+//if so, it will return an errorType of DataConflict and the type should be appended to the
+//generic error message returned
+//The insert sql returns the id and lastUpdated values of the newly inserted ds and have
+//to be added to the struct
+// func (ds *TODeliveryServiceV13) Create(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErrorType) { //
+//
+// 	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(db *sqlx.DB, cfg config.Config) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		defer r.Body.Close()
+		user, err := auth.GetCurrentUser(r.Context())
+		if err != nil {
+			api.HandleErr(w, r, http.StatusInternalServerError, nil, errors.New("getting user: "+err.Error()))
+			return
+		}
+
+		ds := tc.DeliveryServiceNullableV13{}
+		if err := json.NewDecoder(r.Body).Decode(&ds); err != nil {
+			api.HandleErr(w, r, http.StatusBadRequest, errors.New("malformed JSON: "+err.Error()), nil)
+			return
+		}
+
+		if errs := validateV13(db, &ds); len(errs) > 0 {
+			api.HandleErr(w, r, http.StatusBadRequest, errors.New("invalid request: "+util.JoinErrs(errs).Error()), nil)
+			return
+		}
+
+		if authorized, err := isTenantAuthorized(*user, db, &ds.DeliveryServiceNullableV12); err != nil {
+			api.HandleErr(w, r, http.StatusInternalServerError, nil, errors.New("checking tenant: "+err.Error()))
+			return
+		} else if !authorized {
+			api.HandleErr(w, r, http.StatusForbidden, errors.New("not authorized on this tenant"), nil)
+			return
+		}
+
+		ds, errCode, userErr, sysErr := create(db.DB, cfg, user, ds)
+		if userErr != nil || sysErr != nil {
+			api.HandleErr(w, r, errCode, userErr, sysErr)
+			return
+		}
+		api.WriteResp(w, r, []tc.DeliveryServiceNullableV13{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 cdoe, user error, and system error are returned. The status code SHOULD NOT be used, if both errors are nil.
+func create(db *sql.DB, cfg config.Config, user *auth.CurrentUser, ds tc.DeliveryServiceNullableV13) (tc.DeliveryServiceNullableV13, int, error, 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().
+	}
+
+	tx, err := db.Begin()
+	if err != nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("could not begin transaction: " + err.Error())
+	}
+	commitTx := false
+	defer dbhelpers.FinishTx(tx, &commitTx)
+
+	resultRows, err := tx.Query(insertQuery(), &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.LongD [...]
+
+	if err != nil {
+		if pqerr, ok := err.(*pq.Error); ok {
+			err, _ := dbhelpers.ParsePQUniqueConstraintError(pqerr)
+			return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("a delivery service with " + err.Error())
+		}
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("inserting ds: " + err.Error())
+	}
+
+	id := 0
+	lastUpdated := tc.TimeNoMod{}
+	if !resultRows.Next() {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("no deliveryservice request inserted, no id was returned")
+	}
+	if err := resultRows.Scan(&id, &lastUpdated); err != nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("could not scan id from insert: " + err.Error())
+	}
+	if resultRows.Next() {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("too many ids returned from deliveryservice request insert")
+	}
+	ds.ID = &id
+
+	if ds.ID == nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("missing id after insert")
+	}
+	if ds.XMLID == nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("missing xml_id after insert")
+	}
+	if ds.TypeID == nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("missing type after insert")
+	}
+	dsType, err := getTypeNameFromID(*ds.TypeID, tx)
+	if err != nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("getting delivery service type: " + err.Error())
+	}
+	ds.Type = &dsType
+	if ds.Protocol == nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("missing protocol after insert")
+	}
+
+	if err := createDefaultRegex(tx, *ds.ID, *ds.XMLID); err != nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("creating default regex: " + err.Error())
+	}
+
+	matchlists, err := readGetDeliveryServicesMatchLists([]string{*ds.XMLID}, tx)
+	if err != nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("creating DS: reading matchlists: " + err.Error())
+	}
+	if matchlist, ok := matchlists[*ds.XMLID]; !ok {
+		return tc.DeliveryServiceNullableV13{}, 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.DeliveryServiceNullableV13{}, 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 := ensureHeaderRewriteParams(tx, *ds.ID, *ds.XMLID, ds.EdgeHeaderRewrite, edgeTier, *ds.Type); err != nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("creating edge header rewrite parameters: " + err.Error())
+	}
+	if err := ensureHeaderRewriteParams(tx, *ds.ID, *ds.XMLID, ds.MidHeaderRewrite, midTier, *ds.Type); err != nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("creating mid header rewrite parameters: " + err.Error())
+	}
+	if err := ensureRegexRemapParams(tx, *ds.ID, *ds.XMLID, ds.RegexRemap); err != nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("creating regex remap parameters: " + err.Error())
+	}
+	if err := ensureCacheURLParams(tx, *ds.ID, *ds.XMLID, ds.CacheURL); err != nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("creating cache url parameters: " + err.Error())
+	}
+	if err := createDNSSecKeys(tx, cfg, *ds.ID, *ds.XMLID, *ds.Protocol, cdnName, cdnDomain, dnssecEnabled, ds.ExampleURLs); err != nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("creating DNSSEC keys: " + err.Error())
+	}
+	ds.LastUpdated = &lastUpdated
+	commitTx = true
+	api.CreateChangeLogRaw(api.ApiChange, "Created ds: "+*ds.XMLID+" id: "+strconv.Itoa(*ds.ID), *user, db)
+	return ds, http.StatusOK, nil, nil
+}
+
+func (ds *TODeliveryServiceV13) Read(db *sqlx.DB, params map[string]string, user auth.CurrentUser) ([]interface{}, []error, tc.ApiErrorType) {
+	returnable := []interface{}{}
+	dses, errs, errType := readGetDeliveryServices(params, db)
+	if len(errs) > 0 {
+		for _, err := range errs {
+			if err.Error() == `id cannot parse to integer` { // TODO create const for string
+				return nil, []error{errors.New("Resource not found.")}, tc.DataMissingError //matches perl response
+			}
+		}
+		return nil, errs, errType
+	}
+
+	dses, err := filterAuthorized(dses, user, db)
+	if err != nil {
+		log.Errorln("Checking tenancy: " + err.Error())
+		return nil, []error{errors.New("Error checking tenancy.")}, tc.SystemError
+	}
+
+	for _, ds := range dses {
+		returnable = append(returnable, ds)
+	}
+	return returnable, nil, tc.NoError
+}
+
+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 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 := sql.NullInt64{}
+	dsType := ""
+	routingName := ""
+	cdnDomain := ""
+	if err := tx.QueryRow(q, id).Scan(&xmlID, &protocol, &dsType, &routingName, &cdnDomain); err != nil {
+		return "", fmt.Errorf("querying delivery service %v host name: "+err.Error()+"\n", id)
+	}
+	matchLists, err := readGetDeliveryServicesMatchLists([]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(int(protocol.Int64), 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 getTypeNameFromID(id int, tx *sql.Tx) (string, 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 name, nil
+}
+
+func UpdateV13(db *sqlx.DB, cfg config.Config) http.HandlerFunc {
+	return func(w http.ResponseWriter, r *http.Request) {
+		defer r.Body.Close()
+		user, err := auth.GetCurrentUser(r.Context())
+		if err != nil {
+			api.HandleErr(w, r, http.StatusInternalServerError, nil, errors.New("getting user: "+err.Error()))
+			return
+		}
+
+		ds := tc.DeliveryServiceNullableV13{}
+		if err := json.NewDecoder(r.Body).Decode(&ds); err != nil {
+			api.HandleErr(w, r, http.StatusBadRequest, errors.New("malformed JSON: "+err.Error()), nil)
+			return
+		}
+
+		if authorized, err := isTenantAuthorized(*user, db, &ds.DeliveryServiceNullableV12); err != nil {
+			api.HandleErr(w, r, http.StatusInternalServerError, nil, errors.New("checking tenant: "+err.Error()))
+			return
+		} else if !authorized {
+			api.HandleErr(w, r, http.StatusForbidden, errors.New("not authorized on this tenant"), nil)
+			return
+		}
+
+		ds, errCode, userErr, sysErr := update(db.DB, cfg, *user, &ds)
+		if userErr != nil || sysErr != nil {
+			api.HandleErr(w, r, errCode, userErr, sysErr)
+			return
+		}
+		api.WriteResp(w, r, []tc.DeliveryServiceNullableV13{ds})
+	}
+}
+
+func update(db *sql.DB, cfg config.Config, user auth.CurrentUser, ds *tc.DeliveryServiceNullableV13) (tc.DeliveryServiceNullableV13, int, error, error) {
+	tx, err := db.Begin()
+	if err != nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("could not begin transaction: " + err.Error())
+	}
+	commitTx := false
+	defer dbhelpers.FinishTx(tx, &commitTx)
+
+	if ds.XMLID == nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusBadRequest, errors.New("missing xml_id"), nil
+	}
+	if ds.ID == nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusBadRequest, errors.New("missing id"), nil
+	}
+
+	oldHostName, err := getOldHostName(*ds.ID, tx)
+	if err != nil {
+		return tc.DeliveryServiceNullableV13{}, 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 {
+		if err, ok := err.(*pq.Error); ok {
+			err, eType := dbhelpers.ParsePQUniqueConstraintError(err)
+			if eType == tc.DataConflictError {
+				return tc.DeliveryServiceNullableV13{}, http.StatusBadRequest, errors.New("a delivery service with " + err.Error()), nil
+			}
+			return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("query updating delivery service: pq: " + err.Error())
+		}
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("query updating delivery service: " + err.Error())
+	}
+	if !resultRows.Next() {
+		return tc.DeliveryServiceNullableV13{}, http.StatusNotFound, errors.New("no delivery service found with this id"), nil
+	}
+	lastUpdated := tc.TimeNoMod{}
+	if err := resultRows.Scan(&lastUpdated); err != nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("scan updating delivery service: " + err.Error())
+	}
+	if resultRows.Next() {
+		xmlID := ""
+		if ds.XMLID != nil {
+			xmlID = *ds.XMLID
+		}
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("updating delivery service " + xmlID + ": " + "this update affected too many rows: > 1")
+	}
+
+	if ds.ID == nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("missing id after update")
+	}
+	if ds.XMLID == nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("missing xml_id after update")
+	}
+	if ds.TypeID == nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("missing type after update")
+	}
+	if ds.Protocol == nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("missing protocol after update")
+	}
+	if ds.RoutingName == nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("missing routing name after update")
+	}
+	dsType, err := getTypeNameFromID(*ds.TypeID, tx)
+	if err != nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("getting delivery service type after update: " + err.Error())
+	}
+	ds.Type = &dsType
+
+	cdnDomain, err := getCDNDomain(*ds.ID, db) // need to get the domain again, in case it changed.
+	if err != nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("getting CDN domain after update: " + err.Error())
+	}
+
+	newHostName, err := getHostName(*ds.Protocol, *ds.Type, *ds.RoutingName, *ds.MatchList, cdnDomain)
+	if err != nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("getting hostname after update: " + err.Error())
+	}
+
+	matchLists, err := readGetDeliveryServicesMatchLists([]string{*ds.XMLID}, tx)
+	if err != nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("getting matchlists after update: " + err.Error())
+	}
+	if ml, ok := matchLists[*ds.XMLID]; !ok {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("no matchlists after update")
+	} else {
+		ds.MatchList = &ml
+	}
+
+	if oldHostName != newHostName {
+		if err := updateSSLKeys(ds, newHostName, db, cfg); err != nil {
+			return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("updating delivery service " + *ds.XMLID + ": updating SSL keys: " + err.Error())
+		}
+	}
+
+	if err := ensureHeaderRewriteParams(tx, *ds.ID, *ds.XMLID, ds.EdgeHeaderRewrite, edgeTier, *ds.Type); err != nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("creating edge header rewrite parameters: " + err.Error())
+	}
+	if err := ensureHeaderRewriteParams(tx, *ds.ID, *ds.XMLID, ds.MidHeaderRewrite, midTier, *ds.Type); err != nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("creating mid header rewrite parameters: " + err.Error())
+	}
+	if err := ensureRegexRemapParams(tx, *ds.ID, *ds.XMLID, ds.RegexRemap); err != nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("creating mid regex remap parameters: " + err.Error())
+	}
+	if err := ensureCacheURLParams(tx, *ds.ID, *ds.XMLID, ds.CacheURL); err != nil {
+		return tc.DeliveryServiceNullableV13{}, http.StatusInternalServerError, nil, errors.New("creating mid cacheurl parameters: " + err.Error())
+	}
+	ds.LastUpdated = &lastUpdated
+	commitTx = true
+	api.CreateChangeLogRaw(api.ApiChange, "Updated ds: "+*ds.XMLID+" id: "+strconv.Itoa(*ds.ID), user, db)
+	return *ds, http.StatusOK, nil, nil
+}
+
+//The DeliveryService implementation of the Deleter interface
+//all implementations of Deleter should use transactions and return the proper errorType
+func (ds *TODeliveryServiceV13) Delete(db *sqlx.DB, user auth.CurrentUser) (error, tc.ApiErrorType) {
+	log.Debugln("TODeliveryServiceV13.Delete calling id '%v' xmlid '%v'\n", ds.ID, ds.XMLID)
+	// return nil, tc.NoError // debug
+
+	tx, err := db.Begin()
+	if err != nil {
+		log.Errorln("could not begin transaction: " + err.Error())
+		return tc.DBError, tc.SystemError
+	}
+	commitTx := false
+	defer dbhelpers.FinishTx(tx, &commitTx)
+
+	if err != nil {
+		log.Errorln("could not begin transaction: " + err.Error())
+		return tc.DBError, tc.SystemError
+	}
+
+	if ds.ID == nil {
+		log.Errorln("TODeliveryServiceV13.Delete called with nil ID")
+		return tc.DBError, tc.DataMissingError
+	} else if ok, err := ds.V12().LoadXMLID(db); err != nil {
+		log.Errorln("TODeliveryServiceV13.Delete ID '" + string(*ds.ID) + "' loading XML ID: " + err.Error())
+		return tc.DBError, tc.SystemError
+	} else if !ok {
+		log.Errorln("TODeliveryServiceV13.Delete ID '" + string(*ds.ID) + "' had no delivery service!")
+		return tc.DBError, tc.DataMissingError
+	} else if ds.XMLID == nil {
+		// should never happen
+		log.Errorf("TODeliveryServiceV13.Delete ID '%v' loaded nil XML ID!", *ds.ID)
+		return tc.DBError, tc.SystemError
+	}
+
+	result, err := tx.Exec(`DELETE FROM deliveryservice WHERE id=$1`, ds.ID)
+	if err != nil {
+		log.Errorln("TODeliveryServiceV13.Delete deleting delivery service: " + err.Error())
+		return tc.DBError, tc.SystemError
+	}
+	rowsAffected, err := result.RowsAffected()
+	if err != nil {
+		return tc.DBError, tc.SystemError
+	}
+	if rowsAffected != 1 {
+		if rowsAffected < 1 {
+			return errors.New("no delivery service with that id found"), tc.DataMissingError
+		}
+		return fmt.Errorf("this create affected too many rows: %d", rowsAffected), tc.SystemError
+	}
+
+	if _, err := tx.Exec(`DELETE FROM deliveryservice_regex WHERE deliveryservice=$1`, ds.ID); err != nil {
+		log.Errorln("TODeliveryServiceV13.Delete deleting delivery service regexes: " + err.Error())
+		return tc.DBError, tc.SystemError
+	}
+
+	paramConfigFilePrefixes := []string{"hdr_rw_", "hdr_rw_mid_", "regex_remap_", "cacheurl_"}
+	configFiles := []string{}
+	for _, prefix := range paramConfigFilePrefixes {
+		configFiles = append(configFiles, prefix+*ds.XMLID+".config")
+	}
+
+	if _, err := tx.Exec(`DELETE FROM parameter WHERE name = 'location' AND config_file = ANY($1)`, pq.Array(configFiles)); err != nil {
+		log.Errorln("TODeliveryServiceV13.Delete deleting delivery service parameters: " + err.Error())
+		return tc.DBError, tc.SystemError
+	}
+
+	commitTx = true
+	return nil, tc.NoError
+}
+
+// IsTenantAuthorized implements the Tenantable interface to ensure the user is authorized on the deliveryservice tenant
+func (ds *TODeliveryServiceV13) IsTenantAuthorized(user auth.CurrentUser, db *sqlx.DB) (bool, error) {
+	return ds.V12().IsTenantAuthorized(user, db)
+}
+
+func filterAuthorized(dses []tc.DeliveryServiceNullableV13, user auth.CurrentUser, db *sqlx.DB) ([]tc.DeliveryServiceNullableV13, error) {
+	newDSes := []tc.DeliveryServiceNullableV13{}
+	for _, ds := range dses {
+		// TODO add/use a helper func to make a single SQL call, for performance
+		ok, err := tenant.IsResourceAuthorizedToUser(*ds.TenantID, user, db)
+		if err != nil {
+			if ds.XMLID == nil {
+				return nil, errors.New("isResourceAuthorized for delivery service with nil XML ID: " + err.Error())
+			} else {
+				return nil, errors.New("isResourceAuthorized for '" + *ds.XMLID + "': " + err.Error())
+			}
+		}
+		if !ok {
+			continue
+		}
+		newDSes = append(newDSes, ds)
+	}
+	return newDSes, nil
+}
+
+func readGetDeliveryServices(params map[string]string, db *sqlx.DB) ([]tc.DeliveryServiceNullableV13, []error, tc.ApiErrorType) {
+	// 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},
+		"hostName": dbhelpers.WhereColumnInfo{"s.host_name", nil},
+	}
+
+	where, orderBy, queryValues, errs := dbhelpers.BuildWhereAndOrderBy(params, queryParamsToSQLCols)
+	if len(errs) > 0 {
+		return nil, errs, tc.DataConflictError
+	}
+
+	query := selectQuery() + where + orderBy
+
+	tx, err := db.Beginx()
+	if err != nil {
+		log.Errorln("could not begin transaction: " + err.Error())
+		return nil, []error{tc.DBError}, tc.SystemError
+	}
+	commitTx := false
+	defer dbhelpers.FinishTxX(tx, &commitTx)
+
+	rows, err := tx.NamedQuery(query, queryValues)
+	if err != nil {
+		return nil, []error{fmt.Errorf("querying: %v", err)}, tc.SystemError
+	}
+	defer rows.Close()
+
+	dses := []tc.DeliveryServiceNullableV13{}
+	dsCDNDomains := map[string]string{}
+	for rows.Next() {
+		ds := tc.DeliveryServiceNullableV13{}
+		cdnDomain := ""
+		err := rows.Scan(&ds.Active, &ds.CacheURL, &ds.CCRDNSTTL, &ds.CDNID, &ds.CDNName, &ds.CheckPath, &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.InitialDispersion, &ds.IPV6RoutingEnabled, &ds.LastUpdated, &ds.LogsEn [...]
+		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 == "url_sig"
+		dses = append(dses, ds)
+	}
+
+	dsNames := make([]string, len(dses), len(dses))
+	for i, ds := range dses {
+		dsNames[i] = *ds.XMLID
+	}
+
+	matchLists, err := readGetDeliveryServicesMatchLists(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
+		dsProtocol := 0
+		if ds.Protocol != nil {
+			dsProtocol = *ds.Protocol
+		}
+		ds.ExampleURLs = makeExampleURLs(dsProtocol, *ds.Type, *ds.RoutingName, *ds.MatchList, dsCDNDomains[*ds.XMLID])
+		dses[i] = ds
+	}
+
+	commitTx = true
+	return dses, nil, tc.NoError
+}
+
+func updateSSLKeys(ds *tc.DeliveryServiceNullableV13, hostName string, db *sql.DB, cfg config.Config) error {
+	if ds.XMLID == nil {
+		return errors.New("delivery services has no XMLID!")
+	}
+	key, ok, err := riaksvc.GetDeliveryServiceSSLKeysObj(*ds.XMLID, "latest", db, cfg.RiakAuthOptions)
+	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, db, cfg.RiakAuthOptions); err != nil {
+		return errors.New("putting updated SSL key: " + err.Error())
+	}
+	return nil
+}
+
+func getHostName(dsProtocol int, dsType string, dsRoutingName string, dsMatchList []tc.DeliveryServiceMatch, cdnDomain string) (string, error) {
+	exampleURLs := makeExampleURLs(dsProtocol, dsType, dsRoutingName, dsMatchList, cdnDomain)
+
+	exampleURL := ""
+	if 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 strings.HasPrefix(dsType, "HTTP") {
+		if firstDot := strings.Index(host, "."); firstDot == -1 {
+			host = "*" // TODO warn? error?
+		} else {
+			host = "*" + host[firstDot:]
+		}
+	}
+	return host, nil
+}
+
+func getCDNDomain(dsID int, db *sql.DB) (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 := db.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
+}
+
+func makeExampleURLs(protocol int, dsType string, routingName string, matchList []tc.DeliveryServiceMatch, cdnDomain string) []string {
+	examples := []string{}
+	scheme := ""
+	scheme2 := ""
+	switch protocol {
+	case 0:
+		scheme = "http"
+	case 1:
+		scheme = "https"
+	case 2:
+		fallthrough
+	case 3:
+		scheme = "http"
+		scheme2 = "https"
+	default:
+		scheme = "http"
+	}
+	dsIsDNS := strings.HasPrefix(strings.ToLower(dsType), "DNS")
+	regexReplacer := strings.NewReplacer(`\`, ``, `.*`, ``, `.`, ``)
+	for _, match := range matchList {
+		switch {
+		case dsIsDNS:
+			fallthrough
+		case match.Type == `HOST_REGEXP`:
+			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)
+			}
+		case match.Type == `PATH_REGEXP`:
+			examples = append(examples, match.Pattern)
+		}
+	}
+	return examples
+}
+
+func readGetDeliveryServicesMatchLists(dses []string, tx *sql.Tx) (map[string][]tc.DeliveryServiceMatch, error) {
+	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)
+`
+	rows, err := tx.Query(q, pq.Array(dses))
+	if err != nil {
+		return nil, errors.New("getting delivery service regexes: " + err.Error())
+	}
+
+	matches := map[string][]tc.DeliveryServiceMatch{}
+	for rows.Next() {
+		m := tc.DeliveryServiceMatch{}
+		dsName := ""
+		if err := rows.Scan(&dsName, &m.Type, &m.Pattern, &m.SetNumber); err != nil {
+			return nil, errors.New("scanning delivery service regexes: " + err.Error())
+		}
+		matches[dsName] = append(matches[dsName], m)
+	}
+	return matches, nil
+}
+
+type tierType int
+
+const (
+	midTier tierType = iota
+	edgeTier
+)
+
+func ensureHeaderRewriteParams(tx *sql.Tx, dsID int, xmlID string, hdrRW *string, tier tierType, dsType string) error {
+	if tier == midTier && strings.Contains(dsType, "LIVE") && !strings.Contains(dsType, "NATNL") {
+		return nil // live local DSes don't get remap rules
+	}
+	configFile := "hdr_rw_" + xmlID + ".config"
+	if tier == midTier {
+		configFile = "hdr_rw_mid_" + xmlID + ".config"
+	}
+	if hdrRW == nil || *hdrRW == "" {
+		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 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
+}
+
+func selectQuery() string {
+	return `
+SELECT
+ds.active,
+ds.cacheurl,
+ds.ccr_dns_ttl,
+ds.cdn_id,
+cdn.name as cdnName,
+ds.check_path,
+ds.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.mid_header_rewrite,
+COALESCE(ds.miss_lat, 0.0),
+COALESCE(ds.miss_long, 0.0),
+ds.multi_site_origin,
+ds.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,
+org_server_fqdn=$34,
+origin_shield=$35,
+profile=$36,
+protocol=$37,
+qstring_ignore=$38,
+range_request_handling=$39,
+regex_remap=$40,
+regional_geo_blocking=$41,
+remap_text=$42,
+routing_name=$43,
+signing_algorithm=$44,
+ssl_key_version=$45,
+tenant_id=$46,
+tr_request_headers=$47,
+tr_response_headers=$48,
+type=$49,
+xml_id=$50
+WHERE id=$51
+RETURNING last_updated
+`
+}
+
+func insertQuery() string {
+	return `
+INSERT INTO deliveryservice (
+active,
+cacheurl,
+ccr_dns_ttl,
+cdn_id,
+check_path,
+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,
+mid_header_rewrite,
+miss_lat,
+miss_long,
+multi_site_origin,
+org_server_fqdn,
+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)
+RETURNING id, last_updated
+`
+}
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/dnssec.go b/traffic_ops/traffic_ops_golang/deliveryservice/dnssec.go
new file mode 100644
index 0000000..b0c45a1
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/dnssec.go
@@ -0,0 +1,206 @@
+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 (
+	"database/sql"
+	"encoding/base64"
+	"errors"
+	"strings"
+	"time"
+
+	"github.com/apache/incubator-trafficcontrol/lib/go-log"
+	"github.com/apache/incubator-trafficcontrol/lib/go-tc"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/config"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/riaksvc"
+
+	"github.com/miekg/dns"
+)
+
+func createDNSSecKeys(tx *sql.Tx, cfg config.Config, dsID int, xmlID string, dsProtocol int, cdnName string, cdnDomain string, dnssecEnabled bool, exampleURLs []string) error {
+	if !dnssecEnabled {
+		return nil
+	}
+
+	keys, ok, err := riaksvc.GetDNSSECKeys(cdnName, tx, cfg.RiakAuthOptions)
+	if err != nil {
+		log.Errorln("Getting DNSSec keys from Riak: " + err.Error())
+		return errors.New("getting DNSSec keys from Riak: " + err.Error())
+	}
+	if !ok {
+		log.Errorln("Getting DNSSec keys from Riak: no DNSSec keys found")
+		return errors.New("getting DNSSec keys from Riak: no DNSSec keys found")
+	}
+
+	cdnKeys, ok := keys[cdnName]
+	// TODO warn and continue?
+	if !ok {
+		log.Errorln("Getting DNSSec keys from Riak: no DNSSec keys for CDN '" + cdnName + "'")
+		return errors.New("getting DNSSec keys from Riak: no DNSSec keys for CDN")
+	}
+	if len(cdnKeys.ZSK) == 0 {
+		log.Errorln("Getting DNSSec keys from Riak: no DNSSec ZSK keys for CDN '" + cdnName + "'")
+		return errors.New("getting DNSSec keys from Riak: no DNSSec ZSK keys for CDN")
+	}
+	if len(cdnKeys.KSK) == 0 {
+		log.Errorln("Getting DNSSec keys from Riak: no DNSSec ZSK keys for CDN '" + cdnName + "'")
+		return errors.New("getting DNSSec keys from Riak: no DNSSec ZSK keys for CDN")
+	}
+
+	kExpDays := getKeyExpirationDays(cdnKeys.KSK, dnssecDefaultKSKExpirationDays)
+	zExpDays := getKeyExpirationDays(cdnKeys.ZSK, dnssecDefaultZSKExpirationDays)
+	ttl := getKeyTTL(cdnKeys.KSK, dnssecDefaultTTL)
+	dsName, err := getDSDomainName(exampleURLs)
+	if err != nil {
+		log.Errorln("creating DS domain name: " + err.Error())
+		return errors.New("creating DS domain name: " + err.Error())
+	}
+	inception := time.Now()
+	zExpiration := inception.Add(time.Duration(zExpDays) * time.Hour * 24)
+	kExpiration := inception.Add(time.Duration(kExpDays) * time.Hour * 24)
+
+	tld := false
+	effectiveDate := inception
+	zsk, err := getDNSSECKeys(dnssecZSKType, dsName, ttl, inception, zExpiration, dnssecKeyStatusNew, effectiveDate, tld)
+	if err != nil {
+		log.Errorln("getting DNSSEC keys for ZSK: " + err.Error())
+		return errors.New("getting DNSSEC keys for ZSK: " + err.Error())
+	}
+	ksk, err := getDNSSECKeys(dnssecKSKType, dsName, ttl, inception, kExpiration, dnssecKeyStatusNew, effectiveDate, tld)
+	if err != nil {
+		log.Errorln("getting DNSSEC keys for KSK: " + err.Error())
+		return errors.New("getting DNSSEC keys for KSK: " + err.Error())
+	}
+	keys[xmlID] = tc.DNSSECKeySet{ZSK: []tc.DNSSECKey{zsk}, KSK: []tc.DNSSECKey{ksk}}
+
+	if err := riaksvc.PutDNSSECKeys(keys, cdnName, tx, cfg.RiakAuthOptions); err != nil {
+		log.Errorln("putting Riak DNSSEC keys: " + err.Error())
+		return errors.New("putting Riak DNSSEC keys: " + err.Error())
+	}
+	return nil
+}
+
+func getDNSSECKeys(keyType string, dsName string, ttl uint64, inception time.Time, expiration time.Time, status string, effectiveDate time.Time, tld bool) (tc.DNSSECKey, error) {
+	key := tc.DNSSECKey{
+		InceptionDateUnix:  inception.Unix(),
+		ExpirationDateUnix: expiration.Unix(),
+		Name:               dsName + ".",
+		TTLSeconds:         ttl,
+		Status:             status,
+		EffectiveDateUnix:  effectiveDate.Unix(),
+	}
+	isKSK := keyType != dnssecZSKType
+	err := error(nil)
+	key.Public, key.Private, key.DSRecord, err = genKeys(dsName, isKSK, ttl, tld)
+	return key, err
+}
+
+// genKeys generates keys for DNSSEC for a delivery service. Returns the public key, private key, and DS record (which will be nil if ksk or tld is false).
+// This emulates the old Perl Traffic Ops behavior: the public key is of the RFC1035 single-line zone file format, base64 encoded; the private key is of the BIND private-key-file format, base64 encoded; the DSRecord contains the algorithm, digest type, and digest.
+func genKeys(dsName string, ksk bool, ttl uint64, tld bool) (string, string, *tc.DNSSECKeyDSRecord, error) {
+	bits := 1024
+	flags := 256
+	algorithm := dns.RSASHA1 // 5 - http://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml
+	protocol := 3
+
+	if ksk {
+		flags |= 1
+		bits *= 2
+	}
+
+	dnskey := dns.DNSKEY{
+		Hdr: dns.RR_Header{
+			Name:   dsName,
+			Rrtype: dns.TypeDNSKEY,
+			Class:  dns.ClassINET,
+			Ttl:    uint32(ttl),
+		},
+		Flags:     uint16(flags),
+		Protocol:  uint8(protocol),
+		Algorithm: algorithm,
+	}
+
+	priKey, err := dnskey.Generate(bits)
+	if err != nil {
+		return "", "", nil, errors.New("error generating DNS key: " + err.Error())
+	}
+
+	priKeyStr := dnskey.PrivateKeyString(priKey) // BIND9 private-key-file format; cooresponds to Perl Net::DNS::SEC::Private->generate_rsa.dump_rsa_priv
+	priKeyStrBase64 := base64.StdEncoding.EncodeToString([]byte(priKeyStr))
+
+	pubKeyStr := dnskey.String() // RFC1035 single-line zone file format; cooresponds to Perl Net::DNS::RR.plain
+	pubKeyStrBase64 := base64.StdEncoding.EncodeToString([]byte(pubKeyStr))
+
+	keyDS := (*tc.DNSSECKeyDSRecord)(nil)
+	if ksk && tld {
+		dsRecord := dnskey.ToDS(dns.SHA1) // TODO update to SHA512
+		keyDS = &tc.DNSSECKeyDSRecord{Algorithm: int64(dsRecord.Algorithm), DigestType: int64(dsRecord.DigestType), Digest: dsRecord.Digest}
+	}
+
+	return pubKeyStrBase64, priKeyStrBase64, keyDS, nil
+}
+
+// TODO change ttl to time.Duration
+
+const dnssecKSKType = "ksk"
+const dnssecZSKType = "zsk"
+
+func getDSDomainName(dsExampleURLs []string) (string, error) {
+	if len(dsExampleURLs) == 0 {
+		return "", errors.New("no example URLs")
+	}
+
+	dsName := dsExampleURLs[0] + "."
+	firstDot := strings.Index(dsName, ".")
+	if firstDot == -1 {
+		return "", errors.New("malformed example URL, no dots")
+	}
+	if len(dsName) < firstDot+2 {
+		return "", errors.New("malformed example URL, nothing after first dot")
+	}
+	dsName = dsName[firstDot+1:]
+	return dsName, nil
+}
+
+const dnssecKeyStatusNew = "new"
+const secondsPerDay = 86400
+const dnssecDefaultKSKExpirationDays = 365
+const dnssecDefaultZSKExpirationDays = 30
+const dnssecDefaultTTL = 60
+
+func getKeyExpirationDays(keys []tc.DNSSECKey, defaultExpirationDays uint64) uint64 {
+	for _, key := range keys {
+		if key.Status != dnssecKeyStatusNew {
+			continue
+		}
+		return uint64((key.ExpirationDateUnix - key.InceptionDateUnix) / secondsPerDay)
+	}
+	return defaultExpirationDays
+}
+
+func getKeyTTL(keys []tc.DNSSECKey, defaultTTL uint64) uint64 {
+	for _, key := range keys {
+		if key.Status != dnssecKeyStatusNew {
+			continue
+		}
+		return key.TTLSeconds
+	}
+	return defaultTTL
+}
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/request/requests_test.go b/traffic_ops/traffic_ops_golang/deliveryservice/request/requests_test.go
index 7abb25c..993351c 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/request/requests_test.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/request/requests_test.go
@@ -73,14 +73,14 @@ func TestGetDeliveryServiceRequest(t *testing.T) {
 	}
 
 	expectedErrors := []string{
-	/*
-		`'regionalGeoBlocking' is required`,
-		`'xmlId' cannot contain spaces`,
-		`'dscp' is required`,
-		`'displayName' cannot be blank`,
-		`'geoProvider' is required`,
-		`'typeId' is required`,
-	*/
+		/*
+			`'regionalGeoBlocking' is required`,
+			`'xmlId' cannot contain spaces`,
+			`'dscp' is required`,
+			`'displayName' cannot be blank`,
+			`'geoProvider' is required`,
+			`'typeId' is required`,
+		*/
 	}
 
 	r.SetKeys(map[string]interface{}{"id": 10})
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/request/validate.go b/traffic_ops/traffic_ops_golang/deliveryservice/request/validate.go
index 50ae622..74fd1b2 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/request/validate.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/request/validate.go
@@ -24,10 +24,11 @@ import (
 	"fmt"
 	"strconv"
 
-	tc "github.com/apache/incubator-trafficcontrol/lib/go-tc"
+	"github.com/apache/incubator-trafficcontrol/lib/go-tc"
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/deliveryservice"
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/tovalidate"
-	validation "github.com/go-ozzo/ozzo-validation"
+
+	"github.com/go-ozzo/ozzo-validation"
 	"github.com/jmoiron/sqlx"
 )
 
@@ -61,7 +62,7 @@ func (req *TODeliveryServiceRequest) Validate(db *sqlx.DB) []error {
 	errs := tovalidate.ToErrors(errMap)
 
 	// ensure the deliveryservice requested is valid
-	e := deliveryservice.Validate(db, req.DeliveryService)
+	e := deliveryservice.ValidateV13(db, req.DeliveryService)
 	errs = append(errs, e...)
 
 	return errs
diff --git a/traffic_ops/traffic_ops_golang/deliveryservices_keys.go b/traffic_ops/traffic_ops_golang/deliveryservices_keys.go
index dadf694..577e272 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservices_keys.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservices_keys.go
@@ -26,6 +26,7 @@ import (
 	"encoding/base64"
 	"encoding/json"
 	"encoding/pem"
+	"errors"
 	"fmt"
 	"io/ioutil"
 	"net/http"
@@ -38,15 +39,11 @@ import (
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/config"
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/riaksvc"
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
-	"github.com/basho/riak-go-client"
 	"github.com/jmoiron/sqlx"
 )
 
 // Delivery Services: SSL Keys.
 
-// SSLKeysBucket ...
-const SSLKeysBucket = "ssl"
-
 // returns the cdn_id found by domainname.
 func getCDNIDByDomainname(domainName string, db *sqlx.DB) (sql.NullInt64, error) {
 	cdnQuery := `SELECT id from cdn WHERE domain_name = $1`
@@ -100,60 +97,34 @@ func getXMLID(cdnID sql.NullInt64, hostRegex string, db *sqlx.DB) (sql.NullStrin
 	return xmlID, nil
 }
 
-func getDeliveryServiceSSLKeysByXMLID(xmlID string, version string, db *sqlx.DB, cfg config.Config) ([]byte, error) {
-	var respBytes []byte
-	// create and start a cluster
-	cluster, err := riaksvc.GetRiakCluster(db, cfg.RiakAuthOptions)
-	if err != nil {
+func getDeliveryServiceSSLKeysByXMLID(xmlID string, version string, db *sql.DB, cfg config.Config) ([]byte, error) {
+	if cfg.RiakEnabled == false {
+		err := errors.New("Riak is not configured!")
+		log.Errorln("getting delivery services SSL keys: " + err.Error())
 		return nil, err
 	}
-	if err = cluster.Start(); err != nil {
-		return nil, err
-	}
-	defer func() {
-		if err := cluster.Stop(); err != nil {
-			log.Errorf("%v\n", err)
-		}
-	}()
-
-	if version == "" {
-		xmlID = xmlID + "-latest"
-	} else {
-		xmlID = xmlID + "-" + version
-	}
-
-	// get the deliveryservice ssl keys by xmlID and version
-	ro, err := riaksvc.FetchObjectValues(xmlID, SSLKeysBucket, cluster)
+	key, ok, err := riaksvc.GetDeliveryServiceSSLKeysObj(xmlID, version, db, cfg.RiakAuthOptions)
 	if err != nil {
+		log.Errorln("getting delivery service keys: " + err.Error())
 		return nil, err
 	}
-
-	// no keys we're found
-	if ro == nil {
+	if !ok {
 		alert := tc.CreateAlerts(tc.InfoLevel, "no object found for the specified key")
-		respBytes, err = json.Marshal(alert)
+		respBytes, err := json.Marshal(alert)
 		if err != nil {
 			log.Errorf("failed to marshal an alert response: %s\n", err)
 			return nil, err
 		}
-	} else { // keys were found
-		var key tc.DeliveryServiceSSLKeys
-
-		// unmarshal into a response tc.DeliveryServiceSSLKeysResponse object.
-		if err := json.Unmarshal(ro[0].Value, &key); err != nil {
-			log.Errorf("failed at unmarshaling sslkey response: %s\n", err)
-			return nil, err
-		}
-		resp := tc.DeliveryServiceSSLKeysResponse{
-			Response: key,
-		}
-		respBytes, err = json.Marshal(resp)
-		if err != nil {
-			log.Errorf("failed to marshal a sslkeys response: %s\n", err)
-			return nil, err
-		}
+		return respBytes, nil
 	}
 
+	respBytes := []byte{}
+	resp := tc.DeliveryServiceSSLKeysResponse{Response: key}
+	respBytes, err = json.Marshal(resp)
+	if err != nil {
+		log.Errorf("failed to marshal a sslkeys response: %s\n", err)
+		return nil, err
+	}
 	return respBytes, nil
 }
 
@@ -245,8 +216,12 @@ func verifyAndEncodeCertificate(certificate string, rootCA string) (string, erro
 func addDeliveryServiceSSLKeysHandler(db *sqlx.DB, cfg config.Config) http.HandlerFunc {
 	return func(w http.ResponseWriter, r *http.Request) {
 		handleErr := tc.GetHandleErrorsFunc(w, r)
-		var keysObj tc.DeliveryServiceSSLKeys
-
+		if !cfg.RiakEnabled {
+			err := errors.New("Riak is not configured!")
+			log.Errorln("adding Riak SSL keys for delivery service: " + err.Error())
+			handleErr(http.StatusInternalServerError, err)
+			return
+		}
 		defer r.Body.Close()
 
 		ctx := r.Context()
@@ -262,7 +237,7 @@ func addDeliveryServiceSSLKeysHandler(db *sqlx.DB, cfg config.Config) http.Handl
 			return
 		}
 
-		// unmarshal the request
+		keysObj := tc.DeliveryServiceSSLKeys{}
 		if err := json.Unmarshal(data, &keysObj); err != nil {
 			log.Errorf("ERROR: could not unmarshal the request, %v\n", err)
 			handleErr(http.StatusBadRequest, err)
@@ -301,34 +276,8 @@ func addDeliveryServiceSSLKeysHandler(db *sqlx.DB, cfg config.Config) http.Handl
 			return
 		}
 
-		// create and start a cluster
-		cluster, err := riaksvc.GetRiakCluster(db, cfg.RiakAuthOptions)
-		if err != nil {
-			handleErr(http.StatusInternalServerError, err)
-			return
-		}
-		if err = cluster.Start(); err != nil {
-			handleErr(http.StatusInternalServerError, err)
-			return
-		}
-		defer func() {
-			if err := cluster.Stop(); err != nil {
-				log.Errorf("%v\n", err)
-			}
-		}()
-
-		// create a storage object and store the data
-		obj := &riak.Object{
-			ContentType:     "text/json",
-			Charset:         "utf-8",
-			ContentEncoding: "utf-8",
-			Key:             keysObj.DeliveryService,
-			Value:           []byte(keysJSON),
-		}
-
-		err = riaksvc.SaveObject(obj, SSLKeysBucket, cluster)
-		if err != nil {
-			log.Errorf("%v\n", err)
+		if err := riaksvc.PutDeliveryServiceSSLKeysObj(keysObj, db.DB, cfg.RiakAuthOptions); err != nil {
+			log.Errorln("putting Riak SSL keys for delivery service '" + keysObj.DeliveryService + "': " + err.Error())
 			handleErr(http.StatusInternalServerError, err)
 			return
 		}
@@ -430,7 +379,7 @@ func getDeliveryServiceSSLKeysByHostNameHandler(db *sqlx.DB, cfg config.Config)
 						return
 					}
 				}
-				respBytes, err = getDeliveryServiceSSLKeysByXMLID(xmlID, version, db, cfg)
+				respBytes, err = getDeliveryServiceSSLKeysByXMLID(xmlID, version, db.DB, cfg)
 				if err != nil {
 					handleErr(http.StatusInternalServerError, err)
 					return
@@ -485,7 +434,7 @@ func getDeliveryServiceSSLKeysByXMLIDHandler(db *sqlx.DB, cfg config.Config) htt
 			}
 		}
 
-		respBytes, err = getDeliveryServiceSSLKeysByXMLID(xmlID, version, db, cfg)
+		respBytes, err = getDeliveryServiceSSLKeysByXMLID(xmlID, version, db.DB, cfg)
 		if err != nil {
 			handleErr(http.StatusInternalServerError, err)
 			return
diff --git a/traffic_ops/traffic_ops_golang/riaksvc/dsutil.go b/traffic_ops/traffic_ops_golang/riaksvc/dsutil.go
new file mode 100644
index 0000000..8532a53
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/riaksvc/dsutil.go
@@ -0,0 +1,140 @@
+package riaksvc
+
+/*
+ * 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 (
+	"database/sql"
+	"encoding/json"
+	"errors"
+
+	"github.com/apache/incubator-trafficcontrol/lib/go-log"
+	"github.com/apache/incubator-trafficcontrol/lib/go-tc"
+
+	"github.com/basho/riak-go-client"
+)
+
+const DeliveryServiceSSLKeysBucket = "ssl"
+const DNSSECKeysBucket = "dnssec"
+
+func GetDeliveryServiceSSLKeysObj(xmlID string, version string, db *sql.DB, authOpts *riak.AuthOptions) (tc.DeliveryServiceSSLKeys, bool, error) {
+	key := tc.DeliveryServiceSSLKeys{}
+	if version == "" {
+		xmlID += "-latest"
+	} else {
+		xmlID += "-" + version
+	}
+	found := false
+	err := WithCluster(db, authOpts, func(cluster StorageCluster) error {
+		// get the deliveryservice ssl keys by xmlID and version
+		ro, err := FetchObjectValues(xmlID, DeliveryServiceSSLKeysBucket, cluster)
+		if err != nil {
+			return err
+		}
+		if len(ro) == 0 {
+			return nil // not found
+		}
+		if err := json.Unmarshal(ro[0].Value, &key); err != nil {
+			log.Errorf("failed at unmarshaling sslkey response: %s\n", err)
+			return errors.New("unmarshalling Riak result: " + err.Error())
+		}
+		found = true
+		return nil
+	})
+	if err != nil {
+		return key, false, err
+	}
+	return key, found, nil
+}
+
+func PutDeliveryServiceSSLKeysObj(key tc.DeliveryServiceSSLKeys, db *sql.DB, authOpts *riak.AuthOptions) error {
+	keyJSON, err := json.Marshal(&key)
+	if err != nil {
+		return errors.New("marshalling key: " + err.Error())
+	}
+	err = WithCluster(db, authOpts, func(cluster StorageCluster) error {
+		obj := &riak.Object{
+			ContentType:     "text/json",
+			Charset:         "utf-8",
+			ContentEncoding: "utf-8",
+			Key:             key.DeliveryService,
+			Value:           []byte(keyJSON),
+		}
+		if err = SaveObject(obj, DeliveryServiceSSLKeysBucket, cluster); err != nil {
+			return errors.New("saving Riak object: " + err.Error())
+		}
+		return nil
+	})
+	return err
+}
+
+func GetDNSSECKeys(cdnName string, tx *sql.Tx, authOpts *riak.AuthOptions) (tc.DNSSECKeys, bool, error) {
+	key := tc.DNSSECKeys{}
+	found := false
+	log.Errorln("riaksvc.GetDNSSECKeys calling")
+	err := WithClusterTx(tx, authOpts, func(cluster StorageCluster) error {
+		log.Errorln("riaksvc.GetDNSSECKeys in WithClusterTx")
+		ro, err := FetchObjectValues(cdnName, DNSSECKeysBucket, cluster)
+		log.Errorln("riaksvc.GetDNSSECKeys fetched object values")
+		if err != nil {
+			log.Errorln("riaksvc.GetDNSSECKeys fetched object values returning err")
+			return err
+		}
+		if len(ro) == 0 {
+			log.Errorln("riaksvc.GetDNSSECKeys returning nil, len(ro) is 0")
+			return nil // not found
+		}
+		log.Errorln("riaksvc.GetDNSSECKeys unmarshalling")
+		if err := json.Unmarshal(ro[0].Value, &key); err != nil {
+			log.Errorln("Unmarshaling Riak dnssec response: " + err.Error())
+			return errors.New("unmarshalling Riak dnssec response: " + err.Error())
+		}
+		log.Errorln("riaksvc.GetDNSSECKeys unmarshalled, found true, returning nil err")
+		found = true
+		return nil
+	})
+	log.Errorln("riaksvc.GetDNSSECKeys out of WithCluster")
+	if err != nil {
+		log.Errorln("riaksvc.GetDNSSECKeys WithCluster err, returning err")
+		return key, false, err
+	}
+	log.Errorln("riaksvc.GetDNSSECKeys returning success")
+	return key, found, nil
+}
+
+func PutDNSSECKeys(keys tc.DNSSECKeys, cdnName string, tx *sql.Tx, authOpts *riak.AuthOptions) error {
+	keyJSON, err := json.Marshal(&keys)
+	if err != nil {
+		return errors.New("marshalling keys: " + err.Error())
+	}
+	err = WithClusterTx(tx, authOpts, func(cluster StorageCluster) error {
+		obj := &riak.Object{
+			ContentType:     "application/json",
+			Charset:         "utf-8",
+			ContentEncoding: "utf-8",
+			Key:             cdnName,
+			Value:           []byte(keyJSON),
+		}
+		if err = SaveObject(obj, DNSSECKeysBucket, cluster); err != nil {
+			return errors.New("saving Riak object: " + err.Error())
+		}
+		return nil
+	})
+	return err
+}
diff --git a/traffic_ops/traffic_ops_golang/riaksvc/riak_services.go b/traffic_ops/traffic_ops_golang/riaksvc/riak_services.go
index a16c852..c10f2ef 100644
--- a/traffic_ops/traffic_ops_golang/riaksvc/riak_services.go
+++ b/traffic_ops/traffic_ops_golang/riaksvc/riak_services.go
@@ -21,12 +21,14 @@ package riaksvc
 
 import (
 	"crypto/tls"
+	"database/sql"
 	"encoding/json"
 	"errors"
 	"fmt"
 	"io/ioutil"
 	"time"
 
+	"github.com/apache/incubator-trafficcontrol/lib/go-log"
 	"github.com/apache/incubator-trafficcontrol/lib/go-tc"
 
 	"github.com/basho/riak-go-client"
@@ -165,7 +167,7 @@ func SaveObject(obj *riak.Object, bucket string, cluster StorageCluster) error {
 }
 
 // returns a riak cluster of online riak nodes.
-func GetRiakCluster(db *sqlx.DB, authOptions *riak.AuthOptions) (StorageCluster, error) {
+func GetRiakCluster(db *sql.DB, authOptions *riak.AuthOptions) (StorageCluster, error) {
 	riakServerQuery := `
 		SELECT s.host_name, s.domain_name FROM server s
 		INNER JOIN type t on s.type = t.id
@@ -216,3 +218,109 @@ func GetRiakCluster(db *sqlx.DB, authOptions *riak.AuthOptions) (StorageCluster,
 
 	return RiakStorageCluster{Cluster: cluster}, err
 }
+
+func WithCluster(db *sql.DB, authOpts *riak.AuthOptions, f func(StorageCluster) error) error {
+	log.Errorf("DEBUG WithCluster authOps: %++v\n", authOpts)
+	cluster, err := GetRiakCluster(db, authOpts)
+	if err != nil {
+		return errors.New("getting riak cluster: " + err.Error())
+	}
+	if err = cluster.Start(); err != nil {
+		return errors.New("starting riak cluster: " + err.Error())
+	}
+	defer func() {
+		if err := cluster.Stop(); err != nil {
+			log.Errorln("error stopping Riak cluster: " + err.Error())
+		}
+	}()
+	return f(cluster)
+}
+
+func WithClusterX(db *sqlx.DB, authOpts *riak.AuthOptions, f func(StorageCluster) error) error {
+	log.Errorf("DEBUG WithCluster authOps: %++v\n", authOpts)
+	cluster, err := GetRiakCluster(db.DB, authOpts)
+	if err != nil {
+		return errors.New("getting riak cluster: " + err.Error())
+	}
+	if err = cluster.Start(); err != nil {
+		return errors.New("starting riak cluster: " + err.Error())
+	}
+	defer func() {
+		if err := cluster.Stop(); err != nil {
+			log.Errorln("error stopping Riak cluster: " + err.Error())
+		}
+	}()
+	return f(cluster)
+}
+
+// returns a riak cluster of online riak nodes.
+func GetRiakClusterTx(tx *sql.Tx, authOptions *riak.AuthOptions) (StorageCluster, error) {
+	// TODO remove duplication with GetRiakCluster
+
+	riakServerQuery := `
+		SELECT s.host_name, s.domain_name FROM server s
+		INNER JOIN type t on s.type = t.id
+		INNER JOIN status st on s.status = st.id
+		WHERE t.name = 'RIAK' AND st.name = 'ONLINE'
+		`
+
+	if authOptions == nil {
+		return nil, errors.New("ERROR: no riak auth information from riak.conf, cannot authenticate to any riak servers")
+	}
+
+	var nodes []*riak.Node
+	rows, err := tx.Query(riakServerQuery)
+	if err != nil {
+		return nil, err
+	}
+	defer rows.Close()
+
+	for rows.Next() {
+		var s tc.Server
+		var n *riak.Node
+		if err := rows.Scan(&s.HostName, &s.DomainName); err != nil {
+			return nil, err
+		}
+		addr := fmt.Sprintf("%s.%s:%d", s.HostName, s.DomainName, RiakPort)
+		nodeOpts := &riak.NodeOptions{
+			RemoteAddress: addr,
+			AuthOptions:   authOptions,
+		}
+		nodeOpts.AuthOptions.TlsConfig.ServerName = fmt.Sprintf("%s.%s", s.HostName, s.DomainName)
+		n, err := riak.NewNode(nodeOpts)
+		if err != nil {
+			return nil, err
+		}
+		nodes = append(nodes, n)
+	}
+
+	if len(nodes) == 0 {
+		return nil, errors.New("ERROR: no available riak servers")
+	}
+
+	opts := &riak.ClusterOptions{
+		Nodes:             nodes,
+		ExecutionAttempts: MaxCommandExecutionAttempts,
+	}
+
+	cluster, err := riak.NewCluster(opts)
+
+	return RiakStorageCluster{Cluster: cluster}, err
+}
+
+func WithClusterTx(tx *sql.Tx, authOpts *riak.AuthOptions, f func(StorageCluster) error) error {
+	log.Errorf("DEBUG WithCluster authOps: %++v\n", authOpts)
+	cluster, err := GetRiakClusterTx(tx, authOpts)
+	if err != nil {
+		return errors.New("getting riak cluster: " + err.Error())
+	}
+	if err = cluster.Start(); err != nil {
+		return errors.New("starting riak cluster: " + err.Error())
+	}
+	defer func() {
+		if err := cluster.Stop(); err != nil {
+			log.Errorln("error stopping Riak cluster: " + err.Error())
+		}
+	}()
+	return f(cluster)
+}
diff --git a/traffic_ops/traffic_ops_golang/routes.go b/traffic_ops/traffic_ops_golang/routes.go
index 6976ca4..a1bf346 100644
--- a/traffic_ops/traffic_ops_golang/routes.go
+++ b/traffic_ops/traffic_ops_golang/routes.go
@@ -37,6 +37,7 @@ import (
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/cdn"
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/coordinate"
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/crconfig"
+	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/deliveryservice"
 	dsrequest "github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/deliveryservice/request"
 	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/deliveryservice/request/comment"
 	dsserver "github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/deliveryservice/servers"
@@ -268,16 +269,31 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) {
 		{1.1, http.MethodPost, `parameterprofile/?$`, profileparameter.PostParamProfile(d.DB.DB), auth.PrivLevelOperations, Authenticated, nil},
 		{1.1, http.MethodDelete, `profileparameters/{profileId}/{parameterId}$`, api.DeleteHandler(profileparameter.GetRefType(), d.DB), auth.PrivLevelOperations, Authenticated, nil},
 
-		//SSLKeys deliveryservice endpoints here that are marked  marked as '-wip' need to have tenancy checks added
-		{1.3, http.MethodGet, `deliveryservices-wip/xmlId/{xmlID}/sslkeys$`, getDeliveryServiceSSLKeysByXMLIDHandler(d.DB, d.Config), auth.PrivLevelAdmin, Authenticated, nil},
-		{1.3, http.MethodGet, `deliveryservices-wip/hostname/{hostName}/sslkeys$`, getDeliveryServiceSSLKeysByHostNameHandler(d.DB, d.Config), auth.PrivLevelAdmin, Authenticated, nil},
-		{1.3, http.MethodPost, `deliveryservices-wip/hostname/{hostName}/sslkeys/add$`, addDeliveryServiceSSLKeysHandler(d.DB, d.Config), auth.PrivLevelAdmin, Authenticated, nil},
-
 		//CRConfig
 		{1.1, http.MethodGet, `cdns/{cdn}/snapshot/?$`, crconfig.SnapshotGetHandler(d.DB, d.Config), crconfig.PrivLevel, Authenticated, nil},
 		{1.1, http.MethodGet, `cdns/{cdn}/snapshot/new/?$`, crconfig.Handler(d.DB, d.Config), crconfig.PrivLevel, Authenticated, nil},
 		{1.1, http.MethodPut, `cdns/{id}/snapshot/?$`, crconfig.SnapshotHandler(d.DB, d.Config), crconfig.PrivLevel, Authenticated, nil},
 		{1.1, http.MethodPut, `snapshot/{cdn}/?$`, crconfig.SnapshotHandler(d.DB, d.Config), crconfig.PrivLevel, Authenticated, nil},
+
+		//SSLKeys deliveryservice endpoints here that are marked  marked as '-wip' need to have tenancy checks added
+		{1.3, http.MethodGet, `deliveryservices-wip/xmlId/{xmlID}/sslkeys$`, getDeliveryServiceSSLKeysByXMLIDHandler(d.DB, d.Config), auth.PrivLevelAdmin, Authenticated, nil},
+		{1.3, http.MethodGet, `deliveryservices-wip/hostname/{hostName}/sslkeys$`, getDeliveryServiceSSLKeysByHostNameHandler(d.DB, d.Config), auth.PrivLevelAdmin, Authenticated, nil},
+		{1.3, http.MethodPost, `deliveryservices-wip/hostname/{hostName}/sslkeys/add$`, addDeliveryServiceSSLKeysHandler(d.DB, d.Config), auth.PrivLevelAdmin, Authenticated, nil},
+
+		//DeliveryServices
+		{1.3, http.MethodGet, `deliveryservices/?(\.json)?$`, api.ReadHandler(deliveryservice.GetRefTypeV13(d.Config, d.DB), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
+		{1.1, http.MethodGet, `deliveryservices/?(\.json)?$`, api.ReadHandler(deliveryservice.GetRefTypeV12(d.Config, d.DB), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
+		{1.3, http.MethodGet, `deliveryservices/{id}$`, api.ReadHandler(deliveryservice.GetRefTypeV13(d.Config, d.DB), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
+		{1.1, http.MethodGet, `deliveryservices/{id}$`, api.ReadHandler(deliveryservice.GetRefTypeV12(d.Config, d.DB), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
+		{1.3, http.MethodPost, `deliveryservices/?(\.json)?$`, deliveryservice.CreateV13(d.DB, d.Config), auth.PrivLevelOperations, Authenticated, nil},
+		{1.1, http.MethodPost, `deliveryservices/?(\.json)?$`, deliveryservice.CreateV12(d.DB, d.Config), auth.PrivLevelOperations, Authenticated, nil},
+		{1.3, http.MethodPut, `deliveryservices/{id}$`, deliveryservice.UpdateV13(d.DB, d.Config), auth.PrivLevelOperations, Authenticated, nil},
+		{1.1, http.MethodPut, `deliveryservices/{id}$`, deliveryservice.UpdateV12(d.DB, d.Config), auth.PrivLevelOperations, Authenticated, nil},
+		{1.3, http.MethodDelete, `deliveryservices/{id}$`, api.DeleteHandler(deliveryservice.GetRefTypeV13(d.Config, d.DB), d.DB), auth.PrivLevelOperations, Authenticated, nil},
+		{1.1, http.MethodDelete, `deliveryservices/{id}$`, api.DeleteHandler(deliveryservice.GetRefTypeV12(d.Config, d.DB), d.DB), auth.PrivLevelOperations, Authenticated, nil},
+
+		//System
+		{1.1, http.MethodGet, `system/info/?(\.json)?$`, systeminfo.Handler(d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
 	}
 
 	// rawRoutes are served at the root path. These should be almost exclusively old Perl pre-API routes, which have yet to be converted in all clients. New routes should be in the versioned API path.
diff --git a/traffic_ops/traffic_ops_golang/urisigning.go b/traffic_ops/traffic_ops_golang/urisigning.go
index 50679fd..5bf4082 100644
--- a/traffic_ops/traffic_ops_golang/urisigning.go
+++ b/traffic_ops/traffic_ops_golang/urisigning.go
@@ -90,7 +90,7 @@ func getURIsignkeysHandler(db *sqlx.DB, cfg config.Config) http.HandlerFunc {
 		}
 
 		// create and start a cluster
-		cluster, err := riaksvc.GetRiakCluster(db, cfg.RiakAuthOptions)
+		cluster, err := riaksvc.GetRiakCluster(db.DB, cfg.RiakAuthOptions)
 		if err != nil {
 			handleErr(http.StatusInternalServerError, err)
 			return
@@ -172,7 +172,7 @@ func removeDeliveryServiceURIKeysHandler(db *sqlx.DB, cfg config.Config) http.Ha
 		}
 
 		// create and start a cluster
-		cluster, err := riaksvc.GetRiakCluster(db, cfg.RiakAuthOptions)
+		cluster, err := riaksvc.GetRiakCluster(db.DB, cfg.RiakAuthOptions)
 		if err != nil {
 			handleErr(http.StatusInternalServerError, err)
 			return
@@ -281,7 +281,7 @@ func saveDeliveryServiceURIKeysHandler(db *sqlx.DB, cfg config.Config) http.Hand
 		}
 
 		// create and start a cluster
-		cluster, err := riaksvc.GetRiakCluster(db, cfg.RiakAuthOptions)
+		cluster, err := riaksvc.GetRiakCluster(db.DB, cfg.RiakAuthOptions)
 		if err != nil {
 			handleErr(http.StatusInternalServerError, err)
 			return

-- 
To stop receiving notification emails like this one, please contact
dewrich@apache.org.

[incubator-trafficcontrol] 03/03: Add TO Go Deliveryservices Integration Tests

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dewrich pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-trafficcontrol.git

commit 0164fb8bbd8ffeb415ee07399ddb5f431cbd9599
Author: Robert Butts <ro...@apache.org>
AuthorDate: Mon May 28 21:19:09 2018 -0600

    Add TO Go Deliveryservices Integration Tests
---
 traffic_ops/client/v13/deliveryservice.go          | 253 +++++++++++++++++++++
 .../client/v13/deliveryservice_endpoints.go        |  66 ++++++
 traffic_ops/client/v13/endpoints.go                |  18 ++
 traffic_ops/client/v13/tenant.go                   |  86 +++++++
 traffic_ops/client/v13/tenant_endpoints.go         |  25 ++
 traffic_ops/client/v13/util.go                     |  65 ++++++
 .../testing/api/v13/deliveryservices_test.go       | 225 ++++++++++++++++++
 traffic_ops/testing/api/v13/tc-fixtures.json       | 147 ++++++++++--
 .../traffic_ops_golang/api/shared_handlers.go      |   6 +-
 .../deliveryservice/deliveryservices_test.go       |  54 -----
 .../deliveryservice/deliveryservicesv12.go         |  14 +-
 .../deliveryservice/deliveryservicesv13.go         |  51 +++--
 .../deliveryservice/handlers_test.go               |   2 +-
 .../deliveryservice/request/requests_test.go       |  20 +-
 .../riaksvc/riak_services_test.go                  |   6 +-
 traffic_ops/traffic_ops_golang/routes.go           |  12 +-
 16 files changed, 943 insertions(+), 107 deletions(-)

diff --git a/traffic_ops/client/v13/deliveryservice.go b/traffic_ops/client/v13/deliveryservice.go
new file mode 100644
index 0000000..9e5e901
--- /dev/null
+++ b/traffic_ops/client/v13/deliveryservice.go
@@ -0,0 +1,253 @@
+/*
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package v13
+
+import (
+	"encoding/json"
+	"strconv"
+
+	tc "github.com/apache/incubator-trafficcontrol/lib/go-tc"
+)
+
+// DeliveryServices gets an array of DeliveryServices
+// Deprecated: use GetDeliveryServices
+func (to *Session) DeliveryServices() ([]tc.DeliveryService, error) {
+	dses, _, err := to.GetDeliveryServices()
+	return dses, err
+}
+
+func (to *Session) GetDeliveryServices() ([]tc.DeliveryService, ReqInf, error) {
+	var data tc.GetDeliveryServiceResponse
+	reqInf, err := get(to, deliveryServicesEp(), &data)
+	if err != nil {
+		return nil, reqInf, err
+	}
+
+	return data.Response, reqInf, nil
+}
+
+// DeliveryServicesByServer gets an array of all DeliveryServices with the given server ID assigend.
+// Deprecated: use GetDeliveryServicesByServer
+func (to *Session) DeliveryServicesByServer(id int) ([]tc.DeliveryService, error) {
+	dses, _, err := to.GetDeliveryServicesByServer(id)
+	return dses, err
+}
+
+func (to *Session) GetDeliveryServicesByServer(id int) ([]tc.DeliveryService, ReqInf, error) {
+	var data tc.GetDeliveryServiceResponse
+	reqInf, err := get(to, deliveryServicesByServerEp(strconv.Itoa(id)), &data)
+	if err != nil {
+		return nil, reqInf, err
+	}
+
+	return data.Response, reqInf, nil
+}
+
+// DeliveryService gets the DeliveryService for the ID it's passed
+// Deprecated: use GetDeliveryService
+func (to *Session) DeliveryService(id string) (*tc.DeliveryService, error) {
+	ds, _, err := to.GetDeliveryService(id)
+	return ds, err
+}
+
+func (to *Session) GetDeliveryService(id string) (*tc.DeliveryService, ReqInf, error) {
+	var data tc.GetDeliveryServiceResponse
+	reqInf, err := get(to, deliveryServiceEp(id), &data)
+	if err != nil {
+		return nil, reqInf, err
+	}
+	if len(data.Response) == 0 {
+		return nil, reqInf, nil
+	}
+	return &data.Response[0], reqInf, nil
+}
+
+// CreateDeliveryService creates the DeliveryService it's passed
+func (to *Session) CreateDeliveryService(ds *tc.DeliveryService) (*tc.CreateDeliveryServiceResponse, error) {
+	var data tc.CreateDeliveryServiceResponse
+	jsonReq, err := json.Marshal(ds)
+	if err != nil {
+		return nil, err
+	}
+	err = post(to, deliveryServicesEp(), jsonReq, &data)
+	if err != nil {
+		return nil, err
+	}
+
+	return &data, nil
+}
+
+// UpdateDeliveryService updates the DeliveryService matching the ID it's passed with
+// the DeliveryService it is passed
+func (to *Session) UpdateDeliveryService(id string, ds *tc.DeliveryService) (*tc.UpdateDeliveryServiceResponse, error) {
+	var data tc.UpdateDeliveryServiceResponse
+	jsonReq, err := json.Marshal(ds)
+	if err != nil {
+		return nil, err
+	}
+	err = put(to, deliveryServiceEp(id), jsonReq, &data)
+	if err != nil {
+		return nil, err
+	}
+
+	return &data, nil
+}
+
+// DeleteDeliveryService deletes the DeliveryService matching the ID it's passed
+func (to *Session) DeleteDeliveryService(id string) (*tc.DeleteDeliveryServiceResponse, error) {
+	var data tc.DeleteDeliveryServiceResponse
+	err := del(to, deliveryServiceEp(id), &data)
+	if err != nil {
+		return nil, err
+	}
+
+	return &data, nil
+}
+
+// DeliveryServiceState gets the DeliveryServiceState for the ID it's passed
+// Deprecated: use GetDeliveryServiceState
+func (to *Session) DeliveryServiceState(id string) (*tc.DeliveryServiceState, error) {
+	dss, _, err := to.GetDeliveryServiceState(id)
+	return dss, err
+}
+
+func (to *Session) GetDeliveryServiceState(id string) (*tc.DeliveryServiceState, ReqInf, error) {
+	var data tc.DeliveryServiceStateResponse
+	reqInf, err := get(to, deliveryServiceStateEp(id), &data)
+	if err != nil {
+		return nil, reqInf, err
+	}
+
+	return &data.Response, reqInf, nil
+}
+
+// DeliveryServiceHealth gets the DeliveryServiceHealth for the ID it's passed
+// Deprecated: use GetDeliveryServiceHealth
+func (to *Session) DeliveryServiceHealth(id string) (*tc.DeliveryServiceHealth, error) {
+	dsh, _, err := to.GetDeliveryServiceHealth(id)
+	return dsh, err
+}
+
+func (to *Session) GetDeliveryServiceHealth(id string) (*tc.DeliveryServiceHealth, ReqInf, error) {
+	var data tc.DeliveryServiceHealthResponse
+	reqInf, err := get(to, deliveryServiceHealthEp(id), &data)
+	if err != nil {
+		return nil, reqInf, err
+	}
+
+	return &data.Response, reqInf, nil
+}
+
+// DeliveryServiceCapacity gets the DeliveryServiceCapacity for the ID it's passed
+// Deprecated: use GetDeliveryServiceCapacity
+func (to *Session) DeliveryServiceCapacity(id string) (*tc.DeliveryServiceCapacity, error) {
+	dsc, _, err := to.GetDeliveryServiceCapacity(id)
+	return dsc, err
+}
+
+func (to *Session) GetDeliveryServiceCapacity(id string) (*tc.DeliveryServiceCapacity, ReqInf, error) {
+	var data tc.DeliveryServiceCapacityResponse
+	reqInf, err := get(to, deliveryServiceCapacityEp(id), &data)
+	if err != nil {
+		return nil, reqInf, err
+	}
+
+	return &data.Response, reqInf, nil
+}
+
+// DeliveryServiceRouting gets the DeliveryServiceRouting for the ID it's passed
+// Deprecated: use GetDeliveryServiceRouting
+func (to *Session) DeliveryServiceRouting(id string) (*tc.DeliveryServiceRouting, error) {
+	dsr, _, err := to.GetDeliveryServiceRouting(id)
+	return dsr, err
+}
+
+func (to *Session) GetDeliveryServiceRouting(id string) (*tc.DeliveryServiceRouting, ReqInf, error) {
+	var data tc.DeliveryServiceRoutingResponse
+	reqInf, err := get(to, deliveryServiceRoutingEp(id), &data)
+	if err != nil {
+		return nil, reqInf, err
+	}
+
+	return &data.Response, reqInf, nil
+}
+
+// DeliveryServiceServer gets the DeliveryServiceServer
+// Deprecated: use GetDeliveryServiceServer
+func (to *Session) DeliveryServiceServer(page, limit string) ([]tc.DeliveryServiceServer, error) {
+	dss, _, err := to.GetDeliveryServiceServer(page, limit)
+	return dss, err
+}
+
+func (to *Session) GetDeliveryServiceServer(page, limit string) ([]tc.DeliveryServiceServer, ReqInf, error) {
+	var data tc.DeliveryServiceServerResponse
+	reqInf, err := get(to, deliveryServiceServerEp(page, limit), &data)
+	if err != nil {
+		return nil, reqInf, err
+	}
+
+	return data.Response, reqInf, nil
+}
+
+// DeliveryServiceRegexes gets the DeliveryService regexes
+// Deprecated: use GetDeliveryServiceRegexes
+func (to *Session) DeliveryServiceRegexes() ([]tc.DeliveryServiceRegexes, error) {
+	dsrs, _, err := to.GetDeliveryServiceRegexes()
+	return dsrs, err
+}
+func (to *Session) GetDeliveryServiceRegexes() ([]tc.DeliveryServiceRegexes, ReqInf, error) {
+	var data tc.DeliveryServiceRegexResponse
+	reqInf, err := get(to, deliveryServiceRegexesEp(), &data)
+	if err != nil {
+		return nil, reqInf, err
+	}
+
+	return data.Response, reqInf, nil
+}
+
+// DeliveryServiceSSLKeysByID gets the DeliveryServiceSSLKeys by ID
+// Deprecated: use GetDeliveryServiceSSLKeysByID
+func (to *Session) DeliveryServiceSSLKeysByID(id string) (*tc.DeliveryServiceSSLKeys, error) {
+	dsks, _, err := to.GetDeliveryServiceSSLKeysByID(id)
+	return dsks, err
+}
+
+func (to *Session) GetDeliveryServiceSSLKeysByID(id string) (*tc.DeliveryServiceSSLKeys, ReqInf, error) {
+	var data tc.DeliveryServiceSSLKeysResponse
+	reqInf, err := get(to, deliveryServiceSSLKeysByIDEp(id), &data)
+	if err != nil {
+		return nil, reqInf, err
+	}
+
+	return &data.Response, reqInf, nil
+}
+
+// DeliveryServiceSSLKeysByHostname gets the DeliveryServiceSSLKeys by Hostname
+// Deprecated: use GetDeliveryServiceSSLKeysByHostname
+func (to *Session) DeliveryServiceSSLKeysByHostname(hostname string) (*tc.DeliveryServiceSSLKeys, error) {
+	dsks, _, err := to.GetDeliveryServiceSSLKeysByHostname(hostname)
+	return dsks, err
+}
+
+func (to *Session) GetDeliveryServiceSSLKeysByHostname(hostname string) (*tc.DeliveryServiceSSLKeys, ReqInf, error) {
+	var data tc.DeliveryServiceSSLKeysResponse
+	reqInf, err := get(to, deliveryServiceSSLKeysByHostnameEp(hostname), &data)
+	if err != nil {
+		return nil, reqInf, err
+	}
+
+	return &data.Response, reqInf, nil
+}
diff --git a/traffic_ops/client/v13/deliveryservice_endpoints.go b/traffic_ops/client/v13/deliveryservice_endpoints.go
new file mode 100644
index 0000000..c0251cd
--- /dev/null
+++ b/traffic_ops/client/v13/deliveryservice_endpoints.go
@@ -0,0 +1,66 @@
+/*
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package v13
+
+const dsPath = "/deliveryservices"
+
+func deliveryServicesEp() string {
+	return apiBase + dsPath + ".json"
+}
+
+func deliveryServicesByServerEp(id string) string {
+	return apiBase + "/servers/" + id + dsPath + ".json"
+}
+
+func deliveryServiceBaseEp(id string) string {
+	return apiBase + dsPath + "/" + id
+}
+
+func deliveryServiceEp(id string) string {
+	return deliveryServiceBaseEp(id) + ".json"
+}
+
+func deliveryServiceStateEp(id string) string {
+	return deliveryServiceBaseEp(id) + "/state.json"
+}
+
+func deliveryServiceHealthEp(id string) string {
+	return deliveryServiceBaseEp(id) + "/health.json"
+}
+
+func deliveryServiceCapacityEp(id string) string {
+	return deliveryServiceBaseEp(id) + "/capacity.json"
+}
+
+func deliveryServiceRoutingEp(id string) string {
+	return deliveryServiceBaseEp(id) + "/routing.json"
+}
+
+func deliveryServiceServerEp(page, limit string) string {
+	return apiBase + "/deliveryserviceserver.json?page=" + page + "&limit=" + limit
+}
+
+func deliveryServiceRegexesEp() string {
+	return apiBase + "/deliveryservices_regexes.json"
+}
+
+func deliveryServiceSSLKeysByIDEp(id string) string {
+	return apiBase + dsPath + "/xmlId/" + id + "/sslkeys.json"
+}
+
+func deliveryServiceSSLKeysByHostnameEp(hostname string) string {
+	return apiBase + dsPath + "/hostname/" + hostname + "/sslkeys.json"
+}
diff --git a/traffic_ops/client/v13/endpoints.go b/traffic_ops/client/v13/endpoints.go
new file mode 100644
index 0000000..8b1e7ed
--- /dev/null
+++ b/traffic_ops/client/v13/endpoints.go
@@ -0,0 +1,18 @@
+/*
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package v13
+
+const apiBase = "/api/1.3"
diff --git a/traffic_ops/client/v13/tenant.go b/traffic_ops/client/v13/tenant.go
new file mode 100644
index 0000000..a40aa82
--- /dev/null
+++ b/traffic_ops/client/v13/tenant.go
@@ -0,0 +1,86 @@
+/*
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package v13
+
+import (
+	"encoding/json"
+
+	tc "github.com/apache/incubator-trafficcontrol/lib/go-tc"
+)
+
+// Tenants gets an array of Tenants
+func (to *Session) Tenants() ([]tc.Tenant, ReqInf, error) {
+	var data tc.GetTenantsResponse
+	reqInf, err := get(to, tenantsEp(), &data)
+	if err != nil {
+		return nil, reqInf, err
+	}
+
+	return data.Response, reqInf, nil
+}
+
+// Tenant gets the Tenant for the ID it's passed
+func (to *Session) Tenant(id string) (*tc.Tenant, ReqInf, error) {
+	var data tc.GetTenantsResponse
+	reqInf, err := get(to, tenantEp(id), &data)
+	if err != nil {
+		return nil, reqInf, err
+	}
+
+	return &data.Response[0], reqInf, nil
+}
+
+// CreateTenant creates the Tenant it's passed
+func (to *Session) CreateTenant(t *tc.Tenant) (*tc.TenantResponse, error) {
+	var data tc.TenantResponse
+	jsonReq, err := json.Marshal(t)
+	if err != nil {
+		return nil, err
+	}
+	err = post(to, tenantsEp(), jsonReq, &data)
+	if err != nil {
+		return nil, err
+	}
+
+	return &data, nil
+}
+
+// UpdateTenant updates the Tenant matching the ID it's passed with
+// the Tenant it is passed
+func (to *Session) UpdateTenant(id string, t *tc.Tenant) (*tc.TenantResponse, error) {
+	var data tc.TenantResponse
+	jsonReq, err := json.Marshal(t)
+	if err != nil {
+		return nil, err
+	}
+	err = put(to, tenantEp(id), jsonReq, &data)
+	if err != nil {
+		return nil, err
+	}
+
+	return &data, nil
+}
+
+// DeleteTenant deletes the Tenant matching the ID it's passed
+func (to *Session) DeleteTenant(id string) (*tc.DeleteTenantResponse, error) {
+	var data tc.DeleteTenantResponse
+	err := del(to, tenantEp(id), &data)
+	if err != nil {
+		return nil, err
+	}
+
+	return &data, nil
+}
diff --git a/traffic_ops/client/v13/tenant_endpoints.go b/traffic_ops/client/v13/tenant_endpoints.go
new file mode 100644
index 0000000..d3e2d35
--- /dev/null
+++ b/traffic_ops/client/v13/tenant_endpoints.go
@@ -0,0 +1,25 @@
+/*
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package v13
+
+const tenantPath = "/tenants"
+
+func tenantsEp() string {
+	return apiBase + tenantPath
+}
+
+func tenantEp(id string) string {
+	return apiBase + tenantPath + "/" + id
+}
diff --git a/traffic_ops/client/v13/util.go b/traffic_ops/client/v13/util.go
new file mode 100644
index 0000000..a3c05e3
--- /dev/null
+++ b/traffic_ops/client/v13/util.go
@@ -0,0 +1,65 @@
+/*
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+package v13
+import (
+	"encoding/json"
+	"io/ioutil"
+	"errors"
+)
+
+func get(to *Session, endpoint string, respStruct interface{}) (ReqInf, error) {
+	return makeReq(to, "GET", endpoint, nil, respStruct)
+}
+
+func post(to *Session, endpoint string, body []byte, respStruct interface{}) error {
+	_, err := makeReq(to, "POST", endpoint, body, respStruct)
+	return err
+}
+
+func put(to *Session, endpoint string, body []byte, respStruct interface{}) error {
+	_, err := makeReq(to, "PUT", endpoint, body, respStruct)
+	return err
+}
+
+func del(to *Session, endpoint string, respStruct interface{}) error {
+	_, err := makeReq(to, "DELETE", endpoint, nil, respStruct)
+	return err
+}
+
+func makeReq(to *Session, method, endpoint string, body []byte, respStruct interface{}) (ReqInf, error) {
+	resp, remoteAddr, err := to.request(method, endpoint, body) // TODO change to getBytesWithTTL
+	reqInf := ReqInf{RemoteAddr: remoteAddr, CacheHitStatus: CacheHitStatusMiss}
+	if err != nil {
+		return reqInf, err
+	}
+	defer resp.Body.Close()
+
+	bts, err := ioutil.ReadAll(resp.Body)
+	if err != nil {
+		return reqInf, errors.New("reading body: " + err.Error())
+	}
+
+	if err := json.Unmarshal(bts, respStruct); err != nil {
+		return reqInf, errors.New("unmarshalling body '" + string(body) + "': " + err.Error())
+	}
+
+	// debug
+	// if err := json.NewDecoder(resp.Body).Decode(respStruct); err != nil {
+	// 	return reqInf, err
+	// }
+
+	return reqInf, nil
+}
diff --git a/traffic_ops/testing/api/v13/deliveryservices_test.go b/traffic_ops/testing/api/v13/deliveryservices_test.go
new file mode 100644
index 0000000..bbe1987
--- /dev/null
+++ b/traffic_ops/testing/api/v13/deliveryservices_test.go
@@ -0,0 +1,225 @@
+package v13
+
+/*
+
+   Licensed under the Apache License, Version 2.0 (the "License");
+   you may not use this file except in compliance with the License.
+   You may obtain a copy of the License at
+
+   http://www.apache.org/licenses/LICENSE-2.0
+
+   Unless required by applicable law or agreed to in writing, software
+   distributed under the License is distributed on an "AS IS" BASIS,
+   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+   See the License for the specific language governing permissions and
+   limitations under the License.
+*/
+
+import (
+	"strconv"
+
+	"github.com/apache/incubator-trafficcontrol/lib/go-log"
+	"github.com/apache/incubator-trafficcontrol/lib/go-tc"
+	"testing"
+)
+
+func TestDeliveryServices(t *testing.T) {
+	CreateTestCDNs(t)
+	CreateTestTypes(t)
+	CreateTestProfiles(t)
+	CreateTestStatuses(t)
+	CreateTestDivisions(t)
+	CreateTestRegions(t)
+	CreateTestPhysLocations(t)
+	CreateTestCacheGroups(t)
+	CreateTestServers(t)
+	CreateTestDeliveryServices(t)
+	UpdateTestDeliveryServices(t)
+	GetTestDeliveryServices(t)
+	DeleteTestDeliveryServices(t)
+	DeleteTestServers(t)
+	DeleteTestCacheGroups(t)
+	DeleteTestPhysLocations(t)
+	DeleteTestRegions(t)
+	DeleteTestDivisions(t)
+	DeleteTestStatuses(t)
+	DeleteTestProfiles(t)
+	DeleteTestTypes(t)
+	DeleteTestCDNs(t)
+}
+
+func CreateTestDeliveryServices(t *testing.T) {
+	log.Debugln("CreateTestDeliveryServices")
+
+	// TODO delete on DS delete
+	pl := tc.Parameter{
+		ConfigFile: "remap.config",
+		Name:       "location",
+		Value:      "/remap/config/location/parameter/",
+	}
+	_, _, err := TOSession.CreateParameter(pl)
+	if err != nil {
+		t.Fatalf("cannot create parameter: %v\n", err)
+	}
+	for _, ds := range testData.DeliveryServices {
+		respCDNs, _, err := TOSession.GetCDNByName(ds.CDNName)
+		if err != nil {
+			t.Fatalf("cannot GET CDN - %v\n", err)
+		}
+		if len(respCDNs) < 1 {
+			t.Fatalf("cannot GET CDN - no CDNs\n")
+		}
+		ds.CDNID = respCDNs[0].ID
+
+		respTypes, _, err := TOSession.GetTypeByName(ds.Type)
+		if err != nil {
+			t.Fatalf("cannot GET Type by name: %v\n", err)
+		}
+		if len(respTypes) < 1 {
+			t.Fatalf("cannot GET Type - no Types\n")
+		}
+		ds.TypeID = respTypes[0].ID
+
+		if ds.ProfileName != "" {
+			respProfiles, _, err := TOSession.GetProfileByName(ds.ProfileName)
+			if err != nil {
+				t.Fatalf("cannot GET Profile by name: %v\n", err)
+			}
+			if len(respProfiles) < 1 {
+				t.Fatalf("cannot GET Profile - no Profiles\n")
+			}
+			ds.ProfileID = respProfiles[0].ID
+		}
+
+		respTenants, _, err := TOSession.Tenants()
+		if err != nil {
+			t.Fatalf("cannot GET tenants: %v\n", err)
+		}
+		if len(respTenants) < 1 {
+			t.Fatalf("cannot GET tenants: no tenants returned from Traffic Ops\n")
+		}
+		ds.TenantID = respTenants[0].ID
+
+		_, err = TOSession.CreateDeliveryService(&ds)
+		if err != nil {
+			t.Fatalf("could not CREATE delivery service '%s': %v\n", ds.XMLID, err)
+		}
+	}
+}
+
+func GetTestDeliveryServices(t *testing.T) {
+	failed := false
+	actualDSes, _, err := TOSession.GetDeliveryServices()
+	if err != nil {
+		t.Fatalf("cannot GET DeliveryServices: %v - %v\n", err, actualDSes)
+		failed = true
+	}
+	actualDSMap := map[string]tc.DeliveryService{}
+	for _, ds := range actualDSes {
+		actualDSMap[ds.XMLID] = ds
+	}
+	for _, ds := range testData.DeliveryServices {
+		if _, ok := actualDSMap[ds.XMLID]; !ok {
+			t.Errorf("GET DeliveryService missing: %v\n", ds.XMLID)
+			failed = true
+		}
+	}
+	if !failed {
+		log.Debugln("GetTestDeliveryServices() PASSED: ")
+	}
+}
+
+func UpdateTestDeliveryServices(t *testing.T) {
+	failed := false
+	firstDS := testData.DeliveryServices[0]
+
+	dses, _, err := TOSession.GetDeliveryServices()
+	if err != nil {
+		failed = true
+		t.Fatalf("cannot GET Delivery Services: %v\n", err)
+	}
+
+	remoteDS := tc.DeliveryService{}
+	found := false
+	for _, ds := range dses {
+		if ds.XMLID == firstDS.XMLID {
+			found = true
+			remoteDS = ds
+			break
+		}
+	}
+	if !found {
+		failed = true
+		t.Fatalf("GET Delivery Services missing: %v\n", firstDS.XMLID)
+	}
+
+	updatedLongDesc := "something different"
+	updatedMaxDNSAnswers := 164598
+	remoteDS.LongDesc = updatedLongDesc
+	remoteDS.MaxDNSAnswers = updatedMaxDNSAnswers
+
+	if updateResp, err := TOSession.UpdateDeliveryService(strconv.Itoa(remoteDS.ID), &remoteDS); err != nil {
+		t.Errorf("cannot UPDATE DeliveryService by ID: %v - %v\n", err, updateResp)
+	}
+
+	// Retrieve the server to check rack and interfaceName values were updated
+	resp, _, err := TOSession.GetDeliveryService(strconv.Itoa(remoteDS.ID))
+	if err != nil {
+		failed = true
+		t.Fatalf("cannot GET Delivery Service by ID: %v - %v\n", remoteDS.XMLID, err)
+	}
+	if resp == nil {
+		failed = true
+		t.Fatalf("cannot GET Delivery Service by ID: %v - nil\n", remoteDS.XMLID)
+	}
+
+	if resp.LongDesc != updatedLongDesc || resp.MaxDNSAnswers != updatedMaxDNSAnswers {
+		failed = true
+		t.Errorf("results do not match actual: %s, expected: %s\n", resp.LongDesc, updatedLongDesc)
+		t.Errorf("results do not match actual: %v, expected: %v\n", resp.MaxDNSAnswers, updatedMaxDNSAnswers)
+	}
+	if !failed {
+		log.Debugln("UpdatedTestDeliveryServices() PASSED: ")
+	}
+}
+
+func DeleteTestDeliveryServices(t *testing.T) {
+	dses, _, err := TOSession.GetDeliveryServices()
+	failed := false
+	if err != nil {
+		failed = true
+		t.Fatalf("cannot GET Servers: %v\n", err)
+	}
+	for _, testDS := range testData.DeliveryServices {
+		ds := tc.DeliveryService{}
+		found := false
+		for _, realDS := range dses {
+			if realDS.XMLID == testDS.XMLID {
+				ds = realDS
+				found = true
+				break
+			}
+		}
+		if !found {
+			failed = true
+			t.Fatalf("DeliveryService not found in Traffic Ops: %v\n", ds.XMLID)
+		}
+
+		delResp, err := TOSession.DeleteDeliveryService(strconv.Itoa(ds.ID))
+		if err != nil {
+			failed = true
+			t.Errorf("cannot DELETE DeliveryService by ID: %v - %v\n", err, delResp)
+		}
+
+		// Retrieve the Server to see if it got deleted
+		foundDS, err := TOSession.DeliveryService(strconv.Itoa(ds.ID))
+		if err == nil && foundDS != nil {
+			failed = true
+			t.Errorf("expected Delivery Service: %s to be deleted\n", ds.XMLID)
+		}
+	}
+
+	if !failed {
+		log.Debugln("DeleteTestDeliveryServices() PASSED: ")
+	}
+}
diff --git a/traffic_ops/testing/api/v13/tc-fixtures.json b/traffic_ops/testing/api/v13/tc-fixtures.json
index 5fa7ac6..2653051 100644
--- a/traffic_ops/testing/api/v13/tc-fixtures.json
+++ b/traffic_ops/testing/api/v13/tc-fixtures.json
@@ -52,7 +52,7 @@
     ],
     "cdns": [
         {
-            "dnssecEnabled": true,
+            "dnssecEnabled": false,
             "domainName": "test.cdn1.net",
             "name": "cdn1"
         },
@@ -62,12 +62,12 @@
             "name": "cdn2"
         },
         {
-            "dnssecEnabled": true,
+            "dnssecEnabled": false,
             "domainName": "test.cdn3.net",
             "name": "cdn3"
         },
         {
-            "dnssecEnabled": true,
+            "dnssecEnabled": false,
             "domainName": "test.cdn4.net",
             "name": "cdn4"
         }
@@ -170,16 +170,137 @@
     ],
     "deliveryServices": [
         {
-            "active": false,
-            "dscp": 40,
-            "tenantName": "tenant1",
-            "xmlId": "ds1"
-        },
-        {
-            "active": false,
-            "dscp": 40,
-            "tenantName": "tenant2",
-            "xmlId": "ds2"
+          "active": true,
+          "cacheurl": "cacheUrl1",
+          "ccrDnsTtl": 3600,
+          "cdnName": "cdn1",
+          "checkPath": "",
+          "deepCachingType": "NEVER",
+          "displayName": "ds1DisplayName",
+          "dnsBypassCname": null,
+          "dnsBypassIp": "",
+          "dnsBypassIp6": "",
+          "dnsBypassTtl": 30,
+          "dscp": 40,
+          "edgeHeaderRewrite": "edgeHeader1",
+          "exampleURLs": [
+            "http://ccr.ds1.example.net",
+            "https://ccr.ds1.example.net"
+          ],
+          "fqPacingRate": 0,
+          "geoLimit": 0,
+          "geoLimitCountries": "",
+          "geoLimitRedirectURL": null,
+          "geoProvider": 0,
+          "globalMaxMbps": 0,
+          "globalMaxTps": 0,
+          "httpBypassFqdn": "",
+          "infoUrl": "TBD",
+          "initialDispersion": 1,
+          "ipv6RoutingEnabled": true,
+          "lastUpdated": "2018-04-06 16:48:51+00",
+          "logsEnabled": false,
+          "longDesc": "d s 1",
+          "longDesc1": "ds1",
+          "longDesc2": "ds1",
+          "matchList": [
+            {
+              "pattern": ".*\\.ds1\\..*",
+              "setNumber": 0,
+              "type": "HOST_REGEXP"
+            }
+          ],
+          "maxDnsAnswers": 0,
+          "midHeaderRewrite": "midHeader1",
+          "missLat": 41.881944,
+          "missLong": -87.627778,
+          "multiSiteOrigin": false,
+          "orgServerFqdn": "http://origin.example.net",
+          "originShield": null,
+          "profileDescription": null,
+          "profileId": null,
+          "profileName": null,
+          "protocol": 2,
+          "qstringIgnore": 1,
+          "rangeRequestHandling": 0,
+          "regexRemap": "rr1",
+          "regionalGeoBlocking": false,
+          "remapText": "@plugin=tslua.so @pparam=/opt/trafficserver/etc/trafficserver/remapPlugin1.lua",
+          "routingName": "ccr-ds1",
+          "signed": false,
+          "signingAlgorithm": "url_sig",
+          "sslKeyVersion": 2,
+          "tenant": "tenant1",
+          "tenantName": "tenant1",
+          "type": "HTTP_LIVE",
+          "xmlId": "ds1",
+          "anonymousBlockingEnabled": true
+        },
+        {
+          "active": true,
+          "cacheurl": "cacheUrl2",
+          "ccrDnsTtl": 3600,
+          "cdnName": "cdn1",
+          "checkPath": "",
+          "deepCachingType": "NEVER",
+          "displayName": "d s 1",
+          "dnsBypassCname": null,
+          "dnsBypassIp": "",
+          "dnsBypassIp6": "",
+          "dnsBypassTtl": 30,
+          "dscp": 40,
+          "edgeHeaderRewrite": "edgeRewrite2",
+          "exampleURLs": [
+            "http://ccr.ds2.example.net",
+            "https://ccr.ds2x.example.net"
+          ],
+          "fqPacingRate": 0,
+          "geoLimit": 0,
+          "geoLimitCountries": "",
+          "geoLimitRedirectURL": null,
+          "geoProvider": 0,
+          "globalMaxMbps": 0,
+          "globalMaxTps": 0,
+          "httpBypassFqdn": "",
+          "infoUrl": "TBD",
+          "initialDispersion": 1,
+          "ipv6RoutingEnabled": true,
+          "lastUpdated": "2018-04-06 16:48:51+00",
+          "logsEnabled": false,
+          "longDesc": "d s 1",
+          "longDesc1": "ds2",
+          "longDesc2": "ds2",
+          "matchList": [
+            {
+              "pattern": ".*\\.ds2\\..*",
+              "setNumber": 0,
+              "type": "HOST_REGEXP"
+            }
+          ],
+          "maxDnsAnswers": 0,
+          "midHeaderRewrite": "midRewrite2",
+          "missLat": 41.881944,
+          "missLong": -87.627778,
+          "multiSiteOrigin": false,
+          "orgServerFqdn": "http://origin.ds2.example.net",
+          "originShield": null,
+          "profileDescription": null,
+          "profileName": null,
+          "protocol": 2,
+          "qstringIgnore": 1,
+          "rangeRequestHandling": 0,
+          "regexRemap": "regexRemap2",
+          "regionalGeoBlocking": false,
+          "remapText": "@plugin=tslua.so @pparam=/opt/trafficserver/etc/trafficserver/ds2plugin.lua",
+          "routingName": "ccr-ds2",
+          "signed": false,
+          "signingAlgorithm": "url_sig",
+          "sslKeyVersion": 2,
+          "tenant": "tenant2",
+          "tenantName": "tenant2",
+          "type": "HTTP_LIVE",
+          "xmlId": "ds2",
+          "anonymousBlockingEnabled": true
         }
     ],
     "divisions": [
diff --git a/traffic_ops/traffic_ops_golang/api/shared_handlers.go b/traffic_ops/traffic_ops_golang/api/shared_handlers.go
index 9ec3cb9..061397a 100644
--- a/traffic_ops/traffic_ops_golang/api/shared_handlers.go
+++ b/traffic_ops/traffic_ops_golang/api/shared_handlers.go
@@ -27,6 +27,7 @@ import (
 	"net/http"
 	"reflect"
 	"strconv"
+	"strings"
 
 	"github.com/apache/incubator-trafficcontrol/lib/go-log"
 	"github.com/apache/incubator-trafficcontrol/lib/go-tc"
@@ -43,6 +44,9 @@ type KeyFieldInfo struct {
 }
 
 func GetIntKey(s string) (interface{}, error) {
+	if strings.HasSuffix(s, ".json") {
+		s = s[:len(s) - len(".json")]
+	}
 	return strconv.Atoi(s)
 }
 
@@ -249,7 +253,7 @@ func UpdateHandler(typeRef Updater, db *sqlx.DB) http.HandlerFunc {
 		resp := struct {
 			Response interface{} `json:"response"`
 			tc.Alerts
-		}{[]interface{}{u}, tc.CreateAlerts(tc.SuccessLevel, u.GetType()+" was updated.")}
+		}{u, tc.CreateAlerts(tc.SuccessLevel, u.GetType()+" was updated.")}
 
 		respBts, err := json.Marshal(resp)
 		if err != nil {
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_test.go b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_test.go
deleted file mode 100644
index 28c50ce..0000000
--- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_test.go
+++ /dev/null
@@ -1,54 +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 (
-	"testing"
-
-	"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
-)
-
-func getInterface(req *TODeliveryService) interface{} {
-	return req
-}
-
-func TestInterfaces(t *testing.T) {
-	r := getInterface(&TODeliveryService{})
-
-	if _, ok := r.(api.Creator); !ok {
-		t.Errorf("DeliveryService must be Creator")
-	}
-	// TODO: uncomment when ds.Reader interface is implemented
-	/*if _, ok := r.(api.Reader); !ok {
-		t.Errorf("DeliveryService must be Reader")
-	}*/
-	if _, ok := r.(api.Updater); !ok {
-		t.Errorf("DeliveryService must be Updater")
-	}
-	if _, ok := r.(api.Deleter); !ok {
-		t.Errorf("DeliveryService must be Deleter")
-	}
-	if _, ok := r.(api.Identifier); !ok {
-		t.Errorf("DeliveryService must be Identifier")
-	}
-	if _, ok := r.(api.Tenantable); !ok {
-		t.Errorf("DeliveryService must be Tenantable")
-	}
-}
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go
index c5c7ca8..cd2d22a 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv12.go
@@ -132,21 +132,19 @@ func getDSTenantIDByName(db *sql.DB, name string) (*int, bool, error) {
 	return tenantID, true, nil
 }
 
-// LoadXMLID 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) LoadXMLID(db *sqlx.DB) (bool, error) {
+// 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(db *sqlx.DB) (string, bool, error) {
 	if ds.ID == nil {
-		return false, errors.New("missing ID")
+		return "", false, errors.New("missing ID")
 	}
-
 	xmlID := ""
 	if err := db.QueryRow(`SELECT xml_id FROM deliveryservice where id = $1`, ds.ID).Scan(&xmlID); err != nil {
 		if err == sql.ErrNoRows {
-			return false, nil
+			return "", false, nil
 		}
-		return false, fmt.Errorf("querying xml_id for delivery service ID '%v': %v", *ds.ID, err)
+		return "", false, fmt.Errorf("querying xml_id for delivery service ID '%v': %v", *ds.ID, err)
 	}
-	ds.XMLID = &xmlID
-	return true, nil
+	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).
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go
index b8fab5f..4d45bad 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservicesv13.go
@@ -76,7 +76,8 @@ func (ds TODeliveryServiceV13) GetKeys() (map[string]interface{}, bool) {
 }
 
 func (ds *TODeliveryServiceV13) SetKeys(keys map[string]interface{}) {
-	ds.V12().SetKeys(keys)
+	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 {
@@ -358,11 +359,25 @@ func UpdateV13(db *sqlx.DB, cfg config.Config) http.HandlerFunc {
 			return
 		}
 
+		params, _, userErr, sysErr, errCode := api.AllParams(r, nil)
+		if userErr != nil || sysErr != nil {
+			api.HandleErr(w, r, errCode, userErr, sysErr)
+			return
+		}
+		if strings.HasSuffix(params["id"], ".json") {
+			params["id"] = params["id"][:len(params["id"])-len(".json")]
+		}
+		id, err := strconv.Atoi(params["id"])
+		if err != nil {
+			api.HandleErr(w, r, http.StatusBadRequest, errors.New("id must be an integer"), sysErr)
+		}
+
 		ds := tc.DeliveryServiceNullableV13{}
 		if err := json.NewDecoder(r.Body).Decode(&ds); err != nil {
 			api.HandleErr(w, r, http.StatusBadRequest, errors.New("malformed JSON: "+err.Error()), nil)
 			return
 		}
+		ds.ID = &id
 
 		if authorized, err := isTenantAuthorized(*user, db, &ds.DeliveryServiceNullableV12); err != nil {
 			api.HandleErr(w, r, http.StatusInternalServerError, nil, errors.New("checking tenant: "+err.Error()))
@@ -372,7 +387,7 @@ func UpdateV13(db *sqlx.DB, cfg config.Config) http.HandlerFunc {
 			return
 		}
 
-		ds, errCode, userErr, sysErr := update(db.DB, cfg, *user, &ds)
+		ds, errCode, userErr, sysErr = update(db.DB, cfg, *user, &ds)
 		if userErr != nil || sysErr != nil {
 			api.HandleErr(w, r, errCode, userErr, sysErr)
 			return
@@ -521,19 +536,31 @@ func (ds *TODeliveryServiceV13) Delete(db *sqlx.DB, user auth.CurrentUser) (erro
 	if ds.ID == nil {
 		log.Errorln("TODeliveryServiceV13.Delete called with nil ID")
 		return tc.DBError, tc.DataMissingError
-	} else if ok, err := ds.V12().LoadXMLID(db); err != nil {
+	}
+	xmlID, ok, err := ds.V12().GetXMLID(db)
+	if err != nil {
 		log.Errorln("TODeliveryServiceV13.Delete ID '" + string(*ds.ID) + "' loading XML ID: " + err.Error())
 		return tc.DBError, tc.SystemError
-	} else if !ok {
+	}
+	if !ok {
 		log.Errorln("TODeliveryServiceV13.Delete ID '" + string(*ds.ID) + "' had no delivery service!")
 		return tc.DBError, tc.DataMissingError
-	} else if ds.XMLID == nil {
-		// should never happen
-		log.Errorf("TODeliveryServiceV13.Delete ID '%v' loaded nil XML ID!", *ds.ID)
+	}
+	ds.XMLID = &xmlID
+
+	// Note ds regexes MUST be deleted before the ds, because there's a ON DELETE CASCADE on deliveryservice_regex (but not on regex).
+	// Likewise, it MUST happen in a transaction with the later DS delete, so they aren't deleted if the DS delete fails.
+	if _, err := tx.Exec(`DELETE FROM regex WHERE id IN (SELECT regex FROM deliveryservice_regex WHERE deliveryservice=$1)`, *ds.ID); err != nil {
+		log.Errorln("TODeliveryServiceV13.Delete deleting regexes for delivery service: " + err.Error())
+		return tc.DBError, tc.SystemError
+	}
+
+	if _, err := tx.Exec(`DELETE FROM deliveryservice_regex WHERE deliveryservice=$1`, *ds.ID); err != nil {
+		log.Errorln("TODeliveryServiceV13.Delete deleting delivery service regexes: " + err.Error())
 		return tc.DBError, tc.SystemError
 	}
 
-	result, err := tx.Exec(`DELETE FROM deliveryservice WHERE id=$1`, ds.ID)
+	result, err := tx.Exec(`DELETE FROM deliveryservice WHERE id=$1`, *ds.ID)
 	if err != nil {
 		log.Errorln("TODeliveryServiceV13.Delete deleting delivery service: " + err.Error())
 		return tc.DBError, tc.SystemError
@@ -549,11 +576,6 @@ func (ds *TODeliveryServiceV13) Delete(db *sqlx.DB, user auth.CurrentUser) (erro
 		return fmt.Errorf("this create affected too many rows: %d", rowsAffected), tc.SystemError
 	}
 
-	if _, err := tx.Exec(`DELETE FROM deliveryservice_regex WHERE deliveryservice=$1`, ds.ID); err != nil {
-		log.Errorln("TODeliveryServiceV13.Delete deleting delivery service regexes: " + err.Error())
-		return tc.DBError, tc.SystemError
-	}
-
 	paramConfigFilePrefixes := []string{"hdr_rw_", "hdr_rw_mid_", "regex_remap_", "cacheurl_"}
 	configFiles := []string{}
 	for _, prefix := range paramConfigFilePrefixes {
@@ -595,6 +617,9 @@ func filterAuthorized(dses []tc.DeliveryServiceNullableV13, user auth.CurrentUse
 }
 
 func readGetDeliveryServices(params map[string]string, db *sqlx.DB) ([]tc.DeliveryServiceNullableV13, []error, tc.ApiErrorType) {
+	if strings.HasSuffix(params["id"], ".json") {
+		params["id"] = params["id"][:len(params["id"])-len(".json")]
+	}
 	// Query Parameters to Database Query column mappings
 	// see the fields mapped in the SQL query
 	queryParamsToSQLCols := map[string]dbhelpers.WhereColumnInfo{
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/handlers_test.go b/traffic_ops/traffic_ops_golang/deliveryservice/handlers_test.go
index 36be42a..c9ea6a7 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/handlers_test.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/handlers_test.go
@@ -32,7 +32,7 @@ import (
 // TestValidateErrors ...
 func TestValidateErrors(t *testing.T) {
 
-	ds := GetRefType()
+	ds := &TODeliveryServiceV13{}
 	if err := json.Unmarshal([]byte(errorTestCase()), &ds); err != nil {
 		fmt.Printf("err ---> %v\n", err)
 		return
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/request/requests_test.go b/traffic_ops/traffic_ops_golang/deliveryservice/request/requests_test.go
index 993351c..5dc930b 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/request/requests_test.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/request/requests_test.go
@@ -61,14 +61,18 @@ func TestGetDeliveryServiceRequest(t *testing.T) {
 	r := &TODeliveryServiceRequest{
 		ChangeType: &u,
 		Status:     &st,
-		DeliveryService: &tc.DeliveryServiceNullable{
-			XMLID:       &s,
-			CDNID:       &i,
-			LogsEnabled: &b,
-			DSCP:        nil,
-			GeoLimit:    &i,
-			Active:      &b,
-			TypeID:      &i,
+		DeliveryService: &tc.DeliveryServiceNullableV13{
+			DeliveryServiceNullableV12: tc.DeliveryServiceNullableV12{
+				DeliveryServiceNullable: tc.DeliveryServiceNullable{
+					XMLID:       &s,
+					CDNID:       &i,
+					LogsEnabled: &b,
+					DSCP:        nil,
+					GeoLimit:    &i,
+					Active:      &b,
+					TypeID:      &i,
+				},
+			},
 		},
 	}
 
diff --git a/traffic_ops/traffic_ops_golang/riaksvc/riak_services_test.go b/traffic_ops/traffic_ops_golang/riaksvc/riak_services_test.go
index d819333..746e2b1 100644
--- a/traffic_ops/traffic_ops_golang/riaksvc/riak_services_test.go
+++ b/traffic_ops/traffic_ops_golang/riaksvc/riak_services_test.go
@@ -151,7 +151,7 @@ func TestGetRiakCluster(t *testing.T) {
 	rows1.AddRow("www", "devnull.com")
 	mock.ExpectQuery("SELECT").WillReturnRows(rows1)
 
-	if _, err := GetRiakCluster(db, nil); err == nil {
+	if _, err := GetRiakCluster(db.DB, nil); err == nil {
 		t.Errorf("expected an error due to nil RiakAuthoptions in the config but, go no error.")
 	}
 
@@ -161,14 +161,14 @@ func TestGetRiakCluster(t *testing.T) {
 		TlsConfig: &tls.Config{},
 	}
 
-	if _, err := GetRiakCluster(db, &authOptions); err != nil {
+	if _, err := GetRiakCluster(db.DB, &authOptions); err != nil {
 		t.Errorf("expected no errors, actual: %s.", err)
 	}
 
 	rows2 := sqlmock.NewRows([]string{"s.host_name", "s.domain_name"})
 	mock.ExpectQuery("SELECT").WillReturnRows(rows2)
 
-	if _, err := GetRiakCluster(db, &authOptions); err == nil {
+	if _, err := GetRiakCluster(db.DB, &authOptions); err == nil {
 		t.Errorf("expected an error due to no available riak servers.")
 	}
 }
diff --git a/traffic_ops/traffic_ops_golang/routes.go b/traffic_ops/traffic_ops_golang/routes.go
index a1bf346..693bc62 100644
--- a/traffic_ops/traffic_ops_golang/routes.go
+++ b/traffic_ops/traffic_ops_golang/routes.go
@@ -283,14 +283,14 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) {
 		//DeliveryServices
 		{1.3, http.MethodGet, `deliveryservices/?(\.json)?$`, api.ReadHandler(deliveryservice.GetRefTypeV13(d.Config, d.DB), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
 		{1.1, http.MethodGet, `deliveryservices/?(\.json)?$`, api.ReadHandler(deliveryservice.GetRefTypeV12(d.Config, d.DB), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
-		{1.3, http.MethodGet, `deliveryservices/{id}$`, api.ReadHandler(deliveryservice.GetRefTypeV13(d.Config, d.DB), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
-		{1.1, http.MethodGet, `deliveryservices/{id}$`, api.ReadHandler(deliveryservice.GetRefTypeV12(d.Config, d.DB), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
+		{1.3, http.MethodGet, `deliveryservices/{id}/?(\.json)?$`, api.ReadHandler(deliveryservice.GetRefTypeV13(d.Config, d.DB), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
+		{1.1, http.MethodGet, `deliveryservices/{id}/?(\.json)?$`, api.ReadHandler(deliveryservice.GetRefTypeV12(d.Config, d.DB), d.DB), auth.PrivLevelReadOnly, Authenticated, nil},
 		{1.3, http.MethodPost, `deliveryservices/?(\.json)?$`, deliveryservice.CreateV13(d.DB, d.Config), auth.PrivLevelOperations, Authenticated, nil},
 		{1.1, http.MethodPost, `deliveryservices/?(\.json)?$`, deliveryservice.CreateV12(d.DB, d.Config), auth.PrivLevelOperations, Authenticated, nil},
-		{1.3, http.MethodPut, `deliveryservices/{id}$`, deliveryservice.UpdateV13(d.DB, d.Config), auth.PrivLevelOperations, Authenticated, nil},
-		{1.1, http.MethodPut, `deliveryservices/{id}$`, deliveryservice.UpdateV12(d.DB, d.Config), auth.PrivLevelOperations, Authenticated, nil},
-		{1.3, http.MethodDelete, `deliveryservices/{id}$`, api.DeleteHandler(deliveryservice.GetRefTypeV13(d.Config, d.DB), d.DB), auth.PrivLevelOperations, Authenticated, nil},
-		{1.1, http.MethodDelete, `deliveryservices/{id}$`, api.DeleteHandler(deliveryservice.GetRefTypeV12(d.Config, d.DB), d.DB), auth.PrivLevelOperations, Authenticated, nil},
+		{1.3, http.MethodPut, `deliveryservices/{id}/?(\.json)?$`, deliveryservice.UpdateV13(d.DB, d.Config), auth.PrivLevelOperations, Authenticated, nil},
+		{1.1, http.MethodPut, `deliveryservices/{id}/?(\.json)?$`, deliveryservice.UpdateV12(d.DB, d.Config), auth.PrivLevelOperations, Authenticated, nil},
+		{1.3, http.MethodDelete, `deliveryservices/{id}/?(\.json)?$`, api.DeleteHandler(deliveryservice.GetRefTypeV13(d.Config, d.DB), d.DB), auth.PrivLevelOperations, Authenticated, nil},
+		{1.1, http.MethodDelete, `deliveryservices/{id}/?(\.json)?$`, api.DeleteHandler(deliveryservice.GetRefTypeV12(d.Config, d.DB), d.DB), auth.PrivLevelOperations, Authenticated, nil},
 
 		//System
 		{1.1, http.MethodGet, `system/info/?(\.json)?$`, systeminfo.Handler(d.DB), auth.PrivLevelReadOnly, Authenticated, nil},

-- 
To stop receiving notification emails like this one, please contact
dewrich@apache.org.

[incubator-trafficcontrol] 01/03: Vendor miekg/dns

Posted by de...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dewrich pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-trafficcontrol.git

commit 38cbfca37c1fdd1fce2b38fb5b28794425dbfd09
Author: Robert Butts <ro...@apache.org>
AuthorDate: Tue May 1 13:58:05 2018 -0600

    Vendor miekg/dns
---
 LICENSE                                            |    4 +
 licenses/BSD-miekg-dns                             |   32 +
 .../vendor/github.com/miekg/dns/.codecov.yml       |    8 +
 .../vendor/github.com/miekg/dns/.travis.yml        |   20 +
 .../vendor/github.com/miekg/dns/AUTHORS            |    1 +
 .../vendor/github.com/miekg/dns/CONTRIBUTORS       |   10 +
 .../vendor/github.com/miekg/dns/COPYRIGHT          |    9 +
 .../vendor/github.com/miekg/dns/Gopkg.lock         |   21 +
 .../vendor/github.com/miekg/dns/Gopkg.toml         |   26 +
 .../vendor/github.com/miekg/dns/LICENSE            |   32 +
 .../vendor/github.com/miekg/dns/Makefile.fuzz      |   33 +
 .../vendor/github.com/miekg/dns/Makefile.release   |   52 +
 .../vendor/github.com/miekg/dns/README.md          |  168 +
 .../vendor/github.com/miekg/dns/client.go          |  503 +++
 .../vendor/github.com/miekg/dns/client_test.go     |  590 ++++
 .../vendor/github.com/miekg/dns/clientconfig.go    |  139 +
 .../github.com/miekg/dns/clientconfig_test.go      |  181 +
 .../github.com/miekg/dns/compress_generate.go      |  188 +
 .../vendor/github.com/miekg/dns/dane.go            |   43 +
 .../vendor/github.com/miekg/dns/defaults.go        |  288 ++
 .../vendor/github.com/miekg/dns/dns.go             |  107 +
 .../vendor/github.com/miekg/dns/dns_bench_test.go  |  230 ++
 .../vendor/github.com/miekg/dns/dns_test.go        |  320 ++
 .../vendor/github.com/miekg/dns/dnssec.go          |  784 +++++
 .../vendor/github.com/miekg/dns/dnssec_keygen.go   |  178 +
 .../vendor/github.com/miekg/dns/dnssec_keyscan.go  |  297 ++
 .../vendor/github.com/miekg/dns/dnssec_privkey.go  |   93 +
 .../vendor/github.com/miekg/dns/dnssec_test.go     |  841 +++++
 .../vendor/github.com/miekg/dns/dnsutil/util.go    |   83 +
 .../github.com/miekg/dns/dnsutil/util_test.go      |  130 +
 .../vendor/github.com/miekg/dns/doc.go             |  272 ++
 .../vendor/github.com/miekg/dns/dyn_test.go        |    3 +
 .../vendor/github.com/miekg/dns/edns.go            |  627 ++++
 .../vendor/github.com/miekg/dns/edns_test.go       |   68 +
 .../vendor/github.com/miekg/dns/example_test.go    |  146 +
 .../vendor/github.com/miekg/dns/format.go          |   87 +
 .../vendor/github.com/miekg/dns/fuzz.go            |   23 +
 .../vendor/github.com/miekg/dns/generate.go        |  159 +
 .../vendor/github.com/miekg/dns/issue_test.go      |   62 +
 .../vendor/github.com/miekg/dns/labels.go          |  191 ++
 .../vendor/github.com/miekg/dns/labels_test.go     |  201 ++
 .../vendor/github.com/miekg/dns/leak_test.go       |   71 +
 .../vendor/github.com/miekg/dns/length_test.go     |  174 +
 .../vendor/github.com/miekg/dns/msg.go             | 1177 +++++++
 .../vendor/github.com/miekg/dns/msg_generate.go    |  348 ++
 .../vendor/github.com/miekg/dns/msg_helpers.go     |  637 ++++
 .../vendor/github.com/miekg/dns/msg_test.go        |  124 +
 .../vendor/github.com/miekg/dns/nsecx.go           |  106 +
 .../vendor/github.com/miekg/dns/nsecx_test.go      |  133 +
 .../vendor/github.com/miekg/dns/parse_test.go      | 1465 ++++++++
 .../vendor/github.com/miekg/dns/privaterr.go       |  149 +
 .../vendor/github.com/miekg/dns/privaterr_test.go  |  166 +
 .../vendor/github.com/miekg/dns/rawmsg.go          |   49 +
 .../vendor/github.com/miekg/dns/remote_test.go     |   19 +
 .../vendor/github.com/miekg/dns/reverse.go         |   38 +
 .../vendor/github.com/miekg/dns/rr_test.go         |    7 +
 .../vendor/github.com/miekg/dns/sanitize.go        |   84 +
 .../vendor/github.com/miekg/dns/sanitize_test.go   |   75 +
 .../vendor/github.com/miekg/dns/scan.go            | 1007 ++++++
 .../vendor/github.com/miekg/dns/scan_rr.go         | 2199 ++++++++++++
 .../vendor/github.com/miekg/dns/scan_test.go       |   48 +
 .../vendor/github.com/miekg/dns/scanner.go         |   56 +
 .../vendor/github.com/miekg/dns/server.go          |  724 ++++
 .../vendor/github.com/miekg/dns/server_test.go     |  792 +++++
 .../vendor/github.com/miekg/dns/sig0.go            |  218 ++
 .../vendor/github.com/miekg/dns/sig0_test.go       |   89 +
 .../vendor/github.com/miekg/dns/singleinflight.go  |   57 +
 .../vendor/github.com/miekg/dns/smimea.go          |   47 +
 .../vendor/github.com/miekg/dns/tlsa.go            |   47 +
 .../vendor/github.com/miekg/dns/tsig.go            |  386 +++
 .../vendor/github.com/miekg/dns/tsig_test.go       |   52 +
 .../vendor/github.com/miekg/dns/types.go           | 1381 ++++++++
 .../vendor/github.com/miekg/dns/types_generate.go  |  272 ++
 .../vendor/github.com/miekg/dns/types_test.go      |   74 +
 .../vendor/github.com/miekg/dns/udp.go             |  102 +
 .../vendor/github.com/miekg/dns/udp_test.go        |  140 +
 .../vendor/github.com/miekg/dns/udp_windows.go     |   37 +
 .../vendor/github.com/miekg/dns/update.go          |  106 +
 .../vendor/github.com/miekg/dns/update_test.go     |  139 +
 .../vendor/github.com/miekg/dns/version.go         |   15 +
 .../vendor/github.com/miekg/dns/version_test.go    |   10 +
 .../vendor/github.com/miekg/dns/xfr.go             |  260 ++
 .../vendor/github.com/miekg/dns/zcompress.go       |  118 +
 .../vendor/github.com/miekg/dns/zmsg.go            | 3615 ++++++++++++++++++++
 .../vendor/github.com/miekg/dns/ztypes.go          |  863 +++++
 85 files changed, 24926 insertions(+)

diff --git a/LICENSE b/LICENSE
index d1befb5..487b432 100644
--- a/LICENSE
+++ b/LICENSE
@@ -432,3 +432,7 @@ The bytefmt component is used under the MIT license:
 For the siphash component:
 @grove/vendor/github.com/dchest/siphash/*
 ./licenses/CC0
+
+For the miekg/dns component:
+@traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/*
+./licenses/BSD-miekg-dns
diff --git a/licenses/BSD-miekg-dns b/licenses/BSD-miekg-dns
new file mode 100644
index 0000000..5763fa7
--- /dev/null
+++ b/licenses/BSD-miekg-dns
@@ -0,0 +1,32 @@
+Extensions of the original work are copyright (c) 2011 Miek Gieben
+
+As this is fork of the official Go code the same license applies:
+
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/.codecov.yml b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/.codecov.yml
new file mode 100644
index 0000000..f91e5c1
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/.codecov.yml
@@ -0,0 +1,8 @@
+coverage:
+  status:
+    project:
+      default:
+        target: 40%
+        threshold: null
+    patch: false
+    changes: false
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/.travis.yml b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/.travis.yml
new file mode 100644
index 0000000..542dd68
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/.travis.yml
@@ -0,0 +1,20 @@
+language: go
+sudo: false
+go:
+  - 1.9.x
+  - tip
+
+env:
+  - TESTS="-race -v -bench=. -coverprofile=coverage.txt -covermode=atomic"
+  - TESTS="-race -v ./..."
+
+before_install:
+  # don't use the miekg/dns when testing forks
+  - mkdir -p $GOPATH/src/github.com/miekg
+  - ln -s $TRAVIS_BUILD_DIR $GOPATH/src/github.com/miekg/ || true
+
+script:
+  - go test $TESTS
+
+after_success:
+  - bash <(curl -s https://codecov.io/bash)
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/AUTHORS b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/AUTHORS
new file mode 100644
index 0000000..1965683
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/AUTHORS
@@ -0,0 +1 @@
+Miek Gieben <mi...@miek.nl>
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/CONTRIBUTORS b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/CONTRIBUTORS
new file mode 100644
index 0000000..5903779
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/CONTRIBUTORS
@@ -0,0 +1,10 @@
+Alex A. Skinner
+Andrew Tunnell-Jones
+Ask Bjørn Hansen
+Dave Cheney
+Dusty Wilson
+Marek Majkowski
+Peter van Dijk
+Omri Bahumi
+Alex Sergeyev
+James Hartig
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/COPYRIGHT b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/COPYRIGHT
new file mode 100644
index 0000000..35702b1
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/COPYRIGHT
@@ -0,0 +1,9 @@
+Copyright 2009 The Go Authors. All rights reserved. Use of this source code
+is governed by a BSD-style license that can be found in the LICENSE file.
+Extensions of the original work are copyright (c) 2011 Miek Gieben
+
+Copyright 2011 Miek Gieben. All rights reserved. Use of this source code is
+governed by a BSD-style license that can be found in the LICENSE file.
+
+Copyright 2014 CloudFlare. All rights reserved. Use of this source code is
+governed by a BSD-style license that can be found in the LICENSE file.
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/Gopkg.lock b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/Gopkg.lock
new file mode 100644
index 0000000..0c73a64
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/Gopkg.lock
@@ -0,0 +1,21 @@
+# This file is autogenerated, do not edit; changes may be undone by the next 'dep ensure'.
+
+
+[[projects]]
+  branch = "master"
+  name = "golang.org/x/crypto"
+  packages = ["ed25519","ed25519/internal/edwards25519"]
+  revision = "b080dc9a8c480b08e698fb1219160d598526310f"
+
+[[projects]]
+  branch = "master"
+  name = "golang.org/x/net"
+  packages = ["bpf","internal/iana","internal/socket","ipv4","ipv6"]
+  revision = "894f8ed5849b15b810ae41e9590a0d05395bba27"
+
+[solve-meta]
+  analyzer-name = "dep"
+  analyzer-version = 1
+  inputs-digest = "c4abc38abaeeeeb9be92455c9c02cae32841122b8982aaa067ef25bb8e86ff9d"
+  solver-name = "gps-cdcl"
+  solver-version = 1
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/Gopkg.toml b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/Gopkg.toml
new file mode 100644
index 0000000..2f655b2
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/Gopkg.toml
@@ -0,0 +1,26 @@
+
+# Gopkg.toml example
+#
+# Refer to https://github.com/golang/dep/blob/master/docs/Gopkg.toml.md
+# for detailed Gopkg.toml documentation.
+#
+# required = ["github.com/user/thing/cmd/thing"]
+# ignored = ["github.com/user/project/pkgX", "bitbucket.org/user/project/pkgA/pkgY"]
+#
+# [[constraint]]
+#   name = "github.com/user/project"
+#   version = "1.0.0"
+#
+# [[constraint]]
+#   name = "github.com/user/project2"
+#   branch = "dev"
+#   source = "github.com/myfork/project2"
+#
+# [[override]]
+#  name = "github.com/x/y"
+#  version = "2.4.0"
+
+
+[[constraint]]
+  branch = "master"
+  name = "golang.org/x/crypto"
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/LICENSE b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/LICENSE
new file mode 100644
index 0000000..5763fa7
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/LICENSE
@@ -0,0 +1,32 @@
+Extensions of the original work are copyright (c) 2011 Miek Gieben
+
+As this is fork of the official Go code the same license applies:
+
+Copyright (c) 2009 The Go Authors. All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are
+met:
+
+   * Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+   * Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the following disclaimer
+in the documentation and/or other materials provided with the
+distribution.
+   * Neither the name of Google Inc. nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/Makefile.fuzz b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/Makefile.fuzz
new file mode 100644
index 0000000..dc158c4
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/Makefile.fuzz
@@ -0,0 +1,33 @@
+# Makefile for fuzzing
+#
+# Use go-fuzz and needs the tools installed.
+# See https://blog.cloudflare.com/dns-parser-meet-go-fuzzer/
+#
+# Installing go-fuzz:
+# $ make -f Makefile.fuzz get
+# Installs:
+# * github.com/dvyukov/go-fuzz/go-fuzz
+# * get github.com/dvyukov/go-fuzz/go-fuzz-build
+
+all: build
+
+.PHONY: build
+build:
+	go-fuzz-build -tags fuzz github.com/miekg/dns
+
+.PHONY: build-newrr
+build-newrr:
+	go-fuzz-build -func FuzzNewRR -tags fuzz github.com/miekg/dns
+
+.PHONY: fuzz
+fuzz:
+	go-fuzz -bin=dns-fuzz.zip -workdir=fuzz
+
+.PHONY: get
+get:
+	go get github.com/dvyukov/go-fuzz/go-fuzz
+	go get github.com/dvyukov/go-fuzz/go-fuzz-build
+
+.PHONY: clean
+clean:
+	rm *-fuzz.zip
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/Makefile.release b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/Makefile.release
new file mode 100644
index 0000000..8fb748e
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/Makefile.release
@@ -0,0 +1,52 @@
+# Makefile for releasing.
+#
+# The release is controlled from version.go. The version found there is
+# used to tag the git repo, we're not building any artifects so there is nothing
+# to upload to github.
+#
+# * Up the version in version.go
+# * Run: make -f Makefile.release release
+#   * will *commit* your change with 'Release $VERSION'
+#   * push to github
+#
+
+define GO
+//+build ignore
+
+package main
+
+import (
+	"fmt"
+
+	"github.com/miekg/dns"
+)
+
+func main() {
+	fmt.Println(dns.Version.String())
+}
+endef
+
+$(file > version_release.go,$(GO))
+VERSION:=$(shell go run version_release.go)
+TAG="v$(VERSION)"
+
+all:
+	@echo Use the \'release\' target to start a release $(VERSION)
+	rm -f version_release.go
+
+.PHONY: release
+release: commit push
+	@echo Released $(VERSION)
+	rm -f version_release.go
+
+.PHONY: commit
+commit:
+	@echo Committing release $(VERSION)
+	git commit -am"Release $(VERSION)"
+	git tag $(TAG)
+
+.PHONY: push
+push:
+	@echo Pushing release $(VERSION) to master
+	git push --tags
+	git push
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/README.md b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/README.md
new file mode 100644
index 0000000..1ad23c7
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/README.md
@@ -0,0 +1,168 @@
+[![Build Status](https://travis-ci.org/miekg/dns.svg?branch=master)](https://travis-ci.org/miekg/dns)
+[![Code Coverage](https://img.shields.io/codecov/c/github/miekg/dns/master.svg)](https://codecov.io/github/miekg/dns?branch=master)
+[![Go Report Card](https://goreportcard.com/badge/github.com/miekg/dns)](https://goreportcard.com/report/miekg/dns)
+[![](https://godoc.org/github.com/miekg/dns?status.svg)](https://godoc.org/github.com/miekg/dns)
+
+# Alternative (more granular) approach to a DNS library
+
+> Less is more.
+
+Complete and usable DNS library. All widely used Resource Records are supported, including the
+DNSSEC types. It follows a lean and mean philosophy. If there is stuff you should know as a DNS
+programmer there isn't a convenience function for it. Server side and client side programming is
+supported, i.e. you can build servers and resolvers with it.
+
+We try to keep the "master" branch as sane as possible and at the bleeding edge of standards,
+avoiding breaking changes wherever reasonable. We support the last two versions of Go.
+
+# Goals
+
+* KISS;
+* Fast;
+* Small API. If it's easy to code in Go, don't make a function for it.
+
+# Users
+
+A not-so-up-to-date-list-that-may-be-actually-current:
+
+* https://github.com/coredns/coredns
+* https://cloudflare.com
+* https://github.com/abh/geodns
+* http://www.statdns.com/
+* http://www.dnsinspect.com/
+* https://github.com/chuangbo/jianbing-dictionary-dns
+* http://www.dns-lg.com/
+* https://github.com/fcambus/rrda
+* https://github.com/kenshinx/godns
+* https://github.com/skynetservices/skydns
+* https://github.com/hashicorp/consul
+* https://github.com/DevelopersPL/godnsagent
+* https://github.com/duedil-ltd/discodns
+* https://github.com/StalkR/dns-reverse-proxy
+* https://github.com/tianon/rawdns
+* https://mesosphere.github.io/mesos-dns/
+* https://pulse.turbobytes.com/
+* https://play.google.com/store/apps/details?id=com.turbobytes.dig
+* https://github.com/fcambus/statzone
+* https://github.com/benschw/dns-clb-go
+* https://github.com/corny/dnscheck for http://public-dns.info/
+* https://namesmith.io
+* https://github.com/miekg/unbound
+* https://github.com/miekg/exdns
+* https://dnslookup.org
+* https://github.com/looterz/grimd
+* https://github.com/phamhongviet/serf-dns
+* https://github.com/mehrdadrad/mylg
+* https://github.com/bamarni/dockness
+* https://github.com/fffaraz/microdns
+* http://kelda.io
+* https://github.com/ipdcode/hades (JD.COM)
+* https://github.com/StackExchange/dnscontrol/
+* https://www.dnsperf.com/
+* https://dnssectest.net/
+* https://dns.apebits.com
+* https://github.com/oif/apex
+* https://github.com/jedisct1/dnscrypt-proxy
+* https://github.com/jedisct1/rpdns
+
+Send pull request if you want to be listed here.
+
+# Features
+
+* UDP/TCP queries, IPv4 and IPv6;
+* RFC 1035 zone file parsing ($INCLUDE, $ORIGIN, $TTL and $GENERATE (for all record types) are supported;
+* Fast:
+    * Reply speed around ~ 80K qps (faster hardware results in more qps);
+    * Parsing RRs ~ 100K RR/s, that's 5M records in about 50 seconds;
+* Server side programming (mimicking the net/http package);
+* Client side programming;
+* DNSSEC: signing, validating and key generation for DSA, RSA, ECDSA and Ed25519;
+* EDNS0, NSID, Cookies;
+* AXFR/IXFR;
+* TSIG, SIG(0);
+* DNS over TLS: optional encrypted connection between client and server;
+* DNS name compression;
+* Depends only on the standard library.
+
+Have fun!
+
+Miek Gieben  -  2010-2012  -  <mi...@miek.nl>
+
+# Building
+
+Building is done with the `go` tool. If you have setup your GOPATH correctly, the following should
+work:
+
+    go get github.com/miekg/dns
+    go build github.com/miekg/dns
+
+## Examples
+
+A short "how to use the API" is at the beginning of doc.go (this also will show
+when you call `godoc github.com/miekg/dns`).
+
+Example programs can be found in the `github.com/miekg/exdns` repository.
+
+## Supported RFCs
+
+*all of them*
+
+* 103{4,5} - DNS standard
+* 1348 - NSAP record (removed the record)
+* 1982 - Serial Arithmetic
+* 1876 - LOC record
+* 1995 - IXFR
+* 1996 - DNS notify
+* 2136 - DNS Update (dynamic updates)
+* 2181 - RRset definition - there is no RRset type though, just []RR
+* 2537 - RSAMD5 DNS keys
+* 2065 - DNSSEC (updated in later RFCs)
+* 2671 - EDNS record
+* 2782 - SRV record
+* 2845 - TSIG record
+* 2915 - NAPTR record
+* 2929 - DNS IANA Considerations
+* 3110 - RSASHA1 DNS keys
+* 3225 - DO bit (DNSSEC OK)
+* 340{1,2,3} - NAPTR record
+* 3445 - Limiting the scope of (DNS)KEY
+* 3597 - Unknown RRs
+* 403{3,4,5} - DNSSEC + validation functions
+* 4255 - SSHFP record
+* 4343 - Case insensitivity
+* 4408 - SPF record
+* 4509 - SHA256 Hash in DS
+* 4592 - Wildcards in the DNS
+* 4635 - HMAC SHA TSIG
+* 4701 - DHCID
+* 4892 - id.server
+* 5001 - NSID
+* 5155 - NSEC3 record
+* 5205 - HIP record
+* 5702 - SHA2 in the DNS
+* 5936 - AXFR
+* 5966 - TCP implementation recommendations
+* 6605 - ECDSA
+* 6725 - IANA Registry Update
+* 6742 - ILNP DNS
+* 6840 - Clarifications and Implementation Notes for DNS Security
+* 6844 - CAA record
+* 6891 - EDNS0 update
+* 6895 - DNS IANA considerations
+* 6975 - Algorithm Understanding in DNSSEC
+* 7043 - EUI48/EUI64 records
+* 7314 - DNS (EDNS) EXPIRE Option
+* 7477 - CSYNC RR
+* 7828 - edns-tcp-keepalive EDNS0 Option
+* 7553 - URI record
+* 7858 - DNS over TLS: Initiation and Performance Considerations
+* 7871 - EDNS0 Client Subnet
+* 7873 - Domain Name System (DNS) Cookies (draft-ietf-dnsop-cookies)
+* 8080 - EdDSA for DNSSEC
+
+## Loosely based upon
+
+* `ldns`
+* `NSD`
+* `Net::DNS`
+* `GRONG`
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/client.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/client.go
new file mode 100644
index 0000000..282565a
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/client.go
@@ -0,0 +1,503 @@
+package dns
+
+// A client implementation.
+
+import (
+	"bytes"
+	"context"
+	"crypto/tls"
+	"encoding/binary"
+	"io"
+	"net"
+	"strings"
+	"time"
+)
+
+const dnsTimeout time.Duration = 2 * time.Second
+const tcpIdleTimeout time.Duration = 8 * time.Second
+
+// A Conn represents a connection to a DNS server.
+type Conn struct {
+	net.Conn                         // a net.Conn holding the connection
+	UDPSize        uint16            // minimum receive buffer for UDP messages
+	TsigSecret     map[string]string // secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2)
+	tsigRequestMAC string
+}
+
+// A Client defines parameters for a DNS client.
+type Client struct {
+	Net       string      // if "tcp" or "tcp-tls" (DNS over TLS) a TCP query will be initiated, otherwise an UDP one (default is "" for UDP)
+	UDPSize   uint16      // minimum receive buffer for UDP messages
+	TLSConfig *tls.Config // TLS connection configuration
+	Dialer    *net.Dialer // a net.Dialer used to set local address, timeouts and more
+	// Timeout is a cumulative timeout for dial, write and read, defaults to 0 (disabled) - overrides DialTimeout, ReadTimeout,
+	// WriteTimeout when non-zero. Can be overridden with net.Dialer.Timeout (see Client.ExchangeWithDialer and
+	// Client.Dialer) or context.Context.Deadline (see the deprecated ExchangeContext)
+	Timeout        time.Duration
+	DialTimeout    time.Duration     // net.DialTimeout, defaults to 2 seconds, or net.Dialer.Timeout if expiring earlier - overridden by Timeout when that value is non-zero
+	ReadTimeout    time.Duration     // net.Conn.SetReadTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero
+	WriteTimeout   time.Duration     // net.Conn.SetWriteTimeout value for connections, defaults to 2 seconds - overridden by Timeout when that value is non-zero
+	TsigSecret     map[string]string // secret(s) for Tsig map[<zonename>]<base64 secret>, zonename must be in canonical form (lowercase, fqdn, see RFC 4034 Section 6.2)
+	SingleInflight bool              // if true suppress multiple outstanding queries for the same Qname, Qtype and Qclass
+	group          singleflight
+}
+
+// Exchange performs a synchronous UDP query. It sends the message m to the address
+// contained in a and waits for a reply. Exchange does not retry a failed query, nor
+// will it fall back to TCP in case of truncation.
+// See client.Exchange for more information on setting larger buffer sizes.
+func Exchange(m *Msg, a string) (r *Msg, err error) {
+	client := Client{Net: "udp"}
+	r, _, err = client.Exchange(m, a)
+	return r, err
+}
+
+func (c *Client) dialTimeout() time.Duration {
+	if c.Timeout != 0 {
+		return c.Timeout
+	}
+	if c.DialTimeout != 0 {
+		return c.DialTimeout
+	}
+	return dnsTimeout
+}
+
+func (c *Client) readTimeout() time.Duration {
+	if c.ReadTimeout != 0 {
+		return c.ReadTimeout
+	}
+	return dnsTimeout
+}
+
+func (c *Client) writeTimeout() time.Duration {
+	if c.WriteTimeout != 0 {
+		return c.WriteTimeout
+	}
+	return dnsTimeout
+}
+
+// Dial connects to the address on the named network.
+func (c *Client) Dial(address string) (conn *Conn, err error) {
+	// create a new dialer with the appropriate timeout
+	var d net.Dialer
+	if c.Dialer == nil {
+		d = net.Dialer{}
+	} else {
+		d = net.Dialer(*c.Dialer)
+	}
+	d.Timeout = c.getTimeoutForRequest(c.writeTimeout())
+
+	network := "udp"
+	useTLS := false
+
+	switch c.Net {
+	case "tcp-tls":
+		network = "tcp"
+		useTLS = true
+	case "tcp4-tls":
+		network = "tcp4"
+		useTLS = true
+	case "tcp6-tls":
+		network = "tcp6"
+		useTLS = true
+	default:
+		if c.Net != "" {
+			network = c.Net
+		}
+	}
+
+	conn = new(Conn)
+	if useTLS {
+		conn.Conn, err = tls.DialWithDialer(&d, network, address, c.TLSConfig)
+	} else {
+		conn.Conn, err = d.Dial(network, address)
+	}
+	if err != nil {
+		return nil, err
+	}
+	return conn, nil
+}
+
+// Exchange performs a synchronous query. It sends the message m to the address
+// contained in a and waits for a reply. Basic use pattern with a *dns.Client:
+//
+//	c := new(dns.Client)
+//	in, rtt, err := c.Exchange(message, "127.0.0.1:53")
+//
+// Exchange does not retry a failed query, nor will it fall back to TCP in
+// case of truncation.
+// It is up to the caller to create a message that allows for larger responses to be
+// returned. Specifically this means adding an EDNS0 OPT RR that will advertise a larger
+// buffer, see SetEdns0. Messages without an OPT RR will fallback to the historic limit
+// of 512 bytes
+// To specify a local address or a timeout, the caller has to set the `Client.Dialer`
+// attribute appropriately
+func (c *Client) Exchange(m *Msg, address string) (r *Msg, rtt time.Duration, err error) {
+	if !c.SingleInflight {
+		return c.exchange(m, address)
+	}
+
+	t := "nop"
+	if t1, ok := TypeToString[m.Question[0].Qtype]; ok {
+		t = t1
+	}
+	cl := "nop"
+	if cl1, ok := ClassToString[m.Question[0].Qclass]; ok {
+		cl = cl1
+	}
+	r, rtt, err, shared := c.group.Do(m.Question[0].Name+t+cl, func() (*Msg, time.Duration, error) {
+		return c.exchange(m, address)
+	})
+	if r != nil && shared {
+		r = r.Copy()
+	}
+	return r, rtt, err
+}
+
+func (c *Client) exchange(m *Msg, a string) (r *Msg, rtt time.Duration, err error) {
+	var co *Conn
+
+	co, err = c.Dial(a)
+
+	if err != nil {
+		return nil, 0, err
+	}
+	defer co.Close()
+
+	opt := m.IsEdns0()
+	// If EDNS0 is used use that for size.
+	if opt != nil && opt.UDPSize() >= MinMsgSize {
+		co.UDPSize = opt.UDPSize()
+	}
+	// Otherwise use the client's configured UDP size.
+	if opt == nil && c.UDPSize >= MinMsgSize {
+		co.UDPSize = c.UDPSize
+	}
+
+	co.TsigSecret = c.TsigSecret
+	t := time.Now()
+	// write with the appropriate write timeout
+	co.SetWriteDeadline(t.Add(c.getTimeoutForRequest(c.writeTimeout())))
+	if err = co.WriteMsg(m); err != nil {
+		return nil, 0, err
+	}
+
+	co.SetReadDeadline(time.Now().Add(c.getTimeoutForRequest(c.readTimeout())))
+	r, err = co.ReadMsg()
+	if err == nil && r.Id != m.Id {
+		err = ErrId
+	}
+	rtt = time.Since(t)
+	return r, rtt, err
+}
+
+// ReadMsg reads a message from the connection co.
+// If the received message contains a TSIG record the transaction signature
+// is verified. This method always tries to return the message, however if an
+// error is returned there are no guarantees that the returned message is a
+// valid representation of the packet read.
+func (co *Conn) ReadMsg() (*Msg, error) {
+	p, err := co.ReadMsgHeader(nil)
+	if err != nil {
+		return nil, err
+	}
+
+	m := new(Msg)
+	if err := m.Unpack(p); err != nil {
+		// If an error was returned, we still want to allow the user to use
+		// the message, but naively they can just check err if they don't want
+		// to use an erroneous message
+		return m, err
+	}
+	if t := m.IsTsig(); t != nil {
+		if _, ok := co.TsigSecret[t.Hdr.Name]; !ok {
+			return m, ErrSecret
+		}
+		// Need to work on the original message p, as that was used to calculate the tsig.
+		err = TsigVerify(p, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false)
+	}
+	return m, err
+}
+
+// ReadMsgHeader reads a DNS message, parses and populates hdr (when hdr is not nil).
+// Returns message as a byte slice to be parsed with Msg.Unpack later on.
+// Note that error handling on the message body is not possible as only the header is parsed.
+func (co *Conn) ReadMsgHeader(hdr *Header) ([]byte, error) {
+	var (
+		p   []byte
+		n   int
+		err error
+	)
+
+	switch t := co.Conn.(type) {
+	case *net.TCPConn, *tls.Conn:
+		r := t.(io.Reader)
+
+		// First two bytes specify the length of the entire message.
+		l, err := tcpMsgLen(r)
+		if err != nil {
+			return nil, err
+		}
+		p = make([]byte, l)
+		n, err = tcpRead(r, p)
+	default:
+		if co.UDPSize > MinMsgSize {
+			p = make([]byte, co.UDPSize)
+		} else {
+			p = make([]byte, MinMsgSize)
+		}
+		n, err = co.Read(p)
+	}
+
+	if err != nil {
+		return nil, err
+	} else if n < headerSize {
+		return nil, ErrShortRead
+	}
+
+	p = p[:n]
+	if hdr != nil {
+		dh, _, err := unpackMsgHdr(p, 0)
+		if err != nil {
+			return nil, err
+		}
+		*hdr = dh
+	}
+	return p, err
+}
+
+// tcpMsgLen is a helper func to read first two bytes of stream as uint16 packet length.
+func tcpMsgLen(t io.Reader) (int, error) {
+	p := []byte{0, 0}
+	n, err := t.Read(p)
+	if err != nil {
+		return 0, err
+	}
+
+	// As seen with my local router/switch, returns 1 byte on the above read,
+	// resulting a a ShortRead. Just write it out (instead of loop) and read the
+	// other byte.
+	if n == 1 {
+		n1, err := t.Read(p[1:])
+		if err != nil {
+			return 0, err
+		}
+		n += n1
+	}
+
+	if n != 2 {
+		return 0, ErrShortRead
+	}
+	l := binary.BigEndian.Uint16(p)
+	if l == 0 {
+		return 0, ErrShortRead
+	}
+	return int(l), nil
+}
+
+// tcpRead calls TCPConn.Read enough times to fill allocated buffer.
+func tcpRead(t io.Reader, p []byte) (int, error) {
+	n, err := t.Read(p)
+	if err != nil {
+		return n, err
+	}
+	for n < len(p) {
+		j, err := t.Read(p[n:])
+		if err != nil {
+			return n, err
+		}
+		n += j
+	}
+	return n, err
+}
+
+// Read implements the net.Conn read method.
+func (co *Conn) Read(p []byte) (n int, err error) {
+	if co.Conn == nil {
+		return 0, ErrConnEmpty
+	}
+	if len(p) < 2 {
+		return 0, io.ErrShortBuffer
+	}
+	switch t := co.Conn.(type) {
+	case *net.TCPConn, *tls.Conn:
+		r := t.(io.Reader)
+
+		l, err := tcpMsgLen(r)
+		if err != nil {
+			return 0, err
+		}
+		if l > len(p) {
+			return int(l), io.ErrShortBuffer
+		}
+		return tcpRead(r, p[:l])
+	}
+	// UDP connection
+	n, err = co.Conn.Read(p)
+	if err != nil {
+		return n, err
+	}
+	return n, err
+}
+
+// WriteMsg sends a message through the connection co.
+// If the message m contains a TSIG record the transaction
+// signature is calculated.
+func (co *Conn) WriteMsg(m *Msg) (err error) {
+	var out []byte
+	if t := m.IsTsig(); t != nil {
+		mac := ""
+		if _, ok := co.TsigSecret[t.Hdr.Name]; !ok {
+			return ErrSecret
+		}
+		out, mac, err = TsigGenerate(m, co.TsigSecret[t.Hdr.Name], co.tsigRequestMAC, false)
+		// Set for the next read, although only used in zone transfers
+		co.tsigRequestMAC = mac
+	} else {
+		out, err = m.Pack()
+	}
+	if err != nil {
+		return err
+	}
+	if _, err = co.Write(out); err != nil {
+		return err
+	}
+	return nil
+}
+
+// Write implements the net.Conn Write method.
+func (co *Conn) Write(p []byte) (n int, err error) {
+	switch t := co.Conn.(type) {
+	case *net.TCPConn, *tls.Conn:
+		w := t.(io.Writer)
+
+		lp := len(p)
+		if lp < 2 {
+			return 0, io.ErrShortBuffer
+		}
+		if lp > MaxMsgSize {
+			return 0, &Error{err: "message too large"}
+		}
+		l := make([]byte, 2, lp+2)
+		binary.BigEndian.PutUint16(l, uint16(lp))
+		p = append(l, p...)
+		n, err := io.Copy(w, bytes.NewReader(p))
+		return int(n), err
+	}
+	n, err = co.Conn.Write(p)
+	return n, err
+}
+
+// Return the appropriate timeout for a specific request
+func (c *Client) getTimeoutForRequest(timeout time.Duration) time.Duration {
+	var requestTimeout time.Duration
+	if c.Timeout != 0 {
+		requestTimeout = c.Timeout
+	} else {
+		requestTimeout = timeout
+	}
+	// net.Dialer.Timeout has priority if smaller than the timeouts computed so
+	// far
+	if c.Dialer != nil && c.Dialer.Timeout != 0 {
+		if c.Dialer.Timeout < requestTimeout {
+			requestTimeout = c.Dialer.Timeout
+		}
+	}
+	return requestTimeout
+}
+
+// Dial connects to the address on the named network.
+func Dial(network, address string) (conn *Conn, err error) {
+	conn = new(Conn)
+	conn.Conn, err = net.Dial(network, address)
+	if err != nil {
+		return nil, err
+	}
+	return conn, nil
+}
+
+// ExchangeContext performs a synchronous UDP query, like Exchange. It
+// additionally obeys deadlines from the passed Context.
+func ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, err error) {
+	client := Client{Net: "udp"}
+	r, _, err = client.ExchangeContext(ctx, m, a)
+	// ignorint rtt to leave the original ExchangeContext API unchanged, but
+	// this function will go away
+	return r, err
+}
+
+// ExchangeConn performs a synchronous query. It sends the message m via the connection
+// c and waits for a reply. The connection c is not closed by ExchangeConn.
+// This function is going away, but can easily be mimicked:
+//
+//	co := &dns.Conn{Conn: c} // c is your net.Conn
+//	co.WriteMsg(m)
+//	in, _  := co.ReadMsg()
+//	co.Close()
+//
+func ExchangeConn(c net.Conn, m *Msg) (r *Msg, err error) {
+	println("dns: ExchangeConn: this function is deprecated")
+	co := new(Conn)
+	co.Conn = c
+	if err = co.WriteMsg(m); err != nil {
+		return nil, err
+	}
+	r, err = co.ReadMsg()
+	if err == nil && r.Id != m.Id {
+		err = ErrId
+	}
+	return r, err
+}
+
+// DialTimeout acts like Dial but takes a timeout.
+func DialTimeout(network, address string, timeout time.Duration) (conn *Conn, err error) {
+	client := Client{Net: network, Dialer: &net.Dialer{Timeout: timeout}}
+	conn, err = client.Dial(address)
+	if err != nil {
+		return nil, err
+	}
+	return conn, nil
+}
+
+// DialWithTLS connects to the address on the named network with TLS.
+func DialWithTLS(network, address string, tlsConfig *tls.Config) (conn *Conn, err error) {
+	if !strings.HasSuffix(network, "-tls") {
+		network += "-tls"
+	}
+	client := Client{Net: network, TLSConfig: tlsConfig}
+	conn, err = client.Dial(address)
+
+	if err != nil {
+		return nil, err
+	}
+	return conn, nil
+}
+
+// DialTimeoutWithTLS acts like DialWithTLS but takes a timeout.
+func DialTimeoutWithTLS(network, address string, tlsConfig *tls.Config, timeout time.Duration) (conn *Conn, err error) {
+	if !strings.HasSuffix(network, "-tls") {
+		network += "-tls"
+	}
+	client := Client{Net: network, Dialer: &net.Dialer{Timeout: timeout}, TLSConfig: tlsConfig}
+	conn, err = client.Dial(address)
+	if err != nil {
+		return nil, err
+	}
+	return conn, nil
+}
+
+// ExchangeContext acts like Exchange, but honors the deadline on the provided
+// context, if present. If there is both a context deadline and a configured
+// timeout on the client, the earliest of the two takes effect.
+func (c *Client) ExchangeContext(ctx context.Context, m *Msg, a string) (r *Msg, rtt time.Duration, err error) {
+	var timeout time.Duration
+	if deadline, ok := ctx.Deadline(); !ok {
+		timeout = 0
+	} else {
+		timeout = deadline.Sub(time.Now())
+	}
+	// not passing the context to the underlying calls, as the API does not support
+	// context. For timeouts you should set up Client.Dialer and call Client.Exchange.
+	c.Dialer = &net.Dialer{Timeout: timeout}
+	return c.Exchange(m, a)
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/client_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/client_test.go
new file mode 100644
index 0000000..cc41983
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/client_test.go
@@ -0,0 +1,590 @@
+package dns
+
+import (
+	"context"
+	"crypto/tls"
+	"fmt"
+	"net"
+	"strconv"
+	"strings"
+	"sync"
+	"testing"
+	"time"
+)
+
+func TestDialUDP(t *testing.T) {
+	HandleFunc("miek.nl.", HelloServer)
+	defer HandleRemove("miek.nl.")
+
+	s, addrstr, err := RunLocalUDPServer(":0")
+	if err != nil {
+		t.Fatalf("unable to run test server: %v", err)
+	}
+	defer s.Shutdown()
+
+	m := new(Msg)
+	m.SetQuestion("miek.nl.", TypeSOA)
+
+	c := new(Client)
+	conn, err := c.Dial(addrstr)
+	if err != nil {
+		t.Fatalf("failed to dial: %v", err)
+	}
+	if conn == nil {
+		t.Fatalf("conn is nil")
+	}
+}
+
+func TestClientSync(t *testing.T) {
+	HandleFunc("miek.nl.", HelloServer)
+	defer HandleRemove("miek.nl.")
+
+	s, addrstr, err := RunLocalUDPServer(":0")
+	if err != nil {
+		t.Fatalf("unable to run test server: %v", err)
+	}
+	defer s.Shutdown()
+
+	m := new(Msg)
+	m.SetQuestion("miek.nl.", TypeSOA)
+
+	c := new(Client)
+	r, _, err := c.Exchange(m, addrstr)
+	if err != nil {
+		t.Fatalf("failed to exchange: %v", err)
+	}
+	if r == nil {
+		t.Fatal("response is nil")
+	}
+	if r.Rcode != RcodeSuccess {
+		t.Errorf("failed to get an valid answer\n%v", r)
+	}
+	// And now with plain Exchange().
+	r, err = Exchange(m, addrstr)
+	if err != nil {
+		t.Errorf("failed to exchange: %v", err)
+	}
+	if r == nil || r.Rcode != RcodeSuccess {
+		t.Errorf("failed to get an valid answer\n%v", r)
+	}
+}
+
+func TestClientLocalAddress(t *testing.T) {
+	HandleFunc("miek.nl.", HelloServerEchoAddrPort)
+	defer HandleRemove("miek.nl.")
+
+	s, addrstr, err := RunLocalUDPServer(":0")
+	if err != nil {
+		t.Fatalf("unable to run test server: %v", err)
+	}
+	defer s.Shutdown()
+
+	m := new(Msg)
+	m.SetQuestion("miek.nl.", TypeSOA)
+
+	c := new(Client)
+	laddr := net.UDPAddr{IP: net.ParseIP("0.0.0.0"), Port: 12345, Zone: ""}
+	c.Dialer = &net.Dialer{LocalAddr: &laddr}
+	r, _, err := c.Exchange(m, addrstr)
+	if err != nil {
+		t.Fatalf("failed to exchange: %v", err)
+	}
+	if r != nil && r.Rcode != RcodeSuccess {
+		t.Errorf("failed to get an valid answer\n%v", r)
+	}
+	if len(r.Extra) != 1 {
+		t.Errorf("failed to get additional answers\n%v", r)
+	}
+	txt := r.Extra[0].(*TXT)
+	if txt == nil {
+		t.Errorf("invalid TXT response\n%v", txt)
+	}
+	if len(txt.Txt) != 1 || !strings.Contains(txt.Txt[0], ":12345") {
+		t.Errorf("invalid TXT response\n%v", txt.Txt)
+	}
+}
+
+func TestClientTLSSyncV4(t *testing.T) {
+	HandleFunc("miek.nl.", HelloServer)
+	defer HandleRemove("miek.nl.")
+
+	cert, err := tls.X509KeyPair(CertPEMBlock, KeyPEMBlock)
+	if err != nil {
+		t.Fatalf("unable to build certificate: %v", err)
+	}
+
+	config := tls.Config{
+		Certificates: []tls.Certificate{cert},
+	}
+
+	s, addrstr, err := RunLocalTLSServer(":0", &config)
+	if err != nil {
+		t.Fatalf("unable to run test server: %v", err)
+	}
+	defer s.Shutdown()
+
+	m := new(Msg)
+	m.SetQuestion("miek.nl.", TypeSOA)
+
+	c := new(Client)
+
+	// test tcp-tls
+	c.Net = "tcp-tls"
+	c.TLSConfig = &tls.Config{
+		InsecureSkipVerify: true,
+	}
+
+	r, _, err := c.Exchange(m, addrstr)
+	if err != nil {
+		t.Fatalf("failed to exchange: %v", err)
+	}
+	if r == nil {
+		t.Fatal("response is nil")
+	}
+	if r.Rcode != RcodeSuccess {
+		t.Errorf("failed to get an valid answer\n%v", r)
+	}
+
+	// test tcp4-tls
+	c.Net = "tcp4-tls"
+	c.TLSConfig = &tls.Config{
+		InsecureSkipVerify: true,
+	}
+
+	r, _, err = c.Exchange(m, addrstr)
+	if err != nil {
+		t.Fatalf("failed to exchange: %v", err)
+	}
+	if r == nil {
+		t.Fatal("response is nil")
+	}
+	if r.Rcode != RcodeSuccess {
+		t.Errorf("failed to get an valid answer\n%v", r)
+	}
+}
+
+func TestClientSyncBadID(t *testing.T) {
+	HandleFunc("miek.nl.", HelloServerBadID)
+	defer HandleRemove("miek.nl.")
+
+	s, addrstr, err := RunLocalUDPServer(":0")
+	if err != nil {
+		t.Fatalf("unable to run test server: %v", err)
+	}
+	defer s.Shutdown()
+
+	m := new(Msg)
+	m.SetQuestion("miek.nl.", TypeSOA)
+
+	c := new(Client)
+	if _, _, err := c.Exchange(m, addrstr); err != ErrId {
+		t.Errorf("did not find a bad Id")
+	}
+	// And now with plain Exchange().
+	if _, err := Exchange(m, addrstr); err != ErrId {
+		t.Errorf("did not find a bad Id")
+	}
+}
+
+func TestClientEDNS0(t *testing.T) {
+	HandleFunc("miek.nl.", HelloServer)
+	defer HandleRemove("miek.nl.")
+
+	s, addrstr, err := RunLocalUDPServer(":0")
+	if err != nil {
+		t.Fatalf("unable to run test server: %v", err)
+	}
+	defer s.Shutdown()
+
+	m := new(Msg)
+	m.SetQuestion("miek.nl.", TypeDNSKEY)
+
+	m.SetEdns0(2048, true)
+
+	c := new(Client)
+	r, _, err := c.Exchange(m, addrstr)
+	if err != nil {
+		t.Fatalf("failed to exchange: %v", err)
+	}
+
+	if r != nil && r.Rcode != RcodeSuccess {
+		t.Errorf("failed to get a valid answer\n%v", r)
+	}
+}
+
+// Validates the transmission and parsing of local EDNS0 options.
+func TestClientEDNS0Local(t *testing.T) {
+	optStr1 := "1979:0x0707"
+	optStr2 := strconv.Itoa(EDNS0LOCALSTART) + ":0x0601"
+
+	handler := func(w ResponseWriter, req *Msg) {
+		m := new(Msg)
+		m.SetReply(req)
+
+		m.Extra = make([]RR, 1, 2)
+		m.Extra[0] = &TXT{Hdr: RR_Header{Name: m.Question[0].Name, Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"Hello local edns"}}
+
+		// If the local options are what we expect, then reflect them back.
+		ec1 := req.Extra[0].(*OPT).Option[0].(*EDNS0_LOCAL).String()
+		ec2 := req.Extra[0].(*OPT).Option[1].(*EDNS0_LOCAL).String()
+		if ec1 == optStr1 && ec2 == optStr2 {
+			m.Extra = append(m.Extra, req.Extra[0])
+		}
+
+		w.WriteMsg(m)
+	}
+
+	HandleFunc("miek.nl.", handler)
+	defer HandleRemove("miek.nl.")
+
+	s, addrstr, err := RunLocalUDPServer(":0")
+	if err != nil {
+		t.Fatalf("unable to run test server: %s", err)
+	}
+	defer s.Shutdown()
+
+	m := new(Msg)
+	m.SetQuestion("miek.nl.", TypeTXT)
+
+	// Add two local edns options to the query.
+	ec1 := &EDNS0_LOCAL{Code: 1979, Data: []byte{7, 7}}
+	ec2 := &EDNS0_LOCAL{Code: EDNS0LOCALSTART, Data: []byte{6, 1}}
+	o := &OPT{Hdr: RR_Header{Name: ".", Rrtype: TypeOPT}, Option: []EDNS0{ec1, ec2}}
+	m.Extra = append(m.Extra, o)
+
+	c := new(Client)
+	r, _, err := c.Exchange(m, addrstr)
+	if err != nil {
+		t.Fatalf("failed to exchange: %s", err)
+	}
+
+	if r == nil {
+		t.Fatal("response is nil")
+	}
+	if r.Rcode != RcodeSuccess {
+		t.Fatal("failed to get a valid answer")
+	}
+
+	txt := r.Extra[0].(*TXT).Txt[0]
+	if txt != "Hello local edns" {
+		t.Error("Unexpected result for miek.nl", txt, "!= Hello local edns")
+	}
+
+	// Validate the local options in the reply.
+	got := r.Extra[1].(*OPT).Option[0].(*EDNS0_LOCAL).String()
+	if got != optStr1 {
+		t.Errorf("failed to get local edns0 answer; got %s, expected %s", got, optStr1)
+	}
+
+	got = r.Extra[1].(*OPT).Option[1].(*EDNS0_LOCAL).String()
+	if got != optStr2 {
+		t.Errorf("failed to get local edns0 answer; got %s, expected %s", got, optStr2)
+	}
+}
+
+func TestClientConn(t *testing.T) {
+	HandleFunc("miek.nl.", HelloServer)
+	defer HandleRemove("miek.nl.")
+
+	// This uses TCP just to make it slightly different than TestClientSync
+	s, addrstr, err := RunLocalTCPServer(":0")
+	if err != nil {
+		t.Fatalf("unable to run test server: %v", err)
+	}
+	defer s.Shutdown()
+
+	m := new(Msg)
+	m.SetQuestion("miek.nl.", TypeSOA)
+
+	cn, err := Dial("tcp", addrstr)
+	if err != nil {
+		t.Errorf("failed to dial %s: %v", addrstr, err)
+	}
+
+	err = cn.WriteMsg(m)
+	if err != nil {
+		t.Errorf("failed to exchange: %v", err)
+	}
+	r, err := cn.ReadMsg()
+	if err != nil {
+		t.Errorf("failed to get a valid answer: %v", err)
+	}
+	if r == nil || r.Rcode != RcodeSuccess {
+		t.Errorf("failed to get an valid answer\n%v", r)
+	}
+
+	err = cn.WriteMsg(m)
+	if err != nil {
+		t.Errorf("failed to exchange: %v", err)
+	}
+	h := new(Header)
+	buf, err := cn.ReadMsgHeader(h)
+	if buf == nil {
+		t.Errorf("failed to get an valid answer\n%v", r)
+	}
+	if err != nil {
+		t.Errorf("failed to get a valid answer: %v", err)
+	}
+	if int(h.Bits&0xF) != RcodeSuccess {
+		t.Errorf("failed to get an valid answer in ReadMsgHeader\n%v", r)
+	}
+	if h.Ancount != 0 || h.Qdcount != 1 || h.Nscount != 0 || h.Arcount != 1 {
+		t.Errorf("expected to have question and additional in response; got something else: %+v", h)
+	}
+	if err = r.Unpack(buf); err != nil {
+		t.Errorf("unable to unpack message fully: %v", err)
+	}
+}
+
+func TestTruncatedMsg(t *testing.T) {
+	m := new(Msg)
+	m.SetQuestion("miek.nl.", TypeSRV)
+	cnt := 10
+	for i := 0; i < cnt; i++ {
+		r := &SRV{
+			Hdr:    RR_Header{Name: m.Question[0].Name, Rrtype: TypeSRV, Class: ClassINET, Ttl: 0},
+			Port:   uint16(i + 8000),
+			Target: "target.miek.nl.",
+		}
+		m.Answer = append(m.Answer, r)
+
+		re := &A{
+			Hdr: RR_Header{Name: m.Question[0].Name, Rrtype: TypeA, Class: ClassINET, Ttl: 0},
+			A:   net.ParseIP(fmt.Sprintf("127.0.0.%d", i)).To4(),
+		}
+		m.Extra = append(m.Extra, re)
+	}
+	buf, err := m.Pack()
+	if err != nil {
+		t.Errorf("failed to pack: %v", err)
+	}
+
+	r := new(Msg)
+	if err = r.Unpack(buf); err != nil {
+		t.Errorf("unable to unpack message: %v", err)
+	}
+	if len(r.Answer) != cnt {
+		t.Errorf("answer count after regular unpack doesn't match: %d", len(r.Answer))
+	}
+	if len(r.Extra) != cnt {
+		t.Errorf("extra count after regular unpack doesn't match: %d", len(r.Extra))
+	}
+
+	m.Truncated = true
+	buf, err = m.Pack()
+	if err != nil {
+		t.Errorf("failed to pack truncated: %v", err)
+	}
+
+	r = new(Msg)
+	if err = r.Unpack(buf); err != nil && err != ErrTruncated {
+		t.Errorf("unable to unpack truncated message: %v", err)
+	}
+	if !r.Truncated {
+		t.Errorf("truncated message wasn't unpacked as truncated")
+	}
+	if len(r.Answer) != cnt {
+		t.Errorf("answer count after truncated unpack doesn't match: %d", len(r.Answer))
+	}
+	if len(r.Extra) != cnt {
+		t.Errorf("extra count after truncated unpack doesn't match: %d", len(r.Extra))
+	}
+
+	// Now we want to remove almost all of the extra records
+	// We're going to loop over the extra to get the count of the size of all
+	// of them
+	off := 0
+	buf1 := make([]byte, m.Len())
+	for i := 0; i < len(m.Extra); i++ {
+		off, err = PackRR(m.Extra[i], buf1, off, nil, m.Compress)
+		if err != nil {
+			t.Errorf("failed to pack extra: %v", err)
+		}
+	}
+
+	// Remove all of the extra bytes but 10 bytes from the end of buf
+	off -= 10
+	buf1 = buf[:len(buf)-off]
+
+	r = new(Msg)
+	if err = r.Unpack(buf1); err != nil && err != ErrTruncated {
+		t.Errorf("unable to unpack cutoff message: %v", err)
+	}
+	if !r.Truncated {
+		t.Error("truncated cutoff message wasn't unpacked as truncated")
+	}
+	if len(r.Answer) != cnt {
+		t.Errorf("answer count after cutoff unpack doesn't match: %d", len(r.Answer))
+	}
+	if len(r.Extra) != 0 {
+		t.Errorf("extra count after cutoff unpack is not zero: %d", len(r.Extra))
+	}
+
+	// Now we want to remove almost all of the answer records too
+	buf1 = make([]byte, m.Len())
+	as := 0
+	for i := 0; i < len(m.Extra); i++ {
+		off1 := off
+		off, err = PackRR(m.Extra[i], buf1, off, nil, m.Compress)
+		as = off - off1
+		if err != nil {
+			t.Errorf("failed to pack extra: %v", err)
+		}
+	}
+
+	// Keep exactly one answer left
+	// This should still cause Answer to be nil
+	off -= as
+	buf1 = buf[:len(buf)-off]
+
+	r = new(Msg)
+	if err = r.Unpack(buf1); err != nil && err != ErrTruncated {
+		t.Errorf("unable to unpack cutoff message: %v", err)
+	}
+	if !r.Truncated {
+		t.Error("truncated cutoff message wasn't unpacked as truncated")
+	}
+	if len(r.Answer) != 0 {
+		t.Errorf("answer count after second cutoff unpack is not zero: %d", len(r.Answer))
+	}
+
+	// Now leave only 1 byte of the question
+	// Since the header is always 12 bytes, we just need to keep 13
+	buf1 = buf[:13]
+
+	r = new(Msg)
+	err = r.Unpack(buf1)
+	if err == nil || err == ErrTruncated {
+		t.Errorf("error should not be ErrTruncated from question cutoff unpack: %v", err)
+	}
+
+	// Finally, if we only have the header, we don't return an error.
+	buf1 = buf[:12]
+
+	r = new(Msg)
+	if err = r.Unpack(buf1); err != nil {
+		t.Errorf("from header-only unpack should not return an error: %v", err)
+	}
+}
+
+func TestTimeout(t *testing.T) {
+	// Set up a dummy UDP server that won't respond
+	addr, err := net.ResolveUDPAddr("udp", ":0")
+	if err != nil {
+		t.Fatalf("unable to resolve local udp address: %v", err)
+	}
+	conn, err := net.ListenUDP("udp", addr)
+	if err != nil {
+		t.Fatalf("unable to run test server: %v", err)
+	}
+	defer conn.Close()
+	addrstr := conn.LocalAddr().String()
+
+	// Message to send
+	m := new(Msg)
+	m.SetQuestion("miek.nl.", TypeTXT)
+
+	// Use a channel + timeout to ensure we don't get stuck if the
+	// Client Timeout is not working properly
+	done := make(chan struct{}, 2)
+
+	timeout := time.Millisecond
+	allowable := timeout + (10 * time.Millisecond)
+	abortAfter := timeout + (100 * time.Millisecond)
+
+	start := time.Now()
+
+	go func() {
+		c := &Client{Timeout: timeout}
+		_, _, err := c.Exchange(m, addrstr)
+		if err == nil {
+			t.Error("no timeout using Client.Exchange")
+		}
+		done <- struct{}{}
+	}()
+
+	go func() {
+		ctx, cancel := context.WithTimeout(context.Background(), timeout)
+		defer cancel()
+		c := &Client{}
+		_, _, err := c.ExchangeContext(ctx, m, addrstr)
+		if err == nil {
+			t.Error("no timeout using Client.ExchangeContext")
+		}
+		done <- struct{}{}
+	}()
+
+	// Wait for both the Exchange and ExchangeContext tests to be done.
+	for i := 0; i < 2; i++ {
+		select {
+		case <-done:
+		case <-time.After(abortAfter):
+		}
+	}
+
+	length := time.Since(start)
+
+	if length > allowable {
+		t.Errorf("exchange took longer %v than specified Timeout %v", length, allowable)
+	}
+}
+
+// Check that responses from deduplicated requests aren't shared between callers
+func TestConcurrentExchanges(t *testing.T) {
+	cases := make([]*Msg, 2)
+	cases[0] = new(Msg)
+	cases[1] = new(Msg)
+	cases[1].Truncated = true
+	for _, m := range cases {
+		block := make(chan struct{})
+		waiting := make(chan struct{})
+
+		handler := func(w ResponseWriter, req *Msg) {
+			r := m.Copy()
+			r.SetReply(req)
+
+			waiting <- struct{}{}
+			<-block
+			w.WriteMsg(r)
+		}
+
+		HandleFunc("miek.nl.", handler)
+		defer HandleRemove("miek.nl.")
+
+		s, addrstr, err := RunLocalUDPServer(":0")
+		if err != nil {
+			t.Fatalf("unable to run test server: %s", err)
+		}
+		defer s.Shutdown()
+
+		m := new(Msg)
+		m.SetQuestion("miek.nl.", TypeSRV)
+		c := &Client{
+			SingleInflight: true,
+		}
+		r := make([]*Msg, 2)
+
+		var wg sync.WaitGroup
+		wg.Add(len(r))
+		for i := 0; i < len(r); i++ {
+			go func(i int) {
+				defer wg.Done()
+				r[i], _, _ = c.Exchange(m.Copy(), addrstr)
+				if r[i] == nil {
+					t.Errorf("response %d is nil", i)
+				}
+			}(i)
+		}
+		select {
+		case <-waiting:
+		case <-time.After(time.Second):
+			t.FailNow()
+		}
+		close(block)
+		wg.Wait()
+
+		if r[0] == r[1] {
+			t.Errorf("got same response, expected non-shared responses")
+		}
+	}
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/clientconfig.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/clientconfig.go
new file mode 100644
index 0000000..f13cfa3
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/clientconfig.go
@@ -0,0 +1,139 @@
+package dns
+
+import (
+	"bufio"
+	"io"
+	"os"
+	"strconv"
+	"strings"
+)
+
+// ClientConfig wraps the contents of the /etc/resolv.conf file.
+type ClientConfig struct {
+	Servers  []string // servers to use
+	Search   []string // suffixes to append to local name
+	Port     string   // what port to use
+	Ndots    int      // number of dots in name to trigger absolute lookup
+	Timeout  int      // seconds before giving up on packet
+	Attempts int      // lost packets before giving up on server, not used in the package dns
+}
+
+// ClientConfigFromFile parses a resolv.conf(5) like file and returns
+// a *ClientConfig.
+func ClientConfigFromFile(resolvconf string) (*ClientConfig, error) {
+	file, err := os.Open(resolvconf)
+	if err != nil {
+		return nil, err
+	}
+	defer file.Close()
+	return ClientConfigFromReader(file)
+}
+
+// ClientConfigFromReader works like ClientConfigFromFile but takes an io.Reader as argument
+func ClientConfigFromReader(resolvconf io.Reader) (*ClientConfig, error) {
+	c := new(ClientConfig)
+	scanner := bufio.NewScanner(resolvconf)
+	c.Servers = make([]string, 0)
+	c.Search = make([]string, 0)
+	c.Port = "53"
+	c.Ndots = 1
+	c.Timeout = 5
+	c.Attempts = 2
+
+	for scanner.Scan() {
+		if err := scanner.Err(); err != nil {
+			return nil, err
+		}
+		line := scanner.Text()
+		f := strings.Fields(line)
+		if len(f) < 1 {
+			continue
+		}
+		switch f[0] {
+		case "nameserver": // add one name server
+			if len(f) > 1 {
+				// One more check: make sure server name is
+				// just an IP address.  Otherwise we need DNS
+				// to look it up.
+				name := f[1]
+				c.Servers = append(c.Servers, name)
+			}
+
+		case "domain": // set search path to just this domain
+			if len(f) > 1 {
+				c.Search = make([]string, 1)
+				c.Search[0] = f[1]
+			} else {
+				c.Search = make([]string, 0)
+			}
+
+		case "search": // set search path to given servers
+			c.Search = make([]string, len(f)-1)
+			for i := 0; i < len(c.Search); i++ {
+				c.Search[i] = f[i+1]
+			}
+
+		case "options": // magic options
+			for i := 1; i < len(f); i++ {
+				s := f[i]
+				switch {
+				case len(s) >= 6 && s[:6] == "ndots:":
+					n, _ := strconv.Atoi(s[6:])
+					if n < 0 {
+						n = 0
+					} else if n > 15 {
+						n = 15
+					}
+					c.Ndots = n
+				case len(s) >= 8 && s[:8] == "timeout:":
+					n, _ := strconv.Atoi(s[8:])
+					if n < 1 {
+						n = 1
+					}
+					c.Timeout = n
+				case len(s) >= 9 && s[:9] == "attempts:":
+					n, _ := strconv.Atoi(s[9:])
+					if n < 1 {
+						n = 1
+					}
+					c.Attempts = n
+				case s == "rotate":
+					/* not imp */
+				}
+			}
+		}
+	}
+	return c, nil
+}
+
+// NameList returns all of the names that should be queried based on the
+// config. It is based off of go's net/dns name building, but it does not
+// check the length of the resulting names.
+func (c *ClientConfig) NameList(name string) []string {
+	// if this domain is already fully qualified, no append needed.
+	if IsFqdn(name) {
+		return []string{name}
+	}
+
+	// Check to see if the name has more labels than Ndots. Do this before making
+	// the domain fully qualified.
+	hasNdots := CountLabel(name) > c.Ndots
+	// Make the domain fully qualified.
+	name = Fqdn(name)
+
+	// Make a list of names based off search.
+	names := []string{}
+
+	// If name has enough dots, try that first.
+	if hasNdots {
+		names = append(names, name)
+	}
+	for _, s := range c.Search {
+		names = append(names, Fqdn(name+s))
+	}
+	// If we didn't have enough dots, try after suffixes.
+	if !hasNdots {
+		names = append(names, name)
+	}
+	return names
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/clientconfig_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/clientconfig_test.go
new file mode 100644
index 0000000..ad5d7d0
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/clientconfig_test.go
@@ -0,0 +1,181 @@
+package dns
+
+import (
+	"io/ioutil"
+	"os"
+	"path/filepath"
+	"strings"
+	"testing"
+)
+
+const normal string = `
+# Comment
+domain somedomain.com
+nameserver 10.28.10.2
+nameserver 11.28.10.1
+`
+
+const missingNewline string = `
+domain somedomain.com
+nameserver 10.28.10.2
+nameserver 11.28.10.1` // <- NOTE: NO newline.
+
+func testConfig(t *testing.T, data string) {
+	cc, err := ClientConfigFromReader(strings.NewReader(data))
+	if err != nil {
+		t.Errorf("error parsing resolv.conf: %v", err)
+	}
+	if l := len(cc.Servers); l != 2 {
+		t.Errorf("incorrect number of nameservers detected: %d", l)
+	}
+	if l := len(cc.Search); l != 1 {
+		t.Errorf("domain directive not parsed correctly: %v", cc.Search)
+	} else {
+		if cc.Search[0] != "somedomain.com" {
+			t.Errorf("domain is unexpected: %v", cc.Search[0])
+		}
+	}
+}
+
+func TestNameserver(t *testing.T)          { testConfig(t, normal) }
+func TestMissingFinalNewLine(t *testing.T) { testConfig(t, missingNewline) }
+
+func TestNdots(t *testing.T) {
+	ndotsVariants := map[string]int{
+		"options ndots:0":  0,
+		"options ndots:1":  1,
+		"options ndots:15": 15,
+		"options ndots:16": 15,
+		"options ndots:-1": 0,
+		"":                 1,
+	}
+
+	for data := range ndotsVariants {
+		cc, err := ClientConfigFromReader(strings.NewReader(data))
+		if err != nil {
+			t.Errorf("error parsing resolv.conf: %v", err)
+		}
+		if cc.Ndots != ndotsVariants[data] {
+			t.Errorf("Ndots not properly parsed: (Expected: %d / Was: %d)", ndotsVariants[data], cc.Ndots)
+		}
+	}
+}
+
+func TestClientConfigFromReaderAttempts(t *testing.T) {
+	testCases := []struct {
+		data     string
+		expected int
+	}{
+		{data: "options attempts:0", expected: 1},
+		{data: "options attempts:1", expected: 1},
+		{data: "options attempts:15", expected: 15},
+		{data: "options attempts:16", expected: 16},
+		{data: "options attempts:-1", expected: 1},
+		{data: "options attempt:", expected: 2},
+	}
+
+	for _, test := range testCases {
+		test := test
+		t.Run(strings.Replace(test.data, ":", " ", -1), func(t *testing.T) {
+			t.Parallel()
+
+			cc, err := ClientConfigFromReader(strings.NewReader(test.data))
+			if err != nil {
+				t.Errorf("error parsing resolv.conf: %v", err)
+			}
+			if cc.Attempts != test.expected {
+				t.Errorf("A attempts not properly parsed: (Expected: %d / Was: %d)", test.expected, cc.Attempts)
+			}
+		})
+	}
+}
+
+func TestReadFromFile(t *testing.T) {
+	tempDir, err := ioutil.TempDir("", "")
+	if err != nil {
+		t.Fatalf("tempDir: %v", err)
+	}
+	defer os.RemoveAll(tempDir)
+
+	path := filepath.Join(tempDir, "resolv.conf")
+	if err := ioutil.WriteFile(path, []byte(normal), 0644); err != nil {
+		t.Fatalf("writeFile: %v", err)
+	}
+	cc, err := ClientConfigFromFile(path)
+	if err != nil {
+		t.Errorf("error parsing resolv.conf: %v", err)
+	}
+	if l := len(cc.Servers); l != 2 {
+		t.Errorf("incorrect number of nameservers detected: %d", l)
+	}
+	if l := len(cc.Search); l != 1 {
+		t.Errorf("domain directive not parsed correctly: %v", cc.Search)
+	} else {
+		if cc.Search[0] != "somedomain.com" {
+			t.Errorf("domain is unexpected: %v", cc.Search[0])
+		}
+	}
+}
+
+func TestNameListNdots1(t *testing.T) {
+	cfg := ClientConfig{
+		Ndots: 1,
+	}
+	// fqdn should be only result returned
+	names := cfg.NameList("miek.nl.")
+	if len(names) != 1 {
+		t.Errorf("NameList returned != 1 names: %v", names)
+	} else if names[0] != "miek.nl." {
+		t.Errorf("NameList didn't return sent fqdn domain: %v", names[0])
+	}
+
+	cfg.Search = []string{
+		"test",
+	}
+	// Sent domain has NDots and search
+	names = cfg.NameList("miek.nl")
+	if len(names) != 2 {
+		t.Errorf("NameList returned != 2 names: %v", names)
+	} else if names[0] != "miek.nl." {
+		t.Errorf("NameList didn't return sent domain first: %v", names[0])
+	} else if names[1] != "miek.nl.test." {
+		t.Errorf("NameList didn't return search last: %v", names[1])
+	}
+}
+func TestNameListNdots2(t *testing.T) {
+	cfg := ClientConfig{
+		Ndots: 2,
+	}
+
+	// Sent domain has less than NDots and search
+	cfg.Search = []string{
+		"test",
+	}
+	names := cfg.NameList("miek.nl")
+
+	if len(names) != 2 {
+		t.Errorf("NameList returned != 2 names: %v", names)
+	} else if names[0] != "miek.nl.test." {
+		t.Errorf("NameList didn't return search first: %v", names[0])
+	} else if names[1] != "miek.nl." {
+		t.Errorf("NameList didn't return sent domain last: %v", names[1])
+	}
+}
+
+func TestNameListNdots0(t *testing.T) {
+	cfg := ClientConfig{
+		Ndots: 0,
+	}
+	cfg.Search = []string{
+		"test",
+	}
+	// Sent domain has less than NDots and search
+	names := cfg.NameList("miek")
+	if len(names) != 2 {
+		t.Errorf("NameList returned != 2 names: %v", names)
+	} else if names[0] != "miek." {
+		t.Errorf("NameList didn't return search first: %v", names[0])
+	} else if names[1] != "miek.test." {
+		t.Errorf("NameList didn't return sent domain last: %v", names[1])
+	}
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/compress_generate.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/compress_generate.go
new file mode 100644
index 0000000..87fb36f
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/compress_generate.go
@@ -0,0 +1,188 @@
+//+build ignore
+
+// compression_generate.go is meant to run with go generate. It will use
+// go/{importer,types} to track down all the RR struct types. Then for each type
+// it will look to see if there are (compressible) names, if so it will add that
+// type to compressionLenHelperType and comressionLenSearchType which "fake" the
+// compression so that Len() is fast.
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"go/format"
+	"go/importer"
+	"go/types"
+	"log"
+	"os"
+)
+
+var packageHdr = `
+// Code generated by "go run compress_generate.go"; DO NOT EDIT.
+
+package dns
+
+`
+
+// getTypeStruct will take a type and the package scope, and return the
+// (innermost) struct if the type is considered a RR type (currently defined as
+// those structs beginning with a RR_Header, could be redefined as implementing
+// the RR interface). The bool return value indicates if embedded structs were
+// resolved.
+func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) {
+	st, ok := t.Underlying().(*types.Struct)
+	if !ok {
+		return nil, false
+	}
+	if st.Field(0).Type() == scope.Lookup("RR_Header").Type() {
+		return st, false
+	}
+	if st.Field(0).Anonymous() {
+		st, _ := getTypeStruct(st.Field(0).Type(), scope)
+		return st, true
+	}
+	return nil, false
+}
+
+func main() {
+	// Import and type-check the package
+	pkg, err := importer.Default().Import("github.com/miekg/dns")
+	fatalIfErr(err)
+	scope := pkg.Scope()
+
+	var domainTypes []string  // Types that have a domain name in them (either compressible or not).
+	var cdomainTypes []string // Types that have a compressible domain name in them (subset of domainType)
+Names:
+	for _, name := range scope.Names() {
+		o := scope.Lookup(name)
+		if o == nil || !o.Exported() {
+			continue
+		}
+		st, _ := getTypeStruct(o.Type(), scope)
+		if st == nil {
+			continue
+		}
+		if name == "PrivateRR" {
+			continue
+		}
+
+		if scope.Lookup("Type"+o.Name()) == nil && o.Name() != "RFC3597" {
+			log.Fatalf("Constant Type%s does not exist.", o.Name())
+		}
+
+		for i := 1; i < st.NumFields(); i++ {
+			if _, ok := st.Field(i).Type().(*types.Slice); ok {
+				if st.Tag(i) == `dns:"domain-name"` {
+					domainTypes = append(domainTypes, o.Name())
+					continue Names
+				}
+				if st.Tag(i) == `dns:"cdomain-name"` {
+					cdomainTypes = append(cdomainTypes, o.Name())
+					domainTypes = append(domainTypes, o.Name())
+					continue Names
+				}
+				continue
+			}
+
+			switch {
+			case st.Tag(i) == `dns:"domain-name"`:
+				domainTypes = append(domainTypes, o.Name())
+				continue Names
+			case st.Tag(i) == `dns:"cdomain-name"`:
+				cdomainTypes = append(cdomainTypes, o.Name())
+				domainTypes = append(domainTypes, o.Name())
+				continue Names
+			}
+		}
+	}
+
+	b := &bytes.Buffer{}
+	b.WriteString(packageHdr)
+
+	// compressionLenHelperType - all types that have domain-name/cdomain-name can be used for compressing names
+
+	fmt.Fprint(b, "func compressionLenHelperType(c map[string]int, r RR) {\n")
+	fmt.Fprint(b, "switch x := r.(type) {\n")
+	for _, name := range domainTypes {
+		o := scope.Lookup(name)
+		st, _ := getTypeStruct(o.Type(), scope)
+
+		fmt.Fprintf(b, "case *%s:\n", name)
+		for i := 1; i < st.NumFields(); i++ {
+			out := func(s string) { fmt.Fprintf(b, "compressionLenHelper(c, x.%s)\n", st.Field(i).Name()) }
+
+			if _, ok := st.Field(i).Type().(*types.Slice); ok {
+				switch st.Tag(i) {
+				case `dns:"domain-name"`:
+					fallthrough
+				case `dns:"cdomain-name"`:
+					// For HIP we need to slice over the elements in this slice.
+					fmt.Fprintf(b, `for i := range x.%s {
+						compressionLenHelper(c, x.%s[i])
+					}
+`, st.Field(i).Name(), st.Field(i).Name())
+				}
+				continue
+			}
+
+			switch {
+			case st.Tag(i) == `dns:"cdomain-name"`:
+				fallthrough
+			case st.Tag(i) == `dns:"domain-name"`:
+				out(st.Field(i).Name())
+			}
+		}
+	}
+	fmt.Fprintln(b, "}\n}\n\n")
+
+	// compressionLenSearchType - search cdomain-tags types for compressible names.
+
+	fmt.Fprint(b, "func compressionLenSearchType(c map[string]int, r RR) (int, bool) {\n")
+	fmt.Fprint(b, "switch x := r.(type) {\n")
+	for _, name := range cdomainTypes {
+		o := scope.Lookup(name)
+		st, _ := getTypeStruct(o.Type(), scope)
+
+		fmt.Fprintf(b, "case *%s:\n", name)
+		j := 1
+		for i := 1; i < st.NumFields(); i++ {
+			out := func(s string, j int) {
+				fmt.Fprintf(b, "k%d, ok%d := compressionLenSearch(c, x.%s)\n", j, j, st.Field(i).Name())
+			}
+
+			// There are no slice types with names that can be compressed.
+
+			switch {
+			case st.Tag(i) == `dns:"cdomain-name"`:
+				out(st.Field(i).Name(), j)
+				j++
+			}
+		}
+		k := "k1"
+		ok := "ok1"
+		for i := 2; i < j; i++ {
+			k += fmt.Sprintf(" + k%d", i)
+			ok += fmt.Sprintf(" && ok%d", i)
+		}
+		fmt.Fprintf(b, "return %s, %s\n", k, ok)
+	}
+	fmt.Fprintln(b, "}\nreturn 0, false\n}\n\n")
+
+	// gofmt
+	res, err := format.Source(b.Bytes())
+	if err != nil {
+		b.WriteTo(os.Stderr)
+		log.Fatal(err)
+	}
+
+	f, err := os.Create("zcompress.go")
+	fatalIfErr(err)
+	defer f.Close()
+	f.Write(res)
+}
+
+func fatalIfErr(err error) {
+	if err != nil {
+		log.Fatal(err)
+	}
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dane.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dane.go
new file mode 100644
index 0000000..8c4a14e
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dane.go
@@ -0,0 +1,43 @@
+package dns
+
+import (
+	"crypto/sha256"
+	"crypto/sha512"
+	"crypto/x509"
+	"encoding/hex"
+	"errors"
+)
+
+// CertificateToDANE converts a certificate to a hex string as used in the TLSA or SMIMEA records.
+func CertificateToDANE(selector, matchingType uint8, cert *x509.Certificate) (string, error) {
+	switch matchingType {
+	case 0:
+		switch selector {
+		case 0:
+			return hex.EncodeToString(cert.Raw), nil
+		case 1:
+			return hex.EncodeToString(cert.RawSubjectPublicKeyInfo), nil
+		}
+	case 1:
+		h := sha256.New()
+		switch selector {
+		case 0:
+			h.Write(cert.Raw)
+			return hex.EncodeToString(h.Sum(nil)), nil
+		case 1:
+			h.Write(cert.RawSubjectPublicKeyInfo)
+			return hex.EncodeToString(h.Sum(nil)), nil
+		}
+	case 2:
+		h := sha512.New()
+		switch selector {
+		case 0:
+			h.Write(cert.Raw)
+			return hex.EncodeToString(h.Sum(nil)), nil
+		case 1:
+			h.Write(cert.RawSubjectPublicKeyInfo)
+			return hex.EncodeToString(h.Sum(nil)), nil
+		}
+	}
+	return "", errors.New("dns: bad MatchingType or Selector")
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/defaults.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/defaults.go
new file mode 100644
index 0000000..14e18b0
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/defaults.go
@@ -0,0 +1,288 @@
+package dns
+
+import (
+	"errors"
+	"net"
+	"strconv"
+)
+
+const hexDigit = "0123456789abcdef"
+
+// Everything is assumed in ClassINET.
+
+// SetReply creates a reply message from a request message.
+func (dns *Msg) SetReply(request *Msg) *Msg {
+	dns.Id = request.Id
+	dns.Response = true
+	dns.Opcode = request.Opcode
+	if dns.Opcode == OpcodeQuery {
+		dns.RecursionDesired = request.RecursionDesired // Copy rd bit
+		dns.CheckingDisabled = request.CheckingDisabled // Copy cd bit
+	}
+	dns.Rcode = RcodeSuccess
+	if len(request.Question) > 0 {
+		dns.Question = make([]Question, 1)
+		dns.Question[0] = request.Question[0]
+	}
+	return dns
+}
+
+// SetQuestion creates a question message, it sets the Question
+// section, generates an Id and sets the RecursionDesired (RD)
+// bit to true.
+func (dns *Msg) SetQuestion(z string, t uint16) *Msg {
+	dns.Id = Id()
+	dns.RecursionDesired = true
+	dns.Question = make([]Question, 1)
+	dns.Question[0] = Question{z, t, ClassINET}
+	return dns
+}
+
+// SetNotify creates a notify message, it sets the Question
+// section, generates an Id and sets the Authoritative (AA)
+// bit to true.
+func (dns *Msg) SetNotify(z string) *Msg {
+	dns.Opcode = OpcodeNotify
+	dns.Authoritative = true
+	dns.Id = Id()
+	dns.Question = make([]Question, 1)
+	dns.Question[0] = Question{z, TypeSOA, ClassINET}
+	return dns
+}
+
+// SetRcode creates an error message suitable for the request.
+func (dns *Msg) SetRcode(request *Msg, rcode int) *Msg {
+	dns.SetReply(request)
+	dns.Rcode = rcode
+	return dns
+}
+
+// SetRcodeFormatError creates a message with FormError set.
+func (dns *Msg) SetRcodeFormatError(request *Msg) *Msg {
+	dns.Rcode = RcodeFormatError
+	dns.Opcode = OpcodeQuery
+	dns.Response = true
+	dns.Authoritative = false
+	dns.Id = request.Id
+	return dns
+}
+
+// SetUpdate makes the message a dynamic update message. It
+// sets the ZONE section to: z, TypeSOA, ClassINET.
+func (dns *Msg) SetUpdate(z string) *Msg {
+	dns.Id = Id()
+	dns.Response = false
+	dns.Opcode = OpcodeUpdate
+	dns.Compress = false // BIND9 cannot handle compression
+	dns.Question = make([]Question, 1)
+	dns.Question[0] = Question{z, TypeSOA, ClassINET}
+	return dns
+}
+
+// SetIxfr creates message for requesting an IXFR.
+func (dns *Msg) SetIxfr(z string, serial uint32, ns, mbox string) *Msg {
+	dns.Id = Id()
+	dns.Question = make([]Question, 1)
+	dns.Ns = make([]RR, 1)
+	s := new(SOA)
+	s.Hdr = RR_Header{z, TypeSOA, ClassINET, defaultTtl, 0}
+	s.Serial = serial
+	s.Ns = ns
+	s.Mbox = mbox
+	dns.Question[0] = Question{z, TypeIXFR, ClassINET}
+	dns.Ns[0] = s
+	return dns
+}
+
+// SetAxfr creates message for requesting an AXFR.
+func (dns *Msg) SetAxfr(z string) *Msg {
+	dns.Id = Id()
+	dns.Question = make([]Question, 1)
+	dns.Question[0] = Question{z, TypeAXFR, ClassINET}
+	return dns
+}
+
+// SetTsig appends a TSIG RR to the message.
+// This is only a skeleton TSIG RR that is added as the last RR in the
+// additional section. The Tsig is calculated when the message is being send.
+func (dns *Msg) SetTsig(z, algo string, fudge uint16, timesigned int64) *Msg {
+	t := new(TSIG)
+	t.Hdr = RR_Header{z, TypeTSIG, ClassANY, 0, 0}
+	t.Algorithm = algo
+	t.Fudge = fudge
+	t.TimeSigned = uint64(timesigned)
+	t.OrigId = dns.Id
+	dns.Extra = append(dns.Extra, t)
+	return dns
+}
+
+// SetEdns0 appends a EDNS0 OPT RR to the message.
+// TSIG should always the last RR in a message.
+func (dns *Msg) SetEdns0(udpsize uint16, do bool) *Msg {
+	e := new(OPT)
+	e.Hdr.Name = "."
+	e.Hdr.Rrtype = TypeOPT
+	e.SetUDPSize(udpsize)
+	if do {
+		e.SetDo()
+	}
+	dns.Extra = append(dns.Extra, e)
+	return dns
+}
+
+// IsTsig checks if the message has a TSIG record as the last record
+// in the additional section. It returns the TSIG record found or nil.
+func (dns *Msg) IsTsig() *TSIG {
+	if len(dns.Extra) > 0 {
+		if dns.Extra[len(dns.Extra)-1].Header().Rrtype == TypeTSIG {
+			return dns.Extra[len(dns.Extra)-1].(*TSIG)
+		}
+	}
+	return nil
+}
+
+// IsEdns0 checks if the message has a EDNS0 (OPT) record, any EDNS0
+// record in the additional section will do. It returns the OPT record
+// found or nil.
+func (dns *Msg) IsEdns0() *OPT {
+	// EDNS0 is at the end of the additional section, start there.
+	// We might want to change this to *only* look at the last two
+	// records. So we see TSIG and/or OPT - this a slightly bigger
+	// change though.
+	for i := len(dns.Extra) - 1; i >= 0; i-- {
+		if dns.Extra[i].Header().Rrtype == TypeOPT {
+			return dns.Extra[i].(*OPT)
+		}
+	}
+	return nil
+}
+
+// IsDomainName checks if s is a valid domain name, it returns the number of
+// labels and true, when a domain name is valid.  Note that non fully qualified
+// domain name is considered valid, in this case the last label is counted in
+// the number of labels.  When false is returned the number of labels is not
+// defined.  Also note that this function is extremely liberal; almost any
+// string is a valid domain name as the DNS is 8 bit protocol. It checks if each
+// label fits in 63 characters, but there is no length check for the entire
+// string s. I.e.  a domain name longer than 255 characters is considered valid.
+func IsDomainName(s string) (labels int, ok bool) {
+	_, labels, err := packDomainName(s, nil, 0, nil, false)
+	return labels, err == nil
+}
+
+// IsSubDomain checks if child is indeed a child of the parent. If child and parent
+// are the same domain true is returned as well.
+func IsSubDomain(parent, child string) bool {
+	// Entire child is contained in parent
+	return CompareDomainName(parent, child) == CountLabel(parent)
+}
+
+// IsMsg sanity checks buf and returns an error if it isn't a valid DNS packet.
+// The checking is performed on the binary payload.
+func IsMsg(buf []byte) error {
+	// Header
+	if len(buf) < 12 {
+		return errors.New("dns: bad message header")
+	}
+	// Header: Opcode
+	// TODO(miek): more checks here, e.g. check all header bits.
+	return nil
+}
+
+// IsFqdn checks if a domain name is fully qualified.
+func IsFqdn(s string) bool {
+	l := len(s)
+	if l == 0 {
+		return false
+	}
+	return s[l-1] == '.'
+}
+
+// IsRRset checks if a set of RRs is a valid RRset as defined by RFC 2181.
+// This means the RRs need to have the same type, name, and class. Returns true
+// if the RR set is valid, otherwise false.
+func IsRRset(rrset []RR) bool {
+	if len(rrset) == 0 {
+		return false
+	}
+	if len(rrset) == 1 {
+		return true
+	}
+	rrHeader := rrset[0].Header()
+	rrType := rrHeader.Rrtype
+	rrClass := rrHeader.Class
+	rrName := rrHeader.Name
+
+	for _, rr := range rrset[1:] {
+		curRRHeader := rr.Header()
+		if curRRHeader.Rrtype != rrType || curRRHeader.Class != rrClass || curRRHeader.Name != rrName {
+			// Mismatch between the records, so this is not a valid rrset for
+			//signing/verifying
+			return false
+		}
+	}
+
+	return true
+}
+
+// Fqdn return the fully qualified domain name from s.
+// If s is already fully qualified, it behaves as the identity function.
+func Fqdn(s string) string {
+	if IsFqdn(s) {
+		return s
+	}
+	return s + "."
+}
+
+// Copied from the official Go code.
+
+// ReverseAddr returns the in-addr.arpa. or ip6.arpa. hostname of the IP
+// address suitable for reverse DNS (PTR) record lookups or an error if it fails
+// to parse the IP address.
+func ReverseAddr(addr string) (arpa string, err error) {
+	ip := net.ParseIP(addr)
+	if ip == nil {
+		return "", &Error{err: "unrecognized address: " + addr}
+	}
+	if ip.To4() != nil {
+		return strconv.Itoa(int(ip[15])) + "." + strconv.Itoa(int(ip[14])) + "." + strconv.Itoa(int(ip[13])) + "." +
+			strconv.Itoa(int(ip[12])) + ".in-addr.arpa.", nil
+	}
+	// Must be IPv6
+	buf := make([]byte, 0, len(ip)*4+len("ip6.arpa."))
+	// Add it, in reverse, to the buffer
+	for i := len(ip) - 1; i >= 0; i-- {
+		v := ip[i]
+		buf = append(buf, hexDigit[v&0xF])
+		buf = append(buf, '.')
+		buf = append(buf, hexDigit[v>>4])
+		buf = append(buf, '.')
+	}
+	// Append "ip6.arpa." and return (buf already has the final .)
+	buf = append(buf, "ip6.arpa."...)
+	return string(buf), nil
+}
+
+// String returns the string representation for the type t.
+func (t Type) String() string {
+	if t1, ok := TypeToString[uint16(t)]; ok {
+		return t1
+	}
+	return "TYPE" + strconv.Itoa(int(t))
+}
+
+// String returns the string representation for the class c.
+func (c Class) String() string {
+	if s, ok := ClassToString[uint16(c)]; ok {
+		// Only emit mnemonics when they are unambiguous, specically ANY is in both.
+		if _, ok := StringToType[s]; !ok {
+			return s
+		}
+	}
+	return "CLASS" + strconv.Itoa(int(c))
+}
+
+// String returns the string representation for the name n.
+func (n Name) String() string {
+	return sprintName(string(n))
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dns.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dns.go
new file mode 100644
index 0000000..5133eac
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dns.go
@@ -0,0 +1,107 @@
+package dns
+
+import "strconv"
+
+const (
+	year68     = 1 << 31 // For RFC1982 (Serial Arithmetic) calculations in 32 bits.
+	defaultTtl = 3600    // Default internal TTL.
+
+	// DefaultMsgSize is the standard default for messages larger than 512 bytes.
+	DefaultMsgSize = 4096
+	// MinMsgSize is the minimal size of a DNS packet.
+	MinMsgSize = 512
+	// MaxMsgSize is the largest possible DNS packet.
+	MaxMsgSize = 65535
+)
+
+// Error represents a DNS error.
+type Error struct{ err string }
+
+func (e *Error) Error() string {
+	if e == nil {
+		return "dns: <nil>"
+	}
+	return "dns: " + e.err
+}
+
+// An RR represents a resource record.
+type RR interface {
+	// Header returns the header of an resource record. The header contains
+	// everything up to the rdata.
+	Header() *RR_Header
+	// String returns the text representation of the resource record.
+	String() string
+
+	// copy returns a copy of the RR
+	copy() RR
+	// len returns the length (in octets) of the uncompressed RR in wire format.
+	len() int
+	// pack packs an RR into wire format.
+	pack([]byte, int, map[string]int, bool) (int, error)
+}
+
+// RR_Header is the header all DNS resource records share.
+type RR_Header struct {
+	Name     string `dns:"cdomain-name"`
+	Rrtype   uint16
+	Class    uint16
+	Ttl      uint32
+	Rdlength uint16 // Length of data after header.
+}
+
+// Header returns itself. This is here to make RR_Header implements the RR interface.
+func (h *RR_Header) Header() *RR_Header { return h }
+
+// Just to implement the RR interface.
+func (h *RR_Header) copy() RR { return nil }
+
+func (h *RR_Header) copyHeader() *RR_Header {
+	r := new(RR_Header)
+	r.Name = h.Name
+	r.Rrtype = h.Rrtype
+	r.Class = h.Class
+	r.Ttl = h.Ttl
+	r.Rdlength = h.Rdlength
+	return r
+}
+
+func (h *RR_Header) String() string {
+	var s string
+
+	if h.Rrtype == TypeOPT {
+		s = ";"
+		// and maybe other things
+	}
+
+	s += sprintName(h.Name) + "\t"
+	s += strconv.FormatInt(int64(h.Ttl), 10) + "\t"
+	s += Class(h.Class).String() + "\t"
+	s += Type(h.Rrtype).String() + "\t"
+	return s
+}
+
+func (h *RR_Header) len() int {
+	l := len(h.Name) + 1
+	l += 10 // rrtype(2) + class(2) + ttl(4) + rdlength(2)
+	return l
+}
+
+// ToRFC3597 converts a known RR to the unknown RR representation from RFC 3597.
+func (rr *RFC3597) ToRFC3597(r RR) error {
+	buf := make([]byte, r.len()*2)
+	off, err := PackRR(r, buf, 0, nil, false)
+	if err != nil {
+		return err
+	}
+	buf = buf[:off]
+	if int(r.Header().Rdlength) > off {
+		return ErrBuf
+	}
+
+	rfc3597, _, err := unpackRFC3597(*r.Header(), buf, off-int(r.Header().Rdlength))
+	if err != nil {
+		return err
+	}
+	*rr = *rfc3597.(*RFC3597)
+	return nil
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dns_bench_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dns_bench_test.go
new file mode 100644
index 0000000..7bf8bd2
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dns_bench_test.go
@@ -0,0 +1,230 @@
+package dns
+
+import (
+	"net"
+	"testing"
+)
+
+func BenchmarkMsgLength(b *testing.B) {
+	b.StopTimer()
+	makeMsg := func(question string, ans, ns, e []RR) *Msg {
+		msg := new(Msg)
+		msg.SetQuestion(Fqdn(question), TypeANY)
+		msg.Answer = append(msg.Answer, ans...)
+		msg.Ns = append(msg.Ns, ns...)
+		msg.Extra = append(msg.Extra, e...)
+		msg.Compress = true
+		return msg
+	}
+	name1 := "12345678901234567890123456789012345.12345678.123."
+	rrMx := testRR(name1 + " 3600 IN MX 10 " + name1)
+	msg := makeMsg(name1, []RR{rrMx, rrMx}, nil, nil)
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		msg.Len()
+	}
+}
+
+func BenchmarkMsgLengthNoCompression(b *testing.B) {
+	b.StopTimer()
+	makeMsg := func(question string, ans, ns, e []RR) *Msg {
+		msg := new(Msg)
+		msg.SetQuestion(Fqdn(question), TypeANY)
+		msg.Answer = append(msg.Answer, ans...)
+		msg.Ns = append(msg.Ns, ns...)
+		msg.Extra = append(msg.Extra, e...)
+		return msg
+	}
+	name1 := "12345678901234567890123456789012345.12345678.123."
+	rrMx := testRR(name1 + " 3600 IN MX 10 " + name1)
+	msg := makeMsg(name1, []RR{rrMx, rrMx}, nil, nil)
+	b.StartTimer()
+	for i := 0; i < b.N; i++ {
+		msg.Len()
+	}
+}
+
+func BenchmarkMsgLengthPack(b *testing.B) {
+	makeMsg := func(question string, ans, ns, e []RR) *Msg {
+		msg := new(Msg)
+		msg.SetQuestion(Fqdn(question), TypeANY)
+		msg.Answer = append(msg.Answer, ans...)
+		msg.Ns = append(msg.Ns, ns...)
+		msg.Extra = append(msg.Extra, e...)
+		msg.Compress = true
+		return msg
+	}
+	name1 := "12345678901234567890123456789012345.12345678.123."
+	rrMx := testRR(name1 + " 3600 IN MX 10 " + name1)
+	msg := makeMsg(name1, []RR{rrMx, rrMx}, nil, nil)
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		_, _ = msg.Pack()
+	}
+}
+
+func BenchmarkPackDomainName(b *testing.B) {
+	name1 := "12345678901234567890123456789012345.12345678.123."
+	buf := make([]byte, len(name1)+1)
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		_, _ = PackDomainName(name1, buf, 0, nil, false)
+	}
+}
+
+func BenchmarkUnpackDomainName(b *testing.B) {
+	name1 := "12345678901234567890123456789012345.12345678.123."
+	buf := make([]byte, len(name1)+1)
+	_, _ = PackDomainName(name1, buf, 0, nil, false)
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		_, _, _ = UnpackDomainName(buf, 0)
+	}
+}
+
+func BenchmarkUnpackDomainNameUnprintable(b *testing.B) {
+	name1 := "\x02\x02\x02\x025\x02\x02\x02\x02.12345678.123."
+	buf := make([]byte, len(name1)+1)
+	_, _ = PackDomainName(name1, buf, 0, nil, false)
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		_, _, _ = UnpackDomainName(buf, 0)
+	}
+}
+
+func BenchmarkCopy(b *testing.B) {
+	b.ReportAllocs()
+	m := new(Msg)
+	m.SetQuestion("miek.nl.", TypeA)
+	rr := testRR("miek.nl. 2311 IN A 127.0.0.1")
+	m.Answer = []RR{rr}
+	rr = testRR("miek.nl. 2311 IN NS 127.0.0.1")
+	m.Ns = []RR{rr}
+	rr = testRR("miek.nl. 2311 IN A 127.0.0.1")
+	m.Extra = []RR{rr}
+
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		m.Copy()
+	}
+}
+
+func BenchmarkPackA(b *testing.B) {
+	a := &A{Hdr: RR_Header{Name: ".", Rrtype: TypeA, Class: ClassANY}, A: net.IPv4(127, 0, 0, 1)}
+
+	buf := make([]byte, a.len())
+	b.ReportAllocs()
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		_, _ = PackRR(a, buf, 0, nil, false)
+	}
+}
+
+func BenchmarkUnpackA(b *testing.B) {
+	a := &A{Hdr: RR_Header{Name: ".", Rrtype: TypeA, Class: ClassANY}, A: net.IPv4(127, 0, 0, 1)}
+
+	buf := make([]byte, a.len())
+	PackRR(a, buf, 0, nil, false)
+	a = nil
+	b.ReportAllocs()
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		_, _, _ = UnpackRR(buf, 0)
+	}
+}
+
+func BenchmarkPackMX(b *testing.B) {
+	m := &MX{Hdr: RR_Header{Name: ".", Rrtype: TypeA, Class: ClassANY}, Mx: "mx.miek.nl."}
+
+	buf := make([]byte, m.len())
+	b.ReportAllocs()
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		_, _ = PackRR(m, buf, 0, nil, false)
+	}
+}
+
+func BenchmarkUnpackMX(b *testing.B) {
+	m := &MX{Hdr: RR_Header{Name: ".", Rrtype: TypeA, Class: ClassANY}, Mx: "mx.miek.nl."}
+
+	buf := make([]byte, m.len())
+	PackRR(m, buf, 0, nil, false)
+	m = nil
+	b.ReportAllocs()
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		_, _, _ = UnpackRR(buf, 0)
+	}
+}
+
+func BenchmarkPackAAAAA(b *testing.B) {
+	aaaa := testRR(". IN A ::1")
+
+	buf := make([]byte, aaaa.len())
+	b.ReportAllocs()
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		_, _ = PackRR(aaaa, buf, 0, nil, false)
+	}
+}
+
+func BenchmarkUnpackAAAA(b *testing.B) {
+	aaaa := testRR(". IN A ::1")
+
+	buf := make([]byte, aaaa.len())
+	PackRR(aaaa, buf, 0, nil, false)
+	aaaa = nil
+	b.ReportAllocs()
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		_, _, _ = UnpackRR(buf, 0)
+	}
+}
+
+func BenchmarkPackMsg(b *testing.B) {
+	makeMsg := func(question string, ans, ns, e []RR) *Msg {
+		msg := new(Msg)
+		msg.SetQuestion(Fqdn(question), TypeANY)
+		msg.Answer = append(msg.Answer, ans...)
+		msg.Ns = append(msg.Ns, ns...)
+		msg.Extra = append(msg.Extra, e...)
+		msg.Compress = true
+		return msg
+	}
+	name1 := "12345678901234567890123456789012345.12345678.123."
+	rrMx := testRR(name1 + " 3600 IN MX 10 " + name1)
+	msg := makeMsg(name1, []RR{rrMx, rrMx}, nil, nil)
+	buf := make([]byte, 512)
+	b.ReportAllocs()
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		_, _ = msg.PackBuffer(buf)
+	}
+}
+
+func BenchmarkUnpackMsg(b *testing.B) {
+	makeMsg := func(question string, ans, ns, e []RR) *Msg {
+		msg := new(Msg)
+		msg.SetQuestion(Fqdn(question), TypeANY)
+		msg.Answer = append(msg.Answer, ans...)
+		msg.Ns = append(msg.Ns, ns...)
+		msg.Extra = append(msg.Extra, e...)
+		msg.Compress = true
+		return msg
+	}
+	name1 := "12345678901234567890123456789012345.12345678.123."
+	rrMx := testRR(name1 + " 3600 IN MX 10 " + name1)
+	msg := makeMsg(name1, []RR{rrMx, rrMx}, nil, nil)
+	msgBuf, _ := msg.Pack()
+	b.ReportAllocs()
+	b.ResetTimer()
+	for i := 0; i < b.N; i++ {
+		_ = msg.Unpack(msgBuf)
+	}
+}
+
+func BenchmarkIdGeneration(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		_ = id()
+	}
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dns_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dns_test.go
new file mode 100644
index 0000000..3c9d910
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dns_test.go
@@ -0,0 +1,320 @@
+package dns
+
+import (
+	"bytes"
+	"encoding/hex"
+	"net"
+	"testing"
+)
+
+func TestPackUnpack(t *testing.T) {
+	out := new(Msg)
+	out.Answer = make([]RR, 1)
+	key := new(DNSKEY)
+	key = &DNSKEY{Flags: 257, Protocol: 3, Algorithm: RSASHA1}
+	key.Hdr = RR_Header{Name: "miek.nl.", Rrtype: TypeDNSKEY, Class: ClassINET, Ttl: 3600}
+	key.PublicKey = "AwEAAaHIwpx3w4VHKi6i1LHnTaWeHCL154Jug0Rtc9ji5qwPXpBo6A5sRv7cSsPQKPIwxLpyCrbJ4mr2L0EPOdvP6z6YfljK2ZmTbogU9aSU2fiq/4wjxbdkLyoDVgtO+JsxNN4bjr4WcWhsmk1Hg93FV9ZpkWb0Tbad8DFqNDzr//kZ"
+
+	out.Answer[0] = key
+	msg, err := out.Pack()
+	if err != nil {
+		t.Error("failed to pack msg with DNSKEY")
+	}
+	in := new(Msg)
+	if in.Unpack(msg) != nil {
+		t.Error("failed to unpack msg with DNSKEY")
+	}
+
+	sig := new(RRSIG)
+	sig = &RRSIG{TypeCovered: TypeDNSKEY, Algorithm: RSASHA1, Labels: 2,
+		OrigTtl: 3600, Expiration: 4000, Inception: 4000, KeyTag: 34641, SignerName: "miek.nl.",
+		Signature: "AwEAAaHIwpx3w4VHKi6i1LHnTaWeHCL154Jug0Rtc9ji5qwPXpBo6A5sRv7cSsPQKPIwxLpyCrbJ4mr2L0EPOdvP6z6YfljK2ZmTbogU9aSU2fiq/4wjxbdkLyoDVgtO+JsxNN4bjr4WcWhsmk1Hg93FV9ZpkWb0Tbad8DFqNDzr//kZ"}
+	sig.Hdr = RR_Header{Name: "miek.nl.", Rrtype: TypeRRSIG, Class: ClassINET, Ttl: 3600}
+
+	out.Answer[0] = sig
+	msg, err = out.Pack()
+	if err != nil {
+		t.Error("failed to pack msg with RRSIG")
+	}
+
+	if in.Unpack(msg) != nil {
+		t.Error("failed to unpack msg with RRSIG")
+	}
+}
+
+func TestPackUnpack2(t *testing.T) {
+	m := new(Msg)
+	m.Extra = make([]RR, 1)
+	m.Answer = make([]RR, 1)
+	dom := "miek.nl."
+	rr := new(A)
+	rr.Hdr = RR_Header{Name: dom, Rrtype: TypeA, Class: ClassINET, Ttl: 0}
+	rr.A = net.IPv4(127, 0, 0, 1)
+
+	x := new(TXT)
+	x.Hdr = RR_Header{Name: dom, Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}
+	x.Txt = []string{"heelalaollo"}
+
+	m.Extra[0] = x
+	m.Answer[0] = rr
+	_, err := m.Pack()
+	if err != nil {
+		t.Error("Packing failed: ", err)
+		return
+	}
+}
+
+func TestPackUnpack3(t *testing.T) {
+	m := new(Msg)
+	m.Extra = make([]RR, 2)
+	m.Answer = make([]RR, 1)
+	dom := "miek.nl."
+	rr := new(A)
+	rr.Hdr = RR_Header{Name: dom, Rrtype: TypeA, Class: ClassINET, Ttl: 0}
+	rr.A = net.IPv4(127, 0, 0, 1)
+
+	x1 := new(TXT)
+	x1.Hdr = RR_Header{Name: dom, Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}
+	x1.Txt = []string{}
+
+	x2 := new(TXT)
+	x2.Hdr = RR_Header{Name: dom, Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}
+	x2.Txt = []string{"heelalaollo"}
+
+	m.Extra[0] = x1
+	m.Extra[1] = x2
+	m.Answer[0] = rr
+	b, err := m.Pack()
+	if err != nil {
+		t.Error("packing failed: ", err)
+		return
+	}
+
+	var unpackMsg Msg
+	err = unpackMsg.Unpack(b)
+	if err != nil {
+		t.Error("unpacking failed")
+		return
+	}
+}
+
+func TestBailiwick(t *testing.T) {
+	yes := map[string]string{
+		"miek1.nl": "miek1.nl",
+		"miek.nl":  "ns.miek.nl",
+		".":        "miek.nl",
+	}
+	for parent, child := range yes {
+		if !IsSubDomain(parent, child) {
+			t.Errorf("%s should be child of %s", child, parent)
+			t.Errorf("comparelabels %d", CompareDomainName(parent, child))
+			t.Errorf("lenlabels %d %d", CountLabel(parent), CountLabel(child))
+		}
+	}
+	no := map[string]string{
+		"www.miek.nl":  "ns.miek.nl",
+		"m\\.iek.nl":   "ns.miek.nl",
+		"w\\.iek.nl":   "w.iek.nl",
+		"p\\\\.iek.nl": "ns.p.iek.nl", // p\\.iek.nl , literal \ in domain name
+		"miek.nl":      ".",
+	}
+	for parent, child := range no {
+		if IsSubDomain(parent, child) {
+			t.Errorf("%s should not be child of %s", child, parent)
+			t.Errorf("comparelabels %d", CompareDomainName(parent, child))
+			t.Errorf("lenlabels %d %d", CountLabel(parent), CountLabel(child))
+		}
+	}
+}
+
+func TestPackNAPTR(t *testing.T) {
+	for _, n := range []string{
+		`apple.com. IN NAPTR   100 50 "se" "SIP+D2U" "" _sip._udp.apple.com.`,
+		`apple.com. IN NAPTR   90 50 "se" "SIP+D2T" "" _sip._tcp.apple.com.`,
+		`apple.com. IN NAPTR   50 50 "se" "SIPS+D2T" "" _sips._tcp.apple.com.`,
+	} {
+		rr := testRR(n)
+		msg := make([]byte, rr.len())
+		if off, err := PackRR(rr, msg, 0, nil, false); err != nil {
+			t.Errorf("packing failed: %v", err)
+			t.Errorf("length %d, need more than %d", rr.len(), off)
+		}
+	}
+}
+
+func TestToRFC3597(t *testing.T) {
+	a := testRR("miek.nl. IN A 10.0.1.1")
+	x := new(RFC3597)
+	x.ToRFC3597(a)
+	if x.String() != `miek.nl.	3600	CLASS1	TYPE1	\# 4 0a000101` {
+		t.Errorf("string mismatch, got: %s", x)
+	}
+
+	b := testRR("miek.nl. IN MX 10 mx.miek.nl.")
+	x.ToRFC3597(b)
+	if x.String() != `miek.nl.	3600	CLASS1	TYPE15	\# 14 000a026d78046d69656b026e6c00` {
+		t.Errorf("string mismatch, got: %s", x)
+	}
+}
+
+func TestNoRdataPack(t *testing.T) {
+	data := make([]byte, 1024)
+	for typ, fn := range TypeToRR {
+		r := fn()
+		*r.Header() = RR_Header{Name: "miek.nl.", Rrtype: typ, Class: ClassINET, Ttl: 16}
+		_, err := PackRR(r, data, 0, nil, false)
+		if err != nil {
+			t.Errorf("failed to pack RR with zero rdata: %s: %v", TypeToString[typ], err)
+		}
+	}
+}
+
+func TestNoRdataUnpack(t *testing.T) {
+	data := make([]byte, 1024)
+	for typ, fn := range TypeToRR {
+		if typ == TypeSOA || typ == TypeTSIG || typ == TypeTKEY {
+			// SOA, TSIG will not be seen (like this) in dyn. updates?
+			// TKEY requires length fields to be present for the Key and OtherData fields
+			continue
+		}
+		r := fn()
+		*r.Header() = RR_Header{Name: "miek.nl.", Rrtype: typ, Class: ClassINET, Ttl: 16}
+		off, err := PackRR(r, data, 0, nil, false)
+		if err != nil {
+			// Should always works, TestNoDataPack should have caught this
+			t.Errorf("failed to pack RR: %v", err)
+			continue
+		}
+		if _, _, err := UnpackRR(data[:off], 0); err != nil {
+			t.Errorf("failed to unpack RR with zero rdata: %s: %v", TypeToString[typ], err)
+		}
+	}
+}
+
+func TestRdataOverflow(t *testing.T) {
+	rr := new(RFC3597)
+	rr.Hdr.Name = "."
+	rr.Hdr.Class = ClassINET
+	rr.Hdr.Rrtype = 65280
+	rr.Rdata = hex.EncodeToString(make([]byte, 0xFFFF))
+	buf := make([]byte, 0xFFFF*2)
+	if _, err := PackRR(rr, buf, 0, nil, false); err != nil {
+		t.Fatalf("maximum size rrdata pack failed: %v", err)
+	}
+	rr.Rdata += "00"
+	if _, err := PackRR(rr, buf, 0, nil, false); err != ErrRdata {
+		t.Fatalf("oversize rrdata pack didn't return ErrRdata - instead: %v", err)
+	}
+}
+
+func TestCopy(t *testing.T) {
+	rr := testRR("miek.nl. 2311 IN A 127.0.0.1") // Weird TTL to avoid catching TTL
+	rr1 := Copy(rr)
+	if rr.String() != rr1.String() {
+		t.Fatalf("Copy() failed %s != %s", rr.String(), rr1.String())
+	}
+}
+
+func TestMsgCopy(t *testing.T) {
+	m := new(Msg)
+	m.SetQuestion("miek.nl.", TypeA)
+	rr := testRR("miek.nl. 2311 IN A 127.0.0.1")
+	m.Answer = []RR{rr}
+	rr = testRR("miek.nl. 2311 IN NS 127.0.0.1")
+	m.Ns = []RR{rr}
+
+	m1 := m.Copy()
+	if m.String() != m1.String() {
+		t.Fatalf("Msg.Copy() failed %s != %s", m.String(), m1.String())
+	}
+
+	m1.Answer[0] = testRR("somethingelse.nl. 2311 IN A 127.0.0.1")
+	if m.String() == m1.String() {
+		t.Fatalf("Msg.Copy() failed; change to copy changed template %s", m.String())
+	}
+
+	rr = testRR("miek.nl. 2311 IN A 127.0.0.2")
+	m1.Answer = append(m1.Answer, rr)
+	if m1.Ns[0].String() == m1.Answer[1].String() {
+		t.Fatalf("Msg.Copy() failed; append changed underlying array %s", m1.Ns[0].String())
+	}
+}
+
+func TestMsgPackBuffer(t *testing.T) {
+	var testMessages = []string{
+		// news.ycombinator.com.in.escapemg.com.	IN	A, response
+		"586285830001000000010000046e6577730b79636f6d62696e61746f7203636f6d02696e086573636170656d6703636f6d0000010001c0210006000100000e10002c036e7332c02103646e730b67726f6f7665736861726bc02d77ed50e600002a3000000e1000093a8000000e10",
+
+		// news.ycombinator.com.in.escapemg.com.	IN	A, question
+		"586201000001000000000000046e6577730b79636f6d62696e61746f7203636f6d02696e086573636170656d6703636f6d0000010001",
+
+		"398781020001000000000000046e6577730b79636f6d62696e61746f7203636f6d0000010001",
+	}
+
+	for i, hexData := range testMessages {
+		// we won't fail the decoding of the hex
+		input, _ := hex.DecodeString(hexData)
+		m := new(Msg)
+		if err := m.Unpack(input); err != nil {
+			t.Errorf("packet %d failed to unpack", i)
+			continue
+		}
+	}
+}
+
+// Make sure we can decode a TKEY packet from the string, modify the RR, and then pack it again.
+func TestTKEY(t *testing.T) {
+	// An example TKEY RR captured.  There is no known accepted standard text format for a TKEY
+	// record so we do this from a hex string instead of from a text readable string.
+	tkeyStr := "0737362d6d732d370932322d3332633233332463303439663961662d633065612d313165372d363839362d6463333937396666656666640000f900ff0000000000d2086773732d747369670059fd01f359fe53730003000000b8a181b53081b2a0030a0100a10b06092a864882f712010202a2819d04819a60819706092a864886f71201020202006f8187308184a003020105a10302010fa2783076a003020112a26f046db29b1b1d2625da3b20b49dafef930dd1e9aad335e1c5f45dcd95e0005d67a1100f3e573d70506659dbed064553f1ab890f68f65ae10def0dad5b423b39f240ebe666f2886c5fe03819692 [...]
+	tkeyBytes, err := hex.DecodeString(tkeyStr)
+	if err != nil {
+		t.Fatal("unable to decode TKEY string ", err)
+	}
+	// Decode the RR
+	rr, tkeyLen, unPackErr := UnpackRR(tkeyBytes, 0)
+	if unPackErr != nil {
+		t.Fatal("unable to decode TKEY RR", unPackErr)
+	}
+	// Make sure it's a TKEY record
+	if rr.Header().Rrtype != TypeTKEY {
+		t.Fatal("Unable to decode TKEY")
+	}
+	// Make sure we get back the same length
+	if rr.len() != len(tkeyBytes) {
+		t.Fatalf("Lengths don't match %d != %d", rr.len(), len(tkeyBytes))
+	}
+	// make space for it with some fudge room
+	msg := make([]byte, tkeyLen+1000)
+	offset, packErr := PackRR(rr, msg, 0, nil, false)
+	if packErr != nil {
+		t.Fatal("unable to pack TKEY RR", packErr)
+	}
+	if offset != len(tkeyBytes) {
+		t.Fatalf("mismatched TKEY RR size %d != %d", len(tkeyBytes), offset)
+	}
+	if bytes.Compare(tkeyBytes, msg[0:offset]) != 0 {
+		t.Fatal("mismatched TKEY data after rewriting bytes")
+	}
+	t.Logf("got TKEY of: " + rr.String())
+	// Now add some bytes to this and make sure we can encode OtherData properly
+	tkey := rr.(*TKEY)
+	tkey.OtherData = "abcd"
+	tkey.OtherLen = 2
+	offset, packErr = PackRR(tkey, msg, 0, nil, false)
+	if packErr != nil {
+		t.Fatal("unable to pack TKEY RR after modification", packErr)
+	}
+	if offset != (len(tkeyBytes) + 2) {
+		t.Fatalf("mismatched TKEY RR size %d != %d", offset, len(tkeyBytes)+2)
+	}
+	t.Logf("modified to TKEY of: " + rr.String())
+
+	// Make sure we can parse our string output
+	tkey.Hdr.Class = ClassINET // https://github.com/miekg/dns/issues/577
+	newRR, newError := NewRR(tkey.String())
+	if newError != nil {
+		t.Fatalf("unable to parse TKEY string: %s", newError)
+	}
+	t.Log("got reparsed TKEY of newRR: " + newRR.String())
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnssec.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnssec.go
new file mode 100644
index 0000000..ac9fdd4
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnssec.go
@@ -0,0 +1,784 @@
+package dns
+
+import (
+	"bytes"
+	"crypto"
+	"crypto/dsa"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	_ "crypto/md5"
+	"crypto/rand"
+	"crypto/rsa"
+	_ "crypto/sha1"
+	_ "crypto/sha256"
+	_ "crypto/sha512"
+	"encoding/asn1"
+	"encoding/binary"
+	"encoding/hex"
+	"math/big"
+	"sort"
+	"strings"
+	"time"
+
+	"golang.org/x/crypto/ed25519"
+)
+
+// DNSSEC encryption algorithm codes.
+const (
+	_ uint8 = iota
+	RSAMD5
+	DH
+	DSA
+	_ // Skip 4, RFC 6725, section 2.1
+	RSASHA1
+	DSANSEC3SHA1
+	RSASHA1NSEC3SHA1
+	RSASHA256
+	_ // Skip 9, RFC 6725, section 2.1
+	RSASHA512
+	_ // Skip 11, RFC 6725, section 2.1
+	ECCGOST
+	ECDSAP256SHA256
+	ECDSAP384SHA384
+	ED25519
+	ED448
+	INDIRECT   uint8 = 252
+	PRIVATEDNS uint8 = 253 // Private (experimental keys)
+	PRIVATEOID uint8 = 254
+)
+
+// AlgorithmToString is a map of algorithm IDs to algorithm names.
+var AlgorithmToString = map[uint8]string{
+	RSAMD5:           "RSAMD5",
+	DH:               "DH",
+	DSA:              "DSA",
+	RSASHA1:          "RSASHA1",
+	DSANSEC3SHA1:     "DSA-NSEC3-SHA1",
+	RSASHA1NSEC3SHA1: "RSASHA1-NSEC3-SHA1",
+	RSASHA256:        "RSASHA256",
+	RSASHA512:        "RSASHA512",
+	ECCGOST:          "ECC-GOST",
+	ECDSAP256SHA256:  "ECDSAP256SHA256",
+	ECDSAP384SHA384:  "ECDSAP384SHA384",
+	ED25519:          "ED25519",
+	ED448:            "ED448",
+	INDIRECT:         "INDIRECT",
+	PRIVATEDNS:       "PRIVATEDNS",
+	PRIVATEOID:       "PRIVATEOID",
+}
+
+// StringToAlgorithm is the reverse of AlgorithmToString.
+var StringToAlgorithm = reverseInt8(AlgorithmToString)
+
+// AlgorithmToHash is a map of algorithm crypto hash IDs to crypto.Hash's.
+var AlgorithmToHash = map[uint8]crypto.Hash{
+	RSAMD5:           crypto.MD5, // Deprecated in RFC 6725
+	RSASHA1:          crypto.SHA1,
+	RSASHA1NSEC3SHA1: crypto.SHA1,
+	RSASHA256:        crypto.SHA256,
+	ECDSAP256SHA256:  crypto.SHA256,
+	ECDSAP384SHA384:  crypto.SHA384,
+	RSASHA512:        crypto.SHA512,
+	ED25519:          crypto.Hash(0),
+}
+
+// DNSSEC hashing algorithm codes.
+const (
+	_      uint8 = iota
+	SHA1         // RFC 4034
+	SHA256       // RFC 4509
+	GOST94       // RFC 5933
+	SHA384       // Experimental
+	SHA512       // Experimental
+)
+
+// HashToString is a map of hash IDs to names.
+var HashToString = map[uint8]string{
+	SHA1:   "SHA1",
+	SHA256: "SHA256",
+	GOST94: "GOST94",
+	SHA384: "SHA384",
+	SHA512: "SHA512",
+}
+
+// StringToHash is a map of names to hash IDs.
+var StringToHash = reverseInt8(HashToString)
+
+// DNSKEY flag values.
+const (
+	SEP    = 1
+	REVOKE = 1 << 7
+	ZONE   = 1 << 8
+)
+
+// The RRSIG needs to be converted to wireformat with some of the rdata (the signature) missing.
+type rrsigWireFmt struct {
+	TypeCovered uint16
+	Algorithm   uint8
+	Labels      uint8
+	OrigTtl     uint32
+	Expiration  uint32
+	Inception   uint32
+	KeyTag      uint16
+	SignerName  string `dns:"domain-name"`
+	/* No Signature */
+}
+
+// Used for converting DNSKEY's rdata to wirefmt.
+type dnskeyWireFmt struct {
+	Flags     uint16
+	Protocol  uint8
+	Algorithm uint8
+	PublicKey string `dns:"base64"`
+	/* Nothing is left out */
+}
+
+func divRoundUp(a, b int) int {
+	return (a + b - 1) / b
+}
+
+// KeyTag calculates the keytag (or key-id) of the DNSKEY.
+func (k *DNSKEY) KeyTag() uint16 {
+	if k == nil {
+		return 0
+	}
+	var keytag int
+	switch k.Algorithm {
+	case RSAMD5:
+		// Look at the bottom two bytes of the modules, which the last
+		// item in the pubkey. We could do this faster by looking directly
+		// at the base64 values. But I'm lazy.
+		modulus, _ := fromBase64([]byte(k.PublicKey))
+		if len(modulus) > 1 {
+			x := binary.BigEndian.Uint16(modulus[len(modulus)-2:])
+			keytag = int(x)
+		}
+	default:
+		keywire := new(dnskeyWireFmt)
+		keywire.Flags = k.Flags
+		keywire.Protocol = k.Protocol
+		keywire.Algorithm = k.Algorithm
+		keywire.PublicKey = k.PublicKey
+		wire := make([]byte, DefaultMsgSize)
+		n, err := packKeyWire(keywire, wire)
+		if err != nil {
+			return 0
+		}
+		wire = wire[:n]
+		for i, v := range wire {
+			if i&1 != 0 {
+				keytag += int(v) // must be larger than uint32
+			} else {
+				keytag += int(v) << 8
+			}
+		}
+		keytag += (keytag >> 16) & 0xFFFF
+		keytag &= 0xFFFF
+	}
+	return uint16(keytag)
+}
+
+// ToDS converts a DNSKEY record to a DS record.
+func (k *DNSKEY) ToDS(h uint8) *DS {
+	if k == nil {
+		return nil
+	}
+	ds := new(DS)
+	ds.Hdr.Name = k.Hdr.Name
+	ds.Hdr.Class = k.Hdr.Class
+	ds.Hdr.Rrtype = TypeDS
+	ds.Hdr.Ttl = k.Hdr.Ttl
+	ds.Algorithm = k.Algorithm
+	ds.DigestType = h
+	ds.KeyTag = k.KeyTag()
+
+	keywire := new(dnskeyWireFmt)
+	keywire.Flags = k.Flags
+	keywire.Protocol = k.Protocol
+	keywire.Algorithm = k.Algorithm
+	keywire.PublicKey = k.PublicKey
+	wire := make([]byte, DefaultMsgSize)
+	n, err := packKeyWire(keywire, wire)
+	if err != nil {
+		return nil
+	}
+	wire = wire[:n]
+
+	owner := make([]byte, 255)
+	off, err1 := PackDomainName(strings.ToLower(k.Hdr.Name), owner, 0, nil, false)
+	if err1 != nil {
+		return nil
+	}
+	owner = owner[:off]
+	// RFC4034:
+	// digest = digest_algorithm( DNSKEY owner name | DNSKEY RDATA);
+	// "|" denotes concatenation
+	// DNSKEY RDATA = Flags | Protocol | Algorithm | Public Key.
+
+	var hash crypto.Hash
+	switch h {
+	case SHA1:
+		hash = crypto.SHA1
+	case SHA256:
+		hash = crypto.SHA256
+	case SHA384:
+		hash = crypto.SHA384
+	case SHA512:
+		hash = crypto.SHA512
+	default:
+		return nil
+	}
+
+	s := hash.New()
+	s.Write(owner)
+	s.Write(wire)
+	ds.Digest = hex.EncodeToString(s.Sum(nil))
+	return ds
+}
+
+// ToCDNSKEY converts a DNSKEY record to a CDNSKEY record.
+func (k *DNSKEY) ToCDNSKEY() *CDNSKEY {
+	c := &CDNSKEY{DNSKEY: *k}
+	c.Hdr = *k.Hdr.copyHeader()
+	c.Hdr.Rrtype = TypeCDNSKEY
+	return c
+}
+
+// ToCDS converts a DS record to a CDS record.
+func (d *DS) ToCDS() *CDS {
+	c := &CDS{DS: *d}
+	c.Hdr = *d.Hdr.copyHeader()
+	c.Hdr.Rrtype = TypeCDS
+	return c
+}
+
+// Sign signs an RRSet. The signature needs to be filled in with the values:
+// Inception, Expiration, KeyTag, SignerName and Algorithm.  The rest is copied
+// from the RRset. Sign returns a non-nill error when the signing went OK.
+// There is no check if RRSet is a proper (RFC 2181) RRSet.  If OrigTTL is non
+// zero, it is used as-is, otherwise the TTL of the RRset is used as the
+// OrigTTL.
+func (rr *RRSIG) Sign(k crypto.Signer, rrset []RR) error {
+	if k == nil {
+		return ErrPrivKey
+	}
+	// s.Inception and s.Expiration may be 0 (rollover etc.), the rest must be set
+	if rr.KeyTag == 0 || len(rr.SignerName) == 0 || rr.Algorithm == 0 {
+		return ErrKey
+	}
+
+	rr.Hdr.Rrtype = TypeRRSIG
+	rr.Hdr.Name = rrset[0].Header().Name
+	rr.Hdr.Class = rrset[0].Header().Class
+	if rr.OrigTtl == 0 { // If set don't override
+		rr.OrigTtl = rrset[0].Header().Ttl
+	}
+	rr.TypeCovered = rrset[0].Header().Rrtype
+	rr.Labels = uint8(CountLabel(rrset[0].Header().Name))
+
+	if strings.HasPrefix(rrset[0].Header().Name, "*") {
+		rr.Labels-- // wildcard, remove from label count
+	}
+
+	sigwire := new(rrsigWireFmt)
+	sigwire.TypeCovered = rr.TypeCovered
+	sigwire.Algorithm = rr.Algorithm
+	sigwire.Labels = rr.Labels
+	sigwire.OrigTtl = rr.OrigTtl
+	sigwire.Expiration = rr.Expiration
+	sigwire.Inception = rr.Inception
+	sigwire.KeyTag = rr.KeyTag
+	// For signing, lowercase this name
+	sigwire.SignerName = strings.ToLower(rr.SignerName)
+
+	// Create the desired binary blob
+	signdata := make([]byte, DefaultMsgSize)
+	n, err := packSigWire(sigwire, signdata)
+	if err != nil {
+		return err
+	}
+	signdata = signdata[:n]
+	wire, err := rawSignatureData(rrset, rr)
+	if err != nil {
+		return err
+	}
+
+	hash, ok := AlgorithmToHash[rr.Algorithm]
+	if !ok {
+		return ErrAlg
+	}
+
+	switch rr.Algorithm {
+	case ED25519:
+		// ed25519 signs the raw message and performs hashing internally.
+		// All other supported signature schemes operate over the pre-hashed
+		// message, and thus ed25519 must be handled separately here.
+		//
+		// The raw message is passed directly into sign and crypto.Hash(0) is
+		// used to signal to the crypto.Signer that the data has not been hashed.
+		signature, err := sign(k, append(signdata, wire...), crypto.Hash(0), rr.Algorithm)
+		if err != nil {
+			return err
+		}
+
+		rr.Signature = toBase64(signature)
+	default:
+		h := hash.New()
+		h.Write(signdata)
+		h.Write(wire)
+
+		signature, err := sign(k, h.Sum(nil), hash, rr.Algorithm)
+		if err != nil {
+			return err
+		}
+
+		rr.Signature = toBase64(signature)
+	}
+
+	return nil
+}
+
+func sign(k crypto.Signer, hashed []byte, hash crypto.Hash, alg uint8) ([]byte, error) {
+	signature, err := k.Sign(rand.Reader, hashed, hash)
+	if err != nil {
+		return nil, err
+	}
+
+	switch alg {
+	case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512:
+		return signature, nil
+
+	case ECDSAP256SHA256, ECDSAP384SHA384:
+		ecdsaSignature := &struct {
+			R, S *big.Int
+		}{}
+		if _, err := asn1.Unmarshal(signature, ecdsaSignature); err != nil {
+			return nil, err
+		}
+
+		var intlen int
+		switch alg {
+		case ECDSAP256SHA256:
+			intlen = 32
+		case ECDSAP384SHA384:
+			intlen = 48
+		}
+
+		signature := intToBytes(ecdsaSignature.R, intlen)
+		signature = append(signature, intToBytes(ecdsaSignature.S, intlen)...)
+		return signature, nil
+
+	// There is no defined interface for what a DSA backed crypto.Signer returns
+	case DSA, DSANSEC3SHA1:
+		// 	t := divRoundUp(divRoundUp(p.PublicKey.Y.BitLen(), 8)-64, 8)
+		// 	signature := []byte{byte(t)}
+		// 	signature = append(signature, intToBytes(r1, 20)...)
+		// 	signature = append(signature, intToBytes(s1, 20)...)
+		// 	rr.Signature = signature
+
+	case ED25519:
+		return signature, nil
+	}
+
+	return nil, ErrAlg
+}
+
+// Verify validates an RRSet with the signature and key. This is only the
+// cryptographic test, the signature validity period must be checked separately.
+// This function copies the rdata of some RRs (to lowercase domain names) for the validation to work.
+func (rr *RRSIG) Verify(k *DNSKEY, rrset []RR) error {
+	// First the easy checks
+	if !IsRRset(rrset) {
+		return ErrRRset
+	}
+	if rr.KeyTag != k.KeyTag() {
+		return ErrKey
+	}
+	if rr.Hdr.Class != k.Hdr.Class {
+		return ErrKey
+	}
+	if rr.Algorithm != k.Algorithm {
+		return ErrKey
+	}
+	if strings.ToLower(rr.SignerName) != strings.ToLower(k.Hdr.Name) {
+		return ErrKey
+	}
+	if k.Protocol != 3 {
+		return ErrKey
+	}
+
+	// IsRRset checked that we have at least one RR and that the RRs in
+	// the set have consistent type, class, and name. Also check that type and
+	// class matches the RRSIG record.
+	if rrset[0].Header().Class != rr.Hdr.Class {
+		return ErrRRset
+	}
+	if rrset[0].Header().Rrtype != rr.TypeCovered {
+		return ErrRRset
+	}
+
+	// RFC 4035 5.3.2.  Reconstructing the Signed Data
+	// Copy the sig, except the rrsig data
+	sigwire := new(rrsigWireFmt)
+	sigwire.TypeCovered = rr.TypeCovered
+	sigwire.Algorithm = rr.Algorithm
+	sigwire.Labels = rr.Labels
+	sigwire.OrigTtl = rr.OrigTtl
+	sigwire.Expiration = rr.Expiration
+	sigwire.Inception = rr.Inception
+	sigwire.KeyTag = rr.KeyTag
+	sigwire.SignerName = strings.ToLower(rr.SignerName)
+	// Create the desired binary blob
+	signeddata := make([]byte, DefaultMsgSize)
+	n, err := packSigWire(sigwire, signeddata)
+	if err != nil {
+		return err
+	}
+	signeddata = signeddata[:n]
+	wire, err := rawSignatureData(rrset, rr)
+	if err != nil {
+		return err
+	}
+
+	sigbuf := rr.sigBuf()           // Get the binary signature data
+	if rr.Algorithm == PRIVATEDNS { // PRIVATEOID
+		// TODO(miek)
+		// remove the domain name and assume its ours?
+	}
+
+	hash, ok := AlgorithmToHash[rr.Algorithm]
+	if !ok {
+		return ErrAlg
+	}
+
+	switch rr.Algorithm {
+	case RSASHA1, RSASHA1NSEC3SHA1, RSASHA256, RSASHA512, RSAMD5:
+		// TODO(mg): this can be done quicker, ie. cache the pubkey data somewhere??
+		pubkey := k.publicKeyRSA() // Get the key
+		if pubkey == nil {
+			return ErrKey
+		}
+
+		h := hash.New()
+		h.Write(signeddata)
+		h.Write(wire)
+		return rsa.VerifyPKCS1v15(pubkey, hash, h.Sum(nil), sigbuf)
+
+	case ECDSAP256SHA256, ECDSAP384SHA384:
+		pubkey := k.publicKeyECDSA()
+		if pubkey == nil {
+			return ErrKey
+		}
+
+		// Split sigbuf into the r and s coordinates
+		r := new(big.Int).SetBytes(sigbuf[:len(sigbuf)/2])
+		s := new(big.Int).SetBytes(sigbuf[len(sigbuf)/2:])
+
+		h := hash.New()
+		h.Write(signeddata)
+		h.Write(wire)
+		if ecdsa.Verify(pubkey, h.Sum(nil), r, s) {
+			return nil
+		}
+		return ErrSig
+
+	case ED25519:
+		pubkey := k.publicKeyED25519()
+		if pubkey == nil {
+			return ErrKey
+		}
+
+		if ed25519.Verify(pubkey, append(signeddata, wire...), sigbuf) {
+			return nil
+		}
+		return ErrSig
+
+	default:
+		return ErrAlg
+	}
+}
+
+// ValidityPeriod uses RFC1982 serial arithmetic to calculate
+// if a signature period is valid. If t is the zero time, the
+// current time is taken other t is. Returns true if the signature
+// is valid at the given time, otherwise returns false.
+func (rr *RRSIG) ValidityPeriod(t time.Time) bool {
+	var utc int64
+	if t.IsZero() {
+		utc = time.Now().UTC().Unix()
+	} else {
+		utc = t.UTC().Unix()
+	}
+	modi := (int64(rr.Inception) - utc) / year68
+	mode := (int64(rr.Expiration) - utc) / year68
+	ti := int64(rr.Inception) + (modi * year68)
+	te := int64(rr.Expiration) + (mode * year68)
+	return ti <= utc && utc <= te
+}
+
+// Return the signatures base64 encodedig sigdata as a byte slice.
+func (rr *RRSIG) sigBuf() []byte {
+	sigbuf, err := fromBase64([]byte(rr.Signature))
+	if err != nil {
+		return nil
+	}
+	return sigbuf
+}
+
+// publicKeyRSA returns the RSA public key from a DNSKEY record.
+func (k *DNSKEY) publicKeyRSA() *rsa.PublicKey {
+	keybuf, err := fromBase64([]byte(k.PublicKey))
+	if err != nil {
+		return nil
+	}
+
+	// RFC 2537/3110, section 2. RSA Public KEY Resource Records
+	// Length is in the 0th byte, unless its zero, then it
+	// it in bytes 1 and 2 and its a 16 bit number
+	explen := uint16(keybuf[0])
+	keyoff := 1
+	if explen == 0 {
+		explen = uint16(keybuf[1])<<8 | uint16(keybuf[2])
+		keyoff = 3
+	}
+	pubkey := new(rsa.PublicKey)
+
+	pubkey.N = big.NewInt(0)
+	shift := uint64((explen - 1) * 8)
+	expo := uint64(0)
+	for i := int(explen - 1); i > 0; i-- {
+		expo += uint64(keybuf[keyoff+i]) << shift
+		shift -= 8
+	}
+	// Remainder
+	expo += uint64(keybuf[keyoff])
+	if expo > (2<<31)+1 {
+		// Larger expo than supported.
+		// println("dns: F5 primes (or larger) are not supported")
+		return nil
+	}
+	pubkey.E = int(expo)
+
+	pubkey.N.SetBytes(keybuf[keyoff+int(explen):])
+	return pubkey
+}
+
+// publicKeyECDSA returns the Curve public key from the DNSKEY record.
+func (k *DNSKEY) publicKeyECDSA() *ecdsa.PublicKey {
+	keybuf, err := fromBase64([]byte(k.PublicKey))
+	if err != nil {
+		return nil
+	}
+	pubkey := new(ecdsa.PublicKey)
+	switch k.Algorithm {
+	case ECDSAP256SHA256:
+		pubkey.Curve = elliptic.P256()
+		if len(keybuf) != 64 {
+			// wrongly encoded key
+			return nil
+		}
+	case ECDSAP384SHA384:
+		pubkey.Curve = elliptic.P384()
+		if len(keybuf) != 96 {
+			// Wrongly encoded key
+			return nil
+		}
+	}
+	pubkey.X = big.NewInt(0)
+	pubkey.X.SetBytes(keybuf[:len(keybuf)/2])
+	pubkey.Y = big.NewInt(0)
+	pubkey.Y.SetBytes(keybuf[len(keybuf)/2:])
+	return pubkey
+}
+
+func (k *DNSKEY) publicKeyDSA() *dsa.PublicKey {
+	keybuf, err := fromBase64([]byte(k.PublicKey))
+	if err != nil {
+		return nil
+	}
+	if len(keybuf) < 22 {
+		return nil
+	}
+	t, keybuf := int(keybuf[0]), keybuf[1:]
+	size := 64 + t*8
+	q, keybuf := keybuf[:20], keybuf[20:]
+	if len(keybuf) != 3*size {
+		return nil
+	}
+	p, keybuf := keybuf[:size], keybuf[size:]
+	g, y := keybuf[:size], keybuf[size:]
+	pubkey := new(dsa.PublicKey)
+	pubkey.Parameters.Q = big.NewInt(0).SetBytes(q)
+	pubkey.Parameters.P = big.NewInt(0).SetBytes(p)
+	pubkey.Parameters.G = big.NewInt(0).SetBytes(g)
+	pubkey.Y = big.NewInt(0).SetBytes(y)
+	return pubkey
+}
+
+func (k *DNSKEY) publicKeyED25519() ed25519.PublicKey {
+	keybuf, err := fromBase64([]byte(k.PublicKey))
+	if err != nil {
+		return nil
+	}
+	if len(keybuf) != ed25519.PublicKeySize {
+		return nil
+	}
+	return keybuf
+}
+
+type wireSlice [][]byte
+
+func (p wireSlice) Len() int      { return len(p) }
+func (p wireSlice) Swap(i, j int) { p[i], p[j] = p[j], p[i] }
+func (p wireSlice) Less(i, j int) bool {
+	_, ioff, _ := UnpackDomainName(p[i], 0)
+	_, joff, _ := UnpackDomainName(p[j], 0)
+	return bytes.Compare(p[i][ioff+10:], p[j][joff+10:]) < 0
+}
+
+// Return the raw signature data.
+func rawSignatureData(rrset []RR, s *RRSIG) (buf []byte, err error) {
+	wires := make(wireSlice, len(rrset))
+	for i, r := range rrset {
+		r1 := r.copy()
+		r1.Header().Ttl = s.OrigTtl
+		labels := SplitDomainName(r1.Header().Name)
+		// 6.2. Canonical RR Form. (4) - wildcards
+		if len(labels) > int(s.Labels) {
+			// Wildcard
+			r1.Header().Name = "*." + strings.Join(labels[len(labels)-int(s.Labels):], ".") + "."
+		}
+		// RFC 4034: 6.2.  Canonical RR Form. (2) - domain name to lowercase
+		r1.Header().Name = strings.ToLower(r1.Header().Name)
+		// 6.2. Canonical RR Form. (3) - domain rdata to lowercase.
+		//   NS, MD, MF, CNAME, SOA, MB, MG, MR, PTR,
+		//   HINFO, MINFO, MX, RP, AFSDB, RT, SIG, PX, NXT, NAPTR, KX,
+		//   SRV, DNAME, A6
+		//
+		// RFC 6840 - Clarifications and Implementation Notes for DNS Security (DNSSEC):
+		//	Section 6.2 of [RFC4034] also erroneously lists HINFO as a record
+		//	that needs conversion to lowercase, and twice at that.  Since HINFO
+		//	records contain no domain names, they are not subject to case
+		//	conversion.
+		switch x := r1.(type) {
+		case *NS:
+			x.Ns = strings.ToLower(x.Ns)
+		case *MD:
+			x.Md = strings.ToLower(x.Md)
+		case *MF:
+			x.Mf = strings.ToLower(x.Mf)
+		case *CNAME:
+			x.Target = strings.ToLower(x.Target)
+		case *SOA:
+			x.Ns = strings.ToLower(x.Ns)
+			x.Mbox = strings.ToLower(x.Mbox)
+		case *MB:
+			x.Mb = strings.ToLower(x.Mb)
+		case *MG:
+			x.Mg = strings.ToLower(x.Mg)
+		case *MR:
+			x.Mr = strings.ToLower(x.Mr)
+		case *PTR:
+			x.Ptr = strings.ToLower(x.Ptr)
+		case *MINFO:
+			x.Rmail = strings.ToLower(x.Rmail)
+			x.Email = strings.ToLower(x.Email)
+		case *MX:
+			x.Mx = strings.ToLower(x.Mx)
+		case *RP:
+			x.Mbox = strings.ToLower(x.Mbox)
+			x.Txt = strings.ToLower(x.Txt)
+		case *AFSDB:
+			x.Hostname = strings.ToLower(x.Hostname)
+		case *RT:
+			x.Host = strings.ToLower(x.Host)
+		case *SIG:
+			x.SignerName = strings.ToLower(x.SignerName)
+		case *PX:
+			x.Map822 = strings.ToLower(x.Map822)
+			x.Mapx400 = strings.ToLower(x.Mapx400)
+		case *NAPTR:
+			x.Replacement = strings.ToLower(x.Replacement)
+		case *KX:
+			x.Exchanger = strings.ToLower(x.Exchanger)
+		case *SRV:
+			x.Target = strings.ToLower(x.Target)
+		case *DNAME:
+			x.Target = strings.ToLower(x.Target)
+		}
+		// 6.2. Canonical RR Form. (5) - origTTL
+		wire := make([]byte, r1.len()+1) // +1 to be safe(r)
+		off, err1 := PackRR(r1, wire, 0, nil, false)
+		if err1 != nil {
+			return nil, err1
+		}
+		wire = wire[:off]
+		wires[i] = wire
+	}
+	sort.Sort(wires)
+	for i, wire := range wires {
+		if i > 0 && bytes.Equal(wire, wires[i-1]) {
+			continue
+		}
+		buf = append(buf, wire...)
+	}
+	return buf, nil
+}
+
+func packSigWire(sw *rrsigWireFmt, msg []byte) (int, error) {
+	// copied from zmsg.go RRSIG packing
+	off, err := packUint16(sw.TypeCovered, msg, 0)
+	if err != nil {
+		return off, err
+	}
+	off, err = packUint8(sw.Algorithm, msg, off)
+	if err != nil {
+		return off, err
+	}
+	off, err = packUint8(sw.Labels, msg, off)
+	if err != nil {
+		return off, err
+	}
+	off, err = packUint32(sw.OrigTtl, msg, off)
+	if err != nil {
+		return off, err
+	}
+	off, err = packUint32(sw.Expiration, msg, off)
+	if err != nil {
+		return off, err
+	}
+	off, err = packUint32(sw.Inception, msg, off)
+	if err != nil {
+		return off, err
+	}
+	off, err = packUint16(sw.KeyTag, msg, off)
+	if err != nil {
+		return off, err
+	}
+	off, err = PackDomainName(sw.SignerName, msg, off, nil, false)
+	if err != nil {
+		return off, err
+	}
+	return off, nil
+}
+
+func packKeyWire(dw *dnskeyWireFmt, msg []byte) (int, error) {
+	// copied from zmsg.go DNSKEY packing
+	off, err := packUint16(dw.Flags, msg, 0)
+	if err != nil {
+		return off, err
+	}
+	off, err = packUint8(dw.Protocol, msg, off)
+	if err != nil {
+		return off, err
+	}
+	off, err = packUint8(dw.Algorithm, msg, off)
+	if err != nil {
+		return off, err
+	}
+	off, err = packStringBase64(dw.PublicKey, msg, off)
+	if err != nil {
+		return off, err
+	}
+	return off, nil
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnssec_keygen.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnssec_keygen.go
new file mode 100644
index 0000000..33e913a
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnssec_keygen.go
@@ -0,0 +1,178 @@
+package dns
+
+import (
+	"crypto"
+	"crypto/dsa"
+	"crypto/ecdsa"
+	"crypto/elliptic"
+	"crypto/rand"
+	"crypto/rsa"
+	"math/big"
+
+	"golang.org/x/crypto/ed25519"
+)
+
+// Generate generates a DNSKEY of the given bit size.
+// The public part is put inside the DNSKEY record.
+// The Algorithm in the key must be set as this will define
+// what kind of DNSKEY will be generated.
+// The ECDSA algorithms imply a fixed keysize, in that case
+// bits should be set to the size of the algorithm.
+func (k *DNSKEY) Generate(bits int) (crypto.PrivateKey, error) {
+	switch k.Algorithm {
+	case DSA, DSANSEC3SHA1:
+		if bits != 1024 {
+			return nil, ErrKeySize
+		}
+	case RSAMD5, RSASHA1, RSASHA256, RSASHA1NSEC3SHA1:
+		if bits < 512 || bits > 4096 {
+			return nil, ErrKeySize
+		}
+	case RSASHA512:
+		if bits < 1024 || bits > 4096 {
+			return nil, ErrKeySize
+		}
+	case ECDSAP256SHA256:
+		if bits != 256 {
+			return nil, ErrKeySize
+		}
+	case ECDSAP384SHA384:
+		if bits != 384 {
+			return nil, ErrKeySize
+		}
+	case ED25519:
+		if bits != 256 {
+			return nil, ErrKeySize
+		}
+	}
+
+	switch k.Algorithm {
+	case DSA, DSANSEC3SHA1:
+		params := new(dsa.Parameters)
+		if err := dsa.GenerateParameters(params, rand.Reader, dsa.L1024N160); err != nil {
+			return nil, err
+		}
+		priv := new(dsa.PrivateKey)
+		priv.PublicKey.Parameters = *params
+		err := dsa.GenerateKey(priv, rand.Reader)
+		if err != nil {
+			return nil, err
+		}
+		k.setPublicKeyDSA(params.Q, params.P, params.G, priv.PublicKey.Y)
+		return priv, nil
+	case RSAMD5, RSASHA1, RSASHA256, RSASHA512, RSASHA1NSEC3SHA1:
+		priv, err := rsa.GenerateKey(rand.Reader, bits)
+		if err != nil {
+			return nil, err
+		}
+		k.setPublicKeyRSA(priv.PublicKey.E, priv.PublicKey.N)
+		return priv, nil
+	case ECDSAP256SHA256, ECDSAP384SHA384:
+		var c elliptic.Curve
+		switch k.Algorithm {
+		case ECDSAP256SHA256:
+			c = elliptic.P256()
+		case ECDSAP384SHA384:
+			c = elliptic.P384()
+		}
+		priv, err := ecdsa.GenerateKey(c, rand.Reader)
+		if err != nil {
+			return nil, err
+		}
+		k.setPublicKeyECDSA(priv.PublicKey.X, priv.PublicKey.Y)
+		return priv, nil
+	case ED25519:
+		pub, priv, err := ed25519.GenerateKey(rand.Reader)
+		if err != nil {
+			return nil, err
+		}
+		k.setPublicKeyED25519(pub)
+		return priv, nil
+	default:
+		return nil, ErrAlg
+	}
+}
+
+// Set the public key (the value E and N)
+func (k *DNSKEY) setPublicKeyRSA(_E int, _N *big.Int) bool {
+	if _E == 0 || _N == nil {
+		return false
+	}
+	buf := exponentToBuf(_E)
+	buf = append(buf, _N.Bytes()...)
+	k.PublicKey = toBase64(buf)
+	return true
+}
+
+// Set the public key for Elliptic Curves
+func (k *DNSKEY) setPublicKeyECDSA(_X, _Y *big.Int) bool {
+	if _X == nil || _Y == nil {
+		return false
+	}
+	var intlen int
+	switch k.Algorithm {
+	case ECDSAP256SHA256:
+		intlen = 32
+	case ECDSAP384SHA384:
+		intlen = 48
+	}
+	k.PublicKey = toBase64(curveToBuf(_X, _Y, intlen))
+	return true
+}
+
+// Set the public key for DSA
+func (k *DNSKEY) setPublicKeyDSA(_Q, _P, _G, _Y *big.Int) bool {
+	if _Q == nil || _P == nil || _G == nil || _Y == nil {
+		return false
+	}
+	buf := dsaToBuf(_Q, _P, _G, _Y)
+	k.PublicKey = toBase64(buf)
+	return true
+}
+
+// Set the public key for Ed25519
+func (k *DNSKEY) setPublicKeyED25519(_K ed25519.PublicKey) bool {
+	if _K == nil {
+		return false
+	}
+	k.PublicKey = toBase64(_K)
+	return true
+}
+
+// Set the public key (the values E and N) for RSA
+// RFC 3110: Section 2. RSA Public KEY Resource Records
+func exponentToBuf(_E int) []byte {
+	var buf []byte
+	i := big.NewInt(int64(_E)).Bytes()
+	if len(i) < 256 {
+		buf = make([]byte, 1, 1+len(i))
+		buf[0] = uint8(len(i))
+	} else {
+		buf = make([]byte, 3, 3+len(i))
+		buf[0] = 0
+		buf[1] = uint8(len(i) >> 8)
+		buf[2] = uint8(len(i))
+	}
+	buf = append(buf, i...)
+	return buf
+}
+
+// Set the public key for X and Y for Curve. The two
+// values are just concatenated.
+func curveToBuf(_X, _Y *big.Int, intlen int) []byte {
+	buf := intToBytes(_X, intlen)
+	buf = append(buf, intToBytes(_Y, intlen)...)
+	return buf
+}
+
+// Set the public key for X and Y for Curve. The two
+// values are just concatenated.
+func dsaToBuf(_Q, _P, _G, _Y *big.Int) []byte {
+	t := divRoundUp(divRoundUp(_G.BitLen(), 8)-64, 8)
+	buf := []byte{byte(t)}
+	buf = append(buf, intToBytes(_Q, 20)...)
+	buf = append(buf, intToBytes(_P, 64+t*8)...)
+	buf = append(buf, intToBytes(_G, 64+t*8)...)
+	buf = append(buf, intToBytes(_Y, 64+t*8)...)
+	return buf
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnssec_keyscan.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnssec_keyscan.go
new file mode 100644
index 0000000..e2d9d8f
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnssec_keyscan.go
@@ -0,0 +1,297 @@
+package dns
+
+import (
+	"bytes"
+	"crypto"
+	"crypto/dsa"
+	"crypto/ecdsa"
+	"crypto/rsa"
+	"io"
+	"math/big"
+	"strconv"
+	"strings"
+
+	"golang.org/x/crypto/ed25519"
+)
+
+// NewPrivateKey returns a PrivateKey by parsing the string s.
+// s should be in the same form of the BIND private key files.
+func (k *DNSKEY) NewPrivateKey(s string) (crypto.PrivateKey, error) {
+	if s == "" || s[len(s)-1] != '\n' { // We need a closing newline
+		return k.ReadPrivateKey(strings.NewReader(s+"\n"), "")
+	}
+	return k.ReadPrivateKey(strings.NewReader(s), "")
+}
+
+// ReadPrivateKey reads a private key from the io.Reader q. The string file is
+// only used in error reporting.
+// The public key must be known, because some cryptographic algorithms embed
+// the public inside the privatekey.
+func (k *DNSKEY) ReadPrivateKey(q io.Reader, file string) (crypto.PrivateKey, error) {
+	m, err := parseKey(q, file)
+	if m == nil {
+		return nil, err
+	}
+	if _, ok := m["private-key-format"]; !ok {
+		return nil, ErrPrivKey
+	}
+	if m["private-key-format"] != "v1.2" && m["private-key-format"] != "v1.3" {
+		return nil, ErrPrivKey
+	}
+	// TODO(mg): check if the pubkey matches the private key
+	algo, err := strconv.ParseUint(strings.SplitN(m["algorithm"], " ", 2)[0], 10, 8)
+	if err != nil {
+		return nil, ErrPrivKey
+	}
+	switch uint8(algo) {
+	case DSA:
+		priv, err := readPrivateKeyDSA(m)
+		if err != nil {
+			return nil, err
+		}
+		pub := k.publicKeyDSA()
+		if pub == nil {
+			return nil, ErrKey
+		}
+		priv.PublicKey = *pub
+		return priv, nil
+	case RSAMD5:
+		fallthrough
+	case RSASHA1:
+		fallthrough
+	case RSASHA1NSEC3SHA1:
+		fallthrough
+	case RSASHA256:
+		fallthrough
+	case RSASHA512:
+		priv, err := readPrivateKeyRSA(m)
+		if err != nil {
+			return nil, err
+		}
+		pub := k.publicKeyRSA()
+		if pub == nil {
+			return nil, ErrKey
+		}
+		priv.PublicKey = *pub
+		return priv, nil
+	case ECCGOST:
+		return nil, ErrPrivKey
+	case ECDSAP256SHA256:
+		fallthrough
+	case ECDSAP384SHA384:
+		priv, err := readPrivateKeyECDSA(m)
+		if err != nil {
+			return nil, err
+		}
+		pub := k.publicKeyECDSA()
+		if pub == nil {
+			return nil, ErrKey
+		}
+		priv.PublicKey = *pub
+		return priv, nil
+	case ED25519:
+		return readPrivateKeyED25519(m)
+	default:
+		return nil, ErrPrivKey
+	}
+}
+
+// Read a private key (file) string and create a public key. Return the private key.
+func readPrivateKeyRSA(m map[string]string) (*rsa.PrivateKey, error) {
+	p := new(rsa.PrivateKey)
+	p.Primes = []*big.Int{nil, nil}
+	for k, v := range m {
+		switch k {
+		case "modulus", "publicexponent", "privateexponent", "prime1", "prime2":
+			v1, err := fromBase64([]byte(v))
+			if err != nil {
+				return nil, err
+			}
+			switch k {
+			case "modulus":
+				p.PublicKey.N = big.NewInt(0)
+				p.PublicKey.N.SetBytes(v1)
+			case "publicexponent":
+				i := big.NewInt(0)
+				i.SetBytes(v1)
+				p.PublicKey.E = int(i.Int64()) // int64 should be large enough
+			case "privateexponent":
+				p.D = big.NewInt(0)
+				p.D.SetBytes(v1)
+			case "prime1":
+				p.Primes[0] = big.NewInt(0)
+				p.Primes[0].SetBytes(v1)
+			case "prime2":
+				p.Primes[1] = big.NewInt(0)
+				p.Primes[1].SetBytes(v1)
+			}
+		case "exponent1", "exponent2", "coefficient":
+			// not used in Go (yet)
+		case "created", "publish", "activate":
+			// not used in Go (yet)
+		}
+	}
+	return p, nil
+}
+
+func readPrivateKeyDSA(m map[string]string) (*dsa.PrivateKey, error) {
+	p := new(dsa.PrivateKey)
+	p.X = big.NewInt(0)
+	for k, v := range m {
+		switch k {
+		case "private_value(x)":
+			v1, err := fromBase64([]byte(v))
+			if err != nil {
+				return nil, err
+			}
+			p.X.SetBytes(v1)
+		case "created", "publish", "activate":
+			/* not used in Go (yet) */
+		}
+	}
+	return p, nil
+}
+
+func readPrivateKeyECDSA(m map[string]string) (*ecdsa.PrivateKey, error) {
+	p := new(ecdsa.PrivateKey)
+	p.D = big.NewInt(0)
+	// TODO: validate that the required flags are present
+	for k, v := range m {
+		switch k {
+		case "privatekey":
+			v1, err := fromBase64([]byte(v))
+			if err != nil {
+				return nil, err
+			}
+			p.D.SetBytes(v1)
+		case "created", "publish", "activate":
+			/* not used in Go (yet) */
+		}
+	}
+	return p, nil
+}
+
+func readPrivateKeyED25519(m map[string]string) (ed25519.PrivateKey, error) {
+	var p ed25519.PrivateKey
+	// TODO: validate that the required flags are present
+	for k, v := range m {
+		switch k {
+		case "privatekey":
+			p1, err := fromBase64([]byte(v))
+			if err != nil {
+				return nil, err
+			}
+			if len(p1) != 32 {
+				return nil, ErrPrivKey
+			}
+			// RFC 8080 and Golang's x/crypto/ed25519 differ as to how the
+			// private keys are represented. RFC 8080 specifies that private
+			// keys be stored solely as the seed value (p1 above) while the
+			// ed25519 package represents them as the seed value concatenated
+			// to the public key, which is derived from the seed value.
+			//
+			// ed25519.GenerateKey reads exactly 32 bytes from the passed in
+			// io.Reader and uses them as the seed. It also derives the
+			// public key and produces a compatible private key.
+			_, p, err = ed25519.GenerateKey(bytes.NewReader(p1))
+			if err != nil {
+				return nil, err
+			}
+		case "created", "publish", "activate":
+			/* not used in Go (yet) */
+		}
+	}
+	return p, nil
+}
+
+// parseKey reads a private key from r. It returns a map[string]string,
+// with the key-value pairs, or an error when the file is not correct.
+func parseKey(r io.Reader, file string) (map[string]string, error) {
+	s, cancel := scanInit(r)
+	m := make(map[string]string)
+	c := make(chan lex)
+	k := ""
+	defer func() {
+		cancel()
+		// zlexer can send up to two tokens, the next one and possibly 1 remainders.
+		// Do a non-blocking read.
+		_, ok := <-c
+		_, ok = <-c
+		if !ok {
+			// too bad
+		}
+	}()
+	// Start the lexer
+	go klexer(s, c)
+	for l := range c {
+		// It should alternate
+		switch l.value {
+		case zKey:
+			k = l.token
+		case zValue:
+			if k == "" {
+				return nil, &ParseError{file, "no private key seen", l}
+			}
+			//println("Setting", strings.ToLower(k), "to", l.token, "b")
+			m[strings.ToLower(k)] = l.token
+			k = ""
+		}
+	}
+	return m, nil
+}
+
+// klexer scans the sourcefile and returns tokens on the channel c.
+func klexer(s *scan, c chan lex) {
+	var l lex
+	str := "" // Hold the current read text
+	commt := false
+	key := true
+	x, err := s.tokenText()
+	defer close(c)
+	for err == nil {
+		l.column = s.position.Column
+		l.line = s.position.Line
+		switch x {
+		case ':':
+			if commt {
+				break
+			}
+			l.token = str
+			if key {
+				l.value = zKey
+				c <- l
+				// Next token is a space, eat it
+				s.tokenText()
+				key = false
+				str = ""
+			} else {
+				l.value = zValue
+			}
+		case ';':
+			commt = true
+		case '\n':
+			if commt {
+				// Reset a comment
+				commt = false
+			}
+			l.value = zValue
+			l.token = str
+			c <- l
+			str = ""
+			commt = false
+			key = true
+		default:
+			if commt {
+				break
+			}
+			str += string(x)
+		}
+		x, err = s.tokenText()
+	}
+	if len(str) > 0 {
+		// Send remainder
+		l.token = str
+		l.value = zValue
+		c <- l
+	}
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnssec_privkey.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnssec_privkey.go
new file mode 100644
index 0000000..46f3215
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnssec_privkey.go
@@ -0,0 +1,93 @@
+package dns
+
+import (
+	"crypto"
+	"crypto/dsa"
+	"crypto/ecdsa"
+	"crypto/rsa"
+	"math/big"
+	"strconv"
+
+	"golang.org/x/crypto/ed25519"
+)
+
+const format = "Private-key-format: v1.3\n"
+
+// PrivateKeyString converts a PrivateKey to a string. This string has the same
+// format as the private-key-file of BIND9 (Private-key-format: v1.3).
+// It needs some info from the key (the algorithm), so its a method of the DNSKEY
+// It supports rsa.PrivateKey, ecdsa.PrivateKey and dsa.PrivateKey
+func (r *DNSKEY) PrivateKeyString(p crypto.PrivateKey) string {
+	algorithm := strconv.Itoa(int(r.Algorithm))
+	algorithm += " (" + AlgorithmToString[r.Algorithm] + ")"
+
+	switch p := p.(type) {
+	case *rsa.PrivateKey:
+		modulus := toBase64(p.PublicKey.N.Bytes())
+		e := big.NewInt(int64(p.PublicKey.E))
+		publicExponent := toBase64(e.Bytes())
+		privateExponent := toBase64(p.D.Bytes())
+		prime1 := toBase64(p.Primes[0].Bytes())
+		prime2 := toBase64(p.Primes[1].Bytes())
+		// Calculate Exponent1/2 and Coefficient as per: http://en.wikipedia.org/wiki/RSA#Using_the_Chinese_remainder_algorithm
+		// and from: http://code.google.com/p/go/issues/detail?id=987
+		one := big.NewInt(1)
+		p1 := big.NewInt(0).Sub(p.Primes[0], one)
+		q1 := big.NewInt(0).Sub(p.Primes[1], one)
+		exp1 := big.NewInt(0).Mod(p.D, p1)
+		exp2 := big.NewInt(0).Mod(p.D, q1)
+		coeff := big.NewInt(0).ModInverse(p.Primes[1], p.Primes[0])
+
+		exponent1 := toBase64(exp1.Bytes())
+		exponent2 := toBase64(exp2.Bytes())
+		coefficient := toBase64(coeff.Bytes())
+
+		return format +
+			"Algorithm: " + algorithm + "\n" +
+			"Modulus: " + modulus + "\n" +
+			"PublicExponent: " + publicExponent + "\n" +
+			"PrivateExponent: " + privateExponent + "\n" +
+			"Prime1: " + prime1 + "\n" +
+			"Prime2: " + prime2 + "\n" +
+			"Exponent1: " + exponent1 + "\n" +
+			"Exponent2: " + exponent2 + "\n" +
+			"Coefficient: " + coefficient + "\n"
+
+	case *ecdsa.PrivateKey:
+		var intlen int
+		switch r.Algorithm {
+		case ECDSAP256SHA256:
+			intlen = 32
+		case ECDSAP384SHA384:
+			intlen = 48
+		}
+		private := toBase64(intToBytes(p.D, intlen))
+		return format +
+			"Algorithm: " + algorithm + "\n" +
+			"PrivateKey: " + private + "\n"
+
+	case *dsa.PrivateKey:
+		T := divRoundUp(divRoundUp(p.PublicKey.Parameters.G.BitLen(), 8)-64, 8)
+		prime := toBase64(intToBytes(p.PublicKey.Parameters.P, 64+T*8))
+		subprime := toBase64(intToBytes(p.PublicKey.Parameters.Q, 20))
+		base := toBase64(intToBytes(p.PublicKey.Parameters.G, 64+T*8))
+		priv := toBase64(intToBytes(p.X, 20))
+		pub := toBase64(intToBytes(p.PublicKey.Y, 64+T*8))
+		return format +
+			"Algorithm: " + algorithm + "\n" +
+			"Prime(p): " + prime + "\n" +
+			"Subprime(q): " + subprime + "\n" +
+			"Base(g): " + base + "\n" +
+			"Private_value(x): " + priv + "\n" +
+			"Public_value(y): " + pub + "\n"
+
+	case ed25519.PrivateKey:
+		private := toBase64(p[:32])
+		return format +
+			"Algorithm: " + algorithm + "\n" +
+			"PrivateKey: " + private + "\n"
+
+	default:
+		return ""
+	}
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnssec_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnssec_test.go
new file mode 100644
index 0000000..7cf7c41
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnssec_test.go
@@ -0,0 +1,841 @@
+package dns
+
+import (
+	"crypto"
+	"crypto/ecdsa"
+	"crypto/rsa"
+	"reflect"
+	"strings"
+	"testing"
+	"time"
+
+	"golang.org/x/crypto/ed25519"
+)
+
+func getKey() *DNSKEY {
+	key := new(DNSKEY)
+	key.Hdr.Name = "miek.nl."
+	key.Hdr.Class = ClassINET
+	key.Hdr.Ttl = 14400
+	key.Flags = 256
+	key.Protocol = 3
+	key.Algorithm = RSASHA256
+	key.PublicKey = "AwEAAcNEU67LJI5GEgF9QLNqLO1SMq1EdoQ6E9f85ha0k0ewQGCblyW2836GiVsm6k8Kr5ECIoMJ6fZWf3CQSQ9ycWfTyOHfmI3eQ/1Covhb2y4bAmL/07PhrL7ozWBW3wBfM335Ft9xjtXHPy7ztCbV9qZ4TVDTW/Iyg0PiwgoXVesz"
+	return key
+}
+
+func getSoa() *SOA {
+	soa := new(SOA)
+	soa.Hdr = RR_Header{"miek.nl.", TypeSOA, ClassINET, 14400, 0}
+	soa.Ns = "open.nlnetlabs.nl."
+	soa.Mbox = "miekg.atoom.net."
+	soa.Serial = 1293945905
+	soa.Refresh = 14400
+	soa.Retry = 3600
+	soa.Expire = 604800
+	soa.Minttl = 86400
+	return soa
+}
+
+func TestSecure(t *testing.T) {
+	soa := getSoa()
+
+	sig := new(RRSIG)
+	sig.Hdr = RR_Header{"miek.nl.", TypeRRSIG, ClassINET, 14400, 0}
+	sig.TypeCovered = TypeSOA
+	sig.Algorithm = RSASHA256
+	sig.Labels = 2
+	sig.Expiration = 1296534305 // date -u '+%s' -d"2011-02-01 04:25:05"
+	sig.Inception = 1293942305  // date -u '+%s' -d"2011-01-02 04:25:05"
+	sig.OrigTtl = 14400
+	sig.KeyTag = 12051
+	sig.SignerName = "miek.nl."
+	sig.Signature = "oMCbslaAVIp/8kVtLSms3tDABpcPRUgHLrOR48OOplkYo+8TeEGWwkSwaz/MRo2fB4FxW0qj/hTlIjUGuACSd+b1wKdH5GvzRJc2pFmxtCbm55ygAh4EUL0F6U5cKtGJGSXxxg6UFCQ0doJCmiGFa78LolaUOXImJrk6AFrGa0M="
+
+	key := new(DNSKEY)
+	key.Hdr.Name = "miek.nl."
+	key.Hdr.Class = ClassINET
+	key.Hdr.Ttl = 14400
+	key.Flags = 256
+	key.Protocol = 3
+	key.Algorithm = RSASHA256
+	key.PublicKey = "AwEAAcNEU67LJI5GEgF9QLNqLO1SMq1EdoQ6E9f85ha0k0ewQGCblyW2836GiVsm6k8Kr5ECIoMJ6fZWf3CQSQ9ycWfTyOHfmI3eQ/1Covhb2y4bAmL/07PhrL7ozWBW3wBfM335Ft9xjtXHPy7ztCbV9qZ4TVDTW/Iyg0PiwgoXVesz"
+
+	// It should validate. Period is checked separately, so this will keep on working
+	if sig.Verify(key, []RR{soa}) != nil {
+		t.Error("failure to validate")
+	}
+}
+
+func TestSignature(t *testing.T) {
+	sig := new(RRSIG)
+	sig.Hdr.Name = "miek.nl."
+	sig.Hdr.Class = ClassINET
+	sig.Hdr.Ttl = 3600
+	sig.TypeCovered = TypeDNSKEY
+	sig.Algorithm = RSASHA1
+	sig.Labels = 2
+	sig.OrigTtl = 4000
+	sig.Expiration = 1000 //Thu Jan  1 02:06:40 CET 1970
+	sig.Inception = 800   //Thu Jan  1 01:13:20 CET 1970
+	sig.KeyTag = 34641
+	sig.SignerName = "miek.nl."
+	sig.Signature = "AwEAAaHIwpx3w4VHKi6i1LHnTaWeHCL154Jug0Rtc9ji5qwPXpBo6A5sRv7cSsPQKPIwxLpyCrbJ4mr2L0EPOdvP6z6YfljK2ZmTbogU9aSU2fiq/4wjxbdkLyoDVgtO+JsxNN4bjr4WcWhsmk1Hg93FV9ZpkWb0Tbad8DFqNDzr//kZ"
+
+	// Should not be valid
+	if sig.ValidityPeriod(time.Now()) {
+		t.Error("should not be valid")
+	}
+
+	sig.Inception = 315565800   //Tue Jan  1 10:10:00 CET 1980
+	sig.Expiration = 4102477800 //Fri Jan  1 10:10:00 CET 2100
+	if !sig.ValidityPeriod(time.Now()) {
+		t.Error("should be valid")
+	}
+}
+
+func TestSignVerify(t *testing.T) {
+	// The record we want to sign
+	soa := new(SOA)
+	soa.Hdr = RR_Header{"miek.nl.", TypeSOA, ClassINET, 14400, 0}
+	soa.Ns = "open.nlnetlabs.nl."
+	soa.Mbox = "miekg.atoom.net."
+	soa.Serial = 1293945905
+	soa.Refresh = 14400
+	soa.Retry = 3600
+	soa.Expire = 604800
+	soa.Minttl = 86400
+
+	soa1 := new(SOA)
+	soa1.Hdr = RR_Header{"*.miek.nl.", TypeSOA, ClassINET, 14400, 0}
+	soa1.Ns = "open.nlnetlabs.nl."
+	soa1.Mbox = "miekg.atoom.net."
+	soa1.Serial = 1293945905
+	soa1.Refresh = 14400
+	soa1.Retry = 3600
+	soa1.Expire = 604800
+	soa1.Minttl = 86400
+
+	srv := new(SRV)
+	srv.Hdr = RR_Header{"srv.miek.nl.", TypeSRV, ClassINET, 14400, 0}
+	srv.Port = 1000
+	srv.Weight = 800
+	srv.Target = "web1.miek.nl."
+
+	hinfo := &HINFO{
+		Hdr: RR_Header{
+			Name:   "miek.nl.",
+			Rrtype: TypeHINFO,
+			Class:  ClassINET,
+			Ttl:    3789,
+		},
+		Cpu: "X",
+		Os:  "Y",
+	}
+
+	// With this key
+	key := new(DNSKEY)
+	key.Hdr.Rrtype = TypeDNSKEY
+	key.Hdr.Name = "miek.nl."
+	key.Hdr.Class = ClassINET
+	key.Hdr.Ttl = 14400
+	key.Flags = 256
+	key.Protocol = 3
+	key.Algorithm = RSASHA256
+	privkey, _ := key.Generate(512)
+
+	// Fill in the values of the Sig, before signing
+	sig := new(RRSIG)
+	sig.Hdr = RR_Header{"miek.nl.", TypeRRSIG, ClassINET, 14400, 0}
+	sig.TypeCovered = soa.Hdr.Rrtype
+	sig.Labels = uint8(CountLabel(soa.Hdr.Name)) // works for all 3
+	sig.OrigTtl = soa.Hdr.Ttl
+	sig.Expiration = 1296534305 // date -u '+%s' -d"2011-02-01 04:25:05"
+	sig.Inception = 1293942305  // date -u '+%s' -d"2011-01-02 04:25:05"
+	sig.KeyTag = key.KeyTag()   // Get the keyfrom the Key
+	sig.SignerName = key.Hdr.Name
+	sig.Algorithm = RSASHA256
+
+	for _, r := range []RR{soa, soa1, srv, hinfo} {
+		if err := sig.Sign(privkey.(*rsa.PrivateKey), []RR{r}); err != nil {
+			t.Error("failure to sign the record:", err)
+			continue
+		}
+		if err := sig.Verify(key, []RR{r}); err != nil {
+			t.Errorf("failure to validate: %s", r.Header().Name)
+			continue
+		}
+	}
+}
+
+func Test65534(t *testing.T) {
+	t6 := new(RFC3597)
+	t6.Hdr = RR_Header{"miek.nl.", 65534, ClassINET, 14400, 0}
+	t6.Rdata = "505D870001"
+	key := new(DNSKEY)
+	key.Hdr.Name = "miek.nl."
+	key.Hdr.Rrtype = TypeDNSKEY
+	key.Hdr.Class = ClassINET
+	key.Hdr.Ttl = 14400
+	key.Flags = 256
+	key.Protocol = 3
+	key.Algorithm = RSASHA256
+	privkey, _ := key.Generate(1024)
+
+	sig := new(RRSIG)
+	sig.Hdr = RR_Header{"miek.nl.", TypeRRSIG, ClassINET, 14400, 0}
+	sig.TypeCovered = t6.Hdr.Rrtype
+	sig.Labels = uint8(CountLabel(t6.Hdr.Name))
+	sig.OrigTtl = t6.Hdr.Ttl
+	sig.Expiration = 1296534305 // date -u '+%s' -d"2011-02-01 04:25:05"
+	sig.Inception = 1293942305  // date -u '+%s' -d"2011-01-02 04:25:05"
+	sig.KeyTag = key.KeyTag()
+	sig.SignerName = key.Hdr.Name
+	sig.Algorithm = RSASHA256
+	if err := sig.Sign(privkey.(*rsa.PrivateKey), []RR{t6}); err != nil {
+		t.Error(err)
+		t.Error("failure to sign the TYPE65534 record")
+	}
+	if err := sig.Verify(key, []RR{t6}); err != nil {
+		t.Error(err)
+		t.Errorf("failure to validate %s", t6.Header().Name)
+	}
+}
+
+func TestDnskey(t *testing.T) {
+	pubkey, err := ReadRR(strings.NewReader(`
+miek.nl.	IN	DNSKEY	256 3 10 AwEAAZuMCu2FdugHkTrXYgl5qixvcDw1aDDlvL46/xJKbHBAHY16fNUb2b65cwko2Js/aJxUYJbZk5dwCDZxYfrfbZVtDPQuc3o8QaChVxC7/JYz2AHc9qHvqQ1j4VrH71RWINlQo6VYjzN/BGpMhOZoZOEwzp1HfsOE3lNYcoWU1smL ;{id = 5240 (zsk), size = 1024b}
+`), "Kmiek.nl.+010+05240.key")
+	if err != nil {
+		t.Fatal(err)
+	}
+	privStr := `Private-key-format: v1.3
+Algorithm: 10 (RSASHA512)
+Modulus: m4wK7YV26AeROtdiCXmqLG9wPDVoMOW8vjr/EkpscEAdjXp81RvZvrlzCSjYmz9onFRgltmTl3AINnFh+t9tlW0M9C5zejxBoKFXELv8ljPYAdz2oe+pDWPhWsfvVFYg2VCjpViPM38EakyE5mhk4TDOnUd+w4TeU1hyhZTWyYs=
+PublicExponent: AQAB
+PrivateExponent: UfCoIQ/Z38l8vB6SSqOI/feGjHEl/fxIPX4euKf0D/32k30fHbSaNFrFOuIFmWMB3LimWVEs6u3dpbB9CQeCVg7hwU5puG7OtuiZJgDAhNeOnxvo5btp4XzPZrJSxR4WNQnwIiYWbl0aFlL1VGgHC/3By89ENZyWaZcMLW4KGWE=
+Prime1: yxwC6ogAu8aVcDx2wg1V0b5M5P6jP8qkRFVMxWNTw60Vkn+ECvw6YAZZBHZPaMyRYZLzPgUlyYRd0cjupy4+fQ==
+Prime2: xA1bF8M0RTIQ6+A11AoVG6GIR/aPGg5sogRkIZ7ID/sF6g9HMVU/CM2TqVEBJLRPp73cv6ZeC3bcqOCqZhz+pw==
+Exponent1: xzkblyZ96bGYxTVZm2/vHMOXswod4KWIyMoOepK6B/ZPcZoIT6omLCgtypWtwHLfqyCz3MK51Nc0G2EGzg8rFQ==
+Exponent2: Pu5+mCEb7T5F+kFNZhQadHUklt0JUHbi3hsEvVoHpEGSw3BGDQrtIflDde0/rbWHgDPM4WQY+hscd8UuTXrvLw==
+Coefficient: UuRoNqe7YHnKmQzE6iDWKTMIWTuoqqrFAmXPmKQnC+Y+BQzOVEHUo9bXdDnoI9hzXP1gf8zENMYwYLeWpuYlFQ==
+`
+	privkey, err := pubkey.(*DNSKEY).ReadPrivateKey(strings.NewReader(privStr),
+		"Kmiek.nl.+010+05240.private")
+	if err != nil {
+		t.Fatal(err)
+	}
+	if pubkey.(*DNSKEY).PublicKey != "AwEAAZuMCu2FdugHkTrXYgl5qixvcDw1aDDlvL46/xJKbHBAHY16fNUb2b65cwko2Js/aJxUYJbZk5dwCDZxYfrfbZVtDPQuc3o8QaChVxC7/JYz2AHc9qHvqQ1j4VrH71RWINlQo6VYjzN/BGpMhOZoZOEwzp1HfsOE3lNYcoWU1smL" {
+		t.Error("pubkey is not what we've read")
+	}
+	if pubkey.(*DNSKEY).PrivateKeyString(privkey) != privStr {
+		t.Error("privkey is not what we've read")
+		t.Errorf("%v", pubkey.(*DNSKEY).PrivateKeyString(privkey))
+	}
+}
+
+func TestTag(t *testing.T) {
+	key := new(DNSKEY)
+	key.Hdr.Name = "miek.nl."
+	key.Hdr.Rrtype = TypeDNSKEY
+	key.Hdr.Class = ClassINET
+	key.Hdr.Ttl = 3600
+	key.Flags = 256
+	key.Protocol = 3
+	key.Algorithm = RSASHA256
+	key.PublicKey = "AwEAAcNEU67LJI5GEgF9QLNqLO1SMq1EdoQ6E9f85ha0k0ewQGCblyW2836GiVsm6k8Kr5ECIoMJ6fZWf3CQSQ9ycWfTyOHfmI3eQ/1Covhb2y4bAmL/07PhrL7ozWBW3wBfM335Ft9xjtXHPy7ztCbV9qZ4TVDTW/Iyg0PiwgoXVesz"
+
+	tag := key.KeyTag()
+	if tag != 12051 {
+		t.Errorf("wrong key tag: %d for key %v", tag, key)
+	}
+}
+
+func TestKeyRSA(t *testing.T) {
+	if testing.Short() {
+		t.Skip("skipping test in short mode.")
+	}
+	key := new(DNSKEY)
+	key.Hdr.Name = "miek.nl."
+	key.Hdr.Rrtype = TypeDNSKEY
+	key.Hdr.Class = ClassINET
+	key.Hdr.Ttl = 3600
+	key.Flags = 256
+	key.Protocol = 3
+	key.Algorithm = RSASHA256
+	priv, _ := key.Generate(2048)
+
+	soa := new(SOA)
+	soa.Hdr = RR_Header{"miek.nl.", TypeSOA, ClassINET, 14400, 0}
+	soa.Ns = "open.nlnetlabs.nl."
+	soa.Mbox = "miekg.atoom.net."
+	soa.Serial = 1293945905
+	soa.Refresh = 14400
+	soa.Retry = 3600
+	soa.Expire = 604800
+	soa.Minttl = 86400
+
+	sig := new(RRSIG)
+	sig.Hdr = RR_Header{"miek.nl.", TypeRRSIG, ClassINET, 14400, 0}
+	sig.TypeCovered = TypeSOA
+	sig.Algorithm = RSASHA256
+	sig.Labels = 2
+	sig.Expiration = 1296534305 // date -u '+%s' -d"2011-02-01 04:25:05"
+	sig.Inception = 1293942305  // date -u '+%s' -d"2011-01-02 04:25:05"
+	sig.OrigTtl = soa.Hdr.Ttl
+	sig.KeyTag = key.KeyTag()
+	sig.SignerName = key.Hdr.Name
+
+	if err := sig.Sign(priv.(*rsa.PrivateKey), []RR{soa}); err != nil {
+		t.Error("failed to sign")
+		return
+	}
+	if err := sig.Verify(key, []RR{soa}); err != nil {
+		t.Error("failed to verify")
+	}
+}
+
+func TestKeyToDS(t *testing.T) {
+	key := new(DNSKEY)
+	key.Hdr.Name = "miek.nl."
+	key.Hdr.Rrtype = TypeDNSKEY
+	key.Hdr.Class = ClassINET
+	key.Hdr.Ttl = 3600
+	key.Flags = 256
+	key.Protocol = 3
+	key.Algorithm = RSASHA256
+	key.PublicKey = "AwEAAcNEU67LJI5GEgF9QLNqLO1SMq1EdoQ6E9f85ha0k0ewQGCblyW2836GiVsm6k8Kr5ECIoMJ6fZWf3CQSQ9ycWfTyOHfmI3eQ/1Covhb2y4bAmL/07PhrL7ozWBW3wBfM335Ft9xjtXHPy7ztCbV9qZ4TVDTW/Iyg0PiwgoXVesz"
+
+	ds := key.ToDS(SHA1)
+	if strings.ToUpper(ds.Digest) != "B5121BDB5B8D86D0CC5FFAFBAAABE26C3E20BAC1" {
+		t.Errorf("wrong DS digest for SHA1\n%v", ds)
+	}
+}
+
+func TestSignRSA(t *testing.T) {
+	pub := "miek.nl. IN DNSKEY 256 3 5 AwEAAb+8lGNCxJgLS8rYVer6EnHVuIkQDghdjdtewDzU3G5R7PbMbKVRvH2Ma7pQyYceoaqWZQirSj72euPWfPxQnMy9ucCylA+FuH9cSjIcPf4PqJfdupHk9X6EBYjxrCLY4p1/yBwgyBIRJtZtAqM3ceAH2WovEJD6rTtOuHo5AluJ"
+
+	priv := `Private-key-format: v1.3
+Algorithm: 5 (RSASHA1)
+Modulus: v7yUY0LEmAtLythV6voScdW4iRAOCF2N217APNTcblHs9sxspVG8fYxrulDJhx6hqpZlCKtKPvZ649Z8/FCczL25wLKUD4W4f1xKMhw9/g+ol926keT1foQFiPGsItjinX/IHCDIEhEm1m0Cozdx4AfZai8QkPqtO064ejkCW4k=
+PublicExponent: AQAB
+PrivateExponent: YPwEmwjk5HuiROKU4xzHQ6l1hG8Iiha4cKRG3P5W2b66/EN/GUh07ZSf0UiYB67o257jUDVEgwCuPJz776zfApcCB4oGV+YDyEu7Hp/rL8KcSN0la0k2r9scKwxTp4BTJT23zyBFXsV/1wRDK1A5NxsHPDMYi2SoK63Enm/1ptk=
+Prime1: /wjOG+fD0ybNoSRn7nQ79udGeR1b0YhUA5mNjDx/x2fxtIXzygYk0Rhx9QFfDy6LOBvz92gbNQlzCLz3DJt5hw==
+Prime2: wHZsJ8OGhkp5p3mrJFZXMDc2mbYusDVTA+t+iRPdS797Tj0pjvU2HN4vTnTj8KBQp6hmnY7dLp9Y1qserySGbw==
+Exponent1: N0A7FsSRIg+IAN8YPQqlawoTtG1t1OkJ+nWrurPootScApX6iMvn8fyvw3p2k51rv84efnzpWAYiC8SUaQDNxQ==
+Exponent2: SvuYRaGyvo0zemE3oS+WRm2scxR8eiA8WJGeOc+obwOKCcBgeZblXzfdHGcEC1KaOcetOwNW/vwMA46lpLzJNw==
+Coefficient: 8+7ZN/JgByqv0NfULiFKTjtyegUcijRuyij7yNxYbCBneDvZGxJwKNi4YYXWx743pcAj4Oi4Oh86gcmxLs+hGw==
+Created: 20110302104537
+Publish: 20110302104537
+Activate: 20110302104537`
+
+	xk := testRR(pub)
+	k := xk.(*DNSKEY)
+	p, err := k.NewPrivateKey(priv)
+	if err != nil {
+		t.Error(err)
+	}
+	switch priv := p.(type) {
+	case *rsa.PrivateKey:
+		if 65537 != priv.PublicKey.E {
+			t.Error("exponenent should be 65537")
+		}
+	default:
+		t.Errorf("we should have read an RSA key: %v", priv)
+	}
+	if k.KeyTag() != 37350 {
+		t.Errorf("keytag should be 37350, got %d %v", k.KeyTag(), k)
+	}
+
+	soa := new(SOA)
+	soa.Hdr = RR_Header{"miek.nl.", TypeSOA, ClassINET, 14400, 0}
+	soa.Ns = "open.nlnetlabs.nl."
+	soa.Mbox = "miekg.atoom.net."
+	soa.Serial = 1293945905
+	soa.Refresh = 14400
+	soa.Retry = 3600
+	soa.Expire = 604800
+	soa.Minttl = 86400
+
+	sig := new(RRSIG)
+	sig.Hdr = RR_Header{"miek.nl.", TypeRRSIG, ClassINET, 14400, 0}
+	sig.Expiration = 1296534305 // date -u '+%s' -d"2011-02-01 04:25:05"
+	sig.Inception = 1293942305  // date -u '+%s' -d"2011-01-02 04:25:05"
+	sig.KeyTag = k.KeyTag()
+	sig.SignerName = k.Hdr.Name
+	sig.Algorithm = k.Algorithm
+
+	sig.Sign(p.(*rsa.PrivateKey), []RR{soa})
+	if sig.Signature != "D5zsobpQcmMmYsUMLxCVEtgAdCvTu8V/IEeP4EyLBjqPJmjt96bwM9kqihsccofA5LIJ7DN91qkCORjWSTwNhzCv7bMyr2o5vBZElrlpnRzlvsFIoAZCD9xg6ZY7ZyzUJmU6IcTwG4v3xEYajcpbJJiyaw/RqR90MuRdKPiBzSo=" {
+		t.Errorf("signature is not correct: %v", sig)
+	}
+}
+
+func TestSignVerifyECDSA(t *testing.T) {
+	pub := `example.net. 3600 IN DNSKEY 257 3 14 (
+	xKYaNhWdGOfJ+nPrL8/arkwf2EY3MDJ+SErKivBVSum1
+	w/egsXvSADtNJhyem5RCOpgQ6K8X1DRSEkrbYQ+OB+v8
+	/uX45NBwY8rp65F6Glur8I/mlVNgF6W/qTI37m40 )`
+	priv := `Private-key-format: v1.2
+Algorithm: 14 (ECDSAP384SHA384)
+PrivateKey: WURgWHCcYIYUPWgeLmiPY2DJJk02vgrmTfitxgqcL4vwW7BOrbawVmVe0d9V94SR`
+
+	eckey := testRR(pub)
+	privkey, err := eckey.(*DNSKEY).NewPrivateKey(priv)
+	if err != nil {
+		t.Fatal(err)
+	}
+	// TODO: Create separate test for this
+	ds := eckey.(*DNSKEY).ToDS(SHA384)
+	if ds.KeyTag != 10771 {
+		t.Fatal("wrong keytag on DS")
+	}
+	if ds.Digest != "72d7b62976ce06438e9c0bf319013cf801f09ecc84b8d7e9495f27e305c6a9b0563a9b5f4d288405c3008a946df983d6" {
+		t.Fatal("wrong DS Digest")
+	}
+	a := testRR("www.example.net. 3600 IN A 192.0.2.1")
+	sig := new(RRSIG)
+	sig.Hdr = RR_Header{"example.net.", TypeRRSIG, ClassINET, 14400, 0}
+	sig.Expiration, _ = StringToTime("20100909102025")
+	sig.Inception, _ = StringToTime("20100812102025")
+	sig.KeyTag = eckey.(*DNSKEY).KeyTag()
+	sig.SignerName = eckey.(*DNSKEY).Hdr.Name
+	sig.Algorithm = eckey.(*DNSKEY).Algorithm
+
+	if sig.Sign(privkey.(*ecdsa.PrivateKey), []RR{a}) != nil {
+		t.Fatal("failure to sign the record")
+	}
+
+	if err := sig.Verify(eckey.(*DNSKEY), []RR{a}); err != nil {
+		t.Fatalf("failure to validate:\n%s\n%s\n%s\n\n%s\n\n%v",
+			eckey.(*DNSKEY).String(),
+			a.String(),
+			sig.String(),
+			eckey.(*DNSKEY).PrivateKeyString(privkey),
+			err,
+		)
+	}
+}
+
+func TestSignVerifyECDSA2(t *testing.T) {
+	srv1 := testRR("srv.miek.nl. IN SRV 1000 800 0 web1.miek.nl.")
+	srv := srv1.(*SRV)
+
+	// With this key
+	key := new(DNSKEY)
+	key.Hdr.Rrtype = TypeDNSKEY
+	key.Hdr.Name = "miek.nl."
+	key.Hdr.Class = ClassINET
+	key.Hdr.Ttl = 14400
+	key.Flags = 256
+	key.Protocol = 3
+	key.Algorithm = ECDSAP256SHA256
+	privkey, err := key.Generate(256)
+	if err != nil {
+		t.Fatal("failure to generate key")
+	}
+
+	// Fill in the values of the Sig, before signing
+	sig := new(RRSIG)
+	sig.Hdr = RR_Header{"miek.nl.", TypeRRSIG, ClassINET, 14400, 0}
+	sig.TypeCovered = srv.Hdr.Rrtype
+	sig.Labels = uint8(CountLabel(srv.Hdr.Name)) // works for all 3
+	sig.OrigTtl = srv.Hdr.Ttl
+	sig.Expiration = 1296534305 // date -u '+%s' -d"2011-02-01 04:25:05"
+	sig.Inception = 1293942305  // date -u '+%s' -d"2011-01-02 04:25:05"
+	sig.KeyTag = key.KeyTag()   // Get the keyfrom the Key
+	sig.SignerName = key.Hdr.Name
+	sig.Algorithm = ECDSAP256SHA256
+
+	if sig.Sign(privkey.(*ecdsa.PrivateKey), []RR{srv}) != nil {
+		t.Fatal("failure to sign the record")
+	}
+
+	err = sig.Verify(key, []RR{srv})
+	if err != nil {
+		t.Errorf("failure to validate:\n%s\n%s\n%s\n\n%s\n\n%v",
+			key.String(),
+			srv.String(),
+			sig.String(),
+			key.PrivateKeyString(privkey),
+			err,
+		)
+	}
+}
+
+func TestSignVerifyEd25519(t *testing.T) {
+	srv1, err := NewRR("srv.miek.nl. IN SRV 1000 800 0 web1.miek.nl.")
+	if err != nil {
+		t.Fatal(err)
+	}
+	srv := srv1.(*SRV)
+
+	// With this key
+	key := new(DNSKEY)
+	key.Hdr.Rrtype = TypeDNSKEY
+	key.Hdr.Name = "miek.nl."
+	key.Hdr.Class = ClassINET
+	key.Hdr.Ttl = 14400
+	key.Flags = 256
+	key.Protocol = 3
+	key.Algorithm = ED25519
+	privkey, err := key.Generate(256)
+	if err != nil {
+		t.Fatal("failure to generate key")
+	}
+
+	// Fill in the values of the Sig, before signing
+	sig := new(RRSIG)
+	sig.Hdr = RR_Header{"miek.nl.", TypeRRSIG, ClassINET, 14400, 0}
+	sig.TypeCovered = srv.Hdr.Rrtype
+	sig.Labels = uint8(CountLabel(srv.Hdr.Name)) // works for all 3
+	sig.OrigTtl = srv.Hdr.Ttl
+	sig.Expiration = 1296534305 // date -u '+%s' -d"2011-02-01 04:25:05"
+	sig.Inception = 1293942305  // date -u '+%s' -d"2011-01-02 04:25:05"
+	sig.KeyTag = key.KeyTag()   // Get the keyfrom the Key
+	sig.SignerName = key.Hdr.Name
+	sig.Algorithm = ED25519
+
+	if sig.Sign(privkey.(ed25519.PrivateKey), []RR{srv}) != nil {
+		t.Fatal("failure to sign the record")
+	}
+
+	err = sig.Verify(key, []RR{srv})
+	if err != nil {
+		t.Logf("failure to validate:\n%s\n%s\n%s\n\n%s\n\n%v",
+			key.String(),
+			srv.String(),
+			sig.String(),
+			key.PrivateKeyString(privkey),
+			err,
+		)
+	}
+}
+
+// Here the test vectors from the relevant RFCs are checked.
+// rfc6605 6.1
+func TestRFC6605P256(t *testing.T) {
+	exDNSKEY := `example.net. 3600 IN DNSKEY 257 3 13 (
+                 GojIhhXUN/u4v54ZQqGSnyhWJwaubCvTmeexv7bR6edb
+                 krSqQpF64cYbcB7wNcP+e+MAnLr+Wi9xMWyQLc8NAA== )`
+	exPriv := `Private-key-format: v1.2
+Algorithm: 13 (ECDSAP256SHA256)
+PrivateKey: GU6SnQ/Ou+xC5RumuIUIuJZteXT2z0O/ok1s38Et6mQ=`
+	rrDNSKEY := testRR(exDNSKEY)
+	priv, err := rrDNSKEY.(*DNSKEY).NewPrivateKey(exPriv)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	exDS := `example.net. 3600 IN DS 55648 13 2 (
+             b4c8c1fe2e7477127b27115656ad6256f424625bf5c1
+             e2770ce6d6e37df61d17 )`
+	rrDS := testRR(exDS)
+	ourDS := rrDNSKEY.(*DNSKEY).ToDS(SHA256)
+	if !reflect.DeepEqual(ourDS, rrDS.(*DS)) {
+		t.Errorf("DS record differs:\n%v\n%v", ourDS, rrDS.(*DS))
+	}
+
+	exA := `www.example.net. 3600 IN A 192.0.2.1`
+	exRRSIG := `www.example.net. 3600 IN RRSIG A 13 3 3600 (
+                20100909100439 20100812100439 55648 example.net.
+                qx6wLYqmh+l9oCKTN6qIc+bw6ya+KJ8oMz0YP107epXA
+                yGmt+3SNruPFKG7tZoLBLlUzGGus7ZwmwWep666VCw== )`
+	rrA := testRR(exA)
+	rrRRSIG := testRR(exRRSIG)
+	if err := rrRRSIG.(*RRSIG).Verify(rrDNSKEY.(*DNSKEY), []RR{rrA}); err != nil {
+		t.Errorf("failure to validate the spec RRSIG: %v", err)
+	}
+
+	ourRRSIG := &RRSIG{
+		Hdr: RR_Header{
+			Ttl: rrA.Header().Ttl,
+		},
+		KeyTag:     rrDNSKEY.(*DNSKEY).KeyTag(),
+		SignerName: rrDNSKEY.(*DNSKEY).Hdr.Name,
+		Algorithm:  rrDNSKEY.(*DNSKEY).Algorithm,
+	}
+	ourRRSIG.Expiration, _ = StringToTime("20100909100439")
+	ourRRSIG.Inception, _ = StringToTime("20100812100439")
+	err = ourRRSIG.Sign(priv.(*ecdsa.PrivateKey), []RR{rrA})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err = ourRRSIG.Verify(rrDNSKEY.(*DNSKEY), []RR{rrA}); err != nil {
+		t.Errorf("failure to validate our RRSIG: %v", err)
+	}
+
+	// Signatures are randomized
+	rrRRSIG.(*RRSIG).Signature = ""
+	ourRRSIG.Signature = ""
+	if !reflect.DeepEqual(ourRRSIG, rrRRSIG.(*RRSIG)) {
+		t.Fatalf("RRSIG record differs:\n%v\n%v", ourRRSIG, rrRRSIG.(*RRSIG))
+	}
+}
+
+// rfc6605 6.2
+func TestRFC6605P384(t *testing.T) {
+	exDNSKEY := `example.net. 3600 IN DNSKEY 257 3 14 (
+                 xKYaNhWdGOfJ+nPrL8/arkwf2EY3MDJ+SErKivBVSum1
+                 w/egsXvSADtNJhyem5RCOpgQ6K8X1DRSEkrbYQ+OB+v8
+                 /uX45NBwY8rp65F6Glur8I/mlVNgF6W/qTI37m40 )`
+	exPriv := `Private-key-format: v1.2
+Algorithm: 14 (ECDSAP384SHA384)
+PrivateKey: WURgWHCcYIYUPWgeLmiPY2DJJk02vgrmTfitxgqcL4vwW7BOrbawVmVe0d9V94SR`
+	rrDNSKEY := testRR(exDNSKEY)
+	priv, err := rrDNSKEY.(*DNSKEY).NewPrivateKey(exPriv)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	exDS := `example.net. 3600 IN DS 10771 14 4 (
+           72d7b62976ce06438e9c0bf319013cf801f09ecc84b8
+           d7e9495f27e305c6a9b0563a9b5f4d288405c3008a94
+           6df983d6 )`
+	rrDS := testRR(exDS)
+	ourDS := rrDNSKEY.(*DNSKEY).ToDS(SHA384)
+	if !reflect.DeepEqual(ourDS, rrDS.(*DS)) {
+		t.Fatalf("DS record differs:\n%v\n%v", ourDS, rrDS.(*DS))
+	}
+
+	exA := `www.example.net. 3600 IN A 192.0.2.1`
+	exRRSIG := `www.example.net. 3600 IN RRSIG A 14 3 3600 (
+           20100909102025 20100812102025 10771 example.net.
+           /L5hDKIvGDyI1fcARX3z65qrmPsVz73QD1Mr5CEqOiLP
+           95hxQouuroGCeZOvzFaxsT8Glr74hbavRKayJNuydCuz
+           WTSSPdz7wnqXL5bdcJzusdnI0RSMROxxwGipWcJm )`
+	rrA := testRR(exA)
+	rrRRSIG := testRR(exRRSIG)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err = rrRRSIG.(*RRSIG).Verify(rrDNSKEY.(*DNSKEY), []RR{rrA}); err != nil {
+		t.Errorf("failure to validate the spec RRSIG: %v", err)
+	}
+
+	ourRRSIG := &RRSIG{
+		Hdr: RR_Header{
+			Ttl: rrA.Header().Ttl,
+		},
+		KeyTag:     rrDNSKEY.(*DNSKEY).KeyTag(),
+		SignerName: rrDNSKEY.(*DNSKEY).Hdr.Name,
+		Algorithm:  rrDNSKEY.(*DNSKEY).Algorithm,
+	}
+	ourRRSIG.Expiration, _ = StringToTime("20100909102025")
+	ourRRSIG.Inception, _ = StringToTime("20100812102025")
+	err = ourRRSIG.Sign(priv.(*ecdsa.PrivateKey), []RR{rrA})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err = ourRRSIG.Verify(rrDNSKEY.(*DNSKEY), []RR{rrA}); err != nil {
+		t.Errorf("failure to validate our RRSIG: %v", err)
+	}
+
+	// Signatures are randomized
+	rrRRSIG.(*RRSIG).Signature = ""
+	ourRRSIG.Signature = ""
+	if !reflect.DeepEqual(ourRRSIG, rrRRSIG.(*RRSIG)) {
+		t.Fatalf("RRSIG record differs:\n%v\n%v", ourRRSIG, rrRRSIG.(*RRSIG))
+	}
+}
+
+// rfc8080 6.1
+func TestRFC8080Ed25519Example1(t *testing.T) {
+	exDNSKEY := `example.com. 3600 IN DNSKEY 257 3 15 (
+             l02Woi0iS8Aa25FQkUd9RMzZHJpBoRQwAQEX1SxZJA4= )`
+	exPriv := `Private-key-format: v1.2
+Algorithm: 15 (ED25519)
+PrivateKey: ODIyNjAzODQ2MjgwODAxMjI2NDUxOTAyMDQxNDIyNjI=`
+	rrDNSKEY, err := NewRR(exDNSKEY)
+	if err != nil {
+		t.Fatal(err)
+	}
+	priv, err := rrDNSKEY.(*DNSKEY).NewPrivateKey(exPriv)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	exDS := `example.com. 3600 IN DS 3613 15 2 (
+             3aa5ab37efce57f737fc1627013fee07bdf241bd10f3b1964ab55c78e79
+             a304b )`
+	rrDS, err := NewRR(exDS)
+	if err != nil {
+		t.Fatal(err)
+	}
+	ourDS := rrDNSKEY.(*DNSKEY).ToDS(SHA256)
+	if !reflect.DeepEqual(ourDS, rrDS.(*DS)) {
+		t.Fatalf("DS record differs:\n%v\n%v", ourDS, rrDS.(*DS))
+	}
+
+	exMX := `example.com. 3600 IN MX 10 mail.example.com.`
+	exRRSIG := `example.com. 3600 IN RRSIG MX 15 2 3600 (
+             1440021600 1438207200 3613 example.com. (
+             oL9krJun7xfBOIWcGHi7mag5/hdZrKWw15jPGrHpjQeRAvTdszaPD+QLs3f
+             x8A4M3e23mRZ9VrbpMngwcrqNAg== ) )`
+	rrMX, err := NewRR(exMX)
+	if err != nil {
+		t.Fatal(err)
+	}
+	rrRRSIG, err := NewRR(exRRSIG)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err = rrRRSIG.(*RRSIG).Verify(rrDNSKEY.(*DNSKEY), []RR{rrMX}); err != nil {
+		t.Errorf("failure to validate the spec RRSIG: %v", err)
+	}
+
+	ourRRSIG := &RRSIG{
+		Hdr: RR_Header{
+			Ttl: rrMX.Header().Ttl,
+		},
+		KeyTag:     rrDNSKEY.(*DNSKEY).KeyTag(),
+		SignerName: rrDNSKEY.(*DNSKEY).Hdr.Name,
+		Algorithm:  rrDNSKEY.(*DNSKEY).Algorithm,
+	}
+	ourRRSIG.Expiration, _ = StringToTime("20150819220000")
+	ourRRSIG.Inception, _ = StringToTime("20150729220000")
+	err = ourRRSIG.Sign(priv.(ed25519.PrivateKey), []RR{rrMX})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err = ourRRSIG.Verify(rrDNSKEY.(*DNSKEY), []RR{rrMX}); err != nil {
+		t.Errorf("failure to validate our RRSIG: %v", err)
+	}
+
+	if !reflect.DeepEqual(ourRRSIG, rrRRSIG.(*RRSIG)) {
+		t.Fatalf("RRSIG record differs:\n%v\n%v", ourRRSIG, rrRRSIG.(*RRSIG))
+	}
+}
+
+// rfc8080 6.1
+func TestRFC8080Ed25519Example2(t *testing.T) {
+	exDNSKEY := `example.com. 3600 IN DNSKEY 257 3 15 (
+             zPnZ/QwEe7S8C5SPz2OfS5RR40ATk2/rYnE9xHIEijs= )`
+	exPriv := `Private-key-format: v1.2
+Algorithm: 15 (ED25519)
+PrivateKey: DSSF3o0s0f+ElWzj9E/Osxw8hLpk55chkmx0LYN5WiY=`
+	rrDNSKEY, err := NewRR(exDNSKEY)
+	if err != nil {
+		t.Fatal(err)
+	}
+	priv, err := rrDNSKEY.(*DNSKEY).NewPrivateKey(exPriv)
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	exDS := `example.com. 3600 IN DS 35217 15 2 (
+             401781b934e392de492ec77ae2e15d70f6575a1c0bc59c5275c04ebe80c
+             6614c )`
+	rrDS, err := NewRR(exDS)
+	if err != nil {
+		t.Fatal(err)
+	}
+	ourDS := rrDNSKEY.(*DNSKEY).ToDS(SHA256)
+	if !reflect.DeepEqual(ourDS, rrDS.(*DS)) {
+		t.Fatalf("DS record differs:\n%v\n%v", ourDS, rrDS.(*DS))
+	}
+
+	exMX := `example.com. 3600 IN MX 10 mail.example.com.`
+	exRRSIG := `example.com. 3600 IN RRSIG MX 15 2 3600 (
+             1440021600 1438207200 35217 example.com. (
+             zXQ0bkYgQTEFyfLyi9QoiY6D8ZdYo4wyUhVioYZXFdT410QPRITQSqJSnzQ
+             oSm5poJ7gD7AQR0O7KuI5k2pcBg== ) )`
+	rrMX, err := NewRR(exMX)
+	if err != nil {
+		t.Fatal(err)
+	}
+	rrRRSIG, err := NewRR(exRRSIG)
+	if err != nil {
+		t.Fatal(err)
+	}
+	if err = rrRRSIG.(*RRSIG).Verify(rrDNSKEY.(*DNSKEY), []RR{rrMX}); err != nil {
+		t.Errorf("failure to validate the spec RRSIG: %v", err)
+	}
+
+	ourRRSIG := &RRSIG{
+		Hdr: RR_Header{
+			Ttl: rrMX.Header().Ttl,
+		},
+		KeyTag:     rrDNSKEY.(*DNSKEY).KeyTag(),
+		SignerName: rrDNSKEY.(*DNSKEY).Hdr.Name,
+		Algorithm:  rrDNSKEY.(*DNSKEY).Algorithm,
+	}
+	ourRRSIG.Expiration, _ = StringToTime("20150819220000")
+	ourRRSIG.Inception, _ = StringToTime("20150729220000")
+	err = ourRRSIG.Sign(priv.(ed25519.PrivateKey), []RR{rrMX})
+	if err != nil {
+		t.Fatal(err)
+	}
+
+	if err = ourRRSIG.Verify(rrDNSKEY.(*DNSKEY), []RR{rrMX}); err != nil {
+		t.Errorf("failure to validate our RRSIG: %v", err)
+	}
+
+	if !reflect.DeepEqual(ourRRSIG, rrRRSIG.(*RRSIG)) {
+		t.Fatalf("RRSIG record differs:\n%v\n%v", ourRRSIG, rrRRSIG.(*RRSIG))
+	}
+}
+
+func TestInvalidRRSet(t *testing.T) {
+	goodRecords := make([]RR, 2)
+	goodRecords[0] = &TXT{Hdr: RR_Header{Name: "name.cloudflare.com.", Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"Hello world"}}
+	goodRecords[1] = &TXT{Hdr: RR_Header{Name: "name.cloudflare.com.", Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"_o/"}}
+
+	// Generate key
+	keyname := "cloudflare.com."
+	key := &DNSKEY{
+		Hdr:       RR_Header{Name: keyname, Rrtype: TypeDNSKEY, Class: ClassINET, Ttl: 0},
+		Algorithm: ECDSAP256SHA256,
+		Flags:     ZONE,
+		Protocol:  3,
+	}
+	privatekey, err := key.Generate(256)
+	if err != nil {
+		t.Fatal(err.Error())
+	}
+
+	// Need to fill in: Inception, Expiration, KeyTag, SignerName and Algorithm
+	curTime := time.Now()
+	signature := &RRSIG{
+		Inception:  uint32(curTime.Unix()),
+		Expiration: uint32(curTime.Add(time.Hour).Unix()),
+		KeyTag:     key.KeyTag(),
+		SignerName: keyname,
+		Algorithm:  ECDSAP256SHA256,
+	}
+
+	// Inconsistent name between records
+	badRecords := make([]RR, 2)
+	badRecords[0] = &TXT{Hdr: RR_Header{Name: "name.cloudflare.com.", Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"Hello world"}}
+	badRecords[1] = &TXT{Hdr: RR_Header{Name: "nama.cloudflare.com.", Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"_o/"}}
+
+	if IsRRset(badRecords) {
+		t.Fatal("Record set with inconsistent names considered valid")
+	}
+
+	badRecords[0] = &TXT{Hdr: RR_Header{Name: "name.cloudflare.com.", Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"Hello world"}}
+	badRecords[1] = &A{Hdr: RR_Header{Name: "name.cloudflare.com.", Rrtype: TypeA, Class: ClassINET, Ttl: 0}}
+
+	if IsRRset(badRecords) {
+		t.Fatal("Record set with inconsistent record types considered valid")
+	}
+
+	badRecords[0] = &TXT{Hdr: RR_Header{Name: "name.cloudflare.com.", Rrtype: TypeTXT, Class: ClassINET, Ttl: 0}, Txt: []string{"Hello world"}}
+	badRecords[1] = &TXT{Hdr: RR_Header{Name: "name.cloudflare.com.", Rrtype: TypeTXT, Class: ClassCHAOS, Ttl: 0}, Txt: []string{"_o/"}}
+
+	if IsRRset(badRecords) {
+		t.Fatal("Record set with inconsistent record class considered valid")
+	}
+
+	// Sign the good record set and then make sure verification fails on the bad record set
+	if err := signature.Sign(privatekey.(crypto.Signer), goodRecords); err != nil {
+		t.Fatal("Signing good records failed")
+	}
+
+	if err := signature.Verify(key, badRecords); err != ErrRRset {
+		t.Fatal("Verification did not return ErrRRset with inconsistent records")
+	}
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnsutil/util.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnsutil/util.go
new file mode 100644
index 0000000..76ac4de
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnsutil/util.go
@@ -0,0 +1,83 @@
+// Package dnsutil contains higher-level methods useful with the dns
+// package.  While package dns implements the DNS protocols itself,
+// these functions are related but not directly required for protocol
+// processing.  They are often useful in preparing input/output of the
+// functions in package dns.
+package dnsutil
+
+import (
+	"strings"
+
+	"github.com/miekg/dns"
+)
+
+// AddOrigin adds origin to s if s is not already a FQDN.
+// Note that the result may not be a FQDN.  If origin does not end
+// with a ".", the result won't either.
+// This implements the zonefile convention (specified in RFC 1035,
+// Section "5.1. Format") that "@" represents the
+// apex (bare) domain. i.e. AddOrigin("@", "foo.com.") returns "foo.com.".
+func AddOrigin(s, origin string) string {
+	// ("foo.", "origin.") -> "foo." (already a FQDN)
+	// ("foo", "origin.") -> "foo.origin."
+	// ("foo", "origin") -> "foo.origin"
+	// ("foo", ".") -> "foo." (Same as dns.Fqdn())
+	// ("foo.", ".") -> "foo." (Same as dns.Fqdn())
+	// ("@", "origin.") -> "origin." (@ represents the apex (bare) domain)
+	// ("", "origin.") -> "origin." (not obvious)
+	// ("foo", "") -> "foo" (not obvious)
+
+	if dns.IsFqdn(s) {
+		return s // s is already a FQDN, no need to mess with it.
+	}
+	if len(origin) == 0 {
+		return s // Nothing to append.
+	}
+	if s == "@" || len(s) == 0 {
+		return origin // Expand apex.
+	}
+	if origin == "." {
+		return dns.Fqdn(s)
+	}
+
+	return s + "." + origin // The simple case.
+}
+
+// TrimDomainName trims origin from s if s is a subdomain.
+// This function will never return "", but returns "@" instead (@ represents the apex domain).
+func TrimDomainName(s, origin string) string {
+	// An apex (bare) domain is always returned as "@".
+	// If the return value ends in a ".", the domain was not the suffix.
+	// origin can end in "." or not. Either way the results should be the same.
+
+	if len(s) == 0 {
+		return "@"
+	}
+	// Someone is using TrimDomainName(s, ".") to remove a dot if it exists.
+	if origin == "." {
+		return strings.TrimSuffix(s, origin)
+	}
+
+	original := s
+	s = dns.Fqdn(s)
+	origin = dns.Fqdn(origin)
+
+	if !dns.IsSubDomain(origin, s) {
+		return original
+	}
+
+	slabels := dns.Split(s)
+	olabels := dns.Split(origin)
+	m := dns.CompareDomainName(s, origin)
+	if len(olabels) == m {
+		if len(olabels) == len(slabels) {
+			return "@" // origin == s
+		}
+		if (s[0] == '.') && (len(slabels) == (len(olabels) + 1)) {
+			return "@" // TrimDomainName(".foo.", "foo.")
+		}
+	}
+
+	// Return the first (len-m) labels:
+	return s[:slabels[len(slabels)-m]-1]
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnsutil/util_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnsutil/util_test.go
new file mode 100644
index 0000000..6754789
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dnsutil/util_test.go
@@ -0,0 +1,130 @@
+package dnsutil
+
+import "testing"
+
+func TestAddOrigin(t *testing.T) {
+	var tests = []struct{ e1, e2, expected string }{
+		{"@", "example.com", "example.com"},
+		{"foo", "example.com", "foo.example.com"},
+		{"foo.", "example.com", "foo."},
+		{"@", "example.com.", "example.com."},
+		{"foo", "example.com.", "foo.example.com."},
+		{"foo.", "example.com.", "foo."},
+		{"example.com", ".", "example.com."},
+		{"example.com.", ".", "example.com."},
+		// Oddball tests:
+		// In general origin should not be "" or "." but at least
+		// these tests verify we don't crash and will keep results
+		// from changing unexpectedly.
+		{"*.", "", "*."},
+		{"@", "", "@"},
+		{"foobar", "", "foobar"},
+		{"foobar.", "", "foobar."},
+		{"*.", ".", "*."},
+		{"@", ".", "."},
+		{"foobar", ".", "foobar."},
+		{"foobar.", ".", "foobar."},
+	}
+	for _, test := range tests {
+		actual := AddOrigin(test.e1, test.e2)
+		if test.expected != actual {
+			t.Errorf("AddOrigin(%#v, %#v) expected %#v, got %#v\n", test.e1, test.e2, test.expected, actual)
+		}
+	}
+}
+
+func TestTrimDomainName(t *testing.T) {
+	// Basic tests.
+	// Try trimming "example.com" and "example.com." from typical use cases.
+	testsEx := []struct{ experiment, expected string }{
+		{"foo.example.com", "foo"},
+		{"foo.example.com.", "foo"},
+		{".foo.example.com", ".foo"},
+		{".foo.example.com.", ".foo"},
+		{"*.example.com", "*"},
+		{"example.com", "@"},
+		{"example.com.", "@"},
+		{"com.", "com."},
+		{"foo.", "foo."},
+		{"serverfault.com.", "serverfault.com."},
+		{"serverfault.com", "serverfault.com"},
+		{".foo.ronco.com", ".foo.ronco.com"},
+		{".foo.ronco.com.", ".foo.ronco.com."},
+	}
+	for _, dom := range []string{"example.com", "example.com."} {
+		for i, test := range testsEx {
+			actual := TrimDomainName(test.experiment, dom)
+			if test.expected != actual {
+				t.Errorf("%d TrimDomainName(%#v, %#v): expected %v, got %v\n", i, test.experiment, dom, test.expected, actual)
+			}
+		}
+	}
+
+	// Paranoid tests.
+	// These test shouldn't be needed but I was weary of off-by-one errors.
+	// In theory, these can't happen because there are no single-letter TLDs,
+	// but it is good to exercize the code this way.
+	tests := []struct{ experiment, expected string }{
+		{"", "@"},
+		{".", "."},
+		{"a.b.c.d.e.f.", "a.b.c.d.e"},
+		{"b.c.d.e.f.", "b.c.d.e"},
+		{"c.d.e.f.", "c.d.e"},
+		{"d.e.f.", "d.e"},
+		{"e.f.", "e"},
+		{"f.", "@"},
+		{".a.b.c.d.e.f.", ".a.b.c.d.e"},
+		{".b.c.d.e.f.", ".b.c.d.e"},
+		{".c.d.e.f.", ".c.d.e"},
+		{".d.e.f.", ".d.e"},
+		{".e.f.", ".e"},
+		{".f.", "@"},
+		{"a.b.c.d.e.f", "a.b.c.d.e"},
+		{"a.b.c.d.e.", "a.b.c.d.e."},
+		{"a.b.c.d.e", "a.b.c.d.e"},
+		{"a.b.c.d.", "a.b.c.d."},
+		{"a.b.c.d", "a.b.c.d"},
+		{"a.b.c.", "a.b.c."},
+		{"a.b.c", "a.b.c"},
+		{"a.b.", "a.b."},
+		{"a.b", "a.b"},
+		{"a.", "a."},
+		{"a", "a"},
+		{".a.b.c.d.e.f", ".a.b.c.d.e"},
+		{".a.b.c.d.e.", ".a.b.c.d.e."},
+		{".a.b.c.d.e", ".a.b.c.d.e"},
+		{".a.b.c.d.", ".a.b.c.d."},
+		{".a.b.c.d", ".a.b.c.d"},
+		{".a.b.c.", ".a.b.c."},
+		{".a.b.c", ".a.b.c"},
+		{".a.b.", ".a.b."},
+		{".a.b", ".a.b"},
+		{".a.", ".a."},
+		{".a", ".a"},
+	}
+	for _, dom := range []string{"f", "f."} {
+		for i, test := range tests {
+			actual := TrimDomainName(test.experiment, dom)
+			if test.expected != actual {
+				t.Errorf("%d TrimDomainName(%#v, %#v): expected %v, got %v\n", i, test.experiment, dom, test.expected, actual)
+			}
+		}
+	}
+
+	// Test cases for bugs found in the wild.
+	// These test cases provide both origin, s, and the expected result.
+	// If you find a bug in the while, this is probably the easiest place
+	// to add it as a test case.
+	var testsWild = []struct{ e1, e2, expected string }{
+		{"mathoverflow.net.", ".", "mathoverflow.net"},
+		{"mathoverflow.net", ".", "mathoverflow.net"},
+		{"", ".", "@"},
+		{"@", ".", "@"},
+	}
+	for i, test := range testsWild {
+		actual := TrimDomainName(test.e1, test.e2)
+		if test.expected != actual {
+			t.Errorf("%d TrimDomainName(%#v, %#v): expected %v, got %v\n", i, test.e1, test.e2, test.expected, actual)
+		}
+	}
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/doc.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/doc.go
new file mode 100644
index 0000000..0389d72
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/doc.go
@@ -0,0 +1,272 @@
+/*
+Package dns implements a full featured interface to the Domain Name System.
+Server- and client-side programming is supported.
+The package allows complete control over what is sent out to the DNS. The package
+API follows the less-is-more principle, by presenting a small, clean interface.
+
+The package dns supports (asynchronous) querying/replying, incoming/outgoing zone transfers,
+TSIG, EDNS0, dynamic updates, notifies and DNSSEC validation/signing.
+Note that domain names MUST be fully qualified, before sending them, unqualified
+names in a message will result in a packing failure.
+
+Resource records are native types. They are not stored in wire format.
+Basic usage pattern for creating a new resource record:
+
+     r := new(dns.MX)
+     r.Hdr = dns.RR_Header{Name: "miek.nl.", Rrtype: dns.TypeMX,
+     Class: dns.ClassINET, Ttl: 3600}
+     r.Preference = 10
+     r.Mx = "mx.miek.nl."
+
+Or directly from a string:
+
+     mx, err := dns.NewRR("miek.nl. 3600 IN MX 10 mx.miek.nl.")
+
+Or when the default origin (.) and TTL (3600) and class (IN) suit you:
+
+     mx, err := dns.NewRR("miek.nl MX 10 mx.miek.nl")
+
+Or even:
+
+     mx, err := dns.NewRR("$ORIGIN nl.\nmiek 1H IN MX 10 mx.miek")
+
+In the DNS messages are exchanged, these messages contain resource
+records (sets). Use pattern for creating a message:
+
+     m := new(dns.Msg)
+     m.SetQuestion("miek.nl.", dns.TypeMX)
+
+Or when not certain if the domain name is fully qualified:
+
+	m.SetQuestion(dns.Fqdn("miek.nl"), dns.TypeMX)
+
+The message m is now a message with the question section set to ask
+the MX records for the miek.nl. zone.
+
+The following is slightly more verbose, but more flexible:
+
+     m1 := new(dns.Msg)
+     m1.Id = dns.Id()
+     m1.RecursionDesired = true
+     m1.Question = make([]dns.Question, 1)
+     m1.Question[0] = dns.Question{"miek.nl.", dns.TypeMX, dns.ClassINET}
+
+After creating a message it can be sent.
+Basic use pattern for synchronous querying the DNS at a
+server configured on 127.0.0.1 and port 53:
+
+     c := new(dns.Client)
+     in, rtt, err := c.Exchange(m1, "127.0.0.1:53")
+
+Suppressing multiple outstanding queries (with the same question, type and
+class) is as easy as setting:
+
+	c.SingleInflight = true
+
+More advanced options are available using a net.Dialer and the corresponding API.
+For example it is possible to set a timeout, or to specify a source IP address
+and port to use for the connection:
+
+	c := new(dns.Client)
+	laddr := net.UDPAddr{
+		IP: net.ParseIP("[::1]"),
+		Port: 12345,
+		Zone: "",
+	}
+	c.Dialer := &net.Dialer{
+		Timeout: 200 * time.Millisecond,
+		LocalAddr: &laddr,
+	}
+	in, rtt, err := c.Exchange(m1, "8.8.8.8:53")
+
+If these "advanced" features are not needed, a simple UDP query can be sent,
+with:
+
+	in, err := dns.Exchange(m1, "127.0.0.1:53")
+
+When this functions returns you will get dns message. A dns message consists
+out of four sections.
+The question section: in.Question, the answer section: in.Answer,
+the authority section: in.Ns and the additional section: in.Extra.
+
+Each of these sections (except the Question section) contain a []RR. Basic
+use pattern for accessing the rdata of a TXT RR as the first RR in
+the Answer section:
+
+	if t, ok := in.Answer[0].(*dns.TXT); ok {
+		// do something with t.Txt
+	}
+
+Domain Name and TXT Character String Representations
+
+Both domain names and TXT character strings are converted to presentation
+form both when unpacked and when converted to strings.
+
+For TXT character strings, tabs, carriage returns and line feeds will be
+converted to \t, \r and \n respectively. Back slashes and quotations marks
+will be escaped. Bytes below 32 and above 127 will be converted to \DDD
+form.
+
+For domain names, in addition to the above rules brackets, periods,
+spaces, semicolons and the at symbol are escaped.
+
+DNSSEC
+
+DNSSEC (DNS Security Extension) adds a layer of security to the DNS. It
+uses public key cryptography to sign resource records. The
+public keys are stored in DNSKEY records and the signatures in RRSIG records.
+
+Requesting DNSSEC information for a zone is done by adding the DO (DNSSEC OK) bit
+to a request.
+
+     m := new(dns.Msg)
+     m.SetEdns0(4096, true)
+
+Signature generation, signature verification and key generation are all supported.
+
+DYNAMIC UPDATES
+
+Dynamic updates reuses the DNS message format, but renames three of
+the sections. Question is Zone, Answer is Prerequisite, Authority is
+Update, only the Additional is not renamed. See RFC 2136 for the gory details.
+
+You can set a rather complex set of rules for the existence of absence of
+certain resource records or names in a zone to specify if resource records
+should be added or removed. The table from RFC 2136 supplemented with the Go
+DNS function shows which functions exist to specify the prerequisites.
+
+ 3.2.4 - Table Of Metavalues Used In Prerequisite Section
+
+  CLASS    TYPE     RDATA    Meaning                    Function
+  --------------------------------------------------------------
+  ANY      ANY      empty    Name is in use             dns.NameUsed
+  ANY      rrset    empty    RRset exists (value indep) dns.RRsetUsed
+  NONE     ANY      empty    Name is not in use         dns.NameNotUsed
+  NONE     rrset    empty    RRset does not exist       dns.RRsetNotUsed
+  zone     rrset    rr       RRset exists (value dep)   dns.Used
+
+The prerequisite section can also be left empty.
+If you have decided on the prerequisites you can tell what RRs should
+be added or deleted. The next table shows the options you have and
+what functions to call.
+
+ 3.4.2.6 - Table Of Metavalues Used In Update Section
+
+  CLASS    TYPE     RDATA    Meaning                     Function
+  ---------------------------------------------------------------
+  ANY      ANY      empty    Delete all RRsets from name dns.RemoveName
+  ANY      rrset    empty    Delete an RRset             dns.RemoveRRset
+  NONE     rrset    rr       Delete an RR from RRset     dns.Remove
+  zone     rrset    rr       Add to an RRset             dns.Insert
+
+TRANSACTION SIGNATURE
+
+An TSIG or transaction signature adds a HMAC TSIG record to each message sent.
+The supported algorithms include: HmacMD5, HmacSHA1, HmacSHA256 and HmacSHA512.
+
+Basic use pattern when querying with a TSIG name "axfr." (note that these key names
+must be fully qualified - as they are domain names) and the base64 secret
+"so6ZGir4GPAqINNh9U5c3A==":
+
+If an incoming message contains a TSIG record it MUST be the last record in
+the additional section (RFC2845 3.2).  This means that you should make the
+call to SetTsig last, right before executing the query.  If you make any
+changes to the RRset after calling SetTsig() the signature will be incorrect.
+
+	c := new(dns.Client)
+	c.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="}
+	m := new(dns.Msg)
+	m.SetQuestion("miek.nl.", dns.TypeMX)
+	m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix())
+	...
+	// When sending the TSIG RR is calculated and filled in before sending
+
+When requesting an zone transfer (almost all TSIG usage is when requesting zone transfers), with
+TSIG, this is the basic use pattern. In this example we request an AXFR for
+miek.nl. with TSIG key named "axfr." and secret "so6ZGir4GPAqINNh9U5c3A=="
+and using the server 176.58.119.54:
+
+	t := new(dns.Transfer)
+	m := new(dns.Msg)
+	t.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="}
+	m.SetAxfr("miek.nl.")
+	m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix())
+	c, err := t.In(m, "176.58.119.54:53")
+	for r := range c { ... }
+
+You can now read the records from the transfer as they come in. Each envelope is checked with TSIG.
+If something is not correct an error is returned.
+
+Basic use pattern validating and replying to a message that has TSIG set.
+
+	server := &dns.Server{Addr: ":53", Net: "udp"}
+	server.TsigSecret = map[string]string{"axfr.": "so6ZGir4GPAqINNh9U5c3A=="}
+	go server.ListenAndServe()
+	dns.HandleFunc(".", handleRequest)
+
+	func handleRequest(w dns.ResponseWriter, r *dns.Msg) {
+		m := new(dns.Msg)
+		m.SetReply(r)
+		if r.IsTsig() != nil {
+			if w.TsigStatus() == nil {
+				// *Msg r has an TSIG record and it was validated
+				m.SetTsig("axfr.", dns.HmacMD5, 300, time.Now().Unix())
+			} else {
+				// *Msg r has an TSIG records and it was not valided
+			}
+		}
+		w.WriteMsg(m)
+	}
+
+PRIVATE RRS
+
+RFC 6895 sets aside a range of type codes for private use. This range
+is 65,280 - 65,534 (0xFF00 - 0xFFFE). When experimenting with new Resource Records these
+can be used, before requesting an official type code from IANA.
+
+see http://miek.nl/2014/September/21/idn-and-private-rr-in-go-dns/ for more
+information.
+
+EDNS0
+
+EDNS0 is an extension mechanism for the DNS defined in RFC 2671 and updated
+by RFC 6891. It defines an new RR type, the OPT RR, which is then completely
+abused.
+Basic use pattern for creating an (empty) OPT RR:
+
+	o := new(dns.OPT)
+	o.Hdr.Name = "." // MUST be the root zone, per definition.
+	o.Hdr.Rrtype = dns.TypeOPT
+
+The rdata of an OPT RR consists out of a slice of EDNS0 (RFC 6891)
+interfaces. Currently only a few have been standardized: EDNS0_NSID
+(RFC 5001) and EDNS0_SUBNET (draft-vandergaast-edns-client-subnet-02). Note
+that these options may be combined in an OPT RR.
+Basic use pattern for a server to check if (and which) options are set:
+
+	// o is a dns.OPT
+	for _, s := range o.Option {
+		switch e := s.(type) {
+		case *dns.EDNS0_NSID:
+			// do stuff with e.Nsid
+		case *dns.EDNS0_SUBNET:
+			// access e.Family, e.Address, etc.
+		}
+	}
+
+SIG(0)
+
+From RFC 2931:
+
+    SIG(0) provides protection for DNS transactions and requests ....
+    ... protection for glue records, DNS requests, protection for message headers
+    on requests and responses, and protection of the overall integrity of a response.
+
+It works like TSIG, except that SIG(0) uses public key cryptography, instead of the shared
+secret approach in TSIG.
+Supported algorithms: DSA, ECDSAP256SHA256, ECDSAP384SHA384, RSASHA1, RSASHA256 and
+RSASHA512.
+
+Signing subsequent messages in multi-message sessions is not implemented.
+*/
+package dns
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dyn_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dyn_test.go
new file mode 100644
index 0000000..09986a5
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/dyn_test.go
@@ -0,0 +1,3 @@
+package dns
+
+// Find better solution
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/edns.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/edns.go
new file mode 100644
index 0000000..6f9d2ea
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/edns.go
@@ -0,0 +1,627 @@
+package dns
+
+import (
+	"encoding/binary"
+	"encoding/hex"
+	"errors"
+	"fmt"
+	"net"
+	"strconv"
+)
+
+// EDNS0 Option codes.
+const (
+	EDNS0LLQ          = 0x1     // long lived queries: http://tools.ietf.org/html/draft-sekar-dns-llq-01
+	EDNS0UL           = 0x2     // update lease draft: http://files.dns-sd.org/draft-sekar-dns-ul.txt
+	EDNS0NSID         = 0x3     // nsid (See RFC 5001)
+	EDNS0DAU          = 0x5     // DNSSEC Algorithm Understood
+	EDNS0DHU          = 0x6     // DS Hash Understood
+	EDNS0N3U          = 0x7     // NSEC3 Hash Understood
+	EDNS0SUBNET       = 0x8     // client-subnet (See RFC 7871)
+	EDNS0EXPIRE       = 0x9     // EDNS0 expire
+	EDNS0COOKIE       = 0xa     // EDNS0 Cookie
+	EDNS0TCPKEEPALIVE = 0xb     // EDNS0 tcp keep alive (See RFC 7828)
+	EDNS0PADDING      = 0xc     // EDNS0 padding (See RFC 7830)
+	EDNS0LOCALSTART   = 0xFDE9  // Beginning of range reserved for local/experimental use (See RFC 6891)
+	EDNS0LOCALEND     = 0xFFFE  // End of range reserved for local/experimental use (See RFC 6891)
+	_DO               = 1 << 15 // DNSSEC OK
+)
+
+// OPT is the EDNS0 RR appended to messages to convey extra (meta) information.
+// See RFC 6891.
+type OPT struct {
+	Hdr    RR_Header
+	Option []EDNS0 `dns:"opt"`
+}
+
+func (rr *OPT) String() string {
+	s := "\n;; OPT PSEUDOSECTION:\n; EDNS: version " + strconv.Itoa(int(rr.Version())) + "; "
+	if rr.Do() {
+		s += "flags: do; "
+	} else {
+		s += "flags: ; "
+	}
+	s += "udp: " + strconv.Itoa(int(rr.UDPSize()))
+
+	for _, o := range rr.Option {
+		switch o.(type) {
+		case *EDNS0_NSID:
+			s += "\n; NSID: " + o.String()
+			h, e := o.pack()
+			var r string
+			if e == nil {
+				for _, c := range h {
+					r += "(" + string(c) + ")"
+				}
+				s += "  " + r
+			}
+		case *EDNS0_SUBNET:
+			s += "\n; SUBNET: " + o.String()
+		case *EDNS0_COOKIE:
+			s += "\n; COOKIE: " + o.String()
+		case *EDNS0_UL:
+			s += "\n; UPDATE LEASE: " + o.String()
+		case *EDNS0_LLQ:
+			s += "\n; LONG LIVED QUERIES: " + o.String()
+		case *EDNS0_DAU:
+			s += "\n; DNSSEC ALGORITHM UNDERSTOOD: " + o.String()
+		case *EDNS0_DHU:
+			s += "\n; DS HASH UNDERSTOOD: " + o.String()
+		case *EDNS0_N3U:
+			s += "\n; NSEC3 HASH UNDERSTOOD: " + o.String()
+		case *EDNS0_LOCAL:
+			s += "\n; LOCAL OPT: " + o.String()
+		case *EDNS0_PADDING:
+			s += "\n; PADDING: " + o.String()
+		}
+	}
+	return s
+}
+
+func (rr *OPT) len() int {
+	l := rr.Hdr.len()
+	for i := 0; i < len(rr.Option); i++ {
+		l += 4 // Account for 2-byte option code and 2-byte option length.
+		lo, _ := rr.Option[i].pack()
+		l += len(lo)
+	}
+	return l
+}
+
+// return the old value -> delete SetVersion?
+
+// Version returns the EDNS version used. Only zero is defined.
+func (rr *OPT) Version() uint8 {
+	return uint8((rr.Hdr.Ttl & 0x00FF0000) >> 16)
+}
+
+// SetVersion sets the version of EDNS. This is usually zero.
+func (rr *OPT) SetVersion(v uint8) {
+	rr.Hdr.Ttl = rr.Hdr.Ttl&0xFF00FFFF | (uint32(v) << 16)
+}
+
+// ExtendedRcode returns the EDNS extended RCODE field (the upper 8 bits of the TTL).
+func (rr *OPT) ExtendedRcode() int {
+	return int((rr.Hdr.Ttl & 0xFF000000) >> 24)
+}
+
+// SetExtendedRcode sets the EDNS extended RCODE field.
+func (rr *OPT) SetExtendedRcode(v uint8) {
+	rr.Hdr.Ttl = rr.Hdr.Ttl&0x00FFFFFF | (uint32(v) << 24)
+}
+
+// UDPSize returns the UDP buffer size.
+func (rr *OPT) UDPSize() uint16 {
+	return rr.Hdr.Class
+}
+
+// SetUDPSize sets the UDP buffer size.
+func (rr *OPT) SetUDPSize(size uint16) {
+	rr.Hdr.Class = size
+}
+
+// Do returns the value of the DO (DNSSEC OK) bit.
+func (rr *OPT) Do() bool {
+	return rr.Hdr.Ttl&_DO == _DO
+}
+
+// SetDo sets the DO (DNSSEC OK) bit.
+// If we pass an argument, set the DO bit to that value.
+// It is possible to pass 2 or more arguments. Any arguments after the 1st is silently ignored.
+func (rr *OPT) SetDo(do ...bool) {
+	if len(do) == 1 {
+		if do[0] {
+			rr.Hdr.Ttl |= _DO
+		} else {
+			rr.Hdr.Ttl &^= _DO
+		}
+	} else {
+		rr.Hdr.Ttl |= _DO
+	}
+}
+
+// EDNS0 defines an EDNS0 Option. An OPT RR can have multiple options appended to it.
+type EDNS0 interface {
+	// Option returns the option code for the option.
+	Option() uint16
+	// pack returns the bytes of the option data.
+	pack() ([]byte, error)
+	// unpack sets the data as found in the buffer. Is also sets
+	// the length of the slice as the length of the option data.
+	unpack([]byte) error
+	// String returns the string representation of the option.
+	String() string
+}
+
+// EDNS0_NSID option is used to retrieve a nameserver
+// identifier. When sending a request Nsid must be set to the empty string
+// The identifier is an opaque string encoded as hex.
+// Basic use pattern for creating an nsid option:
+//
+//	o := new(dns.OPT)
+//	o.Hdr.Name = "."
+//	o.Hdr.Rrtype = dns.TypeOPT
+//	e := new(dns.EDNS0_NSID)
+//	e.Code = dns.EDNS0NSID
+//	e.Nsid = "AA"
+//	o.Option = append(o.Option, e)
+type EDNS0_NSID struct {
+	Code uint16 // Always EDNS0NSID
+	Nsid string // This string needs to be hex encoded
+}
+
+func (e *EDNS0_NSID) pack() ([]byte, error) {
+	h, err := hex.DecodeString(e.Nsid)
+	if err != nil {
+		return nil, err
+	}
+	return h, nil
+}
+
+// Option implements the EDNS0 interface.
+func (e *EDNS0_NSID) Option() uint16        { return EDNS0NSID } // Option returns the option code.
+func (e *EDNS0_NSID) unpack(b []byte) error { e.Nsid = hex.EncodeToString(b); return nil }
+func (e *EDNS0_NSID) String() string        { return string(e.Nsid) }
+
+// EDNS0_SUBNET is the subnet option that is used to give the remote nameserver
+// an idea of where the client lives. See RFC 7871. It can then give back a different
+// answer depending on the location or network topology.
+// Basic use pattern for creating an subnet option:
+//
+//	o := new(dns.OPT)
+//	o.Hdr.Name = "."
+//	o.Hdr.Rrtype = dns.TypeOPT
+//	e := new(dns.EDNS0_SUBNET)
+//	e.Code = dns.EDNS0SUBNET
+//	e.Family = 1	// 1 for IPv4 source address, 2 for IPv6
+//	e.SourceNetmask = 32	// 32 for IPV4, 128 for IPv6
+//	e.SourceScope = 0
+//	e.Address = net.ParseIP("127.0.0.1").To4()	// for IPv4
+//	// e.Address = net.ParseIP("2001:7b8:32a::2")	// for IPV6
+//	o.Option = append(o.Option, e)
+//
+// This code will parse all the available bits when unpacking (up to optlen).
+// When packing it will apply SourceNetmask. If you need more advanced logic,
+// patches welcome and good luck.
+type EDNS0_SUBNET struct {
+	Code          uint16 // Always EDNS0SUBNET
+	Family        uint16 // 1 for IP, 2 for IP6
+	SourceNetmask uint8
+	SourceScope   uint8
+	Address       net.IP
+}
+
+// Option implements the EDNS0 interface.
+func (e *EDNS0_SUBNET) Option() uint16 { return EDNS0SUBNET }
+
+func (e *EDNS0_SUBNET) pack() ([]byte, error) {
+	b := make([]byte, 4)
+	binary.BigEndian.PutUint16(b[0:], e.Family)
+	b[2] = e.SourceNetmask
+	b[3] = e.SourceScope
+	switch e.Family {
+	case 0:
+		// "dig" sets AddressFamily to 0 if SourceNetmask is also 0
+		// We might don't need to complain either
+		if e.SourceNetmask != 0 {
+			return nil, errors.New("dns: bad address family")
+		}
+	case 1:
+		if e.SourceNetmask > net.IPv4len*8 {
+			return nil, errors.New("dns: bad netmask")
+		}
+		if len(e.Address.To4()) != net.IPv4len {
+			return nil, errors.New("dns: bad address")
+		}
+		ip := e.Address.To4().Mask(net.CIDRMask(int(e.SourceNetmask), net.IPv4len*8))
+		needLength := (e.SourceNetmask + 8 - 1) / 8 // division rounding up
+		b = append(b, ip[:needLength]...)
+	case 2:
+		if e.SourceNetmask > net.IPv6len*8 {
+			return nil, errors.New("dns: bad netmask")
+		}
+		if len(e.Address) != net.IPv6len {
+			return nil, errors.New("dns: bad address")
+		}
+		ip := e.Address.Mask(net.CIDRMask(int(e.SourceNetmask), net.IPv6len*8))
+		needLength := (e.SourceNetmask + 8 - 1) / 8 // division rounding up
+		b = append(b, ip[:needLength]...)
+	default:
+		return nil, errors.New("dns: bad address family")
+	}
+	return b, nil
+}
+
+func (e *EDNS0_SUBNET) unpack(b []byte) error {
+	if len(b) < 4 {
+		return ErrBuf
+	}
+	e.Family = binary.BigEndian.Uint16(b)
+	e.SourceNetmask = b[2]
+	e.SourceScope = b[3]
+	switch e.Family {
+	case 0:
+		// "dig" sets AddressFamily to 0 if SourceNetmask is also 0
+		// It's okay to accept such a packet
+		if e.SourceNetmask != 0 {
+			return errors.New("dns: bad address family")
+		}
+		e.Address = net.IPv4(0, 0, 0, 0)
+	case 1:
+		if e.SourceNetmask > net.IPv4len*8 || e.SourceScope > net.IPv4len*8 {
+			return errors.New("dns: bad netmask")
+		}
+		addr := make([]byte, net.IPv4len)
+		for i := 0; i < net.IPv4len && 4+i < len(b); i++ {
+			addr[i] = b[4+i]
+		}
+		e.Address = net.IPv4(addr[0], addr[1], addr[2], addr[3])
+	case 2:
+		if e.SourceNetmask > net.IPv6len*8 || e.SourceScope > net.IPv6len*8 {
+			return errors.New("dns: bad netmask")
+		}
+		addr := make([]byte, net.IPv6len)
+		for i := 0; i < net.IPv6len && 4+i < len(b); i++ {
+			addr[i] = b[4+i]
+		}
+		e.Address = net.IP{addr[0], addr[1], addr[2], addr[3], addr[4],
+			addr[5], addr[6], addr[7], addr[8], addr[9], addr[10],
+			addr[11], addr[12], addr[13], addr[14], addr[15]}
+	default:
+		return errors.New("dns: bad address family")
+	}
+	return nil
+}
+
+func (e *EDNS0_SUBNET) String() (s string) {
+	if e.Address == nil {
+		s = "<nil>"
+	} else if e.Address.To4() != nil {
+		s = e.Address.String()
+	} else {
+		s = "[" + e.Address.String() + "]"
+	}
+	s += "/" + strconv.Itoa(int(e.SourceNetmask)) + "/" + strconv.Itoa(int(e.SourceScope))
+	return
+}
+
+// The EDNS0_COOKIE option is used to add a DNS Cookie to a message.
+//
+//	o := new(dns.OPT)
+//	o.Hdr.Name = "."
+//	o.Hdr.Rrtype = dns.TypeOPT
+//	e := new(dns.EDNS0_COOKIE)
+//	e.Code = dns.EDNS0COOKIE
+//	e.Cookie = "24a5ac.."
+//	o.Option = append(o.Option, e)
+//
+// The Cookie field consists out of a client cookie (RFC 7873 Section 4), that is
+// always 8 bytes. It may then optionally be followed by the server cookie. The server
+// cookie is of variable length, 8 to a maximum of 32 bytes. In other words:
+//
+//	cCookie := o.Cookie[:16]
+//	sCookie := o.Cookie[16:]
+//
+// There is no guarantee that the Cookie string has a specific length.
+type EDNS0_COOKIE struct {
+	Code   uint16 // Always EDNS0COOKIE
+	Cookie string // Hex-encoded cookie data
+}
+
+func (e *EDNS0_COOKIE) pack() ([]byte, error) {
+	h, err := hex.DecodeString(e.Cookie)
+	if err != nil {
+		return nil, err
+	}
+	return h, nil
+}
+
+// Option implements the EDNS0 interface.
+func (e *EDNS0_COOKIE) Option() uint16        { return EDNS0COOKIE }
+func (e *EDNS0_COOKIE) unpack(b []byte) error { e.Cookie = hex.EncodeToString(b); return nil }
+func (e *EDNS0_COOKIE) String() string        { return e.Cookie }
+
+// The EDNS0_UL (Update Lease) (draft RFC) option is used to tell the server to set
+// an expiration on an update RR. This is helpful for clients that cannot clean
+// up after themselves. This is a draft RFC and more information can be found at
+// http://files.dns-sd.org/draft-sekar-dns-ul.txt
+//
+//	o := new(dns.OPT)
+//	o.Hdr.Name = "."
+//	o.Hdr.Rrtype = dns.TypeOPT
+//	e := new(dns.EDNS0_UL)
+//	e.Code = dns.EDNS0UL
+//	e.Lease = 120 // in seconds
+//	o.Option = append(o.Option, e)
+type EDNS0_UL struct {
+	Code  uint16 // Always EDNS0UL
+	Lease uint32
+}
+
+// Option implements the EDNS0 interface.
+func (e *EDNS0_UL) Option() uint16 { return EDNS0UL }
+func (e *EDNS0_UL) String() string { return strconv.FormatUint(uint64(e.Lease), 10) }
+
+// Copied: http://golang.org/src/pkg/net/dnsmsg.go
+func (e *EDNS0_UL) pack() ([]byte, error) {
+	b := make([]byte, 4)
+	binary.BigEndian.PutUint32(b, e.Lease)
+	return b, nil
+}
+
+func (e *EDNS0_UL) unpack(b []byte) error {
+	if len(b) < 4 {
+		return ErrBuf
+	}
+	e.Lease = binary.BigEndian.Uint32(b)
+	return nil
+}
+
+// EDNS0_LLQ stands for Long Lived Queries: http://tools.ietf.org/html/draft-sekar-dns-llq-01
+// Implemented for completeness, as the EDNS0 type code is assigned.
+type EDNS0_LLQ struct {
+	Code      uint16 // Always EDNS0LLQ
+	Version   uint16
+	Opcode    uint16
+	Error     uint16
+	Id        uint64
+	LeaseLife uint32
+}
+
+// Option implements the EDNS0 interface.
+func (e *EDNS0_LLQ) Option() uint16 { return EDNS0LLQ }
+
+func (e *EDNS0_LLQ) pack() ([]byte, error) {
+	b := make([]byte, 18)
+	binary.BigEndian.PutUint16(b[0:], e.Version)
+	binary.BigEndian.PutUint16(b[2:], e.Opcode)
+	binary.BigEndian.PutUint16(b[4:], e.Error)
+	binary.BigEndian.PutUint64(b[6:], e.Id)
+	binary.BigEndian.PutUint32(b[14:], e.LeaseLife)
+	return b, nil
+}
+
+func (e *EDNS0_LLQ) unpack(b []byte) error {
+	if len(b) < 18 {
+		return ErrBuf
+	}
+	e.Version = binary.BigEndian.Uint16(b[0:])
+	e.Opcode = binary.BigEndian.Uint16(b[2:])
+	e.Error = binary.BigEndian.Uint16(b[4:])
+	e.Id = binary.BigEndian.Uint64(b[6:])
+	e.LeaseLife = binary.BigEndian.Uint32(b[14:])
+	return nil
+}
+
+func (e *EDNS0_LLQ) String() string {
+	s := strconv.FormatUint(uint64(e.Version), 10) + " " + strconv.FormatUint(uint64(e.Opcode), 10) +
+		" " + strconv.FormatUint(uint64(e.Error), 10) + " " + strconv.FormatUint(uint64(e.Id), 10) +
+		" " + strconv.FormatUint(uint64(e.LeaseLife), 10)
+	return s
+}
+
+// EDNS0_DUA implements the EDNS0 "DNSSEC Algorithm Understood" option. See RFC 6975.
+type EDNS0_DAU struct {
+	Code    uint16 // Always EDNS0DAU
+	AlgCode []uint8
+}
+
+// Option implements the EDNS0 interface.
+func (e *EDNS0_DAU) Option() uint16        { return EDNS0DAU }
+func (e *EDNS0_DAU) pack() ([]byte, error) { return e.AlgCode, nil }
+func (e *EDNS0_DAU) unpack(b []byte) error { e.AlgCode = b; return nil }
+
+func (e *EDNS0_DAU) String() string {
+	s := ""
+	for i := 0; i < len(e.AlgCode); i++ {
+		if a, ok := AlgorithmToString[e.AlgCode[i]]; ok {
+			s += " " + a
+		} else {
+			s += " " + strconv.Itoa(int(e.AlgCode[i]))
+		}
+	}
+	return s
+}
+
+// EDNS0_DHU implements the EDNS0 "DS Hash Understood" option. See RFC 6975.
+type EDNS0_DHU struct {
+	Code    uint16 // Always EDNS0DHU
+	AlgCode []uint8
+}
+
+// Option implements the EDNS0 interface.
+func (e *EDNS0_DHU) Option() uint16        { return EDNS0DHU }
+func (e *EDNS0_DHU) pack() ([]byte, error) { return e.AlgCode, nil }
+func (e *EDNS0_DHU) unpack(b []byte) error { e.AlgCode = b; return nil }
+
+func (e *EDNS0_DHU) String() string {
+	s := ""
+	for i := 0; i < len(e.AlgCode); i++ {
+		if a, ok := HashToString[e.AlgCode[i]]; ok {
+			s += " " + a
+		} else {
+			s += " " + strconv.Itoa(int(e.AlgCode[i]))
+		}
+	}
+	return s
+}
+
+// EDNS0_N3U implements the EDNS0 "NSEC3 Hash Understood" option. See RFC 6975.
+type EDNS0_N3U struct {
+	Code    uint16 // Always EDNS0N3U
+	AlgCode []uint8
+}
+
+// Option implements the EDNS0 interface.
+func (e *EDNS0_N3U) Option() uint16        { return EDNS0N3U }
+func (e *EDNS0_N3U) pack() ([]byte, error) { return e.AlgCode, nil }
+func (e *EDNS0_N3U) unpack(b []byte) error { e.AlgCode = b; return nil }
+
+func (e *EDNS0_N3U) String() string {
+	// Re-use the hash map
+	s := ""
+	for i := 0; i < len(e.AlgCode); i++ {
+		if a, ok := HashToString[e.AlgCode[i]]; ok {
+			s += " " + a
+		} else {
+			s += " " + strconv.Itoa(int(e.AlgCode[i]))
+		}
+	}
+	return s
+}
+
+// EDNS0_EXPIRE implementes the EDNS0 option as described in RFC 7314.
+type EDNS0_EXPIRE struct {
+	Code   uint16 // Always EDNS0EXPIRE
+	Expire uint32
+}
+
+// Option implements the EDNS0 interface.
+func (e *EDNS0_EXPIRE) Option() uint16 { return EDNS0EXPIRE }
+func (e *EDNS0_EXPIRE) String() string { return strconv.FormatUint(uint64(e.Expire), 10) }
+
+func (e *EDNS0_EXPIRE) pack() ([]byte, error) {
+	b := make([]byte, 4)
+	b[0] = byte(e.Expire >> 24)
+	b[1] = byte(e.Expire >> 16)
+	b[2] = byte(e.Expire >> 8)
+	b[3] = byte(e.Expire)
+	return b, nil
+}
+
+func (e *EDNS0_EXPIRE) unpack(b []byte) error {
+	if len(b) < 4 {
+		return ErrBuf
+	}
+	e.Expire = binary.BigEndian.Uint32(b)
+	return nil
+}
+
+// The EDNS0_LOCAL option is used for local/experimental purposes. The option
+// code is recommended to be within the range [EDNS0LOCALSTART, EDNS0LOCALEND]
+// (RFC6891), although any unassigned code can actually be used.  The content of
+// the option is made available in Data, unaltered.
+// Basic use pattern for creating a local option:
+//
+//	o := new(dns.OPT)
+//	o.Hdr.Name = "."
+//	o.Hdr.Rrtype = dns.TypeOPT
+//	e := new(dns.EDNS0_LOCAL)
+//	e.Code = dns.EDNS0LOCALSTART
+//	e.Data = []byte{72, 82, 74}
+//	o.Option = append(o.Option, e)
+type EDNS0_LOCAL struct {
+	Code uint16
+	Data []byte
+}
+
+// Option implements the EDNS0 interface.
+func (e *EDNS0_LOCAL) Option() uint16 { return e.Code }
+func (e *EDNS0_LOCAL) String() string {
+	return strconv.FormatInt(int64(e.Code), 10) + ":0x" + hex.EncodeToString(e.Data)
+}
+
+func (e *EDNS0_LOCAL) pack() ([]byte, error) {
+	b := make([]byte, len(e.Data))
+	copied := copy(b, e.Data)
+	if copied != len(e.Data) {
+		return nil, ErrBuf
+	}
+	return b, nil
+}
+
+func (e *EDNS0_LOCAL) unpack(b []byte) error {
+	e.Data = make([]byte, len(b))
+	copied := copy(e.Data, b)
+	if copied != len(b) {
+		return ErrBuf
+	}
+	return nil
+}
+
+// EDNS0_TCP_KEEPALIVE is an EDNS0 option that instructs the server to keep
+// the TCP connection alive. See RFC 7828.
+type EDNS0_TCP_KEEPALIVE struct {
+	Code    uint16 // Always EDNSTCPKEEPALIVE
+	Length  uint16 // the value 0 if the TIMEOUT is omitted, the value 2 if it is present;
+	Timeout uint16 // an idle timeout value for the TCP connection, specified in units of 100 milliseconds, encoded in network byte order.
+}
+
+// Option implements the EDNS0 interface.
+func (e *EDNS0_TCP_KEEPALIVE) Option() uint16 { return EDNS0TCPKEEPALIVE }
+
+func (e *EDNS0_TCP_KEEPALIVE) pack() ([]byte, error) {
+	if e.Timeout != 0 && e.Length != 2 {
+		return nil, errors.New("dns: timeout specified but length is not 2")
+	}
+	if e.Timeout == 0 && e.Length != 0 {
+		return nil, errors.New("dns: timeout not specified but length is not 0")
+	}
+	b := make([]byte, 4+e.Length)
+	binary.BigEndian.PutUint16(b[0:], e.Code)
+	binary.BigEndian.PutUint16(b[2:], e.Length)
+	if e.Length == 2 {
+		binary.BigEndian.PutUint16(b[4:], e.Timeout)
+	}
+	return b, nil
+}
+
+func (e *EDNS0_TCP_KEEPALIVE) unpack(b []byte) error {
+	if len(b) < 4 {
+		return ErrBuf
+	}
+	e.Length = binary.BigEndian.Uint16(b[2:4])
+	if e.Length != 0 && e.Length != 2 {
+		return errors.New("dns: length mismatch, want 0/2 but got " + strconv.FormatUint(uint64(e.Length), 10))
+	}
+	if e.Length == 2 {
+		if len(b) < 6 {
+			return ErrBuf
+		}
+		e.Timeout = binary.BigEndian.Uint16(b[4:6])
+	}
+	return nil
+}
+
+func (e *EDNS0_TCP_KEEPALIVE) String() (s string) {
+	s = "use tcp keep-alive"
+	if e.Length == 0 {
+		s += ", timeout omitted"
+	} else {
+		s += fmt.Sprintf(", timeout %dms", e.Timeout*100)
+	}
+	return
+}
+
+// EDNS0_PADDING option is used to add padding to a request/response. The default
+// value of padding SHOULD be 0x0 but other values MAY be used, for instance if
+// compression is applied before encryption which may break signatures.
+type EDNS0_PADDING struct {
+	Padding []byte
+}
+
+// Option implements the EDNS0 interface.
+func (e *EDNS0_PADDING) Option() uint16        { return EDNS0PADDING }
+func (e *EDNS0_PADDING) pack() ([]byte, error) { return e.Padding, nil }
+func (e *EDNS0_PADDING) unpack(b []byte) error { e.Padding = b; return nil }
+func (e *EDNS0_PADDING) String() string        { return fmt.Sprintf("%0X", e.Padding) }
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/edns_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/edns_test.go
new file mode 100644
index 0000000..f7cf157
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/edns_test.go
@@ -0,0 +1,68 @@
+package dns
+
+import "testing"
+
+func TestOPTTtl(t *testing.T) {
+	e := &OPT{}
+	e.Hdr.Name = "."
+	e.Hdr.Rrtype = TypeOPT
+
+	// verify the default setting of DO=0
+	if e.Do() {
+		t.Errorf("DO bit should be zero")
+	}
+
+	// There are 6 possible invocations of SetDo():
+	//
+	// 1. Starting with DO=0, using SetDo()
+	// 2. Starting with DO=0, using SetDo(true)
+	// 3. Starting with DO=0, using SetDo(false)
+	// 4. Starting with DO=1, using SetDo()
+	// 5. Starting with DO=1, using SetDo(true)
+	// 6. Starting with DO=1, using SetDo(false)
+
+	// verify that invoking SetDo() sets DO=1 (TEST #1)
+	e.SetDo()
+	if !e.Do() {
+		t.Errorf("DO bit should be non-zero")
+	}
+	// verify that using SetDo(true) works when DO=1 (TEST #5)
+	e.SetDo(true)
+	if !e.Do() {
+		t.Errorf("DO bit should still be non-zero")
+	}
+	// verify that we can use SetDo(false) to set DO=0 (TEST #6)
+	e.SetDo(false)
+	if e.Do() {
+		t.Errorf("DO bit should be zero")
+	}
+	// verify that if we call SetDo(false) when DO=0 that it is unchanged (TEST #3)
+	e.SetDo(false)
+	if e.Do() {
+		t.Errorf("DO bit should still be zero")
+	}
+	// verify that using SetDo(true) works for DO=0 (TEST #2)
+	e.SetDo(true)
+	if !e.Do() {
+		t.Errorf("DO bit should be non-zero")
+	}
+	// verify that using SetDo() works for DO=1 (TEST #4)
+	e.SetDo()
+	if !e.Do() {
+		t.Errorf("DO bit should be non-zero")
+	}
+
+	if e.Version() != 0 {
+		t.Errorf("version should be non-zero")
+	}
+
+	e.SetVersion(42)
+	if e.Version() != 42 {
+		t.Errorf("set 42, expected %d, got %d", 42, e.Version())
+	}
+
+	e.SetExtendedRcode(42)
+	if e.ExtendedRcode() != 42 {
+		t.Errorf("set 42, expected %d, got %d", 42, e.ExtendedRcode())
+	}
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/example_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/example_test.go
new file mode 100644
index 0000000..64c1496
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/example_test.go
@@ -0,0 +1,146 @@
+package dns_test
+
+import (
+	"errors"
+	"fmt"
+	"log"
+	"net"
+
+	"github.com/miekg/dns"
+)
+
+// Retrieve the MX records for miek.nl.
+func ExampleMX() {
+	config, _ := dns.ClientConfigFromFile("/etc/resolv.conf")
+	c := new(dns.Client)
+	m := new(dns.Msg)
+	m.SetQuestion("miek.nl.", dns.TypeMX)
+	m.RecursionDesired = true
+	r, _, err := c.Exchange(m, config.Servers[0]+":"+config.Port)
+	if err != nil {
+		return
+	}
+	if r.Rcode != dns.RcodeSuccess {
+		return
+	}
+	for _, a := range r.Answer {
+		if mx, ok := a.(*dns.MX); ok {
+			fmt.Printf("%s\n", mx.String())
+		}
+	}
+}
+
+// Retrieve the DNSKEY records of a zone and convert them
+// to DS records for SHA1, SHA256 and SHA384.
+func ExampleDS() {
+	config, _ := dns.ClientConfigFromFile("/etc/resolv.conf")
+	c := new(dns.Client)
+	m := new(dns.Msg)
+	zone := "miek.nl"
+	m.SetQuestion(dns.Fqdn(zone), dns.TypeDNSKEY)
+	m.SetEdns0(4096, true)
+	r, _, err := c.Exchange(m, config.Servers[0]+":"+config.Port)
+	if err != nil {
+		return
+	}
+	if r.Rcode != dns.RcodeSuccess {
+		return
+	}
+	for _, k := range r.Answer {
+		if key, ok := k.(*dns.DNSKEY); ok {
+			for _, alg := range []uint8{dns.SHA1, dns.SHA256, dns.SHA384} {
+				fmt.Printf("%s; %d\n", key.ToDS(alg).String(), key.Flags)
+			}
+		}
+	}
+}
+
+const TypeAPAIR = 0x0F99
+
+type APAIR struct {
+	addr [2]net.IP
+}
+
+func NewAPAIR() dns.PrivateRdata { return new(APAIR) }
+
+func (rd *APAIR) String() string { return rd.addr[0].String() + " " + rd.addr[1].String() }
+func (rd *APAIR) Parse(txt []string) error {
+	if len(txt) != 2 {
+		return errors.New("two addresses required for APAIR")
+	}
+	for i, s := range txt {
+		ip := net.ParseIP(s)
+		if ip == nil {
+			return errors.New("invalid IP in APAIR text representation")
+		}
+		rd.addr[i] = ip
+	}
+	return nil
+}
+
+func (rd *APAIR) Pack(buf []byte) (int, error) {
+	b := append([]byte(rd.addr[0]), []byte(rd.addr[1])...)
+	n := copy(buf, b)
+	if n != len(b) {
+		return n, dns.ErrBuf
+	}
+	return n, nil
+}
+
+func (rd *APAIR) Unpack(buf []byte) (int, error) {
+	ln := net.IPv4len * 2
+	if len(buf) != ln {
+		return 0, errors.New("invalid length of APAIR rdata")
+	}
+	cp := make([]byte, ln)
+	copy(cp, buf) // clone bytes to use them in IPs
+
+	rd.addr[0] = net.IP(cp[:3])
+	rd.addr[1] = net.IP(cp[4:])
+
+	return len(buf), nil
+}
+
+func (rd *APAIR) Copy(dest dns.PrivateRdata) error {
+	cp := make([]byte, rd.Len())
+	_, err := rd.Pack(cp)
+	if err != nil {
+		return err
+	}
+
+	d := dest.(*APAIR)
+	d.addr[0] = net.IP(cp[:3])
+	d.addr[1] = net.IP(cp[4:])
+	return nil
+}
+
+func (rd *APAIR) Len() int {
+	return net.IPv4len * 2
+}
+
+func ExamplePrivateHandle() {
+	dns.PrivateHandle("APAIR", TypeAPAIR, NewAPAIR)
+	defer dns.PrivateHandleRemove(TypeAPAIR)
+
+	rr, err := dns.NewRR("miek.nl. APAIR (1.2.3.4    1.2.3.5)")
+	if err != nil {
+		log.Fatal("could not parse APAIR record: ", err)
+	}
+	fmt.Println(rr)
+	// Output: miek.nl.	3600	IN	APAIR	1.2.3.4 1.2.3.5
+
+	m := new(dns.Msg)
+	m.Id = 12345
+	m.SetQuestion("miek.nl.", TypeAPAIR)
+	m.Answer = append(m.Answer, rr)
+
+	fmt.Println(m)
+	// ;; opcode: QUERY, status: NOERROR, id: 12345
+	// ;; flags: rd; QUERY: 1, ANSWER: 1, AUTHORITY: 0, ADDITIONAL: 0
+	//
+	// ;; QUESTION SECTION:
+	// ;miek.nl.	IN	 APAIR
+	//
+	// ;; ANSWER SECTION:
+	// miek.nl.	3600	IN	APAIR	1.2.3.4 1.2.3.5
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/format.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/format.go
new file mode 100644
index 0000000..3f5303c
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/format.go
@@ -0,0 +1,87 @@
+package dns
+
+import (
+	"net"
+	"reflect"
+	"strconv"
+)
+
+// NumField returns the number of rdata fields r has.
+func NumField(r RR) int {
+	return reflect.ValueOf(r).Elem().NumField() - 1 // Remove RR_Header
+}
+
+// Field returns the rdata field i as a string. Fields are indexed starting from 1.
+// RR types that holds slice data, for instance the NSEC type bitmap will return a single
+// string where the types are concatenated using a space.
+// Accessing non existing fields will cause a panic.
+func Field(r RR, i int) string {
+	if i == 0 {
+		return ""
+	}
+	d := reflect.ValueOf(r).Elem().Field(i)
+	switch k := d.Kind(); k {
+	case reflect.String:
+		return d.String()
+	case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
+		return strconv.FormatInt(d.Int(), 10)
+	case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
+		return strconv.FormatUint(d.Uint(), 10)
+	case reflect.Slice:
+		switch reflect.ValueOf(r).Elem().Type().Field(i).Tag {
+		case `dns:"a"`:
+			// TODO(miek): Hmm store this as 16 bytes
+			if d.Len() < net.IPv6len {
+				return net.IPv4(byte(d.Index(0).Uint()),
+					byte(d.Index(1).Uint()),
+					byte(d.Index(2).Uint()),
+					byte(d.Index(3).Uint())).String()
+			}
+			return net.IPv4(byte(d.Index(12).Uint()),
+				byte(d.Index(13).Uint()),
+				byte(d.Index(14).Uint()),
+				byte(d.Index(15).Uint())).String()
+		case `dns:"aaaa"`:
+			return net.IP{
+				byte(d.Index(0).Uint()),
+				byte(d.Index(1).Uint()),
+				byte(d.Index(2).Uint()),
+				byte(d.Index(3).Uint()),
+				byte(d.Index(4).Uint()),
+				byte(d.Index(5).Uint()),
+				byte(d.Index(6).Uint()),
+				byte(d.Index(7).Uint()),
+				byte(d.Index(8).Uint()),
+				byte(d.Index(9).Uint()),
+				byte(d.Index(10).Uint()),
+				byte(d.Index(11).Uint()),
+				byte(d.Index(12).Uint()),
+				byte(d.Index(13).Uint()),
+				byte(d.Index(14).Uint()),
+				byte(d.Index(15).Uint()),
+			}.String()
+		case `dns:"nsec"`:
+			if d.Len() == 0 {
+				return ""
+			}
+			s := Type(d.Index(0).Uint()).String()
+			for i := 1; i < d.Len(); i++ {
+				s += " " + Type(d.Index(i).Uint()).String()
+			}
+			return s
+		default:
+			// if it does not have a tag its a string slice
+			fallthrough
+		case `dns:"txt"`:
+			if d.Len() == 0 {
+				return ""
+			}
+			s := d.Index(0).String()
+			for i := 1; i < d.Len(); i++ {
+				s += " " + d.Index(i).String()
+			}
+			return s
+		}
+	}
+	return ""
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/fuzz.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/fuzz.go
new file mode 100644
index 0000000..a8a0918
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/fuzz.go
@@ -0,0 +1,23 @@
+// +build fuzz
+
+package dns
+
+func Fuzz(data []byte) int {
+	msg := new(Msg)
+
+	if err := msg.Unpack(data); err != nil {
+		return 0
+	}
+	if _, err := msg.Pack(); err != nil {
+		return 0
+	}
+
+	return 1
+}
+
+func FuzzNewRR(data []byte) int {
+	if _, err := NewRR(string(data)); err != nil {
+		return 0
+	}
+	return 1
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/generate.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/generate.go
new file mode 100644
index 0000000..e4481a4
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/generate.go
@@ -0,0 +1,159 @@
+package dns
+
+import (
+	"bytes"
+	"errors"
+	"fmt"
+	"strconv"
+	"strings"
+)
+
+// Parse the $GENERATE statement as used in BIND9 zones.
+// See http://www.zytrax.com/books/dns/ch8/generate.html for instance.
+// We are called after '$GENERATE '. After which we expect:
+// * the range (12-24/2)
+// * lhs (ownername)
+// * [[ttl][class]]
+// * type
+// * rhs (rdata)
+// But we are lazy here, only the range is parsed *all* occurrences
+// of $ after that are interpreted.
+// Any error are returned as a string value, the empty string signals
+// "no error".
+func generate(l lex, c chan lex, t chan *Token, o string) string {
+	step := 1
+	if i := strings.IndexAny(l.token, "/"); i != -1 {
+		if i+1 == len(l.token) {
+			return "bad step in $GENERATE range"
+		}
+		if s, err := strconv.Atoi(l.token[i+1:]); err == nil {
+			if s < 0 {
+				return "bad step in $GENERATE range"
+			}
+			step = s
+		} else {
+			return "bad step in $GENERATE range"
+		}
+		l.token = l.token[:i]
+	}
+	sx := strings.SplitN(l.token, "-", 2)
+	if len(sx) != 2 {
+		return "bad start-stop in $GENERATE range"
+	}
+	start, err := strconv.Atoi(sx[0])
+	if err != nil {
+		return "bad start in $GENERATE range"
+	}
+	end, err := strconv.Atoi(sx[1])
+	if err != nil {
+		return "bad stop in $GENERATE range"
+	}
+	if end < 0 || start < 0 || end < start {
+		return "bad range in $GENERATE range"
+	}
+
+	<-c // _BLANK
+	// Create a complete new string, which we then parse again.
+	s := ""
+BuildRR:
+	l = <-c
+	if l.value != zNewline && l.value != zEOF {
+		s += l.token
+		goto BuildRR
+	}
+	for i := start; i <= end; i += step {
+		var (
+			escape bool
+			dom    bytes.Buffer
+			mod    string
+			err    error
+			offset int
+		)
+
+		for j := 0; j < len(s); j++ { // No 'range' because we need to jump around
+			switch s[j] {
+			case '\\':
+				if escape {
+					dom.WriteByte('\\')
+					escape = false
+					continue
+				}
+				escape = true
+			case '$':
+				mod = "%d"
+				offset = 0
+				if escape {
+					dom.WriteByte('$')
+					escape = false
+					continue
+				}
+				escape = false
+				if j+1 >= len(s) { // End of the string
+					dom.WriteString(fmt.Sprintf(mod, i+offset))
+					continue
+				} else {
+					if s[j+1] == '$' {
+						dom.WriteByte('$')
+						j++
+						continue
+					}
+				}
+				// Search for { and }
+				if s[j+1] == '{' { // Modifier block
+					sep := strings.Index(s[j+2:], "}")
+					if sep == -1 {
+						return "bad modifier in $GENERATE"
+					}
+					mod, offset, err = modToPrintf(s[j+2 : j+2+sep])
+					if err != nil {
+						return err.Error()
+					}
+					j += 2 + sep // Jump to it
+				}
+				dom.WriteString(fmt.Sprintf(mod, i+offset))
+			default:
+				if escape { // Pretty useless here
+					escape = false
+					continue
+				}
+				dom.WriteByte(s[j])
+			}
+		}
+		// Re-parse the RR and send it on the current channel t
+		rx, err := NewRR("$ORIGIN " + o + "\n" + dom.String())
+		if err != nil {
+			return err.Error()
+		}
+		t <- &Token{RR: rx}
+		// Its more efficient to first built the rrlist and then parse it in
+		// one go! But is this a problem?
+	}
+	return ""
+}
+
+// Convert a $GENERATE modifier 0,0,d to something Printf can deal with.
+func modToPrintf(s string) (string, int, error) {
+	xs := strings.SplitN(s, ",", 3)
+	if len(xs) != 3 {
+		return "", 0, errors.New("bad modifier in $GENERATE")
+	}
+	// xs[0] is offset, xs[1] is width, xs[2] is base
+	if xs[2] != "o" && xs[2] != "d" && xs[2] != "x" && xs[2] != "X" {
+		return "", 0, errors.New("bad base in $GENERATE")
+	}
+	offset, err := strconv.Atoi(xs[0])
+	if err != nil || offset > 255 {
+		return "", 0, errors.New("bad offset in $GENERATE")
+	}
+	width, err := strconv.Atoi(xs[1])
+	if err != nil || width > 255 {
+		return "", offset, errors.New("bad width in $GENERATE")
+	}
+	switch {
+	case width < 0:
+		return "", offset, errors.New("bad width in $GENERATE")
+	case width == 0:
+		return "%" + xs[1] + xs[2], offset, nil
+	}
+	return "%0" + xs[1] + xs[2], offset, nil
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/issue_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/issue_test.go
new file mode 100644
index 0000000..7299d31
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/issue_test.go
@@ -0,0 +1,62 @@
+package dns
+
+// Tests that solve that an specific issue.
+
+import (
+	"strings"
+	"testing"
+)
+
+func TestTCPRtt(t *testing.T) {
+	m := new(Msg)
+	m.RecursionDesired = true
+	m.SetQuestion("example.org.", TypeA)
+
+	c := &Client{}
+	for _, proto := range []string{"udp", "tcp"} {
+		c.Net = proto
+		_, rtt, err := c.Exchange(m, "8.8.4.4:53")
+		if err != nil {
+			t.Fatal(err)
+		}
+		if rtt == 0 {
+			t.Fatalf("expecting non zero rtt %s, got zero", c.Net)
+		}
+	}
+}
+
+func TestNSEC3MissingSalt(t *testing.T) {
+	rr := testRR("ji6neoaepv8b5o6k4ev33abha8ht9fgc.example. NSEC3 1 1 12 aabbccdd K8UDEMVP1J2F7EG6JEBPS17VP3N8I58H")
+	m := new(Msg)
+	m.Answer = []RR{rr}
+	mb, err := m.Pack()
+	if err != nil {
+		t.Fatalf("expected to pack message. err: %s", err)
+	}
+	if err := m.Unpack(mb); err != nil {
+		t.Fatalf("expected to unpack message. missing salt? err: %s", err)
+	}
+	in := rr.(*NSEC3).Salt
+	out := m.Answer[0].(*NSEC3).Salt
+	if in != out {
+		t.Fatalf("expected salts to match. packed: `%s`. returned: `%s`", in, out)
+	}
+}
+
+func TestNSEC3MixedNextDomain(t *testing.T) {
+	rr := testRR("ji6neoaepv8b5o6k4ev33abha8ht9fgc.example. NSEC3 1 1 12 - k8udemvp1j2f7eg6jebps17vp3n8i58h")
+	m := new(Msg)
+	m.Answer = []RR{rr}
+	mb, err := m.Pack()
+	if err != nil {
+		t.Fatalf("expected to pack message. err: %s", err)
+	}
+	if err := m.Unpack(mb); err != nil {
+		t.Fatalf("expected to unpack message. err: %s", err)
+	}
+	in := strings.ToUpper(rr.(*NSEC3).NextDomain)
+	out := m.Answer[0].(*NSEC3).NextDomain
+	if in != out {
+		t.Fatalf("expected round trip to produce NextDomain `%s`, instead `%s`", in, out)
+	}
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/labels.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/labels.go
new file mode 100644
index 0000000..760b89e
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/labels.go
@@ -0,0 +1,191 @@
+package dns
+
+// Holds a bunch of helper functions for dealing with labels.
+
+// SplitDomainName splits a name string into it's labels.
+// www.miek.nl. returns []string{"www", "miek", "nl"}
+// .www.miek.nl. returns []string{"", "www", "miek", "nl"},
+// The root label (.) returns nil. Note that using
+// strings.Split(s) will work in most cases, but does not handle
+// escaped dots (\.) for instance.
+// s must be a syntactically valid domain name, see IsDomainName.
+func SplitDomainName(s string) (labels []string) {
+	if len(s) == 0 {
+		return nil
+	}
+	fqdnEnd := 0 // offset of the final '.' or the length of the name
+	idx := Split(s)
+	begin := 0
+	if s[len(s)-1] == '.' {
+		fqdnEnd = len(s) - 1
+	} else {
+		fqdnEnd = len(s)
+	}
+
+	switch len(idx) {
+	case 0:
+		return nil
+	case 1:
+		// no-op
+	default:
+		end := 0
+		for i := 1; i < len(idx); i++ {
+			end = idx[i]
+			labels = append(labels, s[begin:end-1])
+			begin = end
+		}
+	}
+
+	labels = append(labels, s[begin:fqdnEnd])
+	return labels
+}
+
+// CompareDomainName compares the names s1 and s2 and
+// returns how many labels they have in common starting from the *right*.
+// The comparison stops at the first inequality. The names are downcased
+// before the comparison.
+//
+// www.miek.nl. and miek.nl. have two labels in common: miek and nl
+// www.miek.nl. and www.bla.nl. have one label in common: nl
+//
+// s1 and s2 must be syntactically valid domain names.
+func CompareDomainName(s1, s2 string) (n int) {
+	// the first check: root label
+	if s1 == "." || s2 == "." {
+		return 0
+	}
+
+	l1 := Split(s1)
+	l2 := Split(s2)
+
+	j1 := len(l1) - 1 // end
+	i1 := len(l1) - 2 // start
+	j2 := len(l2) - 1
+	i2 := len(l2) - 2
+	// the second check can be done here: last/only label
+	// before we fall through into the for-loop below
+	if equal(s1[l1[j1]:], s2[l2[j2]:]) {
+		n++
+	} else {
+		return
+	}
+	for {
+		if i1 < 0 || i2 < 0 {
+			break
+		}
+		if equal(s1[l1[i1]:l1[j1]], s2[l2[i2]:l2[j2]]) {
+			n++
+		} else {
+			break
+		}
+		j1--
+		i1--
+		j2--
+		i2--
+	}
+	return
+}
+
+// CountLabel counts the the number of labels in the string s.
+// s must be a syntactically valid domain name.
+func CountLabel(s string) (labels int) {
+	if s == "." {
+		return
+	}
+	off := 0
+	end := false
+	for {
+		off, end = NextLabel(s, off)
+		labels++
+		if end {
+			return
+		}
+	}
+}
+
+// Split splits a name s into its label indexes.
+// www.miek.nl. returns []int{0, 4, 9}, www.miek.nl also returns []int{0, 4, 9}.
+// The root name (.) returns nil. Also see SplitDomainName.
+// s must be a syntactically valid domain name.
+func Split(s string) []int {
+	if s == "." {
+		return nil
+	}
+	idx := make([]int, 1, 3)
+	off := 0
+	end := false
+
+	for {
+		off, end = NextLabel(s, off)
+		if end {
+			return idx
+		}
+		idx = append(idx, off)
+	}
+}
+
+// NextLabel returns the index of the start of the next label in the
+// string s starting at offset.
+// The bool end is true when the end of the string has been reached.
+// Also see PrevLabel.
+func NextLabel(s string, offset int) (i int, end bool) {
+	quote := false
+	for i = offset; i < len(s)-1; i++ {
+		switch s[i] {
+		case '\\':
+			quote = !quote
+		default:
+			quote = false
+		case '.':
+			if quote {
+				quote = !quote
+				continue
+			}
+			return i + 1, false
+		}
+	}
+	return i + 1, true
+}
+
+// PrevLabel returns the index of the label when starting from the right and
+// jumping n labels to the left.
+// The bool start is true when the start of the string has been overshot.
+// Also see NextLabel.
+func PrevLabel(s string, n int) (i int, start bool) {
+	if n == 0 {
+		return len(s), false
+	}
+	lab := Split(s)
+	if lab == nil {
+		return 0, true
+	}
+	if n > len(lab) {
+		return 0, true
+	}
+	return lab[len(lab)-n], false
+}
+
+// equal compares a and b while ignoring case. It returns true when equal otherwise false.
+func equal(a, b string) bool {
+	// might be lifted into API function.
+	la := len(a)
+	lb := len(b)
+	if la != lb {
+		return false
+	}
+
+	for i := la - 1; i >= 0; i-- {
+		ai := a[i]
+		bi := b[i]
+		if ai >= 'A' && ai <= 'Z' {
+			ai |= ('a' - 'A')
+		}
+		if bi >= 'A' && bi <= 'Z' {
+			bi |= ('a' - 'A')
+		}
+		if ai != bi {
+			return false
+		}
+	}
+	return true
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/labels_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/labels_test.go
new file mode 100644
index 0000000..d9bb556
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/labels_test.go
@@ -0,0 +1,201 @@
+package dns
+
+import "testing"
+
+func TestCompareDomainName(t *testing.T) {
+	s1 := "www.miek.nl."
+	s2 := "miek.nl."
+	s3 := "www.bla.nl."
+	s4 := "nl.www.bla."
+	s5 := "nl."
+	s6 := "miek.nl."
+
+	if CompareDomainName(s1, s2) != 2 {
+		t.Errorf("%s with %s should be %d", s1, s2, 2)
+	}
+	if CompareDomainName(s1, s3) != 1 {
+		t.Errorf("%s with %s should be %d", s1, s3, 1)
+	}
+	if CompareDomainName(s3, s4) != 0 {
+		t.Errorf("%s with %s should be %d", s3, s4, 0)
+	}
+	// Non qualified tests
+	if CompareDomainName(s1, s5) != 1 {
+		t.Errorf("%s with %s should be %d", s1, s5, 1)
+	}
+	if CompareDomainName(s1, s6) != 2 {
+		t.Errorf("%s with %s should be %d", s1, s5, 2)
+	}
+
+	if CompareDomainName(s1, ".") != 0 {
+		t.Errorf("%s with %s should be %d", s1, s5, 0)
+	}
+	if CompareDomainName(".", ".") != 0 {
+		t.Errorf("%s with %s should be %d", ".", ".", 0)
+	}
+	if CompareDomainName("test.com.", "TEST.COM.") != 2 {
+		t.Errorf("test.com. and TEST.COM. should be an exact match")
+	}
+}
+
+func TestSplit(t *testing.T) {
+	splitter := map[string]int{
+		"www.miek.nl.":   3,
+		"www.miek.nl":    3,
+		"www..miek.nl":   4,
+		`www\.miek.nl.`:  2,
+		`www\\.miek.nl.`: 3,
+		".":              0,
+		"nl.":            1,
+		"nl":             1,
+		"com.":           1,
+		".com.":          2,
+	}
+	for s, i := range splitter {
+		if x := len(Split(s)); x != i {
+			t.Errorf("labels should be %d, got %d: %s %v", i, x, s, Split(s))
+		}
+	}
+}
+
+func TestSplit2(t *testing.T) {
+	splitter := map[string][]int{
+		"www.miek.nl.": {0, 4, 9},
+		"www.miek.nl":  {0, 4, 9},
+		"nl":           {0},
+	}
+	for s, i := range splitter {
+		x := Split(s)
+		switch len(i) {
+		case 1:
+			if x[0] != i[0] {
+				t.Errorf("labels should be %v, got %v: %s", i, x, s)
+			}
+		default:
+			if x[0] != i[0] || x[1] != i[1] || x[2] != i[2] {
+				t.Errorf("labels should be %v, got %v: %s", i, x, s)
+			}
+		}
+	}
+}
+
+func TestPrevLabel(t *testing.T) {
+	type prev struct {
+		string
+		int
+	}
+	prever := map[prev]int{
+		{"www.miek.nl.", 0}: 12,
+		{"www.miek.nl.", 1}: 9,
+		{"www.miek.nl.", 2}: 4,
+
+		{"www.miek.nl", 0}: 11,
+		{"www.miek.nl", 1}: 9,
+		{"www.miek.nl", 2}: 4,
+
+		{"www.miek.nl.", 5}: 0,
+		{"www.miek.nl", 5}:  0,
+
+		{"www.miek.nl.", 3}: 0,
+		{"www.miek.nl", 3}:  0,
+	}
+	for s, i := range prever {
+		x, ok := PrevLabel(s.string, s.int)
+		if i != x {
+			t.Errorf("label should be %d, got %d, %t: preving %d, %s", i, x, ok, s.int, s.string)
+		}
+	}
+}
+
+func TestCountLabel(t *testing.T) {
+	splitter := map[string]int{
+		"www.miek.nl.": 3,
+		"www.miek.nl":  3,
+		"nl":           1,
+		".":            0,
+	}
+	for s, i := range splitter {
+		x := CountLabel(s)
+		if x != i {
+			t.Errorf("CountLabel should have %d, got %d", i, x)
+		}
+	}
+}
+
+func TestSplitDomainName(t *testing.T) {
+	labels := map[string][]string{
+		"miek.nl":       {"miek", "nl"},
+		".":             nil,
+		"www.miek.nl.":  {"www", "miek", "nl"},
+		"www.miek.nl":   {"www", "miek", "nl"},
+		"www..miek.nl":  {"www", "", "miek", "nl"},
+		`www\.miek.nl`:  {`www\.miek`, "nl"},
+		`www\\.miek.nl`: {`www\\`, "miek", "nl"},
+		".www.miek.nl.": {"", "www", "miek", "nl"},
+	}
+domainLoop:
+	for domain, splits := range labels {
+		parts := SplitDomainName(domain)
+		if len(parts) != len(splits) {
+			t.Errorf("SplitDomainName returned %v for %s, expected %v", parts, domain, splits)
+			continue domainLoop
+		}
+		for i := range parts {
+			if parts[i] != splits[i] {
+				t.Errorf("SplitDomainName returned %v for %s, expected %v", parts, domain, splits)
+				continue domainLoop
+			}
+		}
+	}
+}
+
+func TestIsDomainName(t *testing.T) {
+	type ret struct {
+		ok  bool
+		lab int
+	}
+	names := map[string]*ret{
+		"..":               {false, 1},
+		"@.":               {true, 1},
+		"www.example.com":  {true, 3},
+		"www.e%ample.com":  {true, 3},
+		"www.example.com.": {true, 3},
+		"mi\\k.nl.":        {true, 2},
+		"mi\\k.nl":         {true, 2},
+	}
+	for d, ok := range names {
+		l, k := IsDomainName(d)
+		if ok.ok != k || ok.lab != l {
+			t.Errorf(" got %v %d for %s ", k, l, d)
+			t.Errorf("have %v %d for %s ", ok.ok, ok.lab, d)
+		}
+	}
+}
+
+func BenchmarkSplitLabels(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		Split("www.example.com.")
+	}
+}
+
+func BenchmarkLenLabels(b *testing.B) {
+	for i := 0; i < b.N; i++ {
+		CountLabel("www.example.com.")
+	}
+}
+
+func BenchmarkCompareDomainName(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		CompareDomainName("www.example.com.", "aa.example.com.")
+	}
+}
+
+func BenchmarkIsSubDomain(b *testing.B) {
+	b.ReportAllocs()
+	for i := 0; i < b.N; i++ {
+		IsSubDomain("www.example.com.", "aa.example.com.")
+		IsSubDomain("example.com.", "aa.example.com.")
+		IsSubDomain("miek.nl.", "aa.example.com.")
+	}
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/leak_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/leak_test.go
new file mode 100644
index 0000000..af37011
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/leak_test.go
@@ -0,0 +1,71 @@
+package dns
+
+import (
+	"fmt"
+	"os"
+	"runtime"
+	"sort"
+	"strings"
+	"testing"
+	"time"
+)
+
+// copied from net/http/main_test.go
+
+func interestingGoroutines() (gs []string) {
+	buf := make([]byte, 2<<20)
+	buf = buf[:runtime.Stack(buf, true)]
+	for _, g := range strings.Split(string(buf), "\n\n") {
+		sl := strings.SplitN(g, "\n", 2)
+		if len(sl) != 2 {
+			continue
+		}
+		stack := strings.TrimSpace(sl[1])
+		if stack == "" ||
+			strings.Contains(stack, "testing.(*M).before.func1") ||
+			strings.Contains(stack, "os/signal.signal_recv") ||
+			strings.Contains(stack, "created by net.startServer") ||
+			strings.Contains(stack, "created by testing.RunTests") ||
+			strings.Contains(stack, "closeWriteAndWait") ||
+			strings.Contains(stack, "testing.Main(") ||
+			strings.Contains(stack, "testing.(*T).Run(") ||
+			// These only show up with GOTRACEBACK=2; Issue 5005 (comment 28)
+			strings.Contains(stack, "runtime.goexit") ||
+			strings.Contains(stack, "created by runtime.gc") ||
+			strings.Contains(stack, "dns.interestingGoroutines") ||
+			strings.Contains(stack, "runtime.MHeap_Scavenger") {
+			continue
+		}
+		gs = append(gs, stack)
+	}
+	sort.Strings(gs)
+	return
+}
+
+func goroutineLeaked() error {
+	if testing.Short() {
+		// Don't worry about goroutine leaks in -short mode or in
+		// benchmark mode. Too distracting when there are false positives.
+		return nil
+	}
+
+	var stackCount map[string]int
+	for i := 0; i < 5; i++ {
+		n := 0
+		stackCount = make(map[string]int)
+		gs := interestingGoroutines()
+		for _, g := range gs {
+			stackCount[g]++
+			n++
+		}
+		if n == 0 {
+			return nil
+		}
+		// Wait for goroutines to schedule and die off:
+		time.Sleep(100 * time.Millisecond)
+	}
+	for stack, count := range stackCount {
+		fmt.Fprintf(os.Stderr, "%d instances of:\n%s\n", count, stack)
+	}
+	return fmt.Errorf("too many goroutines running after dns test(s)")
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/length_test.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/length_test.go
new file mode 100644
index 0000000..5324c22
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/length_test.go
@@ -0,0 +1,174 @@
+package dns
+
+import (
+	"encoding/hex"
+	"fmt"
+	"net"
+	"testing"
+)
+
+func TestCompressLength(t *testing.T) {
+	m := new(Msg)
+	m.SetQuestion("miek.nl", TypeMX)
+	ul := m.Len()
+	m.Compress = true
+	if ul != m.Len() {
+		t.Fatalf("should be equal")
+	}
+}
+
+// Does the predicted length match final packed length?
+func TestMsgCompressLength(t *testing.T) {
+	makeMsg := func(question string, ans, ns, e []RR) *Msg {
+		msg := new(Msg)
+		msg.SetQuestion(Fqdn(question), TypeANY)
+		msg.Answer = append(msg.Answer, ans...)
+		msg.Ns = append(msg.Ns, ns...)
+		msg.Extra = append(msg.Extra, e...)
+		msg.Compress = true
+		return msg
+	}
+
+	name1 := "12345678901234567890123456789012345.12345678.123."
+	rrA := testRR(name1 + " 3600 IN A 192.0.2.1")
+	rrMx := testRR(name1 + " 3600 IN MX 10 " + name1)
+	tests := []*Msg{
+		makeMsg(name1, []RR{rrA}, nil, nil),
+		makeMsg(name1, []RR{rrMx, rrMx}, nil, nil)}
+
+	for _, msg := range tests {
+		predicted := msg.Len()
+		buf, err := msg.Pack()
+		if err != nil {
+			t.Error(err)
+		}
+		if predicted < len(buf) {
+			t.Errorf("predicted compressed length is wrong: predicted %s (len=%d) %d, actual %d",
+				msg.Question[0].Name, len(msg.Answer), predicted, len(buf))
+		}
+	}
+}
+
+func TestMsgLength(t *testing.T) {
+	makeMsg := func(question string, ans, ns, e []RR) *Msg {
+		msg := new(Msg)
+		msg.SetQuestion(Fqdn(question), TypeANY)
+		msg.Answer = append(msg.Answer, ans...)
+		msg.Ns = append(msg.Ns, ns...)
+		msg.Extra = append(msg.Extra, e...)
+		return msg
+	}
+
+	name1 := "12345678901234567890123456789012345.12345678.123."
+	rrA := testRR(name1 + " 3600 IN A 192.0.2.1")
+	rrMx := testRR(name1 + " 3600 IN MX 10 " + name1)
+	tests := []*Msg{
+		makeMsg(name1, []RR{rrA}, nil, nil),
+		makeMsg(name1, []RR{rrMx, rrMx}, nil, nil)}
+
+	for _, msg := range tests {
+		predicted := msg.Len()
+		buf, err := msg.Pack()
+		if err != nil {
+			t.Error(err)
+		}
+		if predicted < len(buf) {
+			t.Errorf("predicted length is wrong: predicted %s (len=%d), actual %d",
+				msg.Question[0].Name, predicted, len(buf))
+		}
+	}
+}
+
+func TestMsgLength2(t *testing.T) {
+	// Serialized replies
+	var testMessages = []string{
+		// google.com. IN A?
+		"064e81800001000b0004000506676f6f676c6503636f6d0000010001c00c00010001000000050004adc22986c00c00010001000000050004adc22987c00c00010001000000050004adc22988c00c00010001000000050004adc22989c00c00010001000000050004adc2298ec00c00010001000000050004adc22980c00c00010001000000050004adc22981c00c00010001000000050004adc22982c00c00010001000000050004adc22983c00c00010001000000050004adc22984c00c00010001000000050004adc22985c00c00020001000000050006036e7331c00cc00c00020001000000050006036e7332c00cc00c00020 [...]
+		// amazon.com. IN A? (reply has no EDNS0 record)
+		// TODO(miek): this one is off-by-one, need to find out why
+		//"6de1818000010004000a000806616d617a6f6e03636f6d0000010001c00c000100010000000500044815c2d4c00c000100010000000500044815d7e8c00c00010001000000050004b02062a6c00c00010001000000050004cdfbf236c00c000200010000000500140570646e733408756c747261646e73036f726700c00c000200010000000500150570646e733508756c747261646e7304696e666f00c00c000200010000000500160570646e733608756c747261646e7302636f02756b00c00c00020001000000050014036e7331037033310664796e656374036e657400c00c00020001000000050006036e7332c0cfc00c0 [...]
+		// yahoo.com. IN A?
+		"fc2d81800001000300070008057961686f6f03636f6d0000010001c00c00010001000000050004628afd6dc00c00010001000000050004628bb718c00c00010001000000050004cebe242dc00c00020001000000050006036e7336c00cc00c00020001000000050006036e7338c00cc00c00020001000000050006036e7331c00cc00c00020001000000050006036e7332c00cc00c00020001000000050006036e7333c00cc00c00020001000000050006036e7334c00cc00c00020001000000050006036e7335c00cc07b0001000100000005000444b48310c08d00010001000000050004448eff10c09f0001000100000005000 [...]
+		// microsoft.com. IN A?
+		"f4368180000100020005000b096d6963726f736f667403636f6d0000010001c00c0001000100000005000440040b25c00c0001000100000005000441373ac9c00c0002000100000005000e036e7331046d736674036e657400c00c00020001000000050006036e7332c04fc00c00020001000000050006036e7333c04fc00c00020001000000050006036e7334c04fc00c00020001000000050006036e7335c04fc04b000100010000000500044137253ec04b001c00010000000500102a010111200500000000000000010001c0650001000100000005000440043badc065001c00010000000500102a010111200600060000000 [...]
+		// google.com. IN MX?
+		"724b8180000100050004000b06676f6f676c6503636f6d00000f0001c00c000f000100000005000c000a056173706d78016cc00cc00c000f0001000000050009001404616c7431c02ac00c000f0001000000050009001e04616c7432c02ac00c000f0001000000050009002804616c7433c02ac00c000f0001000000050009003204616c7434c02ac00c00020001000000050006036e7332c00cc00c00020001000000050006036e7333c00cc00c00020001000000050006036e7334c00cc00c00020001000000050006036e7331c00cc02a00010001000000050004adc2421bc02a001c00010000000500102a00145040080c010 [...]
+		// reddit.com. IN A?
+		"12b98180000100080000000c0672656464697403636f6d0000020001c00c0002000100000005000f046175733204616b616d036e657400c00c000200010000000500070475736534c02dc00c000200010000000500070475737733c02dc00c000200010000000500070475737735c02dc00c00020001000000050008056173696131c02dc00c00020001000000050008056173696139c02dc00c00020001000000050008056e73312d31c02dc00c0002000100000005000a076e73312d313935c02dc02800010001000000050004c30a242ec04300010001000000050004451f1d39c05600010001000000050004451f3bc7c0690 [...]
+	}
+
+	for i, hexData := range testMessages {
+		// we won't fail the decoding of the hex
+		input, _ := hex.DecodeString(hexData)
+
+		m := new(Msg)
+		m.Unpack(input)
+		m.Compress = true
+		lenComp := m.Len()
+		b, _ := m.Pack()
+		pacComp := len(b)
+		m.Compress = false
+		lenUnComp := m.Len()
+		b, _ = m.Pack()
+		pacUnComp := len(b)
+		if pacComp+1 != lenComp {
+			t.Errorf("msg.Len(compressed)=%d actual=%d for test %d", lenComp, pacComp, i)
+		}
+		if pacUnComp+1 != lenUnComp {
+			t.Errorf("msg.Len(uncompressed)=%d actual=%d for test %d", lenUnComp, pacUnComp, i)
+		}
+	}
+}
+
+func TestMsgLengthCompressionMalformed(t *testing.T) {
+	// SOA with empty hostmaster, which is illegal
+	soa := &SOA{Hdr: RR_Header{Name: ".", Rrtype: TypeSOA, Class: ClassINET, Ttl: 12345},
+		Ns:      ".",
+		Mbox:    "",
+		Serial:  0,
+		Refresh: 28800,
+		Retry:   7200,
+		Expire:  604800,
+		Minttl:  60}
+	m := new(Msg)
+	m.Compress = true
+	m.Ns = []RR{soa}
+	m.Len() // Should not crash.
+}
+
+func TestMsgCompressLength2(t *testing.T) {
+	msg := new(Msg)
+	msg.Compress = true
+	msg.SetQuestion(Fqdn("bliep."), TypeANY)
+	msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "blaat.", Rrtype: 0x21, Class: 0x1, Ttl: 0x3c}, Port: 0x4c57, Target: "foo.bar."})
+	msg.Extra = append(msg.Extra, &A{Hdr: RR_Header{Name: "foo.bar.", Rrtype: 0x1, Class: 0x1, Ttl: 0x3c}, A: net.IP{0xac, 0x11, 0x0, 0x3}})
+	predicted := msg.Len()
+	buf, err := msg.Pack()
+	if err != nil {
+		t.Error(err)
+	}
+	if predicted != len(buf) {
+		t.Errorf("predicted compressed length is wrong: predicted %s (len=%d) %d, actual %d",
+			msg.Question[0].Name, len(msg.Answer), predicted, len(buf))
+	}
+}
+
+func TestMsgCompressLengthLargeRecords(t *testing.T) {
+	msg := new(Msg)
+	msg.Compress = true
+	msg.SetQuestion("my.service.acme.", TypeSRV)
+	j := 1
+	for i := 0; i < 250; i++ {
+		target := fmt.Sprintf("host-redis-%d-%d.test.acme.com.node.dc1.consul.", j, i)
+		msg.Answer = append(msg.Answer, &SRV{Hdr: RR_Header{Name: "redis.service.consul.", Class: 1, Rrtype: TypeSRV, Ttl: 0x3c}, Port: 0x4c57, Target: target})
+		msg.Extra = append(msg.Extra, &CNAME{Hdr: RR_Header{Name: target, Class: 1, Rrtype: TypeCNAME, Ttl: 0x3c}, Target: fmt.Sprintf("fx.168.%d.%d.", j, i)})
+	}
+	predicted := msg.Len()
+	buf, err := msg.Pack()
+	if err != nil {
+		t.Error(err)
+	}
+	if predicted != len(buf) {
+		t.Fatalf("predicted compressed length is wrong: predicted %s (len=%d) %d, actual %d", msg.Question[0].Name, len(msg.Answer), predicted, len(buf))
+	}
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/msg.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/msg.go
new file mode 100644
index 0000000..276e6b0
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/msg.go
@@ -0,0 +1,1177 @@
+// DNS packet assembly, see RFC 1035. Converting from - Unpack() -
+// and to - Pack() - wire format.
+// All the packers and unpackers take a (msg []byte, off int)
+// and return (off1 int, ok bool).  If they return ok==false, they
+// also return off1==len(msg), so that the next unpacker will
+// also fail.  This lets us avoid checks of ok until the end of a
+// packing sequence.
+
+package dns
+
+//go:generate go run msg_generate.go
+//go:generate go run compress_generate.go
+
+import (
+	crand "crypto/rand"
+	"encoding/binary"
+	"fmt"
+	"math/big"
+	"math/rand"
+	"strconv"
+	"sync"
+)
+
+const (
+	maxCompressionOffset    = 2 << 13 // We have 14 bits for the compression pointer
+	maxDomainNameWireOctets = 255     // See RFC 1035 section 2.3.4
+)
+
+// Errors defined in this package.
+var (
+	ErrAlg           error = &Error{err: "bad algorithm"}                  // ErrAlg indicates an error with the (DNSSEC) algorithm.
+	ErrAuth          error = &Error{err: "bad authentication"}             // ErrAuth indicates an error in the TSIG authentication.
+	ErrBuf           error = &Error{err: "buffer size too small"}          // ErrBuf indicates that the buffer used is too small for the message.
+	ErrConnEmpty     error = &Error{err: "conn has no connection"}         // ErrConnEmpty indicates a connection is being used before it is initialized.
+	ErrExtendedRcode error = &Error{err: "bad extended rcode"}             // ErrExtendedRcode ...
+	ErrFqdn          error = &Error{err: "domain must be fully qualified"} // ErrFqdn indicates that a domain name does not have a closing dot.
+	ErrId            error = &Error{err: "id mismatch"}                    // ErrId indicates there is a mismatch with the message's ID.
+	ErrKeyAlg        error = &Error{err: "bad key algorithm"}              // ErrKeyAlg indicates that the algorithm in the key is not valid.
+	ErrKey           error = &Error{err: "bad key"}
+	ErrKeySize       error = &Error{err: "bad key size"}
+	ErrLongDomain    error = &Error{err: fmt.Sprintf("domain name exceeded %d wire-format octets", maxDomainNameWireOctets)}
+	ErrNoSig         error = &Error{err: "no signature found"}
+	ErrPrivKey       error = &Error{err: "bad private key"}
+	ErrRcode         error = &Error{err: "bad rcode"}
+	ErrRdata         error = &Error{err: "bad rdata"}
+	ErrRRset         error = &Error{err: "bad rrset"}
+	ErrSecret        error = &Error{err: "no secrets defined"}
+	ErrShortRead     error = &Error{err: "short read"}
+	ErrSig           error = &Error{err: "bad signature"}                      // ErrSig indicates that a signature can not be cryptographically validated.
+	ErrSoa           error = &Error{err: "no SOA"}                             // ErrSOA indicates that no SOA RR was seen when doing zone transfers.
+	ErrTime          error = &Error{err: "bad time"}                           // ErrTime indicates a timing error in TSIG authentication.
+	ErrTruncated     error = &Error{err: "failed to unpack truncated message"} // ErrTruncated indicates that we failed to unpack a truncated message. We unpacked as much as we had so Msg can still be used, if desired.
+)
+
+// Id by default, returns a 16 bits random number to be used as a
+// message id. The random provided should be good enough. This being a
+// variable the function can be reassigned to a custom function.
+// For instance, to make it return a static value:
+//
+//	dns.Id = func() uint16 { return 3 }
+var Id = id
+
+var (
+	idLock sync.Mutex
+	idRand *rand.Rand
+)
+
+// id returns a 16 bits random number to be used as a
+// message id. The random provided should be good enough.
+func id() uint16 {
+	idLock.Lock()
+
+	if idRand == nil {
+		// This (partially) works around
+		// https://github.com/golang/go/issues/11833 by only
+		// seeding idRand upon the first call to id.
+
+		var seed int64
+		var buf [8]byte
+
+		if _, err := crand.Read(buf[:]); err == nil {
+			seed = int64(binary.LittleEndian.Uint64(buf[:]))
+		} else {
+			seed = rand.Int63()
+		}
+
+		idRand = rand.New(rand.NewSource(seed))
+	}
+
+	// The call to idRand.Uint32 must be within the
+	// mutex lock because *rand.Rand is not safe for
+	// concurrent use.
+	//
+	// There is no added performance overhead to calling
+	// idRand.Uint32 inside a mutex lock over just
+	// calling rand.Uint32 as the global math/rand rng
+	// is internally protected by a sync.Mutex.
+	id := uint16(idRand.Uint32())
+
+	idLock.Unlock()
+	return id
+}
+
+// MsgHdr is a a manually-unpacked version of (id, bits).
+type MsgHdr struct {
+	Id                 uint16
+	Response           bool
+	Opcode             int
+	Authoritative      bool
+	Truncated          bool
+	RecursionDesired   bool
+	RecursionAvailable bool
+	Zero               bool
+	AuthenticatedData  bool
+	CheckingDisabled   bool
+	Rcode              int
+}
+
+// Msg contains the layout of a DNS message.
+type Msg struct {
+	MsgHdr
+	Compress bool       `json:"-"` // If true, the message will be compressed when converted to wire format.
+	Question []Question // Holds the RR(s) of the question section.
+	Answer   []RR       // Holds the RR(s) of the answer section.
+	Ns       []RR       // Holds the RR(s) of the authority section.
+	Extra    []RR       // Holds the RR(s) of the additional section.
+}
+
+// ClassToString is a maps Classes to strings for each CLASS wire type.
+var ClassToString = map[uint16]string{
+	ClassINET:   "IN",
+	ClassCSNET:  "CS",
+	ClassCHAOS:  "CH",
+	ClassHESIOD: "HS",
+	ClassNONE:   "NONE",
+	ClassANY:    "ANY",
+}
+
+// OpcodeToString maps Opcodes to strings.
+var OpcodeToString = map[int]string{
+	OpcodeQuery:  "QUERY",
+	OpcodeIQuery: "IQUERY",
+	OpcodeStatus: "STATUS",
+	OpcodeNotify: "NOTIFY",
+	OpcodeUpdate: "UPDATE",
+}
+
+// RcodeToString maps Rcodes to strings.
+var RcodeToString = map[int]string{
+	RcodeSuccess:        "NOERROR",
+	RcodeFormatError:    "FORMERR",
+	RcodeServerFailure:  "SERVFAIL",
+	RcodeNameError:      "NXDOMAIN",
+	RcodeNotImplemented: "NOTIMPL",
+	RcodeRefused:        "REFUSED",
+	RcodeYXDomain:       "YXDOMAIN", // See RFC 2136
+	RcodeYXRrset:        "YXRRSET",
+	RcodeNXRrset:        "NXRRSET",
+	RcodeNotAuth:        "NOTAUTH",
+	RcodeNotZone:        "NOTZONE",
+	RcodeBadSig:         "BADSIG", // Also known as RcodeBadVers, see RFC 6891
+	//	RcodeBadVers:        "BADVERS",
+	RcodeBadKey:    "BADKEY",
+	RcodeBadTime:   "BADTIME",
+	RcodeBadMode:   "BADMODE",
+	RcodeBadName:   "BADNAME",
+	RcodeBadAlg:    "BADALG",
+	RcodeBadTrunc:  "BADTRUNC",
+	RcodeBadCookie: "BADCOOKIE",
+}
+
+// Domain names are a sequence of counted strings
+// split at the dots. They end with a zero-length string.
+
+// PackDomainName packs a domain name s into msg[off:].
+// If compression is wanted compress must be true and the compression
+// map needs to hold a mapping between domain names and offsets
+// pointing into msg.
+func PackDomainName(s string, msg []byte, off int, compression map[string]int, compress bool) (off1 int, err error) {
+	off1, _, err = packDomainName(s, msg, off, compression, compress)
+	return
+}
+
+func packDomainName(s string, msg []byte, off int, compression map[string]int, compress bool) (off1 int, labels int, err error) {
+	// special case if msg == nil
+	lenmsg := 256
+	if msg != nil {
+		lenmsg = len(msg)
+	}
+	ls := len(s)
+	if ls == 0 { // Ok, for instance when dealing with update RR without any rdata.
+		return off, 0, nil
+	}
+	// If not fully qualified, error out, but only if msg == nil #ugly
+	switch {
+	case msg == nil:
+		if s[ls-1] != '.' {
+			s += "."
+			ls++
+		}
+	case msg != nil:
+		if s[ls-1] != '.' {
+			return lenmsg, 0, ErrFqdn
+		}
+	}
+	// Each dot ends a segment of the name.
+	// We trade each dot byte for a length byte.
+	// Except for escaped dots (\.), which are normal dots.
+	// There is also a trailing zero.
+
+	// Compression
+	nameoffset := -1
+	pointer := -1
+	// Emit sequence of counted strings, chopping at dots.
+	begin := 0
+	bs := []byte(s)
+	roBs, bsFresh, escapedDot := s, true, false
+	for i := 0; i < ls; i++ {
+		if bs[i] == '\\' {
+			for j := i; j < ls-1; j++ {
+				bs[j] = bs[j+1]
+			}
+			ls--
+			if off+1 > lenmsg {
+				return lenmsg, labels, ErrBuf
+			}
+			// check for \DDD
+			if i+2 < ls && isDigit(bs[i]) && isDigit(bs[i+1]) && isDigit(bs[i+2]) {
+				bs[i] = dddToByte(bs[i:])
+				for j := i + 1; j < ls-2; j++ {
+					bs[j] = bs[j+2]
+				}
+				ls -= 2
+			}
+			escapedDot = bs[i] == '.'
+			bsFresh = false
+			continue
+		}
+
+		if bs[i] == '.' {
+			if i > 0 && bs[i-1] == '.' && !escapedDot {
+				// two dots back to back is not legal
+				return lenmsg, labels, ErrRdata
+			}
+			if i-begin >= 1<<6 { // top two bits of length must be clear
+				return lenmsg, labels, ErrRdata
+			}
+			// off can already (we're in a loop) be bigger than len(msg)
+			// this happens when a name isn't fully qualified
+			if off+1 > lenmsg {
+				return lenmsg, labels, ErrBuf
+			}
+			if msg != nil {
+				msg[off] = byte(i - begin)
+			}
+			offset := off
+			off++
+			for j := begin; j < i; j++ {
+				if off+1 > lenmsg {
+					return lenmsg, labels, ErrBuf
+				}
+				if msg != nil {
+					msg[off] = bs[j]
+				}
+				off++
+			}
+			if compress && !bsFresh {
+				roBs = string(bs)
+				bsFresh = true
+			}
+			// Don't try to compress '.'
+			// We should only compress when compress it true, but we should also still pick
+			// up names that can be used for *future* compression(s).
+			if compression != nil && roBs[begin:] != "." {
+				if p, ok := compression[roBs[begin:]]; !ok {
+					// Only offsets smaller than this can be used.
+					if offset < maxCompressionOffset {
+						compression[roBs[begin:]] = offset
+					}
+				} else {
+					// The first hit is the longest matching dname
+					// keep the pointer offset we get back and store
+					// the offset of the current name, because that's
+					// where we need to insert the pointer later
+
+					// If compress is true, we're allowed to compress this dname
+					if pointer == -1 && compress {
+						pointer = p         // Where to point to
+						nameoffset = offset // Where to point from
+						break
+					}
+				}
+			}
+			labels++
+			begin = i + 1
+		}
+		escapedDot = false
+	}
+	// Root label is special
+	if len(bs) == 1 && bs[0] == '.' {
+		return off, labels, nil
+	}
+	// If we did compression and we find something add the pointer here
+	if pointer != -1 {
+		// We have two bytes (14 bits) to put the pointer in
+		// if msg == nil, we will never do compression
+		binary.BigEndian.PutUint16(msg[nameoffset:], uint16(pointer^0xC000))
+		off = nameoffset + 1
+		goto End
+	}
+	if msg != nil && off < len(msg) {
+		msg[off] = 0
+	}
+End:
+	off++
+	return off, labels, nil
+}
+
+// Unpack a domain name.
+// In addition to the simple sequences of counted strings above,
+// domain names are allowed to refer to strings elsewhere in the
+// packet, to avoid repeating common suffixes when returning
+// many entries in a single domain.  The pointers are marked
+// by a length byte with the top two bits set.  Ignoring those
+// two bits, that byte and the next give a 14 bit offset from msg[0]
+// where we should pick up the trail.
+// Note that if we jump elsewhere in the packet,
+// we return off1 == the offset after the first pointer we found,
+// which is where the next record will start.
+// In theory, the pointers are only allowed to jump backward.
+// We let them jump anywhere and stop jumping after a while.
+
+// UnpackDomainName unpacks a domain name into a string.
+func UnpackDomainName(msg []byte, off int) (string, int, error) {
+	s := make([]byte, 0, 64)
+	off1 := 0
+	lenmsg := len(msg)
+	maxLen := maxDomainNameWireOctets
+	ptr := 0 // number of pointers followed
+Loop:
+	for {
+		if off >= lenmsg {
+			return "", lenmsg, ErrBuf
+		}
+		c := int(msg[off])
+		off++
+		switch c & 0xC0 {
+		case 0x00:
+			if c == 0x00 {
+				// end of name
+				break Loop
+			}
+			// literal string
+			if off+c > lenmsg {
+				return "", lenmsg, ErrBuf
+			}
+			for j := off; j < off+c; j++ {
+				switch b := msg[j]; b {
+				case '.', '(', ')', ';', ' ', '@':
+					fallthrough
+				case '"', '\\':
+					s = append(s, '\\', b)
+					// presentation-format \X escapes add an extra byte
+					maxLen++
+				default:
+					if b < 32 || b >= 127 { // unprintable, use \DDD
+						var buf [3]byte
+						bufs := strconv.AppendInt(buf[:0], int64(b), 10)
+						s = append(s, '\\')
+						for i := 0; i < 3-len(bufs); i++ {
+							s = append(s, '0')
+						}
+						for _, r := range bufs {
+							s = append(s, r)
+						}
+						// presentation-format \DDD escapes add 3 extra bytes
+						maxLen += 3
+					} else {
+						s = append(s, b)
+					}
+				}
+			}
+			s = append(s, '.')
+			off += c
+		case 0xC0:
+			// pointer to somewhere else in msg.
+			// remember location after first ptr,
+			// since that's how many bytes we consumed.
+			// also, don't follow too many pointers --
+			// maybe there's a loop.
+			if off >= lenmsg {
+				return "", lenmsg, ErrBuf
+			}
+			c1 := msg[off]
+			off++
+			if ptr == 0 {
+				off1 = off
+			}
+			if ptr++; ptr > 10 {
+				return "", lenmsg, &Error{err: "too many compression pointers"}
+			}
+			// pointer should guarantee that it advances and points forwards at least
+			// but the condition on previous three lines guarantees that it's
+			// at least loop-free
+			off = (c^0xC0)<<8 | int(c1)
+		default:
+			// 0x80 and 0x40 are reserved
+			return "", lenmsg, ErrRdata
+		}
+	}
+	if ptr == 0 {
+		off1 = off
+	}
+	if len(s) == 0 {
+		s = []byte(".")
+	} else if len(s) >= maxLen {
+		// error if the name is too long, but don't throw it away
+		return string(s), lenmsg, ErrLongDomain
+	}
+	return string(s), off1, nil
+}
+
+func packTxt(txt []string, msg []byte, offset int, tmp []byte) (int, error) {
+	if len(txt) == 0 {
+		if offset >= len(msg) {
+			return offset, ErrBuf
+		}
+		msg[offset] = 0
+		return offset, nil
+	}
+	var err error
+	for i := range txt {
+		if len(txt[i]) > len(tmp) {
+			return offset, ErrBuf
+		}
+		offset, err = packTxtString(txt[i], msg, offset, tmp)
+		if err != nil {
+			return offset, err
+		}
+	}
+	return offset, nil
+}
+
+func packTxtString(s string, msg []byte, offset int, tmp []byte) (int, error) {
+	lenByteOffset := offset
+	if offset >= len(msg) || len(s) > len(tmp) {
+		return offset, ErrBuf
+	}
+	offset++
+	bs := tmp[:len(s)]
+	copy(bs, s)
+	for i := 0; i < len(bs); i++ {
+		if len(msg) <= offset {
+			return offset, ErrBuf
+		}
+		if bs[i] == '\\' {
+			i++
+			if i == len(bs) {
+				break
+			}
+			// check for \DDD
+			if i+2 < len(bs) && isDigit(bs[i]) && isDigit(bs[i+1]) && isDigit(bs[i+2]) {
+				msg[offset] = dddToByte(bs[i:])
+				i += 2
+			} else {
+				msg[offset] = bs[i]
+			}
+		} else {
+			msg[offset] = bs[i]
+		}
+		offset++
+	}
+	l := offset - lenByteOffset - 1
+	if l > 255 {
+		return offset, &Error{err: "string exceeded 255 bytes in txt"}
+	}
+	msg[lenByteOffset] = byte(l)
+	return offset, nil
+}
+
+func packOctetString(s string, msg []byte, offset int, tmp []byte) (int, error) {
+	if offset >= len(msg) || len(s) > len(tmp) {
+		return offset, ErrBuf
+	}
+	bs := tmp[:len(s)]
+	copy(bs, s)
+	for i := 0; i < len(bs); i++ {
+		if len(msg) <= offset {
+			return offset, ErrBuf
+		}
+		if bs[i] == '\\' {
+			i++
+			if i == len(bs) {
+				break
+			}
+			// check for \DDD
+			if i+2 < len(bs) && isDigit(bs[i]) && isDigit(bs[i+1]) && isDigit(bs[i+2]) {
+				msg[offset] = dddToByte(bs[i:])
+				i += 2
+			} else {
+				msg[offset] = bs[i]
+			}
+		} else {
+			msg[offset] = bs[i]
+		}
+		offset++
+	}
+	return offset, nil
+}
+
+func unpackTxt(msg []byte, off0 int) (ss []string, off int, err error) {
+	off = off0
+	var s string
+	for off < len(msg) && err == nil {
+		s, off, err = unpackTxtString(msg, off)
+		if err == nil {
+			ss = append(ss, s)
+		}
+	}
+	return
+}
+
+func unpackTxtString(msg []byte, offset int) (string, int, error) {
+	if offset+1 > len(msg) {
+		return "", offset, &Error{err: "overflow unpacking txt"}
+	}
+	l := int(msg[offset])
+	if offset+l+1 > len(msg) {
+		return "", offset, &Error{err: "overflow unpacking txt"}
+	}
+	s := make([]byte, 0, l)
+	for _, b := range msg[offset+1 : offset+1+l] {
+		switch b {
+		case '"', '\\':
+			s = append(s, '\\', b)
+		default:
+			if b < 32 || b > 127 { // unprintable
+				var buf [3]byte
+				bufs := strconv.AppendInt(buf[:0], int64(b), 10)
+				s = append(s, '\\')
+				for i := 0; i < 3-len(bufs); i++ {
+					s = append(s, '0')
+				}
+				for _, r := range bufs {
+					s = append(s, r)
+				}
+			} else {
+				s = append(s, b)
+			}
+		}
+	}
+	offset += 1 + l
+	return string(s), offset, nil
+}
+
+// Helpers for dealing with escaped bytes
+func isDigit(b byte) bool { return b >= '0' && b <= '9' }
+
+func dddToByte(s []byte) byte {
+	return byte((s[0]-'0')*100 + (s[1]-'0')*10 + (s[2] - '0'))
+}
+
+// Helper function for packing and unpacking
+func intToBytes(i *big.Int, length int) []byte {
+	buf := i.Bytes()
+	if len(buf) < length {
+		b := make([]byte, length)
+		copy(b[length-len(buf):], buf)
+		return b
+	}
+	return buf
+}
+
+// PackRR packs a resource record rr into msg[off:].
+// See PackDomainName for documentation about the compression.
+func PackRR(rr RR, msg []byte, off int, compression map[string]int, compress bool) (off1 int, err error) {
+	if rr == nil {
+		return len(msg), &Error{err: "nil rr"}
+	}
+
+	off1, err = rr.pack(msg, off, compression, compress)
+	if err != nil {
+		return len(msg), err
+	}
+	// TODO(miek): Not sure if this is needed? If removed we can remove rawmsg.go as well.
+	if rawSetRdlength(msg, off, off1) {
+		return off1, nil
+	}
+	return off, ErrRdata
+}
+
+// UnpackRR unpacks msg[off:] into an RR.
+func UnpackRR(msg []byte, off int) (rr RR, off1 int, err error) {
+	h, off, msg, err := unpackHeader(msg, off)
+	if err != nil {
+		return nil, len(msg), err
+	}
+
+	return UnpackRRWithHeader(h, msg, off)
+}
+
+// UnpackRRWithHeader unpacks the record type specific payload given an existing
+// RR_Header.
+func UnpackRRWithHeader(h RR_Header, msg []byte, off int) (rr RR, off1 int, err error) {
+	end := off + int(h.Rdlength)
+
+	if fn, known := typeToUnpack[h.Rrtype]; !known {
+		rr, off, err = unpackRFC3597(h, msg, off)
+	} else {
+		rr, off, err = fn(h, msg, off)
+	}
+	if off != end {
+		return &h, end, &Error{err: "bad rdlength"}
+	}
+	return rr, off, err
+}
+
+// unpackRRslice unpacks msg[off:] into an []RR.
+// If we cannot unpack the whole array, then it will return nil
+func unpackRRslice(l int, msg []byte, off int) (dst1 []RR, off1 int, err error) {
+	var r RR
+	// Don't pre-allocate, l may be under attacker control
+	var dst []RR
+	for i := 0; i < l; i++ {
+		off1 := off
+		r, off, err = UnpackRR(msg, off)
+		if err != nil {
+			off = len(msg)
+			break
+		}
+		// If offset does not increase anymore, l is a lie
+		if off1 == off {
+			l = i
+			break
+		}
+		dst = append(dst, r)
+	}
+	if err != nil && off == len(msg) {
+		dst = nil
+	}
+	return dst, off, err
+}
+
+// Convert a MsgHdr to a string, with dig-like headers:
+//
+//;; opcode: QUERY, status: NOERROR, id: 48404
+//
+//;; flags: qr aa rd ra;
+func (h *MsgHdr) String() string {
+	if h == nil {
+		return "<nil> MsgHdr"
+	}
+
+	s := ";; opcode: " + OpcodeToString[h.Opcode]
+	s += ", status: " + RcodeToString[h.Rcode]
+	s += ", id: " + strconv.Itoa(int(h.Id)) + "\n"
+
+	s += ";; flags:"
+	if h.Response {
+		s += " qr"
+	}
+	if h.Authoritative {
+		s += " aa"
+	}
+	if h.Truncated {
+		s += " tc"
+	}
+	if h.RecursionDesired {
+		s += " rd"
+	}
+	if h.RecursionAvailable {
+		s += " ra"
+	}
+	if h.Zero { // Hmm
+		s += " z"
+	}
+	if h.AuthenticatedData {
+		s += " ad"
+	}
+	if h.CheckingDisabled {
+		s += " cd"
+	}
+
+	s += ";"
+	return s
+}
+
+// Pack packs a Msg: it is converted to to wire format.
+// If the dns.Compress is true the message will be in compressed wire format.
+func (dns *Msg) Pack() (msg []byte, err error) {
+	return dns.PackBuffer(nil)
+}
+
+// PackBuffer packs a Msg, using the given buffer buf. If buf is too small
+// a new buffer is allocated.
+func (dns *Msg) PackBuffer(buf []byte) (msg []byte, err error) {
+	// We use a similar function in tsig.go's stripTsig.
+	var (
+		dh          Header
+		compression map[string]int
+	)
+
+	if dns.Compress {
+		compression = make(map[string]int) // Compression pointer mappings
+	}
+
+	if dns.Rcode < 0 || dns.Rcode > 0xFFF {
+		return nil, ErrRcode
+	}
+	if dns.Rcode > 0xF {
+		// Regular RCODE field is 4 bits
+		opt := dns.IsEdns0()
+		if opt == nil {
+			return nil, ErrExtendedRcode
+		}
+		opt.SetExtendedRcode(uint8(dns.Rcode >> 4))
+		dns.Rcode &= 0xF
+	}
+
+	// Convert convenient Msg into wire-like Header.
+	dh.Id = dns.Id
+	dh.Bits = uint16(dns.Opcode)<<11 | uint16(dns.Rcode)
+	if dns.Response {
+		dh.Bits |= _QR
+	}
+	if dns.Authoritative {
+		dh.Bits |= _AA
+	}
+	if dns.Truncated {
+		dh.Bits |= _TC
+	}
+	if dns.RecursionDesired {
+		dh.Bits |= _RD
+	}
+	if dns.RecursionAvailable {
+		dh.Bits |= _RA
+	}
+	if dns.Zero {
+		dh.Bits |= _Z
+	}
+	if dns.AuthenticatedData {
+		dh.Bits |= _AD
+	}
+	if dns.CheckingDisabled {
+		dh.Bits |= _CD
+	}
+
+	// Prepare variable sized arrays.
+	question := dns.Question
+	answer := dns.Answer
+	ns := dns.Ns
+	extra := dns.Extra
+
+	dh.Qdcount = uint16(len(question))
+	dh.Ancount = uint16(len(answer))
+	dh.Nscount = uint16(len(ns))
+	dh.Arcount = uint16(len(extra))
+
+	// We need the uncompressed length here, because we first pack it and then compress it.
+	msg = buf
+	uncompressedLen := compressedLen(dns, false)
+	if packLen := uncompressedLen + 1; len(msg) < packLen {
+		msg = make([]byte, packLen)
+	}
+
+	// Pack it in: header and then the pieces.
+	off := 0
+	off, err = dh.pack(msg, off, compression, dns.Compress)
+	if err != nil {
+		return nil, err
+	}
+	for i := 0; i < len(question); i++ {
+		off, err = question[i].pack(msg, off, compression, dns.Compress)
+		if err != nil {
+			return nil, err
+		}
+	}
+	for i := 0; i < len(answer); i++ {
+		off, err = PackRR(answer[i], msg, off, compression, dns.Compress)
+		if err != nil {
+			return nil, err
+		}
+	}
+	for i := 0; i < len(ns); i++ {
+		off, err = PackRR(ns[i], msg, off, compression, dns.Compress)
+		if err != nil {
+			return nil, err
+		}
+	}
+	for i := 0; i < len(extra); i++ {
+		off, err = PackRR(extra[i], msg, off, compression, dns.Compress)
+		if err != nil {
+			return nil, err
+		}
+	}
+	return msg[:off], nil
+}
+
+// Unpack unpacks a binary message to a Msg structure.
+func (dns *Msg) Unpack(msg []byte) (err error) {
+	var (
+		dh  Header
+		off int
+	)
+	if dh, off, err = unpackMsgHdr(msg, off); err != nil {
+		return err
+	}
+
+	dns.Id = dh.Id
+	dns.Response = (dh.Bits & _QR) != 0
+	dns.Opcode = int(dh.Bits>>11) & 0xF
+	dns.Authoritative = (dh.Bits & _AA) != 0
+	dns.Truncated = (dh.Bits & _TC) != 0
+	dns.RecursionDesired = (dh.Bits & _RD) != 0
+	dns.RecursionAvailable = (dh.Bits & _RA) != 0
+	dns.Zero = (dh.Bits & _Z) != 0
+	dns.AuthenticatedData = (dh.Bits & _AD) != 0
+	dns.CheckingDisabled = (dh.Bits & _CD) != 0
+	dns.Rcode = int(dh.Bits & 0xF)
+
+	// If we are at the end of the message we should return *just* the
+	// header. This can still be useful to the caller. 9.9.9.9 sends these
+	// when responding with REFUSED for instance.
+	if off == len(msg) {
+		// reset sections before returning
+		dns.Question, dns.Answer, dns.Ns, dns.Extra = nil, nil, nil, nil
+		return nil
+	}
+
+	// Qdcount, Ancount, Nscount, Arcount can't be trusted, as they are
+	// attacker controlled. This means we can't use them to pre-allocate
+	// slices.
+	dns.Question = nil
+	for i := 0; i < int(dh.Qdcount); i++ {
+		off1 := off
+		var q Question
+		q, off, err = unpackQuestion(msg, off)
+		if err != nil {
+			// Even if Truncated is set, we only will set ErrTruncated if we
+			// actually got the questions
+			return err
+		}
+		if off1 == off { // Offset does not increase anymore, dh.Qdcount is a lie!
+			dh.Qdcount = uint16(i)
+			break
+		}
+		dns.Question = append(dns.Question, q)
+	}
+
+	dns.Answer, off, err = unpackRRslice(int(dh.Ancount), msg, off)
+	// The header counts might have been wrong so we need to update it
+	dh.Ancount = uint16(len(dns.Answer))
+	if err == nil {
+		dns.Ns, off, err = unpackRRslice(int(dh.Nscount), msg, off)
+	}
+	// The header counts might have been wrong so we need to update it
+	dh.Nscount = uint16(len(dns.Ns))
+	if err == nil {
+		dns.Extra, off, err = unpackRRslice(int(dh.Arcount), msg, off)
+	}
+	// The header counts might have been wrong so we need to update it
+	dh.Arcount = uint16(len(dns.Extra))
+
+	if off != len(msg) {
+		// TODO(miek) make this an error?
+		// use PackOpt to let people tell how detailed the error reporting should be?
+		// println("dns: extra bytes in dns packet", off, "<", len(msg))
+	} else if dns.Truncated {
+		// Whether we ran into a an error or not, we want to return that it
+		// was truncated
+		err = ErrTruncated
+	}
+	return err
+}
+
+// Convert a complete message to a string with dig-like output.
+func (dns *Msg) String() string {
+	if dns == nil {
+		return "<nil> MsgHdr"
+	}
+	s := dns.MsgHdr.String() + " "
+	s += "QUERY: " + strconv.Itoa(len(dns.Question)) + ", "
+	s += "ANSWER: " + strconv.Itoa(len(dns.Answer)) + ", "
+	s += "AUTHORITY: " + strconv.Itoa(len(dns.Ns)) + ", "
+	s += "ADDITIONAL: " + strconv.Itoa(len(dns.Extra)) + "\n"
+	if len(dns.Question) > 0 {
+		s += "\n;; QUESTION SECTION:\n"
+		for i := 0; i < len(dns.Question); i++ {
+			s += dns.Question[i].String() + "\n"
+		}
+	}
+	if len(dns.Answer) > 0 {
+		s += "\n;; ANSWER SECTION:\n"
+		for i := 0; i < len(dns.Answer); i++ {
+			if dns.Answer[i] != nil {
+				s += dns.Answer[i].String() + "\n"
+			}
+		}
+	}
+	if len(dns.Ns) > 0 {
+		s += "\n;; AUTHORITY SECTION:\n"
+		for i := 0; i < len(dns.Ns); i++ {
+			if dns.Ns[i] != nil {
+				s += dns.Ns[i].String() + "\n"
+			}
+		}
+	}
+	if len(dns.Extra) > 0 {
+		s += "\n;; ADDITIONAL SECTION:\n"
+		for i := 0; i < len(dns.Extra); i++ {
+			if dns.Extra[i] != nil {
+				s += dns.Extra[i].String() + "\n"
+			}
+		}
+	}
+	return s
+}
+
+// Len returns the message length when in (un)compressed wire format.
+// If dns.Compress is true compression it is taken into account. Len()
+// is provided to be a faster way to get the size of the resulting packet,
+// than packing it, measuring the size and discarding the buffer.
+func (dns *Msg) Len() int { return compressedLen(dns, dns.Compress) }
+
+// compressedLen returns the message length when in compressed wire format
+// when compress is true, otherwise the uncompressed length is returned.
+func compressedLen(dns *Msg, compress bool) int {
+	// We always return one more than needed.
+	l := 12 // Message header is always 12 bytes
+	if compress {
+		compression := map[string]int{}
+		for _, r := range dns.Question {
+			l += r.len()
+			compressionLenHelper(compression, r.Name)
+		}
+		l += compressionLenSlice(l, compression, dns.Answer)
+		l += compressionLenSlice(l, compression, dns.Ns)
+		l += compressionLenSlice(l, compression, dns.Extra)
+
+		return l
+	}
+
+	for _, r := range dns.Question {
+		l += r.len()
+	}
+	for _, r := range dns.Answer {
+		if r != nil {
+			l += r.len()
+		}
+	}
+	for _, r := range dns.Ns {
+		if r != nil {
+			l += r.len()
+		}
+	}
+	for _, r := range dns.Extra {
+		if r != nil {
+			l += r.len()
+		}
+	}
+
+	return l
+}
+
+func compressionLenSlice(len int, c map[string]int, rs []RR) int {
+	var l int
+	for _, r := range rs {
+		if r == nil {
+			continue
+		}
+		// track this length, and the global length in len, while taking compression into account for both.
+		x := r.len()
+		l += x
+		len += x
+
+		k, ok := compressionLenSearch(c, r.Header().Name)
+		if ok {
+			l += 1 - k
+			len += 1 - k
+		}
+
+		if len < maxCompressionOffset {
+			compressionLenHelper(c, r.Header().Name)
+		}
+
+		k, ok = compressionLenSearchType(c, r)
+		if ok {
+			l += 1 - k
+			len += 1 - k
+		}
+
+		if len < maxCompressionOffset {
+			compressionLenHelperType(c, r)
+		}
+	}
+	return l
+}
+
+// Put the parts of the name in the compression map.
+func compressionLenHelper(c map[string]int, s string) {
+	pref := ""
+	lbs := Split(s)
+	for j := len(lbs) - 1; j >= 0; j-- {
+		pref = s[lbs[j]:]
+		if _, ok := c[pref]; !ok {
+			c[pref] = len(pref)
+		}
+	}
+}
+
+// Look for each part in the compression map and returns its length,
+// keep on searching so we get the longest match.
+func compressionLenSearch(c map[string]int, s string) (int, bool) {
+	off := 0
+	end := false
+	if s == "" { // don't bork on bogus data
+		return 0, false
+	}
+	for {
+		if _, ok := c[s[off:]]; ok {
+			return len(s[off:]), true
+		}
+		if end {
+			break
+		}
+		off, end = NextLabel(s, off)
+	}
+	return 0, false
+}
+
+// Copy returns a new RR which is a deep-copy of r.
+func Copy(r RR) RR { r1 := r.copy(); return r1 }
+
+// Len returns the length (in octets) of the uncompressed RR in wire format.
+func Len(r RR) int { return r.len() }
+
+// Copy returns a new *Msg which is a deep-copy of dns.
+func (dns *Msg) Copy() *Msg { return dns.CopyTo(new(Msg)) }
+
+// CopyTo copies the contents to the provided message using a deep-copy and returns the copy.
+func (dns *Msg) CopyTo(r1 *Msg) *Msg {
+	r1.MsgHdr = dns.MsgHdr
+	r1.Compress = dns.Compress
+
+	if len(dns.Question) > 0 {
+		r1.Question = make([]Question, len(dns.Question))
+		copy(r1.Question, dns.Question) // TODO(miek): Question is an immutable value, ok to do a shallow-copy
+	}
+
+	rrArr := make([]RR, len(dns.Answer)+len(dns.Ns)+len(dns.Extra))
+	var rri int
+
+	if len(dns.Answer) > 0 {
+		rrbegin := rri
+		for i := 0; i < len(dns.Answer); i++ {
+			rrArr[rri] = dns.Answer[i].copy()
+			rri++
+		}
+		r1.Answer = rrArr[rrbegin:rri:rri]
+	}
+
+	if len(dns.Ns) > 0 {
+		rrbegin := rri
+		for i := 0; i < len(dns.Ns); i++ {
+			rrArr[rri] = dns.Ns[i].copy()
+			rri++
+		}
+		r1.Ns = rrArr[rrbegin:rri:rri]
+	}
+
+	if len(dns.Extra) > 0 {
+		rrbegin := rri
+		for i := 0; i < len(dns.Extra); i++ {
+			rrArr[rri] = dns.Extra[i].copy()
+			rri++
+		}
+		r1.Extra = rrArr[rrbegin:rri:rri]
+	}
+
+	return r1
+}
+
+func (q *Question) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) {
+	off, err := PackDomainName(q.Name, msg, off, compression, compress)
+	if err != nil {
+		return off, err
+	}
+	off, err = packUint16(q.Qtype, msg, off)
+	if err != nil {
+		return off, err
+	}
+	off, err = packUint16(q.Qclass, msg, off)
+	if err != nil {
+		return off, err
+	}
+	return off, nil
+}
+
+func unpackQuestion(msg []byte, off int) (Question, int, error) {
+	var (
+		q   Question
+		err error
+	)
+	q.Name, off, err = UnpackDomainName(msg, off)
+	if err != nil {
+		return q, off, err
+	}
+	if off == len(msg) {
+		return q, off, nil
+	}
+	q.Qtype, off, err = unpackUint16(msg, off)
+	if err != nil {
+		return q, off, err
+	}
+	if off == len(msg) {
+		return q, off, nil
+	}
+	q.Qclass, off, err = unpackUint16(msg, off)
+	if off == len(msg) {
+		return q, off, nil
+	}
+	return q, off, err
+}
+
+func (dh *Header) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) {
+	off, err := packUint16(dh.Id, msg, off)
+	if err != nil {
+		return off, err
+	}
+	off, err = packUint16(dh.Bits, msg, off)
+	if err != nil {
+		return off, err
+	}
+	off, err = packUint16(dh.Qdcount, msg, off)
+	if err != nil {
+		return off, err
+	}
+	off, err = packUint16(dh.Ancount, msg, off)
+	if err != nil {
+		return off, err
+	}
+	off, err = packUint16(dh.Nscount, msg, off)
+	if err != nil {
+		return off, err
+	}
+	off, err = packUint16(dh.Arcount, msg, off)
+	return off, err
+}
+
+func unpackMsgHdr(msg []byte, off int) (Header, int, error) {
+	var (
+		dh  Header
+		err error
+	)
+	dh.Id, off, err = unpackUint16(msg, off)
+	if err != nil {
+		return dh, off, err
+	}
+	dh.Bits, off, err = unpackUint16(msg, off)
+	if err != nil {
+		return dh, off, err
+	}
+	dh.Qdcount, off, err = unpackUint16(msg, off)
+	if err != nil {
+		return dh, off, err
+	}
+	dh.Ancount, off, err = unpackUint16(msg, off)
+	if err != nil {
+		return dh, off, err
+	}
+	dh.Nscount, off, err = unpackUint16(msg, off)
+	if err != nil {
+		return dh, off, err
+	}
+	dh.Arcount, off, err = unpackUint16(msg, off)
+	return dh, off, err
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/msg_generate.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/msg_generate.go
new file mode 100644
index 0000000..8ba609f
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/msg_generate.go
@@ -0,0 +1,348 @@
+//+build ignore
+
+// msg_generate.go is meant to run with go generate. It will use
+// go/{importer,types} to track down all the RR struct types. Then for each type
+// it will generate pack/unpack methods based on the struct tags. The generated source is
+// written to zmsg.go, and is meant to be checked into git.
+package main
+
+import (
+	"bytes"
+	"fmt"
+	"go/format"
+	"go/importer"
+	"go/types"
+	"log"
+	"os"
+	"strings"
+)
+
+var packageHdr = `
+// Code generated by "go run msg_generate.go"; DO NOT EDIT.
+
+package dns
+
+`
+
+// getTypeStruct will take a type and the package scope, and return the
+// (innermost) struct if the type is considered a RR type (currently defined as
+// those structs beginning with a RR_Header, could be redefined as implementing
+// the RR interface). The bool return value indicates if embedded structs were
+// resolved.
+func getTypeStruct(t types.Type, scope *types.Scope) (*types.Struct, bool) {
+	st, ok := t.Underlying().(*types.Struct)
+	if !ok {
+		return nil, false
+	}
+	if st.Field(0).Type() == scope.Lookup("RR_Header").Type() {
+		return st, false
+	}
+	if st.Field(0).Anonymous() {
+		st, _ := getTypeStruct(st.Field(0).Type(), scope)
+		return st, true
+	}
+	return nil, false
+}
+
+func main() {
+	// Import and type-check the package
+	pkg, err := importer.Default().Import("github.com/miekg/dns")
+	fatalIfErr(err)
+	scope := pkg.Scope()
+
+	// Collect actual types (*X)
+	var namedTypes []string
+	for _, name := range scope.Names() {
+		o := scope.Lookup(name)
+		if o == nil || !o.Exported() {
+			continue
+		}
+		if st, _ := getTypeStruct(o.Type(), scope); st == nil {
+			continue
+		}
+		if name == "PrivateRR" {
+			continue
+		}
+
+		// Check if corresponding TypeX exists
+		if scope.Lookup("Type"+o.Name()) == nil && o.Name() != "RFC3597" {
+			log.Fatalf("Constant Type%s does not exist.", o.Name())
+		}
+
+		namedTypes = append(namedTypes, o.Name())
+	}
+
+	b := &bytes.Buffer{}
+	b.WriteString(packageHdr)
+
+	fmt.Fprint(b, "// pack*() functions\n\n")
+	for _, name := range namedTypes {
+		o := scope.Lookup(name)
+		st, _ := getTypeStruct(o.Type(), scope)
+
+		fmt.Fprintf(b, "func (rr *%s) pack(msg []byte, off int, compression map[string]int, compress bool) (int, error) {\n", name)
+		fmt.Fprint(b, `off, err := rr.Hdr.pack(msg, off, compression, compress)
+if err != nil {
+	return off, err
+}
+headerEnd := off
+`)
+		for i := 1; i < st.NumFields(); i++ {
+			o := func(s string) {
+				fmt.Fprintf(b, s, st.Field(i).Name())
+				fmt.Fprint(b, `if err != nil {
+return off, err
+}
+`)
+			}
+
+			if _, ok := st.Field(i).Type().(*types.Slice); ok {
+				switch st.Tag(i) {
+				case `dns:"-"`: // ignored
+				case `dns:"txt"`:
+					o("off, err = packStringTxt(rr.%s, msg, off)\n")
+				case `dns:"opt"`:
+					o("off, err = packDataOpt(rr.%s, msg, off)\n")
+				case `dns:"nsec"`:
+					o("off, err = packDataNsec(rr.%s, msg, off)\n")
+				case `dns:"domain-name"`:
+					o("off, err = packDataDomainNames(rr.%s, msg, off, compression, compress)\n")
+				default:
+					log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
+				}
+				continue
+			}
+
+			switch {
+			case st.Tag(i) == `dns:"-"`: // ignored
+			case st.Tag(i) == `dns:"cdomain-name"`:
+				o("off, err = PackDomainName(rr.%s, msg, off, compression, compress)\n")
+			case st.Tag(i) == `dns:"domain-name"`:
+				o("off, err = PackDomainName(rr.%s, msg, off, compression, false)\n")
+			case st.Tag(i) == `dns:"a"`:
+				o("off, err = packDataA(rr.%s, msg, off)\n")
+			case st.Tag(i) == `dns:"aaaa"`:
+				o("off, err = packDataAAAA(rr.%s, msg, off)\n")
+			case st.Tag(i) == `dns:"uint48"`:
+				o("off, err = packUint48(rr.%s, msg, off)\n")
+			case st.Tag(i) == `dns:"txt"`:
+				o("off, err = packString(rr.%s, msg, off)\n")
+
+			case strings.HasPrefix(st.Tag(i), `dns:"size-base32`): // size-base32 can be packed just like base32
+				fallthrough
+			case st.Tag(i) == `dns:"base32"`:
+				o("off, err = packStringBase32(rr.%s, msg, off)\n")
+
+			case strings.HasPrefix(st.Tag(i), `dns:"size-base64`): // size-base64 can be packed just like base64
+				fallthrough
+			case st.Tag(i) == `dns:"base64"`:
+				o("off, err = packStringBase64(rr.%s, msg, off)\n")
+
+			case strings.HasPrefix(st.Tag(i), `dns:"size-hex:SaltLength`):
+				// directly write instead of using o() so we get the error check in the correct place
+				field := st.Field(i).Name()
+				fmt.Fprintf(b, `// Only pack salt if value is not "-", i.e. empty
+if rr.%s != "-" {
+  off, err = packStringHex(rr.%s, msg, off)
+  if err != nil {
+    return off, err
+  }
+}
+`, field, field)
+				continue
+			case strings.HasPrefix(st.Tag(i), `dns:"size-hex`): // size-hex can be packed just like hex
+				fallthrough
+			case st.Tag(i) == `dns:"hex"`:
+				o("off, err = packStringHex(rr.%s, msg, off)\n")
+
+			case st.Tag(i) == `dns:"octet"`:
+				o("off, err = packStringOctet(rr.%s, msg, off)\n")
+			case st.Tag(i) == "":
+				switch st.Field(i).Type().(*types.Basic).Kind() {
+				case types.Uint8:
+					o("off, err = packUint8(rr.%s, msg, off)\n")
+				case types.Uint16:
+					o("off, err = packUint16(rr.%s, msg, off)\n")
+				case types.Uint32:
+					o("off, err = packUint32(rr.%s, msg, off)\n")
+				case types.Uint64:
+					o("off, err = packUint64(rr.%s, msg, off)\n")
+				case types.String:
+					o("off, err = packString(rr.%s, msg, off)\n")
+				default:
+					log.Fatalln(name, st.Field(i).Name())
+				}
+			default:
+				log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
+			}
+		}
+		// We have packed everything, only now we know the rdlength of this RR
+		fmt.Fprintln(b, "rr.Header().Rdlength = uint16(off-headerEnd)")
+		fmt.Fprintln(b, "return off, nil }\n")
+	}
+
+	fmt.Fprint(b, "// unpack*() functions\n\n")
+	for _, name := range namedTypes {
+		o := scope.Lookup(name)
+		st, _ := getTypeStruct(o.Type(), scope)
+
+		fmt.Fprintf(b, "func unpack%s(h RR_Header, msg []byte, off int) (RR, int, error) {\n", name)
+		fmt.Fprintf(b, "rr := new(%s)\n", name)
+		fmt.Fprint(b, "rr.Hdr = h\n")
+		fmt.Fprint(b, `if noRdata(h) {
+return rr, off, nil
+	}
+var err error
+rdStart := off
+_ = rdStart
+
+`)
+		for i := 1; i < st.NumFields(); i++ {
+			o := func(s string) {
+				fmt.Fprintf(b, s, st.Field(i).Name())
+				fmt.Fprint(b, `if err != nil {
+return rr, off, err
+}
+`)
+			}
+
+			// size-* are special, because they reference a struct member we should use for the length.
+			if strings.HasPrefix(st.Tag(i), `dns:"size-`) {
+				structMember := structMember(st.Tag(i))
+				structTag := structTag(st.Tag(i))
+				switch structTag {
+				case "hex":
+					fmt.Fprintf(b, "rr.%s, off, err = unpackStringHex(msg, off, off + int(rr.%s))\n", st.Field(i).Name(), structMember)
+				case "base32":
+					fmt.Fprintf(b, "rr.%s, off, err = unpackStringBase32(msg, off, off + int(rr.%s))\n", st.Field(i).Name(), structMember)
+				case "base64":
+					fmt.Fprintf(b, "rr.%s, off, err = unpackStringBase64(msg, off, off + int(rr.%s))\n", st.Field(i).Name(), structMember)
+				default:
+					log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
+				}
+				fmt.Fprint(b, `if err != nil {
+return rr, off, err
+}
+`)
+				continue
+			}
+
+			if _, ok := st.Field(i).Type().(*types.Slice); ok {
+				switch st.Tag(i) {
+				case `dns:"-"`: // ignored
+				case `dns:"txt"`:
+					o("rr.%s, off, err = unpackStringTxt(msg, off)\n")
+				case `dns:"opt"`:
+					o("rr.%s, off, err = unpackDataOpt(msg, off)\n")
+				case `dns:"nsec"`:
+					o("rr.%s, off, err = unpackDataNsec(msg, off)\n")
+				case `dns:"domain-name"`:
+					o("rr.%s, off, err = unpackDataDomainNames(msg, off, rdStart + int(rr.Hdr.Rdlength))\n")
+				default:
+					log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
+				}
+				continue
+			}
+
+			switch st.Tag(i) {
+			case `dns:"-"`: // ignored
+			case `dns:"cdomain-name"`:
+				fallthrough
+			case `dns:"domain-name"`:
+				o("rr.%s, off, err = UnpackDomainName(msg, off)\n")
+			case `dns:"a"`:
+				o("rr.%s, off, err = unpackDataA(msg, off)\n")
+			case `dns:"aaaa"`:
+				o("rr.%s, off, err = unpackDataAAAA(msg, off)\n")
+			case `dns:"uint48"`:
+				o("rr.%s, off, err = unpackUint48(msg, off)\n")
+			case `dns:"txt"`:
+				o("rr.%s, off, err = unpackString(msg, off)\n")
+			case `dns:"base32"`:
+				o("rr.%s, off, err = unpackStringBase32(msg, off, rdStart + int(rr.Hdr.Rdlength))\n")
+			case `dns:"base64"`:
+				o("rr.%s, off, err = unpackStringBase64(msg, off, rdStart + int(rr.Hdr.Rdlength))\n")
+			case `dns:"hex"`:
+				o("rr.%s, off, err = unpackStringHex(msg, off, rdStart + int(rr.Hdr.Rdlength))\n")
+			case `dns:"octet"`:
+				o("rr.%s, off, err = unpackStringOctet(msg, off)\n")
+			case "":
+				switch st.Field(i).Type().(*types.Basic).Kind() {
+				case types.Uint8:
+					o("rr.%s, off, err = unpackUint8(msg, off)\n")
+				case types.Uint16:
+					o("rr.%s, off, err = unpackUint16(msg, off)\n")
+				case types.Uint32:
+					o("rr.%s, off, err = unpackUint32(msg, off)\n")
+				case types.Uint64:
+					o("rr.%s, off, err = unpackUint64(msg, off)\n")
+				case types.String:
+					o("rr.%s, off, err = unpackString(msg, off)\n")
+				default:
+					log.Fatalln(name, st.Field(i).Name())
+				}
+			default:
+				log.Fatalln(name, st.Field(i).Name(), st.Tag(i))
+			}
+			// If we've hit len(msg) we return without error.
+			if i < st.NumFields()-1 {
+				fmt.Fprintf(b, `if off == len(msg) {
+return rr, off, nil
+	}
+`)
+			}
+		}
+		fmt.Fprintf(b, "return rr, off, err }\n\n")
+	}
+	// Generate typeToUnpack map
+	fmt.Fprintln(b, "var typeToUnpack = map[uint16]func(RR_Header, []byte, int) (RR, int, error){")
+	for _, name := range namedTypes {
+		if name == "RFC3597" {
+			continue
+		}
+		fmt.Fprintf(b, "Type%s: unpack%s,\n", name, name)
+	}
+	fmt.Fprintln(b, "}\n")
+
+	// gofmt
+	res, err := format.Source(b.Bytes())
+	if err != nil {
+		b.WriteTo(os.Stderr)
+		log.Fatal(err)
+	}
+
+	// write result
+	f, err := os.Create("zmsg.go")
+	fatalIfErr(err)
+	defer f.Close()
+	f.Write(res)
+}
+
+// structMember will take a tag like dns:"size-base32:SaltLength" and return the last part of this string.
+func structMember(s string) string {
+	fields := strings.Split(s, ":")
+	if len(fields) == 0 {
+		return ""
+	}
+	f := fields[len(fields)-1]
+	// f should have a closing "
+	if len(f) > 1 {
+		return f[:len(f)-1]
+	}
+	return f
+}
+
+// structTag will take a tag like dns:"size-base32:SaltLength" and return base32.
+func structTag(s string) string {
+	fields := strings.Split(s, ":")
+	if len(fields) < 2 {
+		return ""
+	}
+	return fields[1][len("\"size-"):]
+}
+
+func fatalIfErr(err error) {
+	if err != nil {
+		log.Fatal(err)
+	}
+}
diff --git a/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/msg_helpers.go b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/msg_helpers.go
new file mode 100644
index 0000000..946d5ac
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/vendor/github.com/miekg/dns/msg_helpers.go
@@ -0,0 +1,637 @@
+package dns
+
+import (
+	"encoding/base32"
+	"encoding/base64"
+	"encoding/binary"
+	"encoding/hex"
+	"net"
+	"strconv"
+)
+
+// helper functions called from the generated zmsg.go
+
+// These function are named after the tag to help pack/unpack, if there is no tag it is the name
+// of the type they pack/unpack (string, int, etc). We prefix all with unpackData or packData, so packDataA or
+// packDataDomainName.
+
+func unpackDataA(msg []byte, off int) (net.IP, int, error) {
+	if off+net.IPv4len > len(msg) {
+		return nil, len(msg), &Error{err: "overflow unpacking a"}
+	}
+	a := append(make(net.IP, 0, net.IPv4len), msg[off:off+net.IPv4len]...)
+	off += net.IPv4len
+	return a, off, nil
+}
+
+func packDataA(a net.IP, msg []byte, off int) (int, error) {
+	// It must be a slice of 4, even if it is 16, we encode only the first 4
+	if off+net.IPv4len > len(msg) {
+		return len(msg), &Error{err: "overflow packing a"}
+	}
+	switch len(a) {
+	case net.IPv4len, net.IPv6len:
+		copy(msg[off:], a.To4())
+		off += net.IPv4len
+	case 0:
+		// Allowed, for dynamic updates.
+	default:
+		return len(msg), &Error{err: "overflow packing a"}
+	}
+	return off, nil
+}
+
+func unpackDataAAAA(msg []byte, off int) (net.IP, int, error) {
+	if off+net.IPv6len > len(msg) {
+		return nil, len(msg), &Error{err: "overflow unpacking aaaa"}
+	}
+	aaaa := append(make(net.IP, 0, net.IPv6len), msg[off:off+net.IPv6len]...)
+	off += net.IPv6len
+	return aaaa, off, nil
+}
+
+func packDataAAAA(aaaa net.IP, msg []byte, off int) (int, error) {
+	if off+net.IPv6len > len(msg) {
+		return len(msg), &Error{err: "overflow packing aaaa"}
+	}
+
+	switch len(aaaa) {
+	case net.IPv6len:
+		copy(msg[off:], aaaa)
+		off += net.IPv6len
+	case 0:
+		// Allowed, dynamic updates.
+	default:
+		return len(msg), &Error{err: "overflow packing aaaa"}
+	}
+	return off, nil
+}
+
+// unpackHeader unpacks an RR header, returning the offset to the end of the header and a
+// re-sliced msg according to the expected length of the RR.
+func unpackHeader(msg []byte, off int) (rr RR_Header, off1 int, truncmsg []byte, err error) {
+	hdr := RR_Header{}
+	if off == len(msg) {
+		return hdr, off, msg, nil
+	}
+
+	hdr.Name, off, err = UnpackDomainName(msg, off)
+	if err != nil {
+		return hdr, len(msg), msg, err
+	}
+	hdr.Rrtype, off, err = unpackUint16(msg, off)
+	if err != nil {
+		return hdr, len(msg), msg, err
+	}
+	hdr.Class, off, err = unpackUint16(msg, off)
+	if err != nil {
+		return hdr, len(msg), msg, err
+	}
+	hdr.Ttl, off, err = unpackUint32(msg, off)
+	if err != nil {
+		return hdr, len(msg), msg, err
+	}
+	hdr.Rdlength, off, err = unpackUint16(msg, off)
+	if err != nil {
+		return hdr, len(msg), msg, err
+	}
+	msg, err = truncateMsgFromRdlength(msg, off, hdr.Rdlength)
+	return hdr, off, msg, err
+}
+
+// pack packs an RR header, returning the offset to the end of the header.
+// See PackDomainName for documentation about the compression.
+func (hdr RR_Header) pack(msg []byte, off int, compression map[string]int, compress bool) (off1 int, err error) {
+	if off == len(msg) {
+		return off, nil
+	}
+
+	off, err = PackDomainName(hdr.Name, msg, off, compression, compress)
+	if err != nil {
+		return len(msg), err
+	}
+	off, err = packUint16(hdr.Rrtype, msg, off)
+	if err != nil {
+		return len(msg), err
+	}
+	off, err = packUint16(hdr.Class, msg, off)
+	if err != nil {
+		return len(msg), err
+	}
+	off, err = packUint32(hdr.Ttl, msg, off)
+	if err != nil {
+		return len(msg), err
+	}
+	off, err = packUint16(hdr.Rdlength, msg, off)
+	if err != nil {
+		return len(msg), err
+	}
+	return off, nil
+}
+
+// helper helper functions.
+
+// truncateMsgFromRdLength truncates msg to match the expected length of the RR.
+// Returns an error if msg is smaller than the expected size.
+func truncateMsgFromRdlength(msg []byte, off int, rdlength uint16) (truncmsg []byte, err error) {
+	lenrd := off + int(rdlength)
+	if lenrd > len(msg) {
+		return msg, &Error{err: "overflowing header size"}
+	}
+	return msg[:lenrd], nil
+}
+
+func fromBase32(s []byte) (buf []byte, err error) {
+	for i, b := range s {
+		if b >= 'a' && b <= 'z' {
+			s[i] = b - 32
+		}
+	}
+	buflen := base32.HexEncoding.DecodedLen(len(s))
+	buf = make([]byte, buflen)
+	n, err := base32.HexEncoding.Decode(buf, s)
+	buf = buf[:n]
+	return
+}
+
+func toBase32(b []byte) string { return base32.HexEncoding.EncodeToString(b) }
+
+func fromBase64(s []byte) (buf []byte, err error) {
+	buflen := base64.StdEncoding.DecodedLen(len(s))
+	buf = make([]byte, buflen)
+	n, err := base64.StdEncoding.Decode(buf, s)
+	buf = buf[:n]
+	return
+}
+
+func toBase64(b []byte) string { return base64.StdEncoding.EncodeToString(b) }
+
+// dynamicUpdate returns true if the Rdlength is zero.
+func noRdata(h RR_Header) bool { return h.Rdlength == 0 }
+
+func unpackUint8(msg []byte, off int) (i uint8, off1 int, err error) {
+	if off+1 > len(msg) {
+		return 0, len(msg), &Error{err: "overflow unpacking uint8"}
+	}
+	return uint8(msg[off]), off + 1, nil
+}
+
+func packUint8(i uint8, msg []byte, off int) (off1 int, err error) {
+	if off+1 > len(msg) {
+		return len(msg), &Error{err: "overflow packing uint8"}
+	}
+	msg[off] = byte(i)
+	return off + 1, nil
+}
+
+func unpackUint16(msg []byte, off int) (i uint16, off1 int, err error) {
+	if off+2 > len(msg) {
+		return 0, len(msg), &Error{err: "overflow unpacking uint16"}
+	}
+	return binary.BigEndian.Uint16(msg[off:]), off + 2, nil
+}
+
+func packUint16(i uint16, msg []byte, off int) (off1 int, err error) {
+	if off+2 > len(msg) {
+		return len(msg), &Error{err: "overflow packing uint16"}
+	}
+	binary.BigEndian.PutUint16(msg[off:], i)
+	return off + 2, nil
+}
+
+func unpackUint32(msg []byte, off int) (i uint32, off1 int, err error) {
+	if off+4 > len(msg) {
+		return 0, len(msg), &Error{err: "overflow unpacking uint32"}
+	}
+	return binary.BigEndian.Uint32(msg[off:]), off + 4, nil
+}
+
+func packUint32(i uint32, msg []byte, off int) (off1 int, err error) {
+	if off+4 > len(msg) {
+		return len(msg), &Error{err: "overflow packing uint32"}
+	}
+	binary.BigEndian.PutUint32(msg[off:], i)
+	return off + 4, nil
+}
+
+func unpackUint48(msg []byte, off int) (i uint64, off1 int, err error) {
+	if off+6 > len(msg) {
+		return 0, len(msg), &Error{err: "overflow unpacking uint64 as uint48"}
+	}
+	// Used in TSIG where the last 48 bits are occupied, so for now, assume a uint48 (6 bytes)
+	i = (uint64(uint64(msg[off])<<40 | uint64(msg[off+1])<<32 | uint64(msg[off+2])<<24 | uint64(msg[off+3])<<16 |
+		uint64(msg[off+4])<<8 | uint64(msg[off+5])))
+	off += 6
+	return i, off, nil
+}
+
+func packUint48(i uint64, msg []byte, off int) (off1 int, err error) {
+	if off+6 > len(msg) {
+		return len(msg), &Error{err: "overflow packing uint64 as uint48"}
+	}
+	msg[off] = byte(i >> 40)
+	msg[off+1] = byte(i >> 32)
+	msg[off+2] = byte(i >> 24)
+	msg[off+3] = byte(i >> 16)
+	msg[off+4] = byte(i >> 8)
+	msg[off+5] = byte(i)
+	off += 6
+	return off, nil
+}
+
+func unpackUint64(msg []byte, off int) (i uint64, off1 int, err error) {
+	if off+8 > len(msg) {
+		return 0, len(msg), &Error{err: "overflow unpacking uint64"}
+	}
+	return binary.BigEndian.Uint64(msg[off:]), off + 8, nil
+}
+
+func packUint64(i uint64, msg []byte, off int) (off1 int, err error) {
+	if off+8 > len(msg) {
+		return len(msg), &Error{err: "overflow packing uint64"}
+	}
+	binary.BigEndian.PutUint64(msg[off:], i)
+	off += 8
+	return off, nil
+}
+
+func unpackString(msg []byte, off int) (string, int, error) {
+	if off+1 > len(msg) {
+		return "", off, &Error{err: "overflow unpacking txt"}
+	}
+	l := int(msg[off])
+	if off+l+1 > len(msg) {
+		return "", off, &Error{err: "overflow unpacking txt"}
+	}
+	s := make([]byte, 0, l)
+	for _, b := range msg[off+1 : off+1+l] {
+		switch b {
+		case '"', '\\':
+			s = append(s, '\\', b)
+		default:
+			if b < 32 || b > 127 { // unprintable
+				var buf [3]byte
+				bufs := strconv.AppendInt(buf[:0], int64(b), 10)
+				s = append(s, '\\')
+				for i := 0; i < 3-len(bufs); i++ {
+					s = append(s, '0')
+				}
+				for _, r := range bufs {
+					s = append(s, r)
+				}
+			} else {
+				s = append(s, b)
+			}
+		}
+	}
+	off += 1 + l
+	return string(s), off, nil
+}
+
+func packString(s string, msg []byte, off int) (int, error) {
+	txtTmp := make([]byte, 256*4+1)
+	off, err := packTxtString(s, msg, off, txtTmp)
+	if err != nil {
+		return len(msg), err
+	}
+	return off, nil
+}
+
+func unpackStringBase32(msg []byte, off, end int) (string, int, error) {
+	if end > len(msg) {
+		return "", len(msg), &Error{err: "overflow unpacking base32"}
+	}
+	s := toBase32(msg[off:end])
+	return s, end, nil
+}
+
+func packStringBase32(s string, msg []byte, off int) (int, error) {
+	b32, err := fromBase32([]byte(s))
+	if err != nil {
+		return len(msg), err
+	}
+	if off+len(b32) > len(msg) {
+		return len(msg), &Error{err: "overflow packing base32"}
+	}
+	copy(msg[off:off+len(b32)], b32)
+	off += len(b32)
+	return off, nil
+}
+
+func unpackStringBase64(msg []byte, off, end int) (string, int, error) {
+	// Rest of the RR is base64 encoded value, so we don't need an explicit length
+	// to be set. Thus far all RR's that have base64 encoded fields have those as their
+	// last one. What we do need is the end of the RR!
+	if end > len(msg) {
+		return "", len(msg), &Error{err: "overflow unpacking base64"}
+	}
+	s := toBase64(msg[off:end])
+	return s, end, nil
+}
+
+func packStringBase64(s string, msg []byte, off int) (int, error) {
+	b64, err := fromBase64([]byte(s))
+	if err != nil {
+		return len(msg), err
+	}
+	if off+len(b64) > len(msg) {
+		return len(msg), &Error{err: "overflow packing base64"}
+	}
+	copy(msg[off:off+len(b64)], b64)
+	off += len(b64)
+	return off, nil
+}
+
+func unpackStringHex(msg []byte, off, end int) (string, int, error) {
+	// Rest of the RR is hex encoded value, so we don't need an explicit length
+	// to be set. NSEC and TSIG have hex fields with a length field.
+	// What we do need is the end of the RR!
+	if end > len(msg) {
+		return "", len(msg), &Error{err: "overflow unpacking hex"}
+	}
+
+	s := hex.EncodeToString(msg[off:end])
+	return s, end, nil
+}
+
+func packStringHex(s string, msg []byte, off int) (int, error) {
+	h, err := hex.DecodeString(s)
+	if err != nil {
+		return len(msg), err
+	}
+	if off+(len(h)) > len(msg) {
+		return len(msg), &Error{err: "overflow packing hex"}
+	}
+	copy(msg[off:off+len(h)], h)
+	off += len(h)
+	return off, nil
+}
+
+func unpackStringTxt(msg []byte, off int) ([]string, int, error) {
+	txt, off, err := unpackTxt(msg, off)
+	if err != nil {
+		return nil, len(msg), err
+	}
+	return txt, off, nil
+}
+
+func packStringTxt(s []string, msg []byte, off int) (int, error) {
+	txtTmp := make([]byte, 256*4+1) // If the whole string consists out of \DDD we need this many.
+	off, err := packTxt(s, msg, off, txtTmp)
+	if err != nil {
+		return len(msg), err
+	}
+	return off, nil
+}
+
+func unpackDataOpt(msg []byte, off int) ([]EDNS0, int, error) {
+	var edns []EDNS0
+Option:
+	code := uint16(0)
+	if off+4 > len(msg) {
+		return nil, len(msg), &Error{err: "overflow unpacking opt"}
+	}
+	code = binary.BigEndian.Uint16(msg[off:])
+	off += 2
+	optlen := binary.BigEndian.Uint16(msg[off:])
+	off += 2
+	if off+int(optlen) > len(msg) {
+		return nil, len(msg), &Error{err: "overflow unpacking opt"}
+	}
+	switch code {
+	case EDNS0NSID:
+		e := new(EDNS0_NSID)
+		if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
+			return nil, len(msg), err
+		}
+		edns = append(edns, e)
+		off += int(optlen)
+	case EDNS0SUBNET:
+		e := new(EDNS0_SUBNET)
+		if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
+			return nil, len(msg), err
+		}
+		edns = append(edns, e)
+		off += int(optlen)
+	case EDNS0COOKIE:
+		e := new(EDNS0_COOKIE)
+		if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
+			return nil, len(msg), err
+		}
+		edns = append(edns, e)
+		off += int(optlen)
+	case EDNS0UL:
+		e := new(EDNS0_UL)
+		if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
+			return nil, len(msg), err
+		}
+		edns = append(edns, e)
+		off += int(optlen)
+	case EDNS0LLQ:
+		e := new(EDNS0_LLQ)
+		if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
+			return nil, len(msg), err
+		}
+		edns = append(edns, e)
+		off += int(optlen)
+	case EDNS0DAU:
+		e := new(EDNS0_DAU)
+		if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
+			return nil, len(msg), err
+		}
+		edns = append(edns, e)
+		off += int(optlen)
+	case EDNS0DHU:
+		e := new(EDNS0_DHU)
+		if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
+			return nil, len(msg), err
+		}
+		edns = append(edns, e)
+		off += int(optlen)
+	case EDNS0N3U:
+		e := new(EDNS0_N3U)
+		if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
+			return nil, len(msg), err
+		}
+		edns = append(edns, e)
+		off += int(optlen)
+	case EDNS0PADDING:
+		e := new(EDNS0_PADDING)
+		if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
+			return nil, len(msg), err
+		}
+		edns = append(edns, e)
+		off += int(optlen)
+	default:
+		e := new(EDNS0_LOCAL)
+		e.Code = code
+		if err := e.unpack(msg[off : off+int(optlen)]); err != nil {
+			return nil, len(msg), err
+		}
+		edns = append(edns, e)
+		off += int(optlen)
+	}
+
+	if off < len(msg) {
+		goto Option
+	}
+
+	return edns, off, nil
+}
+
+func packDataOpt(options []EDNS0, msg []byte, off int) (int, error) {
+	for _, el := range options {
+		b, err := el.pack()
+		if err != nil || off+3 > len(msg) {
+			return len(msg), &Error{err: "overflow packing opt"}
+		}
+		binary.BigEndian.PutUint16(msg[off:], el.Option())      // Option code
+		binary.BigEndian.PutUint16(msg[off+2:], uint16(len(b))) // Length
+		off += 4
+		if off+len(b) > len(msg) {
+			copy(msg[off:], b)
+			off = len(msg)
+			continue
+		}
+		// Actual data
+		copy(msg[off:off+len(b)], b)
+		off += len(b)
+	}
+	return off, nil
+}
+
+func unpackStringOctet(msg []byte, off int) (string, int, error) {
+	s := string(msg[off:])
+	return s, len(msg), nil
+}
+
+func packStringOctet(s string, msg []byte, off int) (int, error) {
+	txtTmp := make([]byte, 256*4+1)
+	off, err := packOctetString(s, msg, off, txtTmp)
+	if err != nil {
+		return len(msg), err
+	}
+	return off, nil
+}
+
+func unpackDataNsec(msg []byte, off int) ([]uint16, int, error) {
+	var nsec []uint16
+	length, window, lastwindow := 0, 0, -1
+	for off < len(msg) {
+		if off+2 > len(msg) {
+			return nsec, len(msg), &Error{err: "overflow unpacking nsecx"}
+		}
+		window = int(msg[off])
+		length = int(msg[off+1])
+		off += 2
+		if window <= lastwindow {
+			// RFC 4034: Blocks are present in the NSEC RR RDATA in
+			// increasing numerical order.
+			return nsec, len(msg), &Error{err: "out of order NSEC block"}
+		}
+		if length == 0 {
+			// RFC 4034: Blocks with no types present MUST NOT be included.
+			return nsec, len(msg), &Error{err: "empty NSEC block"}
+		}
+		if length > 32 {
+			return nsec, len(msg), &Error{err: "NSEC block too long"}
+		}
+		if off+length > len(msg) {
+			return nsec, len(msg), &Error{err: "overflowing NSEC block"}
+		}
+
+		// Walk the bytes in the window and extract the type bits
+		for j := 0; j < length; j++ {
+			b := msg[off+j]
+			// Check the bits one by one, and set the type
+			if b&0x80 == 0x80 {
+				nsec = append(nsec, uint16(window*256+j*8+0))
+			}
+			if b&0x40 == 0x40 {
+				nsec = append(nsec, uint16(window*256+j*8+1))
+			}
+			if b&0x20 == 0x20 {
+				nsec = append(nsec, uint16(window*256+j*8+2))
+			}
+			if b&0x10 == 0x10 {
+				nsec = append(nsec, uint16(window*256+j*8+3))
+			}
+			if b&0x8 == 0x8 {
+				nsec = append(nsec, uint16(window*256+j*8+4))
+			}
+			if b&0x4 == 0x4 {
+				nsec = append(nsec, uint16(window*256+j*8+5))
+			}
+			if b&0x2 == 0x2 {
+				nsec = append(nsec, uint16(window*256+j*8+6))
+			}
+			if b&0x1 == 0x1 {
+				nsec = append(nsec, uint16(window*256+j*8+7))
+			}
+		}
+		off += length
+		lastwindow = window
+	}
+	return nsec, off, nil
+}
+
+func packDataNsec(bitmap []uint16, msg []byte, off int) (int, error) {
+	if len(bitmap) == 0 {
+		return off, nil
+	}
+	var lastwindow, lastlength uint16
+	for j := 0; j < len(bitmap); j++ {
+		t := bitmap[j]
+		window := t / 256
+		length := (t-window*256)/8 + 1
+		if window > lastwindow && lastlength != 0 { // New window, jump to the new offset
+			off += int(lastlength) + 2
+			lastlength = 0
+		}
+		if window < lastwindow || length < lastlength {
+			return len(msg), &Error{err: "nsec bits out of order"}
+		}
+		if off+2+int(length) > len(msg) {
+			return len(msg), &Error{err: "overflow packing nsec"}
+		}
+		// Setting the window #
+		msg[off] = byte(window)
+		// Setting the octets length
+		msg[off+1] = byte(length)
+		// Setting the bit value for the type in the right octet
+		msg[off+1+int(length)] |= byte(1 << (7 - (t % 8)))
+		lastwindow, lastlength = window, length
+	}
+	off += int(lastlength) + 2
+	return off, nil
+}
+
+func unpackDataDomainNames(msg []byte, off, end int) ([]string, int, error) {
... 15531 lines suppressed ...

-- 
To stop receiving notification emails like this one, please contact
dewrich@apache.org.