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

ocket8888 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 df422c9ca2 Add required capabilities to delivery service structure 
(#7224)
df422c9ca2 is described below

commit df422c9ca2142deacb4da8df951854695d70a669
Author: Srijeet Chatterjee <[email protected]>
AuthorDate: Wed Dec 14 16:12:54 2022 -0700

    Add required capabilities to delivery service structure (#7224)
    
    * initial commit
    
    * fix unit test
    
    * code review
    
    * code review round 2
    
    * fix unit test
    
    * add allocation
    
    * code review
    
    * code review
    
    * remove superfluous call to WriteHeader
    
    * go fmt
---
 CHANGELOG.md                                       |   1 +
 cache-config/testing/ort-tests/tcdata/todb.go      |   1 -
 docs/source/api/v4/deliveryservice_requests.rst    |  16 +-
 .../api/v4/deliveryservice_requests_id_assign.rst  |   6 +-
 .../api/v4/deliveryservice_requests_id_status.rst  |   6 +-
 docs/source/api/v4/deliveryservices.rst            |  21 +-
 docs/source/api/v4/deliveryservices_id.rst         |  14 +-
 .../v4/deliveryservices_required_capabilities.rst  |   3 +
 docs/source/api/v4/servers_id_deliveryservices.rst |   5 +
 .../v5/deliveryservices_required_capabilities.rst  | 227 -----------
 lib/go-tc/deliveryservice_requests.go              | 139 ++++++-
 lib/go-tc/deliveryservice_requests_test.go         |   4 +-
 lib/go-tc/deliveryservices.go                      |  16 +-
 lib/go-tc/deliveryservices_test.go                 |   2 +
 ...022300_add_required_capabilities_to_ds.down.sql |  29 ++
 ...15022300_add_required_capabilities_to_ds.up.sql |  23 ++
 traffic_ops/testing/api/v3/todb_test.go            |   1 -
 .../testing/api/v4/deliveryservices_test.go        |   5 +-
 traffic_ops/testing/api/v4/todb_test.go            |   1 -
 traffic_ops/testing/api/v4/traffic_control_test.go |   2 +-
 .../deliveryservices_required_capabilities_test.go | 424 ++++++++++-----------
 .../testing/api/v5/deliveryservices_test.go        |   7 +-
 .../testing/api/v5/deliveryserviceservers_test.go  |   2 +-
 traffic_ops/testing/api/v5/todb_test.go            |   1 -
 .../traffic_ops_golang/api/shared_handlers.go      |  66 +++-
 .../traffic_ops_golang/crconfig/deliveryservice.go |   4 +-
 .../traffic_ops_golang/dbhelpers/db_helpers.go     |  40 +-
 .../deliveryservice/deliveryservices.go            | 114 ++++--
 .../deliveryservices_required_capabilities.go      |  58 ++-
 .../deliveryservices_required_capabilities_test.go |   9 +-
 .../deliveryservice/deliveryservices_test.go       |   1 +
 .../traffic_ops_golang/deliveryservice/eligible.go |   4 +-
 .../deliveryservice/request/assign.go              |   8 +-
 .../deliveryservice/request/requests.go            |  69 +++-
 .../deliveryservice/request/status.go              |   8 +-
 .../deliveryservice/request/validate.go            |  27 +-
 traffic_ops/traffic_ops_golang/routing/routes.go   |  12 +-
 traffic_ops/traffic_ops_golang/server/servers.go   |  20 +-
 .../server/servers_server_capability.go            |  15 +-
 .../traffic_ops_golang/topology/topologies.go      |   5 +-
 .../test/integration/Data/deliveryservices.ts      |   4 +-
 41 files changed, 760 insertions(+), 660 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index ae65b3249e..ca36c02dc2 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -24,6 +24,7 @@ The format is based on [Keep a 
Changelog](http://keepachangelog.com/en/1.0.0/).
 - [#7113](https://github.com/apache/trafficcontrol/pull/7113) *Traffic Portal* 
Minimize the Server Server Capability part of the *Traffic Servers* section of 
the Snapshot Diff
 
 ### Changed
+- [#7224](https://github.com/apache/trafficcontrol/pull/7224) *Traffic Ops* 
Required Capabilities are now a part of the `DeliveryService` structure.
 - [#7063](https://github.com/apache/trafficcontrol/pull/7063) *Traffic Ops* 
Python client now uses Traffic Ops API 4.1 by default.
 - [#6981](https://github.com/apache/trafficcontrol/pull/6981) *Traffic Portal* 
Obscures sensitive text in Delivery Service "Raw Remap" fields, private SSL 
keys, "Header Rewrite" rules, and ILO interface passwords by default.
 - [#7037](https://github.com/apache/trafficcontrol/pull/7037) *Traffic Router* 
Uses Traffic Ops API 4.0 by default
diff --git a/cache-config/testing/ort-tests/tcdata/todb.go 
b/cache-config/testing/ort-tests/tcdata/todb.go
index 0bbb27b76c..b526c0416c 100644
--- a/cache-config/testing/ort-tests/tcdata/todb.go
+++ b/cache-config/testing/ort-tests/tcdata/todb.go
@@ -247,7 +247,6 @@ func (r *TCData) Teardown(db *sql.DB) error {
 
        sqlStmt := `
        DELETE FROM api_capability;
-       DELETE FROM deliveryservices_required_capability;
        DELETE FROM server_server_capability;
        DELETE FROM server_capability;
        DELETE FROM to_extension;
diff --git a/docs/source/api/v4/deliveryservice_requests.rst 
b/docs/source/api/v4/deliveryservice_requests.rst
index dd628cd4d3..5748454f9b 100644
--- a/docs/source/api/v4/deliveryservice_requests.rst
+++ b/docs/source/api/v4/deliveryservice_requests.rst
@@ -72,7 +72,7 @@ Request Structure
 .. code-block:: http
        :caption: Request Example
 
-       GET /api/4.0/deliveryservice_requests?status=draft HTTP/1.1
+       GET /api/4.1/deliveryservice_requests?status=draft HTTP/1.1
        User-Agent: python-requests/2.22.0
        Accept-Encoding: gzip, deflate
        Accept: */*
@@ -163,6 +163,7 @@ The response is an array of representations of 
:term:`Delivery Service Requests`
                        "regional": false,
                        "regionalGeoBlocking": false,
                        "remapText": null,
+                       "requiredCapabilities": [],
                        "routingName": "video",
                        "signed": false,
                        "sslKeyVersion": 1,
@@ -213,7 +214,7 @@ The request must be a well-formed representation of a 
:term:`Delivery Service Re
 .. code-block:: http
        :caption: Request Example
 
-       POST /api/4.0/deliveryservice_requests HTTP/1.1
+       POST /api/4.1/deliveryservice_requests HTTP/1.1
        User-Agent: python-requests/2.22.0
        Accept-Encoding: gzip, deflate
        Accept: */*
@@ -280,6 +281,7 @@ The request must be a well-formed representation of a 
:term:`Delivery Service Re
                        "regional": false,
                        "regionalGeoBlocking": false,
                        "remapText": null,
+                       "requiredCapabilities": [],
                        "routingName": "video",
                        "signed": false,
                        "sslKeyVersion": 1,
@@ -328,7 +330,7 @@ The response will be a representation of the created 
:term:`Delivery Service Req
        Content-Encoding: gzip
        Content-Type: application/json
        Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 24 Feb 2020 20:11:12 
GMT; Max-Age=3600; HttpOnly
-       Location: /api/4.0/deliveryservice_requests/2
+       Location: /api/4.1/deliveryservice_requests/2
        X-Server-Name: traffic_ops_golang/
        Date: Mon, 24 Feb 2020 19:11:12 GMT
        Content-Length: 901
@@ -405,6 +407,7 @@ The response will be a representation of the created 
:term:`Delivery Service Req
                                "regional": false,
                                "regionalGeoBlocking": false,
                                "remapText": null,
+                               "requiredCapabilities": [],
                                "routingName": "video",
                                "signed": false,
                                "sslKeyVersion": 1,
@@ -490,6 +493,7 @@ The response will be a representation of the created 
:term:`Delivery Service Req
                                "regional": false,
                                "regionalGeoBlocking": false,
                                "remapText": null,
+                               "requiredCapabilities": [],
                                "routingName": "video",
                                "signed": false,
                                "sslKeyVersion": 1,
@@ -550,7 +554,7 @@ The request body must be a representation of a 
:term:`Delivery Service Request`
 .. code-block:: http
        :caption: Request Example
 
-       PUT /api/4.0/deliveryservice_requests?id=1 HTTP/1.1
+       PUT /api/4.1/deliveryservice_requests?id=1 HTTP/1.1
        User-Agent: python-requests/2.22.0
        Accept-Encoding: gzip, deflate
        Accept: */*
@@ -667,6 +671,7 @@ The response is a full representation of the edited 
:term:`Delivery Service Requ
                        "regional": false,
                        "regionalGeoBlocking": false,
                        "remapText": null,
+                       "requiredCapabilities": [],
                        "routingName": "video",
                        "signed": false,
                        "sslKeyVersion": 1,
@@ -748,6 +753,7 @@ The response is a full representation of the edited 
:term:`Delivery Service Requ
                        "regional": false,
                        "regionalGeoBlocking": false,
                        "remapText": null,
+                       "requiredCapabilities": [],
                        "routingName": "cdn",
                        "signed": false,
                        "sslKeyVersion": null,
@@ -800,7 +806,7 @@ Request Structure
 .. code-block:: http
        :caption: Request Example
 
-       DELETE /api/4.0/deliveryservice_requests?id=1 HTTP/1.1
+       DELETE /api/4.1/deliveryservice_requests?id=1 HTTP/1.1
        User-Agent: python-requests/2.22.0
        Accept-Encoding: gzip, deflate
        Accept: */*
diff --git a/docs/source/api/v4/deliveryservice_requests_id_assign.rst 
b/docs/source/api/v4/deliveryservice_requests_id_assign.rst
index 9a9710acec..7737b67881 100644
--- a/docs/source/api/v4/deliveryservice_requests_id_assign.rst
+++ b/docs/source/api/v4/deliveryservice_requests_id_assign.rst
@@ -42,7 +42,7 @@ Request Structure
 .. code-block:: http
        :caption: Request Example
 
-       GET /api/4.0/deliveryservice_requests/1/assign HTTP/1.1
+       GET /api/4.1/deliveryservice_requests/1/assign HTTP/1.1
        User-Agent: python-requests/2.24.0
        Accept-Encoding: gzip, deflate
        Accept: */*
@@ -103,7 +103,7 @@ Request Structure
 .. code-block:: http
        :caption: Request Example
 
-       PUT /api/4.0/deliveryservice_requests/1/assign HTTP/1.1
+       PUT /api/4.1/deliveryservice_requests/1/assign HTTP/1.1
        User-Agent: python-requests/2.24.0
        Accept-Encoding: gzip, deflate
        Accept: */*
@@ -198,6 +198,7 @@ The response contains a full representation of the newly 
assigned :term:`Deliver
                        "regional": false,
                        "regionalGeoBlocking": false,
                        "remapText": null,
+                       "requiredCapabilities": [],
                        "routingName": "video",
                        "signed": false,
                        "sslKeyVersion": 1,
@@ -279,6 +280,7 @@ The response contains a full representation of the newly 
assigned :term:`Deliver
                        "regional": false,
                        "regionalGeoBlocking": false,
                        "remapText": null,
+                       "requiredCapabilities": [],
                        "routingName": "cdn",
                        "signed": false,
                        "sslKeyVersion": null,
diff --git a/docs/source/api/v4/deliveryservice_requests_id_status.rst 
b/docs/source/api/v4/deliveryservice_requests_id_status.rst
index 933e8abe25..fc1333918d 100644
--- a/docs/source/api/v4/deliveryservice_requests_id_status.rst
+++ b/docs/source/api/v4/deliveryservice_requests_id_status.rst
@@ -45,7 +45,7 @@ Request Structure
 .. code-block:: http
        :caption: Request Example
 
-       GET /api/4.0/deliveryservice_requests/1/status HTTP/1.1
+       GET /api/4.1/deliveryservice_requests/1/status HTTP/1.1
        User-Agent: python-requests/2.24.0
        Accept-Encoding: gzip, deflate
        Accept: */*
@@ -98,7 +98,7 @@ Request Structure
 .. code-block:: http
        :caption: Request Example
 
-       PUT /api/4.0/deliveryservice_requests/1/status HTTP/1.1
+       PUT /api/4.1/deliveryservice_requests/1/status HTTP/1.1
        User-Agent: python-requests/2.22.0
        Accept-Encoding: gzip, deflate
        Accept: */*
@@ -195,6 +195,7 @@ The response is a full representation of the modified 
:term:`DSR`.
                        "regional": false,
                        "regionalGeoBlocking": false,
                        "remapText": null,
+                       "requiredCapabilities": [],
                        "routingName": "video",
                        "signed": false,
                        "sslKeyVersion": 1,
@@ -276,6 +277,7 @@ The response is a full representation of the modified 
:term:`DSR`.
                        "regional": false,
                        "regionalGeoBlocking": false,
                        "remapText": null,
+                       "requiredCapabilities": [],
                        "routingName": "cdn",
                        "signed": false,
                        "sslKeyVersion": null,
diff --git a/docs/source/api/v4/deliveryservices.rst 
b/docs/source/api/v4/deliveryservices.rst
index f6e3934d9b..9f98b62155 100644
--- a/docs/source/api/v4/deliveryservices.rst
+++ b/docs/source/api/v4/deliveryservices.rst
@@ -73,7 +73,7 @@ Request Structure
 .. code-block:: http
        :caption: Request Example
 
-       GET /api/4.0/deliveryservices?xmlId=demo2 HTTP/1.1
+       GET /api/4.1/deliveryservices?xmlId=demo2 HTTP/1.1
        Host: trafficops.infra.ciab.test
        User-Agent: python-requests/2.24.0
        Accept-Encoding: gzip, deflate
@@ -148,6 +148,10 @@ Response Structure
 :regional:              A boolean value defining the :ref:`ds-regional` 
setting on this :term:`Delivery Service`
 :regionalGeoBlocking:   A boolean defining the :ref:`ds-regionalgeo` setting 
on this :term:`Delivery Service`
 :remapText:             :ref:`ds-raw-remap`
+:requiredCapabilities:  An array of the capabilities that this Delivery 
Service requires.
+
+       .. versionadded:: 4.1
+
 :serviceCategory:       The name of the :ref:`ds-service-category` with which 
the :term:`Delivery Service` is associated
 :signed:                ``true`` if  and only if ``signingAlgorithm`` is not 
``null``, ``false`` otherwise
 :signingAlgorithm:      Either a :ref:`ds-signing-algorithm` or ``null`` to 
indicate URL/URI signing is not implemented on this :term:`Delivery Service`
@@ -250,6 +254,7 @@ Response Structure
                        "regional": false,
                        "regionalGeoBlocking": false,
                        "remapText": null,
+                       "requiredCapabilities": [],
                        "routingName": "video",
                        "serviceCategory": null,
                        "signed": false,
@@ -328,6 +333,10 @@ Request Structure
 :regional:                  A boolean value defining the :ref:`ds-regional` 
setting on this :term:`Delivery Service`
 :regionalGeoBlocking:       A boolean defining the :ref:`ds-regionalgeo` 
setting on this :term:`Delivery Service`
 :remapText:                 :ref:`ds-raw-remap`
+:requiredCapabilities:      An array of the capabilities that this Delivery 
Service requires.
+
+       .. versionadded:: 4.1
+
 :serviceCategory:           The name of the :ref:`ds-service-category` with 
which the :term:`Delivery Service` is associated - or ``null`` if there is to 
be no such category
 :signed:                    ``true`` if  and only if ``signingAlgorithm`` is 
not ``null``, ``false`` otherwise
 :signingAlgorithm:          Either a :ref:`ds-signing-algorithm` or ``null`` 
to indicate URL/URI signing is not implemented on this :term:`Delivery Service`
@@ -348,7 +357,7 @@ Request Structure
 .. code-block:: http
        :caption: Request Example
 
-       POST /api/4.0/deliveryservices HTTP/1.1
+       POST /api/4.1/deliveryservices HTTP/1.1
        User-Agent: python-requests/2.24.0
        Accept-Encoding: gzip, deflate
        Accept: */*
@@ -407,6 +416,7 @@ Request Structure
                "regexRemap": null,
                "regional": false,
                "regionalGeoBlocking": false,
+               "requiredCapabilities": [],
                "routingName": "test",
                "serviceCategory": null,
                "signed": false,
@@ -495,6 +505,10 @@ Response Structure
 :regional:              A boolean value defining the :ref:`ds-regional` 
setting on this :term:`Delivery Service`
 :regionalGeoBlocking:   A boolean defining the :ref:`ds-regionalgeo` setting 
on this :term:`Delivery Service`
 :remapText:             :ref:`ds-raw-remap`
+:requiredCapabilities:  An array of the capabilities that this Delivery 
Service requires.
+
+       .. versionadded:: 4.1
+
 :serviceCategory:       The name of the :ref:`ds-service-category` with which 
the :term:`Delivery Service` is associated
 :signed:                ``true`` if  and only if ``signingAlgorithm`` is not 
``null``, ``false`` otherwise
 :signingAlgorithm:      Either a :ref:`ds-signing-algorithm` or ``null`` to 
indicate URL/URI signing is not implemented on this :term:`Delivery Service`
@@ -522,7 +536,7 @@ Response Structure
        Access-Control-Allow-Origin: *
        Content-Encoding: gzip
        Content-Type: application/json
-       Location: /api/4.0/deliveryservices?id=6
+       Location: /api/4.1/deliveryservices?id=6
        Permissions-Policy: interest-cohort=()
        Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 07 Jun 2021 23:37:37 
GMT; Max-Age=3600; HttpOnly
        Vary: Accept-Encoding
@@ -606,6 +620,7 @@ Response Structure
                "regional": false,
                "regionalGeoBlocking": false,
                "remapText": null,
+               "requiredCapabilities": [],
                "routingName": "test",
                "serviceCategory": null,
                "signed": false,
diff --git a/docs/source/api/v4/deliveryservices_id.rst 
b/docs/source/api/v4/deliveryservices_id.rst
index c2ce373d9f..0865427028 100644
--- a/docs/source/api/v4/deliveryservices_id.rst
+++ b/docs/source/api/v4/deliveryservices_id.rst
@@ -81,6 +81,10 @@ Request Structure
 :regional:                  A boolean value defining the :ref:`ds-regional` 
setting on this :term:`Delivery Service`
 :regionalGeoBlocking:       A boolean defining the :ref:`ds-regionalgeo` 
setting on this :term:`Delivery Service`
 :remapText:                 :ref:`ds-raw-remap`
+:requiredCapabilities:      An array of the capabilities that this Delivery 
Service requires.
+
+       .. versionadded:: 4.1
+
 :routingName:               The :ref:`ds-routing-name` of this :term:`Delivery 
Service`
 
                .. note:: If the Delivery Service has SSL Keys, then 
``routingName`` is not allowed to change as that would invalidate the SSL Key
@@ -106,7 +110,7 @@ Request Structure
 .. code-block:: http
        :caption: Request Example
 
-       PUT /api/4.0/deliveryservices/6 HTTP/1.1
+       PUT /api/4.1/deliveryservices/6 HTTP/1.1
        Host: trafficops.infra.ciab.test
        User-Agent: python-requests/2.24.0
        Accept-Encoding: gzip, deflate
@@ -165,6 +169,7 @@ Request Structure
                "regexRemap": null,
                "regional": false,
                "regionalGeoBlocking": false,
+               "requiredCapabilities": [],
                "routingName": "test",
                "serviceCategory": null,
                "signed": false,
@@ -249,6 +254,10 @@ Response Structure
 :regexRemap:            A :ref:`ds-regex-remap`
 :regional:              A boolean value defining the :ref:`ds-regional` 
setting on this :term:`Delivery Service`
 :regionalGeoBlocking:   A boolean defining the :ref:`ds-regionalgeo` setting 
on this :term:`Delivery Service`
+:requiredCapabilities:  An array of the capabilities that this Delivery 
Service requires.
+
+       .. versionadded:: 4.1
+
 :remapText:             :ref:`ds-raw-remap`
 :serviceCategory:       The name of the :ref:`ds-service-category` with which 
the :term:`Delivery Service` is associated
 :signed:                ``true`` if  and only if ``signingAlgorithm`` is not 
``null``, ``false`` otherwise
@@ -355,6 +364,7 @@ Response Structure
                "regional": false,
                "regionalGeoBlocking": false,
                "remapText": null,
+               "requiredCapabilities": [],
                "routingName": "test",
                "serviceCategory": null,
                "signed": false,
@@ -394,7 +404,7 @@ Request Structure
 .. code-block:: http
        :caption: Request Example
 
-       DELETE /api/4.0/deliveryservices/2 HTTP/1.1
+       DELETE /api/4.1/deliveryservices/2 HTTP/1.1
        Host: trafficops.infra.ciab.test
        User-Agent: curl/7.47.0
        Accept: */*
diff --git a/docs/source/api/v4/deliveryservices_required_capabilities.rst 
b/docs/source/api/v4/deliveryservices_required_capabilities.rst
index 1bf4b4d81a..f3a2dd9fd7 100644
--- a/docs/source/api/v4/deliveryservices_required_capabilities.rst
+++ b/docs/source/api/v4/deliveryservices_required_capabilities.rst
@@ -19,6 +19,9 @@
 ``deliveryservices_required_capabilities``
 ******************************************
 
+.. deprecated:: 4.1
+       This endpoint will be removed in a future release, in favor of 
:ref:`ds-required-capabilities` being a part of :term:`Delivery Services`.
+
 ``GET``
 =======
 Gets all associations of :term:`Server Capability` to :term:`Delivery 
Services`.
diff --git a/docs/source/api/v4/servers_id_deliveryservices.rst 
b/docs/source/api/v4/servers_id_deliveryservices.rst
index 386de7532a..9b5d6adb3e 100644
--- a/docs/source/api/v4/servers_id_deliveryservices.rst
+++ b/docs/source/api/v4/servers_id_deliveryservices.rst
@@ -134,6 +134,10 @@ Response Structure
 :regional:             A boolean value defining the :ref:`ds-regional` setting 
on this :term:`Delivery Service`
 :regionalGeoBlocking:  A boolean defining the :ref:`ds-regionalgeo` setting on 
this :term:`Delivery Service`
 :remapText:            :ref:`ds-raw-remap`
+:requiredCapabilities: An array of the capabilities that this Delivery Service 
requires.
+
+       .. versionadded:: 4.1
+
 :signed:               ``true`` if  and only if ``signingAlgorithm`` is not 
``null``, ``false`` otherwise
 :signingAlgorithm:     Either a :ref:`ds-signing-algorithm` or ``null`` to 
indicate URL/URI signing is not implemented on this :term:`Delivery Service`
 :rangeSliceBlockSize: An integer that defines the byte block size for the ATS 
Slice Plugin. It can only and must be set if ``rangeRequestHandling`` is set to 
3.
@@ -234,6 +238,7 @@ Response Structure
                "regional": false,
                "regionalGeoBlocking": false,
                "remapText": null,
+               "requiredCapabilities": [],
                "routingName": "cdn",
                "serviceCategory": null,
                "signed": false,
diff --git a/docs/source/api/v5/deliveryservices_required_capabilities.rst 
b/docs/source/api/v5/deliveryservices_required_capabilities.rst
deleted file mode 100644
index 6761728b5f..0000000000
--- a/docs/source/api/v5/deliveryservices_required_capabilities.rst
+++ /dev/null
@@ -1,227 +0,0 @@
-..
-..
-.. 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.
-..
-
-.. _to-api-deliveryservices-required-capabilities:
-
-******************************************
-``deliveryservices_required_capabilities``
-******************************************
-
-``GET``
-=======
-Gets all associations of :term:`Server Capability` to :term:`Delivery 
Services`.
-
-:Auth. Required: Yes
-:Roles Required: None
-:Permissions Required: DELIVERY-SERVICE:READ
-:Response Type:  Array
-
-Request Structure
------------------
-.. table:: Request Query Parameters
-
-       
+--------------------+----------+---------------------------------------------------------------------------------------------------------------+
-       | Name               | Required | Description                           
                                                                        |
-       
+====================+==========+===============================================================================================================+
-       | deliveryServiceID  | no       | Filter :term:`Server Capability` 
associations by :term:`Delivery Service` integral, unique identifier         |
-       
+--------------------+----------+---------------------------------------------------------------------------------------------------------------+
-       | xmlID              | no       | Filter :term:`Server Capability` 
associations by :term:`Delivery Service` :ref:`ds-xmlid`                     |
-       
+--------------------+----------+---------------------------------------------------------------------------------------------------------------+
-       | requiredCapability | no       | Filter :term:`Server Capability` 
associations by :term:`Server Capability` name                               |
-       
+--------------------+----------+---------------------------------------------------------------------------------------------------------------+
-       | orderby            | no       | Choose the ordering of the results - 
must be the name of one of the fields of the objects in the ``response`` |
-       |                    |          | array                                 
                                                                        |
-       
+--------------------+----------+---------------------------------------------------------------------------------------------------------------+
-       | sortOrder          | no       | Changes the order of sorting. Either 
ascending (default or "asc") or descending ("desc")                      |
-       
+--------------------+----------+---------------------------------------------------------------------------------------------------------------+
-       | limit              | no       | Choose the maximum number of results 
to return                                                                |
-       
+--------------------+----------+---------------------------------------------------------------------------------------------------------------+
-       | offset             | no       | The number of results to skip before 
beginning to return results. Must use in conjunction with limit.         |
-       
+--------------------+----------+---------------------------------------------------------------------------------------------------------------+
-       | page               | no       | Return the n\ :sup:`th` page of 
results, where "n" is the value of this parameter, pages are ``limit`` long   |
-       |                    |          | and the first page is 1. If 
``offset`` was defined, this query parameter has no effect. ``limit`` must be   
  |
-       |                    |          | defined to make use of ``page``.      
                                                                        |
-       
+--------------------+----------+---------------------------------------------------------------------------------------------------------------+
-
-.. code-block:: http
-       :caption: Request Example
-
-       GET /api/5.0/deliveryservices_required_capabilities HTTP/1.1
-       Host: trafficops.infra.ciab.test
-       User-Agent: curl/7.47.0
-       Accept: */*
-       Cookie: mojolicious=...
-
-Response Structure
-------------------
-:deliveryServiceID:   The associated :term:`Delivery Service`'s integral, 
unique identifier
-:xmlID:               The associated :term:`Delivery Service`'s :ref:`ds-xmlid`
-:lastUpdated:         The date and time at which this association between the 
:term:`Delivery Service` and the :term:`Server Capability` was last updated, in 
:ref:`non-rfc-datetime`
-:requiredCapability:  The :term:`Server Capability`'s name
-
-.. code-block:: http
-       :caption: Response Example
-
-       HTTP/1.1 200 OK
-       Access-Control-Allow-Credentials: true
-       Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, 
Accept, Set-Cookie, Cookie
-       Access-Control-Allow-Methods: POST,GET,OPTIONS,DELETE
-       Access-Control-Allow-Origin: *
-       Content-Type: application/json
-       Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 
GMT; Max-Age=3600; HttpOnly
-       Whole-Content-Sha512: 
UFO3/jcBFmFZM7CsrsIwTfPc5v8gUiXqJm6BNp1boPb4EQBnWNXZh/DbBwhMAOJoeqDImoDlrLnrVjQGO4AooA==
-       X-Server-Name: traffic_ops_golang/
-       Date: Mon, 07 Oct 2019 22:15:11 GMT
-       Content-Length: 396
-
-       {
-               "response": [
-                       {
-                               "deliveryServiceID": 1,
-                               "lastUpdated": "2019-10-07 22:05:31+00",
-                               "requiredCapability": "ram",
-                               "xmlId": "example_ds-1"
-                       },
-                       {
-                               "deliveryServiceID": 2,
-                               "lastUpdated": "2019-10-07 22:05:31+00",
-                               "requiredCapability": "disk",
-                               "xmlId": "example_ds-2"
-                       }
-               ]
-       }
-
-``POST``
-========
-Associates a :term:`Server Capability` with a :term:`Delivery Service`.
-
-:Auth. Required: Yes
-:Roles Required: "admin" or "operations"
-:Permissions Required: DELIVERY-SERVICE:READ, DELIVERY-SERVICE:UPDATE
-:Response Type:  Object
-
-.. note:: A :term:`Server Capability` can only be made required on a 
:term:`Delivery Service` if its associated Servers already have that 
:term:`Server Capability` assigned.
-
-Request Structure
------------------
-:deliveryServiceID:   The integral, unique identifier of the :term:`Delivery 
Service` to be associated
-:requiredCapability:  The name of the :term:`Server Capability` to be 
associated
-
-.. code-block:: http
-       :caption: Request Example
-
-       POST /api/5.0/deliveryservices_required_capabilities HTTP/1.1
-       Host: trafficops.infra.ciab.test
-       User-Agent: curl/7.47.0
-       Accept: */*
-       Cookie: mojolicious=...
-       Content-Length: 56
-       Content-Type: application/json
-
-       {
-               "deliveryServiceID": 1,
-               "requiredCapability": "disk"
-       }
-
-Response Structure
-------------------
-:deliveryServiceID:   The newly associated :term:`Delivery Service`'s 
integral, unique identifier
-:lastUpdated:         The date and time at which this association between the 
:term:`Delivery Service` and the :term:`Server Capability` was last updated, in 
:ref:`non-rfc-datetime`
-:requiredCapability:  The newly associated :term:`Server Capability`'s name
-
-.. code-block:: http
-       :caption: Response Example
-
-       HTTP/1.1 200 OK
-       Access-Control-Allow-Credentials: true
-       Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, 
Accept, Set-Cookie, Cookie
-       Access-Control-Allow-Methods: POST,GET,OPTIONS,DELETE
-       Access-Control-Allow-Origin: *
-       Content-Type: application/json
-       Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 
GMT; Max-Age=3600; HttpOnly
-       Whole-Content-Sha512: 
eQrl48zWids0kDpfCYmmtYMpegjnFxfOVvlBYxxLSfp7P7p6oWX4uiC+/Cfh2X9i3G+MQ36eH95gukJqOBOGbQ==
-       X-Server-Name: traffic_ops_golang/
-       Date: Mon, 07 Oct 2019 22:15:11 GMT
-       Content-Length: 287
-
-       {
-               "alerts": [
-                       {
-                               "level": "success",
-                               "text": "deliveryservice.RequiredCapability was 
created."
-                       }
-               ],
-               "response": {
-                       "deliveryServiceID": 1,
-                       "lastUpdated": "2019-10-07 22:15:11+00",
-                       "requiredCapability": "disk"
-               }
-       }
-
-``DELETE``
-==========
-Dissociate a :term:`Server Capability` from a :term:`Delivery Service`.
-
-:Auth. Required: Yes
-:Roles Required: "admin" or "operations"
-:Permissions Required: DELIVERY-SERVICE:READ, DELIVERY-SERVICE:UPDATE
-:Response Type:  ``undefined``
-
-Request Structure
------------------
-:deliveryServiceID:   The integral, unique identifier of the :term:`Delivery 
Service` from which a :term:`Server Capability` will be dissociated
-:requiredCapability:  The name of the :term:`Server Capability` to dissociate
-
-.. code-block:: http
-       :caption: Request Example
-
-       POST /api/5.0/deliveryservices_required_capabilities HTTP/1.1
-       Host: trafficops.infra.ciab.test
-       User-Agent: curl/7.47.0
-       Accept: */*
-       Cookie: mojolicious=...
-       Content-Length: 56
-       Content-Type: application/json
-
-       {
-               "deliveryServiceID": 1,
-               "requiredCapability": "disk"
-       }
-
-Response Structure
-------------------
-.. code-block:: http
-       :caption: Response Example
-
-       HTTP/1.1 200 OK
-       Access-Control-Allow-Credentials: true
-       Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, 
Accept, Set-Cookie, Cookie
-       Access-Control-Allow-Methods: POST,GET,OPTIONS,DELETE
-       Access-Control-Allow-Origin: *
-       Content-Type: application/json
-       Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 
GMT; Max-Age=3600; HttpOnly
-       Whole-Content-Sha512: 
eQrl48zWids0kDpfCYmmtYMpegjnFxfOVvlBYxxLSfp7P7p6oWX4uiC+/Cfh2X9i3G+MQ36eH95gukJqOBOGbQ==
-       X-Server-Name: traffic_ops_golang/
-       Date: Mon, 07 Oct 2019 22:15:11 GMT
-       Content-Length: 127
-
-       {
-               "alerts": [
-                       {
-                               "level": "success",
-                               "text": "deliveryservice.RequiredCapability was 
deleted."
-                       }
-               ]
-       }
diff --git a/lib/go-tc/deliveryservice_requests.go 
b/lib/go-tc/deliveryservice_requests.go
index 9f65168ab6..817cabc0c3 100644
--- a/lib/go-tc/deliveryservice_requests.go
+++ b/lib/go-tc/deliveryservice_requests.go
@@ -435,6 +435,60 @@ type DeliveryServiceRequestNullable struct {
        XMLID           *string                     `json:"-" db:"xml_id"`
 }
 
+// Downgrade will convert an instance of DeliveryServiceRequestV41 to 
DeliveryServiceRequestV40.
+// Note that this function does a shallow copy of the requested and original 
Delivery Service structures.
+func (dsr DeliveryServiceRequestV41) Downgrade() DeliveryServiceRequestV40 {
+       var dsrV40 DeliveryServiceRequestV40
+       dsrV40.Assignee = copyStringIfNotNil(dsr.Assignee)
+       dsrV40.AssigneeID = copyIntIfNotNil(dsr.AssigneeID)
+       dsrV40.Author = dsr.Author
+       dsrV40.AuthorID = copyIntIfNotNil(dsr.AuthorID)
+       dsrV40.ChangeType = dsr.ChangeType
+       dsrV40.CreatedAt = dsr.CreatedAt
+       dsrV40.ID = copyIntIfNotNil(dsr.ID)
+       dsrV40.LastEditedBy = dsr.LastEditedBy
+       dsrV40.LastEditedByID = copyIntIfNotNil(dsr.LastEditedByID)
+       dsrV40.LastUpdated = dsr.LastUpdated
+       if dsr.Original != nil {
+               dsrV40.Original = new(DeliveryServiceV40)
+               dsrV40.Original = &dsr.Original.DeliveryServiceV40
+       }
+       if dsr.Requested != nil {
+               dsrV40.Requested = new(DeliveryServiceV40)
+               dsrV40.Requested = &dsr.Requested.DeliveryServiceV40
+       }
+       dsrV40.Status = dsr.Status
+       dsrV40.XMLID = dsr.XMLID
+       return dsrV40
+}
+
+// Upgrade will convert an instance of DeliveryServiceRequestV40 to 
DeliveryServiceRequestV41.
+// Note that this function does a shallow copy of the requested and original 
Delivery Service structures.
+func (dsrV40 DeliveryServiceRequestV40) Upgrade() DeliveryServiceRequestV41 {
+       var dsrV4 DeliveryServiceRequestV41
+       dsrV4.Assignee = copyStringIfNotNil(dsrV40.Assignee)
+       dsrV4.AssigneeID = copyIntIfNotNil(dsrV40.AssigneeID)
+       dsrV4.Author = dsrV40.Author
+       dsrV4.AuthorID = copyIntIfNotNil(dsrV40.AuthorID)
+       dsrV4.ChangeType = dsrV40.ChangeType
+       dsrV4.CreatedAt = dsrV40.CreatedAt
+       dsrV4.ID = copyIntIfNotNil(dsrV40.ID)
+       dsrV4.LastEditedBy = dsrV40.LastEditedBy
+       dsrV4.LastEditedByID = copyIntIfNotNil(dsrV40.LastEditedByID)
+       dsrV4.LastUpdated = dsrV40.LastUpdated
+       if dsrV40.Original != nil {
+               dsrV4.Original = new(DeliveryServiceV41)
+               dsrV4.Original = &DeliveryServiceV4{DeliveryServiceV40: 
*dsrV40.Original}
+       }
+       if dsrV40.Requested != nil {
+               dsrV4.Requested = new(DeliveryServiceV41)
+               dsrV4.Requested = &DeliveryServiceV4{DeliveryServiceV40: 
*dsrV40.Requested}
+       }
+       dsrV4.Status = dsrV40.Status
+       dsrV4.XMLID = dsrV40.XMLID
+       return dsrV4
+}
+
 // Upgrade coerces the DeliveryServiceRequestNullable to the newer
 // DeliveryServiceRequestV40 structure.
 //
@@ -468,11 +522,13 @@ func (dsr DeliveryServiceRequestNullable) Upgrade() 
DeliveryServiceRequestV40 {
        }
        if dsr.DeliveryService != nil {
                if upgraded.ChangeType == DSRChangeTypeDelete {
-                       upgraded.Original = new(DeliveryServiceV4)
-                       *upgraded.Original = dsr.DeliveryService.UpgradeToV4()
+                       upgraded.Original = new(DeliveryServiceV40)
+                       orig := 
dsr.DeliveryService.UpgradeToV4().DeliveryServiceV40
+                       upgraded.Original = &orig
                } else {
-                       upgraded.Requested = new(DeliveryServiceV4)
-                       *upgraded.Requested = dsr.DeliveryService.UpgradeToV4()
+                       upgraded.Requested = new(DeliveryServiceV40)
+                       requested := 
dsr.DeliveryService.UpgradeToV4().DeliveryServiceV40
+                       upgraded.Requested = &requested
                }
        }
        if dsr.ID != nil {
@@ -693,6 +749,53 @@ func (dsrct *DSRChangeType) UnmarshalJSON(b []byte) error {
        return nil
 }
 
+// DeliveryServiceRequestV41 is the type of a Delivery Service Request in
+// Traffic Ops API version 4.1.
+type DeliveryServiceRequestV41 struct {
+       // Assignee is the username of the user assigned to the Delivery Service
+       // Request, if any.
+       Assignee *string `json:"assignee"`
+       // AssigneeID is the integral, unique identifier of the user assigned 
to the
+       // Delivery Service Request, if any.
+       AssigneeID *int `json:"-" db:"assignee_id"`
+       // Author is the username of the user who created the Delivery Service
+       // Request.
+       Author string `json:"author"`
+       // AuthorID is the integral, unique identifier of the user who created 
the
+       // Delivery Service Request, if/when it is known.
+       AuthorID *int `json:"-" db:"author_id"`
+       // ChangeType represents the type of change being made, must be one of
+       // "create", "change" or "delete".
+       ChangeType DSRChangeType `json:"changeType" db:"change_type"`
+       // CreatedAt is the date/time at which the Delivery Service Request was
+       // created.
+       CreatedAt time.Time `json:"createdAt" db:"created_at"`
+       // ID is the integral, unique identifier for the Delivery Service 
Request
+       // if/when it is known.
+       ID *int `json:"id" db:"id"`
+       // LastEditedBy is the username of the user by whom the Delivery Service
+       // Request was last edited.
+       LastEditedBy string `json:"lastEditedBy"`
+       // LastEditedByID is the integral, unique identifier of the user by 
whom the
+       // Delivery Service Request was last edited, if/when it is known.
+       LastEditedByID *int `json:"-" db:"last_edited_by_id"`
+       // LastUpdated is the date/time at which the Delivery Service was last
+       // modified.
+       LastUpdated time.Time `json:"lastUpdated" db:"last_updated"`
+       // Original is the original Delivery Service for which changes are
+       // requested. This is present in responses only for ChangeTypes 
'change' and
+       // 'delete', and is only required in requests where ChangeType is 
'delete'.
+       Original *DeliveryServiceV41 `json:"original,omitempty" db:"original"`
+       // Requested is the set of requested changes. This is present in 
responses
+       // only for ChangeTypes 'change' and 'create', and is only required in
+       // requests in those cases.
+       Requested *DeliveryServiceV41 `json:"requested,omitempty" 
db:"deliveryservice"`
+       // Status is the status of the Delivery Service Request.
+       Status RequestStatus `json:"status" db:"status"`
+       // Used internally to define the affected Delivery Service.
+       XMLID string `json:"-"`
+}
+
 // DeliveryServiceRequestV40 is the type of a Delivery Service Request in
 // Traffic Ops API version 4.0.
 type DeliveryServiceRequestV40 struct {
@@ -729,11 +832,11 @@ type DeliveryServiceRequestV40 struct {
        // Original is the original Delivery Service for which changes are
        // requested. This is present in responses only for ChangeTypes 
'change' and
        // 'delete', and is only required in requests where ChangeType is 
'delete'.
-       Original *DeliveryServiceV4 `json:"original,omitempty" db:"original"`
+       Original *DeliveryServiceV40 `json:"original,omitempty" db:"original"`
        // Requested is the set of requested changes. This is present in 
responses
        // only for ChangeTypes 'change' and 'create', and is only required in
        // requests in those cases.
-       Requested *DeliveryServiceV4 `json:"requested,omitempty" 
db:"deliveryservice"`
+       Requested *DeliveryServiceV40 `json:"requested,omitempty" 
db:"deliveryservice"`
        // Status is the status of the Delivery Service Request.
        Status RequestStatus `json:"status" db:"status"`
        // Used internally to define the affected Delivery Service.
@@ -742,7 +845,7 @@ type DeliveryServiceRequestV40 struct {
 
 // DeliveryServiceRequestV4 is the type of a Delivery Service Request as it
 // appears in API version 4.
-type DeliveryServiceRequestV4 = DeliveryServiceRequestV40
+type DeliveryServiceRequestV4 = DeliveryServiceRequestV41
 
 // IsOpen returns whether or not the Delivery Service Request is still "open" -
 // i.e. has not been rejected or completed.
@@ -791,10 +894,16 @@ func (dsr DeliveryServiceRequestV40) Downgrade() 
DeliveryServiceRequestNullable
        downgraded.CreatedAt = TimeNoModFromTime(dsr.CreatedAt)
        if dsr.Requested != nil {
                downgraded.DeliveryService = new(DeliveryServiceNullableV30)
-               *downgraded.DeliveryService = dsr.Requested.DowngradeToV31()
+               if dsr.Requested != nil {
+                       dsV4 := DeliveryServiceV4{DeliveryServiceV40: 
*dsr.Requested}
+                       *downgraded.DeliveryService = dsV4.DowngradeToV31()
+               }
        } else if dsr.Original != nil {
                downgraded.DeliveryService = new(DeliveryServiceNullableV30)
-               *downgraded.DeliveryService = dsr.Original.DowngradeToV31()
+               if dsr.Original != nil {
+                       dsV4 := DeliveryServiceV4{DeliveryServiceV40: 
*dsr.Original}
+                       *downgraded.DeliveryService = dsV4.DowngradeToV31()
+               }
        }
        if dsr.ID != nil {
                downgraded.ID = new(int)
@@ -1014,7 +1123,7 @@ func (dsr DeliveryServiceRequestV5) Downgrade() 
DeliveryServiceRequestV4 {
 // Original) are copied using the DeliveryServiceV4.Upgrade method (which is
 // also deep).
 func (dsr DeliveryServiceRequestV4) Upgrade() DeliveryServiceRequestV5 {
-       downgraded := DeliveryServiceRequestV5{
+       upgraded := DeliveryServiceRequestV5{
                Assignee:       copyStringIfNotNil(dsr.Assignee),
                AssigneeID:     copyIntIfNotNil(dsr.AssigneeID),
                Author:         dsr.Author,
@@ -1029,14 +1138,14 @@ func (dsr DeliveryServiceRequestV4) Upgrade() 
DeliveryServiceRequestV5 {
                XMLID:          dsr.XMLID,
        }
        if dsr.Requested != nil {
-               downgraded.Requested = new(DeliveryServiceV5)
-               *downgraded.Requested = dsr.Requested.Upgrade()
+               upgraded.Requested = new(DeliveryServiceV5)
+               *upgraded.Requested = dsr.Requested.Upgrade()
        }
        if dsr.Original != nil {
-               downgraded.Original = new(DeliveryServiceV5)
-               *downgraded.Original = dsr.Original.Upgrade()
+               upgraded.Original = new(DeliveryServiceV5)
+               *upgraded.Original = dsr.Original.Upgrade()
        }
-       return downgraded
+       return upgraded
 }
 
 // IsOpen returns whether or not the Delivery Service Request is still "open" -
diff --git a/lib/go-tc/deliveryservice_requests_test.go 
b/lib/go-tc/deliveryservice_requests_test.go
index aa5a3a55c3..b011e715c8 100644
--- a/lib/go-tc/deliveryservice_requests_test.go
+++ b/lib/go-tc/deliveryservice_requests_test.go
@@ -151,7 +151,7 @@ func TestDeliveryServiceRequestV40_Downgrade(t *testing.T) {
                LastEditedBy:   "last edited by",
                LastEditedByID: nil,
                LastUpdated:    time.Now(),
-               Requested:      &DeliveryServiceV4{},
+               Requested:      &DeliveryServiceV40{},
                Status:         RequestStatusComplete,
        }
        dsr.Requested.XMLID = &xmlid
@@ -216,7 +216,7 @@ func ExampleDeliveryServiceRequestV40_SetXMLID() {
        var dsr DeliveryServiceRequestV40
        fmt.Println(dsr.XMLID == "")
 
-       dsr.Requested = new(DeliveryServiceV4)
+       dsr.Requested = new(DeliveryServiceV40)
        dsr.Requested.XMLID = new(string)
        *dsr.Requested.XMLID = "test"
        dsr.SetXMLID()
diff --git a/lib/go-tc/deliveryservices.go b/lib/go-tc/deliveryservices.go
index b3a999ddf6..daf61e8f9b 100644
--- a/lib/go-tc/deliveryservices.go
+++ b/lib/go-tc/deliveryservices.go
@@ -283,7 +283,8 @@ type DeliveryServiceV41 struct {
        // Regional indicates whether the Delivery Service's 
MaxOriginConnections is
        // only per Cache Group, rather than divided over all Cache Servers in 
child
        // Cache Groups of the Origin.
-       Regional bool `json:"regional" db:"regional"`
+       Regional             bool     `json:"regional" db:"regional"`
+       RequiredCapabilities []string `json:"requiredCapabilities" 
db:"required_capabilities"`
 }
 
 // DeliveryServiceV4 is a Delivery Service as it appears in version 4 of the
@@ -1437,6 +1438,8 @@ type DeliveryServiceV50 struct {
        // use, because the input is in no way restricted, validated, or 
limited in
        // scope to the Delivery Service.
        RemapText *string `json:"remapText" db:"remap_text"`
+       // RequiredCapabilities is an array of capabilities required for this 
delivery service.
+       RequiredCapabilities []string `json:"requiredCapabilities" 
db:"required_capabilities"`
        // RoutingName defines the lowest-level DNS label used by the Delivery
        // Service, e.g. if trafficcontrol.apache.org were a Delivery Service, 
it
        // would have a RoutingName of "trafficcontrol".
@@ -1608,7 +1611,8 @@ func (ds DeliveryServiceV5) Downgrade() DeliveryServiceV4 
{
                        },
                        TLSVersions: make([]string, len(ds.TLSVersions)),
                },
-               Regional: ds.Regional,
+               Regional:             ds.Regional,
+               RequiredCapabilities: make([]string, 
len(ds.RequiredCapabilities)),
        }
 
        *downgraded.Active = ds.Active == DSActiveStateActive
@@ -1628,7 +1632,9 @@ func (ds DeliveryServiceV5) Downgrade() DeliveryServiceV4 
{
                copy(*downgraded.MatchList, ds.MatchList)
        }
        copy(downgraded.TLSVersions, ds.TLSVersions)
-
+       if len(ds.RequiredCapabilities) > 0 {
+               copy(downgraded.RequiredCapabilities, ds.RequiredCapabilities)
+       }
        return downgraded
 }
 
@@ -1691,6 +1697,7 @@ func (ds DeliveryServiceV4) Upgrade() DeliveryServiceV5 {
                Regional:                  ds.Regional,
                RegionalGeoBlocking:       coalesceBool(ds.RegionalGeoBlocking, 
false),
                RemapText:                 copyStringIfNotNil(ds.RemapText),
+               RequiredCapabilities:      make([]string, 
len(ds.RequiredCapabilities)),
                RoutingName:               coalesceString(ds.RoutingName, ""),
                ServiceCategory:           
copyStringIfNotNil(ds.ServiceCategory),
                Signed:                    ds.Signed,
@@ -1726,6 +1733,9 @@ func (ds DeliveryServiceV4) Upgrade() DeliveryServiceV5 {
                copy(upgraded.MatchList, *ds.MatchList)
        }
        copy(upgraded.TLSVersions, ds.TLSVersions)
+       if len(ds.RequiredCapabilities) > 0 {
+               copy(upgraded.RequiredCapabilities, ds.RequiredCapabilities)
+       }
 
        return upgraded
 }
diff --git a/lib/go-tc/deliveryservices_test.go 
b/lib/go-tc/deliveryservices_test.go
index 03f6e05721..51d491cd1d 100644
--- a/lib/go-tc/deliveryservices_test.go
+++ b/lib/go-tc/deliveryservices_test.go
@@ -473,6 +473,7 @@ func dsUpgradeAndDowngradeTestingPair() 
(DeliveryServiceNullableV30, DeliverySer
        typ := DSTypeDNS
        typeID := 22
        xmlid := "xmlid"
+       requiredCapabilities := []string{"foo"}
 
        newDS := DeliveryServiceV4{}
        newDS.Active = new(bool)
@@ -547,6 +548,7 @@ func dsUpgradeAndDowngradeTestingPair() 
(DeliveryServiceNullableV30, DeliverySer
        newDS.Type = &typ
        newDS.TypeID = &typeID
        newDS.XMLID = &xmlid
+       newDS.RequiredCapabilities = requiredCapabilities
 
        active := false
        oldDS := DeliveryServiceNullableV30{
diff --git 
a/traffic_ops/app/db/migrations/2022112215022300_add_required_capabilities_to_ds.down.sql
 
b/traffic_ops/app/db/migrations/2022112215022300_add_required_capabilities_to_ds.down.sql
new file mode 100644
index 0000000000..18dd306f19
--- /dev/null
+++ 
b/traffic_ops/app/db/migrations/2022112215022300_add_required_capabilities_to_ds.down.sql
@@ -0,0 +1,29 @@
+/*
+ * 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.
+ */
+
+CREATE TABLE IF NOT EXISTS public.deliveryservices_required_capability (
+                                                                    
required_capability TEXT NOT NULL,
+                                                                    
deliveryservice_id BIGINT NOT NULL,
+                                                                    
last_updated timestamp with time zone DEFAULT now() NOT NULL,
+
+    CONSTRAINT ds_capability_primary PRIMARY KEY (deliveryservice_id, 
required_capability)
+    );
+
+INSERT INTO public.deliveryservices_required_capability ("deliveryservice_id", 
"required_capability") SELECT id AS deliveryservice_id, 
UNNEST(required_capabilities) AS required_capability FROM deliveryservice d 
GROUP BY d.id, d.required_capabilities;
+
+ALTER TABLE public.deliveryservice
+DROP COLUMN required_capabilities;
diff --git 
a/traffic_ops/app/db/migrations/2022112215022300_add_required_capabilities_to_ds.up.sql
 
b/traffic_ops/app/db/migrations/2022112215022300_add_required_capabilities_to_ds.up.sql
new file mode 100644
index 0000000000..17c840f5a6
--- /dev/null
+++ 
b/traffic_ops/app/db/migrations/2022112215022300_add_required_capabilities_to_ds.up.sql
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+
+ALTER TABLE public.deliveryservice
+    ADD COLUMN required_capabilities TEXT[];
+
+UPDATE public.deliveryservice d SET required_capabilities = (SELECT 
ARRAY_AGG(required_capability) AS required_capabilities FROM 
deliveryservices_required_capability drc WHERE drc.deliveryservice_id = d.id);
+
+DROP TABLE public.deliveryservices_required_capability;
diff --git a/traffic_ops/testing/api/v3/todb_test.go 
b/traffic_ops/testing/api/v3/todb_test.go
index f42ae00ad4..123e94fa25 100644
--- a/traffic_ops/testing/api/v3/todb_test.go
+++ b/traffic_ops/testing/api/v3/todb_test.go
@@ -253,7 +253,6 @@ func Teardown(db *sql.DB) error {
 
        sqlStmt := `
        DELETE FROM api_capability;
-       DELETE FROM deliveryservices_required_capability;
        DELETE FROM server_server_capability;
        DELETE FROM server_capability;
        DELETE FROM to_extension;
diff --git a/traffic_ops/testing/api/v4/deliveryservices_test.go 
b/traffic_ops/testing/api/v4/deliveryservices_test.go
index d5c68acd6e..facb71aedd 100644
--- a/traffic_ops/testing/api/v4/deliveryservices_test.go
+++ b/traffic_ops/testing/api/v4/deliveryservices_test.go
@@ -354,8 +354,9 @@ func TestDeliveryServices(t *testing.T) {
                                "BAD REQUEST when ADDING TOPOLOGY to DS with DS 
REQUIRED CAPABILITY": {
                                        EndpointID: GetDeliveryServiceId(t, 
"ds1"), ClientSession: TOSession,
                                        RequestBody: generateDeliveryService(t, 
map[string]interface{}{
-                                               "topology": "top-for-ds-req",
-                                               "xmlId":    "ds1",
+                                               "requiredCapabilities": 
[]string{"foo"},
+                                               "topology":             
"top-for-ds-req",
+                                               "xmlId":                "ds1",
                                        }),
                                        Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
                                },
diff --git a/traffic_ops/testing/api/v4/todb_test.go 
b/traffic_ops/testing/api/v4/todb_test.go
index 7f9104a8a3..a80a3e997a 100644
--- a/traffic_ops/testing/api/v4/todb_test.go
+++ b/traffic_ops/testing/api/v4/todb_test.go
@@ -385,7 +385,6 @@ func Teardown(db *sql.DB) error {
 
        sqlStmt := `
        DELETE FROM api_capability;
-       DELETE FROM deliveryservices_required_capability;
        DELETE FROM server_server_capability;
        DELETE FROM server_capability;
        DELETE FROM to_extension;
diff --git a/traffic_ops/testing/api/v4/traffic_control_test.go 
b/traffic_ops/testing/api/v4/traffic_control_test.go
index ddbc8b06a0..cd08c61af9 100644
--- a/traffic_ops/testing/api/v4/traffic_control_test.go
+++ b/traffic_ops/testing/api/v4/traffic_control_test.go
@@ -28,7 +28,7 @@ type TrafficControl struct {
        Capabilities                                      []tc.Capability       
                  `json:"capability"`
        Coordinates                                       []tc.Coordinate       
                  `json:"coordinates"`
        DeliveryServicesRegexes                           
[]tc.DeliveryServiceRegexesTest         `json:"deliveryServicesRegexes"`
-       DeliveryServiceRequests                           
[]tc.DeliveryServiceRequestV40          `json:"deliveryServiceRequests"`
+       DeliveryServiceRequests                           
[]tc.DeliveryServiceRequestV4           `json:"deliveryServiceRequests"`
        DeliveryServiceRequestComments                    
[]tc.DeliveryServiceRequestComment      `json:"deliveryServiceRequestComments"`
        DeliveryServices                                  
[]tc.DeliveryServiceV4                  `json:"deliveryservices"`
        DeliveryServicesRequiredCapabilities              
[]tc.DeliveryServicesRequiredCapability 
`json:"deliveryservicesRequiredCapabilities"`
diff --git 
a/traffic_ops/testing/api/v5/deliveryservices_required_capabilities_test.go 
b/traffic_ops/testing/api/v5/deliveryservices_required_capabilities_test.go
index fba249641b..bb78a7adff 100644
--- a/traffic_ops/testing/api/v5/deliveryservices_required_capabilities_test.go
+++ b/traffic_ops/testing/api/v5/deliveryservices_required_capabilities_test.go
@@ -16,217 +16,211 @@ package v5
 */
 
 import (
-       "net/http"
-       "net/url"
-       "strconv"
-       "testing"
-       "time"
-
-       "github.com/apache/trafficcontrol/lib/go-rfc"
+       "fmt"
        "github.com/apache/trafficcontrol/lib/go-tc"
-       "github.com/apache/trafficcontrol/lib/go-util"
        "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert"
        "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils"
        "github.com/apache/trafficcontrol/traffic_ops/toclientlib"
        client "github.com/apache/trafficcontrol/traffic_ops/v5-client"
+       "testing"
 )
 
-func TestDeliveryServicesRequiredCapabilities(t *testing.T) {
-       WithObjs(t, []TCObj{CDNs, Types, Tenants, Users, Parameters, Profiles, 
Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, 
ServerCapabilities, Topologies, ServiceCategories, DeliveryServices, 
DeliveryServiceServerAssignments, ServerServerCapabilities, 
DeliveryServicesRequiredCapabilities}, func() {
-
-               currentTime := time.Now().UTC().Add(-15 * time.Second)
-               currentTimeRFC := currentTime.Format(time.RFC1123)
-               tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123)
-
-               methodTests := utils.TestCase[client.Session, 
client.RequestOptions, tc.DeliveryServicesRequiredCapability]{
-                       "GET": {
-                               "NOT MODIFIED when NO CHANGES made": {
-                                       ClientSession: TOSession,
-                                       RequestOpts:   
client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}},
-                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)),
-                               },
-                               "OK when VALID request": {
-                                       ClientSession: TOSession,
-                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
-                               },
-                               "OK when VALID DELIVERYSERVICEID parameter": {
-                                       ClientSession: TOSession,
-                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"deliveryServiceId": 
{strconv.Itoa(GetDeliveryServiceId(t, "ds1")())}}},
-                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
utils.ResponseLengthGreaterOrEqual(1),
-                                               
validateDSRCExpectedFields(map[string]interface{}{"DeliveryServiceId": "ds1"})),
-                               },
-                               "OK when VALID XMLID parameter": {
-                                       ClientSession: TOSession,
-                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"xmlID": {"ds2"}}},
-                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
utils.ResponseLengthGreaterOrEqual(1),
-                                               
validateDSRCExpectedFields(map[string]interface{}{"XMLID": "ds2"})),
-                               },
-                               "OK when VALID REQUIREDCAPABILITY parameter": {
-                                       ClientSession: TOSession,
-                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"requiredCapability": 
{"bar"}}},
-                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
utils.ResponseLengthGreaterOrEqual(1),
-                                               
validateDSRCExpectedFields(map[string]interface{}{"RequiredCapability": 
"bar"})),
-                               },
-                               "FIRST RESULT when LIMIT=1": {
-                                       ClientSession: TOSession,
-                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"orderby": 
{"requiredCapability"}, "limit": {"1"}}},
-                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
validateDSRCPagination("limit")),
-                               },
-                               "SECOND RESULT when LIMIT=1 OFFSET=1": {
-                                       ClientSession: TOSession,
-                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"orderby": 
{"requiredCapability"}, "limit": {"1"}, "offset": {"1"}}},
-                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
validateDSRCPagination("offset")),
-                               },
-                               "SECOND RESULT when LIMIT=1 PAGE=2": {
-                                       ClientSession: TOSession,
-                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"orderby": 
{"requiredCapability"}, "limit": {"1"}, "page": {"2"}}},
-                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
validateDSRCPagination("page")),
-                               },
-                               "BAD REQUEST when INVALID LIMIT parameter": {
-                                       ClientSession: TOSession,
-                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"limit": {"-2"}}},
-                                       Expectations:  
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
-                               },
-                               "BAD REQUEST when INVALID OFFSET parameter": {
-                                       ClientSession: TOSession,
-                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "offset": 
{"0"}}},
-                                       Expectations:  
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
-                               },
-                               "BAD REQUEST when INVALID PAGE parameter": {
-                                       ClientSession: TOSession,
-                                       RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "page": 
{"0"}}},
-                                       Expectations:  
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
-                               },
-                               "OK when CHANGES made": {
-                                       ClientSession: TOSession,
-                                       RequestOpts:   
client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: 
{currentTimeRFC}}},
-                                       Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
-                               },
-                       },
-                       "POST": {
-                               "BAD REQUEST when REASSIGNING REQUIRED 
CAPABILITY to DELIVERY SERVICE": {
-                                       ClientSession: TOSession,
-                                       RequestBody: 
tc.DeliveryServicesRequiredCapability{
-                                               DeliveryServiceID:  
util.IntPtr(GetDeliveryServiceId(t, "ds1")()),
-                                               RequiredCapability: 
util.StrPtr("foo"),
-                                       },
-                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
-                               },
-                               "BAD REQUEST when SERVERS DONT have 
CAPABILITY": {
-                                       ClientSession: TOSession,
-                                       RequestBody: 
tc.DeliveryServicesRequiredCapability{
-                                               DeliveryServiceID:  
util.IntPtr(GetDeliveryServiceId(t, "test-ds-server-assignments")()),
-                                               RequiredCapability: 
util.StrPtr("disk"),
-                                       },
-                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
-                               },
-                               "BAD REQUEST when DELIVERY SERVICE HAS TOPOLOGY 
where SERVERS DONT have CAPABILITY": {
-                                       ClientSession: TOSession,
-                                       RequestBody: 
tc.DeliveryServicesRequiredCapability{
-                                               DeliveryServiceID:  
util.IntPtr(GetDeliveryServiceId(t, "ds-top-req-cap")()),
-                                               RequiredCapability: 
util.StrPtr("bar"),
-                                       },
-                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
-                               },
-                               "BAD REQUEST when DELIVERY SERVICE ID EMPTY": {
-                                       ClientSession: TOSession,
-                                       RequestBody: 
tc.DeliveryServicesRequiredCapability{
-                                               RequiredCapability: 
util.StrPtr("bar"),
-                                       },
-                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
-                               },
-                               "BAD REQUEST when REQUIRED CAPABILITY EMPTY": {
-                                       ClientSession: TOSession,
-                                       RequestBody: 
tc.DeliveryServicesRequiredCapability{
-                                               DeliveryServiceID: 
util.IntPtr(GetDeliveryServiceId(t, "ds1")()),
-                                       },
-                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
-                               },
-                               "NOT FOUND when NON-EXISTENT REQUIRED 
CAPABILITY": {
-                                       ClientSession: TOSession,
-                                       RequestBody: 
tc.DeliveryServicesRequiredCapability{
-                                               DeliveryServiceID:  
util.IntPtr(GetDeliveryServiceId(t, "ds1")()),
-                                               RequiredCapability: 
util.StrPtr("bogus"),
-                                       },
-                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)),
-                               },
-                               "NOT FOUND when NON-EXISTENT DELIVERY SERVICE 
ID": {
-                                       ClientSession: TOSession,
-                                       RequestBody: 
tc.DeliveryServicesRequiredCapability{
-                                               DeliveryServiceID:  
util.IntPtr(-1),
-                                               RequiredCapability: 
util.StrPtr("foo"),
-                                       },
-                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)),
-                               },
-                               "BAD REQUEST when INVALID DELIVERY SERVICE 
TYPE": {
-                                       ClientSession: TOSession,
-                                       RequestBody: 
tc.DeliveryServicesRequiredCapability{
-                                               DeliveryServiceID:  
util.IntPtr(GetDeliveryServiceId(t, "anymap-ds")()),
-                                               RequiredCapability: 
util.StrPtr("foo"),
-                                       },
-                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
-                               },
-                       },
-                       "DELETE": {
-                               "OK when VALID request": {
-                                       ClientSession: TOSession,
-                                       RequestBody: 
tc.DeliveryServicesRequiredCapability{
-                                               DeliveryServiceID:  
util.IntPtr(GetDeliveryServiceId(t, "ds-top-req-cap")()),
-                                               RequiredCapability: 
util.StrPtr("ram"),
-                                       },
-                                       Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
-                               },
-                               "NOT FOUND when NON-EXISTENT DELIVERYSERVICEID 
parameter": {
-                                       ClientSession: TOSession,
-                                       RequestBody: 
tc.DeliveryServicesRequiredCapability{
-                                               DeliveryServiceID:  
util.IntPtr(-1),
-                                               RequiredCapability: 
util.StrPtr("foo"),
-                                       },
-                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)),
-                               },
-                               "NOT FOUND when NON-EXISTENT REQUIREDCAPABILITY 
parameter": {
-                                       ClientSession: TOSession,
-                                       RequestBody: 
tc.DeliveryServicesRequiredCapability{
-                                               DeliveryServiceID:  
util.IntPtr(GetDeliveryServiceId(t, "ds1")()),
-                                               RequiredCapability: 
util.StrPtr("bogus"),
-                                       },
-                                       Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)),
-                               },
-                       },
-               }
-
-               for method, testCases := range methodTests {
-                       t.Run(method, func(t *testing.T) {
-                               for name, testCase := range testCases {
-                                       switch method {
-                                       case "GET":
-                                               t.Run(name, func(t *testing.T) {
-                                                       resp, reqInf, err := 
testCase.ClientSession.GetDeliveryServicesRequiredCapabilities(testCase.RequestOpts)
-                                                       for _, check := range 
testCase.Expectations {
-                                                               check(t, 
reqInf, resp.Response, resp.Alerts, err)
-                                                       }
-                                               })
-                                       case "POST":
-                                               t.Run(name, func(t *testing.T) {
-                                                       alerts, reqInf, err := 
testCase.ClientSession.CreateDeliveryServicesRequiredCapability(testCase.RequestBody,
 testCase.RequestOpts)
-                                                       for _, check := range 
testCase.Expectations {
-                                                               check(t, 
reqInf, nil, alerts, err)
-                                                       }
-                                               })
-                                       case "DELETE":
-                                               t.Run(name, func(t *testing.T) {
-                                                       alerts, reqInf, err := 
testCase.ClientSession.DeleteDeliveryServicesRequiredCapability(*testCase.RequestBody.DeliveryServiceID,
 *testCase.RequestBody.RequiredCapability, testCase.RequestOpts)
-                                                       for _, check := range 
testCase.Expectations {
-                                                               check(t, 
reqInf, nil, alerts, err)
-                                                       }
-                                               })
-                                       }
-                               }
-                       })
-               }
-       })
-
-}
+//func TestDeliveryServicesRequiredCapabilities(t *testing.T) {
+//     WithObjs(t, []TCObj{CDNs, Types, Tenants, Users, Parameters, Profiles, 
Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, 
ServerCapabilities, Topologies, ServiceCategories, DeliveryServices, 
DeliveryServiceServerAssignments, ServerServerCapabilities, 
DeliveryServicesRequiredCapabilities}, func() {
+//
+//             currentTime := time.Now().UTC().Add(-15 * time.Second)
+//             currentTimeRFC := currentTime.Format(time.RFC1123)
+//             tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123)
+//
+//             methodTests := utils.TestCase[client.Session, 
client.RequestOptions, tc.DeliveryServicesRequiredCapability]{
+//                     "GET": {
+//                             "NOT MODIFIED when NO CHANGES made": {
+//                                     ClientSession: TOSession,
+//                                     RequestOpts:   
client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}},
+//                                     Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)),
+//                             },
+//                             "OK when VALID request": {
+//                                     ClientSession: TOSession,
+//                                     Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+//                             },
+//                             "OK when VALID DELIVERYSERVICEID parameter": {
+//                                     ClientSession: TOSession,
+//                                     RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"deliveryServiceId": 
{strconv.Itoa(GetDeliveryServiceId(t, "ds1")())}}},
+//                                     Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
utils.ResponseLengthGreaterOrEqual(1),
+//                                             
validateDSRCExpectedFields(map[string]interface{}{"DeliveryServiceId": "ds1"})),
+//                             },
+//                             "OK when VALID XMLID parameter": {
+//                                     ClientSession: TOSession,
+//                                     RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"xmlID": {"ds2"}}},
+//                                     Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
utils.ResponseLengthGreaterOrEqual(1),
+//                                             
validateDSRCExpectedFields(map[string]interface{}{"XMLID": "ds2"})),
+//                             },
+//                             "OK when VALID REQUIREDCAPABILITY parameter": {
+//                                     ClientSession: TOSession,
+//                                     RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"requiredCapability": 
{"bar"}}},
+//                                     Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
utils.ResponseLengthGreaterOrEqual(1),
+//                                             
validateDSRCExpectedFields(map[string]interface{}{"RequiredCapability": 
"bar"})),
+//                             },
+//                             "FIRST RESULT when LIMIT=1": {
+//                                     ClientSession: TOSession,
+//                                     RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"orderby": 
{"requiredCapability"}, "limit": {"1"}}},
+//                                     Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
validateDSRCPagination("limit")),
+//                             },
+//                             "SECOND RESULT when LIMIT=1 OFFSET=1": {
+//                                     ClientSession: TOSession,
+//                                     RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"orderby": 
{"requiredCapability"}, "limit": {"1"}, "offset": {"1"}}},
+//                                     Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
validateDSRCPagination("offset")),
+//                             },
+//                             "SECOND RESULT when LIMIT=1 PAGE=2": {
+//                                     ClientSession: TOSession,
+//                                     RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"orderby": 
{"requiredCapability"}, "limit": {"1"}, "page": {"2"}}},
+//                                     Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), 
validateDSRCPagination("page")),
+//                             },
+//                             "BAD REQUEST when INVALID LIMIT parameter": {
+//                                     ClientSession: TOSession,
+//                                     RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"limit": {"-2"}}},
+//                                     Expectations:  
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+//                             },
+//                             "BAD REQUEST when INVALID OFFSET parameter": {
+//                                     ClientSession: TOSession,
+//                                     RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "offset": 
{"0"}}},
+//                                     Expectations:  
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+//                             },
+//                             "BAD REQUEST when INVALID PAGE parameter": {
+//                                     ClientSession: TOSession,
+//                                     RequestOpts:   
client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "page": 
{"0"}}},
+//                                     Expectations:  
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+//                             },
+//                             "OK when CHANGES made": {
+//                                     ClientSession: TOSession,
+//                                     RequestOpts:   
client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: 
{currentTimeRFC}}},
+//                                     Expectations:  
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+//                             },
+//                     },
+//                     "POST": {
+//                             "BAD REQUEST when REASSIGNING REQUIRED 
CAPABILITY to DELIVERY SERVICE": {
+//                                     ClientSession: TOSession,
+//                                     RequestBody: 
tc.DeliveryServicesRequiredCapability{
+//                                             DeliveryServiceID:  
util.IntPtr(GetDeliveryServiceId(t, "ds1")()),
+//                                             RequiredCapability: 
util.StrPtr("foo"),
+//                                     },
+//                                     Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+//                             },
+//                             "BAD REQUEST when SERVERS DONT have 
CAPABILITY": {
+//                                     ClientSession: TOSession,
+//                                     RequestBody: 
tc.DeliveryServicesRequiredCapability{
+//                                             DeliveryServiceID:  
util.IntPtr(GetDeliveryServiceId(t, "test-ds-server-assignments")()),
+//                                             RequiredCapability: 
util.StrPtr("disk"),
+//                                     },
+//                                     Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+//                             },
+//                             "BAD REQUEST when DELIVERY SERVICE HAS TOPOLOGY 
where SERVERS DONT have CAPABILITY": {
+//                                     ClientSession: TOSession,
+//                                     RequestBody: 
tc.DeliveryServicesRequiredCapability{
+//                                             DeliveryServiceID:  
util.IntPtr(GetDeliveryServiceId(t, "ds-top-req-cap")()),
+//                                             RequiredCapability: 
util.StrPtr("bar"),
+//                                     },
+//                                     Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+//                             },
+//                             "BAD REQUEST when DELIVERY SERVICE ID EMPTY": {
+//                                     ClientSession: TOSession,
+//                                     RequestBody: 
tc.DeliveryServicesRequiredCapability{
+//                                             RequiredCapability: 
util.StrPtr("bar"),
+//                                     },
+//                                     Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+//                             },
+//                             "BAD REQUEST when REQUIRED CAPABILITY EMPTY": {
+//                                     ClientSession: TOSession,
+//                                     RequestBody: 
tc.DeliveryServicesRequiredCapability{
+//                                             DeliveryServiceID: 
util.IntPtr(GetDeliveryServiceId(t, "ds1")()),
+//                                     },
+//                                     Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+//                             },
+//                             "NOT FOUND when NON-EXISTENT REQUIRED 
CAPABILITY": {
+//                                     ClientSession: TOSession,
+//                                     RequestBody: 
tc.DeliveryServicesRequiredCapability{
+//                                             DeliveryServiceID:  
util.IntPtr(GetDeliveryServiceId(t, "ds1")()),
+//                                             RequiredCapability: 
util.StrPtr("bogus"),
+//                                     },
+//                                     Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)),
+//                             },
+//                             "NOT FOUND when NON-EXISTENT DELIVERY SERVICE 
ID": {
+//                                     ClientSession: TOSession,
+//                                     RequestBody: 
tc.DeliveryServicesRequiredCapability{
+//                                             DeliveryServiceID:  
util.IntPtr(-1),
+//                                             RequiredCapability: 
util.StrPtr("foo"),
+//                                     },
+//                                     Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)),
+//                             },
+//                             "BAD REQUEST when INVALID DELIVERY SERVICE 
TYPE": {
+//                                     ClientSession: TOSession,
+//                                     RequestBody: 
tc.DeliveryServicesRequiredCapability{
+//                                             DeliveryServiceID:  
util.IntPtr(GetDeliveryServiceId(t, "anymap-ds")()),
+//                                             RequiredCapability: 
util.StrPtr("foo"),
+//                                     },
+//                                     Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
+//                             },
+//                     },
+//                     "DELETE": {
+//                             "OK when VALID request": {
+//                                     ClientSession: TOSession,
+//                                     RequestBody: 
tc.DeliveryServicesRequiredCapability{
+//                                             DeliveryServiceID:  
util.IntPtr(GetDeliveryServiceId(t, "ds-top-req-cap")()),
+//                                             RequiredCapability: 
util.StrPtr("ram"),
+//                                     },
+//                                     Expectations: 
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+//                             },
+//                             "NOT FOUND when NON-EXISTENT DELIVERYSERVICEID 
parameter": {
+//                                     ClientSession: TOSession,
+//                                     RequestBody: 
tc.DeliveryServicesRequiredCapability{
+//                                             DeliveryServiceID:  
util.IntPtr(-1),
+//                                             RequiredCapability: 
util.StrPtr("foo"),
+//                                     },
+//                                     Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)),
+//                             },
+//                             "NOT FOUND when NON-EXISTENT REQUIREDCAPABILITY 
parameter": {
+//                                     ClientSession: TOSession,
+//                                     RequestBody: 
tc.DeliveryServicesRequiredCapability{
+//                                             DeliveryServiceID:  
util.IntPtr(GetDeliveryServiceId(t, "ds1")()),
+//                                             RequiredCapability: 
util.StrPtr("bogus"),
+//                                     },
+//                                     Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)),
+//                             },
+//                     },
+//             }
+//
+//             for method, testCases := range methodTests {
+//                     t.Run(method, func(t *testing.T) {
+//                             for name, testCase := range testCases {
+//                                     switch method {
+//                                     case "GET":
+//                                             t.Run(name, func(t *testing.T) {
+//                                                     resp, reqInf, err := 
testCase.ClientSession.GetDeliveryServicesRequiredCapabilities(testCase.RequestOpts)
+//                                                     for _, check := range 
testCase.Expectations {
+//                                                             check(t, 
reqInf, resp.Response, resp.Alerts, err)
+//                                                     }
+//                                             })
+//                                     case "POST":
+//                                             t.Run(name, func(t *testing.T) {
+//                                                     alerts, reqInf, err := 
testCase.ClientSession.CreateDeliveryServicesRequiredCapability(testCase.RequestBody,
 testCase.RequestOpts)
+//                                                     for _, check := range 
testCase.Expectations {
+//                                                             check(t, 
reqInf, nil, alerts, err)
+//                                                     }
+//                                             })
+//                                     case "DELETE":
+//                                             t.Run(name, func(t *testing.T) {
+//                                                     alerts, reqInf, err := 
testCase.ClientSession.DeleteDeliveryServicesRequiredCapability(*testCase.RequestBody.DeliveryServiceID,
 *testCase.RequestBody.RequiredCapability, testCase.RequestOpts)
+//                                                     for _, check := range 
testCase.Expectations {
+//                                                             check(t, 
reqInf, nil, alerts, err)
+//                                                     }
+//                                             })
+//                                     }
+//                             }
+//                     })
+//             }
+//     })
+//
+//}
 
 func validateDSRCExpectedFields(expectedResp map[string]interface{}) 
utils.CkReqFunc {
        return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ 
tc.Alerts, _ error) {
@@ -276,18 +270,24 @@ func CreateTestDeliveryServicesRequiredCapabilities(t 
*testing.T) {
                        DeliveryServiceID:  &dsId,
                        RequiredCapability: dsrc.RequiredCapability,
                }
-               resp, _, err := 
TOSession.CreateDeliveryServicesRequiredCapability(dsrc, 
client.RequestOptions{})
+               opts := client.NewRequestOptions()
+               opts.QueryParameters.Set("id", fmt.Sprint(dsId))
+               resp, _, err := TOSession.GetDeliveryServices(opts)
+               assert.NoError(t, err, "Error getting delivery services: %v - 
alerts: %v", err, resp.Alerts)
+               assert.Equal(t, len(resp.Response), 1, "Expected response to 
have exactly 1 delivery service, but got %d", len(resp.Response))
+               ds := resp.Response[0]
+               ds.RequiredCapabilities = []string{*dsrc.RequiredCapability}
+               _, _, err = TOSession.UpdateDeliveryService(dsId, ds, 
client.NewRequestOptions())
                assert.NoError(t, err, "Unexpected error creating a Delivery 
Service/Required Capability relationship: %v - alerts: %+v", err, resp.Alerts)
        }
 }
 
 func DeleteTestDeliveryServicesRequiredCapabilities(t *testing.T) {
-       // Get Required Capabilities to delete them
-       dsrcs, _, err := 
TOSession.GetDeliveryServicesRequiredCapabilities(client.RequestOptions{})
-       assert.NoError(t, err, "Error getting Delivery Service/Required 
Capability relationships: %v - alerts: %+v", err, dsrcs.Alerts)
-
-       for _, dsrc := range dsrcs.Response {
-               alerts, _, err := 
TOSession.DeleteDeliveryServicesRequiredCapability(*dsrc.DeliveryServiceID, 
*dsrc.RequiredCapability, client.RequestOptions{})
-               assert.NoError(t, err, "Error deleting a relationship between a 
Delivery Service and a Capability: %v - alerts: %+v", err, alerts.Alerts)
+       resp, _, err := TOSession.GetDeliveryServices(client.RequestOptions{})
+       assert.NoError(t, err, "Error getting delivery services: %v - alerts: 
%v", err, resp.Alerts)
+       for _, r := range resp.Response {
+               r.RequiredCapabilities = []string{}
+               response, _, err := TOSession.UpdateDeliveryService(*r.ID, r, 
client.RequestOptions{})
+               assert.NoError(t, err, "Error removing Delivery Service/ 
Required Capability relationship: %v - alerts: %v", err, response.Alerts)
        }
 }
diff --git a/traffic_ops/testing/api/v5/deliveryservices_test.go 
b/traffic_ops/testing/api/v5/deliveryservices_test.go
index ded0f46730..3b9f574057 100644
--- a/traffic_ops/testing/api/v5/deliveryservices_test.go
+++ b/traffic_ops/testing/api/v5/deliveryservices_test.go
@@ -32,7 +32,7 @@ import (
 )
 
 func TestDeliveryServices(t *testing.T) {
-       WithObjs(t, []TCObj{CDNs, Types, Tenants, Users, Parameters, Profiles, 
Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, 
ServerCapabilities, ServiceCategories, DeliveryServices, 
ServerServerCapabilities, DeliveryServicesRequiredCapabilities, 
DeliveryServiceServerAssignments}, func() {
+       WithObjs(t, []TCObj{CDNs, Types, Tenants, Users, Parameters, Profiles, 
Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, 
ServerCapabilities, ServiceCategories, DeliveryServices, 
ServerServerCapabilities, DeliveryServiceServerAssignments}, func() {
 
                currentTime := time.Now().UTC().Add(-15 * time.Second)
                currentTimeRFC := currentTime.Format(time.RFC1123)
@@ -362,8 +362,9 @@ func TestDeliveryServices(t *testing.T) {
                                "BAD REQUEST when ADDING TOPOLOGY to DS with DS 
REQUIRED CAPABILITY": {
                                        EndpointID: GetDeliveryServiceId(t, 
"ds1"), ClientSession: TOSession,
                                        RequestBody: generateDeliveryService(t, 
map[string]interface{}{
-                                               "topology": "top-for-ds-req",
-                                               "xmlId":    "ds1",
+                                               "topology":             
"top-for-ds-req",
+                                               "xmlId":                "ds1",
+                                               "requiredCapabilities": 
[]string{"foo"},
                                        }),
                                        Expectations: 
utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
                                },
diff --git a/traffic_ops/testing/api/v5/deliveryserviceservers_test.go 
b/traffic_ops/testing/api/v5/deliveryserviceservers_test.go
index 7b78249824..8feb52d947 100644
--- a/traffic_ops/testing/api/v5/deliveryserviceservers_test.go
+++ b/traffic_ops/testing/api/v5/deliveryserviceservers_test.go
@@ -31,7 +31,7 @@ import (
 )
 
 func TestDeliveryServiceServers(t *testing.T) {
-       WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, 
Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, 
ServiceCategories, ServerCapabilities, DeliveryServices, 
DeliveryServiceServerAssignments, ServerServerCapabilities, 
DeliveryServicesRequiredCapabilities}, func() {
+       WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, 
Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, 
ServiceCategories, ServerCapabilities, DeliveryServices, 
DeliveryServiceServerAssignments, ServerServerCapabilities}, func() {
 
                tomorrow := time.Now().UTC().AddDate(0, 0, 
1).Format(time.RFC1123)
 
diff --git a/traffic_ops/testing/api/v5/todb_test.go 
b/traffic_ops/testing/api/v5/todb_test.go
index 2b09fa2903..aed6c19387 100644
--- a/traffic_ops/testing/api/v5/todb_test.go
+++ b/traffic_ops/testing/api/v5/todb_test.go
@@ -385,7 +385,6 @@ func Teardown(db *sql.DB) error {
 
        sqlStmt := `
        DELETE FROM api_capability;
-       DELETE FROM deliveryservices_required_capability;
        DELETE FROM server_server_capability;
        DELETE FROM server_capability;
        DELETE FROM to_extension;
diff --git a/traffic_ops/traffic_ops_golang/api/shared_handlers.go 
b/traffic_ops/traffic_ops_golang/api/shared_handlers.go
index 2f7e139801..594b34a62c 100644
--- a/traffic_ops/traffic_ops_golang/api/shared_handlers.go
+++ b/traffic_ops/traffic_ops_golang/api/shared_handlers.go
@@ -142,6 +142,7 @@ func SetLastModifiedHeader(r *http.Request, useIMS bool) 
bool {
 type errWriterFunc func(w http.ResponseWriter, r *http.Request, tx *sql.Tx, 
statusCode int, userErr error, sysErr error)
 type readSuccessWriterFunc func(w http.ResponseWriter, r *http.Request, 
statusCode int, results interface{})
 type deleteSuccessWriterFunc func(w http.ResponseWriter, r *http.Request, 
message string)
+type createSuccessWriterFunc func(w http.ResponseWriter, r *http.Request, 
statusCode int, alerts tc.Alerts, results interface{})
 
 // ReadHandler creates a handler function from the pointer to a struct 
implementing the Reader interface
 //
@@ -471,17 +472,33 @@ func deleteHandlerHelper(deleter Deleter, errHandler 
errWriterFunc, successHandl
 //     *change log entry
 //     *forming and writing the body over the wire
 func CreateHandler(creator Creator) http.HandlerFunc {
+       return createHandlerHelper(
+               creator,
+               HandleErr,
+               func(w http.ResponseWriter, r *http.Request, statusCode int, 
alerts tc.Alerts, results interface{}) {
+                       if len(alerts.Alerts) > 0 {
+                               WriteAlertsObj(w, r, statusCode, alerts, 
results)
+                       } else {
+                               w.WriteHeader(statusCode)
+                               WriteResp(w, r, results)
+                       }
+
+               },
+       )
+}
+
+func createHandlerHelper(creator Creator, errHandler errWriterFunc, 
successHandler createSuccessWriterFunc) http.HandlerFunc {
        return func(w http.ResponseWriter, r *http.Request) {
                inf, userErr, sysErr, errCode := NewInfo(r, nil, nil)
                if userErr != nil || sysErr != nil {
-                       HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+                       errHandler(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
                        return
                }
                defer inf.Close()
 
                interfacePtr := reflect.ValueOf(creator)
                if interfacePtr.Kind() != reflect.Ptr {
-                       HandleErr(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, errors.New("reflect: can only indirect 
from a pointer"))
+                       errHandler(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, errors.New("reflect: can only indirect 
from a pointer"))
                        return
                }
                objectType := reflect.Indirect(interfacePtr).Type()
@@ -491,7 +508,7 @@ func CreateHandler(creator Creator) http.HandlerFunc {
                if c, ok := obj.(MultipleCreator); ok && 
c.AllowMultipleCreates() {
                        data, err := ioutil.ReadAll(r.Body)
                        if err != nil {
-                               HandleErr(w, r, inf.Tx.Tx, 
http.StatusBadRequest, err, nil)
+                               errHandler(w, r, inf.Tx.Tx, 
http.StatusBadRequest, err, nil)
                                return
                        }
 
@@ -502,7 +519,7 @@ func CreateHandler(creator Creator) http.HandlerFunc {
 
                        objSlice, err := parseMultipleCreates(data, objectType, 
inf)
                        if err != nil {
-                               HandleErr(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, err)
+                               errHandler(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, err)
                                return
                        }
 
@@ -515,30 +532,30 @@ func CreateHandler(creator Creator) http.HandlerFunc {
                                        if sysErr != nil {
                                                code = 
http.StatusInternalServerError
                                        }
-                                       HandleErr(w, r, inf.Tx.Tx, code, 
userErr, sysErr)
+                                       errHandler(w, r, inf.Tx.Tx, code, 
userErr, sysErr)
                                        return
                                }
 
                                if t, ok := objElem.(Tenantable); ok {
                                        authorized, err := 
t.IsTenantAuthorized(inf.User)
                                        if err != nil {
-                                               HandleErr(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, errors.New("checking tenant authorized: 
"+err.Error()))
+                                               errHandler(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, errors.New("checking tenant authorized: 
"+err.Error()))
                                                return
                                        }
                                        if !authorized {
-                                               HandleErr(w, r, inf.Tx.Tx, 
http.StatusForbidden, errors.New("not authorized on this tenant"), nil)
+                                               errHandler(w, r, inf.Tx.Tx, 
http.StatusForbidden, errors.New("not authorized on this tenant"), nil)
                                                return
                                        }
                                }
 
                                userErr, sysErr, errCode = objElem.Create()
                                if userErr != nil || sysErr != nil {
-                                       HandleErr(w, r, inf.Tx.Tx, errCode, 
userErr, sysErr)
+                                       errHandler(w, r, inf.Tx.Tx, errCode, 
userErr, sysErr)
                                        return
                                }
 
                                if err = CreateChangeLog(ApiChange, Created, 
objElem, inf.User, inf.Tx.Tx); err != nil {
-                                       HandleErr(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, fmt.Errorf("inserting changelog: %w", err))
+                                       errHandler(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, fmt.Errorf("inserting changelog: %w", err))
                                        return
                                }
                        }
@@ -562,7 +579,7 @@ func CreateHandler(creator Creator) http.HandlerFunc {
                                        
alerts.AddAlerts(objElem.(AlertsResponse).GetAlerts())
                                }
                        }
-                       WriteAlertsObj(w, r, http.StatusOK, alerts, responseObj)
+                       successHandler(w, r, http.StatusOK, alerts, responseObj)
 
                } else {
                        userErr, sysErr := decodeAndValidateRequestBody(r, obj)
@@ -571,41 +588,58 @@ func CreateHandler(creator Creator) http.HandlerFunc {
                                if sysErr != nil {
                                        code = http.StatusInternalServerError
                                }
-                               HandleErr(w, r, inf.Tx.Tx, code, userErr, 
sysErr)
+                               errHandler(w, r, inf.Tx.Tx, code, userErr, 
sysErr)
                                return
                        }
 
                        if t, ok := obj.(Tenantable); ok {
                                authorized, err := 
t.IsTenantAuthorized(inf.User)
                                if err != nil {
-                                       HandleErr(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, errors.New("checking tenant authorized: 
"+err.Error()))
+                                       errHandler(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, errors.New("checking tenant authorized: 
"+err.Error()))
                                        return
                                }
                                if !authorized {
-                                       HandleErr(w, r, inf.Tx.Tx, 
http.StatusForbidden, errors.New("not authorized on this tenant"), nil)
+                                       errHandler(w, r, inf.Tx.Tx, 
http.StatusForbidden, errors.New("not authorized on this tenant"), nil)
                                        return
                                }
                        }
 
                        userErr, sysErr, errCode = obj.Create()
                        if userErr != nil || sysErr != nil {
-                               HandleErr(w, r, inf.Tx.Tx, errCode, userErr, 
sysErr)
+                               errHandler(w, r, inf.Tx.Tx, errCode, userErr, 
sysErr)
                                return
                        }
 
                        if err := CreateChangeLog(ApiChange, Created, obj, 
inf.User, inf.Tx.Tx); err != nil {
-                               HandleErr(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, fmt.Errorf("inserting changelog: %w", err))
+                               errHandler(w, r, inf.Tx.Tx, 
http.StatusInternalServerError, nil, fmt.Errorf("inserting changelog: %w", err))
                                return
                        }
                        alerts := tc.CreateAlerts(tc.SuccessLevel, 
obj.GetType()+" was created.")
                        if alertsObj, hasAlerts := obj.(AlertsResponse); 
hasAlerts {
                                alerts.AddAlerts(alertsObj.GetAlerts())
                        }
-                       WriteAlertsObj(w, r, http.StatusOK, alerts, obj)
+                       successHandler(w, r, http.StatusOK, alerts, obj)
                }
        }
 }
 
+// DeprecatedCreateHandler creates a net/http.HandlerFunc for the passed 
Creator object, and adds a deprecation
+// notice, optionally with a passed alternative route suggestion.
+func DeprecatedCreateHandler(creator Creator, alternative *string) 
http.HandlerFunc {
+       return createHandlerHelper(
+               creator,
+               func(w http.ResponseWriter, r *http.Request, tx *sql.Tx, 
statusCode int, userErr error, sysErr error) {
+                       HandleDeprecatedErr(w, r, tx, statusCode, userErr, 
sysErr, alternative)
+               },
+               func(w http.ResponseWriter, r *http.Request, statusCode int, 
alerts tc.Alerts, results interface{}) {
+                       depAlerts := CreateDeprecationAlerts(alternative)
+                       al := tc.Alerts{Alerts: depAlerts.Alerts}
+                       al.AddAlerts(alerts)
+                       WriteAlertsObj(w, r, statusCode, al, results)
+               },
+       )
+}
+
 func parseMultipleCreates(data []byte, desiredType reflect.Type, inf *APIInfo) 
([]Creator, error) {
        buf := ioutil.NopCloser(bytes.NewReader(data))
 
diff --git a/traffic_ops/traffic_ops_golang/crconfig/deliveryservice.go 
b/traffic_ops/traffic_ops_golang/crconfig/deliveryservice.go
index 94875ac764..17805994a7 100644
--- a/traffic_ops/traffic_ops_golang/crconfig/deliveryservice.go
+++ b/traffic_ops/traffic_ops_golang/crconfig/deliveryservice.go
@@ -131,9 +131,7 @@ SELECT d.anonymous_blocking_enabled,
        d.miss_long,
        p.name AS profile,
        d.protocol,
-       (SELECT ARRAY_AGG(required_capability ORDER BY required_capability)
-                         FROM deliveryservices_required_capability
-                         WHERE deliveryservice_id = d.id) AS 
required_capabilities,
+       d.required_capabilities,
        d.topology,
        d.tr_request_headers,
        d.tr_response_headers,
diff --git a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go 
b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
index 7b1dae9fd0..d4e493fa7b 100644
--- a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
+++ b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
@@ -850,13 +850,11 @@ func GetRequiredCapabilitiesOfDeliveryServices(ids []int, 
tx *sql.Tx) (map[int][
        dsCaps := make(map[int][]string, len(ids))
        q := `
 SELECT
-  d.id,
-  ARRAY_REMOVE(ARRAY_AGG(dsrc.required_capability ORDER BY 
dsrc.required_capability), NULL) AS required_capabilities
-FROM deliveryservice d
-LEFT JOIN deliveryservices_required_capability dsrc on d.id = 
dsrc.deliveryservice_id
-WHERE
-  d.id = ANY($1)
-GROUP BY d.id
+  ds.id,
+  ARRAY_REMOVE((ds.required_capabilities), NULL) AS required_capabilities
+FROM deliveryservice ds
+WHERE ds.id = ANY($1)
+GROUP BY ds.id, ds.required_capabilities
 `
        rows, err := tx.Query(q, pq.Array(&queryIDs))
        if err != nil {
@@ -927,23 +925,14 @@ func ScanCachegroupsServerCapabilities(rows *sql.Rows) 
(map[string][]int, map[in
 // GetDSRequiredCapabilitiesFromID returns the server's capabilities.
 func GetDSRequiredCapabilitiesFromID(id int, tx *sql.Tx) ([]string, error) {
        q := `
-       SELECT required_capability
-       FROM deliveryservices_required_capability
-       WHERE deliveryservice_id = $1
-       ORDER BY required_capability`
-       rows, err := tx.Query(q, id)
-       if err != nil {
-               return nil, errors.New("querying deliveryservice required 
capabilities from id: " + err.Error())
-       }
-       defer rows.Close()
+       SELECT required_capabilities
+       FROM deliveryservice
+       WHERE id = $1
+       ORDER BY required_capabilities`
 
        caps := []string{}
-       for rows.Next() {
-               var cap string
-               if err := rows.Scan(&cap); err != nil {
-                       return nil, errors.New("scanning capability: " + 
err.Error())
-               }
-               caps = append(caps, cap)
+       if err := tx.QueryRow(q, id).Scan(pq.Array(&caps)); err != nil {
+               return nil, errors.New("getting/ scanning capability: " + 
err.Error())
        }
        return caps, nil
 }
@@ -1546,7 +1535,7 @@ func GetDeliveryServiceTypeAndCDNName(dsID int, tx 
*sql.Tx) (tc.DSType, string,
        return dsType, cdnName, true, nil
 }
 
-// GetDeliveryServiceTypeAndTopology returns the type of the deliveryservice 
and the name of its topology.
+// GetDeliveryServiceTypeRequiredCapabilitiesAndTopology returns the type of 
the deliveryservice and the name of its topology.
 func GetDeliveryServiceTypeRequiredCapabilitiesAndTopology(dsID int, tx 
*sql.Tx) (tc.DSType, []string, *string, bool, error) {
        var dsType tc.DSType
        var reqCap []string
@@ -1554,13 +1543,12 @@ func 
GetDeliveryServiceTypeRequiredCapabilitiesAndTopology(dsID int, tx *sql.Tx)
        q := `
 SELECT
   t.name,
-  ARRAY_REMOVE(ARRAY_AGG(dsrc.required_capability ORDER BY 
dsrc.required_capability), NULL) AS required_capabilities,
+  ARRAY_REMOVE(ds.required_capabilities, NULL) AS required_capabilities,
   ds.topology
 FROM deliveryservice AS ds
-LEFT JOIN deliveryservices_required_capability AS dsrc ON 
dsrc.deliveryservice_id = ds.id
 JOIN type t ON ds.type = t.id
 WHERE ds.id = $1
-GROUP BY t.name, ds.topology
+GROUP BY t.name, ds.topology, ds.required_capabilities
 `
        if err := tx.QueryRow(q, dsID).Scan(&dsType, pq.Array(&reqCap), 
&topology); err != nil {
                if err == sql.ErrNoRows {
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go
index 809c586477..1ead836a1c 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices.go
@@ -347,7 +347,7 @@ func recreateTLSVersions(versions []string, dsid int, tx 
*sql.Tx) error {
        return nil
 }
 
-// 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.
+// createV40 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 createV40(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, dsV4 
tc.DeliveryServiceV40, omitExtraLongDescFields bool) (*tc.DeliveryServiceV40, 
int, error, error) {
        ds, code, userErr, sysErr := createV41(w, r, inf, 
tc.DeliveryServiceV41{DeliveryServiceV40: dsV4}, omitExtraLongDescFields)
        if userErr != nil || sysErr != nil || ds == nil {
@@ -377,12 +377,15 @@ func createV41(w http.ResponseWriter, r *http.Request, 
inf *api.APIInfo, ds tc.D
 // error are returned. The status code SHOULD NOT be used, if both errors are
 // nil.
 func createV50(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, ds 
tc.DeliveryServiceV5, omitExtraLongDescFields bool, longDesc1, longDesc2 
*string) (*tc.DeliveryServiceV5, int, error, error) {
+       var err error
        tx := inf.Tx.Tx
-       err := Validate(tx, &ds)
-       if err != nil {
-               return nil, http.StatusBadRequest, fmt.Errorf("invalid request: 
%w", err), nil
+       userErr, sysErr := Validate(tx, &ds)
+       if userErr != nil {
+               return nil, http.StatusBadRequest, fmt.Errorf("invalid request: 
%w", userErr), nil
+       }
+       if sysErr != nil {
+               return nil, http.StatusInternalServerError, nil, sysErr
        }
-
        if authorized, err := isTenantAuthorized(inf, &ds); err != nil {
                return nil, http.StatusInternalServerError, nil, 
fmt.Errorf("checking tenant: %w", err)
        } else if !authorized {
@@ -469,6 +472,7 @@ func createV50(w http.ResponseWriter, r *http.Request, inf 
*api.APIInfo, ds tc.D
                        ds.LastHeaderRewrite,
                        ds.ServiceCategory,
                        ds.MaxRequestHeaderBytes,
+                       pq.Array(ds.RequiredCapabilities),
                )
        } else {
                resultRows, err = tx.Query(insertQuery(),
@@ -532,6 +536,7 @@ func createV50(w http.ResponseWriter, r *http.Request, inf 
*api.APIInfo, ds tc.D
                        ds.LastHeaderRewrite,
                        ds.ServiceCategory,
                        ds.MaxRequestHeaderBytes,
+                       pq.Array(ds.RequiredCapabilities),
                )
        }
 
@@ -916,6 +921,13 @@ func updateV31(w http.ResponseWriter, r *http.Request, inf 
*api.APIInfo, dsV31 *
        dsNull := tc.DeliveryServiceNullableV30(*dsV31)
        ds := dsNull.UpgradeToV4()
        dsV41 := ds
+       dsMap, err := 
dbhelpers.GetRequiredCapabilitiesOfDeliveryServices([]int{*dsV31.ID}, inf.Tx.Tx)
+       if err != nil {
+               return nil, http.StatusInternalServerError, nil, err
+       }
+       if caps, ok := dsMap[*dsV31.ID]; ok {
+               dsV41.RequiredCapabilities = caps
+       }
        if dsV41.ID == nil {
                return nil, http.StatusInternalServerError, nil, 
errors.New("cannot update a Delivery Service with nil ID")
        }
@@ -926,11 +938,11 @@ func updateV31(w http.ResponseWriter, r *http.Request, 
inf *api.APIInfo, dsV31 *
                return nil, http.StatusInternalServerError, nil, 
fmt.Errorf("getting TLS versions for DS #%d in API version < 4.0: %w", 
*dsV41.ID, sysErr)
        }
 
-       res, status, usrErr, sysErr := updateV40(w, r, inf, 
&dsV41.DeliveryServiceV40, false)
+       res, status, usrErr, sysErr := updateV41(w, r, inf, &dsV41, false)
        if res == nil || usrErr != nil || sysErr != nil {
                return nil, status, usrErr, sysErr
        }
-       ds.DeliveryServiceV40 = *res
+       ds.DeliveryServiceV40 = res.DeliveryServiceV40
        if dsV31.CacheURL != nil {
                _, err := tx.Exec("UPDATE deliveryservice SET cacheurl = $1 
WHERE id = $2",
                        *dsV31.CacheURL,
@@ -991,8 +1003,12 @@ func updateV41(w http.ResponseWriter, r *http.Request, 
inf *api.APIInfo, dsV4 *t
 func updateV50(w http.ResponseWriter, r *http.Request, inf *api.APIInfo, ds 
*tc.DeliveryServiceV5, omitExtraLongDescFields bool, longDesc1, longDesc2 
*string) (*tc.DeliveryServiceV5, int, error, error) {
        tx := inf.Tx.Tx
        user := inf.User
-       if err := Validate(tx, ds); err != nil {
-               return nil, http.StatusBadRequest, fmt.Errorf("invalid request: 
%w", err), nil
+       userErr, sysErr := Validate(tx, ds)
+       if userErr != nil {
+               return nil, http.StatusBadRequest, fmt.Errorf("invalid request: 
%w", userErr), nil
+       }
+       if sysErr != nil {
+               return nil, http.StatusInternalServerError, nil, sysErr
        }
 
        if authorized, err := isTenantAuthorized(inf, ds); err != nil {
@@ -1014,8 +1030,6 @@ func updateV50(w http.ResponseWriter, r *http.Request, 
inf *api.APIInfo, ds *tc.
        }
 
        var errCode int
-       var userErr error
-       var sysErr error
        var oldDetails TODeliveryServiceOldDetails
        if dsType.HasSSLKeys() {
                oldDetails, userErr, sysErr, errCode = getOldDetails(*ds.ID, tx)
@@ -1057,12 +1071,8 @@ func updateV50(w http.ResponseWriter, r *http.Request, 
inf *api.APIInfo, ds *tc.
        }
 
        if ds.Topology != nil {
-               requiredCapabilities, err := 
dbhelpers.GetDSRequiredCapabilitiesFromID(*ds.ID, tx)
-               if err != nil {
-                       return nil, http.StatusInternalServerError, nil, 
fmt.Errorf("getting existing DS required capabilities: %w", err)
-               }
-               if len(requiredCapabilities) > 0 {
-                       if userErr, sysErr, status := 
EnsureTopologyBasedRequiredCapabilities(tx, *ds.ID, *ds.Topology, 
requiredCapabilities); userErr != nil || sysErr != nil {
+               if len(ds.RequiredCapabilities) > 0 {
+                       if userErr, sysErr, status := 
EnsureTopologyBasedRequiredCapabilities(tx, *ds.ID, *ds.Topology, 
ds.RequiredCapabilities); userErr != nil || sysErr != nil {
                                return nil, status, userErr, sysErr
                        }
                }
@@ -1141,6 +1151,7 @@ func updateV50(w http.ResponseWriter, r *http.Request, 
inf *api.APIInfo, ds *tc.
                        ds.LastHeaderRewrite,
                        ds.ServiceCategory,
                        ds.MaxRequestHeaderBytes,
+                       pq.Array(ds.RequiredCapabilities),
                        ds.ID,
                )
        } else {
@@ -1205,6 +1216,7 @@ func updateV50(w http.ResponseWriter, r *http.Request, 
inf *api.APIInfo, ds *tc.
                        ds.LastHeaderRewrite,
                        ds.ServiceCategory,
                        ds.MaxRequestHeaderBytes,
+                       pq.Array(ds.RequiredCapabilities),
                        ds.ID,
                )
        }
@@ -1530,9 +1542,7 @@ var validTLSVersionPattern = 
regexp.MustCompile(`^\d+\.\d+$`)
 // providing default values to some properties when they are zero-valued or nil
 // references*. This will panic if either argument is nil. The error returned 
is
 // safe for clients to see.
-//
-// TODO: return system errors as well.
-func Validate(tx *sql.Tx, ds *tc.DeliveryServiceV5) error {
+func Validate(tx *sql.Tx, ds *tc.DeliveryServiceV5) (error, error) {
        sanitize(ds)
        neverOrAlways := 
validation.NewStringRule(tovalidate.IsOneOfStringICase("NEVER", "ALWAYS"),
                "must be one of 'NEVER' or 'ALWAYS'")
@@ -1584,10 +1594,48 @@ func Validate(tx *sql.Tx, ds *tc.DeliveryServiceV5) 
error {
        if err := validateTypeFields(tx, ds); err != nil {
                errs = append(errs, fmt.Errorf("type fields: %w", err))
        }
+       userErr, sysErr := validateRequiredCapabilities(tx, ds)
+       if sysErr != nil {
+               return nil, fmt.Errorf("reading/ scanning required 
capabilities: %w", sysErr)
+       }
+       if userErr != nil {
+               errs = append(errs, errors.New("required capabilities: 
"+userErr.Error()))
+       }
        if len(errs) == 0 {
-               return nil
+               return nil, nil
+       }
+       return util.JoinErrs(errs), nil
+}
+
+func validateRequiredCapabilities(tx *sql.Tx, ds *tc.DeliveryServiceV5) 
(error, error) {
+       missing := make([]string, 0)
+       var missingCap string
+       query := `SELECT missing
+FROM (
+    SELECT UNNEST($1::TEXT[])
+    EXCEPT
+    SELECT UNNEST(ARRAY_AGG(name)) FROM server_capability) t(missing)
+`
+       if len(ds.RequiredCapabilities) > 0 {
+               rows, err := tx.Query(query, pq.Array(ds.RequiredCapabilities))
+               if err != nil {
+                       return nil, err
+               }
+               defer rows.Close()
+               for rows.Next() {
+                       err = rows.Scan(&missingCap)
+                       if err != nil {
+                               return nil, err
+                       }
+                       missing = append(missing, missingCap)
+               }
+               if len(missing) > 0 {
+                       msg := strings.Join(missing, ",")
+                       userErr := fmt.Errorf("the following capabilities do 
not exist: %s", msg)
+                       return userErr, nil
+               }
        }
-       return util.JoinErrs(errs)
+       return nil, nil
 }
 
 func validateGeoLimitCountries(ds *tc.DeliveryServiceV5) error {
@@ -1947,6 +1995,7 @@ func GetDeliveryServices(query string, queryValues 
map[string]interface{}, tx *s
                        &ds.Regional,
                        &ds.RegionalGeoBlocking,
                        &ds.RemapText,
+                       pq.Array(&ds.RequiredCapabilities),
                        &ds.RoutingName,
                        &ds.ServiceCategory,
                        &ds.SigningAlgorithm,
@@ -2484,6 +2533,7 @@ ds.active,
        ds.regional,
        ds.regional_geo_blocking,
        ds.remap_text,
+       COALESCE(ds.required_capabilities, '{}'),
        ds.routing_name,
        ds.service_category,
        ds.signing_algorithm,
@@ -2569,8 +2619,9 @@ first_header_rewrite=$56,
 inner_header_rewrite=$57,
 last_header_rewrite=$58,
 service_category=$59,
-max_request_header_bytes=$60
-WHERE id=$61
+max_request_header_bytes=$60,
+required_capabilities=$61
+WHERE id=$62
 RETURNING last_updated
 `
 }
@@ -2636,8 +2687,9 @@ first_header_rewrite=$54,
 inner_header_rewrite=$55,
 last_header_rewrite=$56,
 service_category=$57,
-max_request_header_bytes=$58
-WHERE id=$59
+max_request_header_bytes=$58,
+required_capabilities=$59
+WHERE id=$60
 RETURNING last_updated
 `
 }
@@ -2704,9 +2756,10 @@ first_header_rewrite,
 inner_header_rewrite,
 last_header_rewrite,
 service_category,
-max_request_header_bytes
+max_request_header_bytes,
+required_capabilities
 )
-VALUES 
($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$50,$51,$52,$53,$54,$55,$56,$57,$58,$59,$60)
+VALUES 
($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$50,$51,$52,$53,$54,$55,$56,$57,$58,$59,$60,$61)
 RETURNING id, last_updated
 `
 }
@@ -2771,9 +2824,10 @@ first_header_rewrite,
 inner_header_rewrite,
 last_header_rewrite,
 service_category,
-max_request_header_bytes
+max_request_header_bytes,
+required_capabilities
 )
-VALUES 
($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$50,$51,$52,$53,$54,$55,$56,$57,$58)
+VALUES 
($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15,$16,$17,$18,$19,$20,$21,$22,$23,$24,$25,$26,$27,$28,$29,$30,$31,$32,$33,$34,$35,$36,$37,$38,$39,$40,$41,$42,$43,$44,$45,$46,$47,$48,$49,$50,$51,$52,$53,$54,$55,$56,$57,$58,$59)
 RETURNING id, last_updated
 `
 }
diff --git 
a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_required_capabilities.go
 
b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_required_capabilities.go
index b6b75b3dd8..aac113ea1a 100644
--- 
a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_required_capabilities.go
+++ 
b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_required_capabilities.go
@@ -65,36 +65,31 @@ func (rc *RequiredCapability) NewReadObj() interface{} {
 // SelectQuery implements the api.GenericReader interface.
 func (rc *RequiredCapability) SelectQuery() string {
        return `SELECT
-       rc.required_capability,
-       rc.deliveryservice_id,
+       UNNEST(ds.required_capabilities) as required_capability,
+       ds.id as deliveryservice_id,
        ds.xml_id,
-       rc.last_updated
-       FROM deliveryservices_required_capability rc
-       JOIN deliveryservice ds ON ds.id = rc.deliveryservice_id`
+       ds.last_updated
+       FROM deliveryservice ds`
 }
 
 // ParamColumns implements the api.GenericReader interface.
 func (rc *RequiredCapability) ParamColumns() 
map[string]dbhelpers.WhereColumnInfo {
        return map[string]dbhelpers.WhereColumnInfo{
                deliveryServiceQueryParam: dbhelpers.WhereColumnInfo{
-                       Column:  "rc.deliveryservice_id",
+                       Column:  "ds.id",
                        Checker: api.IsInt,
                },
                xmlIDQueryParam: dbhelpers.WhereColumnInfo{
                        Column:  "ds.xml_id",
                        Checker: nil,
                },
-               requiredCapabilityQueryParam: dbhelpers.WhereColumnInfo{
-                       Column:  "rc.required_capability",
-                       Checker: nil,
-               },
        }
 }
 
 // DeleteQuery implements the api.GenericDeleter interface.
 func (rc *RequiredCapability) DeleteQuery() string {
-       return `DELETE FROM deliveryservices_required_capability
-       WHERE deliveryservice_id = :deliveryservice_id AND required_capability 
= :required_capability`
+       return `UPDATE deliveryservice ds SET required_capabilities = 
ARRAY_REMOVE((select required_capabilities from deliveryservice WHERE 
id=:deliveryservice_id), :required_capability)
+WHERE id=:deliveryservice_id AND EXISTS(SELECT 1 FROM deliveryservice ds WHERE 
id=:deliveryservice_id AND :required_capability = 
ANY(ds.required_capabilities))`
 }
 
 // GetKeyFieldsInfo implements the api.Identifier interface.
@@ -209,7 +204,7 @@ func (rc *RequiredCapability) getCapabilities(h 
http.Header, tenantIDs []int, us
 
        where, queryValues = dbhelpers.AddTenancyCheck(where, queryValues, 
"ds.tenant_id", tenantIDs)
        if useIMS {
-               runSecond, maxTime = 
ims.TryIfModifiedSinceQuery(rc.APIInfo().Tx, h, queryValues, 
selectMaxLastUpdatedQueryRC(where, orderBy, pagination))
+               runSecond, maxTime = 
ims.TryIfModifiedSinceQuery(rc.APIInfo().Tx, h, queryValues, 
selectMaxLastUpdatedQuery(where))
                if !runSecond {
                        log.Debugln("IMS HIT")
                        return results, nil, nil, http.StatusNotModified, 
&maxTime
@@ -220,6 +215,10 @@ func (rc *RequiredCapability) getCapabilities(h 
http.Header, tenantIDs []int, us
        }
        query := rc.SelectQuery() + where + orderBy + pagination
 
+       if reqdCap, ok := rc.APIInfo().Params[requiredCapabilityQueryParam]; ok 
{
+               query = `WITH res AS (` + query + `) SELECT 
res.required_capability, res.deliveryservice_id, res.xml_id, res.last_updated 
FROM res WHERE res.required_capability=:requiredCapability`
+               queryValues[requiredCapabilityQueryParam] = reqdCap
+       }
        rows, err := rc.APIInfo().Tx.NamedQuery(query, queryValues)
        if err != nil {
                return nil, nil, err, http.StatusInternalServerError, nil
@@ -239,10 +238,10 @@ func (rc *RequiredCapability) getCapabilities(h 
http.Header, tenantIDs []int, us
 
 func selectMaxLastUpdatedQueryRC(where string, orderBy string, pagination 
string) string {
        return `SELECT max(t) from (
-               SELECT max(rc.last_updated) as t FROM 
deliveryservices_required_capability rc
-       JOIN deliveryservice ds ON ds.id = rc.deliveryservice_id ` + where + 
orderBy + pagination +
+               SELECT max(d.last_updated) as t FROM deliveryservice d` +
+               where + orderBy + pagination +
                ` UNION ALL
-       select max(last_updated) as t from last_deleted l where 
l.table_name='deliveryservices_required_capability') as res`
+       select max(last_updated) as t from last_deleted l where 
l.table_name='deliveryservice') as res`
 }
 
 // Delete implements the api.CRUDer interface.
@@ -292,7 +291,7 @@ func (rc *RequiredCapability) Create() (error, error, int) {
        }
 
        if !dsType.IsHTTP() && !dsType.IsDNS() {
-               return errors.New("Invalid DS type. Only DNS and HTTP delivery 
services can have required capabilities"), nil, http.StatusBadRequest
+               return errors.New("invalid DS type. Only DNS and HTTP delivery 
services can have required capabilities"), nil, http.StatusBadRequest
        }
 
        usrErr, sysErr, rCode := rc.checkServerCap()
@@ -300,6 +299,11 @@ func (rc *RequiredCapability) Create() (error, error, int) 
{
                return usrErr, sysErr, rCode
        }
 
+       for _, reqCap := range reqCaps {
+               if reqCap == *rc.RequiredCapability {
+                       return fmt.Errorf("capability %s already exists for 
delivery service with ID: %d", *rc.RequiredCapability, *rc.DeliveryServiceID), 
nil, http.StatusBadRequest
+               }
+       }
        if topology == nil {
                usrErr, sysErr, rCode = rc.ensureDSServerCap()
                if usrErr != nil || sysErr != nil {
@@ -325,7 +329,7 @@ func (rc *RequiredCapability) Create() (error, error, int) {
        rowsAffected := 0
        for rows.Next() {
                rowsAffected++
-               if err := rows.StructScan(&rc); err != nil {
+               if err := rows.Scan(&rc.DeliveryServiceID, &rc.LastUpdated); 
err != nil {
                        return nil, fmt.Errorf("%s create scanning: %s", 
rc.GetType(), err.Error()), http.StatusInternalServerError
                }
        }
@@ -518,18 +522,12 @@ func (rc *RequiredCapability) isTenantAuthorized() (bool, 
error) {
 }
 
 func rcInsertQuery() string {
-       return `INSERT INTO deliveryservices_required_capability (
-required_capability,
-deliveryservice_id) VALUES (
-:required_capability,
-:deliveryservice_id) RETURNING deliveryservice_id, required_capability, 
last_updated`
+       return `UPDATE deliveryservice ds SET required_capabilities = 
array_append((select required_capabilities from deliveryservice WHERE 
id=:deliveryservice_id), :required_capability)
+WHERE id=:deliveryservice_id RETURNING ds.id, ds.last_updated`
 }
 
-// language=SQL
-const HasRequiredCapabilitiesQuery = `
-SELECT EXISTS(
-       SELECT drc.required_capability
-       FROM deliveryservices_required_capability drc
-       WHERE drc.deliveryservice_id = $1
-)
+const GetRequiredCapabilitiesQuery = `
+SELECT ds.required_capabilities
+FROM deliveryservice ds
+WHERE ds.id = $1
 `
diff --git 
a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_required_capabilities_test.go
 
b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_required_capabilities_test.go
index af60e2075f..5c480ea690 100644
--- 
a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_required_capabilities_test.go
+++ 
b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_required_capabilities_test.go
@@ -92,12 +92,11 @@ func TestCreateDeliveryServicesRequiredCapability(t 
*testing.T) {
        arrayRows := sqlmock.NewRows([]string{"array"})
        mock.ExpectQuery("SELECT ds.server FROM 
deliveryservice_server").WillReturnRows(arrayRows)
 
-       rows := sqlmock.NewRows([]string{"required_capability", 
"deliveryservice_id", "last_updated"}).AddRow(
-               util.StrPtr("mem"),
+       rows := sqlmock.NewRows([]string{"deliveryservice_id", 
"last_updated"}).AddRow(
                util.IntPtr(1),
                time.Now(),
        )
-       mock.ExpectQuery("INSERT INTO 
deliveryservices_required_capability").WillReturnRows(rows)
+       mock.ExpectQuery("UPDATE deliveryservice").WillReturnRows(rows)
 
        userErr, sysErr, errCode := rc.Create()
        if userErr != nil {
@@ -197,7 +196,7 @@ func TestReadDeliveryServicesRequiredCapability(t 
*testing.T) {
                capability.XMLID,
                time.Now(),
        )
-       mock.ExpectQuery("SELECT .* FROM 
deliveryservices_required_capability").WillReturnRows(rows)
+       mock.ExpectQuery("SELECT .* FROM deliveryservice").WillReturnRows(rows)
 
        results, userErr, sysErr, errCode, _ := rc.Read(nil, false)
        if userErr != nil {
@@ -248,7 +247,7 @@ func TestDeleteDeliveryServicesRequiredCapability(t 
*testing.T) {
 
        
mock.ExpectQuery("SELECT").WillReturnRows(sqlmock.NewRows([]string{"xml_id", 
"name"}).AddRow("name", "cdnName"))
        mock.ExpectQuery("SELECT 
c.username").WillReturnRows(sqlmock.NewRows(nil))
-       mock.ExpectExec("DELETE").WillReturnResult(sqlmock.NewResult(1, 1))
+       mock.ExpectExec("UPDATE").WillReturnResult(sqlmock.NewResult(1, 1))
 
        rc := RequiredCapability{
                api.APIInfoImpl{
diff --git 
a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_test.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_test.go
index 01e9fd1fe2..b397c1c53d 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_test.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/deliveryservices_test.go
@@ -321,6 +321,7 @@ func TestReadGetDeliveryServices(t *testing.T) {
                {"regional", false},
                {"regional_geo_blocking", false},
                {"remap_text", nil},
+               {"required_capabilities", nil},
                {"routing_name", "video"},
                {"service_category", nil},
                {"signing_algorithm", nil},
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/eligible.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/eligible.go
index d863aa423b..691a1ae7ba 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/eligible.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/eligible.go
@@ -147,7 +147,7 @@ t.name as server_type,
 s.type as server_type_id,
 s.config_update_time > s.config_apply_time AS upd_pending,
 ARRAY(select ssc.server_capability from server_server_capability ssc where 
ssc.server = s.id order by ssc.server_capability) as server_capabilities,
-ARRAY(select drc.required_capability from deliveryservices_required_capability 
drc where drc.deliveryservice_id = (select v from ds_id) order by 
drc.required_capability) as deliveryservice_capabilities
+(SELECT ds.required_capabilities FROM deliveryservice ds WHERE ds.id = $2) AS 
deliveryservice_capabilities
 `
        idRows, err := tx.Query(fmt.Sprintf(queryFormatString, "", 
queryWhereClause), dsID)
        if err != nil {
@@ -168,7 +168,7 @@ ARRAY(select drc.required_capability from 
deliveryservices_required_capability d
                return nil, errors.New("unable to get server interfaces: " + 
err.Error())
        }
 
-       rows, err := tx.Query(fmt.Sprintf(queryFormatString, dataFetchQuery, 
queryWhereClause), dsID)
+       rows, err := tx.Query(fmt.Sprintf(queryFormatString, dataFetchQuery, 
queryWhereClause), dsID, dsID)
        if err != nil {
                return nil, errors.New("querying delivery service eligible 
servers: " + err.Error())
        }
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/request/assign.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/request/assign.go
index d34d138c72..69a56dbcc5 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/request/assign.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/request/assign.go
@@ -225,9 +225,13 @@ func PutAssignment(w http.ResponseWriter, r *http.Request) 
{
        if inf.Version.Major >= 5 {
                resp = dsr
        } else if inf.Version.Major >= 4 {
-               resp = dsr.Downgrade()
+               if inf.Version.Major >= 1 {
+                       resp = dsr.Downgrade()
+               } else {
+                       resp = dsr.Downgrade().Downgrade()
+               }
        } else {
-               resp = dsr.Downgrade().Downgrade()
+               resp = dsr.Downgrade().Downgrade().Downgrade()
        }
 
        api.WriteRespAlertObj(w, r, tc.SuccessLevel, message, resp)
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/request/requests.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/request/requests.go
index a86d9f35f7..533625aab9 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/request/requests.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/request/requests.go
@@ -286,9 +286,17 @@ func Get(w http.ResponseWriter, r *http.Request) {
                        api.WriteResp(w, r, dsrs)
                        return
                }
+               if version.Minor >= 1 {
+                       downgraded := make([]tc.DeliveryServiceRequestV4, 0, 
len(dsrs))
+                       for _, dsr := range dsrs {
+                               downgraded = append(downgraded, dsr.Downgrade())
+                       }
+                       api.WriteResp(w, r, downgraded)
+                       return
+               }
                downgraded := make([]tc.DeliveryServiceRequestV40, 0, len(dsrs))
                for _, dsr := range dsrs {
-                       downgraded = append(downgraded, dsr.Downgrade())
+                       downgraded = append(downgraded, 
dsr.Downgrade().Downgrade())
                }
                api.WriteResp(w, r, downgraded)
                return
@@ -296,7 +304,7 @@ func Get(w http.ResponseWriter, r *http.Request) {
 
        downgraded := make([]tc.DeliveryServiceRequestNullable, 0, len(dsrs))
        for _, dsr := range dsrs {
-               downgraded = append(downgraded, dsr.Downgrade().Downgrade())
+               downgraded = append(downgraded, 
dsr.Downgrade().Downgrade().Downgrade())
        }
 
        api.WriteResp(w, r, downgraded)
@@ -501,6 +509,7 @@ func createV4(w http.ResponseWriter, r *http.Request, inf 
*api.APIInfo) (result
        upgraded.SetXMLID()
        if ok, err = dbhelpers.DSRExistsWithXMLID(upgraded.XMLID, tx); err != 
nil {
                err = fmt.Errorf("checking for existence of DSR with xmlid 
'%s'", upgraded.XMLID)
+
                api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, 
err)
                return
        } else if ok {
@@ -528,7 +537,11 @@ func createV4(w http.ResponseWriter, r *http.Request, inf 
*api.APIInfo) (result
 
        w.Header().Set("Location", 
fmt.Sprintf("/api/%d.%d/deliveryservice_requests/%d", inf.Version.Major, 
inf.Version.Minor, *dsr.ID))
        w.WriteHeader(http.StatusCreated)
-       api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Delivery Service request 
created", dsr)
+       if inf.Version.Minor >= 1 {
+               api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Delivery Service 
request created", dsr)
+       } else {
+               api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Delivery Service 
request created", dsr.Downgrade())
+       }
 
        result.Successful = true
        result.Assignee = dsr.Assignee
@@ -546,12 +559,17 @@ func createLegacy(w http.ResponseWriter, r *http.Request, 
inf *api.APIInfo) (res
                api.HandleErr(w, r, tx, http.StatusBadRequest, userErr, nil)
                return
        }
-       if err := validateLegacy(dsr, tx); err != nil {
-               api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+       userErr, sysErr := validateLegacy(dsr, tx)
+       if sysErr != nil {
+               api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, 
sysErr)
+               return
+       }
+       if userErr != nil {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, userErr, nil)
                return
        }
 
-       upgraded := dsr.Upgrade().Upgrade()
+       upgraded := dsr.Upgrade().Upgrade().Upgrade()
        authorized, err := isTenantAuthorized(upgraded, inf)
        if err != nil {
                sysErr := fmt.Errorf("checking tenant authorized: %w", err)
@@ -848,12 +866,23 @@ func putV50(w http.ResponseWriter, r *http.Request, inf 
*api.APIInfo) (result ds
        return
 }
 
-func putV40(w http.ResponseWriter, r *http.Request, inf *api.APIInfo) (result 
dsrManipulationResult) {
+func putV4(w http.ResponseWriter, r *http.Request, inf *api.APIInfo) (result 
dsrManipulationResult) {
        tx := inf.Tx.Tx
-       var dsr tc.DeliveryServiceRequestV40
-       if err := json.NewDecoder(r.Body).Decode(&dsr); err != nil {
-               api.HandleErr(w, r, tx, http.StatusBadRequest, 
fmt.Errorf("decoding: %w", err), nil)
-               return
+       var dsr tc.DeliveryServiceRequestV4
+       var dsrV40 tc.DeliveryServiceRequestV40
+
+       if inf.Version.Minor == 0 {
+               if err := json.NewDecoder(r.Body).Decode(&dsrV40); err != nil {
+                       api.HandleErr(w, r, tx, http.StatusBadRequest, 
fmt.Errorf("decoding: %v", err), nil)
+                       return
+               }
+               dsr = dsrV40.Upgrade()
+       } else {
+               if err := json.NewDecoder(r.Body).Decode(&dsr); err != nil {
+                       api.HandleErr(w, r, tx, http.StatusBadRequest, 
fmt.Errorf("decoding: %v", err), nil)
+                       return
+               }
+               dsrV40 = dsr.Downgrade()
        }
        if userErr, sysErr := validateV4(dsr, tx); userErr != nil || sysErr != 
nil {
                api.HandleErr(w, r, tx, http.StatusBadRequest, userErr, sysErr)
@@ -928,8 +957,8 @@ func putV40(w http.ResponseWriter, r *http.Request, inf 
*api.APIInfo) (result ds
                api.HandleErr(w, r, tx, errCode, userErr, sysErr)
                return
        }
-       dsr = upgraded.Downgrade()
-       dsr.SetXMLID()
+       upgraded.SetXMLID()
+       dsr.XMLID = upgraded.XMLID
 
        if dsr.ChangeType == tc.DSRChangeTypeUpdate {
                query := deliveryservice.SelectDeliveryServicesQuery + `WHERE 
xml_id=:XMLID`
@@ -973,8 +1002,13 @@ func putLegacy(w http.ResponseWriter, r *http.Request, 
inf *api.APIInfo) (result
                api.HandleErr(w, r, tx, http.StatusBadRequest, userErr, nil)
                return
        }
-       if err := validateLegacy(dsr, tx); err != nil {
-               api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil)
+       userErr, sysErr := validateLegacy(dsr, tx)
+       if sysErr != nil {
+               api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, 
sysErr)
+               return
+       }
+       if userErr != nil {
+               api.HandleErr(w, r, tx, http.StatusBadRequest, userErr, nil)
                return
        }
 
@@ -989,7 +1023,7 @@ func putLegacy(w http.ResponseWriter, r *http.Request, inf 
*api.APIInfo) (result
        dsr.LastEditedByID = new(tc.IDNoMod)
        *dsr.LastEditedByID = tc.IDNoMod(inf.User.ID)
 
-       upgraded := dsr.Upgrade().Upgrade()
+       upgraded := dsr.Upgrade().Upgrade().Upgrade()
 
        authorized, err := isTenantAuthorized(upgraded, inf)
        if err != nil {
@@ -1024,6 +1058,7 @@ func putLegacy(w http.ResponseWriter, r *http.Request, 
inf *api.APIInfo) (result
                return
        }
        upgraded.SetXMLID()
+       dsr.XMLID = &upgraded.XMLID
 
        api.WriteRespAlertObj(w, r, tc.SuccessLevel, fmt.Sprintf("Delivery 
Service Request #%d updated", inf.IntParams["id"]), dsr)
        result.Action = api.Updated
@@ -1099,7 +1134,7 @@ func Put(w http.ResponseWriter, r *http.Request) {
        case 5:
                result = putV50(w, r, inf)
        case 4:
-               result = putV40(w, r, inf)
+               result = putV4(w, r, inf)
        case 3:
                result = putLegacy(w, r, inf)
        }
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/request/status.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/request/status.go
index d252a3906d..695f744742 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/request/status.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/request/status.go
@@ -227,9 +227,13 @@ func PutStatus(w http.ResponseWriter, r *http.Request) {
        if inf.Version.Major >= 5 {
                resp = dsr
        } else if inf.Version.Major >= 4 {
-               resp = dsr.Downgrade()
+               if inf.Version.Minor >= 1 {
+                       resp = dsr.Downgrade()
+               } else {
+                       resp = dsr.Downgrade().Downgrade()
+               }
        } else {
-               resp = dsr.Downgrade().Downgrade()
+               resp = dsr.Downgrade().Downgrade().Downgrade()
        }
 
        api.WriteRespAlertObj(w, r, tc.SuccessLevel, message, resp)
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/request/validate.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/request/validate.go
index 47fb24265d..963543d617 100644
--- a/traffic_ops/traffic_ops_golang/deliveryservice/request/validate.go
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/request/validate.go
@@ -35,7 +35,7 @@ import (
 
 // validateLegacy ensures all required fields are present and in correct form.
 // Also checks request JSON is complete and valid.
-func validateLegacy(dsr tc.DeliveryServiceRequestNullable, tx *sql.Tx) error {
+func validateLegacy(dsr tc.DeliveryServiceRequestNullable, tx *sql.Tx) (error, 
error) {
        if tx == nil {
                log.Errorln("validating a legacy Delivery Service Request: nil 
transaction was passed")
        }
@@ -46,7 +46,7 @@ func validateLegacy(dsr tc.DeliveryServiceRequestNullable, tx 
*sql.Tx) error {
 
                if err != nil {
                        log.Errorf("querying for dsr by ID %d: %v", *dsr.ID, 
err)
-                       return errors.New("unknown error")
+                       return errors.New("unknown error"), nil
                }
        }
 
@@ -69,11 +69,13 @@ func validateLegacy(dsr tc.DeliveryServiceRequestNullable, 
tx *sql.Tx) error {
        errs := tovalidate.ToErrors(errMap)
        // ensure the deliveryservice requested is valid
        upgraded := dsr.DeliveryService.UpgradeToV4().Upgrade()
-       e := deliveryservice.Validate(tx, &upgraded)
-
-       errs = append(errs, e)
+       userErr, sysErr := deliveryservice.Validate(tx, &upgraded)
+       if sysErr != nil {
+               return nil, sysErr
+       }
+       errs = append(errs, userErr)
 
-       return util.JoinErrs(errs)
+       return util.JoinErrs(errs), sysErr
 }
 
 // validateV4 validates a DSR, returning - in order - a user-facing error that
@@ -85,6 +87,7 @@ func validateV4(dsr tc.DeliveryServiceRequestV4, tx *sql.Tx) 
(error, error) {
 // validateV4 validates a DSR, returning - in order - a user-facing error that
 // should be shown to the client, and a system error.
 func validateV5(dsr tc.DeliveryServiceRequestV5, tx *sql.Tx) (error, error) {
+       var userErr, sysErr error
        if tx == nil {
                return nil, errors.New("nil transaction")
        }
@@ -125,11 +128,11 @@ func validateV5(dsr tc.DeliveryServiceRequestV5, tx 
*sql.Tx) (error, error) {
                                if ds == nil {
                                        return fmt.Errorf("required for 
changeType='%s'", dsr.ChangeType)
                                }
-                               err := deliveryservice.Validate(tx, ds)
-                               if err == nil {
+                               userErr, sysErr = deliveryservice.Validate(tx, 
ds)
+                               if userErr == nil && sysErr == nil {
                                        dsr.XMLID = ds.XMLID
                                }
-                               return err
+                               return userErr
                        },
                )),
                validation.Field(&dsr.Original, validation.By(
@@ -182,9 +185,5 @@ func validateV5(dsr tc.DeliveryServiceRequestV5, tx 
*sql.Tx) (error, error) {
                        },
                )),
        )
-       if err != nil {
-               return err, nil
-       }
-
-       return err, nil
+       return err, sysErr
 }
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go 
b/traffic_ops/traffic_ops_golang/routing/routes.go
index 87703283d0..ff4e4b0dcb 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -30,6 +30,7 @@ import (
 
        "github.com/apache/trafficcontrol/lib/go-log"
        "github.com/apache/trafficcontrol/lib/go-tc"
+       "github.com/apache/trafficcontrol/lib/go-util"
        "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/about"
        "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/acme"
        "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
@@ -395,11 +396,6 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
                {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodPut, Path: `deliveryservices/{xmlID}/urisignkeys$`, Handler: 
urisigning.SaveDeliveryServiceURIKeysHandler, RequiredPrivLevel: 
auth.PrivLevelAdmin, RequiredPermissions: []string{"DS-SECURITY-KEY:UPDATE"}, 
Authenticated: Authenticated, Middlewares: nil, ID: 4764896931},
                {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodDelete, Path: `deliveryservices/{xmlID}/urisignkeys$`, Handler: 
urisigning.RemoveDeliveryServiceURIKeysHandler, RequiredPrivLevel: 
auth.PrivLevelAdmin, RequiredPermissions: []string{"DS-SECURITY-KEY:DELETE", 
"DS-SECURITY-KEY:READ", "DELIVERY-SERVICE:READ", "DELIVERY-SERVICE:UPDATE"}, 
Authenticated: Authenticated, Middlewares: nil, ID: 42992541731},
 
-               //Delivery Service Required Capabilities: CRUD
-               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodGet, Path: `deliveryservices_required_capabilities/?$`, Handler: 
api.ReadHandler(&deliveryservice.RequiredCapability{}), RequiredPrivLevel: 
auth.PrivLevelReadOnly, RequiredPermissions: []string{"DELIVERY-SERVICE:READ"}, 
Authenticated: Authenticated, Middlewares: nil, ID: 415852222731},
-               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodPost, Path: `deliveryservices_required_capabilities/?$`, Handler: 
api.CreateHandler(&deliveryservice.RequiredCapability{}), RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: 
[]string{"DELIVERY-SERVICE:READ", "DELIVERY-SERVICE:UPDATE"}, Authenticated: 
Authenticated, Middlewares: nil, ID: 409687399231},
-               {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodDelete, Path: `deliveryservices_required_capabilities/?$`, Handler: 
api.DeleteHandler(&deliveryservice.RequiredCapability{}), RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: 
[]string{"DELIVERY-SERVICE:READ", "DELIVERY-SERVICE:UPDATE"}, Authenticated: 
Authenticated, Middlewares: nil, ID: 449628930431},
-
                // Federations by CDN (the actual table for federation)
                {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodGet, Path: `cdns/{name}/federations/?$`, Handler: 
api.ReadHandler(&cdnfederation.TOCDNFederation{}), RequiredPrivLevel: 
auth.PrivLevelReadOnly, RequiredPermissions: []string{"CDN:READ", 
"FEDERATION:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, 
Middlewares: nil, ID: 48922503231},
                {Version: api.Version{Major: 5, Minor: 0}, Method: 
http.MethodPost, Path: `cdns/{name}/federations/?$`, Handler: 
api.CreateHandler(&cdnfederation.TOCDNFederation{}), RequiredPrivLevel: 
auth.PrivLevelAdmin, RequiredPermissions: []string{"FEDERATION:CREATE", 
"FEDERATION:READ, CDN:READ"}, Authenticated: Authenticated, Middlewares: nil, 
ID: 495489421931},
@@ -798,9 +794,9 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
                {Version: api.Version{Major: 4, Minor: 0}, Method: 
http.MethodDelete, Path: `deliveryservices/{xmlID}/urisignkeys$`, Handler: 
urisigning.RemoveDeliveryServiceURIKeysHandler, RequiredPrivLevel: 
auth.PrivLevelAdmin, RequiredPermissions: []string{"DS-SECURITY-KEY:DELETE", 
"DS-SECURITY-KEY:READ", "DELIVERY-SERVICE:READ", "DELIVERY-SERVICE:UPDATE"}, 
Authenticated: Authenticated, Middlewares: nil, ID: 4299254173},
 
                //Delivery Service Required Capabilities: CRUD
-               {Version: api.Version{Major: 4, Minor: 0}, Method: 
http.MethodGet, Path: `deliveryservices_required_capabilities/?$`, Handler: 
api.ReadHandler(&deliveryservice.RequiredCapability{}), RequiredPrivLevel: 
auth.PrivLevelReadOnly, RequiredPermissions: []string{"DELIVERY-SERVICE:READ"}, 
Authenticated: Authenticated, Middlewares: nil, ID: 41585222273},
-               {Version: api.Version{Major: 4, Minor: 0}, Method: 
http.MethodPost, Path: `deliveryservices_required_capabilities/?$`, Handler: 
api.CreateHandler(&deliveryservice.RequiredCapability{}), RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: 
[]string{"DELIVERY-SERVICE:READ", "DELIVERY-SERVICE:UPDATE"}, Authenticated: 
Authenticated, Middlewares: nil, ID: 40968739923},
-               {Version: api.Version{Major: 4, Minor: 0}, Method: 
http.MethodDelete, Path: `deliveryservices_required_capabilities/?$`, Handler: 
api.DeleteHandler(&deliveryservice.RequiredCapability{}), RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: 
[]string{"DELIVERY-SERVICE:READ", "DELIVERY-SERVICE:UPDATE"}, Authenticated: 
Authenticated, Middlewares: nil, ID: 44962893043},
+               {Version: api.Version{Major: 4, Minor: 0}, Method: 
http.MethodGet, Path: `deliveryservices_required_capabilities/?$`, Handler: 
api.DeprecatedReadHandler(&deliveryservice.RequiredCapability{}, 
util.StrPtr("the deliveryservices endpoint")), RequiredPrivLevel: 
auth.PrivLevelReadOnly, RequiredPermissions: []string{"DELIVERY-SERVICE:READ"}, 
Authenticated: Authenticated, Middlewares: nil, ID: 41585222273},
+               {Version: api.Version{Major: 4, Minor: 0}, Method: 
http.MethodPost, Path: `deliveryservices_required_capabilities/?$`, Handler: 
api.DeprecatedCreateHandler(&deliveryservice.RequiredCapability{}, 
util.StrPtr("the deliveryservices endpoint")), RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: 
[]string{"DELIVERY-SERVICE:READ", "DELIVERY-SERVICE:UPDATE"}, Authenticated: 
Authenticated, Middlewares: nil, ID: 40968739923},
+               {Version: api.Version{Major: 4, Minor: 0}, Method: 
http.MethodDelete, Path: `deliveryservices_required_capabilities/?$`, Handler: 
api.DeprecatedDeleteHandler(&deliveryservice.RequiredCapability{}, 
util.StrPtr("the deliveryservices endpoint")), RequiredPrivLevel: 
auth.PrivLevelOperations, RequiredPermissions: 
[]string{"DELIVERY-SERVICE:READ", "DELIVERY-SERVICE:UPDATE"}, Authenticated: 
Authenticated, Middlewares: nil, ID: 44962893043},
 
                // Federations by CDN (the actual table for federation)
                {Version: api.Version{Major: 4, Minor: 0}, Method: 
http.MethodGet, Path: `cdns/{name}/federations/?$`, Handler: 
api.ReadHandler(&cdnfederation.TOCDNFederation{}), RequiredPrivLevel: 
auth.PrivLevelReadOnly, RequiredPermissions: []string{"CDN:READ", 
"FEDERATION:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, 
Middlewares: nil, ID: 4892250323},
diff --git a/traffic_ops/traffic_ops_golang/server/servers.go 
b/traffic_ops/traffic_ops_golang/server/servers.go
index e576aeb7e1..a2d2461ce2 100644
--- a/traffic_ops/traffic_ops_golang/server/servers.go
+++ b/traffic_ops/traffic_ops_golang/server/servers.go
@@ -104,9 +104,9 @@ AND (
        FROM server_server_capability ssc
        WHERE ssc."server" = s.id
 ) @> (
-       SELECT ARRAY_AGG(drc.required_capability)
-       FROM deliveryservices_required_capability drc
-       WHERE drc.deliveryservice_id = d.id
+       SELECT d.required_capabilities
+       FROM deliveryservice d
+       WHERE d.id = :dsId
 )
 `
 
@@ -803,6 +803,7 @@ func getServers(h http.Header, params map[string]string, tx 
*sqlx.Tx, user *auth
        usesMids := false
        queryAddition := ""
        dsHasRequiredCapabilities := false
+       var requiredCapabilities []string
        var dsID int
        var cdnID int
        var serverCount uint64
@@ -825,10 +826,13 @@ func getServers(h http.Header, params map[string]string, 
tx *sqlx.Tx, user *auth
                }
 
                var joinSubQuery string
-               if err = 
tx.QueryRow(deliveryservice.HasRequiredCapabilitiesQuery, 
dsID).Scan(&dsHasRequiredCapabilities); err != nil {
-                       err = fmt.Errorf("unable to get required capabilities 
for deliveryservice %d: %s", dsID, err)
+               if err := 
tx.QueryRow(deliveryservice.GetRequiredCapabilitiesQuery, 
dsID).Scan(pq.Array(&requiredCapabilities)); err != nil && err != sql.ErrNoRows 
{
+                       err = fmt.Errorf("unable to get required capabilities 
for deliveryservice %d: %w", dsID, err)
                        return nil, 0, nil, err, 
http.StatusInternalServerError, nil
                }
+               if requiredCapabilities != nil && len(requiredCapabilities) > 0 
{
+                       dsHasRequiredCapabilities = true
+               }
                joinSubQuery = dssTopologiesJoinSubquery
                // only if dsId is part of params: add join on 
deliveryservice_server table
                queryAddition = fmt.Sprintf(deliveryServiceServersJoin, 
joinSubQuery)
@@ -1111,9 +1115,9 @@ func getMidServers(edgeIDs []int, servers 
map[int]tc.ServerV40, dsID int, cdnID
                capabilities.array_agg
                @>
                (
-               SELECT ARRAY_AGG(drc.required_capability)
-               FROM deliveryservices_required_capability drc
-               WHERE drc.deliveryservice_id=:ds_id)
+               SELECT ds.required_capabilities
+               FROM deliveryservice ds
+               WHERE ds.id=:ds_id)
                )`
        } else {
                // TODO: include secondary parent?
diff --git a/traffic_ops/traffic_ops_golang/server/servers_server_capability.go 
b/traffic_ops/traffic_ops_golang/server/servers_server_capability.go
index ed52f21ad9..b1a5ee56ab 100644
--- a/traffic_ops/traffic_ops_golang/server/servers_server_capability.go
+++ b/traffic_ops/traffic_ops_golang/server/servers_server_capability.go
@@ -363,13 +363,13 @@ server) VALUES (
 func checkDSReqCapQuery() string {
        return `
 SELECT ARRAY(
-       SELECT dsrc.deliveryservice_id
-       FROM deliveryservices_required_capability as dsrc
-       WHERE deliveryservice_id IN (
+       SELECT ds.id
+       FROM deliveryservice as ds
+       WHERE id IN (
                SELECT deliveryservice
                FROM deliveryservice_server
                WHERE server = $1)
-       AND dsrc.required_capability = $2)`
+       AND $2 = ANY(ds.required_capabilities))`
 }
 
 // get the topology-based DSes (with all their required capabilities) that a 
given
@@ -380,15 +380,14 @@ SELECT
   ds.xml_id,
   ds.topology,
   ds.tenant_id,
-  ARRAY_AGG(dsrc.required_capability) AS req_caps
+  ds.required_capabilities AS req_caps
 FROM server s
 JOIN cachegroup c ON s.cachegroup = c.id
 JOIN topology_cachegroup tc ON c.name = tc.cachegroup
 JOIN deliveryservice ds ON ds.topology = tc.topology
-JOIN deliveryservices_required_capability dsrc ON dsrc.deliveryservice_id = 
ds.id
 WHERE s.id = $1
-GROUP BY ds.xml_id, ds.tenant_id, ds.topology
-HAVING $2 = ANY(ARRAY_AGG(dsrc.required_capability))
+GROUP BY ds.xml_id, ds.tenant_id, ds.topology, ds.required_capabilities
+HAVING $2 = ANY(ds.required_capabilities)
 `
 }
 
diff --git a/traffic_ops/traffic_ops_golang/topology/topologies.go 
b/traffic_ops/traffic_ops_golang/topology/topologies.go
index 988913cc9d..9bbdf533be 100644
--- a/traffic_ops/traffic_ops_golang/topology/topologies.go
+++ b/traffic_ops/traffic_ops_golang/topology/topologies.go
@@ -340,12 +340,11 @@ func getDSRequiredCapabilitiesByTopology(name string, tx 
*sql.Tx) (map[string][]
 SELECT
   d.xml_id,
   d.cdn_id,
-  ARRAY_AGG(drc.required_capability) AS required_capabilities
+  d.required_capabilities
 FROM deliveryservice d
-JOIN deliveryservices_required_capability drc ON d.id = drc.deliveryservice_id
 WHERE
   d.topology = $1
-GROUP BY d.xml_id, d.cdn_id
+GROUP BY d.xml_id, d.cdn_id, d.required_capabilities
 `
        rows, err := tx.Query(q, name)
        if err != nil {
diff --git a/traffic_portal/test/integration/Data/deliveryservices.ts 
b/traffic_portal/test/integration/Data/deliveryservices.ts
index 06bc4da2f6..4514acb7ed 100644
--- a/traffic_portal/test/integration/Data/deliveryservices.ts
+++ b/traffic_portal/test/integration/Data/deliveryservices.ts
@@ -361,7 +361,7 @@ export const deliveryservices = {
                                {
                                        rcName: "DSTestCap",
                                        xmlID: "tpdservice2",
-                                       validationMessage: 
"deliveryservice.RequiredCapability was created."
+                                       validationMessage: "This endpoint is 
deprecated, please use the deliveryservices endpoint instead"
                                }
                        ],
                        remove: [
@@ -469,7 +469,7 @@ export const deliveryservices = {
                                {
                                        rcName: "DSTestCap",
                                        xmlID: "optpdservice2",
-                                       validationMessage: 
"deliveryservice.RequiredCapability was created."
+                                       validationMessage: "This endpoint is 
deprecated, please use the deliveryservices endpoint instead"
                                }
                        ],
                        remove: [

Reply via email to