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: [