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

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


The following commit(s) were added to refs/heads/master by this push:
     new c93160a  Revert Delivery Service type alias (#4975)
c93160a is described below

commit c93160a93b8914522561b46036828e14e5119f1f
Author: ocket8888 <[email protected]>
AuthorDate: Thu Aug 20 10:04:21 2020 -0600

    Revert Delivery Service type alias (#4975)
    
    * Reverted updating the 'DeliveryServiceNullable' alias to v3
    
    Instead, 'DeliveryServiceNullable' is now pinned to 
'DeliveryServiceNullableV15', as before, and is deprecated
    in favor of versioned structures. This also includes creating new client 
methods to return the v3 structures
    and new versioned response types to contain them.
    
    * Fix panics in DSV30 validation
    
    * Fixed panic in atstccfg getting DS by CDNID
    
    * Fix GetDeliveryServicesV30 not using passed parameters
    
    * Fix API tests
---
 lib/go-atscfg/cacheurldotconfig.go                 |   2 +-
 lib/go-atscfg/headerrewritedotconfig.go            |   2 +-
 lib/go-atscfg/hostingdotconfig.go                  |   2 +-
 lib/go-atscfg/hostingdotconfig_test.go             |  10 +-
 lib/go-atscfg/meta.go                              |   6 +-
 lib/go-atscfg/meta_test.go                         |   2 +-
 lib/go-atscfg/regexremapdotconfig.go               |   2 +-
 lib/go-atscfg/sslmulticertdotconfig.go             |   2 +-
 lib/go-atscfg/topologyheaderrewritedotconfig.go    |   2 +-
 lib/go-tc/deliveryservices.go                      | 143 ++++++++++++++--
 .../api/v3/cachegroupsdeliveryservices_test.go     |   2 +-
 .../testing/api/v3/deliveryservices_test.go        | 126 ++++++++------
 .../testing/api/v3/deliveryserviceservers_test.go  |   6 +-
 traffic_ops/testing/api/v3/session_test.go         |   3 +-
 traffic_ops/testing/api/v3/traffic_control_test.go |   2 +-
 .../deliveryservice/deliveryservices.go            |  37 ++---
 .../traffic_ops_golang/deliveryservice/safe.go     |   2 +-
 traffic_ops/v3-client/deliveryservice.go           | 183 +++++++++++++++++++++
 .../atstccfg/cfgfile/cacheurldotconfig.go          |   2 +-
 traffic_ops_ort/atstccfg/cfgfile/cfgfile.go        |   9 +-
 traffic_ops_ort/atstccfg/cfgfile/cfgfile_test.go   |   6 +-
 .../atstccfg/cfgfile/headerrewritedotconfig.go     |   2 +-
 .../atstccfg/cfgfile/headerrewritemiddotconfig.go  |   2 +-
 .../atstccfg/cfgfile/hostingdotconfig.go           |   2 +-
 traffic_ops_ort/atstccfg/cfgfile/meta.go           |   2 +-
 .../atstccfg/cfgfile/parentdotconfig.go            |   6 +-
 .../atstccfg/cfgfile/regexremapdotconfig.go        |   4 +-
 traffic_ops_ort/atstccfg/cfgfile/remapdotconfig.go |   2 +-
 .../cfgfile/topologyheaderrewritedotconfig.go      |   2 +-
 traffic_ops_ort/atstccfg/config/config.go          |   2 +-
 traffic_ops_ort/atstccfg/toreqnew/toreqnew.go      |  12 +-
 31 files changed, 464 insertions(+), 123 deletions(-)

diff --git a/lib/go-atscfg/cacheurldotconfig.go 
b/lib/go-atscfg/cacheurldotconfig.go
index 1f48110..263984a 100644
--- a/lib/go-atscfg/cacheurldotconfig.go
+++ b/lib/go-atscfg/cacheurldotconfig.go
@@ -35,7 +35,7 @@ type CacheURLDS struct {
        CacheURL      string
 }
 
-func DeliveryServicesToCacheURLDSes(dses []tc.DeliveryServiceNullable) 
map[tc.DeliveryServiceName]CacheURLDS {
+func DeliveryServicesToCacheURLDSes(dses []tc.DeliveryServiceNullableV30) 
map[tc.DeliveryServiceName]CacheURLDS {
        sDSes := map[tc.DeliveryServiceName]CacheURLDS{}
        for _, ds := range dses {
                if ds.OrgServerFQDN == nil || ds.QStringIgnore == nil || 
ds.XMLID == nil || ds.Active == nil {
diff --git a/lib/go-atscfg/headerrewritedotconfig.go 
b/lib/go-atscfg/headerrewritedotconfig.go
index d1d3fd8..ab8575f 100644
--- a/lib/go-atscfg/headerrewritedotconfig.go
+++ b/lib/go-atscfg/headerrewritedotconfig.go
@@ -104,7 +104,7 @@ func HeaderRewriteServerFromServerNotNullable(sv tc.Server) 
(HeaderRewriteServer
        return HeaderRewriteServer{Status: status, HostName: sv.HostName, 
DomainName: sv.DomainName, Port: sv.TCPPort}, nil
 }
 
-func HeaderRewriteDSFromDS(ds *tc.DeliveryServiceNullable) (HeaderRewriteDS, 
error) {
+func HeaderRewriteDSFromDS(ds *tc.DeliveryServiceNullableV30) 
(HeaderRewriteDS, error) {
        errs := []error{}
        if ds.ID == nil {
                errs = append(errs, errors.New("ID cannot be nil"))
diff --git a/lib/go-atscfg/hostingdotconfig.go 
b/lib/go-atscfg/hostingdotconfig.go
index f2d57ef..4e85149 100644
--- a/lib/go-atscfg/hostingdotconfig.go
+++ b/lib/go-atscfg/hostingdotconfig.go
@@ -43,7 +43,7 @@ func MakeHostingDotConfig(
        toToolName string, // tm.toolname global parameter (TODO: cache itself?)
        toURL string, // tm.url global parameter (TODO: cache itself?)
        params map[string]string, // map[name]value - config file should always 
be storage.config
-       dses []tc.DeliveryServiceNullable,
+       dses []tc.DeliveryServiceNullableV30,
        topologies []tc.Topology,
 ) string {
        text := GenericHeaderComment(server.HostName, toToolName, toURL)
diff --git a/lib/go-atscfg/hostingdotconfig_test.go 
b/lib/go-atscfg/hostingdotconfig_test.go
index e1b309e..9efb213 100644
--- a/lib/go-atscfg/hostingdotconfig_test.go
+++ b/lib/go-atscfg/hostingdotconfig_test.go
@@ -44,9 +44,9 @@ func TestMakeHostingDotConfig(t *testing.T) {
                "https://origin4.example.net/";,
                "http://origin5.example.net/";,
        }
-       dses := []tc.DeliveryServiceNullable{}
+       dses := []tc.DeliveryServiceNullableV30{}
        for _, origin := range origins {
-               ds := tc.DeliveryServiceNullable{}
+               ds := tc.DeliveryServiceNullableV30{}
                ds.OrgServerFQDN = util.StrPtr(origin)
                dses = append(dses, ds)
        }
@@ -101,19 +101,19 @@ func TestMakeHostingDotConfigTopologiesIgnoreDSS(t 
*testing.T) {
                "somethingelse":     "somethingelse-shouldnotappearinconfig",
        }
 
-       dsTopology := tc.DeliveryServiceNullable{}
+       dsTopology := tc.DeliveryServiceNullableV30{}
        dsTopology.OrgServerFQDN = util.StrPtr("https://origin0.example.net";)
        dsTopology.XMLID = util.StrPtr("ds-topology")
        dsTopology.Topology = util.StrPtr("t0")
        dsTopology.Active = util.BoolPtr(true)
 
-       dsTopologyWithoutServer := tc.DeliveryServiceNullable{}
+       dsTopologyWithoutServer := tc.DeliveryServiceNullableV30{}
        dsTopologyWithoutServer.OrgServerFQDN = 
util.StrPtr("https://origin1.example.net";)
        dsTopologyWithoutServer.XMLID = 
util.StrPtr("ds-topology-without-server")
        dsTopologyWithoutServer.Topology = util.StrPtr("t1")
        dsTopologyWithoutServer.Active = util.BoolPtr(true)
 
-       dses := []tc.DeliveryServiceNullable{dsTopology, 
dsTopologyWithoutServer}
+       dses := []tc.DeliveryServiceNullableV30{dsTopology, 
dsTopologyWithoutServer}
 
        topologies := []tc.Topology{
                tc.Topology{
diff --git a/lib/go-atscfg/meta.go b/lib/go-atscfg/meta.go
index cd23d76..9a3d23e 100644
--- a/lib/go-atscfg/meta.go
+++ b/lib/go-atscfg/meta.go
@@ -66,7 +66,7 @@ func MakeMetaConfig(
        locationParams map[string]ConfigProfileParams, // 
map[configFile]params; 'location' and 'URL' Parameters on serverHostName's 
Profile
        uriSignedDSes []tc.DeliveryServiceName,
        scopeParams map[string]string, // map[configFileName]scopeParam
-       dses map[tc.DeliveryServiceName]tc.DeliveryServiceNullable,
+       dses map[tc.DeliveryServiceName]tc.DeliveryServiceNullableV30,
        cacheGroupArr []tc.CacheGroupNullable,
        topologies []tc.Topology,
 ) string {
@@ -101,7 +101,7 @@ func AddMetaObjConfigDir(
        locationParams map[string]ConfigProfileParams, // 
map[configFile]params; 'location' and 'URL' Parameters on serverHostName's 
Profile
        uriSignedDSes []tc.DeliveryServiceName,
        scopeParams map[string]string, // map[configFileName]scopeParam
-       dses map[tc.DeliveryServiceName]tc.DeliveryServiceNullable,
+       dses map[tc.DeliveryServiceName]tc.DeliveryServiceNullableV30,
        cacheGroupArr []tc.CacheGroupNullable,
        topologies []tc.Topology,
 ) (tc.ATSConfigMetaData, error) {
@@ -273,7 +273,7 @@ func MakeMetaObj(
        locationParams map[string]ConfigProfileParams, // 
map[configFile]params; 'location' and 'URL' Parameters on serverHostName's 
Profile
        uriSignedDSes []tc.DeliveryServiceName,
        scopeParams map[string]string, // map[configFileName]scopeParam
-       dses map[tc.DeliveryServiceName]tc.DeliveryServiceNullable,
+       dses map[tc.DeliveryServiceName]tc.DeliveryServiceNullableV30,
        cacheGroupArr []tc.CacheGroupNullable,
        topologies []tc.Topology,
        configDir string,
diff --git a/lib/go-atscfg/meta_test.go b/lib/go-atscfg/meta_test.go
index fd5ed97..874c26e 100644
--- a/lib/go-atscfg/meta_test.go
+++ b/lib/go-atscfg/meta_test.go
@@ -110,7 +110,7 @@ func TestMakeMetaConfig(t *testing.T) {
                },
        }
        uriSignedDSes := []tc.DeliveryServiceName{"mydsname"}
-       dses := 
map[tc.DeliveryServiceName]tc.DeliveryServiceNullable{"mydsname": {}}
+       dses := 
map[tc.DeliveryServiceName]tc.DeliveryServiceNullableV30{"mydsname": {}}
 
        scopeParams := map[string]string{"custom.config": 
string(tc.ATSConfigMetaDataConfigFileScopeProfiles)}
 
diff --git a/lib/go-atscfg/regexremapdotconfig.go 
b/lib/go-atscfg/regexremapdotconfig.go
index 76447c1..4d643dd 100644
--- a/lib/go-atscfg/regexremapdotconfig.go
+++ b/lib/go-atscfg/regexremapdotconfig.go
@@ -36,7 +36,7 @@ type CDNDS struct {
        RegexRemap    string
 }
 
-func DeliveryServicesToCDNDSes(dses []tc.DeliveryServiceNullable) 
map[tc.DeliveryServiceName]CDNDS {
+func DeliveryServicesToCDNDSes(dses []tc.DeliveryServiceNullableV30) 
map[tc.DeliveryServiceName]CDNDS {
        sDSes := map[tc.DeliveryServiceName]CDNDS{}
        for _, ds := range dses {
                if ds.OrgServerFQDN == nil || ds.QStringIgnore == nil || 
ds.XMLID == nil {
diff --git a/lib/go-atscfg/sslmulticertdotconfig.go 
b/lib/go-atscfg/sslmulticertdotconfig.go
index d9b0139..e40c8af 100644
--- a/lib/go-atscfg/sslmulticertdotconfig.go
+++ b/lib/go-atscfg/sslmulticertdotconfig.go
@@ -38,7 +38,7 @@ type SSLMultiCertDS struct {
        ExampleURLs []string
 }
 
-func DeliveryServicesToSSLMultiCertDSes(dses []tc.DeliveryServiceNullable) 
map[tc.DeliveryServiceName]SSLMultiCertDS {
+func DeliveryServicesToSSLMultiCertDSes(dses []tc.DeliveryServiceNullableV30) 
map[tc.DeliveryServiceName]SSLMultiCertDS {
        sDSes := map[tc.DeliveryServiceName]SSLMultiCertDS{}
        for _, ds := range dses {
                if ds.Type == nil || ds.Protocol == nil || ds.XMLID == nil {
diff --git a/lib/go-atscfg/topologyheaderrewritedotconfig.go 
b/lib/go-atscfg/topologyheaderrewritedotconfig.go
index 094bcfc..a464bf2 100644
--- a/lib/go-atscfg/topologyheaderrewritedotconfig.go
+++ b/lib/go-atscfg/topologyheaderrewritedotconfig.go
@@ -47,7 +47,7 @@ func MakeTopologyHeaderRewriteDotConfig(
        server tc.Server,
        toToolName string, // tm.toolname global parameter (TODO: cache itself?)
        toURL string, // tm.url global parameter (TODO: cache itself?)
-       ds tc.DeliveryServiceNullable,
+       ds tc.DeliveryServiceNullableV30,
        topologies []tc.Topology,
        cacheGroupArr []tc.CacheGroupNullable,
        servers []tc.Server,
diff --git a/lib/go-tc/deliveryservices.go b/lib/go-tc/deliveryservices.go
index 8cf11dd..99342c4 100644
--- a/lib/go-tc/deliveryservices.go
+++ b/lib/go-tc/deliveryservices.go
@@ -45,7 +45,16 @@ type DeliveryServicesResponse struct {
        Alerts
 }
 
+// DeliveryServicesResponseV30 is the type of a response from the
+// /api/3.0/deliveryservices Traffic Ops endpoint.
+// TODO: Move these into the respective clients?
+type DeliveryServicesResponseV30 struct {
+       Response []DeliveryServiceNullableV30 `json:"response"`
+       Alerts
+}
+
 // DeliveryServicesNullableResponse ...
+// Deprecated: Please only use the versioned structures.
 type DeliveryServicesNullableResponse struct {
        Response []DeliveryServiceNullable `json:"response"`
        Alerts
@@ -59,6 +68,7 @@ type CreateDeliveryServiceResponse struct {
 }
 
 // CreateDeliveryServiceNullableResponse ...
+// Deprecated: Please only use the versioned structures.
 type CreateDeliveryServiceNullableResponse struct {
        Response []DeliveryServiceNullable `json:"response"`
        Alerts
@@ -72,6 +82,7 @@ type UpdateDeliveryServiceResponse struct {
 }
 
 // UpdateDeliveryServiceNullableResponse ...
+// Deprecated: Please only use the versioned structures.
 type UpdateDeliveryServiceNullableResponse struct {
        Response []DeliveryServiceNullable `json:"response"`
        Alerts
@@ -159,9 +170,7 @@ type DeliveryServiceV11 struct {
        XMLID                    string                 `json:"xmlId"`
 }
 
-type DeliveryServiceNullableV30 DeliveryServiceNullable // this type alias 
should always alias the latest minor version of the deliveryservices endpoints
-
-type DeliveryServiceNullable struct {
+type DeliveryServiceNullableV30 struct {
        DeliveryServiceNullableV15
        Topology           *string `json:"topology" db:"topology"`
        FirstHeaderRewrite *string `json:"firstHeaderRewrite" 
db:"first_header_rewrite"`
@@ -170,6 +179,8 @@ type DeliveryServiceNullable struct {
        ServiceCategory    *string `json:"serviceCategory" 
db:"service_category"`
 }
 
+// Deprecated: Use versioned structures only from now on.
+type DeliveryServiceNullable DeliveryServiceNullableV15
 type DeliveryServiceNullableV15 struct {
        DeliveryServiceNullableV14
        EcsEnabled          bool `json:"ecsEnabled" db:"ecs_enabled"`
@@ -324,7 +335,7 @@ func ParseOrgServerFQDN(orgServerFQDN string) (*string, 
*string, *string, error)
        return &protocol, &FQDN, port, nil
 }
 
-func (ds *DeliveryServiceNullable) Sanitize() {
+func (ds *DeliveryServiceNullableV30) Sanitize() {
        if ds.GeoLimitCountries != nil {
                *ds.GeoLimitCountries = 
strings.ToUpper(strings.Replace(*ds.GeoLimitCountries, " ", "", -1))
        }
@@ -433,9 +444,81 @@ func (ds *DeliveryServiceNullable) validateTypeFields(tx 
*sql.Tx) error {
                        
validation.By(requiredIfMatchesTypeName([]string{DNSRegexType, HTTPRegexType}, 
typeName))),
                "rangeRequestHandling": 
validation.Validate(ds.RangeRequestHandling,
                        
validation.By(requiredIfMatchesTypeName([]string{DNSRegexType, HTTPRegexType}, 
typeName))),
+       }
+       toErrs := tovalidate.ToErrors(errs)
+       if len(toErrs) > 0 {
+               return errors.New(util.JoinErrsStr(toErrs))
+       }
+       return nil
+}
+
+func (ds *DeliveryServiceNullableV30) validateTypeFields(tx *sql.Tx) error {
+       // Validate the TypeName related fields below
+       err := error(nil)
+       DNSRegexType := "^DNS.*$"
+       HTTPRegexType := "^HTTP.*$"
+       SteeringRegexType := "^STEERING.*$"
+       latitudeErr := "Must be a floating point number within the range +-90"
+       longitudeErr := "Must be a floating point number within the range +-180"
+
+       typeName, err := ValidateTypeID(tx, ds.TypeID, "deliveryservice")
+       if err != nil {
+               return err
+       }
+
+       errs := validation.Errors{
+               "consistentHashQueryParams": validation.Validate(ds,
+                       validation.By(func(dsi interface{}) error {
+                               ds := dsi.(*DeliveryServiceNullableV30)
+                               if len(ds.ConsistentHashQueryParams) == 0 || 
DSType(typeName).IsHTTP() {
+                                       return nil
+                               }
+                               return fmt.Errorf("consistentHashQueryParams 
not allowed for '%s' deliveryservice type", typeName)
+                       })),
+               "initialDispersion": validation.Validate(ds.InitialDispersion,
+                       
validation.By(requiredIfMatchesTypeName([]string{HTTPRegexType}, typeName)),
+                       validation.By(tovalidate.IsGreaterThanZero)),
+               "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)),
+                       validation.Min(-90.0).Error(latitudeErr),
+                       validation.Max(90.0).Error(latitudeErr)),
+               "missLong": validation.Validate(ds.MissLong,
+                       
validation.By(requiredIfMatchesTypeName([]string{DNSRegexType, HTTPRegexType}, 
typeName)),
+                       validation.Min(-180.0).Error(longitudeErr),
+                       validation.Max(180.0).Error(longitudeErr)),
+               "multiSiteOrigin": validation.Validate(ds.MultiSiteOrigin,
+                       
validation.By(requiredIfMatchesTypeName([]string{DNSRegexType, HTTPRegexType}, 
typeName))),
+               "orgServerFqdn": validation.Validate(ds.OrgServerFQDN,
+                       
validation.By(requiredIfMatchesTypeName([]string{DNSRegexType, HTTPRegexType}, 
typeName)),
+                       validation.NewStringRule(validateOrgServerFQDN, "must 
start with http:// or https:// and be followed by a valid hostname with an 
optional port (no trailing slash)")),
+               "rangeSliceBlockSize": validation.Validate(ds,
+                       validation.By(func(dsi interface{}) error {
+                               ds := dsi.(*DeliveryServiceNullableV30)
+                               if ds.RangeRequestHandling != nil {
+                                       if *ds.RangeRequestHandling == 3 {
+                                               return 
validation.Validate(ds.RangeSliceBlockSize, validation.Required,
+                                                       // Per Slice Plugin 
implementation
+                                                       validation.Min(262144), 
  // 256KiB
+                                                       
validation.Max(33554432), // 32MiB
+                                               )
+                                       }
+                                       if ds.RangeSliceBlockSize != nil {
+                                               return 
errors.New("rangeSliceBlockSize can only be set if the rangeRequestHandling is 
set to 3 (Use the Slice Plugin)")
+                                       }
+                               }
+                               return nil
+                       })),
+               "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))),
                "topology": validation.Validate(ds,
                        validation.By(func(dsi interface{}) error {
-                               ds := dsi.(*DeliveryServiceNullable)
+                               ds := dsi.(*DeliveryServiceNullableV30)
                                if ds.Topology != nil && 
DSType(typeName).IsSteering() {
                                        return fmt.Errorf("steering 
deliveryservice types cannot be assigned to a topology")
                                }
@@ -450,6 +533,37 @@ func (ds *DeliveryServiceNullable) validateTypeFields(tx 
*sql.Tx) error {
 }
 
 func (ds *DeliveryServiceNullable) Validate(tx *sql.Tx) error {
+       neverOrAlways := 
validation.NewStringRule(tovalidate.IsOneOfStringICase("NEVER", "ALWAYS"),
+               "must be one of 'NEVER' or 'ALWAYS'")
+       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")
+       noLineBreaks := validation.NewStringRule(tovalidate.NoLineBreaks, 
"cannot contain line breaks")
+       errs := tovalidate.ToErrors(validation.Errors{
+               "active":              validation.Validate(ds.Active, 
validation.NotNil),
+               "cdnId":               validation.Validate(ds.CDNID, 
validation.Required),
+               "deepCachingType":     validation.Validate(ds.DeepCachingType, 
neverOrAlways),
+               "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),
+               "remapText":           validation.Validate(ds.RemapText, 
noLineBreaks),
+               "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, 
validation.Required, noSpaces, noPeriods, validation.Length(1, 48)),
+       })
+       if err := ds.validateTypeFields(tx); err != nil {
+               errs = append(errs, errors.New("type fields: "+err.Error()))
+       }
+       if len(errs) == 0 {
+               return nil
+       }
+       return util.JoinErrs(errs)
+}
+
+func (ds *DeliveryServiceNullableV30) Validate(tx *sql.Tx) error {
        ds.Sanitize()
        neverOrAlways := 
validation.NewStringRule(tovalidate.IsOneOfStringICase("NEVER", "ALWAYS"),
                "must be one of 'NEVER' or 'ALWAYS'")
@@ -484,7 +598,7 @@ func (ds *DeliveryServiceNullable) Validate(tx *sql.Tx) 
error {
        return util.JoinErrs(errs)
 }
 
-func (ds *DeliveryServiceNullable) validateTopologyFields() error {
+func (ds *DeliveryServiceNullableV30) validateTopologyFields() error {
        if ds.Topology != nil && (ds.EdgeHeaderRewrite != nil || 
ds.MidHeaderRewrite != nil) {
                return errors.New("cannot set edgeHeaderRewrite or 
midHeaderRewrite while a Topology is assigned. Use firstHeaderRewrite, 
innerHeaderRewrite, and/or lastHeaderRewrite instead")
        }
@@ -678,12 +792,21 @@ func (r *DeliveryServiceSafeUpdateRequest) 
Validate(*sql.Tx) error {
        return nil
 }
 
-// DeliveryServiceSafeUpdateResponse represents Traffic Ops's response to a 
PUT request to its
-// /deliveryservices/{{ID}}/safe endpoint.
+// DeliveryServiceSafeUpdateResponse represents Traffic Ops's response to a PUT
+// request to its /deliveryservices/{{ID}}/safe endpoint.
+// Deprecated: Please only use versioned structures.
 type DeliveryServiceSafeUpdateResponse struct {
        Alerts
        // Response contains the representation of the Delivery Service after 
it has been updated.
-       // Note that this should be "cast" to the appropriate version of 
Delivery Service, reflecting
-       // the API version that was called.
        Response []DeliveryServiceNullable `json:"response"`
 }
+
+// DeliveryServiceSafeUpdateResponse represents Traffic Ops's response to a PUT
+// request to its /api/3.0/deliveryservices/{{ID}}/safe endpoint.
+// Deprecated: Please only use versioned structures.
+type DeliveryServiceSafeUpdateResponseV30 struct {
+       Alerts
+       // Response contains the representation of the Delivery Service after 
it has
+       // been updated.
+       Response []DeliveryServiceNullableV30 `json:"response"`
+}
diff --git a/traffic_ops/testing/api/v3/cachegroupsdeliveryservices_test.go 
b/traffic_ops/testing/api/v3/cachegroupsdeliveryservices_test.go
index 2b3a9a6..5189342 100644
--- a/traffic_ops/testing/api/v3/cachegroupsdeliveryservices_test.go
+++ b/traffic_ops/testing/api/v3/cachegroupsdeliveryservices_test.go
@@ -40,7 +40,7 @@ func CreateTestCachegroupsDeliveryServices(t *testing.T) {
                t.Fatalf("cannot test cachegroups delivery services: expected 
no initial delivery service servers, actual %v", len(dss.Response))
        }
 
-       dses, _, err := TOSession.GetDeliveryServicesNullable(nil)
+       dses, _, err := TOSession.GetDeliveryServicesV30(nil, nil)
        if err != nil {
                t.Fatalf("cannot GET DeliveryServices: %v - %v", err, dses)
        }
diff --git a/traffic_ops/testing/api/v3/deliveryservices_test.go 
b/traffic_ops/testing/api/v3/deliveryservices_test.go
index 2f373be..8ea06c4 100644
--- a/traffic_ops/testing/api/v3/deliveryservices_test.go
+++ b/traffic_ops/testing/api/v3/deliveryservices_test.go
@@ -21,6 +21,7 @@ import (
        "fmt"
        "github.com/apache/trafficcontrol/lib/go-rfc"
        "net/http"
+       "net/url"
        "reflect"
        "strconv"
        "testing"
@@ -28,7 +29,7 @@ import (
 
        "github.com/apache/trafficcontrol/lib/go-tc"
        "github.com/apache/trafficcontrol/lib/go-util"
-       toclient "github.com/apache/trafficcontrol/traffic_ops/client"
+       toclient "github.com/apache/trafficcontrol/traffic_ops/v3-client"
 )
 
 func TestDeliveryServices(t *testing.T) {
@@ -55,7 +56,7 @@ func TestDeliveryServices(t *testing.T) {
 }
 
 func GetTestDeliveryServicesIMSAfterChange(t *testing.T, header http.Header) {
-       _, reqInf, err := TOSession.GetDeliveryServicesNullable(header)
+       _, reqInf, err := TOSession.GetDeliveryServicesV30(header, nil)
        if err != nil {
                t.Fatalf("could not GET Delivery Services: %v", err)
        }
@@ -66,7 +67,7 @@ func GetTestDeliveryServicesIMSAfterChange(t *testing.T, 
header http.Header) {
        currentTime = currentTime.Add(1 * time.Second)
        timeStr := currentTime.Format(time.RFC1123)
        header.Set(rfc.IfModifiedSince, timeStr)
-       _, reqInf, err = TOSession.GetDeliveryServicesNullable(header)
+       _, reqInf, err = TOSession.GetDeliveryServicesV30(header, nil)
        if err != nil {
                t.Fatalf("could not GET Delivery Services: %v", err)
        }
@@ -78,12 +79,12 @@ func GetTestDeliveryServicesIMSAfterChange(t *testing.T, 
header http.Header) {
 func PostDeliveryServiceTest(t *testing.T) {
        ds := testData.DeliveryServices[0]
        ds.XMLID = util.StrPtr("")
-       _, err := TOSession.CreateDeliveryServiceNullable(&ds)
+       _, _, err := TOSession.CreateDeliveryServiceV30(ds)
        if err == nil {
                t.Fatal("Expected error with empty xmlid")
        }
        ds.XMLID = nil
-       _, err = TOSession.CreateDeliveryServiceNullable(&ds)
+       _, _, err = TOSession.CreateDeliveryServiceV30(ds)
        if err == nil {
                t.Fatal("Expected error with nil xmlid")
        }
@@ -100,7 +101,7 @@ func CreateTestDeliveryServices(t *testing.T) {
                t.Errorf("cannot create parameter: %v", err)
        }
        for _, ds := range testData.DeliveryServices {
-               _, err = TOSession.CreateDeliveryServiceNullable(&ds)
+               _, _, err = TOSession.CreateDeliveryServiceV30(ds)
                if err != nil {
                        t.Errorf("could not CREATE delivery service '%s': %v", 
*ds.XMLID, err)
                }
@@ -113,7 +114,7 @@ func GetTestDeliveryServicesIMS(t *testing.T) {
        futureTime := time.Now().AddDate(0, 0, 1)
        time := futureTime.Format(time.RFC1123)
        header.Set(rfc.IfModifiedSince, time)
-       _, reqInf, err := TOSession.GetDeliveryServicesNullable(header)
+       _, reqInf, err := TOSession.GetDeliveryServicesV30(header, nil)
        if err != nil {
                t.Fatalf("could not GET Delivery Services: %v", err)
        }
@@ -123,11 +124,11 @@ func GetTestDeliveryServicesIMS(t *testing.T) {
 }
 
 func GetTestDeliveryServices(t *testing.T) {
-       actualDSes, _, err := TOSession.GetDeliveryServicesNullable(nil)
+       actualDSes, _, err := TOSession.GetDeliveryServicesV30(nil, nil)
        if err != nil {
                t.Errorf("cannot GET DeliveryServices: %v - %v", err, 
actualDSes)
        }
-       actualDSMap := map[string]tc.DeliveryServiceNullable{}
+       actualDSMap := make(map[string]tc.DeliveryServiceNullableV30, 
len(actualDSes))
        for _, ds := range actualDSes {
                actualDSMap[*ds.XMLID] = ds
        }
@@ -152,12 +153,12 @@ func GetTestDeliveryServices(t *testing.T) {
 func UpdateTestDeliveryServices(t *testing.T) {
        firstDS := testData.DeliveryServices[0]
 
-       dses, _, err := TOSession.GetDeliveryServicesNullable(nil)
+       dses, _, err := TOSession.GetDeliveryServicesV30(nil, nil)
        if err != nil {
                t.Errorf("cannot GET Delivery Services: %v", err)
        }
 
-       remoteDS := tc.DeliveryServiceNullable{}
+       remoteDS := tc.DeliveryServiceNullableV30{}
        found := false
        for _, ds := range dses {
                if *ds.XMLID == *firstDS.XMLID {
@@ -178,18 +179,21 @@ func UpdateTestDeliveryServices(t *testing.T) {
        remoteDS.MaxOriginConnections = &updatedMaxOriginConnections
        remoteDS.MatchList = nil // verify that this field is optional in a PUT 
request, doesn't cause nil dereference panic
 
-       if updateResp, err := 
TOSession.UpdateDeliveryServiceNullable(strconv.Itoa(*remoteDS.ID), &remoteDS); 
err != nil {
+       if updateResp, _, err := 
TOSession.UpdateDeliveryServiceV30(*remoteDS.ID, remoteDS); err != nil {
                t.Errorf("cannot UPDATE DeliveryService by ID: %v - %v", err, 
updateResp)
        }
 
        // Retrieve the server to check rack and interfaceName values were 
updated
-       resp, _, err := 
TOSession.GetDeliveryServiceNullable(strconv.Itoa(*remoteDS.ID), nil)
+       params := url.Values{}
+       params.Set("id", strconv.Itoa(*remoteDS.ID))
+       apiResp, _, err := TOSession.GetDeliveryServicesV30(nil, params)
        if err != nil {
-               t.Errorf("cannot GET Delivery Service by ID: %v - %v", 
remoteDS.XMLID, err)
+               t.Fatalf("cannot GET Delivery Service by ID: %v - %v", 
remoteDS.XMLID, err)
        }
-       if resp == nil {
-               t.Errorf("cannot GET Delivery Service by ID: %v - nil", 
remoteDS.XMLID)
+       if len(apiResp) < 1 {
+               t.Fatalf("cannot GET Delivery Service by ID: %v - nil", 
remoteDS.XMLID)
        }
+       resp := apiResp[0]
 
        if *resp.LongDesc != updatedLongDesc || *resp.MaxDNSAnswers != 
updatedMaxDNSAnswers || *resp.MaxOriginConnections != 
updatedMaxOriginConnections {
                t.Errorf("results do not match actual: %s, expected: %s", 
*resp.LongDesc, updatedLongDesc)
@@ -201,12 +205,12 @@ func UpdateTestDeliveryServices(t *testing.T) {
 func UpdateNullableTestDeliveryServices(t *testing.T) {
        firstDS := testData.DeliveryServices[0]
 
-       dses, _, err := TOSession.GetDeliveryServicesNullable(nil)
+       dses, _, err := TOSession.GetDeliveryServicesV30(nil, nil)
        if err != nil {
                t.Fatalf("cannot GET Delivery Services: %v", err)
        }
 
-       remoteDS := tc.DeliveryServiceNullable{}
+       var remoteDS tc.DeliveryServiceNullableV30
        found := false
        for _, ds := range dses {
                if ds.XMLID == nil || ds.ID == nil {
@@ -227,18 +231,20 @@ func UpdateNullableTestDeliveryServices(t *testing.T) {
        remoteDS.LongDesc = &updatedLongDesc
        remoteDS.MaxDNSAnswers = &updatedMaxDNSAnswers
 
-       if updateResp, err := 
TOSession.UpdateDeliveryServiceNullable(strconv.Itoa(*remoteDS.ID), &remoteDS); 
err != nil {
+       if updateResp, _, err := 
TOSession.UpdateDeliveryServiceV30(*remoteDS.ID, remoteDS); err != nil {
                t.Fatalf("cannot UPDATE DeliveryService by ID: %v - %v", err, 
updateResp)
        }
 
-       // Retrieve the server to check rack and interfaceName values were 
updated
-       resp, _, err := 
TOSession.GetDeliveryServiceNullable(strconv.Itoa(*remoteDS.ID), nil)
+       params := url.Values{}
+       params.Set("id", strconv.Itoa(*remoteDS.ID))
+       apiResp, _, err := TOSession.GetDeliveryServicesV30(nil, params)
        if err != nil {
                t.Fatalf("cannot GET Delivery Service by ID: %v - %v", 
remoteDS.XMLID, err)
        }
-       if resp == nil {
+       if apiResp == nil {
                t.Fatalf("cannot GET Delivery Service by ID: %v - nil", 
remoteDS.XMLID)
        }
+       resp := apiResp[0]
 
        if resp.LongDesc == nil || resp.MaxDNSAnswers == nil {
                t.Errorf("results do not match actual: %v, expected: %s", 
resp.LongDesc, updatedLongDesc)
@@ -253,7 +259,7 @@ func UpdateNullableTestDeliveryServices(t *testing.T) {
 
 // UpdateDeliveryServiceWithInvalidTopology ensures that a topology cannot be 
assigned to (CLIENT_)STEERING delivery services.
 func UpdateDeliveryServiceWithInvalidTopology(t *testing.T) {
-       dses, _, err := TOSession.GetDeliveryServicesNullable(nil)
+       dses, _, err := TOSession.GetDeliveryServicesV30(nil, nil)
        if err != nil {
                t.Fatalf("cannot GET Delivery Services: %v", err)
        }
@@ -263,7 +269,7 @@ func UpdateDeliveryServiceWithInvalidTopology(t *testing.T) 
{
                if *ds.Type == tc.DSTypeClientSteering {
                        found = true
                        ds.Topology = util.StrPtr("my-topology")
-                       if _, err := 
TOSession.UpdateDeliveryServiceNullable(strconv.Itoa(*ds.ID), &ds); err == nil {
+                       if _, _, err := 
TOSession.UpdateDeliveryServiceV30(*ds.ID, ds); err == nil {
                                t.Errorf("assigning topology to CLIENT_STEERING 
delivery service - expected: error, actual: no error")
                        }
                }
@@ -276,7 +282,7 @@ func UpdateDeliveryServiceWithInvalidTopology(t *testing.T) 
{
 // UpdateDeliveryServiceTopologyHeaderRewriteFields ensures that a delivery 
service can only use firstHeaderRewrite,
 // innerHeaderRewrite, or lastHeadeRewrite if a topology is assigned.
 func UpdateDeliveryServiceTopologyHeaderRewriteFields(t *testing.T) {
-       dses, _, err := TOSession.GetDeliveryServicesNullable(nil)
+       dses, _, err := TOSession.GetDeliveryServicesV30(nil, nil)
        if err != nil {
                t.Fatalf("cannot GET Delivery Services: %v", err)
        }
@@ -288,7 +294,7 @@ func UpdateDeliveryServiceTopologyHeaderRewriteFields(t 
*testing.T) {
                ds.FirstHeaderRewrite = util.StrPtr("foo")
                ds.InnerHeaderRewrite = util.StrPtr("bar")
                ds.LastHeaderRewrite = util.StrPtr("baz")
-               _, err := 
TOSession.UpdateDeliveryServiceNullable(strconv.Itoa(*ds.ID), &ds)
+               _, _, err := TOSession.UpdateDeliveryServiceV30(*ds.ID, ds)
                if ds.Topology != nil && err != nil {
                        t.Errorf("expected: no error updating topology-based 
header rewrite fields for topology-based DS, actual: %v", err)
                }
@@ -300,7 +306,7 @@ func UpdateDeliveryServiceTopologyHeaderRewriteFields(t 
*testing.T) {
                ds.LastHeaderRewrite = nil
                ds.EdgeHeaderRewrite = util.StrPtr("foo")
                ds.MidHeaderRewrite = util.StrPtr("bar")
-               _, err = 
TOSession.UpdateDeliveryServiceNullable(strconv.Itoa(*ds.ID), &ds)
+               _, _, err = TOSession.UpdateDeliveryServiceV30(*ds.ID, ds)
                if ds.Topology != nil && err == nil {
                        t.Errorf("expected: error updating legacy header 
rewrite fields for topology-based DS, actual: nil")
                }
@@ -317,12 +323,12 @@ func UpdateDeliveryServiceTopologyHeaderRewriteFields(t 
*testing.T) {
 func UpdateDeliveryServiceWithInvalidRemapText(t *testing.T) {
        firstDS := testData.DeliveryServices[0]
 
-       dses, _, err := TOSession.GetDeliveryServicesNullable(nil)
+       dses, _, err := TOSession.GetDeliveryServicesV30(nil, nil)
        if err != nil {
                t.Fatalf("cannot GET Delivery Services: %v", err)
        }
 
-       remoteDS := tc.DeliveryServiceNullable{}
+       var remoteDS tc.DeliveryServiceNullableV30
        found := false
        for _, ds := range dses {
                if ds.XMLID == nil || ds.ID == nil {
@@ -341,7 +347,7 @@ func UpdateDeliveryServiceWithInvalidRemapText(t 
*testing.T) {
        updatedRemapText := "@plugin=tslua.so 
@pparam=/opt/trafficserver/etc/trafficserver/remapPlugin1.lua\nline2"
        remoteDS.RemapText = &updatedRemapText
 
-       if _, err := 
TOSession.UpdateDeliveryServiceNullable(strconv.Itoa(*remoteDS.ID), &remoteDS); 
err == nil {
+       if _, _, err := TOSession.UpdateDeliveryServiceV30(*remoteDS.ID, 
remoteDS); err == nil {
                t.Errorf("Delivery service updated with invalid remap text: 
%v", updatedRemapText)
        }
 }
@@ -360,12 +366,12 @@ func UpdateDeliveryServiceWithInvalidSliceRangeRequest(t 
*testing.T) {
                t.Fatal("no HTTP or DNS Delivery Services to test with")
        }
 
-       dses, _, err := TOSession.GetDeliveryServicesNullable(nil)
+       dses, _, err := TOSession.GetDeliveryServicesV30(nil, nil)
        if err != nil {
                t.Fatalf("cannot GET Delivery Services: %v", err)
        }
 
-       remoteDS := tc.DeliveryServiceNullable{}
+       var remoteDS tc.DeliveryServiceNullableV30
        found := false
        for _, ds := range dses {
                if ds.XMLID == nil || ds.ID == nil {
@@ -411,7 +417,7 @@ func UpdateDeliveryServiceWithInvalidSliceRangeRequest(t 
*testing.T) {
                t.Run(tc.description, func(t *testing.T) {
                        remoteDS.RangeSliceBlockSize = tc.slicePluginSize
                        remoteDS.RangeRequestHandling = tc.rangeRequestSetting
-                       if _, err := 
TOSession.UpdateDeliveryServiceNullable(strconv.Itoa(*remoteDS.ID), &remoteDS); 
err == nil {
+                       if _, _, err := 
TOSession.UpdateDeliveryServiceV30(*remoteDS.ID, remoteDS); err == nil {
                                t.Error("Delivery service updated with invalid 
slice plugin configuration")
                        }
                })
@@ -465,7 +471,9 @@ func GetAccessibleToTest(t *testing.T) {
 }
 
 func getByTenants(tenantID int, expectedCount int) error {
-       deliveryServices, _, err := 
TOSession.GetAccessibleDeliveryServicesByTenant(tenantID)
+       params := url.Values{}
+       params.Set("accessibleTo", strconv.Itoa(tenantID))
+       deliveryServices, _, err := TOSession.GetDeliveryServicesV30(nil, 
params)
        if err != nil {
                return err
        }
@@ -476,33 +484,40 @@ func getByTenants(tenantID int, expectedCount int) error {
 }
 
 func DeleteTestDeliveryServices(t *testing.T) {
-       dses, _, err := TOSession.GetDeliveryServicesNullable(nil)
+       dses, _, err := TOSession.GetDeliveryServicesV30(nil, nil)
        if err != nil {
                t.Errorf("cannot GET deliveryservices: %v", err)
        }
        for _, testDS := range testData.DeliveryServices {
-               ds := tc.DeliveryServiceNullable{}
+               var ds tc.DeliveryServiceNullableV30
                found := false
                for _, realDS := range dses {
-                       if *realDS.XMLID == *testDS.XMLID {
+                       if realDS.XMLID != nil && *realDS.XMLID == 
*testDS.XMLID {
                                ds = realDS
                                found = true
                                break
                        }
                }
                if !found {
-                       t.Errorf("DeliveryService not found in Traffic Ops: 
%v", ds.XMLID)
+                       t.Errorf("DeliveryService not found in Traffic Ops: 
%v", *ds.XMLID)
+                       continue
                }
 
                delResp, err := 
TOSession.DeleteDeliveryService(strconv.Itoa(*ds.ID))
                if err != nil {
                        t.Errorf("cannot DELETE DeliveryService by ID: %v - 
%v", err, delResp)
+                       continue
                }
 
                // Retrieve the Server to see if it got deleted
-               foundDS, _, err := 
TOSession.GetDeliveryServiceNullable(strconv.Itoa(*ds.ID), nil)
-               if err == nil && foundDS != nil {
-                       t.Errorf("expected Delivery Service: %s to be deleted", 
*ds.XMLID)
+               params := url.Values{}
+               params.Set("id", strconv.Itoa(*ds.ID))
+               foundDS, _, err := TOSession.GetDeliveryServicesV30(nil, params)
+               if err != nil {
+                       t.Errorf("Unexpected error deleting Delivery Service 
'%s': %v", *ds.XMLID, err)
+               }
+               if len(foundDS) > 0 {
+                       t.Errorf("expected Delivery Service: %s to be deleted, 
but %d exist with same ID (#%d)", *ds.XMLID, len(foundDS), *ds.ID)
                }
        }
 
@@ -517,22 +532,35 @@ func DeleteTestDeliveryServices(t *testing.T) {
 }
 
 func DeliveryServiceMinorVersionsTest(t *testing.T) {
+       if len(testData.DeliveryServices) < 5 {
+               t.Fatalf("Need at least 5 DSes to test minor versions; got: 
%d", len(testData.DeliveryServices))
+       }
        testDS := testData.DeliveryServices[4]
+       if testDS.XMLID == nil {
+               t.Fatal("expected XMLID: ds-test-minor-versions, actual: <nil>")
+       }
        if *testDS.XMLID != "ds-test-minor-versions" {
                t.Errorf("expected XMLID: ds-test-minor-versions, actual: %s", 
*testDS.XMLID)
        }
 
-       dses, _, err := TOSession.GetDeliveryServicesNullable(nil)
+       dses, _, err := TOSession.GetDeliveryServicesV30(nil, nil)
        if err != nil {
                t.Errorf("cannot GET DeliveryServices: %v - %v", err, dses)
        }
-       ds := tc.DeliveryServiceNullable{}
+
+       var ds tc.DeliveryServiceNullableV30
+       found := false
        for _, d := range dses {
-               if *d.XMLID == *testDS.XMLID {
+               if d.XMLID != nil && *d.XMLID == *testDS.XMLID {
                        ds = d
+                       found = true
                        break
                }
        }
+       if !found {
+               t.Fatalf("Delivery Service '%s' not found in Traffic Ops", 
*testDS.XMLID)
+       }
+
        // GET latest, verify expected values for 1.3 and 1.4 fields
        if ds.DeepCachingType == nil {
                t.Errorf("expected DeepCachingType: %s, actual: nil", 
testDS.DeepCachingType.String())
@@ -585,15 +613,14 @@ func DeliveryServiceMinorVersionsTest(t *testing.T) {
        if err != nil {
                t.Errorf("cannot POST deliveryservice, failed to marshal JSON: 
%s", err.Error())
        }
-
 }
 
 func DeliveryServiceTenancyTest(t *testing.T) {
-       dses, _, err := TOSession.GetDeliveryServicesNullable(nil)
+       dses, _, err := TOSession.GetDeliveryServicesV30(nil, nil)
        if err != nil {
                t.Errorf("cannot GET deliveryservices: %v", err)
        }
-       tenant3DS := tc.DeliveryServiceNullable{}
+       var tenant3DS tc.DeliveryServiceNullableV30
        foundTenant3DS := false
        for _, d := range dses {
                if *d.XMLID == "ds3" {
@@ -624,7 +651,7 @@ func DeliveryServiceTenancyTest(t *testing.T) {
        }
 
        // assert that tenant4user cannot update tenant3user's deliveryservice
-       if _, err = 
tenant4TOClient.UpdateDeliveryServiceNullable(string(*tenant3DS.ID), 
&tenant3DS); err == nil {
+       if _, _, err = tenant4TOClient.UpdateDeliveryServiceV30(*tenant3DS.ID, 
tenant3DS); err == nil {
                t.Errorf("expected tenant4user to be unable to update tenant3's 
deliveryservice (%s)", *tenant3DS.XMLID)
        }
 
@@ -636,8 +663,7 @@ func DeliveryServiceTenancyTest(t *testing.T) {
        // assert that tenant4user cannot create a deliveryservice outside of 
its tenant
        tenant3DS.XMLID = util.StrPtr("deliveryservicetenancytest")
        tenant3DS.DisplayName = util.StrPtr("deliveryservicetenancytest")
-       if _, err = tenant4TOClient.CreateDeliveryServiceNullable(&tenant3DS); 
err == nil {
+       if _, _, err = tenant4TOClient.CreateDeliveryServiceV30(tenant3DS); err 
== nil {
                t.Error("expected tenant4user to be unable to create a 
deliveryservice outside of its tenant")
        }
-
 }
diff --git a/traffic_ops/testing/api/v3/deliveryserviceservers_test.go 
b/traffic_ops/testing/api/v3/deliveryserviceservers_test.go
index 58c1ed6..8818252 100644
--- a/traffic_ops/testing/api/v3/deliveryserviceservers_test.go
+++ b/traffic_ops/testing/api/v3/deliveryserviceservers_test.go
@@ -41,12 +41,14 @@ func TestDeliveryServiceServersWithRequiredCapabilities(t 
*testing.T) {
 }
 
 func AssignServersToTopologyBasedDeliveryService(t *testing.T) {
-       ds, _, err := TOSession.GetDeliveryServiceByXMLIDNullable("ds-top", nil)
+       params := url.Values{}
+       params.Set("xmlId", "ds-top")
+       ds, _, err := TOSession.GetDeliveryServicesV30(nil, params)
        if err != nil {
                t.Fatalf("cannot GET delivery service 'ds-top': %s", 
err.Error())
        }
        if len(ds) != 1 {
-               t.Fatalf("expected one delivery service: 'ds-top', actual: %v", 
ds)
+               t.Fatalf("expected one delivery service: 'ds-top', actual: %v", 
len(ds))
        }
        if ds[0].Topology == nil {
                t.Fatal("expected delivery service: 'ds-top' to have a non-nil 
Topology, actual: nil")
diff --git a/traffic_ops/testing/api/v3/session_test.go 
b/traffic_ops/testing/api/v3/session_test.go
index ce5e966..f10c9cb 100644
--- a/traffic_ops/testing/api/v3/session_test.go
+++ b/traffic_ops/testing/api/v3/session_test.go
@@ -18,7 +18,8 @@ package v3
 import (
        "time"
 
-       "github.com/apache/trafficcontrol/traffic_ops/client"
+       "github.com/apache/trafficcontrol/traffic_ops/v3-client"
+
        _ "github.com/lib/pq"
 )
 
diff --git a/traffic_ops/testing/api/v3/traffic_control_test.go 
b/traffic_ops/testing/api/v3/traffic_control_test.go
index 15953e4..478ad01 100644
--- a/traffic_ops/testing/api/v3/traffic_control_test.go
+++ b/traffic_ops/testing/api/v3/traffic_control_test.go
@@ -30,7 +30,7 @@ type TrafficControl struct {
        DeliveryServicesRegexes              []tc.DeliveryServiceRegexesTest    
     `json:"deliveryServicesRegexes"`
        DeliveryServiceRequests              []tc.DeliveryServiceRequest        
     `json:"deliveryServiceRequests"`
        DeliveryServiceRequestComments       []tc.DeliveryServiceRequestComment 
     `json:"deliveryServiceRequestComments"`
-       DeliveryServices                     []tc.DeliveryServiceNullable       
     `json:"deliveryservices"`
+       DeliveryServices                     []tc.DeliveryServiceNullableV30    
     `json:"deliveryservices"`
        DeliveryServicesRequiredCapabilities 
[]tc.DeliveryServicesRequiredCapability 
`json:"deliveryservicesRequiredCapabilities"`
        Divisions                            []tc.Division                      
     `json:"divisions"`
        Federations                          []tc.CDNFederation                 
     `json:"federations"`
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go
index c31b924..fd9a948 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go
@@ -48,15 +48,15 @@ import (
 
 type TODeliveryService struct {
        api.APIInfoImpl
-       tc.DeliveryServiceNullable
+       tc.DeliveryServiceNullableV30
 }
 
 func (ds TODeliveryService) MarshalJSON() ([]byte, error) {
-       return json.Marshal(ds.DeliveryServiceNullable)
+       return json.Marshal(ds.DeliveryServiceNullableV30)
 }
 
 func (ds *TODeliveryService) UnmarshalJSON(data []byte) error {
-       return json.Unmarshal(data, ds.DeliveryServiceNullable)
+       return json.Unmarshal(data, ds.DeliveryServiceNullableV30)
 }
 
 func (ds *TODeliveryService) APIInfo() *api.APIInfo { return ds.ReqInfo }
@@ -90,11 +90,11 @@ func (ds *TODeliveryService) GetType() string {
 
 // 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 *TODeliveryService) IsTenantAuthorized(user *auth.CurrentUser) (bool, 
error) {
-       return isTenantAuthorized(ds.ReqInfo, &ds.DeliveryServiceNullable)
+       return isTenantAuthorized(ds.ReqInfo, &ds.DeliveryServiceNullableV30)
 }
 
 func (ds *TODeliveryService) Validate() error {
-       return ds.DeliveryServiceNullable.Validate(ds.APIInfo().Tx.Tx)
+       return ds.DeliveryServiceNullableV30.Validate(ds.APIInfo().Tx.Tx)
 }
 
 func CreateV12(w http.ResponseWriter, r *http.Request) {
@@ -245,8 +245,7 @@ func createV15(w http.ResponseWriter, r *http.Request, inf 
*api.APIInfo, reqDS t
 }
 
 // create creates the given ds in the database, and returns the DS with its id 
and other fields created on insert set. On error, the HTTP status code, user 
error, and system error are returned. The status code SHOULD NOT be used, if 
both errors are nil.
-func createV30(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, reqDS 
tc.DeliveryServiceNullableV30) (*tc.DeliveryServiceNullableV30, int, error, 
error) {
-       ds := tc.DeliveryServiceNullable(reqDS)
+func createV30(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, ds 
tc.DeliveryServiceNullableV30) (*tc.DeliveryServiceNullableV30, int, error, 
error) {
        user := inf.User
        tx := inf.Tx.Tx
        cfg := inf.Config
@@ -735,9 +734,7 @@ WHERE
        return nil, status, userErr, sysErr
 }
 
-func updateV30(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, reqDS 
*tc.DeliveryServiceNullableV30) (*tc.DeliveryServiceNullableV30, int, error, 
error) {
-       converted := tc.DeliveryServiceNullable(*reqDS)
-       ds := &converted
+func updateV30(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, ds 
*tc.DeliveryServiceNullableV30) (*tc.DeliveryServiceNullableV30, int, error, 
error) {
        tx := inf.Tx.Tx
        cfg := inf.Config
        user := inf.User
@@ -990,7 +987,7 @@ func (v *TODeliveryService) DeleteQuery() string {
        return `DELETE FROM deliveryservice WHERE id = :id`
 }
 
-func readGetDeliveryServices(h http.Header, params map[string]string, tx 
*sqlx.Tx, user *auth.CurrentUser, useIMS bool) ([]tc.DeliveryServiceNullable, 
error, error, int, *time.Time) {
+func readGetDeliveryServices(h http.Header, params map[string]string, tx 
*sqlx.Tx, user *auth.CurrentUser, useIMS bool) 
([]tc.DeliveryServiceNullableV30, error, error, int, *time.Time) {
        var maxTime time.Time
        var runSecond bool
        if strings.HasSuffix(params["id"], ".json") {
@@ -1024,7 +1021,7 @@ func readGetDeliveryServices(h http.Header, params 
map[string]string, tx *sqlx.T
                runSecond, maxTime = ims.TryIfModifiedSinceQuery(tx, h, 
queryValues, selectMaxLastUpdatedQuery(where))
                if !runSecond {
                        log.Debugln("IMS HIT")
-                       return []tc.DeliveryServiceNullable{}, nil, nil, 
http.StatusNotModified, &maxTime
+                       return []tc.DeliveryServiceNullableV30{}, nil, nil, 
http.StatusNotModified, &maxTime
                }
                log.Debugln("IMS MISS")
        } else {
@@ -1117,7 +1114,7 @@ func getTypeFromID(id int, tx *sql.Tx) (tc.DSType, error) 
{
        return tc.DSTypeFromString(name), nil
 }
 
-func updatePrimaryOrigin(tx *sql.Tx, user *auth.CurrentUser, ds 
tc.DeliveryServiceNullable) error {
+func updatePrimaryOrigin(tx *sql.Tx, user *auth.CurrentUser, ds 
tc.DeliveryServiceNullableV30) error {
        count := 0
        q := `SELECT count(*) FROM origin WHERE deliveryservice = $1 AND 
is_primary`
        if err := tx.QueryRow(q, *ds.ID).Scan(&count); err != nil {
@@ -1157,7 +1154,7 @@ func updatePrimaryOrigin(tx *sql.Tx, user 
*auth.CurrentUser, ds tc.DeliveryServi
        return nil
 }
 
-func createPrimaryOrigin(tx *sql.Tx, user *auth.CurrentUser, ds 
tc.DeliveryServiceNullable) error {
+func createPrimaryOrigin(tx *sql.Tx, user *auth.CurrentUser, ds 
tc.DeliveryServiceNullableV30) error {
        if ds.OrgServerFQDN == nil {
                return nil
        }
@@ -1189,21 +1186,21 @@ func getDSType(tx *sql.Tx, xmlid string) (tc.DSType, 
bool, error) {
        return tc.DSTypeFromString(name), true, nil
 }
 
-func GetDeliveryServices(query string, queryValues map[string]interface{}, tx 
*sqlx.Tx) ([]tc.DeliveryServiceNullable, error, error, int) {
+func GetDeliveryServices(query string, queryValues map[string]interface{}, tx 
*sqlx.Tx) ([]tc.DeliveryServiceNullableV30, error, error, int) {
        rows, err := tx.NamedQuery(query, queryValues)
        if err != nil {
                return nil, nil, fmt.Errorf("querying: %v", err), 
http.StatusInternalServerError
        }
        defer rows.Close()
 
-       dses := []tc.DeliveryServiceNullable{}
+       dses := []tc.DeliveryServiceNullableV30{}
        dsCDNDomains := map[string]string{}
 
        // ensure json generated from this slice won't come out as `null` if 
empty
        dsQueryParams := []string{}
 
        for rows.Next() {
-               ds := tc.DeliveryServiceNullable{}
+               ds := tc.DeliveryServiceNullableV30{}
                cdnDomain := ""
                err := rows.Scan(&ds.Active,
                        &ds.AnonymousBlockingEnabled,
@@ -1323,7 +1320,7 @@ func GetDeliveryServices(query string, queryValues 
map[string]interface{}, tx *s
        return dses, nil, nil, http.StatusOK
 }
 
-func updateSSLKeys(ds *tc.DeliveryServiceNullable, hostName string, tx 
*sql.Tx, cfg *config.Config) error {
+func updateSSLKeys(ds *tc.DeliveryServiceNullableV30, hostName string, tx 
*sql.Tx, cfg *config.Config) error {
        if ds.XMLID == nil {
                return errors.New("delivery services has no XMLID!")
        }
@@ -1642,7 +1639,7 @@ func GetDSSelectQuery() string {
 }
 
 // getTenantID returns the tenant Id of the given delivery service. Note it 
may return a nil id and nil error, if the tenant ID in the database is nil.
-func getTenantID(tx *sql.Tx, ds *tc.DeliveryServiceNullable) (*int, error) {
+func getTenantID(tx *sql.Tx, ds *tc.DeliveryServiceNullableV30) (*int, error) {
        if ds.ID == nil && ds.XMLID == nil {
                return nil, errors.New("delivery service has no ID or XMLID")
        }
@@ -1654,7 +1651,7 @@ func getTenantID(tx *sql.Tx, ds 
*tc.DeliveryServiceNullable) (*int, error) {
        return existingID, err
 }
 
-func isTenantAuthorized(inf *api.APIInfo, ds *tc.DeliveryServiceNullable) 
(bool, error) {
+func isTenantAuthorized(inf *api.APIInfo, ds *tc.DeliveryServiceNullableV30) 
(bool, error) {
        tx := inf.Tx.Tx
        user := inf.User
 
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/safe.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/safe.go
index c8bc1b2..845baf0 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/safe.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/safe.go
@@ -105,7 +105,7 @@ func UpdateSafe(w http.ResponseWriter, r *http.Request) {
                        api.WriteRespAlertObj(w, r, tc.SuccessLevel, alertMsg, 
[]tc.DeliveryServiceNullableV12{ds.DeliveryServiceNullableV12})
                }
        } else {
-               api.WriteRespAlertObj(w, r, tc.SuccessLevel, alertMsg, 
[]tc.DeliveryServiceNullable{ds})
+               api.WriteRespAlertObj(w, r, tc.SuccessLevel, alertMsg, 
[]tc.DeliveryServiceNullableV30{ds})
        }
 
        api.CreateChangeLogRawTx(api.ApiChange, fmt.Sprintf("DS: %s, ID: %d, 
ACTION: Updated safe fields", *ds.XMLID, *ds.ID), inf.User, tx)
diff --git a/traffic_ops/v3-client/deliveryservice.go 
b/traffic_ops/v3-client/deliveryservice.go
index 73195a1..1e1cec5 100644
--- a/traffic_ops/v3-client/deliveryservice.go
+++ b/traffic_ops/v3-client/deliveryservice.go
@@ -20,6 +20,7 @@ import (
        "errors"
        "fmt"
        "net/http"
+       "net/url"
        "strconv"
 
        "github.com/apache/trafficcontrol/lib/go-tc"
@@ -111,8 +112,23 @@ const (
        API_DELIVERY_SERVICES_SERVERS = apiBase + "/deliveryservices/%s/servers"
 )
 
+// GetDeliveryServicesByServerV30 returns all of the (tenant-visible) Delivery
+// Services to which the server identified by the integral, unique identifier
+// 'id' is assigned.
+func (to *Session) GetDeliveryServicesByServerV30(id int) 
([]tc.DeliveryServiceNullableV30, ReqInf, error) {
+       var data tc.DeliveryServicesResponseV30
+       reqInf, err := get(to, fmt.Sprintf(API_SERVER_DELIVERY_SERVICES, id), 
&data, nil)
+       return data.Response, reqInf, err
+}
+
 // GetDeliveryServicesByServer returns all of the (tenant-visible) Delivery 
Services assigned to
 // the server identified by the integral, unique identifier 'id'.
+//
+// Warning: This method coerces its returned data into an APIv1.5 format.
+//
+// Deprecated: Please used versioned library imports in the future, and
+// versioned methods, specifically, for API v3.0 - in this case,
+// GetDeliveryServicesByServerV30.
 func (to *Session) GetDeliveryServicesByServer(id int) 
([]tc.DeliveryServiceNullable, ReqInf, error) {
        var data tc.DeliveryServicesNullableResponse
 
@@ -124,7 +140,28 @@ func (to *Session) GetDeliveryServicesByServer(id int) 
([]tc.DeliveryServiceNull
        return data.Response, reqInf, nil
 }
 
+// GetDeliveryServicesV30 returns all (tenant-visible) Delivery Services that
+// satisfy the passed query string parameters. See the API documentation for
+// information on the available parameters.
+func (to *Session) GetDeliveryServicesV30(header http.Header, params 
url.Values) ([]tc.DeliveryServiceNullableV30, ReqInf, error) {
+       uri := API_DELIVERY_SERVICES
+       if params != nil {
+               uri += "?" + params.Encode()
+       }
+
+       var data tc.DeliveryServicesResponseV30
+
+       reqInf, err := get(to, uri, &data, header)
+       return data.Response, reqInf, err
+}
+
 // GetDeliveryServicesNullable returns a slice of Delivery Services.
+//
+// Warning: This method coerces its returned data into an APIv1.5 format.
+//
+// Deprecated: Please used versioned library imports in the future, and
+// versioned methods, specifically, for API v3.0 - in this case,
+// GetDeliveryServicesV30.
 func (to *Session) GetDeliveryServicesNullable(header http.Header) 
([]tc.DeliveryServiceNullable, ReqInf, error) {
        data := struct {
                Response []tc.DeliveryServiceNullable `json:"response"`
@@ -138,6 +175,12 @@ func (to *Session) GetDeliveryServicesNullable(header 
http.Header) ([]tc.Deliver
 
 // GetDeliveryServicesByCDNID returns the (tenant-visible) Delivery Services 
within the CDN identified
 // by the integral, unique identifier 'cdnID'.
+//
+// Warning: This method coerces its returned data into an APIv1.5 format.
+//
+// Deprecated: Please used versioned library imports in the future, and
+// versioned methods, specifically, for API v3.0 - in this case,
+// GetDeliveryServicesV30.
 func (to *Session) GetDeliveryServicesByCDNID(cdnID int, header http.Header) 
([]tc.DeliveryServiceNullable, ReqInf, error) {
        data := struct {
                Response []tc.DeliveryServiceNullable `json:"response"`
@@ -151,6 +194,12 @@ func (to *Session) GetDeliveryServicesByCDNID(cdnID int, 
header http.Header) ([]
 
 // GetDeliveryServiceNullable returns the Delivery Service identified by the 
integral, unique identifier
 // 'id' (which must be passed as a string).
+//
+// Warning: This method coerces its returned data into an APIv1.5 format.
+//
+// Deprecated: Please used versioned library imports in the future, and
+// versioned methods, specifically, for API v3.0 - in this case,
+// GetDeliveryServicesV30.
 func (to *Session) GetDeliveryServiceNullable(id string, header http.Header) 
(*tc.DeliveryServiceNullable, ReqInf, error) {
        data := struct {
                Response []tc.DeliveryServiceNullable `json:"response"`
@@ -168,6 +217,12 @@ func (to *Session) GetDeliveryServiceNullable(id string, 
header http.Header) (*t
 // GetDeliveryServiceByXMLIDNullable returns the Delivery Service identified 
by the passed XMLID.
 // The length of the returned slice should always be 1 when the request is 
succesful - if it isn't
 // something very wicked has happened to Traffic Ops.
+//
+// Warning: This method coerces its returned data into an APIv1.5 format.
+//
+// Deprecated: Please used versioned library imports in the future, and
+// versioned methods, specifically, for API v3.0 - in this case,
+// GetDeliveryServicesV30.
 func (to *Session) GetDeliveryServiceByXMLIDNullable(XMLID string, header 
http.Header) ([]tc.DeliveryServiceNullable, ReqInf, error) {
        var data tc.DeliveryServicesNullableResponse
        reqInf, err := get(to, API_DELIVERY_SERVICES+"?xmlId="+XMLID, &data, 
header)
@@ -178,7 +233,75 @@ func (to *Session) GetDeliveryServiceByXMLIDNullable(XMLID 
string, header http.H
        return data.Response, reqInf, nil
 }
 
+// CreateDeliveryServiceV30 creates the Delivery Service it's passed.
+func (to *Session) CreateDeliveryServiceV30(ds tc.DeliveryServiceNullableV30) 
(tc.DeliveryServiceNullableV30, ReqInf, error) {
+       var reqInf ReqInf
+       if ds.TypeID == nil && ds.Type != nil {
+               ty, _, err := to.GetTypeByName(ds.Type.String(), nil)
+               if err != nil {
+                       return tc.DeliveryServiceNullableV30{}, reqInf, err
+               }
+               if len(ty) == 0 {
+                       return tc.DeliveryServiceNullableV30{}, reqInf, 
fmt.Errorf("no type named %s", ds.Type)
+               }
+               ds.TypeID = &ty[0].ID
+       }
+
+       if ds.CDNID == nil && ds.CDNName != nil {
+               cdns, _, err := to.GetCDNByName(*ds.CDNName, nil)
+               if err != nil {
+                       return tc.DeliveryServiceNullableV30{}, reqInf, err
+               }
+               if len(cdns) == 0 {
+                       return tc.DeliveryServiceNullableV30{}, reqInf, 
errors.New("no CDN named " + *ds.CDNName)
+               }
+               ds.CDNID = &cdns[0].ID
+       }
+
+       if ds.ProfileID == nil && ds.ProfileName != nil {
+               profiles, _, err := to.GetProfileByName(*ds.ProfileName, nil)
+               if err != nil {
+                       return tc.DeliveryServiceNullableV30{}, reqInf, err
+               }
+               if len(profiles) == 0 {
+                       return tc.DeliveryServiceNullableV30{}, reqInf, 
errors.New("no Profile named " + *ds.ProfileName)
+               }
+               ds.ProfileID = &profiles[0].ID
+       }
+
+       if ds.TenantID == nil && ds.Tenant != nil {
+               ten, _, err := to.TenantByName(*ds.Tenant, nil)
+               if err != nil {
+                       return tc.DeliveryServiceNullableV30{}, reqInf, err
+               }
+               ds.TenantID = &ten.ID
+       }
+
+       bts, err := json.Marshal(ds)
+       if err != nil {
+               return tc.DeliveryServiceNullableV30{}, reqInf, nil
+       }
+
+       var data tc.DeliveryServicesResponseV30
+       reqInf, err = post(to, API_DELIVERY_SERVICES, bts, &data)
+       if err != nil {
+               return tc.DeliveryServiceNullableV30{}, reqInf, err
+       }
+       if len(data.Response) != 1 {
+               return tc.DeliveryServiceNullableV30{}, reqInf, 
fmt.Errorf("failed to create Delivery Service, response indicated %d were 
created", len(data.Response))
+       }
+
+       return data.Response[0], reqInf, nil
+}
+
 // CreateDeliveryServiceNullable creates the DeliveryService it's passed.
+//
+// Warning: This method coerces its returned data into an APIv1.5 format, and
+// only accepts input in an APIv1.5 format.
+//
+// Deprecated: Please used versioned library imports in the future, and
+// versioned methods, specifically, for API v3.0 - in this case,
+// CreateDeliveryServiceV30.
 func (to *Session) CreateDeliveryServiceNullable(ds 
*tc.DeliveryServiceNullable) (*tc.CreateDeliveryServiceNullableResponse, error) 
{
        if ds.TypeID == nil && ds.Type != nil {
                ty, _, err := to.GetTypeByName(ds.Type.String(), nil)
@@ -234,8 +357,36 @@ func (to *Session) CreateDeliveryServiceNullable(ds 
*tc.DeliveryServiceNullable)
        return &data, nil
 }
 
+// UpdateDeliveryServiceV30 replaces the Delivery Service identified by the
+// integral, unique identifier 'id' with the one it's passed.
+func (to *Session) UpdateDeliveryServiceV30(id int, ds 
tc.DeliveryServiceNullableV30) (tc.DeliveryServiceNullableV30, ReqInf, error) {
+       var reqInf ReqInf
+       bts, err := json.Marshal(ds)
+       if err != nil {
+               return tc.DeliveryServiceNullableV30{}, reqInf, err
+       }
+
+       var data tc.DeliveryServicesResponseV30
+       reqInf, err = put(to, fmt.Sprintf(API_DELIVERY_SERVICE_ID, id), bts, 
&data)
+       if err != nil {
+               return tc.DeliveryServiceNullableV30{}, reqInf, err
+       }
+       if len(data.Response) != 1 {
+               return tc.DeliveryServiceNullableV30{}, reqInf, 
fmt.Errorf("failed to update Delivery Service #%d; response indicated that %d 
were updated", id, len(data.Response))
+       }
+       return data.Response[0], reqInf, nil
+
+}
+
 // UpdateDeliveryServiceNullable updates the DeliveryService matching the ID 
it's
 // passed with the DeliveryService it is passed.
+//
+// Warning: This method coerces its returned data into an APIv1.5 format, and
+// only accepts input in an APIv1.5 format.
+//
+// Deprecated: Please used versioned library imports in the future, and
+// versioned methods, specifically, for API v3.0 - in this case,
+// UpdateDeliveryServiceV30.
 func (to *Session) UpdateDeliveryServiceNullable(id string, ds 
*tc.DeliveryServiceNullable) (*tc.UpdateDeliveryServiceNullableResponse, error) 
{
        var data tc.UpdateDeliveryServiceNullableResponse
        jsonReq, err := json.Marshal(ds)
@@ -360,7 +511,33 @@ func (to *Session) GetDeliveryServiceURISigningKeys(dsName 
string, header http.H
        return []byte(data), reqInf, nil
 }
 
+// SafeDeliveryServiceUpdateV30 updates the "safe" fields of the Delivery
+// Service identified by the integral, unique identifier 'id'.
+func (to *Session) SafeDeliveryServiceUpdateV30(id int, r 
tc.DeliveryServiceSafeUpdateRequest) (tc.DeliveryServiceNullableV30, ReqInf, 
error) {
+       var reqInf ReqInf
+       req, err := json.Marshal(r)
+       if err != nil {
+               return tc.DeliveryServiceNullableV30{}, reqInf, err
+       }
+
+       var data tc.DeliveryServiceSafeUpdateResponseV30
+       reqInf, err = put(to, fmt.Sprintf(API_DELIVERY_SERVICES_SAFE_UPDATE, 
id), req, &data)
+       if err != nil {
+               return tc.DeliveryServiceNullableV30{}, reqInf, err
+       }
+       if len(data.Response) != 1 {
+               return tc.DeliveryServiceNullableV30{}, reqInf, 
fmt.Errorf("failed to safe update Delivery Service #%d; response indicated that 
%d were updated", id, len(data.Response))
+       }
+       return data.Response[0], reqInf, nil
+}
+
 // UpdateDeliveryServiceSafe updates the given Delivery Service identified by 
'id' with the given "safe" fields.
+//
+// Warning: This method coerces its returned data into an APIv1.5 format.
+//
+// Deprecated: Please used versioned library imports in the future, and
+// versioned methods, specifically, for API v3.0 - in this case,
+// SafeDeliveryServiceUpdateV30.
 func (to *Session) UpdateDeliveryServiceSafe(id int, ds 
tc.DeliveryServiceSafeUpdateRequest) ([]tc.DeliveryServiceNullable, ReqInf, 
error) {
        var reqInf ReqInf
        var resp tc.DeliveryServiceSafeUpdateResponse
@@ -382,6 +559,12 @@ func (to *Session) UpdateDeliveryServiceSafe(id int, ds 
tc.DeliveryServiceSafeUp
 
 // GetAccessibleDeliveryServicesByTenant gets all delivery services associated 
with the given tenant and all of
 // its children.
+//
+// Warning: This method coerces its returned data into an APIv1.5 format.
+//
+// Deprecated: Please used versioned library imports in the future, and
+// versioned methods, specifically, for API v3.0 - in this case,
+// GetDeliveryServicesV30.
 func (to *Session) GetAccessibleDeliveryServicesByTenant(tenantId int) 
([]tc.DeliveryServiceNullable, ReqInf, error) {
        data := tc.DeliveryServicesNullableResponse{}
        reqInf, err := get(to, fmt.Sprintf("%v?accessibleTo=%v", 
API_DELIVERY_SERVICES, tenantId), &data, nil)
diff --git a/traffic_ops_ort/atstccfg/cfgfile/cacheurldotconfig.go 
b/traffic_ops_ort/atstccfg/cfgfile/cacheurldotconfig.go
index 37c3c33..64d42a0 100644
--- a/traffic_ops_ort/atstccfg/cfgfile/cacheurldotconfig.go
+++ b/traffic_ops_ort/atstccfg/cfgfile/cacheurldotconfig.go
@@ -43,7 +43,7 @@ func GetConfigFileCDNCacheURL(toData *config.TOData, fileName 
string) (string, s
                dssMap[*dss.DeliveryService] = 
append(dssMap[*dss.DeliveryService], *dss.Server)
        }
 
-       dsesWithServers := []tc.DeliveryServiceNullable{}
+       dsesWithServers := []tc.DeliveryServiceNullableV30{}
        for _, ds := range toData.DeliveryServices {
                if ds.ID == nil {
                        continue // TODO warn
diff --git a/traffic_ops_ort/atstccfg/cfgfile/cfgfile.go 
b/traffic_ops_ort/atstccfg/cfgfile/cfgfile.go
index a29a691..d21110c 100644
--- a/traffic_ops_ort/atstccfg/cfgfile/cfgfile.go
+++ b/traffic_ops_ort/atstccfg/cfgfile/cfgfile.go
@@ -112,7 +112,14 @@ func GetTOData(cfg config.TCCfg) (*config.TOData, error) {
                        dses, unsupported, err := 
cfg.TOClientNew.GetCDNDeliveryServices(server.CDNID)
                        if err == nil && unsupported {
                                log.Warnln("Traffic Ops newer than ORT, falling 
back to previous API Delivery Services!")
-                               dses, err = 
cfg.TOClient.GetCDNDeliveryServices(server.CDNID)
+                               var legacyDSes []tc.DeliveryServiceNullable
+                               legacyDSes, err = 
cfg.TOClient.GetCDNDeliveryServices(server.CDNID)
+                               if err == nil {
+                                       dses = 
make([]tc.DeliveryServiceNullableV30, 0, len(legacyDSes))
+                                       for _, ds := range legacyDSes {
+                                               dses = append(dses, 
tc.DeliveryServiceNullableV30{DeliveryServiceNullableV15: 
tc.DeliveryServiceNullableV15(ds)})
+                                       }
+                               }
                        }
                        if err != nil {
                                return errors.New("getting delivery services: " 
+ err.Error())
diff --git a/traffic_ops_ort/atstccfg/cfgfile/cfgfile_test.go 
b/traffic_ops_ort/atstccfg/cfgfile/cfgfile_test.go
index c05bc40..720091d 100644
--- a/traffic_ops_ort/atstccfg/cfgfile/cfgfile_test.go
+++ b/traffic_ops_ort/atstccfg/cfgfile/cfgfile_test.go
@@ -207,11 +207,11 @@ func randDSS() tc.DeliveryServiceServer {
        }
 }
 
-func randDS() *tc.DeliveryServiceNullable {
+func randDS() *tc.DeliveryServiceNullableV30 {
        deepCachingTypeNever := tc.DeepCachingTypeNever
        dsTypeHTTP := tc.DSTypeHTTP
        protocol := tc.DSProtocolHTTP
-       ds := tc.DeliveryServiceNullable{}
+       ds := tc.DeliveryServiceNullableV30{}
        ds.EcsEnabled = *randBool()
        ds.RangeSliceBlockSize = randInt()
        ds.ConsistentHashRegex = randStr()
@@ -505,7 +505,7 @@ func MakeFakeTOData() *config.TOData {
                        *randParam(),
                        *randParam(),
                },
-               DeliveryServices: []tc.DeliveryServiceNullable{
+               DeliveryServices: []tc.DeliveryServiceNullableV30{
                        ds0,
                        ds1,
                },
diff --git a/traffic_ops_ort/atstccfg/cfgfile/headerrewritedotconfig.go 
b/traffic_ops_ort/atstccfg/cfgfile/headerrewritedotconfig.go
index 7b27540..57bc277 100644
--- a/traffic_ops_ort/atstccfg/cfgfile/headerrewritedotconfig.go
+++ b/traffic_ops_ort/atstccfg/cfgfile/headerrewritedotconfig.go
@@ -31,7 +31,7 @@ import (
 func GetConfigFileCDNHeaderRewrite(toData *config.TOData, fileName string) 
(string, string, string, error) {
        dsName := strings.TrimSuffix(strings.TrimPrefix(fileName, 
atscfg.HeaderRewritePrefix), atscfg.ConfigSuffix) // TODO verify prefix and 
suffix? Perl doesn't
 
-       tcDS := tc.DeliveryServiceNullable{}
+       tcDS := tc.DeliveryServiceNullableV30{}
        for _, ds := range toData.DeliveryServices {
                if ds.XMLID == nil || *ds.XMLID != dsName {
                        continue
diff --git a/traffic_ops_ort/atstccfg/cfgfile/headerrewritemiddotconfig.go 
b/traffic_ops_ort/atstccfg/cfgfile/headerrewritemiddotconfig.go
index 2f0b3e3..d9261a3 100644
--- a/traffic_ops_ort/atstccfg/cfgfile/headerrewritemiddotconfig.go
+++ b/traffic_ops_ort/atstccfg/cfgfile/headerrewritemiddotconfig.go
@@ -32,7 +32,7 @@ import (
 func GetConfigFileCDNHeaderRewriteMid(toData *config.TOData, fileName string) 
(string, string, string, error) {
        dsName := strings.TrimSuffix(strings.TrimPrefix(fileName, 
atscfg.HeaderRewriteMidPrefix), atscfg.ConfigSuffix) // TODO verify prefix and 
suffix? Perl doesn't
 
-       tcDS := tc.DeliveryServiceNullable{}
+       tcDS := tc.DeliveryServiceNullableV30{}
        for _, ds := range toData.DeliveryServices {
                if ds.XMLID == nil || *ds.XMLID != dsName {
                        continue
diff --git a/traffic_ops_ort/atstccfg/cfgfile/hostingdotconfig.go 
b/traffic_ops_ort/atstccfg/cfgfile/hostingdotconfig.go
index f80ef4c..f96d1e4 100644
--- a/traffic_ops_ort/atstccfg/cfgfile/hostingdotconfig.go
+++ b/traffic_ops_ort/atstccfg/cfgfile/hostingdotconfig.go
@@ -66,7 +66,7 @@ func GetConfigFileServerHostingDotConfig(toData 
*config.TOData) (string, string,
 
        isMid := strings.HasPrefix(toData.Server.Type, tc.MidTypePrefix)
 
-       filteredDSes := []tc.DeliveryServiceNullable{}
+       filteredDSes := []tc.DeliveryServiceNullableV30{}
        for _, ds := range toData.DeliveryServices {
                if ds.Active == nil || ds.Type == nil || ds.XMLID == nil || 
ds.CDNID == nil || ds.ID == nil || ds.OrgServerFQDN == nil {
                        // some DSes have nil origins. I think MSO? TODO: verify
diff --git a/traffic_ops_ort/atstccfg/cfgfile/meta.go 
b/traffic_ops_ort/atstccfg/cfgfile/meta.go
index 065962a..447e969 100644
--- a/traffic_ops_ort/atstccfg/cfgfile/meta.go
+++ b/traffic_ops_ort/atstccfg/cfgfile/meta.go
@@ -126,7 +126,7 @@ func GetMeta(toData *config.TOData, dir string) 
(*tc.ATSConfigMetaData, error) {
                }
        }
 
-       dses := map[tc.DeliveryServiceName]tc.DeliveryServiceNullable{}
+       dses := map[tc.DeliveryServiceName]tc.DeliveryServiceNullableV30{}
        if tc.CacheTypeFromString(toData.Server.Type) != tc.CacheTypeMid {
                dsIDs := map[int]struct{}{}
                for _, ds := range toData.DeliveryServices {
diff --git a/traffic_ops_ort/atstccfg/cfgfile/parentdotconfig.go 
b/traffic_ops_ort/atstccfg/cfgfile/parentdotconfig.go
index 9afd3d6..838504b 100644
--- a/traffic_ops_ort/atstccfg/cfgfile/parentdotconfig.go
+++ b/traffic_ops_ort/atstccfg/cfgfile/parentdotconfig.go
@@ -272,7 +272,7 @@ func GetConfigFileServerParentDotConfig(toData 
*config.TOData) (string, string,
                parentConfigServerCacheProfileParams[cgServer.Profile] = 
profileCache
        }
 
-       dsIDMap := map[int]tc.DeliveryServiceNullable{}
+       dsIDMap := map[int]tc.DeliveryServiceNullableV30{}
        for _, ds := range toData.DeliveryServices {
                if ds.ID == nil {
                        log.Errorln("delivery services got nil ID!")
@@ -286,7 +286,7 @@ func GetConfigFileServerParentDotConfig(toData 
*config.TOData) (string, string,
                dsIDMap[*ds.ID] = ds
        }
 
-       allDSMap := map[int]tc.DeliveryServiceNullable{} // all DSes for this 
server, NOT all dses in TO
+       allDSMap := map[int]tc.DeliveryServiceNullableV30{} // all DSes for 
this server, NOT all dses in TO
        for _, dsIDs := range parentServerDSes {
                for dsID, _ := range dsIDs {
                        if _, ok := dsIDMap[dsID]; !ok {
@@ -460,7 +460,7 @@ func GetConfigFileServerParentDotConfig(toData 
*config.TOData) (string, string,
 }
 
 // GetDSOrigins takes a map[deliveryServiceID]DeliveryService, and returns a 
map[DeliveryServiceID]OriginURI.
-func GetDSOrigins(dses map[int]tc.DeliveryServiceNullable) 
(map[int]*atscfg.OriginURI, error) {
+func GetDSOrigins(dses map[int]tc.DeliveryServiceNullableV30) 
(map[int]*atscfg.OriginURI, error) {
        dsOrigins := map[int]*atscfg.OriginURI{}
        for _, ds := range dses {
                if ds.ID == nil {
diff --git a/traffic_ops_ort/atstccfg/cfgfile/regexremapdotconfig.go 
b/traffic_ops_ort/atstccfg/cfgfile/regexremapdotconfig.go
index d419bee..ffbff95 100644
--- a/traffic_ops_ort/atstccfg/cfgfile/regexremapdotconfig.go
+++ b/traffic_ops_ort/atstccfg/cfgfile/regexremapdotconfig.go
@@ -39,7 +39,7 @@ func GetConfigFileCDNRegexRemap(toData *config.TOData, 
fileName string) (string,
        }
 
        // only send the requested DS to atscfg. The atscfg.Make will work 
correctly even if we send it other DSes, but this will prevent 
atscfg.DeliveryServicesToCDNDSes from logging errors about AnyMap and Steering 
DSes without origins.
-       ds := tc.DeliveryServiceNullable{}
+       ds := tc.DeliveryServiceNullableV30{}
        for _, dsesDS := range toData.DeliveryServices {
                if dsesDS.XMLID == nil {
                        continue // TODO log?
@@ -53,7 +53,7 @@ func GetConfigFileCDNRegexRemap(toData *config.TOData, 
fileName string) (string,
                return `{"alerts":[{"level":"error","text":"Error - delivery 
service '` + dsName + `' not found! Do you have a regex_remap_*.config location 
Parameter for a delivery service that doesn't exist?"}]}`, "", "", 
config.ErrNotFound
        }
 
-       cfgDSes := 
atscfg.DeliveryServicesToCDNDSes([]tc.DeliveryServiceNullable{ds})
+       cfgDSes := 
atscfg.DeliveryServicesToCDNDSes([]tc.DeliveryServiceNullableV30{ds})
 
        return 
atscfg.MakeRegexRemapDotConfig(tc.CDNName(toData.Server.CDNName), 
toData.TOToolName, toData.TOURL, fileName, cfgDSes), 
atscfg.ContentTypeRegexRemapDotConfig, atscfg.LineCommentRegexRemapDotConfig, 
nil
 }
diff --git a/traffic_ops_ort/atstccfg/cfgfile/remapdotconfig.go 
b/traffic_ops_ort/atstccfg/cfgfile/remapdotconfig.go
index 07c24bd..39ede1f 100644
--- a/traffic_ops_ort/atstccfg/cfgfile/remapdotconfig.go
+++ b/traffic_ops_ort/atstccfg/cfgfile/remapdotconfig.go
@@ -88,7 +88,7 @@ func GetConfigFileServerRemapDotConfig(toData *config.TOData) 
(string, string, s
                useInactive = true
        }
 
-       filteredDSes := []tc.DeliveryServiceNullable{}
+       filteredDSes := []tc.DeliveryServiceNullableV30{}
        for _, ds := range toData.DeliveryServices {
                if ds.ID == nil {
                        continue // TODO log?
diff --git a/traffic_ops_ort/atstccfg/cfgfile/topologyheaderrewritedotconfig.go 
b/traffic_ops_ort/atstccfg/cfgfile/topologyheaderrewritedotconfig.go
index c1b1204..f0f9a2b 100644
--- a/traffic_ops_ort/atstccfg/cfgfile/topologyheaderrewritedotconfig.go
+++ b/traffic_ops_ort/atstccfg/cfgfile/topologyheaderrewritedotconfig.go
@@ -35,7 +35,7 @@ func GetConfigFileServerTopologyHeaderRewrite(toData 
*config.TOData, fileName st
        dsName = strings.TrimPrefix(dsName, atscfg.HeaderRewriteInnerPrefix)
        dsName = strings.TrimPrefix(dsName, atscfg.HeaderRewriteLastPrefix)
 
-       tcDS := tc.DeliveryServiceNullable{}
+       tcDS := tc.DeliveryServiceNullableV30{}
        for _, ds := range toData.DeliveryServices {
                if ds.XMLID == nil || *ds.XMLID != dsName {
                        continue
diff --git a/traffic_ops_ort/atstccfg/config/config.go 
b/traffic_ops_ort/atstccfg/config/config.go
index fd63a49..5defb73 100644
--- a/traffic_ops_ort/atstccfg/config/config.go
+++ b/traffic_ops_ort/atstccfg/config/config.go
@@ -257,7 +257,7 @@ type TOData struct {
        ParentConfigParams []tc.Parameter
 
        // DeliveryServices must include all Delivery Services on the current 
server's cdn, including those not assigned to the server. Must not include 
delivery services on other cdns.
-       DeliveryServices []tc.DeliveryServiceNullable
+       DeliveryServices []tc.DeliveryServiceNullableV30
 
        // DeliveryServiceServers must include all delivery service servers in 
Traffic Ops for all delivery services on the current cdn, including those not 
assigned to the current server.
        DeliveryServiceServers []tc.DeliveryServiceServer
diff --git a/traffic_ops_ort/atstccfg/toreqnew/toreqnew.go 
b/traffic_ops_ort/atstccfg/toreqnew/toreqnew.go
index 07e7470..fdf64da 100644
--- a/traffic_ops_ort/atstccfg/toreqnew/toreqnew.go
+++ b/traffic_ops_ort/atstccfg/toreqnew/toreqnew.go
@@ -40,7 +40,7 @@ import (
        "github.com/apache/trafficcontrol/lib/go-log"
        "github.com/apache/trafficcontrol/lib/go-tc"
 
-       toclient "github.com/apache/trafficcontrol/traffic_ops/client"
+       toclient "github.com/apache/trafficcontrol/traffic_ops/v3-client"
        "github.com/apache/trafficcontrol/traffic_ops_ort/atstccfg/torequtil"
 )
 
@@ -72,11 +72,13 @@ func New(cookies string, url *url.URL, user string, pass 
string, insecure bool,
 // GetCDNDeliveryServices returns the deliveryservices, whether this client's 
version is unsupported by the server, and any error.
 // Note if the server returns a 404 or 503, this returns false and a nil error.
 // Users should check the "not supported" bool, and use the vendored TOClient 
if it's set, and set proper defaults for the new feature(s).
-func (cl *TOClient) GetCDNDeliveryServices(cdnID int) 
([]tc.DeliveryServiceNullable, bool, error) {
-       deliveryServices := []tc.DeliveryServiceNullable{}
+func (cl *TOClient) GetCDNDeliveryServices(cdnID int) 
([]tc.DeliveryServiceNullableV30, bool, error) {
+       deliveryServices := []tc.DeliveryServiceNullableV30{}
        unsupported := false
        err := torequtil.GetRetry(cl.NumRetries, 
"cdn_"+strconv.Itoa(cdnID)+"_deliveryservices", &deliveryServices, func(obj 
interface{}) error {
-               toDSes, reqInf, err := cl.C.GetDeliveryServicesByCDNID(cdnID, 
nil)
+               params := url.Values{}
+               params.Set("cdn", strconv.Itoa(cdnID))
+               toDSes, reqInf, err := cl.C.GetDeliveryServicesV30(nil, params)
                if err != nil {
                        if IsUnsupportedErr(err) {
                                unsupported = true
@@ -84,7 +86,7 @@ func (cl *TOClient) GetCDNDeliveryServices(cdnID int) 
([]tc.DeliveryServiceNulla
                        }
                        return errors.New("getting delivery services from 
Traffic Ops '" + torequtil.MaybeIPStr(reqInf.RemoteAddr) + "': " + err.Error())
                }
-               dses := obj.(*[]tc.DeliveryServiceNullable)
+               dses := obj.(*[]tc.DeliveryServiceNullableV30)
                *dses = toDSes
                return nil
        })

Reply via email to