This is an automated email from the ASF dual-hosted git repository.
rshah pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git
The following commit(s) were added to refs/heads/master by this push:
new de900da884 `cdns/{{name}}/federations` and
`cdns/{{name}}/federations/{{ID}}` use RFC3339 timestamps (#7806)
de900da884 is described below
commit de900da884f457ebd0039f7874e5c6338c5d10e0
Author: ocket8888 <[email protected]>
AuthorDate: Wed Sep 20 10:13:22 2023 -0600
`cdns/{{name}}/federations` and `cdns/{{name}}/federations/{{ID}}` use
RFC3339 timestamps (#7806)
* Fix 304 Not Modified responses written with body instead of LastModified
header
* add V5 structures
* Update GET handlers to serve v5 structures to v5-requesting clients
* Update POST handler to support v5
* Update PUT handler to support v5
* Update DELETE handler to support v5
* Fix inaccuracies in old API version docs for cdnfederations
* Fix RFC roles not using grave accents
* Update client and client tests
* Update CHANGELOG
* Update TOTest
* Fix dependency_license pattern
* Update enroller
* Fix comparison using pointers instead of values in tests
* Fix improperly cased query string parameter
* fix the selectMaxLastUpdatedQuery - and actually use it for that purpose
* Implement IUS/ETag
* Revert embedding queries from files
* Add TTL minimum
* Fix testing data to fit new restriction
---
.dependency_license | 1 +
CHANGELOG.md | 1 +
docs/source/api/index.rst | 2 +-
docs/source/api/v3/cdns_name_federations.rst | 14 +-
docs/source/api/v4/cdns_name_federations.rst | 14 +-
docs/source/api/v4/deliveryservices.rst | 4 +-
docs/source/api/v4/deliveryservices_id.rst | 2 +-
docs/source/api/v4/deliveryservices_id_safe.rst | 2 +-
docs/source/api/v4/servers_id_deliveryservices.rst | 2 +-
docs/source/api/v5/cdns_name_federations.rst | 87 +++--
docs/source/api/v5/cdns_name_federations_id.rst | 160 +++++++--
docs/source/api/v5/coordinates.rst | 8 +-
docs/source/api/v5/deliveryservices.rst | 4 +-
docs/source/api/v5/deliveryservices_id.rst | 2 +-
docs/source/api/v5/deliveryservices_id_safe.rst | 2 +-
docs/source/api/v5/servers.rst | 4 +-
docs/source/api/v5/servers_id.rst | 4 +-
docs/source/api/v5/servers_id_deliveryservices.rst | 2 +-
docs/source/api/v5/statuses_id.rst | 2 +-
infrastructure/cdn-in-a-box/enroller/enroller.go | 40 +--
lib/go-tc/federation.go | 34 ++
lib/go-tc/totest/federations.go | 12 +-
lib/go-tc/totest/traffic_control.go | 2 +-
traffic_ops/testing/api/v5/cdn_federations_test.go | 54 ++-
traffic_ops/testing/api/v5/federations_test.go | 7 +-
traffic_ops/testing/api/v5/tc-fixtures.json | 12 +-
traffic_ops/testing/api/v5/traffic_control_test.go | 2 +-
traffic_ops/traffic_ops_golang/api/api.go | 7 +-
traffic_ops/traffic_ops_golang/api/api_test.go | 2 +-
.../cdnfederation/cdnfederations.go | 384 ++++++++++++++++++---
.../cdnfederation/cdnfederations_test.go | 312 +++++++++++++++++
traffic_ops/traffic_ops_golang/routing/routes.go | 9 +-
traffic_ops/traffic_ops_golang/server/servers.go | 7 +-
traffic_ops/traffic_ops_golang/util/ims/ims.go | 7 +-
traffic_ops/v5-client/cdnfederations.go | 16 +-
35 files changed, 985 insertions(+), 239 deletions(-)
diff --git a/.dependency_license b/.dependency_license
index f39518e10a..0adb340f0c 100644
--- a/.dependency_license
+++ b/.dependency_license
@@ -55,6 +55,7 @@ traffic_router/core/src/test/resources/api/.*/steering*,
Apache-2.0
traffic_router/core/src/test/resources/api/.*/federations/all, Apache-2.0
BUILD_NUMBER$, Apache-2.0
\.jks, Apache-2.0 # Java Key Store
+traffic_ops/traffic_ops_golang/.*\.sql, Apache-2.0 # embedded sql queries -
can't have comment blocks due to github.com/jmoiron/sqlx library limitations
# Images, created for this project or used under an Apache license.
^misc/logos/ATC-PNG\.png, Apache-2.0
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 3e3bacca95..e86a2b1ba1 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,7 @@ The format is based on [Keep a
Changelog](http://keepachangelog.com/en/1.0.0/).
## [unreleased]
- [#7665](https://github.com/apache/trafficcontrol/pull/7665) *Automation*
Changes to Ansible role dataset_loader to add ATS 9 support
- [#7802](https://github.com/apache/trafficcontrol/pull/7802) *Traffic Control
Health Client* Fixed ReadMe.md typos and duplicates.
+- [#7806](https://github.com/apache/trafficcontrol/pull/7806) *Traffic Ops*
`cdns/{{name}}/federations` and `cdns/{{name}}/federations/{{ID}}` use RFC3339
timestamps in APIv5
### Added
- [#7672](https://github.com/apache/trafficcontrol/pull/7672) *Traffic Control
Health Client* Added peer monitor flag while using `strategies.yaml`.
- [#7609](https://github.com/apache/trafficcontrol/pull/7609) *Traffic Portal*
Added Scope Query Param to SSO login.
diff --git a/docs/source/api/index.rst b/docs/source/api/index.rst
index 136db1986e..808847a294 100644
--- a/docs/source/api/index.rst
+++ b/docs/source/api/index.rst
@@ -89,7 +89,7 @@ The following, reserved properties of ``summary`` are
guaranteed to always have
Traffic Ops's Custom Date/Time Format
-------------------------------------
-Traffic Ops will often return responses from its API that include dates. As of
the time of this writing, the vast majority of those dates are written in a
non-:rfc:3339 format (this is tracked by :issue:`5911`). This is most commonly
the case in the ``last_updated`` properties of objects returned as JSON-encoded
documents. The format used is :samp:`{YYYY}-{MM}-{DD} {hh}:{mm}:{ss}±{ZZ}`,
where ``YYYY`` is the 4-digit year, ``MM`` is the two-digit (zero padded)
month, ``DD`` is the two-dig [...]
+Traffic Ops will often return responses from its API that include dates. As of
the time of this writing, the vast majority of those dates are written in a
non-:RFC:`3339` format (this is tracked by :issue:`5911`). This is most
commonly the case in the ``last_updated`` properties of objects returned as
JSON-encoded documents. The format used is :samp:`{YYYY}-{MM}-{DD}
{hh}:{mm}:{ss}±{ZZ}`, where ``YYYY`` is the 4-digit year, ``MM`` is the
two-digit (zero padded) month, ``DD`` is the two-d [...]
.. note:: In practice, all Traffic Ops API responses use the UTC timezone
(offset ``+00``), but do note that this custom format is not capable of
representing all timezones.
diff --git a/docs/source/api/v3/cdns_name_federations.rst
b/docs/source/api/v3/cdns_name_federations.rst
index 2167fcb6a0..29cf413b1a 100644
--- a/docs/source/api/v3/cdns_name_federations.rst
+++ b/docs/source/api/v3/cdns_name_federations.rst
@@ -42,7 +42,9 @@ Request Structure
+-----------+---------------------------------------------------------------------------------------------------------------+
| Name | Description
|
+===========+===============================================================================================================+
- | id | Return only the federation that has this id
|
+ | id | Return only the :term:`Federation` that has this ID
|
+
+-----------+---------------------------------------------------------------------------------------------------------------+
+ | cname | Return only those :term:`Federations` that have this
CNAME |
+-----------+---------------------------------------------------------------------------------------------------------------+
| orderby | Choose the ordering of the results - must be the name of
one of the fields of the objects in the ``response`` |
| | array
|
@@ -75,10 +77,7 @@ Response Structure
:id: The integral, unique identifier for the :term:`Delivery Service`
:xmlId: The :term:`Delivery Service`'s uniquely identifying 'xml_id'
-:description: An optionally-present field containing a description of the field
-
- .. note:: This key will only be present if the description was provided
when the federation was created. Refer to the ``POST`` method of this endpoint
to see how federations can be created.
-
+:description: A human-readable description of the :term:`Federation`. This can
be ``null`` as well as an empty string.
:lastUpdated: The date and time at which this federation was last modified, in
:ref:`non-rfc-datetime`
:ttl: Time to Live (TTL) for the ``cname``, in hours
@@ -158,10 +157,7 @@ Response Structure
------------------
:id: The integral, unique identifier of the :term:`Federation`
:cname: The Canonical Name (CNAME) used by the federation
-:description: An optionally-present field containing a description of the field
-
- .. note:: This key will only be present if the description was provided
when the federation was created
-
+:description: The description of the :term:`Federation`
:lastUpdated: The date and time at which this federation was last modified, in
:ref:`non-rfc-datetime`
:ttl: Time to Live (TTL) for the ``cname``, in hours
diff --git a/docs/source/api/v4/cdns_name_federations.rst
b/docs/source/api/v4/cdns_name_federations.rst
index 00ac1e7f8b..5148a69337 100644
--- a/docs/source/api/v4/cdns_name_federations.rst
+++ b/docs/source/api/v4/cdns_name_federations.rst
@@ -43,7 +43,9 @@ Request Structure
+-----------+---------------------------------------------------------------------------------------------------------------+
| Name | Description
|
+===========+===============================================================================================================+
- | id | Return only the federation that has this id
|
+ | id | Return only the :term:`Federation` that has this ID
|
+
+-----------+---------------------------------------------------------------------------------------------------------------+
+ | cname | Return only those :term:`Federations` that have this
CNAME |
+-----------+---------------------------------------------------------------------------------------------------------------+
| orderby | Choose the ordering of the results - must be the name of
one of the fields of the objects in the ``response`` |
| | array
|
@@ -76,10 +78,7 @@ Response Structure
:id: The integral, unique identifier for the :term:`Delivery Service`
:xmlId: The :term:`Delivery Service`'s uniquely identifying 'xml_id'
-:description: An optionally-present field containing a description of the field
-
- .. note:: This key will only be present if the description was provided
when the federation was created. Refer to the ``POST`` method of this endpoint
to see how federations can be created.
-
+:description: A human-readable description of the :term:`Federation`. This can
be ``null`` as well as an empty string.
:lastUpdated: The date and time at which this federation was last modified, in
:ref:`non-rfc-datetime`
:ttl: Time to Live (TTL) for the ``cname``, in hours
@@ -160,10 +159,7 @@ Response Structure
------------------
:id: The intergral, unique identifier of the :term:`Federation`
:cname: The Canonical Name (CNAME) used by the federation
-:description: An optionally-present field containing a description of the field
-
- .. note:: This key will only be present if the description was provided
when the federation was created
-
+:description: The description of the :term:`Federation`
:lastUpdated: The date and time at which this federation was last modified, in
:ref:`non-rfc-datetime`
:ttl: Time to Live (TTL) for the ``cname``, in hours
diff --git a/docs/source/api/v4/deliveryservices.rst
b/docs/source/api/v4/deliveryservices.rst
index 9f98b62155..df87c5d9a5 100644
--- a/docs/source/api/v4/deliveryservices.rst
+++ b/docs/source/api/v4/deliveryservices.rst
@@ -116,7 +116,7 @@ Response Structure
:innerHeaderRewrite: A set of :ref:`ds-inner-header-rw-rules`
:ipv6RoutingEnabled: A boolean that defines the :ref:`ds-ipv6-routing`
setting on this :term:`Delivery Service`
:lastHeaderRewrite: A set of :ref:`ds-last-header-rw-rules`
-:lastUpdated: The date and time at which this :term:`Delivery
Service` was last updated, in :rfc:3339 format
+:lastUpdated: The date and time at which this :term:`Delivery
Service` was last updated, in :RFC:`3339` format
.. versionchanged:: 4.0
Prior to API version 4.0, this property used
:ref:`non-rfc-datetime`.
@@ -473,7 +473,7 @@ Response Structure
:innerHeaderRewrite: A set of :ref:`ds-inner-header-rw-rules`
:ipv6RoutingEnabled: A boolean that defines the :ref:`ds-ipv6-routing`
setting on this :term:`Delivery Service`
:lastHeaderRewrite: A set of :ref:`ds-last-header-rw-rules`
-:lastUpdated: The date and time at which this :term:`Delivery
Service` was last updated, in :rfc:3339 format
+:lastUpdated: The date and time at which this :term:`Delivery
Service` was last updated, in :RFC:`3339` format
.. versionchanged:: 4.0
Prior to API version 4.0, this property used
:ref:`non-rfc-datetime`.
diff --git a/docs/source/api/v4/deliveryservices_id.rst
b/docs/source/api/v4/deliveryservices_id.rst
index 0865427028..a677c7da27 100644
--- a/docs/source/api/v4/deliveryservices_id.rst
+++ b/docs/source/api/v4/deliveryservices_id.rst
@@ -223,7 +223,7 @@ Response Structure
:innerHeaderRewrite: A set of :ref:`ds-inner-header-rw-rules`
:ipv6RoutingEnabled: A boolean that defines the :ref:`ds-ipv6-routing`
setting on this :term:`Delivery Service`
:lastHeaderRewrite: A set of :ref:`ds-last-header-rw-rules`
-:lastUpdated: The date and time at which this :term:`Delivery
Service` was last updated, in :rfc:3339 format
+:lastUpdated: The date and time at which this :term:`Delivery
Service` was last updated, in :RFC:`3339` format
.. versionchanged:: 4.0
Prior to API version 4.0, this property used
:ref:`non-rfc-datetime`.
diff --git a/docs/source/api/v4/deliveryservices_id_safe.rst
b/docs/source/api/v4/deliveryservices_id_safe.rst
index 8bafe33110..bbbc1eea6e 100644
--- a/docs/source/api/v4/deliveryservices_id_safe.rst
+++ b/docs/source/api/v4/deliveryservices_id_safe.rst
@@ -95,7 +95,7 @@ Response Structure
:innerHeaderRewrite: A set of :ref:`ds-inner-header-rw-rules`
:ipv6RoutingEnabled: A boolean that defines the :ref:`ds-ipv6-routing`
setting on this :term:`Delivery Service`
:lastHeaderRewrite: A set of :ref:`ds-last-header-rw-rules`
-:lastUpdated: The date and time at which this :term:`Delivery
Service` was last updated, in :rfc:3339 format
+:lastUpdated: The date and time at which this :term:`Delivery
Service` was last updated, in :RFC:`3339` format
.. versionchanged:: 4.0
Prior to API version 4.0, this property used
:ref:`non-rfc-datetime`.
diff --git a/docs/source/api/v4/servers_id_deliveryservices.rst
b/docs/source/api/v4/servers_id_deliveryservices.rst
index 84515033c4..cc4aac7673 100644
--- a/docs/source/api/v4/servers_id_deliveryservices.rst
+++ b/docs/source/api/v4/servers_id_deliveryservices.rst
@@ -103,7 +103,7 @@ Response Structure
:innerHeaderRewrite: A set of :ref:`ds-inner-header-rw-rules`
:ipv6RoutingEnabled: A boolean that defines the :ref:`ds-ipv6-routing`
setting on this :term:`Delivery Service`
:lastHeaderRewrite: A set of :ref:`ds-last-header-rw-rules`
-:lastUpdated: The date and time at which this :term:`Delivery
Service` was last updated, in :rfc:3339 format
+:lastUpdated: The date and time at which this :term:`Delivery
Service` was last updated, in :RFC:`3339` format
.. versionchanged:: 4.0
Prior to API version 4.0, this property used
:ref:`non-rfc-datetime`.
diff --git a/docs/source/api/v5/cdns_name_federations.rst
b/docs/source/api/v5/cdns_name_federations.rst
index e9188086fa..d5dce307f0 100644
--- a/docs/source/api/v5/cdns_name_federations.rst
+++ b/docs/source/api/v5/cdns_name_federations.rst
@@ -32,18 +32,24 @@ Request Structure
-----------------
.. table:: Request Path Parameters
-
+-----------+---------------------------------------------------------------------------------------------------------------+
- | Name | Description
|
-
+===========+===============================================================================================================+
- | name | The name of the CDN for which federations will be listed
|
-
+-----------+---------------------------------------------------------------------------------------------------------------+
+
+-----------+------------------------------------------------------------------+
+ | Name | Description
|
+
+===========+==================================================================+
+ | name | The name of the CDN for which :term:`Federations` will be
listed |
+
+-----------+------------------------------------------------------------------+
.. table:: Request Query Parameters
+-----------+---------------------------------------------------------------------------------------------------------------+
| Name | Description
|
+===========+===============================================================================================================+
- | id | Return only the federation that has this id
|
+ | id | Return only the :term:`Federation` that has this ID
|
+
+-----------+---------------------------------------------------------------------------------------------------------------+
+ | cname | Return only those :term:`Federations` that have this
CNAME |
+
+-----------+---------------------------------------------------------------------------------------------------------------+
+ | dsID | Return only those :term:`Federations` assigned to a
:term:`Delivery Service` that has this ID |
+
+-----------+---------------------------------------------------------------------------------------------------------------+
+ | xmlID | Return only those :term:`Federations` assigned to a
:term:`Delivery Service` that has this :ref:`ds-xmlid` |
+-----------+---------------------------------------------------------------------------------------------------------------+
| orderby | Choose the ordering of the results - must be the name of
one of the fields of the objects in the ``response`` |
| | array
|
@@ -70,18 +76,22 @@ Request Structure
Response Structure
------------------
-:cname: The Canonical Name (CNAME) used by the federation
-:deliveryService: An object with keys that provide identifying information for
the :term:`Delivery Service` using this federation
+:cname: The :abbr:`CNAME (Canonical Name)` used by the
:term:`Federation`
+:deliveryService: An object with keys that provide identifying information for
the :term:`Delivery Service` using this :term:`Federation`
:id: The integral, unique identifier for the :term:`Delivery Service`
- :xmlId: The :term:`Delivery Service`'s uniquely identifying 'xml_id'
+ :xmlID: The :term:`Delivery Service`'s uniquely identifying
:ref:`ds-xmlid`
+
+ .. versionchanged:: 5.0
+ Prior to version 5, this field was known by the name
``xmlId`` - improperly formatted camelCase.
-:description: An optionally-present field containing a description of the field
+:description: A human-readable description of the :term:`Federation`. This can
be ``null`` as well as an empty string.
+:lastUpdated: The date and time at which this :term:`Federation` was last
modified, in :RFC:`3339` format
- .. note:: This key will only be present if the description was provided
when the federation was created. Refer to the ``POST`` method of this endpoint
to see how federations can be created.
+ .. versionchanged:: 5.0
+ In earlier versions of the API, this field was given in
:ref:`non-rfc-datetime`.
-:lastUpdated: The date and time at which this federation was last modified, in
:ref:`non-rfc-datetime`
-:ttl: Time to Live (TTL) for the ``cname``, in hours
+:ttl: :abbr:`TTL (Time to Live)` for the ``cname``, in hours
.. code-block:: http
:caption: Response Example
@@ -102,19 +112,23 @@ Response Structure
{
"id": 1,
"cname": "test.quest.",
- "ttl": 48,
+ "ttl": 68,
"description": "A test federation",
- "lastUpdated": "2018-12-05 00:05:16+00",
+ "lastUpdated": "2018-12-05T00:05:16Z",
"deliveryService": {
"id": 1,
- "xmlId": "demo1"
+ "xmlID": "demo1"
}
}
]}
``POST``
========
-Creates a new federation.
+Creates a new :term:`Federation`.
+
+.. caution:: Despite the URL of this endpoint, this does `**not**` create a
:term:`Federation` within any particular CDN. A :term:`Federation` is
associated with a CDN purely because any :term:`Delivery Service` to which it
is assigned is scoped to a CDN. Therefore, upon creation a :term:`Federation`
is not associated with any CDN in particular.
+
+.. warning:: There is no restriction on using the special "ALL" CDN for
:term:`Federations` - but this is highly discouraged, because many things treat
that CDN specially and may not work properly if it is used as though it were a
normal CDN.
:Auth. Required: Yes
:Roles Required: "admin"
@@ -125,18 +139,21 @@ Request Structure
-----------------
.. table:: Request Path Parameters
-
+------+----------------------------------------------------------------+
- | Name | Description
|
-
+======+================================================================+
- | name | The name of the CDN for which a new federation will be created
|
-
+------+----------------------------------------------------------------+
+
+------+------------------------------------------------------------------------+
+ | Name | Description
|
+
+======+========================================================================+
+ | name | The name of the CDN for which a new :term:`Federation` will be
created |
+
+------+------------------------------------------------------------------------+
-:cname: The Canonical Name (CNAME) used by the federation
+:cname: The :abbr:`CNAME (Canonical Name)` used by the :term:`Federation`
- .. note:: The CNAME must end with a "``.``"
+ .. tip:: The CNAME must end with a "``.``"
:description: An optional description of the federation
-:ttl: Time to Live (TTL) for the name record used for ``cname``
+:ttl: Time to Live (TTL) for the name record used for ``cname`` -
minimum of 60
+
+ .. versionchanged:: 5.0
+ In earlier API versions, there is no enforced minimum (although
Traffic Portal would never allow a value under 60).
.. code-block:: http
:caption: Request Example
@@ -151,7 +168,7 @@ Request Structure
{
"cname": "test.quest.",
- "ttl": 48,
+ "ttl": 68,
"description": "A test federation"
}
@@ -159,14 +176,14 @@ Request Structure
Response Structure
------------------
:id: The integral, unique identifier of the :term:`Federation`
-:cname: The Canonical Name (CNAME) used by the federation
-:description: An optionally-present field containing a description of the field
-
- .. note:: This key will only be present if the description was provided
when the federation was created
+:cname: The :abbr:`CNAME (Canonical Name)` used by the :term:`Federation`
+:description: The description of the :term:`Federation`
+:lastUpdated: The date and time at which this federation was last modified, in
:RFC:`3339` format
-:lastUpdated: The date and time at which this federation was last modified, in
:ref:`non-rfc-datetime`
-:ttl: Time to Live (TTL) for the ``cname``, in hours
+ .. versionchanged:: 5.0
+ In earlier versions of the API, this field was given in
:ref:`non-rfc-datetime`.
+:ttl: :abbr:`TTL (Time to Live)` for the ``cname``, in hours
.. code-block:: http
:caption: Response Example
@@ -185,14 +202,14 @@ Response Structure
{ "alerts": [
{
- "text": "cdnfederation was created.",
+ "text": "Federation was created",
"level": "success"
}
],
"response": {
"id": 1,
"cname": "test.quest.",
- "ttl": 48,
+ "ttl": 68,
"description": "A test federation",
- "lastUpdated": "2018-12-05 00:05:16+00"
+ "lastUpdated": "2018-12-05T00:05:16Z"
}}
diff --git a/docs/source/api/v5/cdns_name_federations_id.rst
b/docs/source/api/v5/cdns_name_federations_id.rst
index 6fee0b5b72..89545e92d3 100644
--- a/docs/source/api/v5/cdns_name_federations_id.rst
+++ b/docs/source/api/v5/cdns_name_federations_id.rst
@@ -19,9 +19,87 @@
``cdns/{{name}}/federations/{{ID}}``
************************************
+``GET``
+=======
+Retrieves a list of federations in use by a specific CDN.
+
+.. versionadded:: 5.0
+
+:Auth. Required: Yes
+:Roles Required: None
+:Permissions Required: CDN:READ, FEDERATION:READ, DELIVERY-SERVICE:READ
+:Response Type: Object
+
+Request Structure
+-----------------
+.. table:: Request Path Parameters
+
+
+------+---------------------------------------------------------------------------------------------+
+ | Name | Description
|
+
+======+=============================================================================================+
+ | name | The name of the CDN for which the :term:`Federation`
identified by ``ID`` will be inspected |
+
+------+---------------------------------------------------------------------------------------------+
+ | ID | An integral, unique identifier for the :term:`Federation` to
be inspected |
+
+------+---------------------------------------------------------------------------------------------+
+
+.. code-block:: http
+ :caption: Request Example
+
+ GET /api/5.0/cdns/CDN-in-a-Box/federations/1 HTTP/1.1
+ Host: trafficops.infra.ciab.test
+ User-Agent: curl/7.62.0
+ Accept: */*
+ Cookie: mojolicious=...
+
+Response Structure
+------------------
+:cname: The :abbr:`CNAME (Canonical Name)` used by the
:term:`Federation`
+:deliveryService: An object with keys that provide identifying information for
the :term:`Delivery Service` using this :term:`Federation`
+
+ :id: The integral, unique identifier for the :term:`Delivery Service`
+ :xmlID: The :term:`Delivery Service`'s uniquely identifying
:ref:`ds-xmlid`
+
+ .. versionchanged:: 5.0
+ Prior to version 5, this field was known by the name
``xmlId`` - improperly formatted camelCase.
+
+:description: A human-readable description of the :term:`Federation`. This can
be ``null`` as well as an empty string.
+:lastUpdated: The date and time at which this :term:`Federation` was last
modified, in :RFC:`3339` format
+
+ .. versionchanged:: 5.0
+ In earlier versions of the API, this field was given in
:ref:`non-rfc-datetime`.
+
+:ttl: :abbr:`TTL (Time to Live)` for the ``cname``, in hours
+
+.. 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,PUT,DELETE
+ access-control-allow-origin: *
+ content-type: application/json
+ set-cookie: mojolicious=...; Path=/; HttpOnly
+ whole-content-sha512:
SJA7G+7G5KcOfCtnE3Dq5DCobWtGRUKSppiDkfLZoG5+paq4E1aZGqUb6vGVsd+TpPg75MLlhyqfdfCHnhLX/g==
+ x-server-name: traffic_ops_golang/
+ content-length: 170
+ date: Wed, 05 Dec 2018 00:35:40 GMT
+
+ { "response": {
+ "id": 1,
+ "cname": "test.quest.",
+ "ttl": 68,
+ "description": "A test federation",
+ "lastUpdated": "2018-12-05T00:05:16Z",
+ "deliveryService": {
+ "id": 1,
+ "xmlID": "demo1"
+ }
+ }}
+
``PUT``
=======
-Updates a federation.
+Updates a :term:`Federation`.
:Auth. Required: Yes
:Roles Required: "admin"
@@ -32,20 +110,25 @@ Request Structure
-----------------
.. table:: Request Path Parameters
-
+------+-------------------------------------------------------------------------------------+
- | Name | Description
|
-
+======+=====================================================================================+
- | name | The name of the CDN for which the federation identified by
``ID`` will be inspected |
-
+------+-------------------------------------------------------------------------------------+
- | ID | An integral, unique identifier for the federation to be
inspected |
-
+------+-------------------------------------------------------------------------------------+
+
+------+-------------------------------------------------------------------------------------------+
+ | Name | Description
|
+
+======+===========================================================================================+
+ | name | The name of the CDN for which the :term:`Federation`
identified by ``ID`` will be updated |
+
+------+-------------------------------------------------------------------------------------------+
+ | ID | An integral, unique identifier for the :term:`Federation` to
be updated |
+
+------+-------------------------------------------------------------------------------------------+
-:cname: The Canonical Name (CNAME) used by the federation
+.. caution:: The name of the CDN doesn't actually matter. It doesn't even need
to be the name of any existing CDN.
+
+:cname: The :abbr:`CNAME (Canonical Name)` used by the :term:`Federation`
.. note:: The CNAME must end with a "``.``"
:description: An optional description of the federation
-:ttl: Time to Live (TTL) for the name record used for ``cname``
+:ttl: Time to Live (TTL) for the name record used for ``cname`` -
minimum of 60
+
+ .. versionchanged:: 5.0
+ In earlier API versions, there is no enforced minimum (although
Traffic Portal would never allow a value under 60).
.. code-block:: http
:caption: Request Example
@@ -60,20 +143,20 @@ Request Structure
{
"cname": "foo.bar.",
- "ttl": 48
+ "ttl": 68
}
Response Structure
------------------
-:cname: The Canonical Name (CNAME) used by the federation
-:description: An optionally-present field containing a description of the field
+:cname: The :abbr:`CNAME (Canonical Name)` used by the :term:`Federation`
+:description: A human-readable description of the :term:`Federation`. This can
be ``null`` as well as an empty string.
+:lastUpdated: The date and time at which this :term:`Federation` was last
modified, in :RFC:`3339` format
- .. note:: This key will only be present if the description was provided
when the federation was created
-
-:lastUpdated: The date and time at which this federation was last modified, in
:ref:`non-rfc-datetime`
-:ttl: Time to Live (TTL) for the ``cname``, in hours
+ .. versionchanged:: 5.0
+ In earlier versions of the API, this field was given in
:ref:`non-rfc-datetime`.
+:ttl: :abbr:`TTL (Time to Live)` for the ``cname``, in hours
.. code-block:: http
:caption: Response Example
@@ -92,16 +175,16 @@ Response Structure
{ "alerts": [
{
- "text": "cdnfederation was updated.",
+ "text": "Federation was updated",
"level": "success"
}
],
"response": {
"id": 1,
"cname": "foo.bar.",
- "ttl": 48,
+ "ttl": 68,
"description": null,
- "lastUpdated": "2018-12-05 01:03:40+00"
+ "lastUpdated": "2018-12-05T01:03:40Z"
}}
@@ -112,19 +195,24 @@ Deletes a specific federation.
:Auth. Required: Yes
:Roles Required: "admin"
:Permissions Required: FEDERATION:DELETE, FEDERATION:READ, CDN:READ
-:Response Type: ``undefined``
+:Response Type: Object
+
+.. versionchanged:: 5.0
+ In earlier API versions, no ``response`` property is present in these
responses.
Request Structure
-----------------
.. table:: Request Path Parameters
-
+------+-------------------------------------------------------------------------------------+
- | Name | Description
|
-
+======+=====================================================================================+
- | name | The name of the CDN for which the federation identified by
``ID`` will be inspected |
-
+------+-------------------------------------------------------------------------------------+
- | ID | An integral, unique identifier for the federation to be
inspected |
-
+------+-------------------------------------------------------------------------------------+
+
+------+-------------------------------------------------------------------------------------------+
+ | Name | Description
|
+
+======+===========================================================================================+
+ | name | The name of the CDN for which the :term:`Federation`
identified by ``ID`` will be deleted |
+
+------+-------------------------------------------------------------------------------------------+
+ | ID | An integral, unique identifier for the :term:`Federation` to
be deleted |
+
+------+-------------------------------------------------------------------------------------------+
+
+.. caution:: The name of the CDN doesn't actually matter. It doesn't even need
to be the name of any existing CDN.
.. code-block:: http
:caption: Request Example
@@ -137,6 +225,11 @@ Request Structure
Response Structure
------------------
+:cname: The :abbr:`CNAME (Canonical Name)` used by the :term:`Federation`
+:description: A human-readable description of the :term:`Federation`. This can
be ``null`` as well as an empty string.
+:lastUpdated: The date and time at which this :term:`Federation` was last
modified, in :RFC:`3339` format
+:ttl: :abbr:`TTL (Time to Live)` for the ``cname``, in hours
+
.. code-block:: http
:caption: Response Example
@@ -154,7 +247,14 @@ Response Structure
{ "alerts": [
{
- "text": "cdnfederation was deleted.",
+ "text": "Federation was deleted",
"level": "success"
}
- ]}
+ ],
+ "response": {
+ "id": 1,
+ "cname": "foo.bar.",
+ "ttl": 68,
+ "description": null,
+ "lastUpdated": "2018-12-05T01:03:40Z"
+ }}
diff --git a/docs/source/api/v5/coordinates.rst
b/docs/source/api/v5/coordinates.rst
index dc966fbeab..8f7c885bce 100644
--- a/docs/source/api/v5/coordinates.rst
+++ b/docs/source/api/v5/coordinates.rst
@@ -56,7 +56,7 @@ Request Structure
Response Structure
------------------
:id: Integral, unique, identifier for this coordinate pair
-:lastUpdated: The time and date at which this entry was last updated, in
:rfc:3339 format
+:lastUpdated: The time and date at which this entry was last updated, in
:RFC:`3339` format
:latitude: Latitude of the coordinate
:longitude: Longitude of the coordinate
:name: The name of the coordinate - typically this just reflects the
name of the Cache Group for which the coordinate was created
@@ -159,7 +159,7 @@ Request Structure
Response Structure
------------------
:id: Integral, unique, identifier for the newly created coordinate
pair
-:lastUpdated: The time and date at which this entry was last updated, in
:rfc:3339 format
+:lastUpdated: The time and date at which this entry was last updated, in
:RFC:`3339` format
:latitude: Latitude of the newly created coordinate
:longitude: Longitude of the newly created coordinate
:name: The name of the coordinate
@@ -233,7 +233,7 @@ Request Structure
Response Structure
------------------
:id: Integral, unique, identifier for the coordinate pair
-:lastUpdated: The time and date at which this entry was last updated, in
:rfc:3339 format
+:lastUpdated: The time and date at which this entry was last updated, in
:RFC:`3339` format
:latitude: Latitude of the coordinate
:longitude: Longitude of the coordinate
:name: The name of the coordinate
@@ -279,7 +279,7 @@ Deletes a coordinate
Request Structure
-----------------
:id: Integral, unique, identifier for the coordinate pair
-:lastUpdated: The time and date at which this entry was last updated, in
:rfc:3339 format
+:lastUpdated: The time and date at which this entry was last updated, in
:RFC:`3339` format
:latitude: Latitude of the coordinate
:longitude: Longitude of the coordinate
:name: The name of the coordinate
diff --git a/docs/source/api/v5/deliveryservices.rst
b/docs/source/api/v5/deliveryservices.rst
index bc75722c32..21a0c01799 100644
--- a/docs/source/api/v5/deliveryservices.rst
+++ b/docs/source/api/v5/deliveryservices.rst
@@ -116,7 +116,7 @@ Response Structure
:innerHeaderRewrite: A set of :ref:`ds-inner-header-rw-rules`
:ipv6RoutingEnabled: A boolean that defines the :ref:`ds-ipv6-routing`
setting on this :term:`Delivery Service`
:lastHeaderRewrite: A set of :ref:`ds-last-header-rw-rules`
-:lastUpdated: The date and time at which this :term:`Delivery
Service` was last updated, in :rfc:3339 format
+:lastUpdated: The date and time at which this :term:`Delivery
Service` was last updated, in :RFC:`3339` format
:logsEnabled: A boolean that defines the :ref:`ds-logs-enabled`
setting on this :term:`Delivery Service`
:longDesc: The :ref:`ds-longdesc` of this :term:`Delivery
Service`
:matchList: The :term:`Delivery Service`'s :ref:`ds-matchlist`
@@ -459,7 +459,7 @@ Response Structure
:innerHeaderRewrite: A set of :ref:`ds-inner-header-rw-rules`
:ipv6RoutingEnabled: A boolean that defines the :ref:`ds-ipv6-routing`
setting on this :term:`Delivery Service`
:lastHeaderRewrite: A set of :ref:`ds-last-header-rw-rules`
-:lastUpdated: The date and time at which this :term:`Delivery
Service` was last updated, in :rfc:3339 format
+:lastUpdated: The date and time at which this :term:`Delivery
Service` was last updated, in :RFC:`3339` format
:logsEnabled: A boolean that defines the :ref:`ds-logs-enabled`
setting on this :term:`Delivery Service`
:longDesc: The :ref:`ds-longdesc` of this :term:`Delivery
Service`
:matchList: The :term:`Delivery Service`'s :ref:`ds-matchlist`
diff --git a/docs/source/api/v5/deliveryservices_id.rst
b/docs/source/api/v5/deliveryservices_id.rst
index 52e41c83ae..a832b67bd4 100644
--- a/docs/source/api/v5/deliveryservices_id.rst
+++ b/docs/source/api/v5/deliveryservices_id.rst
@@ -218,7 +218,7 @@ Response Structure
:innerHeaderRewrite: A set of :ref:`ds-inner-header-rw-rules`
:ipv6RoutingEnabled: A boolean that defines the :ref:`ds-ipv6-routing`
setting on this :term:`Delivery Service`
:lastHeaderRewrite: A set of :ref:`ds-last-header-rw-rules`
-:lastUpdated: The date and time at which this :term:`Delivery
Service` was last updated, in :rfc:3339 format
+:lastUpdated: The date and time at which this :term:`Delivery
Service` was last updated, in :RFC:`3339` format
:logsEnabled: A boolean that defines the :ref:`ds-logs-enabled`
setting on this :term:`Delivery Service`
:longDesc: The :ref:`ds-longdesc` of this :term:`Delivery
Service`
:matchList: The :term:`Delivery Service`'s :ref:`ds-matchlist`
diff --git a/docs/source/api/v5/deliveryservices_id_safe.rst
b/docs/source/api/v5/deliveryservices_id_safe.rst
index d5b4cdfe7b..3405c693d6 100644
--- a/docs/source/api/v5/deliveryservices_id_safe.rst
+++ b/docs/source/api/v5/deliveryservices_id_safe.rst
@@ -95,7 +95,7 @@ Response Structure
:innerHeaderRewrite: A set of :ref:`ds-inner-header-rw-rules`
:ipv6RoutingEnabled: A boolean that defines the :ref:`ds-ipv6-routing`
setting on this :term:`Delivery Service`
:lastHeaderRewrite: A set of :ref:`ds-last-header-rw-rules`
-:lastUpdated: The date and time at which this :term:`Delivery
Service` was last updated, in :rfc:3339 format
+:lastUpdated: The date and time at which this :term:`Delivery
Service` was last updated, in :RFC:`3339` format
:logsEnabled: A boolean that defines the :ref:`ds-logs-enabled`
setting on this :term:`Delivery Service`
:longDesc: The :ref:`ds-longdesc` of this :term:`Delivery
Service`
:matchList: The :term:`Delivery Service`'s :ref:`ds-matchlist`
diff --git a/docs/source/api/v5/servers.rst b/docs/source/api/v5/servers.rst
index 1166d01146..cbf5665c06 100644
--- a/docs/source/api/v5/servers.rst
+++ b/docs/source/api/v5/servers.rst
@@ -142,7 +142,7 @@ Response Structure
:routerPortName: The human-readable name of the router responsible
for reaching this server's interface.
:routerPortName: The human-readable name of the port used by the
router responsible for reaching this server's interface.
-:lastUpdated: The date and time at which this server description was last
modified, in :RFC:3339 format
+:lastUpdated: The date and time at which this server description was last
modified, in :RFC:`3339` format
.. versionchanged:: 5.0
In earlier versions of the API, this field was given in
:ref:`non-rfc-datetime`.
@@ -480,7 +480,7 @@ Response Structure
:routerPortName: The human-readable name of the router responsible
for reaching this server's interface.
:routerPortName: The human-readable name of the port used by the
router responsible for reaching this server's interface.
-:lastUpdated: The date and time at which this server description was last
modified, in :RFC:3339 format
+:lastUpdated: The date and time at which this server description was last
modified, in :RFC:`3339` format
.. versionchanged:: 5.0
In earlier versions of the API, this field was given in
:ref:`non-rfc-datetime`.
diff --git a/docs/source/api/v5/servers_id.rst
b/docs/source/api/v5/servers_id.rst
index f6275e6484..2573a4260a 100644
--- a/docs/source/api/v5/servers_id.rst
+++ b/docs/source/api/v5/servers_id.rst
@@ -236,7 +236,7 @@ Response Structure
:routerPortName: The human-readable name of the router responsible
for reaching this server's interface.
:routerPortName: The human-readable name of the port used by the
router responsible for reaching this server's interface.
-:lastUpdated: The date and time at which this server description was last
modified, in :RFC:3339 format
+:lastUpdated: The date and time at which this server description was last
modified, in :RFC:`3339` format
.. versionchanged:: 5.0
In earlier versions of the API, this field was given in
:ref:`non-rfc-datetime`.
@@ -466,7 +466,7 @@ Response Structure
:routerPortName: The human-readable name of the router responsible
for reaching this server's interface.
:routerPortName: The human-readable name of the port used by the
router responsible for reaching this server's interface.
-:lastUpdated: The date and time at which this server description was last
modified, in :RFC:3339 format
+:lastUpdated: The date and time at which this server description was last
modified, in :RFC:`3339` format
.. versionchanged:: 5.0
In earlier versions of the API, this field was given in
:ref:`non-rfc-datetime`.
diff --git a/docs/source/api/v5/servers_id_deliveryservices.rst
b/docs/source/api/v5/servers_id_deliveryservices.rst
index d62b6516d6..a43dfd50dd 100644
--- a/docs/source/api/v5/servers_id_deliveryservices.rst
+++ b/docs/source/api/v5/servers_id_deliveryservices.rst
@@ -102,7 +102,7 @@ Response Structure
:innerHeaderRewrite: A set of :ref:`ds-inner-header-rw-rules`
:ipv6RoutingEnabled: A boolean that defines the :ref:`ds-ipv6-routing`
setting on this :term:`Delivery Service`
:lastHeaderRewrite: A set of :ref:`ds-last-header-rw-rules`
-:lastUpdated: The date and time at which this :term:`Delivery
Service` was last updated, in :rfc:3339 format
+:lastUpdated: The date and time at which this :term:`Delivery
Service` was last updated, in :RFC:`3339` format
:logsEnabled: A boolean that defines the :ref:`ds-logs-enabled`
setting on this :term:`Delivery Service`
:longDesc: The :ref:`ds-longdesc` of this :term:`Delivery
Service`
:matchList: The :term:`Delivery Service`'s :ref:`ds-matchlist`
diff --git a/docs/source/api/v5/statuses_id.rst
b/docs/source/api/v5/statuses_id.rst
index dcca4425b3..4742251aff 100644
--- a/docs/source/api/v5/statuses_id.rst
+++ b/docs/source/api/v5/statuses_id.rst
@@ -48,7 +48,7 @@ Response Structure
------------------
:description: A short description of the status
:id: The integral, unique identifier of this status
-:lastUpdated: The date and time at which this status was last modified, in
:rfc:3339 format
+:lastUpdated: The date and time at which this status was last modified, in
:RFC:`3339` format
:name: The name of the status
.. code-block:: http
diff --git a/infrastructure/cdn-in-a-box/enroller/enroller.go
b/infrastructure/cdn-in-a-box/enroller/enroller.go
index 926a9db478..7af02ccd9d 100644
--- a/infrastructure/cdn-in-a-box/enroller/enroller.go
+++ b/infrastructure/cdn-in-a-box/enroller/enroller.go
@@ -812,7 +812,12 @@ func enrollFederation(toSession *session, r io.Reader)
error {
}
opts := client.NewRequestOptions()
for _, mapping := range federation.Mappings {
- var cdnFederation tc.CDNFederation
+ if mapping.CName == nil || mapping.TTL == nil {
+ err = fmt.Errorf("mapping found with null or undefined
CName and/or TTL: %+v", mapping)
+ log.Errorln(err)
+ return err
+ }
+ var cdnFederation tc.CDNFederationV5
var cdnName string
{
xmlID := string(federation.DeliveryService)
@@ -831,9 +836,9 @@ func enrollFederation(toSession *session, r io.Reader)
error {
}
deliveryService := deliveryServices.Response[0]
cdnName = *deliveryService.CDNName
- cdnFederation = tc.CDNFederation{
- CName: mapping.CName,
- TTL: mapping.TTL,
+ cdnFederation = tc.CDNFederationV5{
+ CName: *mapping.CName,
+ TTL: *mapping.TTL,
}
resp, _, err :=
toSession.CreateCDNFederation(cdnFederation, cdnName, client.RequestOptions{})
if err != nil {
@@ -842,13 +847,8 @@ func enrollFederation(toSession *session, r io.Reader)
error {
return err
}
cdnFederation = resp.Response
- if cdnFederation.ID == nil {
- err = fmt.Errorf("federation returned from
creation through Traffic Ops with null or undefined ID")
- log.Infoln(err)
- return err
- }
- if alerts, _, err :=
toSession.CreateFederationDeliveryServices(*cdnFederation.ID,
[]int{*deliveryService.ID}, true, client.RequestOptions{}); err != nil {
- err = fmt.Errorf("assigning Delivery Service %s
to Federation with ID %d: %v - alerts: %+v", xmlID, *cdnFederation.ID, err,
alerts.Alerts)
+ if alerts, _, err :=
toSession.CreateFederationDeliveryServices(cdnFederation.ID,
[]int{*deliveryService.ID}, true, client.RequestOptions{}); err != nil {
+ err = fmt.Errorf("assigning Delivery Service %s
to Federation with ID %d: %v - alerts: %+v", xmlID, cdnFederation.ID, err,
alerts.Alerts)
log.Infoln(err)
return err
}
@@ -865,10 +865,10 @@ func enrollFederation(toSession *session, r io.Reader)
error {
log.Infoln(err)
return err
}
- resp, _, err :=
toSession.CreateFederationUsers(*cdnFederation.ID, []int{*user.Response.ID},
true, client.RequestOptions{})
+ resp, _, err :=
toSession.CreateFederationUsers(cdnFederation.ID, []int{*user.Response.ID},
true, client.RequestOptions{})
if err != nil {
username := user.Response.Username
- err = fmt.Errorf("assigning User '%s' to
Federation with ID %d: %v - alerts: %+v", username, *cdnFederation.ID, err,
resp.Alerts)
+ err = fmt.Errorf("assigning User '%s' to
Federation with ID %d: %v - alerts: %+v", username, cdnFederation.ID, err,
resp.Alerts)
log.Infoln(err)
return err
}
@@ -885,20 +885,20 @@ func enrollFederation(toSession *session, r io.Reader)
error {
allResolverIDs = append(allResolverIDs,
resolverIDs...)
}
}
- if resp, _, err :=
toSession.AssignFederationFederationResolver(*cdnFederation.ID, allResolverIDs,
true, client.RequestOptions{}); err != nil {
- err = fmt.Errorf("assigning Federation Resolvers to
Federation with ID %d: %v - alerts: %+v", *cdnFederation.ID, err, resp.Alerts)
+ if resp, _, err :=
toSession.AssignFederationFederationResolver(cdnFederation.ID, allResolverIDs,
true, client.RequestOptions{}); err != nil {
+ err = fmt.Errorf("assigning Federation Resolvers to
Federation with ID %d: %v - alerts: %+v", cdnFederation.ID, err, resp.Alerts)
log.Infoln(err)
return err
}
- opts.QueryParameters.Set("id", strconv.Itoa(*cdnFederation.ID))
- response, _, err := toSession.GetCDNFederationsByName(cdnName,
opts)
+ opts.QueryParameters.Set("id", strconv.Itoa(cdnFederation.ID))
+ response, _, err := toSession.GetCDNFederations(cdnName, opts)
opts.QueryParameters.Del("id")
if err != nil {
- err = fmt.Errorf("getting CDN Federation with ID %d: %v
- alerts: %+v", *cdnFederation.ID, err, response.Alerts)
+ err = fmt.Errorf("getting CDN Federation with ID %d: %v
- alerts: %+v", cdnFederation.ID, err, response.Alerts)
return err
}
if len(response.Response) < 1 {
- err = fmt.Errorf("unable to GET a CDN Federation ID %d
in CDN %s", *cdnFederation.ID, cdnName)
+ err = fmt.Errorf("unable to GET a CDN Federation ID %d
in CDN %s", cdnFederation.ID, cdnName)
log.Infoln(err)
return err
}
@@ -908,7 +908,7 @@ func enrollFederation(toSession *session, r io.Reader)
error {
enc.SetIndent("", " ")
err = enc.Encode(&cdnFederation)
if err != nil {
- err = fmt.Errorf("encoding CDNFederation %s with ID %d:
%v", *cdnFederation.CName, *cdnFederation.ID, err)
+ err = fmt.Errorf("encoding CDNFederation %s with ID %d:
%v", cdnFederation.CName, cdnFederation.ID, err)
log.Infoln(err)
return err
}
diff --git a/lib/go-tc/federation.go b/lib/go-tc/federation.go
index 8447ed658b..b565f3fb97 100644
--- a/lib/go-tc/federation.go
+++ b/lib/go-tc/federation.go
@@ -24,6 +24,7 @@ import (
"errors"
"fmt"
"net"
+ "time"
"github.com/apache/trafficcontrol/lib/go-util"
@@ -69,6 +70,39 @@ type CDNFederation struct {
*DeliveryServiceIDs `json:"deliveryService,omitempty"`
}
+// CDNFederationDeliveryService holds information about an assigned Delivery
+// Service within a CDNFederationV5 structure.
+type CDNFederationDeliveryService = struct {
+ ID int `json:"id" db:"ds_id"`
+ XMLID string `json:"xmlID" db:"xml_id"`
+}
+
+// CDNFederationV5 represents a Federation of some CDN as it appears in version
+// 5 of the Traffic Ops API.
+type CDNFederationV5 struct {
+ ID int `json:"id" db:"id"`
+ CName string `json:"cname" db:"cname"`
+ TTL int `json:"ttl" db:"ttl"`
+ Description *string `json:"description" db:"description"`
+ LastUpdated time.Time `json:"lastUpdated" db:"last_updated"`
+
+ DeliveryService *CDNFederationDeliveryService
`json:"deliveryService,omitempty"`
+}
+
+// CDNFederationsV5Response represents a Traffic Ops APIv5 response to a
request
+// for one or more of a CDN's Federations.
+type CDNFederationsV5Response struct {
+ Response []CDNFederationV5 `json:"response"`
+ Alerts
+}
+
+// CDNFederationV5Response represents a Traffic Ops APIv5 response to a request
+// for a single CDN's Federations.
+type CDNFederationV5Response struct {
+ Response CDNFederationV5 `json:"response"`
+ Alerts
+}
+
// DeliveryServiceIDs are pairs of identifiers for Delivery Services.
type DeliveryServiceIDs struct {
DsId *int `json:"id,omitempty" db:"ds_id"`
diff --git a/lib/go-tc/totest/federations.go b/lib/go-tc/totest/federations.go
index 99a1be0bce..9f90efab36 100644
--- a/lib/go-tc/totest/federations.go
+++ b/lib/go-tc/totest/federations.go
@@ -45,16 +45,14 @@ func GetFederationID(t *testing.T, cname string) func() int
{
}
}
-func setFederationID(t *testing.T, cdnFederation tc.CDNFederation) {
- assert.RequireNotNil(t, cdnFederation.CName, "Federation CName was nil
after posting.")
- assert.RequireNotNil(t, cdnFederation.ID, "Federation ID was nil after
posting.")
- fedIDs[*cdnFederation.CName] = *cdnFederation.ID
+func setFederationID(t *testing.T, cdnFederation tc.CDNFederationV5) {
+ fedIDs[cdnFederation.CName] = cdnFederation.ID
}
func CreateTestCDNFederations(t *testing.T, cl *toclient.Session, dat
TrafficControl) {
for _, federation := range dat.Federations {
opts := toclient.NewRequestOptions()
- opts.QueryParameters.Set("xmlId",
*federation.DeliveryServiceIDs.XmlId)
+ opts.QueryParameters.Set("xmlId",
federation.DeliveryService.XMLID)
dsResp, _, err := cl.GetDeliveryServices(opts)
assert.RequireNoError(t, err, "Could not get Delivery Service
by XML ID: %v", err)
assert.RequireEqual(t, 1, len(dsResp.Response), "Expected one
Delivery Service, but got %d", len(dsResp.Response))
@@ -67,7 +65,7 @@ func CreateTestCDNFederations(t *testing.T, cl
*toclient.Session, dat TrafficCon
setFederationID(t, resp.Response)
assert.RequireNotNil(t, resp.Response.ID, "Federation ID was
nil after posting.")
assert.RequireNotNil(t, dsResp.Response[0].ID, "Delivery
Service ID was nil.")
- _, _, err =
cl.CreateFederationDeliveryServices(*resp.Response.ID,
[]int{*dsResp.Response[0].ID}, false, toclient.NewRequestOptions())
+ _, _, err =
cl.CreateFederationDeliveryServices(resp.Response.ID,
[]int{*dsResp.Response[0].ID}, false, toclient.NewRequestOptions())
assert.NoError(t, err, "Could not create Federation Delivery
Service: %v", err)
}
}
@@ -79,7 +77,7 @@ func DeleteTestCDNFederations(t *testing.T, cl
*toclient.Session) {
assert.NoError(t, err, "Cannot delete federation #%d: %v -
alerts: %+v", id, err, resp.Alerts)
opts.QueryParameters.Set("id", strconv.Itoa(id))
- data, _, err := cl.GetCDNFederationsByName(FederationCDNName,
opts)
+ data, _, err := cl.GetCDNFederations(FederationCDNName, opts)
assert.Equal(t, 0, len(data.Response), "expected federation to
be deleted")
}
fedIDs = make(map[string]int) // reset the global variable for the next
test
diff --git a/lib/go-tc/totest/traffic_control.go
b/lib/go-tc/totest/traffic_control.go
index a6260a48d9..bd384718ff 100644
--- a/lib/go-tc/totest/traffic_control.go
+++ b/lib/go-tc/totest/traffic_control.go
@@ -37,7 +37,7 @@ type TrafficControl struct {
DeliveryServiceServerAssignments
[]tc.DeliveryServiceServerV5
`json:"deliveryServiceServerAssignments"`
TopologyBasedDeliveryServicesRequiredCapabilities
[]tc.DeliveryServicesRequiredCapability
`json:"topologyBasedDeliveryServicesRequiredCapabilities"`
Divisions []tc.DivisionV5
`json:"divisions"`
- Federations []tc.CDNFederation
`json:"federations"`
+ Federations []tc.CDNFederationV5
`json:"federations"`
FederationResolvers
[]tc.FederationResolverV5 `json:"federation_resolvers"`
Jobs
[]tc.InvalidationJobCreateV4 `json:"jobs"`
Origins []tc.OriginV5
`json:"origins"`
diff --git a/traffic_ops/testing/api/v5/cdn_federations_test.go
b/traffic_ops/testing/api/v5/cdn_federations_test.go
index 2878b3dfe8..d0a554774e 100644
--- a/traffic_ops/testing/api/v5/cdn_federations_test.go
+++ b/traffic_ops/testing/api/v5/cdn_federations_test.go
@@ -45,7 +45,7 @@ func TestCDNFederations(t *testing.T) {
currentTimeRFC := currentTime.Format(time.RFC1123)
tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123)
- methodTests := utils.TestCase[client.Session,
client.RequestOptions, tc.CDNFederation]{
+ methodTests := utils.TestCase[client.Session,
client.RequestOptions, tc.CDNFederationV5]{
"GET": {
"NOT MODIFIED when NO CHANGES made": {
ClientSession: TOSession,
@@ -107,9 +107,9 @@ func TestCDNFederations(t *testing.T) {
"OK when VALID request": {
EndpointID: GetFederationID(t,
"google.com."),
ClientSession: TOSession,
- RequestBody: tc.CDNFederation{
- CName:
util.Ptr("new.cname."),
- TTL: util.Ptr(34),
+ RequestBody: tc.CDNFederationV5{
+ CName: "new.cname.",
+ TTL: 64,
Description:
util.Ptr("updated"),
},
Expectations:
utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
validateCDNFederationUpdateFields(map[string]interface{}{"CName":
"new.cname."})),
@@ -118,9 +118,9 @@ func TestCDNFederations(t *testing.T) {
EndpointID: GetFederationID(t,
"booya.com."),
ClientSession: TOSession,
RequestOpts:
client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince:
{currentTimeRFC}}},
- RequestBody: tc.CDNFederation{
- CName:
util.Ptr("booya.com."),
- TTL: util.Ptr(34),
+ RequestBody: tc.CDNFederationV5{
+ CName: "booya.com.",
+ TTL: 64,
Description: util.Ptr("fooya"),
},
Expectations:
utils.CkRequest(utils.HasError(),
utils.HasStatus(http.StatusPreconditionFailed)),
@@ -128,9 +128,9 @@ func TestCDNFederations(t *testing.T) {
"PRECONDITION FAILED when updating with IFMATCH
ETAG Header": {
EndpointID: GetFederationID(t,
"booya.com."),
ClientSession: TOSession,
- RequestBody: tc.CDNFederation{
- CName:
util.Ptr("new.cname."),
- TTL: util.Ptr(34),
+ RequestBody: tc.CDNFederationV5{
+ CName: "new.cname.",
+ TTL: 64,
Description:
util.Ptr("updated"),
},
RequestOpts:
client.RequestOptions{Header: http.Header{rfc.IfMatch:
{rfc.ETag(currentTime)}}},
@@ -145,7 +145,7 @@ func TestCDNFederations(t *testing.T) {
switch method {
case "GET":
t.Run(name, func(t *testing.T) {
- resp, reqInf, err :=
testCase.ClientSession.GetCDNFederationsByName(cdnName, testCase.RequestOpts)
+ resp, reqInf, err :=
testCase.ClientSession.GetCDNFederations(cdnName, testCase.RequestOpts)
for _, check := range
testCase.Expectations {
check(t,
reqInf, resp.Response, resp.Alerts, err)
}
@@ -181,12 +181,11 @@ func TestCDNFederations(t *testing.T) {
func validateCDNFederationUpdateFields(expectedResp map[string]interface{})
utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _
tc.Alerts, _ error) {
assert.RequireNotNil(t, resp, "Expected CDN Federation response
to not be nil.")
- CDNFederationResp := resp.(tc.CDNFederation)
+ CDNFederationResp := resp.(tc.CDNFederationV5)
for field, expected := range expectedResp {
switch field {
case "CName":
- assert.RequireNotNil(t,
CDNFederationResp.CName, "Expected CName to not be nil.")
- assert.Equal(t, expected,
*CDNFederationResp.CName, "Expected CName to be %v, but got %s", expected,
*CDNFederationResp.CName)
+ assert.Equal(t, expected,
CDNFederationResp.CName, "Expected CName to be %v, but got %s", expected,
CDNFederationResp.CName)
default:
t.Errorf("Expected field: %v, does not exist in
response", field)
}
@@ -196,11 +195,11 @@ func validateCDNFederationUpdateFields(expectedResp
map[string]interface{}) util
func validateCDNFederationPagination(paginationParam string) utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _
tc.Alerts, _ error) {
- paginationResp := resp.([]tc.CDNFederation)
+ paginationResp := resp.([]tc.CDNFederationV5)
opts := client.NewRequestOptions()
opts.QueryParameters.Set("orderby", "id")
- respBase, _, err := TOSession.GetCDNFederationsByName(cdnName,
opts)
+ respBase, _, err := TOSession.GetCDNFederations(cdnName, opts)
assert.RequireNoError(t, err, "Cannot get Federation Users: %v
- alerts: %+v", err, respBase.Alerts)
CDNfederations := respBase.Response
@@ -220,10 +219,9 @@ func validateCDNFederationCNameSort() utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{},
alerts tc.Alerts, _ error) {
assert.RequireNotNil(t, resp, "Expected CDN Federation response
to not be nil.")
var federationCNames []string
- CDNFederationResp := resp.([]tc.CDNFederation)
- for _, CDNFederation := range CDNFederationResp {
- assert.RequireNotNil(t, CDNFederation.CName, "Expected
CDN Federation CName to not be nil.")
- federationCNames = append(federationCNames,
*CDNFederation.CName)
+ CDNFederationResp := resp.([]tc.CDNFederationV5)
+ for _, CDNFederationV5 := range CDNFederationResp {
+ federationCNames = append(federationCNames,
CDNFederationV5.CName)
}
assert.Equal(t, true, sort.StringsAreSorted(federationCNames),
"List is not sorted by their names: %v", federationCNames)
}
@@ -233,9 +231,9 @@ func validateCDNFederationIDDescSort() utils.CkReqFunc {
return func(t *testing.T, _ toclientlib.ReqInf, resp interface{},
alerts tc.Alerts, _ error) {
assert.RequireNotNil(t, resp, "Expected CDN Federation response
to not be nil.")
var CDNFederationIDs []int
- CDNFederationResp := resp.([]tc.CDNFederation)
+ CDNFederationResp := resp.([]tc.CDNFederationV5)
for _, federation := range CDNFederationResp {
- CDNFederationIDs = append([]int{*federation.ID},
CDNFederationIDs...)
+ CDNFederationIDs = append([]int{federation.ID},
CDNFederationIDs...)
}
assert.Equal(t, true, sort.IntsAreSorted(CDNFederationIDs),
"List is not sorted by their ids: %v", CDNFederationIDs)
}
@@ -249,16 +247,14 @@ func GetFederationID(t *testing.T, cname string) func()
int {
}
}
-func setFederationID(t *testing.T, cdnFederation tc.CDNFederation) {
- assert.RequireNotNil(t, cdnFederation.CName, "Federation CName was nil
after posting.")
- assert.RequireNotNil(t, cdnFederation.ID, "Federation ID was nil after
posting.")
- fedIDs[*cdnFederation.CName] = *cdnFederation.ID
+func setFederationID(t *testing.T, cdnFederation tc.CDNFederationV5) {
+ fedIDs[cdnFederation.CName] = cdnFederation.ID
}
func CreateTestCDNFederations(t *testing.T) {
for _, federation := range testData.Federations {
opts := client.NewRequestOptions()
- opts.QueryParameters.Set("xmlId",
*federation.DeliveryServiceIDs.XmlId)
+ opts.QueryParameters.Set("xmlId",
federation.DeliveryService.XMLID)
dsResp, _, err := TOSession.GetDeliveryServices(opts)
assert.RequireNoError(t, err, "Could not get Delivery Service
by XML ID: %v", err)
assert.RequireEqual(t, 1, len(dsResp.Response), "Expected one
Delivery Service, but got %d", len(dsResp.Response))
@@ -271,7 +267,7 @@ func CreateTestCDNFederations(t *testing.T) {
setFederationID(t, resp.Response)
assert.RequireNotNil(t, resp.Response.ID, "Federation ID was
nil after posting.")
assert.RequireNotNil(t, dsResp.Response[0].ID, "Delivery
Service ID was nil.")
- _, _, err =
TOSession.CreateFederationDeliveryServices(*resp.Response.ID,
[]int{*dsResp.Response[0].ID}, false, client.NewRequestOptions())
+ _, _, err =
TOSession.CreateFederationDeliveryServices(resp.Response.ID,
[]int{*dsResp.Response[0].ID}, false, client.NewRequestOptions())
assert.NoError(t, err, "Could not create Federation Delivery
Service: %v", err)
}
}
@@ -283,7 +279,7 @@ func DeleteTestCDNFederations(t *testing.T) {
assert.NoError(t, err, "Cannot delete federation #%d: %v -
alerts: %+v", id, err, resp.Alerts)
opts.QueryParameters.Set("id", strconv.Itoa(id))
- data, _, err := TOSession.GetCDNFederationsByName(cdnName, opts)
+ data, _, err := TOSession.GetCDNFederations(cdnName, opts)
assert.Equal(t, 0, len(data.Response), "expected federation to
be deleted")
}
fedIDs = make(map[string]int) // reset the global variable for the next
test
diff --git a/traffic_ops/testing/api/v5/federations_test.go
b/traffic_ops/testing/api/v5/federations_test.go
index 4bf2dce381..51dcb6d5fe 100644
--- a/traffic_ops/testing/api/v5/federations_test.go
+++ b/traffic_ops/testing/api/v5/federations_test.go
@@ -49,18 +49,19 @@ func TestFederations(t *testing.T) {
validateAllFederationsFields([]map[string]interface{}{
{
"DeliveryService": "ds1",
+ // TODO: Why
are these hard-coded copies of the test data?
"Mappings":
[]map[string]interface{}{
{
"CName": "the.cname.com.",
-
"TTL": 48,
+
"TTL": 68,
},
{
"CName": "booya.com.",
-
"TTL": 34,
+
"TTL": 64,
},
{
"CName": "google.com.",
-
"TTL": 30,
+
"TTL": 60,
},
},
},
diff --git a/traffic_ops/testing/api/v5/tc-fixtures.json
b/traffic_ops/testing/api/v5/tc-fixtures.json
index 3fd3bba2ae..05a885f81e 100644
--- a/traffic_ops/testing/api/v5/tc-fixtures.json
+++ b/traffic_ops/testing/api/v5/tc-fixtures.json
@@ -1864,26 +1864,26 @@
"federations": [
{
"cname": "the.cname.com.",
- "ttl": 48,
+ "ttl": 68,
"description": "the description",
"deliveryService": {
- "xmlId": "ds1"
+ "xmlID": "ds1"
}
},
{
"cname": "booya.com.",
- "ttl": 34,
+ "ttl": 64,
"description": "fooya",
"deliveryService": {
- "xmlId": "ds1"
+ "xmlID": "ds1"
}
},
{
"cname": "google.com.",
- "ttl": 30,
+ "ttl": 60,
"description": "google",
"deliveryService": {
- "xmlId": "ds1"
+ "xmlID": "ds1"
}
}
],
diff --git a/traffic_ops/testing/api/v5/traffic_control_test.go
b/traffic_ops/testing/api/v5/traffic_control_test.go
index ae2a4452ba..3fca22db40 100644
--- a/traffic_ops/testing/api/v5/traffic_control_test.go
+++ b/traffic_ops/testing/api/v5/traffic_control_test.go
@@ -35,7 +35,7 @@ type TrafficControl struct {
DeliveryServiceServerAssignments
[]tc.DeliveryServiceServers
`json:"deliveryServiceServerAssignments"`
TopologyBasedDeliveryServicesRequiredCapabilities
[]tc.DeliveryServicesRequiredCapability
`json:"topologyBasedDeliveryServicesRequiredCapabilities"`
Divisions []tc.DivisionV5
`json:"divisions"`
- Federations []tc.CDNFederation
`json:"federations"`
+ Federations []tc.CDNFederationV5
`json:"federations"`
FederationResolvers
[]tc.FederationResolverV5 `json:"federation_resolvers"`
Jobs
[]tc.InvalidationJobCreateV4 `json:"jobs"`
Origins []tc.OriginV5
`json:"origins"`
diff --git a/traffic_ops/traffic_ops_golang/api/api.go
b/traffic_ops/traffic_ops_golang/api/api.go
index 94a37ff538..7c9c014442 100644
--- a/traffic_ops/traffic_ops_golang/api/api.go
+++ b/traffic_ops/traffic_ops_golang/api/api.go
@@ -720,13 +720,14 @@ func (inf APIInfo) WriteOKResponseWithSummary(resp any,
count uint64) (int, erro
}
// WriteNotModifiedResponse writes a 304 Not Modified response with the given
-// object as the 'response' property of the response body.
+// time as the last modified time in the headers.
//
// This CANNOT be used by any APIInfo that wasn't constructed for the caller by
// Wrap - ing a Handler (yet).
-func (inf APIInfo) WriteNotModifiedResponse(resp any) (int, error, error) {
+func (inf APIInfo) WriteNotModifiedResponse(lastModified time.Time) (int,
error, error) {
+ inf.w.Header().Set(rfc.LastModified, FormatLastModified(lastModified))
inf.w.WriteHeader(http.StatusNotModified)
- WriteResp(inf.w, inf.request, resp)
+ setRespWritten(inf.request)
return http.StatusNotModified, nil, nil
}
diff --git a/traffic_ops/traffic_ops_golang/api/api_test.go
b/traffic_ops/traffic_ops_golang/api/api_test.go
index e852cdeaaa..56d8a5ce7c 100644
--- a/traffic_ops/traffic_ops_golang/api/api_test.go
+++ b/traffic_ops/traffic_ops_golang/api/api_test.go
@@ -321,7 +321,7 @@ func TestAPIInfo_WriteNotModifiedResponse(t *testing.T) {
request: r,
w: w,
}
- code, userErr, sysErr := inf.WriteNotModifiedResponse("test")
+ code, userErr, sysErr := inf.WriteNotModifiedResponse(time.Time{})
if code != http.StatusNotModified {
t.Errorf("WriteNotModifiedResponse should return a %d %s code,
got: %d %s", http.StatusNotModified, http.StatusText(http.StatusNotModified),
code, http.StatusText(code))
}
diff --git a/traffic_ops/traffic_ops_golang/cdnfederation/cdnfederations.go
b/traffic_ops/traffic_ops_golang/cdnfederation/cdnfederations.go
index b15b61283b..514ae7c5a3 100644
--- a/traffic_ops/traffic_ops_golang/cdnfederation/cdnfederations.go
+++ b/traffic_ops/traffic_ops_golang/cdnfederation/cdnfederations.go
@@ -1,3 +1,5 @@
+// Package cdnfederation is one of many, many packages that contain logic
+// pertaining to federations of CDNs and/or parts of CDNs.
package cdnfederation
/*
@@ -28,14 +30,19 @@ import (
"strings"
"time"
+ "github.com/apache/trafficcontrol/lib/go-log"
"github.com/apache/trafficcontrol/lib/go-tc"
"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
"github.com/apache/trafficcontrol/lib/go-util"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/tenant"
+
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/util/ims"
+
"github.com/asaskevich/govalidator"
validation "github.com/go-ozzo/ozzo-validation"
+ "github.com/go-ozzo/ozzo-validation/is"
+ "github.com/lib/pq"
)
// we need a type alias to define functions on
@@ -49,32 +56,103 @@ func (v *TOCDNFederation) GetLastUpdated() (*time.Time,
bool, error) {
return api.GetLastUpdated(v.APIInfo().Tx, *v.ID, "federation")
}
+func selectMaxLastUpdatedQuery(where, orderBy, pagination string) string {
+ return `
+ SELECT max(t)
+ FROM (
+ (
+ SELECT federation.last_updated AS t
+ FROM federation
+ JOIN federation_deliveryservice fds
+ ON fds.federation = federation.id
+ JOIN deliveryservice ds
+ ON ds.id = fds.deliveryservice
+ JOIN cdn c
+ ON c.id = ds.cdn_id ` + where + orderBy +
pagination +
+ `)
+ UNION ALL
+ (
+ SELECT max(last_updated) AS t
+ FROM last_deleted l
+ WHERE l.table_name='federation'
+ )
+ ) AS res`
+}
+
func (v *TOCDNFederation) SetLastUpdated(t tc.TimeNoMod) { v.LastUpdated = &t }
-func (v *TOCDNFederation) InsertQuery() string { return
insertQuery() }
-func (v *TOCDNFederation) SelectMaxLastUpdatedQuery(where, orderBy,
pagination, tableName string) string {
- return `SELECT max(t) from (
- SELECT max(federation.last_updated) as t from federation
- join federation_deliveryservice fds on fds.federation =
federation.id
- join deliveryservice ds on ds.id = fds.deliveryservice
- join cdn c on c.id = ds.cdn_id ` + where + orderBy + pagination
+
- ` UNION ALL
- select max(last_updated) as t from last_deleted l where
l.table_name='federation') as res`
+func (*TOCDNFederation) InsertQuery() string {
+ return `
+ INSERT INTO federation (
+ cname,
+ ttl,
+ description
+ ) VALUES (
+ :cname,
+ :ttl,
+ :description
+ ) RETURNING id, last_updated`
+}
+func (v *TOCDNFederation) SelectMaxLastUpdatedQuery(where, orderBy,
pagination, _ string) string {
+ return selectMaxLastUpdatedQuery(where, orderBy, pagination)
}
func (v *TOCDNFederation) NewReadObj() interface{} { return &TOCDNFederation{}
}
func (v *TOCDNFederation) SelectQuery() string {
return selectByCDNName()
}
-func (v *TOCDNFederation) ParamColumns() map[string]dbhelpers.WhereColumnInfo {
- cols := map[string]dbhelpers.WhereColumnInfo{
- "id": dbhelpers.WhereColumnInfo{Column: "federation.id",
Checker: api.IsInt},
- "name": dbhelpers.WhereColumnInfo{Column: "c.name", Checker:
nil},
- "cname": dbhelpers.WhereColumnInfo{Column: "federation.cname",
Checker: nil},
+
+func paramColumnInfo(v api.Version) map[string]dbhelpers.WhereColumnInfo {
+ if v.GreaterThanOrEqualTo(&api.Version{Major: 5}) {
+ return map[string]dbhelpers.WhereColumnInfo{
+ "id": {
+ Column: "federation.id",
+ Checker: api.IsInt,
+ },
+ "name": {
+ Column: "c.name",
+ },
+ "cname": {
+ Column: "federation.cname",
+ },
+ "xmlID": {
+ Column: "ds.xml_id",
+ },
+ "dsID": {
+ Column: "ds.id",
+ Checker: api.IsInt,
+ },
+ }
+ }
+ return map[string]dbhelpers.WhereColumnInfo{
+ "id": {
+ Column: "federation.id",
+ Checker: api.IsInt,
+ },
+ "name": {
+ Column: "c.name",
+ Checker: nil,
+ },
+ "cname": {
+ Column: "federation.cname",
+ Checker: nil,
+ },
}
- return cols
}
-func (v *TOCDNFederation) DeleteQuery() string { return deleteQuery() }
-func (v *TOCDNFederation) UpdateQuery() string { return updateQuery() }
+
+func (v *TOCDNFederation) ParamColumns() map[string]dbhelpers.WhereColumnInfo {
+ return paramColumnInfo(*v.ReqInfo.Version)
+}
+func (*TOCDNFederation) DeleteQuery() string { return `DELETE FROM federation
WHERE id = :id` }
+func (*TOCDNFederation) UpdateQuery() string {
+ return `
+UPDATE federation SET
+ cname = :cname,
+ ttl = :ttl,
+ description = :description
+WHERE
+ id=:id
+RETURNING last_updated`
+}
// Fufills `Identifier' interface
func (fed TOCDNFederation) GetKeyFieldsInfo() []api.KeyFieldInfo {
@@ -348,9 +426,8 @@ func selectByID() string {
// WHERE federation.id = :id (determined by dbhelper)
}
-func selectByCDNName() string {
- return `
- SELECT
+const selectQuery = `
+SELECT
ds.tenant_id,
federation.id AS id,
federation.cname,
@@ -359,37 +436,254 @@ func selectByCDNName() string {
federation.last_updated,
ds.id AS ds_id,
ds.xml_id
- FROM federation
- JOIN federation_deliveryservice AS fd ON federation.id = fd.federation
- JOIN deliveryservice AS ds ON ds.id = fd.deliveryservice
- JOIN cdn c ON c.id = ds.cdn_id`
- // WHERE cdn.name = :cdn_name (determined by dbhelper)
+FROM federation
+JOIN
+ federation_deliveryservice AS fd
+ ON federation.id = fd.federation
+JOIN
+ deliveryservice AS ds
+ ON ds.id = fd.deliveryservice
+JOIN
+ cdn AS c
+ ON c.id = ds.cdn_id
+`
+
+const insertQuery = `
+INSERT INTO federation (
+ cname,
+ ttl,
+ "description"
+) VALUES (
+ $1,
+ $2,
+ $3
+)
+RETURNING
+ id,
+ last_updated
+`
+
+func selectByCDNName() string {
+ return selectQuery
}
-func updateQuery() string {
- return `
+const updateQuery = `
UPDATE federation SET
- cname = :cname,
- ttl = :ttl,
- description = :description
+ cname = $1,
+ ttl = $2,
+ "description" = $3
WHERE
- id=:id
-RETURNING last_updated`
+ id = $4
+RETURNING
+ last_updated
+`
+
+const deleteQuery = `
+DELETE FROM federation
+WHERE id = $1
+RETURNING
+ cname,
+ "description",
+ id,
+ last_updated,
+ ttl
+`
+
+func addTenancyStmt(where string) string {
+ if where == "" {
+ where = "WHERE "
+ } else {
+ where += " AND "
+ }
+ where += "ds.tenant_id = ANY(:tenantIDs)"
+ return where
}
-func insertQuery() string {
- return `
- INSERT INTO federation (
- cname,
- ttl,
- description
- ) VALUES (
- :cname,
- :ttl,
- :description
- ) RETURNING id, last_updated`
+func getCDNFederations(inf *api.APIInfo) ([]tc.CDNFederationV5, time.Time,
int, error, error) {
+ tenantList, err := tenant.GetUserTenantIDListTx(inf.Tx.Tx,
inf.User.TenantID)
+ if err != nil {
+ return nil, time.Time{}, http.StatusInternalServerError, nil,
fmt.Errorf("getting tenant list for user: %w", err)
+ }
+
+ cols := paramColumnInfo(*inf.Version)
+
+ where, orderBy, pagination, queryValues, errs :=
dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, cols)
+ if len(errs) > 0 {
+ return nil, time.Time{}, http.StatusBadRequest,
util.JoinErrs(errs), nil
+ }
+ queryValues["tenantIDs"] = pq.Array(tenantList)
+
+ where = addTenancyStmt(where)
+
+ if inf.UseIMS() {
+ query := selectMaxLastUpdatedQuery(where, orderBy, pagination)
+ cont, max := ims.TryIfModifiedSinceQuery(inf.Tx,
inf.RequestHeaders(), queryValues, query)
+ if !cont {
+ log.Debugln("IMS HIT")
+ return nil, max, http.StatusNotModified, nil, nil
+ }
+ log.Debugln("IMS MISS")
+ } else {
+ log.Debugln("Non IMS request")
+ }
+
+ query := selectQuery + where + orderBy + pagination
+ rows, err := inf.Tx.NamedQuery(query, queryValues)
+ if err != nil {
+ userErr, sysErr, code := api.ParseDBError(err)
+ return nil, time.Time{}, code, userErr, sysErr
+ }
+ defer log.Close(rows, "closing CDNFederation rows")
+
+ ret := []tc.CDNFederationV5{}
+ for rows.Next() {
+ fed := tc.CDNFederationV5{
+ DeliveryService: new(tc.CDNFederationDeliveryService),
+ }
+ var tenantID int
+ err := rows.Scan(
+ &tenantID,
+ &fed.ID,
+ &fed.CName,
+ &fed.TTL,
+ &fed.Description,
+ &fed.LastUpdated,
+ &fed.DeliveryService.ID,
+ &fed.DeliveryService.XMLID,
+ )
+ if err != nil {
+ return nil, time.Time{},
http.StatusInternalServerError, nil, fmt.Errorf("scanning a CDN Federation:
%w", err)
+ }
+
+ ret = append(ret, fed)
+ }
+
+ return ret, time.Time{}, http.StatusOK, nil, nil
+}
+
+// Read handles GET requests to `cdns/{{name}}/federations`.
+func Read(inf *api.APIInfo) (int, error, error) {
+ api.DefaultSort(inf, "cname")
+ feds, max, code, userErr, sysErr := getCDNFederations(inf)
+ if userErr != nil || sysErr != nil {
+ return code, userErr, sysErr
+ }
+ if feds == nil {
+ return inf.WriteNotModifiedResponse(max)
+ }
+ return inf.WriteOKResponse(feds)
}
-func deleteQuery() string {
- return `DELETE FROM federation WHERE id = :id`
+// ReadID handles GET requests to `cdns/{{name}}/federations/{{ID}}`.
+func ReadID(inf *api.APIInfo) (int, error, error) {
+ feds, max, code, userErr, sysErr := getCDNFederations(inf)
+ if userErr != nil || sysErr != nil {
+ return code, userErr, sysErr
+ }
+ if feds == nil {
+ return inf.WriteNotModifiedResponse(max)
+ }
+
+ id := inf.IntParams["id"]
+ if len(feds) == 0 {
+ return http.StatusNotFound, fmt.Errorf("no such Federation #%d
in CDN %s", id, inf.Params["name"]), nil
+ }
+ if len(feds) > 1 {
+ return http.StatusInternalServerError, nil, fmt.Errorf("%d CDN
federations found by ID: %d", len(feds), id)
+ }
+ return inf.WriteOKResponse(feds[0])
+}
+
+func validate(fed tc.CDNFederationV5) error {
+ endsWithDot := validation.NewStringRule(
+ func(str string) bool {
+ return strings.HasSuffix(str, ".")
+ },
+ "must end with a period",
+ )
+
+ validateErrs := validation.Errors{
+ "cname": validation.Validate(fed.CName, validation.Required,
is.DNSName, endsWithDot),
+ "ttl": validation.Validate(fed.TTL, validation.Required,
validation.Min(60)),
+ }
+
+ return util.JoinErrs(tovalidate.ToErrors(validateErrs))
+}
+
+// Create handles POST requests to `cdns/{{name}}/federations`.
+func Create(inf *api.APIInfo) (int, error, error) {
+ var fed tc.CDNFederationV5
+ if err := inf.DecodeBody(&fed); err != nil {
+ return http.StatusBadRequest, fmt.Errorf("parsing request body:
%w", err), nil
+ }
+
+ // You can't set this at creation time, but if it was in the request it
+ // would be shown in the response - we're supposed to ignore extra
fields.
+ // This doesn't do that exactly, but it helps.
+ fed.DeliveryService = nil
+
+ err := validate(fed)
+ if err != nil {
+ return http.StatusBadRequest, err, nil
+ }
+
+ err = inf.Tx.Tx.QueryRow(insertQuery, fed.CName, fed.TTL,
fed.Description).Scan(&fed.ID, &fed.LastUpdated)
+ if err != nil {
+ userErr, sysErr, code := api.ParseDBError(err)
+ return code, userErr, fmt.Errorf("inserting a CDN Federation:
%w", sysErr)
+ }
+
+ return inf.WriteCreatedResponse(fed, "Federation was created",
"federations/"+strconv.Itoa(fed.ID))
+}
+
+// Update handles PUT requests to `cdns/{{name}}/federations/{{id}}`.
+func Update(inf *api.APIInfo) (int, error, error) {
+ var fed tc.CDNFederationV5
+ if err := inf.DecodeBody(&fed); err != nil {
+ return http.StatusBadRequest, fmt.Errorf("parsing request body:
%w", err), nil
+ }
+
+ id := inf.IntParams["id"]
+
+ var lastModified time.Time
+ err := inf.Tx.QueryRow("SELECT last_updated FROM federation WHERE id =
$1", id).Scan(&lastModified)
+ if err != nil {
+ return http.StatusInternalServerError, nil, fmt.Errorf("getting
last modified time for Federation #%d: %w", id, err)
+ }
+ if !api.IsUnmodified(inf.RequestHeaders(), lastModified) {
+ return http.StatusPreconditionFailed,
api.ResourceModifiedError, nil
+ }
+
+ // You can't set this via a PUT request, but if it was in the request it
+ // would be shown in the response - we're supposed to ignore extra
fields.
+ // This doesn't do that exactly, but it helps.
+ fed.DeliveryService = nil
+ fed.ID = id
+
+ err = validate(fed)
+ if err != nil {
+ return http.StatusBadRequest, err, nil
+ }
+
+ err = inf.Tx.Tx.QueryRow(updateQuery, fed.CName, fed.TTL,
fed.Description, id).Scan(&fed.LastUpdated)
+ if err != nil {
+ userErr, sysErr, code := api.ParseDBError(err)
+ return code, userErr, sysErr
+ }
+
+ return inf.WriteSuccessResponse(fed, "Federation was updated")
+}
+
+// Delete handles DELETE requests to `cdns/{{name}}/federations/{{id}}`.
+func Delete(inf *api.APIInfo) (int, error, error) {
+ id := inf.IntParams["id"]
+
+ var fed tc.CDNFederationV5
+ err := inf.Tx.QueryRow(deleteQuery, id).Scan(&fed.CName,
&fed.Description, &fed.ID, &fed.LastUpdated, &fed.TTL)
+ if err != nil {
+ userErr, sysErr, code := api.ParseDBError(err)
+ return code, userErr, sysErr
+ }
+
+ return inf.WriteSuccessResponse(fed, "Federation was deleted")
}
diff --git
a/traffic_ops/traffic_ops_golang/cdnfederation/cdnfederations_test.go
b/traffic_ops/traffic_ops_golang/cdnfederation/cdnfederations_test.go
new file mode 100644
index 0000000000..e671122a00
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/cdnfederation/cdnfederations_test.go
@@ -0,0 +1,312 @@
+package cdnfederation
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "errors"
+ "net/http"
+ "net/http/httptest"
+ "reflect"
+ "strings"
+ "testing"
+ "time"
+
+ "github.com/apache/trafficcontrol/lib/go-tc"
+ "github.com/apache/trafficcontrol/lib/go-util"
+ "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+ "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth"
+ "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/config"
+
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/trafficvault/backends/disabled"
+ "github.com/jmoiron/sqlx"
+ "gopkg.in/DATA-DOG/go-sqlmock.v1"
+)
+
+func TestAddTenancyStmt(t *testing.T) {
+ where := ""
+ output := addTenancyStmt(where)
+ if expected := "WHERE ds.tenant_id = ANY(:tenantIDs)"; output !=
expected {
+ t.Errorf("Incorrect statement from blank WHERE; want: '%s',
got: '%s", expected, output)
+ }
+ where = "WHERE cname=:cname"
+ output = addTenancyStmt(where)
+ if expected := "WHERE cname=:cname AND ds.tenant_id = ANY(:tenantIDs)";
output != expected {
+ t.Errorf("Incorrect statement from blank WHERE; want: '%s',
got: '%s", expected, output)
+ }
+}
+
+func TestParamColumnInfo(t *testing.T) {
+ params := paramColumnInfo(api.Version{Major: 4})
+ if l := len(params); l != 3 {
+ t.Errorf("Expected versions prior to 5 to support 3 query
params, found support for: %d", l)
+ }
+ for _, param := range [3]string{"cname", "id", "name"} {
+ if _, ok := params[param]; !ok {
+ t.Errorf("Expected versions prior to 5 to support the
'%s' query param, but support for such wasn't found", param)
+ }
+ }
+
+ params = paramColumnInfo(api.Version{Major: 5})
+ if l := len(params); l != 5 {
+ t.Errorf("Expected versions 5 and later to support 5 query
params, found support for: %d", l)
+ }
+ for _, param := range [5]string{"cname", "dsID", "id", "name", "xmlID"}
{
+ if _, ok := params[param]; !ok {
+ t.Errorf("Expected versions 5 and later to support the
'%s' query param, but support for such wasn't found", param)
+ }
+ }
+}
+
+func getMockTx(t *testing.T) (sqlmock.Sqlmock, *sqlx.Tx, *sqlx.DB) {
+ t.Helper()
+ mockDB, mock, err := sqlmock.New()
+ if err != nil {
+ t.Fatalf("an error '%s' was not expected when opening a stub
database connection", err)
+ }
+
+ db := sqlx.NewDb(mockDB, "sqlmock")
+ mock.ExpectBegin()
+
+ return mock, db.MustBegin(), db
+}
+
+func cleanup(t *testing.T, mock sqlmock.Sqlmock, db *sqlx.DB) {
+ t.Helper()
+ mock.ExpectClose()
+ db.Close()
+ if err := mock.ExpectationsWereMet(); err != nil {
+ t.Errorf("expectations were not met: %v", err)
+ }
+}
+
+func gettingUserTenantListFails(t *testing.T) {
+ mock, tx, db := getMockTx(t)
+ defer cleanup(t, mock, db)
+ defer func() {
+ mock.ExpectRollback()
+ tx.Rollback()
+ }()
+
+ err := errors.New("unknown failure")
+ mock.ExpectQuery("WITH RECURSIVE").WillReturnError(err)
+ _, _, code, userErr, sysErr := getCDNFederations(&api.APIInfo{Tx: tx,
User: &auth.CurrentUser{TenantID: 1}})
+
+ if code != http.StatusInternalServerError {
+ t.Errorf("Incorrect response code when getting user tenants
fails; want: %d, got: %d", http.StatusInternalServerError, code)
+ }
+ if userErr != nil {
+ t.Errorf("Unexpected user-facing error: %v", userErr)
+ }
+ if sysErr == nil {
+ t.Fatal("Expected a system error but didn't get one")
+ }
+
+ // You can't use `errors.Is` here because sqlmock doesn't wrap the
error you
+ // give it, so we have to resort to comparing text and praying there's
no
+ // weird coincidence going on behind the scenes.
+ if !strings.Contains(sysErr.Error(), err.Error()) {
+ t.Errorf("Incorrect system error returned; want: %v, got: %v",
err, sysErr)
+ }
+}
+
+func buildingQueryPartsFails(t *testing.T) {
+ mock, tx, db := getMockTx(t)
+ defer cleanup(t, mock, db)
+ defer func() {
+ mock.ExpectRollback()
+ tx.Rollback()
+ }()
+
+ rows := sqlmock.NewRows([]string{"id"})
+ rows.AddRow(1)
+
+ mock.ExpectQuery("WITH RECURSIVE").WillReturnRows(rows)
+
+ inf := api.APIInfo{
+ Params: map[string]string{
+ "dsID": "not an integer",
+ },
+ Tx: tx,
+ User: &auth.CurrentUser{TenantID: 1},
+ Version: &api.Version{Major: 5},
+ }
+ _, _, code, userErr, sysErr := getCDNFederations(&inf)
+ if code != http.StatusBadRequest {
+ t.Errorf("Incorrect response code when getting user tenants
fails; want: %d, got: %d", http.StatusBadRequest, code)
+ }
+ if sysErr != nil {
+ t.Errorf("Unexpected system error: %v", sysErr)
+ }
+ if userErr == nil {
+ t.Fatal("Expected a user-facing error, but didn't get one")
+ }
+ if !strings.Contains(userErr.Error(), "dsID") {
+ t.Errorf("Incorrect user error; expected it to mention 'dsID',
but it didn't: %v", userErr)
+ }
+}
+
+func everythingWorks(t *testing.T) {
+ mock, tx, db := getMockTx(t)
+ defer cleanup(t, mock, db)
+ defer func() {
+ mock.ExpectCommit()
+ tx.Commit()
+ }()
+
+ rows := sqlmock.NewRows([]string{"id"})
+ rows.AddRow(1)
+
+ mock.ExpectQuery("WITH RECURSIVE").WillReturnRows(rows)
+
+ fedRows := sqlmock.NewRows([]string{"tenant_id", "id", "cname", "ttl",
"description", "last_updated", "ds_id", "xml_id"})
+ fed := tc.CDNFederationV5{
+ CName: "test.quest.",
+ Description: util.Ptr("a non-blank description"),
+ DeliveryService: &tc.CDNFederationDeliveryService{
+ ID: 1,
+ XMLID: "some-xmlid",
+ },
+ ID: 1,
+ LastUpdated: time.Time{}.Add(time.Hour),
+ TTL: 5,
+ }
+
+ fedRows.AddRow(1, fed.ID, fed.CName, fed.TTL, fed.Description,
fed.LastUpdated, fed.DeliveryService.ID, fed.DeliveryService.XMLID)
+ mock.ExpectQuery("SELECT").WillReturnRows(fedRows)
+
+ feds, _, _, userErr, sysErr := getCDNFederations(&api.APIInfo{Tx: tx,
User: &auth.CurrentUser{TenantID: 1}, Version: &api.Version{Major: 5}})
+ if userErr != nil {
+ t.Errorf("Unexpected user-facing error: %v", userErr)
+ }
+ if sysErr != nil {
+ t.Errorf("Unexpected system error: %v", sysErr)
+ }
+ if l := len(feds); l != 1 {
+ t.Fatalf("Expected one federation to be returned; got: %d", l)
+ }
+ if !reflect.DeepEqual(feds[0], fed) {
+ t.Errorf("expected a federation like '%#v', but instead found:
%#v", fed, feds[0])
+ }
+}
+
+func TestGetCDNFederations(t *testing.T) {
+ t.Run("getting user Tenant list fails", gettingUserTenantListFails)
+ t.Run("building where/orderby/pagination fails",
buildingQueryPartsFails)
+ t.Run("everything works", everythingWorks)
+}
+
+func wrapContext(r *http.Request, key any, value any) *http.Request {
+ return r.WithContext(context.WithValue(r.Context(), key, value))
+}
+
+func testingInf(t *testing.T, body []byte) (*http.Request, sqlmock.Sqlmock,
*sqlx.DB) {
+ t.Helper()
+ mockDB, mock, err := sqlmock.New()
+ if err != nil {
+ t.Fatalf("failed to open a stub database connection: %v", err)
+ }
+
+ db := sqlx.NewDb(mockDB, "sqlmock")
+
+ r := httptest.NewRequest(http.MethodPost,
"/api/5.0/cdns/ALL/federations", bytes.NewReader(body))
+ r = wrapContext(r, api.ConfigContextKey,
&config.Config{ConfigTrafficOpsGolang:
config.ConfigTrafficOpsGolang{DBQueryTimeoutSeconds: 1000}})
+ r = wrapContext(r, api.DBContextKey, db)
+ r = wrapContext(r, api.TrafficVaultContextKey, &disabled.Disabled{})
+ r = wrapContext(r, api.ReqIDContextKey, uint64(0))
+ r = wrapContext(r, auth.CurrentUserKey, auth.CurrentUser{})
+ r = wrapContext(r, api.PathParamsKey, make(map[string]string))
+
+ mock.ExpectBegin()
+
+ return r, mock, db
+}
+
+func TestCreate(t *testing.T) {
+ newFed := tc.CDNFederationV5{
+ CName: "test.quest.",
+ TTL: 420,
+ Description: nil,
+ }
+ bts, err := json.Marshal(newFed)
+ if err != nil {
+ t.Fatalf("marshaling testing request body: %v", err)
+ }
+
+ newFed.ID = 1
+ newFed.LastUpdated = time.Time{}.Add(time.Hour)
+
+ r, mock, db := testingInf(t, bts)
+ defer cleanup(t, mock, db)
+
+ rows := sqlmock.NewRows([]string{"id", "last_updated"})
+ rows.AddRow(newFed.ID, newFed.LastUpdated)
+ mock.ExpectQuery("INSERT").WillReturnRows(rows)
+
+ f := api.Wrap(Create, nil, nil)
+ w := httptest.NewRecorder()
+ f(w, r)
+
+ if w.Code != http.StatusCreated {
+ t.Errorf("Incorrect response code; want: %d, got: %d",
http.StatusCreated, w.Code)
+ }
+
+ var created tc.CDNFederationV5Response
+ err = json.Unmarshal(w.Body.Bytes(), &created)
+ if err != nil {
+ t.Fatalf("Unmarshaling response: %v", err)
+ }
+
+ if created.Response != newFed {
+ t.Errorf("Didn't create the expected Federation; want: %#v,
got: %#v", newFed, created.Response)
+ }
+}
+
+func TestValidate(t *testing.T) {
+ fed := tc.CDNFederationV5{
+ CName: "test.quest.",
+ TTL: 60,
+ }
+ err := validate(fed)
+ if err != nil {
+ t.Errorf("Unexpected validation error: %v", err)
+ }
+
+ fed.TTL--
+ err = validate(fed)
+ if err == nil {
+ t.Fatal("Expected an error for TTL below minimum, but didn't
get one")
+ }
+ if !strings.Contains(err.Error(), "ttl") {
+ t.Errorf("Expected error message to mention 'ttl': %v", err)
+ }
+
+ fed.TTL = 60
+ fed.CName = "test.quest"
+ err = validate(fed)
+ if err == nil {
+ t.Fatal("Expected an error for a CNAME without a terminating
'.', but didn't get one")
+ }
+ if !strings.Contains(err.Error(), "cname") {
+ t.Errorf("Expected error message to mention 'cname': %v", err)
+ }
+
+}
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go
b/traffic_ops/traffic_ops_golang/routing/routes.go
index 2aa1b328e1..23d70f83c6 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -395,10 +395,11 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
{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},
// 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},
- {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodPut, Path: `cdns/{name}/federations/{id}$`, Handler:
api.UpdateHandler(&cdnfederation.TOCDNFederation{}), RequiredPrivLevel:
auth.PrivLevelAdmin, RequiredPermissions: []string{"FEDERATION:UPDATE",
"FEDERATION:READ", "CDN:READ"}, Authenticated: Authenticated, Middlewares: nil,
ID: 42606546631},
- {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodDelete, Path: `cdns/{name}/federations/{id}$`, Handler:
api.DeleteHandler(&cdnfederation.TOCDNFederation{}), RequiredPrivLevel:
auth.PrivLevelAdmin, RequiredPermissions: []string{"FEDERATION:DELETE",
"FEDERATION:READ", "CDN:READ"}, Authenticated: Authenticated, Middlewares: nil,
ID: 444285290231},
+ {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodGet, Path: `cdns/{name}/federations/?$`, Handler:
api.Wrap(cdnfederation.Read, []string{"name"}, nil), 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.Wrap(cdnfederation.Create, nil, nil), RequiredPrivLevel:
auth.PrivLevelAdmin, RequiredPermissions: []string{"FEDERATION:CREATE",
"FEDERATION:READ, CDN:READ"}, Authenticated: Authenticated, Middlewares: nil,
ID: 495489421931},
+ {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodGet, Path: `cdns/{name}/federations/{id}/?$`, Handler:
api.Wrap(cdnfederation.ReadID, []string{"name"}, []string{"id"}),
RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions:
[]string{"CDN:READ", "FEDERATION:READ", "DELIVERY-SERVICE:READ"},
Authenticated: Authenticated, Middlewares: nil, ID: 48922503232},
+ {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodPut, Path: `cdns/{name}/federations/{id}$`, Handler:
api.Wrap(cdnfederation.Update, nil, []string{"id"}), RequiredPrivLevel:
auth.PrivLevelAdmin, RequiredPermissions: []string{"FEDERATION:UPDATE",
"FEDERATION:READ", "CDN:READ"}, Authenticated: Authenticated, Middlewares: nil,
ID: 42606546631},
+ {Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodDelete, Path: `cdns/{name}/federations/{id}$`, Handler:
api.Wrap(cdnfederation.Delete, nil, []string{"id"}), RequiredPrivLevel:
auth.PrivLevelAdmin, RequiredPermissions: []string{"FEDERATION:DELETE",
"FEDERATION:READ", "CDN:READ"}, Authenticated: Authenticated, Middlewares: nil,
ID: 444285290231},
{Version: api.Version{Major: 5, Minor: 0}, Method:
http.MethodPost, Path: `cdns/{name}/dnsseckeys/ksk/generate$`, Handler:
cdn.GenerateKSK, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions:
[]string{"DNS-SEC:CREATE", "CDN:UPDATE", "CDN:READ"}, Authenticated:
Authenticated, Middlewares: nil, ID: 47292428131},
diff --git a/traffic_ops/traffic_ops_golang/server/servers.go
b/traffic_ops/traffic_ops_golang/server/servers.go
index 718f2d1870..5601df03fb 100644
--- a/traffic_ops/traffic_ops_golang/server/servers.go
+++ b/traffic_ops/traffic_ops_golang/server/servers.go
@@ -674,11 +674,8 @@ func Read(inf *api.APIInfo) (int, error, error) {
version := inf.Version
servers, serverCount, userErr, sysErr, errCode, maxTime :=
getServers(inf.RequestHeaders(), inf.Params, inf.Tx, inf.User, useIMS,
*version, inf.Config.RoleBasedPermissions)
- if useIMS && maxTime != nil {
- inf.SetLastModified(*maxTime)
- }
- if errCode == http.StatusNotModified {
- return inf.WriteNotModifiedResponse([]tc.ServerV5{})
+ if useIMS && maxTime != nil && errCode == http.StatusNotModified {
+ return inf.WriteNotModifiedResponse(*maxTime)
}
if userErr != nil || sysErr != nil {
return errCode, userErr, sysErr
diff --git a/traffic_ops/traffic_ops_golang/util/ims/ims.go
b/traffic_ops/traffic_ops_golang/util/ims/ims.go
index 53dbbbddbe..f53e76ec7c 100644
--- a/traffic_ops/traffic_ops_golang/util/ims/ims.go
+++ b/traffic_ops/traffic_ops_golang/util/ims/ims.go
@@ -2,6 +2,7 @@ package ims
import (
"database/sql"
+ "errors"
"net/http"
"time"
@@ -61,13 +62,13 @@ func TryIfModifiedSinceQuery(tx *sqlx.Tx, h http.Header,
queryValues map[string]
if rows != nil {
defer rows.Close()
}
+ if errors.Is(err, sql.ErrNoRows) {
+ return dontRunSecond, maxTime
+ }
if err != nil {
log.Errorf("Couldn't get the max last updated time:
%v", err)
return runSecond, maxTime
}
- if err == sql.ErrNoRows {
- return dontRunSecond, maxTime
- }
// This should only ever contain one row
if rows.Next() {
v := &LatestTimestamp{}
diff --git a/traffic_ops/v5-client/cdnfederations.go
b/traffic_ops/v5-client/cdnfederations.go
index dcd6bfcb66..c574af79eb 100644
--- a/traffic_ops/v5-client/cdnfederations.go
+++ b/traffic_ops/v5-client/cdnfederations.go
@@ -30,8 +30,8 @@ import (
// CreateCDNFederation creates the given Federation in the CDN with the given
// name.
-func (to *Session) CreateCDNFederation(f tc.CDNFederation, cdnName string,
opts RequestOptions) (tc.CreateCDNFederationResponse, toclientlib.ReqInf,
error) {
- var data tc.CreateCDNFederationResponse
+func (to *Session) CreateCDNFederation(f tc.CDNFederationV5, cdnName string,
opts RequestOptions) (tc.CDNFederationV5Response, toclientlib.ReqInf, error) {
+ var data tc.CDNFederationV5Response
route := "/cdns/" + url.PathEscape(cdnName) + "/federations"
inf, err := to.post(route, opts, f, &data)
return data, inf, err
@@ -39,8 +39,8 @@ func (to *Session) CreateCDNFederation(f tc.CDNFederation,
cdnName string, opts
// GetCDNFederationsByName retrieves all Federations in the CDN with the given
// name.
-func (to *Session) GetCDNFederationsByName(cdnName string, opts
RequestOptions) (tc.CDNFederationResponse, toclientlib.ReqInf, error) {
- var data tc.CDNFederationResponse
+func (to *Session) GetCDNFederations(cdnName string, opts RequestOptions)
(tc.CDNFederationsV5Response, toclientlib.ReqInf, error) {
+ var data tc.CDNFederationsV5Response
route := "/cdns/" + url.PathEscape(cdnName) + "/federations"
inf, err := to.get(route, opts, &data)
return data, inf, err
@@ -48,8 +48,8 @@ func (to *Session) GetCDNFederationsByName(cdnName string,
opts RequestOptions)
// UpdateCDNFederation replaces the Federation with the given ID in the CDN
// with the given name with the provided Federation.
-func (to *Session) UpdateCDNFederation(f tc.CDNFederation, cdnName string, id
int, opts RequestOptions) (tc.UpdateCDNFederationResponse, toclientlib.ReqInf,
error) {
- var data tc.UpdateCDNFederationResponse
+func (to *Session) UpdateCDNFederation(f tc.CDNFederationV5, cdnName string,
id int, opts RequestOptions) (tc.CDNFederationV5Response, toclientlib.ReqInf,
error) {
+ var data tc.CDNFederationV5Response
route := fmt.Sprintf("/cdns/%s/federations/%d",
url.PathEscape(cdnName), id)
inf, err := to.put(route, opts, f, &data)
return data, inf, err
@@ -57,8 +57,8 @@ func (to *Session) UpdateCDNFederation(f tc.CDNFederation,
cdnName string, id in
// DeleteCDNFederation deletes the Federation with the given ID in the CDN
// with the given name.
-func (to *Session) DeleteCDNFederation(cdnName string, id int, opts
RequestOptions) (tc.DeleteCDNFederationResponse, toclientlib.ReqInf, error) {
- var data tc.DeleteCDNFederationResponse
+func (to *Session) DeleteCDNFederation(cdnName string, id int, opts
RequestOptions) (tc.CDNFederationV5Response, toclientlib.ReqInf, error) {
+ var data tc.CDNFederationV5Response
route := fmt.Sprintf("/cdns/%s/federations/%d",
url.PathEscape(cdnName), id)
inf, err := to.del(route, opts, &data)
return data, inf, err