diff --git a/.github/workflows/traffic-ops.yml b/.github/workflows/traffic-ops.yml index 9b08d200d2..ce04ac71a6 100644 --- a/.github/workflows/traffic-ops.yml +++ b/.github/workflows/traffic-ops.yml @@ -202,3 +202,77 @@ jobs: path: ${{ github.workspace }}/traffic_ops/traffic_ops_golang/traffic.ops.log - name: Save Alpine Docker image run: .github/actions/save-alpine-tar/entrypoint.sh save ${{ env.ALPINE_VERSION }} + + APIv5Tests: + if: github.event.pull_request.draft == false + runs-on: ubuntu-latest + + services: + postgres: + image: postgres:13 + env: + POSTGRES_USER: traffic_ops + POSTGRES_PASSWORD: twelve + POSTGRES_DB: traffic_ops + ports: + - 5432:5432 + options: --health-cmd pg_isready --health-interval 10s --health-timeout 5s --health-retries 5 + + smtp: + image: maildev/maildev:2.0.0-beta3 + ports: + - 25:25 + options: >- + --entrypoint=bin/maildev + --user=root + --health-cmd="sh -c \"[[ \$(wget -qO- http://smtp/healthz) == true ]]\"" + -- + maildev/maildev:2.0.0-beta3 + --smtp=25 + --hide-extensions=STARTTLS + --web=80 + + steps: + - name: Checkout + uses: actions/checkout@master + - name: Cache Alpine Docker image + uses: actions/cache@v2 + with: + path: ${{ github.workspace }}/docker-images + key: docker-images/alpine@${{ env.ALPINE_VERSION }}.tar.gz + - name: Import cached Alpine Docker image + run: .github/actions/save-alpine-tar/entrypoint.sh load ${{ env.ALPINE_VERSION }} + - name: Initialize Traffic Ops Database + id: todb + uses: ./.github/actions/todb-init + - name: Initialize Traffic Vault Database + id: tvdb + uses: ./.github/actions/tvdb-init + - name: Check Go Version + run: echo "::set-output name=value::$(cat GO_VERSION)" + id: go-version + - name: Install Go + uses: actions/setup-go@v2 + with: + go-version: ${{ steps.go-version.outputs.value }} + - name: Run API v5 tests + id: v5Tests + if: ${{ steps.todb.outcome == 'success' && always() }} + uses: ./.github/actions/to-integration-tests + with: + version: 5 + smtp_address: 172.17.0.1 + - name: Upload v5 Vault logs + if: ${{ steps.v5Tests.outcome != 'success' && always() }} + uses: actions/upload-artifact@v2 + with: + name: v5 Traffic Vault logs + path: ${{ github.workspace }}/infrastructure/cdn-in-a-box/traffic.vault.logs + - name: Upload v5 Ops logs + if: ${{ steps.v5Tests.outcome != 'success' && always() }} + uses: actions/upload-artifact@v2 + with: + name: v5 Traffic Ops logs + path: ${{ github.workspace }}/traffic_ops/traffic_ops_golang/traffic.ops.log + - name: Save Alpine Docker image + run: .github/actions/save-alpine-tar/entrypoint.sh save ${{ env.ALPINE_VERSION }} diff --git a/CHANGELOG.md b/CHANGELOG.md index c74a5b183a..df848da75e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - [#2101](https://github.com/apache/trafficcontrol/issues/2101) Added the ability to tell if a Delivery Service is the target of another steering DS. - [#6033](https://github.com/apache/trafficcontrol/issues/6033) Added ability to assign multiple server capabilities to a server. - [#7032](https://github.com/apache/trafficcontrol/issues/7032) Add t3c-apply flag to use local ATS version for config generation rather than Server package Parameter, to allow managing the ATS OS package via external tools. See 'man t3c-apply' and 'man t3c-generate' for details. +- Traffic Ops API version 5.0 - [Traffic Monitor] Added logging for `ipv4Availability` and `ipv6Availability` in TM. - [Traffic Ops] Added the `ASN` field in TO Server struct, which provides the ability to query servers by `ASN`. @@ -15,6 +16,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). ### Changed - Traffic Portal now obscures sensitive text in Delivery Service "Raw Remap" fields, private SSL keys, "Header Rewrite" rules, and ILO interface passwords by default. - Traffic Router now uses Traffic Ops API 4.0 by default +- The Traffic Ops Python client now uses Traffic Ops API 4.1 by default. ### Fixed - Traffic Stats: Reuse InfluxDB client handle to prevent potential connection leaks diff --git a/docs/source/api/index.rst b/docs/source/api/index.rst index db4d5cf060..136db1986e 100644 --- a/docs/source/api/index.rst +++ b/docs/source/api/index.rst @@ -22,7 +22,7 @@ The Traffic Ops API provides programmatic access to read and write Traffic Contr How to Read this Documentation ============================== -Each endpoint for each version is on its own page, titled with the request path. The request paths shown on each endpoint's page are - unless otherwise noted - only usable by being appended to the request path prefix ``/api//`` where ```` is the API version being requested. The API versions officially supported as of the time of this writing are 3.0, 3.1, and 4.0. All endpoints are documented as though they were being used in version 3.1 in the version 3 documentation and version 4.0 in the version 4 documentation. If an endpoint or request method of an endpoint is only available after a specific version, that will be noted next to the method or endpoint name. If changes were made to the structure of an endpoint's input or output, the version number and nature of the change will be noted. +Each endpoint for each version is on its own page, titled with the request path. The request paths shown on each endpoint's page are - unless otherwise noted - only usable by being appended to the request path prefix ``/api//`` where ```` is the API version being requested. The API versions officially supported as of the time of this writing are 3.0, 3.1, 4.0, 4.1, and 5.0. All endpoints are documented as though they were being used in version 3.1 in the version 3 documentation, version 4.1 in the version 4 documentation, and version 5.0 in the version 5 documentation. If an endpoint or request method of an endpoint is only available after a specific version, that will be noted next to the method or endpoint name. If changes were made to the structure of an endpoint's input or output, the version number and nature of the change will be noted. Every endpoint is documented with a section for each method, containing the subsections "Request Structure" and "Response Structure" which identify all properties and structure of the Request to and Response from the endpoint. Before these subsections, three key pieces of information will be provided: @@ -332,5 +332,15 @@ API routes available in version 4. v4/* +API V5 Routes +============= +API routes available in version 5. + +.. toctree:: + :maxdepth: 4 + :glob: + + v5/* + .. [1] A cookie obtained by logging in through Traffic Portal can be used to access API endpoints under the Traffic Portal domain name - since it will proxy such requests back to Traffic Ops. This is not recommended in actual deployments, however, because it will involve an extra network connection which could be avoided by simply using the Traffic Ops domain itself. diff --git a/docs/source/api/v4/acme_accounts.rst b/docs/source/api/v4/acme_accounts.rst index 391f521253..0e9b90e8b9 100644 --- a/docs/source/api/v4/acme_accounts.rst +++ b/docs/source/api/v4/acme_accounts.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-acme-accounts: +.. _to-api-v4-acme-accounts: ***************** ``acme_accounts`` diff --git a/docs/source/api/v4/acme_accounts_provider_email.rst b/docs/source/api/v4/acme_accounts_provider_email.rst index 4dd7777d7e..3535af4997 100644 --- a/docs/source/api/v4/acme_accounts_provider_email.rst +++ b/docs/source/api/v4/acme_accounts_provider_email.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-acme-accounts-provider-email: +.. _to-api-v4-acme-accounts-provider-email: **************************************** ``acme_accounts/{{provider}}/{{email}}`` diff --git a/docs/source/api/v4/acme_accounts_providers.rst b/docs/source/api/v4/acme_accounts_providers.rst index 8dfc1c3170..eea9ca96a3 100644 --- a/docs/source/api/v4/acme_accounts_providers.rst +++ b/docs/source/api/v4/acme_accounts_providers.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-acme-accounts-providers: +.. _to-api-v4-acme-accounts-providers: *************************** ``acme_accounts/providers`` diff --git a/docs/source/api/v4/acme_autorenew.rst b/docs/source/api/v4/acme_autorenew.rst index 0ac7171f26..0e9568935b 100644 --- a/docs/source/api/v4/acme_autorenew.rst +++ b/docs/source/api/v4/acme_autorenew.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-acme-autorenew: +.. _to-api-v4-acme-autorenew: ****************** ``acme_autorenew`` diff --git a/docs/source/api/v4/asns.rst b/docs/source/api/v4/asns.rst index cdb37e064e..4dfa127822 100644 --- a/docs/source/api/v4/asns.rst +++ b/docs/source/api/v4/asns.rst @@ -14,7 +14,7 @@ .. -.. _to-api-asns: +.. _to-api-v4-asns: ******** ``asns`` diff --git a/docs/source/api/v4/asns_id.rst b/docs/source/api/v4/asns_id.rst index 72cb4b1a05..6312c46d20 100644 --- a/docs/source/api/v4/asns_id.rst +++ b/docs/source/api/v4/asns_id.rst @@ -14,7 +14,7 @@ .. -.. _to-api-asns-id: +.. _to-api-v4-asns-id: *************** ``asns/{{id}}`` diff --git a/docs/source/api/v4/async_status.rst b/docs/source/api/v4/async_status.rst index 6f1a1e9acc..e22eac5c37 100644 --- a/docs/source/api/v4/async_status.rst +++ b/docs/source/api/v4/async_status.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-async_status: +.. _to-api-v4-async_status: *********************** ``async_status/{{id}}`` diff --git a/docs/source/api/v4/cache_stats.rst b/docs/source/api/v4/cache_stats.rst index 297205b60d..462bfbcc0b 100644 --- a/docs/source/api/v4/cache_stats.rst +++ b/docs/source/api/v4/cache_stats.rst @@ -14,14 +14,14 @@ .. -.. _to-api-cache_stats: +.. _to-api-v4-cache_stats: *************** ``cache_stats`` *************** Retrieves detailed, aggregated statistics for caches in a specific CDN. -.. seealso:: This gives an aggregate of statistics for *all caches* within a particular CDN and time range. For statistics basic statistics from all caches regardless of CDN and at the current time, use :ref:`to-api-caches-stats`. +.. seealso:: This gives an aggregate of statistics for *all caches* within a particular CDN and time range. For statistics basic statistics from all caches regardless of CDN and at the current time, use :ref:`to-api-v4-caches-stats`. ``GET`` ------- @@ -65,7 +65,7 @@ Request Structure | | | Epoch, or in the same, proprietary format as the ``lastUpdated`` fields prevalent throughout the Traffic Ops API | +---------------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ -.. _cache_stats-get-request-example: +.. _cache_stats-v4-get-request-example: .. code-block:: http :caption: Request Example @@ -78,7 +78,7 @@ Request Structure Content Format """""""""""""" -It's important to note in :ref:`cache_stats-get-request-example` the use of a complex "Accept" header. This endpoint accepts two special media types in the "Accept" header that instruct it on how to format the timestamps associated with the returned data. Specifically, Traffic Ops will recognize the special, optional, non-standard parameter of :mimetype:`application/json`: ``timestamp``. The values of this parameter are restricted to one of +It's important to note in :ref:`cache_stats-v4-get-request-example` the use of a complex "Accept" header. This endpoint accepts two special media types in the "Accept" header that instruct it on how to format the timestamps associated with the returned data. Specifically, Traffic Ops will recognize the special, optional, non-standard parameter of :mimetype:`application/json`: ``timestamp``. The values of this parameter are restricted to one of rfc Returned timestamps will be formatted according to :rfc:`3339` (no sub-second precision). diff --git a/docs/source/api/v4/cachegroups.rst b/docs/source/api/v4/cachegroups.rst index 561776d9a2..5cc80bc04f 100644 --- a/docs/source/api/v4/cachegroups.rst +++ b/docs/source/api/v4/cachegroups.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cachegroups: +.. _to-api-v4-cachegroups: *************** ``cachegroups`` @@ -150,7 +150,7 @@ Request Structure :shortName: An abbreviation of the ``name`` :typeId: An integral, unique identifier for the :ref:`Cache Group's Type ` - .. note:: The actual, integral, unique identifiers for these :term:`Types` must first be obtained, generally via :ref:`to-api-types`. + .. note:: The actual, integral, unique identifiers for these :term:`Types` must first be obtained, generally via :ref:`to-api-v4-types`. .. code-block:: http :caption: Request Example diff --git a/docs/source/api/v4/cachegroups_id.rst b/docs/source/api/v4/cachegroups_id.rst index 284e3477fd..c0fe9f1b68 100644 --- a/docs/source/api/v4/cachegroups_id.rst +++ b/docs/source/api/v4/cachegroups_id.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cachegroups-id: +.. _to-api-v4-cachegroups-id: ********************** ``cachegroups/{{ID}}`` @@ -55,7 +55,7 @@ Request Structure :shortName: An abbreviation of the ``name`` :typeId: An integral, unique identifier for the :ref:`Cache Group's Type ` - .. note:: The actual, integral, unique identifiers for these :term:`Types` must first be obtained, generally via :ref:`to-api-types`. + .. note:: The actual, integral, unique identifiers for these :term:`Types` must first be obtained, generally via :ref:`to-api-v4-types`. .. code-block:: http :caption: Request Example diff --git a/docs/source/api/v4/cachegroups_id_deliveryservices.rst b/docs/source/api/v4/cachegroups_id_deliveryservices.rst index d014e9cf1b..8ca74d00c7 100644 --- a/docs/source/api/v4/cachegroups_id_deliveryservices.rst +++ b/docs/source/api/v4/cachegroups_id_deliveryservices.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cachegroups-id-deliveryservices: +.. _to-api-v4-cachegroups-id-deliveryservices: *************************************** ``cachegroups/{{ID}}/deliveryservices`` @@ -91,4 +91,3 @@ Response Structure 2 ] }} - diff --git a/docs/source/api/v4/cachegroups_id_queue_update.rst b/docs/source/api/v4/cachegroups_id_queue_update.rst index ced0e56484..d9cffaa8e7 100644 --- a/docs/source/api/v4/cachegroups_id_queue_update.rst +++ b/docs/source/api/v4/cachegroups_id_queue_update.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cachegroups-id-queue_update: +.. _to-api-v4-cachegroups-id-queue_update: *********************************** ``cachegroups/{{ID}}/queue_update`` diff --git a/docs/source/api/v4/caches_stats.rst b/docs/source/api/v4/caches_stats.rst index 9b2238cff1..9243251f7d 100644 --- a/docs/source/api/v4/caches_stats.rst +++ b/docs/source/api/v4/caches_stats.rst @@ -14,14 +14,14 @@ .. -.. _to-api-caches-stats: +.. _to-api-v4-caches-stats: **************** ``caches/stats`` **************** An API endpoint that returns cache statistics using the :ref:`tm-api`. -.. seealso:: This gives a set of basic statistics for *all* :term:`cache servers` at the current time. For statistics from time ranges and/or aggregated over a specific CDN, use :ref:`to-api-cache_stats`. +.. seealso:: This gives a set of basic statistics for *all* :term:`cache servers` at the current time. For statistics from time ranges and/or aggregated over a specific CDN, use :ref:`to-api-v4-cache_stats`. ``GET`` ======= diff --git a/docs/source/api/v4/cdn_locks.rst b/docs/source/api/v4/cdn_locks.rst index 0bd4619974..37b527e158 100644 --- a/docs/source/api/v4/cdn_locks.rst +++ b/docs/source/api/v4/cdn_locks.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cdn-locks: +.. _to-api-v4-cdn-locks: ***************** ``cdn_locks`` diff --git a/docs/source/api/v4/cdn_notifications.rst b/docs/source/api/v4/cdn_notifications.rst index c056192786..ca37c3b68b 100644 --- a/docs/source/api/v4/cdn_notifications.rst +++ b/docs/source/api/v4/cdn_notifications.rst @@ -14,7 +14,7 @@ .. -.. _to-api-cdn-notifications: +.. _to-api-v4-cdn-notifications: ********************* ``cdn_notifications`` diff --git a/docs/source/api/v4/cdns.rst b/docs/source/api/v4/cdns.rst index 219625ecb3..8e13605df5 100644 --- a/docs/source/api/v4/cdns.rst +++ b/docs/source/api/v4/cdns.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cdns: +.. _to-api-v4-cdns: ******** ``cdns`` diff --git a/docs/source/api/v4/cdns_capacity.rst b/docs/source/api/v4/cdns_capacity.rst index 70560fa8f5..56bd64625e 100644 --- a/docs/source/api/v4/cdns_capacity.rst +++ b/docs/source/api/v4/cdns_capacity.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cdns-capacity: +.. _to-api-v4-cdns-capacity: ***************** ``cdns/capacity`` diff --git a/docs/source/api/v4/cdns_dnsseckeys_generate.rst b/docs/source/api/v4/cdns_dnsseckeys_generate.rst index 243a75fa0e..f050af072b 100644 --- a/docs/source/api/v4/cdns_dnsseckeys_generate.rst +++ b/docs/source/api/v4/cdns_dnsseckeys_generate.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cdns-dnsseckeys-generate: +.. _to-api-v4-cdns-dnsseckeys-generate: **************************** ``cdns/dnsseckeys/generate`` diff --git a/docs/source/api/v4/cdns_dnsseckeys_refresh.rst b/docs/source/api/v4/cdns_dnsseckeys_refresh.rst index 314e750a30..89c78c8734 100644 --- a/docs/source/api/v4/cdns_dnsseckeys_refresh.rst +++ b/docs/source/api/v4/cdns_dnsseckeys_refresh.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cdns-dnsseckeys-refresh: +.. _to-api-v4-cdns-dnsseckeys-refresh: *************************** ``cdns/dnsseckeys/refresh`` diff --git a/docs/source/api/v4/cdns_domains.rst b/docs/source/api/v4/cdns_domains.rst index 4211a1cbc7..9148580e7f 100644 --- a/docs/source/api/v4/cdns_domains.rst +++ b/docs/source/api/v4/cdns_domains.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cdns-domains: +.. _to-api-v4-cdns-domains: **************** ``cdns/domains`` diff --git a/docs/source/api/v4/cdns_health.rst b/docs/source/api/v4/cdns_health.rst index fde114c615..f12bc2050d 100644 --- a/docs/source/api/v4/cdns_health.rst +++ b/docs/source/api/v4/cdns_health.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cdns-health: +.. _to-api-v4-cdns-health: *************** ``cdns/health`` diff --git a/docs/source/api/v4/cdns_id.rst b/docs/source/api/v4/cdns_id.rst index 90d1e20fcf..55b2ebe859 100644 --- a/docs/source/api/v4/cdns_id.rst +++ b/docs/source/api/v4/cdns_id.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cdns-id: +.. _to-api-v4-cdns-id: *************** ``cdns/{{ID}}`` @@ -134,4 +134,3 @@ Response Structure "level": "success" } ]} - diff --git a/docs/source/api/v4/cdns_id_queue_update.rst b/docs/source/api/v4/cdns_id_queue_update.rst index 3f179e6352..fdcd7e57e5 100644 --- a/docs/source/api/v4/cdns_id_queue_update.rst +++ b/docs/source/api/v4/cdns_id_queue_update.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cdns-id-queue_update: +.. _to-api-v4-cdns-id-queue_update: **************************** ``cdns/{{ID}}/queue_update`` diff --git a/docs/source/api/v4/cdns_name_configs_monitoring.rst b/docs/source/api/v4/cdns_name_configs_monitoring.rst index 552be5f98c..132bd2d79c 100644 --- a/docs/source/api/v4/cdns_name_configs_monitoring.rst +++ b/docs/source/api/v4/cdns_name_configs_monitoring.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cdns-name-configs-monitoring: +.. _to-api-v4-cdns-name-configs-monitoring: ************************************ ``cdns/{{name}}/configs/monitoring`` diff --git a/docs/source/api/v4/cdns_name_dnsseckeys_ksk_generate.rst b/docs/source/api/v4/cdns_name_dnsseckeys_ksk_generate.rst index 79fdd9b4ca..e8502dc7e5 100644 --- a/docs/source/api/v4/cdns_name_dnsseckeys_ksk_generate.rst +++ b/docs/source/api/v4/cdns_name_dnsseckeys_ksk_generate.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cdns-name-dnsseckeys-ksk-generate: +.. _to-api-v4-cdns-name-dnsseckeys-ksk-generate: ***************************************** ``cdns/{{name}}/dnsseckeys/ksk/generate`` diff --git a/docs/source/api/v4/cdns_name_federations.rst b/docs/source/api/v4/cdns_name_federations.rst index 2f391c2a10..9b5a8b87bf 100644 --- a/docs/source/api/v4/cdns_name_federations.rst +++ b/docs/source/api/v4/cdns_name_federations.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cdns-name-federations: +.. _to-api-v4-cdns-name-federations: ***************************** ``cdns/{{name}}/federations`` diff --git a/docs/source/api/v4/cdns_name_federations_id.rst b/docs/source/api/v4/cdns_name_federations_id.rst index 4b934f266c..f9ebb96bf4 100644 --- a/docs/source/api/v4/cdns_name_federations_id.rst +++ b/docs/source/api/v4/cdns_name_federations_id.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cdns-name-federations-id: +.. _to-api-v4-cdns-name-federations-id: ************************************ ``cdns/{{name}}/federations/{{ID}}`` diff --git a/docs/source/api/v4/cdns_name_health.rst b/docs/source/api/v4/cdns_name_health.rst index bcf3e6caf9..0d651920f9 100644 --- a/docs/source/api/v4/cdns_name_health.rst +++ b/docs/source/api/v4/cdns_name_health.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cdns-name-health: +.. _to-api-v4-cdns-name-health: ************************ ``cdns/{{name}}/health`` diff --git a/docs/source/api/v4/cdns_name_name.rst b/docs/source/api/v4/cdns_name_name.rst index 0cd153dd7f..6e5f1311c7 100644 --- a/docs/source/api/v4/cdns_name_name.rst +++ b/docs/source/api/v4/cdns_name_name.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cdns-name-name: +.. _to-api-v4-cdns-name-name: ********************** ``cdns/name/{{name}}`` @@ -61,4 +61,3 @@ Response Structure "level": "success" } ]} - diff --git a/docs/source/api/v4/cdns_name_name_dnsseckeys.rst b/docs/source/api/v4/cdns_name_name_dnsseckeys.rst index bfa46790d3..941eb1ea0d 100644 --- a/docs/source/api/v4/cdns_name_name_dnsseckeys.rst +++ b/docs/source/api/v4/cdns_name_name_dnsseckeys.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cdns-name-name-dnsseckeys: +.. _to-api-v4-cdns-name-name-dnsseckeys: ********************************* ``cdns/name/{{name}}/dnsseckeys`` @@ -139,4 +139,3 @@ Response Structure { "response": "Successfully deleted dnssec keys for test" } - diff --git a/docs/source/api/v4/cdns_name_name_sslkeys.rst b/docs/source/api/v4/cdns_name_name_sslkeys.rst index 4418ed583f..ee5e3660c9 100644 --- a/docs/source/api/v4/cdns_name_name_sslkeys.rst +++ b/docs/source/api/v4/cdns_name_name_sslkeys.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cdns-name-name-sslkeys: +.. _to-api-v4-cdns-name-name-sslkeys: ****************************** ``cdns/name/{{name}}/sslkeys`` diff --git a/docs/source/api/v4/cdns_name_snapshot.rst b/docs/source/api/v4/cdns_name_snapshot.rst index 0309995139..03070a17bc 100644 --- a/docs/source/api/v4/cdns_name_snapshot.rst +++ b/docs/source/api/v4/cdns_name_snapshot.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cdns-name-snapshot: +.. _to-api-v4-cdns-name-snapshot: ************************** ``cdns/{{name}}/snapshot`` diff --git a/docs/source/api/v4/cdns_name_snapshot_new.rst b/docs/source/api/v4/cdns_name_snapshot_new.rst index 911f1ee73d..3438e77e6d 100644 --- a/docs/source/api/v4/cdns_name_snapshot_new.rst +++ b/docs/source/api/v4/cdns_name_snapshot_new.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cdns-name-snapshot-new: +.. _to-api-v4-cdns-name-snapshot-new: ****************************** ``cdns/{{name}}/snapshot/new`` diff --git a/docs/source/api/v4/cdns_routing.rst b/docs/source/api/v4/cdns_routing.rst index 1d00ff9c27..b97f7f0660 100644 --- a/docs/source/api/v4/cdns_routing.rst +++ b/docs/source/api/v4/cdns_routing.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cdns-routing: +.. _to-api-v4-cdns-routing: **************** ``cdns/routing`` diff --git a/docs/source/api/v4/consistenthash.rst b/docs/source/api/v4/consistenthash.rst index 3bd31cc891..b0d8f18a60 100644 --- a/docs/source/api/v4/consistenthash.rst +++ b/docs/source/api/v4/consistenthash.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-consistenthash: +.. _to-api-v4-consistenthash: ****************** ``consistenthash`` diff --git a/docs/source/api/v4/coordinates.rst b/docs/source/api/v4/coordinates.rst index 6cda66f3c8..a3f39a32ea 100644 --- a/docs/source/api/v4/coordinates.rst +++ b/docs/source/api/v4/coordinates.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-coordinates: +.. _to-api-v4-coordinates: *************** ``coordinates`` diff --git a/docs/source/api/v4/current_stats.rst b/docs/source/api/v4/current_stats.rst index 467775c846..05a78d758b 100644 --- a/docs/source/api/v4/current_stats.rst +++ b/docs/source/api/v4/current_stats.rst @@ -14,7 +14,7 @@ .. -.. _to-api-current-stats: +.. _to-api-v4-current-stats: ***************** ``current_stats`` diff --git a/docs/source/api/v4/dbdump.rst b/docs/source/api/v4/dbdump.rst index e622cab685..d5090fd060 100644 --- a/docs/source/api/v4/dbdump.rst +++ b/docs/source/api/v4/dbdump.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-dbdump: +.. _to-api-v4-dbdump: ********** ``dbdump`` diff --git a/docs/source/api/v4/deliveryservice_request_comments.rst b/docs/source/api/v4/deliveryservice_request_comments.rst index 49d67a87fc..85ee56c9b8 100644 --- a/docs/source/api/v4/deliveryservice_request_comments.rst +++ b/docs/source/api/v4/deliveryservice_request_comments.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservice_request_comments: +.. _to-api-v4-deliveryservice_request_comments: ************************************ ``deliveryservice_request_comments`` diff --git a/docs/source/api/v4/deliveryservice_requests.rst b/docs/source/api/v4/deliveryservice_requests.rst index bb9ca7a2a8..084164c58b 100644 --- a/docs/source/api/v4/deliveryservice_requests.rst +++ b/docs/source/api/v4/deliveryservice_requests.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservice-requests: +.. _to-api-v4-deliveryservice-requests: **************************** ``deliveryservice_requests`` @@ -194,7 +194,7 @@ The response is an array of representations of :term:`Delivery Service Requests` "status": "draft" }]} -.. _to-api-deliveryservice-requests-post: +.. _to-api-v4-deliveryservice-requests-post: ``POST`` ======== @@ -524,7 +524,7 @@ The response will be a representation of the created :term:`Delivery Service Req ======= Updates an existing :term:`Delivery Service Request`. Note that "closed" :term:`Delivery Service Requests` are uneditable. -.. seealso:: The proper way to change a :term:`Delivery Service Request`'s :ref:`dsr-status` is by using the :ref:`to-api-deliveryservice_requests-id-status` endpoint's ``PUT`` handler. +.. seealso:: The proper way to change a :term:`Delivery Service Request`'s :ref:`dsr-status` is by using the :ref:`to-api-v4-deliveryservice_requests-id-status` endpoint's ``PUT`` handler. :Auth. Required: Yes :Roles Required: "admin", "Federation", "operations", "Portal", or "Steering" diff --git a/docs/source/api/v4/deliveryservice_requests_id_assign.rst b/docs/source/api/v4/deliveryservice_requests_id_assign.rst index a8e14b2aa9..ceab2da19a 100644 --- a/docs/source/api/v4/deliveryservice_requests_id_assign.rst +++ b/docs/source/api/v4/deliveryservice_requests_id_assign.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservice_requests-id-assign: +.. _to-api-v4-deliveryservice_requests-id-assign: ****************************************** ``deliveryservice_requests/{{ID}}/assign`` diff --git a/docs/source/api/v4/deliveryservice_requests_id_status.rst b/docs/source/api/v4/deliveryservice_requests_id_status.rst index a61b58aa34..2238bf30d2 100644 --- a/docs/source/api/v4/deliveryservice_requests_id_status.rst +++ b/docs/source/api/v4/deliveryservice_requests_id_status.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservice_requests-id-status: +.. _to-api-v4-deliveryservice_requests-id-status: ****************************************** ``deliveryservice_requests/{{ID}}/status`` diff --git a/docs/source/api/v4/deliveryservice_stats.rst b/docs/source/api/v4/deliveryservice_stats.rst index 895d02ecc6..9c785d4a34 100644 --- a/docs/source/api/v4/deliveryservice_stats.rst +++ b/docs/source/api/v4/deliveryservice_stats.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservice_stats: +.. _to-api-v4-deliveryservice_stats: ************************* ``deliveryservice_stats`` diff --git a/docs/source/api/v4/deliveryservices.rst b/docs/source/api/v4/deliveryservices.rst index 9996bcf1ed..2e1ff81a73 100644 --- a/docs/source/api/v4/deliveryservices.rst +++ b/docs/source/api/v4/deliveryservices.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservices: +.. _to-api-v4-deliveryservices: ******************** ``deliveryservices`` diff --git a/docs/source/api/v4/deliveryservices_id.rst b/docs/source/api/v4/deliveryservices_id.rst index 3ba01c06f6..c946ee62fa 100644 --- a/docs/source/api/v4/deliveryservices_id.rst +++ b/docs/source/api/v4/deliveryservices_id.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservices-id: +.. _to-api-v4-deliveryservices-id: *************************** ``deliveryservices/{{ID}}`` diff --git a/docs/source/api/v4/deliveryservices_id_capacity.rst b/docs/source/api/v4/deliveryservices_id_capacity.rst index 90f86119cf..7b1bd56a21 100644 --- a/docs/source/api/v4/deliveryservices_id_capacity.rst +++ b/docs/source/api/v4/deliveryservices_id_capacity.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservices-id-capacity: +.. _to-api-v4-deliveryservices-id-capacity: ************************************ ``deliveryservices/{{ID}}/capacity`` diff --git a/docs/source/api/v4/deliveryservices_id_health.rst b/docs/source/api/v4/deliveryservices_id_health.rst index 7ff51196d1..bffe7aa74e 100644 --- a/docs/source/api/v4/deliveryservices_id_health.rst +++ b/docs/source/api/v4/deliveryservices_id_health.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservices-id-health: +.. _to-api-v4-deliveryservices-id-health: ********************************** ``deliveryservices/{{ID}}/health`` diff --git a/docs/source/api/v4/deliveryservices_id_regexes.rst b/docs/source/api/v4/deliveryservices_id_regexes.rst index 4c7d3ad719..dc13e90b07 100644 --- a/docs/source/api/v4/deliveryservices_id_regexes.rst +++ b/docs/source/api/v4/deliveryservices_id_regexes.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservices-id-regexes: +.. _to-api-v4-deliveryservices-id-regexes: *********************************** ``deliveryservices/{{ID}}/regexes`` diff --git a/docs/source/api/v4/deliveryservices_id_regexes_rid.rst b/docs/source/api/v4/deliveryservices_id_regexes_rid.rst index 781a20db4c..2f8340aef0 100644 --- a/docs/source/api/v4/deliveryservices_id_regexes_rid.rst +++ b/docs/source/api/v4/deliveryservices_id_regexes_rid.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservices-id-regexes-rid: +.. _to-api-v4-deliveryservices-id-regexes-rid: ******************************************* ``deliveryservices/{{ID}}/regexes/{{rID}}`` diff --git a/docs/source/api/v4/deliveryservices_id_routing.rst b/docs/source/api/v4/deliveryservices_id_routing.rst index 0d92b7b789..7725063f0f 100644 --- a/docs/source/api/v4/deliveryservices_id_routing.rst +++ b/docs/source/api/v4/deliveryservices_id_routing.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservices-id-routing: +.. _to-api-v4-deliveryservices-id-routing: *********************************** ``deliveryservices/{{ID}}/routing`` diff --git a/docs/source/api/v4/deliveryservices_id_safe.rst b/docs/source/api/v4/deliveryservices_id_safe.rst index c6a529b437..147bc9eda8 100644 --- a/docs/source/api/v4/deliveryservices_id_safe.rst +++ b/docs/source/api/v4/deliveryservices_id_safe.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservices-id-safe: +.. _to-api-v4-deliveryservices-id-safe: ******************************** ``deliveryservices/{{ID}}/safe`` diff --git a/docs/source/api/v4/deliveryservices_id_servers.rst b/docs/source/api/v4/deliveryservices_id_servers.rst index 02e578982a..69a64377f8 100644 --- a/docs/source/api/v4/deliveryservices_id_servers.rst +++ b/docs/source/api/v4/deliveryservices_id_servers.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservices-id-servers: +.. _to-api-v4-deliveryservices-id-servers: *********************************** ``deliveryservices/{{ID}}/servers`` diff --git a/docs/source/api/v4/deliveryservices_id_servers_eligible.rst b/docs/source/api/v4/deliveryservices_id_servers_eligible.rst index f51c34f946..903b661aba 100644 --- a/docs/source/api/v4/deliveryservices_id_servers_eligible.rst +++ b/docs/source/api/v4/deliveryservices_id_servers_eligible.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservices-id-servers-eligible: +.. _to-api-v4-deliveryservices-id-servers-eligible: ******************************************** ``deliveryservices/{{ID}}/servers/eligible`` diff --git a/docs/source/api/v4/deliveryservices_id_urlkeys.rst b/docs/source/api/v4/deliveryservices_id_urlkeys.rst index cd1b51f174..d30db02412 100644 --- a/docs/source/api/v4/deliveryservices_id_urlkeys.rst +++ b/docs/source/api/v4/deliveryservices_id_urlkeys.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservices-id-urlkeys: +.. _to-api-v4-deliveryservices-id-urlkeys: *********************************** ``deliveryservices/{{ID}}/urlkeys`` @@ -21,7 +21,7 @@ ``GET`` ======= -.. seealso:: :ref:`to-api-deliveryservices-xmlid-xmlid-urlkeys` +.. seealso:: :ref:`to-api-v4-deliveryservices-xmlid-xmlid-urlkeys` Retrieves URL signing keys for a :term:`Delivery Service`. @@ -96,7 +96,7 @@ Response Structure ``DELETE`` ========== -.. seealso:: :ref:`to-api-deliveryservices-xmlid-xmlid-urlkeys` +.. seealso:: :ref:`to-api-v4-deliveryservices-xmlid-xmlid-urlkeys` Deletes URL signing keys for a :term:`Delivery Service`. diff --git a/docs/source/api/v4/deliveryservices_regexes.rst b/docs/source/api/v4/deliveryservices_regexes.rst index 2bff8db293..341af3e17a 100644 --- a/docs/source/api/v4/deliveryservices_regexes.rst +++ b/docs/source/api/v4/deliveryservices_regexes.rst @@ -14,7 +14,7 @@ .. -.. _to-api-deliveryservices_regexes: +.. _to-api-v4-deliveryservices_regexes: **************************** ``deliveryservices_regexes`` diff --git a/docs/source/api/v4/deliveryservices_required_capabilities.rst b/docs/source/api/v4/deliveryservices_required_capabilities.rst index 92561630d9..1bf4b4d81a 100644 --- a/docs/source/api/v4/deliveryservices_required_capabilities.rst +++ b/docs/source/api/v4/deliveryservices_required_capabilities.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservices-required-capabilities: +.. _to-api-v4-deliveryservices-required-capabilities: ****************************************** ``deliveryservices_required_capabilities`` diff --git a/docs/source/api/v4/deliveryservices_sslkeys_add.rst b/docs/source/api/v4/deliveryservices_sslkeys_add.rst index 2e68d1c869..9b6f000154 100644 --- a/docs/source/api/v4/deliveryservices_sslkeys_add.rst +++ b/docs/source/api/v4/deliveryservices_sslkeys_add.rst @@ -13,13 +13,13 @@ .. limitations under the License. .. -.. _to-api-deliveryservices-sslkeys-add: +.. _to-api-v4-deliveryservices-sslkeys-add: ******************************** ``deliveryservices/sslkeys/add`` ******************************** -.. seealso:: In most cases it is preferable to allow Traffic Ops to generate the keys via :ref:`to-api-deliveryservices-sslkeys-generate`, rather than uploading them manually using this endpoint. +.. seealso:: In most cases it is preferable to allow Traffic Ops to generate the keys via :ref:`to-api-v4-deliveryservices-sslkeys-generate`, rather than uploading them manually using this endpoint. ``POST`` ======== diff --git a/docs/source/api/v4/deliveryservices_sslkeys_generate.rst b/docs/source/api/v4/deliveryservices_sslkeys_generate.rst index 282e31efc3..1a3f47b376 100644 --- a/docs/source/api/v4/deliveryservices_sslkeys_generate.rst +++ b/docs/source/api/v4/deliveryservices_sslkeys_generate.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservices-sslkeys-generate: +.. _to-api-v4-deliveryservices-sslkeys-generate: ************************************* ``deliveryservices/sslkeys/generate`` diff --git a/docs/source/api/v4/deliveryservices_sslkeys_generate_acme.rst b/docs/source/api/v4/deliveryservices_sslkeys_generate_acme.rst index 9a1b185291..56e882f91e 100644 --- a/docs/source/api/v4/deliveryservices_sslkeys_generate_acme.rst +++ b/docs/source/api/v4/deliveryservices_sslkeys_generate_acme.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservices-sslkeys-generate-acme: +.. _to-api-v4-deliveryservices-sslkeys-generate-acme: ****************************************** ``deliveryservices/sslkeys/generate/acme`` diff --git a/docs/source/api/v4/deliveryservices_sslkeys_generate_letsencrypt.rst b/docs/source/api/v4/deliveryservices_sslkeys_generate_letsencrypt.rst index e4599b3924..5e1eff459c 100644 --- a/docs/source/api/v4/deliveryservices_sslkeys_generate_letsencrypt.rst +++ b/docs/source/api/v4/deliveryservices_sslkeys_generate_letsencrypt.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservices-sslkeys-generate-letsencrypt: +.. _to-api-v4-deliveryservices-sslkeys-generate-letsencrypt: ************************************************* ``deliveryservices/sslkeys/generate/letsencrypt`` diff --git a/docs/source/api/v4/deliveryservices_xmlid_servers.rst b/docs/source/api/v4/deliveryservices_xmlid_servers.rst index d824467e72..961df9107c 100644 --- a/docs/source/api/v4/deliveryservices_xmlid_servers.rst +++ b/docs/source/api/v4/deliveryservices_xmlid_servers.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservices-xmlid-servers: +.. _to-api-v4-deliveryservices-xmlid-servers: *************************************** ``deliveryservices/{{xml_id}}/servers`` diff --git a/docs/source/api/v4/deliveryservices_xmlid_urisignkeys.rst b/docs/source/api/v4/deliveryservices_xmlid_urisignkeys.rst index 9e4f0ca9d7..5d55008602 100644 --- a/docs/source/api/v4/deliveryservices_xmlid_urisignkeys.rst +++ b/docs/source/api/v4/deliveryservices_xmlid_urisignkeys.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservices-xmlid-urisignkeys: +.. _to-api-v4-deliveryservices-xmlid-urisignkeys: ******************************************* ``deliveryservices/{{xml_id}}/urisignkeys`` diff --git a/docs/source/api/v4/deliveryservices_xmlid_xmlid_sslkeys.rst b/docs/source/api/v4/deliveryservices_xmlid_xmlid_sslkeys.rst index 992cb063ce..ad62a4d93d 100644 --- a/docs/source/api/v4/deliveryservices_xmlid_xmlid_sslkeys.rst +++ b/docs/source/api/v4/deliveryservices_xmlid_xmlid_sslkeys.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservices-xmlid-xmlid-sslkeys: +.. _to-api-v4-deliveryservices-xmlid-xmlid-sslkeys: ******************************************** ``deliveryservices/xmlId/{{XMLID}}/sslkeys`` diff --git a/docs/source/api/v4/deliveryservices_xmlid_xmlid_urlkeys.rst b/docs/source/api/v4/deliveryservices_xmlid_xmlid_urlkeys.rst index a1ed4921db..ae284388c4 100644 --- a/docs/source/api/v4/deliveryservices_xmlid_xmlid_urlkeys.rst +++ b/docs/source/api/v4/deliveryservices_xmlid_xmlid_urlkeys.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservices-xmlid-xmlid-urlkeys: +.. _to-api-v4-deliveryservices-xmlid-xmlid-urlkeys: ******************************************** ``deliveryservices/xmlId/{{xmlid}}/urlkeys`` @@ -21,7 +21,7 @@ ``GET`` ======= -.. seealso:: :ref:`to-api-deliveryservices-id-urlkeys` +.. seealso:: :ref:`to-api-v4-deliveryservices-id-urlkeys` Retrieves URL signing keys for a :term:`Delivery Service`. @@ -71,7 +71,7 @@ Response Structure ``DELETE`` ========== -.. seealso:: :ref:`to-api-deliveryservices-id-urlkeys` +.. seealso:: :ref:`to-api-v4-deliveryservices-id-urlkeys` Deletes URL signing keys for a :term:`Delivery Service`. diff --git a/docs/source/api/v4/deliveryservices_xmlid_xmlid_urlkeys_copyfromxmlid_copyfromxmlid.rst b/docs/source/api/v4/deliveryservices_xmlid_xmlid_urlkeys_copyfromxmlid_copyfromxmlid.rst index 0a9505b431..083a30dafd 100644 --- a/docs/source/api/v4/deliveryservices_xmlid_xmlid_urlkeys_copyfromxmlid_copyfromxmlid.rst +++ b/docs/source/api/v4/deliveryservices_xmlid_xmlid_urlkeys_copyfromxmlid_copyfromxmlid.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservices-xmlid-xml_id-urlkeys-copyFrom_xml_id: +.. _to-api-v4-deliveryservices-xmlid-xml_id-urlkeys-copyFrom_xml_id: ******************************************************************************* ``deliveryservices/xmlId/{{xml_id}}/urlkeys/copyFromXmlId/{{copyFrom_xml_id}}`` diff --git a/docs/source/api/v4/deliveryservices_xmlid_xmlid_urlkeys_generate.rst b/docs/source/api/v4/deliveryservices_xmlid_xmlid_urlkeys_generate.rst index 62f2dedab3..7229f676e7 100644 --- a/docs/source/api/v4/deliveryservices_xmlid_xmlid_urlkeys_generate.rst +++ b/docs/source/api/v4/deliveryservices_xmlid_xmlid_urlkeys_generate.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryservices-xmlid-xmlid-urlkeys-generate: +.. _to-api-v4-deliveryservices-xmlid-xmlid-urlkeys-generate: ****************************************************** ``deliveryservices/xmlId/{{xml_id}}/urlkeys/generate`` diff --git a/docs/source/api/v4/deliveryserviceserver.rst b/docs/source/api/v4/deliveryserviceserver.rst index e3c3f94272..59360026be 100644 --- a/docs/source/api/v4/deliveryserviceserver.rst +++ b/docs/source/api/v4/deliveryserviceserver.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryserviceserver: +.. _to-api-v4-deliveryserviceserver: ************************* ``deliveryserviceserver`` diff --git a/docs/source/api/v4/deliveryserviceserver_dsid_serverid.rst b/docs/source/api/v4/deliveryserviceserver_dsid_serverid.rst index f23ee1584f..68cc92e149 100644 --- a/docs/source/api/v4/deliveryserviceserver_dsid_serverid.rst +++ b/docs/source/api/v4/deliveryserviceserver_dsid_serverid.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-deliveryserviceserver-dsid-serverid: +.. _to-api-v4-deliveryserviceserver-dsid-serverid: *********************************************** ``deliveryserviceserver/{{DSID}}/{{serverID}}`` diff --git a/docs/source/api/v4/divisions.rst b/docs/source/api/v4/divisions.rst index 77e6627043..acdfefc5e4 100644 --- a/docs/source/api/v4/divisions.rst +++ b/docs/source/api/v4/divisions.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-divisions: +.. _to-api-v4-divisions: ************* ``divisions`` diff --git a/docs/source/api/v4/divisions_id.rst b/docs/source/api/v4/divisions_id.rst index 4096468513..98f384cf36 100644 --- a/docs/source/api/v4/divisions_id.rst +++ b/docs/source/api/v4/divisions_id.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-divisions-id: +.. _to-api-v4-divisions-id: ******************** ``divisions/{{ID}}`` diff --git a/docs/source/api/v4/federation_resolvers.rst b/docs/source/api/v4/federation_resolvers.rst index 0e14ba97b4..65a3c2eea0 100644 --- a/docs/source/api/v4/federation_resolvers.rst +++ b/docs/source/api/v4/federation_resolvers.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-federation_resolvers: +.. _to-api-v4-federation_resolvers: ************************ ``federation_resolvers`` @@ -114,7 +114,7 @@ Request Structure .. caution:: This field should only ever be an identifier for one of the :term:`Types` "RESOLVE4" or "RESOLVE6", but there is **no protection for this built into Traffic Ops** and therefore **any valid** :term:`Type` **identifier will be silently accepted by Traffic Ops** and so care should be taken to ensure that these :term:`Types` are properly identified. If any :term:`Type` besides "RESOLVE4" or "RESOLVE6" is identified, the resulting resolver *will* **not** *work*. - .. seealso:: :ref:`to-api-types` is the endpoint that can be used to determine the identifier for various :term:`Types` + .. seealso:: :ref:`to-api-v4-types` is the endpoint that can be used to determine the identifier for various :term:`Types` .. code-block:: http :caption: Request Example diff --git a/docs/source/api/v4/federations.rst b/docs/source/api/v4/federations.rst index daabb48c5f..a44c68351b 100644 --- a/docs/source/api/v4/federations.rst +++ b/docs/source/api/v4/federations.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-federations: +.. _to-api-v4-federations: *************** ``federations`` @@ -80,9 +80,9 @@ Response Structure ======== Allows a user to create :term:`Federation` Resolvers for :term:`Delivery Services`, providing the :term:`Delivery Service` is within a CDN that has some associated :term:`Federation`. -.. warning:: Confusingly, this method of this endpoint does **not** create a new :term:`Federation`; to do that, the :ref:`to-api-cdns-name-federations` endpoint must be used. Furthermore, the :term:`Federation` must properly be assigned to a :term:`Delivery Service` using the :ref:`to-api-federations-id-deliveryservices` and assigned to the user creating Resolvers using :ref:`to-api-federations-id-users`. +.. warning:: Confusingly, this method of this endpoint does **not** create a new :term:`Federation`; to do that, the :ref:`to-api-v4-cdns-name-federations` endpoint must be used. Furthermore, the :term:`Federation` must properly be assigned to a :term:`Delivery Service` using the :ref:`to-api-v4-federations-id-deliveryservices` and assigned to the user creating Resolvers using :ref:`to-api-v4-federations-id-users`. -.. seealso:: The :ref:`to-api-federations-id-federation_resolvers` endpoint duplicates this functionality. +.. seealso:: The :ref:`to-api-v4-federations-id-federation_resolvers` endpoint duplicates this functionality. :Auth. Required: Yes :Roles Required: "admin", "Federation", "operations", "Portal", or "Steering" diff --git a/docs/source/api/v4/federations_all.rst b/docs/source/api/v4/federations_all.rst index 53d0e00501..7a3ae5044f 100644 --- a/docs/source/api/v4/federations_all.rst +++ b/docs/source/api/v4/federations_all.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-federations-all: +.. _to-api-v4-federations-all: ******************* ``federations/all`` diff --git a/docs/source/api/v4/federations_id_deliveryservices.rst b/docs/source/api/v4/federations_id_deliveryservices.rst index a1a1f47d5a..a0c67978f2 100644 --- a/docs/source/api/v4/federations_id_deliveryservices.rst +++ b/docs/source/api/v4/federations_id_deliveryservices.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-federations-id-deliveryservices: +.. _to-api-v4-federations-id-deliveryservices: *************************************** ``federations/{{ID}}/deliveryservices`` @@ -172,4 +172,3 @@ Response Structure ], "replace": true }} - diff --git a/docs/source/api/v4/federations_id_deliveryservices_id.rst b/docs/source/api/v4/federations_id_deliveryservices_id.rst index 22035435dd..43bc893b63 100644 --- a/docs/source/api/v4/federations_id_deliveryservices_id.rst +++ b/docs/source/api/v4/federations_id_deliveryservices_id.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-federations-id-deliveryservices-id: +.. _to-api-v4-federations-id-deliveryservices-id: ************************************************ ``federations/{{ID}}/deliveryservices/{{dsID}}`` diff --git a/docs/source/api/v4/federations_id_federation_resolvers.rst b/docs/source/api/v4/federations_id_federation_resolvers.rst index 1bfa79958b..1496ec522f 100644 --- a/docs/source/api/v4/federations_id_federation_resolvers.rst +++ b/docs/source/api/v4/federations_id_federation_resolvers.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-federations-id-federation_resolvers: +.. _to-api-v4-federations-id-federation_resolvers: ******************************************* ``federations/{{ID}}/federation_resolvers`` diff --git a/docs/source/api/v4/federations_id_users.rst b/docs/source/api/v4/federations_id_users.rst index b7d87191f1..8738cf047b 100644 --- a/docs/source/api/v4/federations_id_users.rst +++ b/docs/source/api/v4/federations_id_users.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-federations-id-users: +.. _to-api-v4-federations-id-users: **************************** ``federations/{{ID}}/users`` diff --git a/docs/source/api/v4/federations_id_users_id.rst b/docs/source/api/v4/federations_id_users_id.rst index 4296fa396c..d576e4a41b 100644 --- a/docs/source/api/v4/federations_id_users_id.rst +++ b/docs/source/api/v4/federations_id_users_id.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-federations-id-users-id: +.. _to-api-v4-federations-id-users-id: *************************************** ``federations/{{ID}}/users/{{userID}}`` diff --git a/docs/source/api/v4/isos.rst b/docs/source/api/v4/isos.rst index c24ca44e69..9ad54eb91f 100644 --- a/docs/source/api/v4/isos.rst +++ b/docs/source/api/v4/isos.rst @@ -14,7 +14,7 @@ .. limitations under the License. .. -.. _to-api-isos: +.. _to-api-v4-isos: ******** ``isos`` @@ -53,7 +53,7 @@ Request Structure :ipNetmask: An optional\ [1]_ string specifying the subnet mask of the generated system image :osversionDir: The name of the directory containing the ISO source - .. seealso:: :ref:`to-api-osversions` + .. seealso:: :ref:`to-api-v4-osversions` :rootPass: The password used by the generated system image's ``root`` user @@ -107,4 +107,3 @@ ISO image as a streaming download. Transfer-Encoding: chunked Whole-Content-sha512: sLSVQGrLCQ4hGQhv2reragQHWNi2aKMcz2c/HMAH45tLcZ1LenPyOzWRcRfHUNbV4PEEKOoiTfwE2HlA+WtRIQ== X-Server-Name: traffic_ops_golang/ - diff --git a/docs/source/api/v4/jobs.rst b/docs/source/api/v4/jobs.rst index ed1eaeebd6..f69651ae37 100644 --- a/docs/source/api/v4/jobs.rst +++ b/docs/source/api/v4/jobs.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-jobs: +.. _to-api-v4-jobs: ******** ``jobs`` diff --git a/docs/source/api/v4/letsencrypt_autorenew.rst b/docs/source/api/v4/letsencrypt_autorenew.rst index d79a3673f6..ed6c8c475b 100644 --- a/docs/source/api/v4/letsencrypt_autorenew.rst +++ b/docs/source/api/v4/letsencrypt_autorenew.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-letsencrypt-autorenew: +.. _to-api-v4-letsencrypt-autorenew: ************************* ``letsencrypt/autorenew`` diff --git a/docs/source/api/v4/letsencrypt_dnsrecords.rst b/docs/source/api/v4/letsencrypt_dnsrecords.rst index 1295cff547..1d50f90afc 100644 --- a/docs/source/api/v4/letsencrypt_dnsrecords.rst +++ b/docs/source/api/v4/letsencrypt_dnsrecords.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-letsencrypt-dnsrecord: +.. _to-api-v4-letsencrypt-dnsrecord: ************************** ``letsencrypt/dnsrecords`` diff --git a/docs/source/api/v4/logs.rst b/docs/source/api/v4/logs.rst index c714ebb9bc..4b4151069b 100644 --- a/docs/source/api/v4/logs.rst +++ b/docs/source/api/v4/logs.rst @@ -12,13 +12,13 @@ .. See the License for the specific language governing permissions and .. limitations under the License. .. -.. _to-api-logs: +.. _to-api-v4-logs: ******** ``logs`` ******** -.. note:: This endpoint's responses will contain a cookie (``last_seen_log``) that is used by :ref:`to-api-logs-newcount` to determine the time of last access. Be sure your client uses cookies properly if you intend to use :ref:`to-api-logs-newcount` in concert with this endpoint! +.. note:: This endpoint's responses will contain a cookie (``last_seen_log``) that is used by :ref:`to-api-v4-logs-newcount` to determine the time of last access. Be sure your client uses cookies properly if you intend to use :ref:`to-api-v4-logs-newcount` in concert with this endpoint! ``GET`` ======= diff --git a/docs/source/api/v4/logs_newcount.rst b/docs/source/api/v4/logs_newcount.rst index b74573f09d..ceda9650e6 100644 --- a/docs/source/api/v4/logs_newcount.rst +++ b/docs/source/api/v4/logs_newcount.rst @@ -14,7 +14,7 @@ .. -.. _to-api-logs-newcount: +.. _to-api-v4-logs-newcount: ***************** ``logs/newcount`` @@ -22,9 +22,9 @@ ``GET`` ======= -Gets the number of new changes made to the Traffic Control system - "new" being defined as the last time the client requested either :ref:`to-api-logs` +Gets the number of new changes made to the Traffic Control system - "new" being defined as the last time the client requested either :ref:`to-api-v4-logs` -.. note:: This endpoint's functionality is implemented by the :ref:`to-api-logs` endpoint's response setting cookies for the client to use when requesting _this_ endpoint. Take care that your client respects cookies! +.. note:: This endpoint's functionality is implemented by the :ref:`to-api-v4-logs` endpoint's response setting cookies for the client to use when requesting _this_ endpoint. Take care that your client respects cookies! :Auth. Required: Yes :Roles Required: None diff --git a/docs/source/api/v4/multiple_server_capabilities.rst b/docs/source/api/v4/multiple_server_capabilities.rst index 37f56b1549..fed438bdac 100644 --- a/docs/source/api/v4/multiple_server_capabilities.rst +++ b/docs/source/api/v4/multiple_server_capabilities.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-multiple_server_capabilities: +.. _to-api-v4-multiple_server_capabilities: ******************************** ``multiple_server_capabilities`` diff --git a/docs/source/api/v4/oc_ci_configuration.rst b/docs/source/api/v4/oc_ci_configuration.rst index c497785b98..0f5cb0a994 100644 --- a/docs/source/api/v4/oc_ci_configuration.rst +++ b/docs/source/api/v4/oc_ci_configuration.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-oc-fci-configuration: +.. _to-api-v4-oc-fci-configuration: *********************** ``OC/CI/configuration`` diff --git a/docs/source/api/v4/oc_ci_configuration_host.rst b/docs/source/api/v4/oc_ci_configuration_host.rst index 688e2bf523..07822d803b 100644 --- a/docs/source/api/v4/oc_ci_configuration_host.rst +++ b/docs/source/api/v4/oc_ci_configuration_host.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-oc-fci-configuration-host: +.. _to-api-v4-oc-fci-configuration-host: ******************************** ``OC/CI/configuration/{{host}}`` diff --git a/docs/source/api/v4/oc_ci_configuration_request_id_approved.rst b/docs/source/api/v4/oc_ci_configuration_request_id_approved.rst index 7c4ab4cf71..61b9cb75ef 100644 --- a/docs/source/api/v4/oc_ci_configuration_request_id_approved.rst +++ b/docs/source/api/v4/oc_ci_configuration_request_id_approved.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-oc-fci-configuration-request-id-approved: +.. _to-api-v4-oc-fci-configuration-request-id-approved: *************************************************** ``OC/CI/configuration/request/{{id}}/{{approved}}`` diff --git a/docs/source/api/v4/oc_ci_configuration_requests.rst b/docs/source/api/v4/oc_ci_configuration_requests.rst index cab8c5e20f..5e52337566 100644 --- a/docs/source/api/v4/oc_ci_configuration_requests.rst +++ b/docs/source/api/v4/oc_ci_configuration_requests.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-oc-ci-configuration_requests: +.. _to-api-v4-oc-ci-configuration_requests: ******************************** ``OC/CI/configuration/requests`` diff --git a/docs/source/api/v4/oc_fci_advertisement.rst b/docs/source/api/v4/oc_fci_advertisement.rst index beb3bbc81f..a71588ba0d 100644 --- a/docs/source/api/v4/oc_fci_advertisement.rst +++ b/docs/source/api/v4/oc_fci_advertisement.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-oc-fci-advertisement: +.. _to-api-v4-oc-fci-advertisement: ************************ ``OC/FCI/advertisement`` @@ -135,4 +135,3 @@ Response Structure } ] } - diff --git a/docs/source/api/v4/origins.rst b/docs/source/api/v4/origins.rst index 6003859719..835e940a0c 100644 --- a/docs/source/api/v4/origins.rst +++ b/docs/source/api/v4/origins.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-origins: +.. _to-api-v4-origins: *********** ``origins`` diff --git a/docs/source/api/v4/osversions.rst b/docs/source/api/v4/osversions.rst index e4b4cfb589..a8581e8fc9 100644 --- a/docs/source/api/v4/osversions.rst +++ b/docs/source/api/v4/osversions.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-osversions: +.. _to-api-v4-osversions: ************** ``osversions`` diff --git a/docs/source/api/v4/parameterprofile.rst b/docs/source/api/v4/parameterprofile.rst index 47a41667fb..65f15af9d4 100644 --- a/docs/source/api/v4/parameterprofile.rst +++ b/docs/source/api/v4/parameterprofile.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-parameterprofile: +.. _to-api-v4-parameterprofile: ******************** ``parameterprofile`` diff --git a/docs/source/api/v4/parameters.rst b/docs/source/api/v4/parameters.rst index 90ba968c52..cc25d94ac6 100644 --- a/docs/source/api/v4/parameters.rst +++ b/docs/source/api/v4/parameters.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-parameters: +.. _to-api-v4-parameters: ************** ``parameters`` diff --git a/docs/source/api/v4/parameters_id.rst b/docs/source/api/v4/parameters_id.rst index 24d8b0f9a9..57ef1cdadb 100644 --- a/docs/source/api/v4/parameters_id.rst +++ b/docs/source/api/v4/parameters_id.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-parameters-id: +.. _to-api-v4-parameters-id: ********************* ``parameters/{{ID}}`` diff --git a/docs/source/api/v4/phys_locations.rst b/docs/source/api/v4/phys_locations.rst index fe163bd1f4..2d3d2fcab1 100644 --- a/docs/source/api/v4/phys_locations.rst +++ b/docs/source/api/v4/phys_locations.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-phys_locations: +.. _to-api-v4-phys_locations: ****************** ``phys_locations`` diff --git a/docs/source/api/v4/phys_locations_id.rst b/docs/source/api/v4/phys_locations_id.rst index fff3232c3c..a73a8cdb86 100644 --- a/docs/source/api/v4/phys_locations_id.rst +++ b/docs/source/api/v4/phys_locations_id.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-phys_locations-id: +.. _to-api-v4-phys_locations-id: ************************* ``phys_locations/{{ID}}`` diff --git a/docs/source/api/v4/ping.rst b/docs/source/api/v4/ping.rst index 60176bc475..61699096c2 100644 --- a/docs/source/api/v4/ping.rst +++ b/docs/source/api/v4/ping.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-ping: +.. _to-api-v4-ping: ******** ``ping`` diff --git a/docs/source/api/v4/plugins.rst b/docs/source/api/v4/plugins.rst index 95e24af091..5e1a48ac0c 100644 --- a/docs/source/api/v4/plugins.rst +++ b/docs/source/api/v4/plugins.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-plugins: +.. _to-api-v4-plugins: *********** ``plugins`` diff --git a/docs/source/api/v4/profileparameter.rst b/docs/source/api/v4/profileparameter.rst index cbe652adf5..a27af757bf 100644 --- a/docs/source/api/v4/profileparameter.rst +++ b/docs/source/api/v4/profileparameter.rst @@ -13,12 +13,12 @@ .. limitations under the License. .. -.. _to-api-profileparameter: +.. _to-api-v4-profileparameter: ******************** ``profileparameter`` ******************** -.. seealso:: :ref:`to-api-profileparameters`. +.. seealso:: :ref:`to-api-v4-profileparameters`. ``POST`` ======== diff --git a/docs/source/api/v4/profileparameters.rst b/docs/source/api/v4/profileparameters.rst index 5ca1adb198..e72c3c78d0 100644 --- a/docs/source/api/v4/profileparameters.rst +++ b/docs/source/api/v4/profileparameters.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-profileparameters: +.. _to-api-v4-profileparameters: ********************* ``profileparameters`` diff --git a/docs/source/api/v4/profileparameters_profileID_parameterID.rst b/docs/source/api/v4/profileparameters_profileID_parameterID.rst index a48e20cd2a..b0481f01f6 100644 --- a/docs/source/api/v4/profileparameters_profileID_parameterID.rst +++ b/docs/source/api/v4/profileparameters_profileID_parameterID.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-profileparameters-profileID-parameterID: +.. _to-api-v4-profileparameters-profileID-parameterID: *************************************************** ``profileparameters/{{profileID}}/{{parameterID}}`` diff --git a/docs/source/api/v4/profiles.rst b/docs/source/api/v4/profiles.rst index 2b61218cbc..fd030a7d31 100644 --- a/docs/source/api/v4/profiles.rst +++ b/docs/source/api/v4/profiles.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-profiles: +.. _to-api-v4-profiles: ************ ``profiles`` diff --git a/docs/source/api/v4/profiles_id.rst b/docs/source/api/v4/profiles_id.rst index 3b901d4066..e5ff9d122e 100644 --- a/docs/source/api/v4/profiles_id.rst +++ b/docs/source/api/v4/profiles_id.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-profiles-id: +.. _to-api-v4-profiles-id: ******************* ``profiles/{{ID}}`` diff --git a/docs/source/api/v4/profiles_id_export.rst b/docs/source/api/v4/profiles_id_export.rst index bd28ad230d..0412e6165d 100644 --- a/docs/source/api/v4/profiles_id_export.rst +++ b/docs/source/api/v4/profiles_id_export.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-profiles-id-export: +.. _to-api-v4-profiles-id-export: ************************** ``profiles/{{ID}}/export`` diff --git a/docs/source/api/v4/profiles_id_parameters.rst b/docs/source/api/v4/profiles_id_parameters.rst index da2c84e2ef..6ac42e2ad3 100644 --- a/docs/source/api/v4/profiles_id_parameters.rst +++ b/docs/source/api/v4/profiles_id_parameters.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-profiles-id-parameters: +.. _to-api-v4-profiles-id-parameters: ****************************** ``profiles/{{ID}}/parameters`` diff --git a/docs/source/api/v4/profiles_import.rst b/docs/source/api/v4/profiles_import.rst index 0422314a31..2e3cbb1fe9 100644 --- a/docs/source/api/v4/profiles_import.rst +++ b/docs/source/api/v4/profiles_import.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-profiles-import: +.. _to-api-v4-profiles-import: ******************* ``profiles/import`` @@ -22,7 +22,7 @@ ``POST`` ======== -Imports a :term:`Profile` that was exported via :ref:`to-api-profiles-id-export` +Imports a :term:`Profile` that was exported via :ref:`to-api-v4-profiles-id-export` .. note:: On import of the :term:`Profile` :term:`Parameters` if a :term:`Parameter` already exists with the same :ref:`parameter-name`, :ref:`parameter-config-file` and :ref:`parameter-value` it will link that to the :term:`Profile` instead of creating it. diff --git a/docs/source/api/v4/profiles_name_name_copy_copy.rst b/docs/source/api/v4/profiles_name_name_copy_copy.rst index 2426e545d2..c8ea6ef23e 100644 --- a/docs/source/api/v4/profiles_name_name_copy_copy.rst +++ b/docs/source/api/v4/profiles_name_name_copy_copy.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-profiles-name-name-copy-copy: +.. _to-api-v4-profiles-name-name-copy-copy: **************************************** ``profiles/name/{{name}}/copy/{{copy}}`` diff --git a/docs/source/api/v4/profiles_name_name_parameters.rst b/docs/source/api/v4/profiles_name_name_parameters.rst index 6f3711e9c5..146254e1ba 100644 --- a/docs/source/api/v4/profiles_name_name_parameters.rst +++ b/docs/source/api/v4/profiles_name_name_parameters.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-profiles-name-name-parameters: +.. _to-api-v4-profiles-name-name-parameters: ************************************* ``profiles/name/{{name}}/parameters`` diff --git a/docs/source/api/v4/regions.rst b/docs/source/api/v4/regions.rst index 9b0f0ccffa..056dca40ab 100644 --- a/docs/source/api/v4/regions.rst +++ b/docs/source/api/v4/regions.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-regions: +.. _to-api-v4-regions: *********** ``regions`` @@ -97,7 +97,7 @@ Response Structure } ]} -.. _to-api-regions-post: +.. _to-api-v4-regions-post: ``POST`` ======== diff --git a/docs/source/api/v4/regions_id.rst b/docs/source/api/v4/regions_id.rst index ce7dcb297d..abb3ceb65c 100644 --- a/docs/source/api/v4/regions_id.rst +++ b/docs/source/api/v4/regions_id.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-regions-id: +.. _to-api-v4-regions-id: ****************** ``regions/{{ID}}`` diff --git a/docs/source/api/v4/roles.rst b/docs/source/api/v4/roles.rst index 87bc34db74..8d0e15ad2b 100644 --- a/docs/source/api/v4/roles.rst +++ b/docs/source/api/v4/roles.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-roles: +.. _to-api-v4-roles: ********* ``roles`` diff --git a/docs/source/api/v4/server_capabilities.rst b/docs/source/api/v4/server_capabilities.rst index 048d4899a1..2f8915ea9d 100644 --- a/docs/source/api/v4/server_capabilities.rst +++ b/docs/source/api/v4/server_capabilities.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-server_capabilities: +.. _to-api-v4-server_capabilities: *********************** ``server_capabilities`` diff --git a/docs/source/api/v4/server_server_capabilities.rst b/docs/source/api/v4/server_server_capabilities.rst index 9c37b23983..1fcb139573 100644 --- a/docs/source/api/v4/server_server_capabilities.rst +++ b/docs/source/api/v4/server_server_capabilities.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-server-server-capabilities: +.. _to-api-v4-server-server-capabilities: ****************************** ``server_server_capabilities`` diff --git a/docs/source/api/v4/servercheck.rst b/docs/source/api/v4/servercheck.rst index ec96cbe686..47f6d61e24 100644 --- a/docs/source/api/v4/servercheck.rst +++ b/docs/source/api/v4/servercheck.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-servercheck: +.. _to-api-v4-servercheck: *************** ``servercheck`` @@ -149,4 +149,3 @@ Response Structure ]} .. [1] No roles are required to use this endpoint, however access is controlled by username. Only the reserved user ``extension`` is permitted the use of this endpoint. - diff --git a/docs/source/api/v4/servercheck_extensions.rst b/docs/source/api/v4/servercheck_extensions.rst index 3406dbf4b4..2619d79b8b 100644 --- a/docs/source/api/v4/servercheck_extensions.rst +++ b/docs/source/api/v4/servercheck_extensions.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-servercheck_extensions: +.. _to-api-v4-servercheck_extensions: ************************** ``servercheck/extensions`` diff --git a/docs/source/api/v4/servercheck_extensions_id.rst b/docs/source/api/v4/servercheck_extensions_id.rst index dfaa581846..23f1fde1a6 100644 --- a/docs/source/api/v4/servercheck_extensions_id.rst +++ b/docs/source/api/v4/servercheck_extensions_id.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-servercheck_extensions-id: +.. _to-api-v4-servercheck_extensions-id: ********************************* ``servercheck/extensions/{{ID}}`` diff --git a/docs/source/api/v4/servers.rst b/docs/source/api/v4/servers.rst index 1a57951d0a..313a00bf15 100644 --- a/docs/source/api/v4/servers.rst +++ b/docs/source/api/v4/servers.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-servers: +.. _to-api-v4-servers: *********** ``servers`` @@ -40,7 +40,7 @@ Request Structure | cachegroupName | no | Return only those servers within the :term:`Cache Group` that has this :ref:`cache-group-name` | +----------------+----------+-------------------------------------------------------------------------------------------------------------------+ | dsId | no | Return only those servers assigned to the :term:`Delivery Service` identified by this integral, unique identifier.| - | | | If the Delivery Service has a :term:`Topology` assigned to it, the :ref:`to-api-servers` endpoint will return | + | | | If the Delivery Service has a :term:`Topology` assigned to it, the :ref:`to-api-v4-servers` endpoint will return | | | | each server whose :term:`Cache Group` is associated with a :term:`Topology Node` of that Topology and has the | | | | :term:`Server Capabilities` that are | | | | :term:`required by the Delivery Service ` but excluding | diff --git a/docs/source/api/v4/servers_hostname_update.rst b/docs/source/api/v4/servers_hostname_update.rst index cde4c32ff3..cfea9f8142 100644 --- a/docs/source/api/v4/servers_hostname_update.rst +++ b/docs/source/api/v4/servers_hostname_update.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-servers-hostname-update: +.. _to-api-v4-servers-hostname-update: ************************************* ``servers/{{HostName-Or-ID}}/update`` diff --git a/docs/source/api/v4/servers_hostname_update_status.rst b/docs/source/api/v4/servers_hostname_update_status.rst index 0ea35732b9..5c13dbd3a9 100644 --- a/docs/source/api/v4/servers_hostname_update_status.rst +++ b/docs/source/api/v4/servers_hostname_update_status.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-servers-hostname-update_status: +.. _to-api-v4-servers-hostname-update_status: ************************************** ``servers/{{hostname}}/update_status`` @@ -67,7 +67,7 @@ Each object in the returned array\ [#uniqueness]_ will contain the following fie :revalApplyTime: The last time a content invalidation/revalidation request was applied by this server. This field defaults to standard epoch :status: The name of the status of this server - .. seealso:: :ref:`health-proto` gives more information on how these statuses are used, and the ``GET`` method of the :ref:`to-api-statuses` endpoint can be used to retrieve information about all server statuses configured in Traffic Ops. + .. seealso:: :ref:`health-proto` gives more information on how these statuses are used, and the ``GET`` method of the :ref:`to-api-v4-statuses` endpoint can be used to retrieve information about all server statuses configured in Traffic Ops. :upd_pending: ``true`` if the server has pending updates, ``false`` otherwise :use_reval_pending: A boolean which tells :term:`ORT` whether or not this version of Traffic Ops should use pending :term:`Content Invalidation Jobs` diff --git a/docs/source/api/v4/servers_id.rst b/docs/source/api/v4/servers_id.rst index e2a005b6ff..881323b709 100644 --- a/docs/source/api/v4/servers_id.rst +++ b/docs/source/api/v4/servers_id.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-servers-id: +.. _to-api-v4-servers-id: ****************** ``servers/{{ID}}`` diff --git a/docs/source/api/v4/servers_id_deliveryservices.rst b/docs/source/api/v4/servers_id_deliveryservices.rst index a04d7fcf2c..3f584811c1 100644 --- a/docs/source/api/v4/servers_id_deliveryservices.rst +++ b/docs/source/api/v4/servers_id_deliveryservices.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-servers-id-deliveryservices: +.. _to-api-v4-servers-id-deliveryservices: *********************************** ``servers/{{ID}}/deliveryservices`` diff --git a/docs/source/api/v4/servers_id_queue_update.rst b/docs/source/api/v4/servers_id_queue_update.rst index ff28ec0961..c059472cde 100644 --- a/docs/source/api/v4/servers_id_queue_update.rst +++ b/docs/source/api/v4/servers_id_queue_update.rst @@ -13,12 +13,12 @@ .. limitations under the License. .. -.. _to-api-servers-id-queue_update: +.. _to-api-v4-servers-id-queue_update: ******************************* ``servers/{{ID}}/queue_update`` ******************************* -.. caution:: In the vast majority of cases, it is advisable that the ``PUT`` method of the :ref:`to-api-servers-id` endpoint be used instead. +.. caution:: In the vast majority of cases, it is advisable that the ``PUT`` method of the :ref:`to-api-v4-servers-id` endpoint be used instead. ``POST`` ======== diff --git a/docs/source/api/v4/servers_id_status.rst b/docs/source/api/v4/servers_id_status.rst index 40f7171a0b..a07e537690 100644 --- a/docs/source/api/v4/servers_id_status.rst +++ b/docs/source/api/v4/servers_id_status.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-servers-id-status: +.. _to-api-v4-servers-id-status: ************************* ``servers/{{ID}}/status`` diff --git a/docs/source/api/v4/service_categories.rst b/docs/source/api/v4/service_categories.rst index a65a08677b..24e1b71f39 100644 --- a/docs/source/api/v4/service_categories.rst +++ b/docs/source/api/v4/service_categories.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-service-categories: +.. _to-api-v4-service-categories: ********************** ``service_categories`` diff --git a/docs/source/api/v4/service_categories_name.rst b/docs/source/api/v4/service_categories_name.rst index b739ca3a19..ecbedd619a 100644 --- a/docs/source/api/v4/service_categories_name.rst +++ b/docs/source/api/v4/service_categories_name.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-service-categories-name: +.. _to-api-v4-service-categories-name: ******************************* ``service_categories/{{name}}`` diff --git a/docs/source/api/v4/snapshot.rst b/docs/source/api/v4/snapshot.rst index b0a7001bcc..53e0013ae7 100644 --- a/docs/source/api/v4/snapshot.rst +++ b/docs/source/api/v4/snapshot.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-snapshot: +.. _to-api-v4-snapshot: ************ ``snapshot`` @@ -21,8 +21,8 @@ ``PUT`` ======= -Performs a CDN :term:`Snapshot`. Effectively, this propagates the new *configuration* of the CDN to its *operating state*, which replaces the output of the :ref:`to-api-cdns-name-snapshot` endpoint with the output of the :ref:`to-api-cdns-name-snapshot-new` endpoint. -This also changes the output of the :ref:`to-api-cdns-name-configs-monitoring` endpoint since that endpoint returns the latest monitoring information from the *operating state*. +Performs a CDN :term:`Snapshot`. Effectively, this propagates the new *configuration* of the CDN to its *operating state*, which replaces the output of the :ref:`to-api-v4-cdns-name-snapshot` endpoint with the output of the :ref:`to-api-v4-cdns-name-snapshot-new` endpoint. +This also changes the output of the :ref:`to-api-v4-cdns-name-configs-monitoring` endpoint since that endpoint returns the latest monitoring information from the *operating state*. .. Note:: By default, snapshotting the CDN also deletes all HTTPS certificates for every :term:`Delivery Service` which has been deleted since the last :term:`Snapshot`. In order to disable this behavior, set ``disable_auto_cert_deletion`` in :ref:`cdn.conf` to ``true``. diff --git a/docs/source/api/v4/sslkey_expirations.rst b/docs/source/api/v4/sslkey_expirations.rst index f299c87f89..21083ba187 100644 --- a/docs/source/api/v4/sslkey_expirations.rst +++ b/docs/source/api/v4/sslkey_expirations.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-sslkey_expirations: +.. _to-api-v4-sslkey_expirations: ********************** ``sslkey_expirations`` diff --git a/docs/source/api/v4/staticdnsentries.rst b/docs/source/api/v4/staticdnsentries.rst index 04a8e6280c..b392f30fc5 100644 --- a/docs/source/api/v4/staticdnsentries.rst +++ b/docs/source/api/v4/staticdnsentries.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-staticdnsentries: +.. _to-api-v4-staticdnsentries: ******************** ``staticdnsentries`` diff --git a/docs/source/api/v4/stats_summary.rst b/docs/source/api/v4/stats_summary.rst index 1d8dd45d89..9010b300fd 100644 --- a/docs/source/api/v4/stats_summary.rst +++ b/docs/source/api/v4/stats_summary.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-stats-summary: +.. _to-api-v4-stats-summary: ***************** ``stats_summary`` diff --git a/docs/source/api/v4/statuses.rst b/docs/source/api/v4/statuses.rst index 02833d4aad..fb3d8207e3 100644 --- a/docs/source/api/v4/statuses.rst +++ b/docs/source/api/v4/statuses.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-statuses: +.. _to-api-v4-statuses: ************ ``statuses`` diff --git a/docs/source/api/v4/statuses_id.rst b/docs/source/api/v4/statuses_id.rst index a48d790bc9..28d6d2d3bb 100644 --- a/docs/source/api/v4/statuses_id.rst +++ b/docs/source/api/v4/statuses_id.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-statuses-id: +.. _to-api-v4-statuses-id: ********************* ``statuses/{{ID}}`` diff --git a/docs/source/api/v4/steering.rst b/docs/source/api/v4/steering.rst index 8c36fe463a..a1c94a6edf 100644 --- a/docs/source/api/v4/steering.rst +++ b/docs/source/api/v4/steering.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-steering: +.. _to-api-v4-steering: ************ ``steering`` diff --git a/docs/source/api/v4/steering_id_targets.rst b/docs/source/api/v4/steering_id_targets.rst index 890248b4be..fe83fe4fa9 100644 --- a/docs/source/api/v4/steering_id_targets.rst +++ b/docs/source/api/v4/steering_id_targets.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-steering-id-targets: +.. _to-api-v4-steering-id-targets: *************************** ``steering/{{ID}}/targets`` diff --git a/docs/source/api/v4/steering_id_targets_targetID.rst b/docs/source/api/v4/steering_id_targets_targetID.rst index ec036fbfbf..60774cb147 100644 --- a/docs/source/api/v4/steering_id_targets_targetID.rst +++ b/docs/source/api/v4/steering_id_targets_targetID.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-steering-id-targets-targetID: +.. _to-api-v4-steering-id-targets-targetID: **************************************** ``steering/{{ID}}/targets/{{targetID}}`` diff --git a/docs/source/api/v4/system_info.rst b/docs/source/api/v4/system_info.rst index 2531a5e8f6..a4e5fe83cd 100644 --- a/docs/source/api/v4/system_info.rst +++ b/docs/source/api/v4/system_info.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-system-info: +.. _to-api-v4-system-info: *************** ``system/info`` diff --git a/docs/source/api/v4/tenants.rst b/docs/source/api/v4/tenants.rst index 2b15f361b5..ecabc5ce34 100644 --- a/docs/source/api/v4/tenants.rst +++ b/docs/source/api/v4/tenants.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-tenants: +.. _to-api-v4-tenants: *********** ``tenants`` diff --git a/docs/source/api/v4/tenants_id.rst b/docs/source/api/v4/tenants_id.rst index 046c4cb0af..3acf5f3d54 100644 --- a/docs/source/api/v4/tenants_id.rst +++ b/docs/source/api/v4/tenants_id.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-tenants-id: +.. _to-api-v4-tenants-id: ****************** ``tenants/{{ID}}`` diff --git a/docs/source/api/v4/topologies.rst b/docs/source/api/v4/topologies.rst index 8ad3a572b0..b28cdedcab 100644 --- a/docs/source/api/v4/topologies.rst +++ b/docs/source/api/v4/topologies.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-topologies: +.. _to-api-v4-topologies: ************** ``topologies`` diff --git a/docs/source/api/v4/topologies_name_queue_update.rst b/docs/source/api/v4/topologies_name_queue_update.rst index 904dcdd9c9..5667341c56 100644 --- a/docs/source/api/v4/topologies_name_queue_update.rst +++ b/docs/source/api/v4/topologies_name_queue_update.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-topologies-name-queue_update: +.. _to-api-v4-topologies-name-queue_update: ************************************ ``topologies/{{name}}/queue_update`` diff --git a/docs/source/api/v4/types.rst b/docs/source/api/v4/types.rst index a6592657e1..cb95cceabd 100644 --- a/docs/source/api/v4/types.rst +++ b/docs/source/api/v4/types.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-types: +.. _to-api-v4-types: ********* ``types`` diff --git a/docs/source/api/v4/types_id.rst b/docs/source/api/v4/types_id.rst index 2a8a14baac..2bda381ddc 100644 --- a/docs/source/api/v4/types_id.rst +++ b/docs/source/api/v4/types_id.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-types-id: +.. _to-api-v4-types-id: **************** ``types/{{ID}}`` diff --git a/docs/source/api/v4/user_current.rst b/docs/source/api/v4/user_current.rst index b1e8e16c85..ac2191b6ce 100644 --- a/docs/source/api/v4/user_current.rst +++ b/docs/source/api/v4/user_current.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-user-current: +.. _to-api-v4-user-current: **************** ``user/current`` @@ -21,7 +21,7 @@ ``GET`` ======= -.. caution:: As a username is needed to log in, any administrator or application must necessarily know the current username at any given time. Thus it's generally better to use the ``username`` query parameter of a ``GET`` request to :ref:`to-api-users` instead. +.. caution:: As a username is needed to log in, any administrator or application must necessarily know the current username at any given time. Thus it's generally better to use the ``username`` query parameter of a ``GET`` request to :ref:`to-api-v4-users` instead. Retrieves the details of the authenticated user. @@ -67,7 +67,7 @@ Response Structure :phoneNumber: The user's phone number :postalCode: The postal code of the area in which the user resides :publicSshKey: The user's public key used for the SSH protocol -:registrationSent: If the user was created using the :ref:`to-api-users-register` endpoint, this will be the date and time at which the registration email was sent - otherwise it will be ``null`` +:registrationSent: If the user was created using the :ref:`to-api-v4-users-register` endpoint, this will be the date and time at which the registration email was sent - otherwise it will be ``null`` :role: The name of the :term:`Role` assigned to this user :stateOrProvince: The name of the state or province where this user resides :tenant: The name of the :term:`Tenant` to which this user belongs @@ -126,7 +126,7 @@ Response Structure ``PUT`` ======= -.. warning:: Assuming the current user's integral, unique identifier is known, it's generally better to use the ``PUT`` method of the :ref:`to-api-users` instead. +.. warning:: Assuming the current user's integral, unique identifier is known, it's generally better to use the ``PUT`` method of the :ref:`to-api-v4-users` instead. .. warning:: Users that login via LDAP pass-back cannot be modified @@ -233,7 +233,7 @@ Response Structure :phoneNumber: The user's phone number :postalCode: The postal code of the area in which the user resides :publicSshKey: The user's public key used for the SSH protocol -:registrationSent: If the user was created using the :ref:`to-api-users-register` endpoint, this will be the date and time at which the registration email was sent - otherwise it will be ``null`` +:registrationSent: If the user was created using the :ref:`to-api-v4-users-register` endpoint, this will be the date and time at which the registration email was sent - otherwise it will be ``null`` :role: The name of the :term:`Role` assigned to this user :stateOrProvince: The name of the state or province where this user resides :tenant: The name of the :term:`Tenant` to which this user belongs diff --git a/docs/source/api/v4/user_login.rst b/docs/source/api/v4/user_login.rst index e2ef28b991..50a395043d 100644 --- a/docs/source/api/v4/user_login.rst +++ b/docs/source/api/v4/user_login.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-user-login: +.. _to-api-v4-user-login: ************** ``user/login`` diff --git a/docs/source/api/v4/user_login_oauth.rst b/docs/source/api/v4/user_login_oauth.rst index 90a2db6b6d..d4d864a629 100644 --- a/docs/source/api/v4/user_login_oauth.rst +++ b/docs/source/api/v4/user_login_oauth.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-user-login-oauth: +.. _to-api-v4-user-login-oauth: ******************** ``user/login/oauth`` diff --git a/docs/source/api/v4/user_login_token.rst b/docs/source/api/v4/user_login_token.rst index d5fb2502cd..8a09a1491f 100644 --- a/docs/source/api/v4/user_login_token.rst +++ b/docs/source/api/v4/user_login_token.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-user-login-token: +.. _to-api-v4-user-login-token: ******************** ``user/login/token`` @@ -21,7 +21,7 @@ ``POST`` ======== -Authentication of a user using a token. Normally, the token is obtained via a call to either :ref:`to-api-user-reset_password` or :ref:`to-api-users-register`. +Authentication of a user using a token. Normally, the token is obtained via a call to either :ref:`to-api-v4-user-reset_password` or :ref:`to-api-v4-users-register`. :Auth. Required: No :Roles Required: None diff --git a/docs/source/api/v4/user_logout.rst b/docs/source/api/v4/user_logout.rst index c6efff0cd1..68bc1b3b43 100644 --- a/docs/source/api/v4/user_logout.rst +++ b/docs/source/api/v4/user_logout.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-user-logout: +.. _to-api-v4-user-logout: *************** ``user/logout`` diff --git a/docs/source/api/v4/user_reset_password.rst b/docs/source/api/v4/user_reset_password.rst index 6b6bedcbee..41b3a4122c 100644 --- a/docs/source/api/v4/user_reset_password.rst +++ b/docs/source/api/v4/user_reset_password.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-user-reset_password: +.. _to-api-v4-user-reset_password: *********************** ``user/reset_password`` diff --git a/docs/source/api/v4/users.rst b/docs/source/api/v4/users.rst index aa8f8cd40a..c78e9add4e 100644 --- a/docs/source/api/v4/users.rst +++ b/docs/source/api/v4/users.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-users: +.. _to-api-v4-users: ********* ``users`` @@ -91,7 +91,7 @@ Response Structure :phoneNumber: The user's phone number :postalCode: The postal code of the area in which the user resides :publicSshKey: The user's public key used for the SSH protocol -:registrationSent: If the user was created using the :ref:`to-api-users-register` endpoint, this will be the date and time at which the registration email was sent - otherwise it will be ``null`` +:registrationSent: If the user was created using the :ref:`to-api-v4-users-register` endpoint, this will be the date and time at which the registration email was sent - otherwise it will be ``null`` :role: The name of the role assigned to this user :stateOrProvince: The name of the state or province where this user resides :tenant: The name of the tenant to which this user belongs @@ -238,7 +238,7 @@ Response Structure :phoneNumber: The user's phone number :postalCode: The postal code of the area in which the user resides :publicSshKey: The user's public key used for the SSH protocol -:registrationSent: If the user was created using the :ref:`to-api-users-register` endpoint, this will be the date and time at which the registration email was sent - otherwise it will be ``null`` +:registrationSent: If the user was created using the :ref:`to-api-v4-users-register` endpoint, this will be the date and time at which the registration email was sent - otherwise it will be ``null`` :role: The name of the role assigned to this user :stateOrProvince: The name of the state or province where this user resides :tenant: The name of the tenant to which this user belongs diff --git a/docs/source/api/v4/users_id.rst b/docs/source/api/v4/users_id.rst index be49381bf1..a5dc2c8851 100644 --- a/docs/source/api/v4/users_id.rst +++ b/docs/source/api/v4/users_id.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-users-id: +.. _to-api-v4-users-id: **************** ``users/{{ID}}`` @@ -71,7 +71,7 @@ Response Structure :phoneNumber: The user's phone number :postalCode: The postal code of the area in which the user resides :publicSshKey: The user's public key used for the SSH protocol -:registrationSent: If the user was created using the :ref:`to-api-users-register` endpoint, this will be the date and time at which the registration email was sent - otherwise it will be ``null`` +:registrationSent: If the user was created using the :ref:`to-api-v4-users-register` endpoint, this will be the date and time at which the registration email was sent - otherwise it will be ``null`` :role: The name of the role assigned to this user :stateOrProvince: The name of the state or province where this user resides :tenant: The name of the tenant to which this user belongs @@ -230,7 +230,7 @@ Response Structure :phoneNumber: The user's phone number :postalCode: The postal code of the area in which the user resides :publicSshKey: The user's public key used for the SSH protocol -:registrationSent: If the user was created using the :ref:`to-api-users-register` endpoint, this will be the date and time at which the registration email was sent - otherwise it will be ``null`` +:registrationSent: If the user was created using the :ref:`to-api-v4-users-register` endpoint, this will be the date and time at which the registration email was sent - otherwise it will be ``null`` :role: The name of the role assigned to this user :stateOrProvince: The name of the state or province where this user resides :tenant: The name of the tenant to which this user belongs diff --git a/docs/source/api/v4/users_register.rst b/docs/source/api/v4/users_register.rst index 376d2cd022..97ca295141 100644 --- a/docs/source/api/v4/users_register.rst +++ b/docs/source/api/v4/users_register.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-users-register: +.. _to-api-v4-users-register: ****************** ``users/register`` diff --git a/docs/source/api/v4/vault_ping.rst b/docs/source/api/v4/vault_ping.rst index e5d86ee41d..c07d390492 100644 --- a/docs/source/api/v4/vault_ping.rst +++ b/docs/source/api/v4/vault_ping.rst @@ -12,7 +12,7 @@ .. See the License for the specific language governing permissions and .. limitations under the License. .. -.. _to-api-vault-ping: +.. _to-api-v4-vault-ping: ************** ``vault/ping`` diff --git a/docs/source/api/v5/about.rst b/docs/source/api/v5/about.rst new file mode 100644 index 0000000000..7841e66921 --- /dev/null +++ b/docs/source/api/v5/about.rst @@ -0,0 +1,80 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-about: + +*********** +``about`` +*********** + +``GET`` +======= + +Returns info about the Traffic Ops build that is currently running, generated at startup. The output will be the same until the Traffic Ops :ref:`version changes `. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: None +:Response Type: Object + +Request Structure +----------------- +No parameters available. + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/about HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + +Response Structure +------------------ +:commitHash: The `Git `_ commit hash that Traffic Ops was built at. +:commits: The number of commits in the branch of the commit that Traffic Ops was built at, including that commit. Calculated by running ``git rev-list HEAD | wc -l``. +:goVersion: The version of `Go `_ that was used to build Traffic Ops. +:release: The major version of CentOS or Red Hat Enterprise Linux that the build environment was running. +:name: The human-readable name of the `RPM `_ file. +:RPMVersion: The entire name of the RPM file, excluding the file extension. +:Version: The version of :abbr:`ATC (Apache Traffic Control)` that this version of Traffic Control belongs to. + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 24 Feb 2020 19:35:28 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: 7SVQsddCUVRs+sineziRGR6OyMli7XLZbjxyMQgW6E506bh5thMOuttPFT7aJckDcgT45PlhexycwlApOHI4Vw== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 24 Feb 2020 18:35:28 GMT + Content-Length: 145 + + { + "commitHash": "1c9a2e9c", + "commits": "10555", + "goVersion": "go1.11.13", + "release": "el7", + "name": "traffic_ops", + "RPMVersion": "traffic_ops-4.0.0-10555.1c9a2e9c.el7", + "Version": "4.0.0" + } diff --git a/docs/source/api/v5/acme_accounts.rst b/docs/source/api/v5/acme_accounts.rst new file mode 100644 index 0000000000..cf33837496 --- /dev/null +++ b/docs/source/api/v5/acme_accounts.rst @@ -0,0 +1,202 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-acme-accounts: + +***************** +``acme_accounts`` +***************** + +.. versionadded:: 3.1 + +``GET`` +======= +Gets information for all :term:`ACME Account` s. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: ACME:READ +:Response Type: Array + +Request Structure +----------------- +No parameters available + + +Response Structure +------------------ +:email: The email connected to the :term:`ACME Account`. +:privateKey: The private key connected to the :term:`ACME Account`. +:uri: The URI for the :term:`ACME Account`. Differs per provider. +:provider: The :abbr:`ACME (Automatic Certificate Management Environment)` provider. + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Content-Type: application/json + + { "response": [ + { + "email": "sample@example.com", + "privateKey": "-----BEGIN RSA PRIVATE KEY-----\nSampleKey\n-----END RSA PRIVATE KEY-----\n", + "uri": "https://acme.example.com/acct/1", + "provider": "Lets Encrypt" + } + ]} + + +``POST`` +======== +Creates a new :term:`ACME Account`. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: ACME:CREATE, ACME:READ +:Response Type: Object + +Request Structure +----------------- +The request body must be a single :term:`ACME Account` object with the following keys: + +:email: The email connected to the :term:`ACME Account`. +:privateKey: The private key connected to the :term:`ACME Account`. +:uri: The URI for the :term:`ACME Account`. Differs per provider. +:provider: The :abbr:`ACME (Automatic Certificate Management Environment)` provider. + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/acme_accounts HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 181 + Content-Type: application/json + + { + "email": "sample@example.com", + "privateKey": "-----BEGIN RSA PRIVATE KEY-----\nSampleKey\n-----END RSA PRIVATE KEY-----\n", + "uri": "https://acme.example.com/acct/1", + "provider": "Lets Encrypt" + } + +Response Structure +------------------ +:email: The email connected to the :term:`ACME Account`. +:privateKey: The private key connected to the :term:`ACME Account`. +:uri: The URI for the :term:`ACME Account`. Differs per provider. +:provider: The :abbr:`ACME (Automatic Certificate Management Environment)` provider. + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 201 Created + 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=/; Expires=Mon, 10 Dec 2020 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: eQrl48zWids0kDpfCYmmtYMpegjnFxfOVvlBYxxLSfp7P7p6oWX4uiC+/Cfh2X9i3G+MQ36eH95gukJqOBOGbQ== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 05 Dec 2018 19:18:21 GMT + Content-Length: 253 + + { "alerts": [ + { + "text": "Acme account created", + "level":"success" + } + ], + "response": { + "email": "sample@example.com", + "privateKey": "-----BEGIN RSA PRIVATE KEY-----\nSampleKey\n-----END RSA PRIVATE KEY-----\n", + "uri": "https://acme.example.com/acct/1", + "provider": "Lets Encrypt" + }} + + +``PUT`` +======= +Updates an existing :term:`ACME Account`. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: ACME:UPDATE, ACME:READ +:Response Type: Object + +Request Structure +----------------- +The request body must be a single :term:`ACME Account` object with the following keys: + +:email: The email connected to the :term:`ACME Account`. +:privateKey: The private key connected to the :term:`ACME Account`. +:uri: The URI for the :term:`ACME Account`. Differs per provider. +:provider: The :abbr:`ACME (Automatic Certificate Management Environment)` provider. + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/acme_accounts HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 181 + Content-Type: application/json + + { + "email": "sample@example.com", + "privateKey": "-----BEGIN RSA PRIVATE KEY-----\nSampleKey\n-----END RSA PRIVATE KEY-----\n", + "uri": "https://acme.example.com/acct/1", + "provider": "Lets Encrypt" + } + +Response Structure +------------------ +:email: The email connected to the :term:`ACME Account`. +:privateKey: The private key connected to the :term:`ACME Account`. +:uri: The URI for the :term:`ACME Account`. Differs per provider. +:provider: The :abbr:`ACME (Automatic Certificate Management Environment)` provider. + +.. 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=/; Expires=Mon, 10 Dec 2020 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: eQrl48zWids0kDpfCYmmtYMpegjnFxfOVvlBYxxLSfp7P7p6oWX4uiC+/Cfh2X9i3G+MQ36eH95gukJqOBOGbQ== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 05 Dec 2018 19:18:21 GMT + Content-Length: 253 + + { "alerts": [ + { + "text": "Acme account updated", + "level":"success" + } + ], + "response": { + "email": "sample@example.com", + "privateKey": "-----BEGIN RSA PRIVATE KEY-----\nSampleKey\n-----END RSA PRIVATE KEY-----\n", + "uri": "https://acme.example.com/acct/1", + "provider": "Lets Encrypt" + }} diff --git a/docs/source/api/v5/acme_accounts_provider_email.rst b/docs/source/api/v5/acme_accounts_provider_email.rst new file mode 100644 index 0000000000..4dd7777d7e --- /dev/null +++ b/docs/source/api/v5/acme_accounts_provider_email.rst @@ -0,0 +1,67 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-acme-accounts-provider-email: + +**************************************** +``acme_accounts/{{provider}}/{{email}}`` +**************************************** + +.. versionadded:: 3.1 + +``DELETE`` +========== +Delete :term:`ACME Account` information. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: ACME:DELETE, ACME:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +----------+-----------------------------------------------------------------------------------------------------------------+ + | Name | Description | + +==========+=================================================================================================================+ + | provider | The :abbr:`ACME (Automatic Certificate Management Environment)` provider for the account to be deleted | + +----------+-----------------------------------------------------------------------------------------------------------------+ + | email | The email used in the :term:`ACME Account` to be deleted | + +----------+-----------------------------------------------------------------------------------------------------------------+ + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 10 Dec 2020 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: rGD2sOMHYF0sga1zuTytyLHCUkkc3ZwQRKvZ/HuPzObOP4WztKTOVXB4uhs3iJqBg9zRB2TucMxONHN+3/yShQ== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 10 Dec 2020 14:24:34 GMT + Content-Length: 60 + + {"alerts": [ + { + "text": "Acme account deleted", + "level":"success" + } + ]} diff --git a/docs/source/api/v5/acme_accounts_providers.rst b/docs/source/api/v5/acme_accounts_providers.rst new file mode 100644 index 0000000000..e08e38c116 --- /dev/null +++ b/docs/source/api/v5/acme_accounts_providers.rst @@ -0,0 +1,49 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-acme-accounts-providers: + +*************************** +``acme_accounts/providers`` +*************************** + +``GET`` +======= +Gets a list of all :abbr:`ACME (Automatic Certificate Management Environment)` providers set up in :ref:`cdn.conf` and Let's Encrypt. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: ACME:READ +:Response Type: Array + +Request Structure +----------------- +No parameters available + + +Response Structure +------------------ + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Content-Type: application/json + + { "response": [ + "CertAuth1", + "CertAuth2", + "CertAuth3" + ]} diff --git a/docs/source/api/v5/acme_autorenew.rst b/docs/source/api/v5/acme_autorenew.rst new file mode 100644 index 0000000000..82fbd6cd09 --- /dev/null +++ b/docs/source/api/v5/acme_autorenew.rst @@ -0,0 +1,50 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-acme-autorenew: + +****************** +``acme_autorenew`` +****************** + +``POST`` +======== +Generates SSL certificates and private keys for all :term:`Delivery Services` that have certificates expiring within the configured time. This uses:abbr:`ACME (Automatic Certificate Management Environment)` or Let's Encrypt depending on the certificate. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: ACME:READ, DS-SECURITY-KEY:UPDATE, DELIVERY-SERVICE:UPDATE +:Response Type: ``undefined`` + +Request Structure +----------------- +No parameters available + + +Response Structure +------------------ + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 202 Accepted + Content-Type: application/json + + { "alerts": [ + { + "text": "Beginning async call to renew certificates. This may take a few minutes. Status updates can be found here: /api/5.0/async_status/1", + "level": "success" + } + ]} diff --git a/docs/source/api/v5/asns.rst b/docs/source/api/v5/asns.rst new file mode 100644 index 0000000000..76b4782043 --- /dev/null +++ b/docs/source/api/v5/asns.rst @@ -0,0 +1,300 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + + +.. _to-api-asns: + +******** +``asns`` +******** +.. seealso:: `The Autonomous System Wikipedia page `_ for an explanation of what an :abbr:`ASN (Autonomous System Number)` actually is. + +``GET`` +======= +List all :abbr:`ASNs (Autonomous System Numbers)`. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: ASN:READ, CACHE-GROUP:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +------------+----------+-----------------------------------------------------------------------------------------------------+ + | Parameter | Required | Description | + +============+==========+=====================================================================================================+ + | cachegroup | no | The :ref:`cache-group-id` of a :term:`Cache Group` - only :abbr:`ASNs (Autonomous System Numbers)` | + | | | for this :term:`Cache Group` will be returned. | + +------------+----------+-----------------------------------------------------------------------------------------------------+ + | id | no | The integral, unique identifier of the desired | + | | | :abbr:`ASN (Autonomous System Number)`-to-:term:`Cache Group` association | + +------------+----------+-----------------------------------------------------------------------------------------------------+ + | orderby | no | Choose the ordering of the results - must be the name of one of the fields of the objects in the | + | | | ``response`` array | + +------------+----------+-----------------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +------------+----------+-----------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +------------+----------+-----------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with | + | | | limit | + +------------+----------+-----------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are | + | | | ``limit`` long and the first page is 1. If ``offset`` was defined, this query parameter has no | + | | | effect. ``limit`` must be defined to make use of ``page``. | + +------------+----------+-----------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/asns HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + +Response Structure +------------------ +:asn: An :abbr:`ASN (Autonomous System Number)` as specified by IANA for identifying a service provider +:cachegroup: A string that is the :ref:`cache-group-name` of the :term:`Cache Group` that is associated with this :abbr:`ASN (Autonomous System Number)` +:cachegroupId: An integer that is the :ref:`cache-group-id` of the :term:`Cache Group` that is associated with this :abbr:`ASN (Autonomous System Number)` +:id: An integral, unique identifier for this association between an :abbr:`ASN (Autonomous System Number)` and a :term:`Cache Group` +:lastUpdated: The time and date this server entry was last updated in :ref:`non-rfc-datetime` + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 02 Dec 2019 22:51:14 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: F2NmDbTpXqrIQDX7IBKH9+1drtTL4XedSfJv6klMgLEZwbLCkddIXuSLpmgVCID6kTVqy3fTKjZS3U+HJ3YUEQ== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 02 Dec 2019 21:51:14 GMT + Content-Length: 128 + + { "response": [ + { + "asn": 1, + "cachegroup": "TRAFFIC_ANALYTICS", + "cachegroupId": 1, + "id": 1, + "lastUpdated": "2019-12-02 21:49:08+00" + } + ]} + + + +``POST`` +======== +Creates a new :abbr:`ASN (Autonomous System Number)`. + +.. note:: There cannot be two different ASN object with the same ``asn``. An ASN may only belong to one cachegroup, but a cachegroup can have zero or more ASNs. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: ASN:CREATE, ASN:READ, CACHE-GROUP:READ, CACHE-GROUP:UPDATE +:Response Type: Object + +Request Structure +----------------- +:asn: The value of the new :abbr:`ASN (Autonomous System Number)` +:cachegroup: An optional field which, if present, is a string that specifies the :ref:`cache-group-name` of a :term:`Cache Group` to which this :abbr:`ASN (Autonomous System Number)` will be assigned + + .. note:: While this endpoint accepts the ``cachegroup`` field, sending this in the request payload has no effect except that the response will (erroneously) name the :term:`Cache Group` to which the :abbr:`ASN (Autonomous System Number)` was assigned. Any subsequent requests will reveal that, in fact, the :term:`Cache Group` is set entirely by the ``cachegroupId`` field, and so the actual :ref:`cache-group-name` may differ from what was in the request. + +:cachegroupId: An integer that is the :ref:`cache-group-id` of a :term:`Cache Group` to which this :abbr:`ASN (Autonomous System Number)` will be assigned + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/asns HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 29 + + {"asn": 1, "cachegroupId": 1} + + +Response Structure +------------------ +:asn: An :abbr:`ASN (Autonomous System Number)` as specified by IANA for identifying a service provider +:cachegroup: A string that is the :ref:`cache-group-name` of the :term:`Cache Group` that is associated with this :abbr:`ASN (Autonomous System Number)` +:cachegroupId: An integer that is the :ref:`cache-group-id` of the :term:`Cache Group` that is associated with this :abbr:`ASN (Autonomous System Number)` +:id: An integral, unique identifier for this association between an :abbr:`ASN (Autonomous System Number)` and a :term:`Cache Group` +:lastUpdated: The time and date this server entry was last updated in :ref:`non-rfc-datetime` + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 02 Dec 2019 22:49:08 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: mx8b2GTYojz4QtMxXCMoQyZogCB504vs0yv6WGly4dwM81W3XiejWNuUwchRBYYi8QHaWsMZ3DaiGGfQi/8Giw== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 02 Dec 2019 21:49:08 GMT + Content-Length: 150 + + { "alerts": [ + { + "text": "asn was created.", + "level": "success" + } + ], + "response": { + "asn": 1, + "cachegroup": null, + "cachegroupId": 1, + "id": 1, + "lastUpdated": "2019-12-02 21:49:08+00" + }} + +``PUT`` +======= +Updates an existing :abbr:`ASN (Autonomous System Number)`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: ASN:UPDATE, ASN:READ, CACHE-GROUP:READ, CACHE-GROUP:UPDATE +:Response Type: Object + +Request Structure +----------------- +:asn: The value of the new :abbr:`ASN (Autonomous System Number)`. +:cachegroup: A string that specifies the :ref:`cache-group-name` of a :term:`Cache Group` to which this :abbr:`ASN (Autonomous System Number)` will be assigned. If you do not pass this field, the cachegroup will be ``null``. +:cachegroupId: The integral, unique identifier of the status of the :term:`Cache Group`. + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/asns?id=1 HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 53 + + { + "asn": 1, + "cachegroup": "TRAFFIC_OPS", + "cachegroupId": 2 + } + +Response Structure +------------------ +:asn: An :abbr:`ASN (Autonomous System Number)` as specified by IANA for identifying a service provider +:cachegroup: A string that is the :ref:`cache-group-name` of the :term:`Cache Group` that is associated with this :abbr:`ASN (Autonomous System Number)` +:cachegroupId: An integer that is the :ref:`cache-group-id` of the :term:`Cache Group` that is associated with this :abbr:`ASN (Autonomous System Number)` +:id: An integral, unique identifier for this association between an :abbr:`ASN (Autonomous System Number)` and a :term:`Cache Group` +:lastUpdated: The time and date this server entry was last updated in :ref:`non-rfc-datetime` + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Tue, 25 Feb 2020 07:21:10 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: MjvwJg6AFbdqGPlAhK+2pfiN+VFjzgeNnhXoMVbh6+fRQYKeej6CCj3x09hwOl4uhp9d9RySrE/CQ3+L1b2VGQ== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 25 Feb 2020 06:21:10 GMT + Content-Length: 164 + + { + "alerts": [ + { + "text": "asn was updated.", + "level": "success" + } + ], + "response": { + "asn": 1, + "cachegroup": "TRAFFIC_OPS", + "cachegroupId": 2, + "id": 1, + "lastUpdated": "2020-02-25 06:21:10+00" + } + } + +``DELETE`` +---------- +Deletes an existing :abbr:`ASN (Autonomous System Number)`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: ASN:DELETE, ASN:READ, CACHE-GROUP:READ, CACHE-GROUP:UPDATE +:Response Type: ``undefined`` + +Request Structure +----------------- + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/asns?id=1 HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 0 + +Response Structure +------------------ + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Tue, 25 Feb 2020 08:27:33 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: Woz8NSHIYVpX4V5X4xZWZIX1hvGL2uian7nUhjZ8F23Nb9RWQRMIg/cc+1vXEzkT/ehKV9t11FKRLX+avSae0g== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 25 Feb 2020 07:27:33 GMT + Content-Length: 83 + + { + "alerts": [ + { + "text": "asn was deleted.", + "level": "success" + } + ] + } diff --git a/docs/source/api/v5/asns_id.rst b/docs/source/api/v5/asns_id.rst new file mode 100644 index 0000000000..687ed06e87 --- /dev/null +++ b/docs/source/api/v5/asns_id.rst @@ -0,0 +1,155 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + + +.. _to-api-asns-id: + +*************** +``asns/{{id}}`` +*************** +.. seealso:: `The Autonomous System Wikipedia page `_ for an explanation of what an :abbr:`ASN (Autonomous System Number)` actually is. + +``PUT`` +======= +Allows user to edit an existing :abbr:`ASN (Autonomous System Number)`-to-:term:`Cache Group` association. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: ASN:UPDATE, ASN:READ, CACHE-GROUP:UPDATE, CACHE-GROUP:READ +:Response Type: Object + +Request Structure +----------------- +:asn: The new :abbr:`ASN (Autonomous System Number)` which will be associated with the identified :term:`Cache Group` - must not conflict with existing associations +:cachegroup: An optional field which, if present, is a string that specifies the :ref:`cache-group-name` of a :term:`Cache Group` to which this :abbr:`ASN (Autonomous System Number)` will be assigned + + .. note:: While this endpoint accepts the ``cachegroup`` field, sending this in the request payload has no effect except that the response will (erroneously) name the :term:`Cache Group` to which the :abbr:`ASN (Autonomous System Number)` was assigned. Any subsequent requests will reveal that, in fact, the :term:`Cache Group` is set entirely by the ``cachegroupId`` field, and so the actual :ref:`cache-group-name` may differ from what was in the request. + +:cachegroupId: An integer that is the :ref:`cache-group-id` of a :term:`Cache Group` to which this :abbr:`ASN (Autonomous System Number)` will be assigned - must not conflict with existing associations + + +.. table:: Request Path Parameters + + +------+----------+--------------------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +======+==========+==========================================================================================================================+ + | id | yes | The integral, unique identifier of the desired :abbr:`ASN (Autonomous System Number)`-to-:term:`Cache Group` association | + +------+----------+--------------------------------------------------------------------------------------------------------------------------+ + + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/asns/1 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 29 + Content-Type: application/x-www-form-urlencoded + + {"asn": 2, "cachegroupId": 1} + +Response Structure +------------------ +:asn: An :abbr:`ASN (Autonomous System Number)` as specified by IANA for identifying a service provider +:cachegroup: A string that is the :ref:`cache-group-name` of the :term:`Cache Group` that is associated with this :abbr:`ASN (Autonomous System Number)` +:cachegroupId: An integer that is the :ref:`cache-group-id` of the :term:`Cache Group` that is associated with this :abbr:`ASN (Autonomous System Number)` +:id: An integral, unique identifier for this association between an :abbr:`ASN (Autonomous System Number)` and a :term:`Cache Group` +:lastUpdated: The time and date this server entry was last updated in :ref:`non-rfc-datetime` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: /83P4LJVsrQx9BKHFxo5pbhQMlB4o3a9v3PpkspyOJcpNx1S/GJhCPpiANBki547sbY+0vTq76IriHZ4GYp8bA== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 08 Nov 2018 14:37:39 GMT + Content-Length: 160 + + { "alerts": [ + { + "text": "asn was updated.", + "level": "success" + } + ], + "response": { + "asn": 2, + "cachegroup": null, + "cachegroupId": 1, + "id": 1, + "lastUpdated": "2018-11-08 14:37:39+00" + }} + +``DELETE`` +========== +Deletes an association between an :abbr:`ASN (Autonomous System Number)` and a :term:`Cache Group`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: ASN:DELETE, ASN:READ, CACHE-GROUP:READ, CACHE-GROUP:UPDATE +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+----------+--------------------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +======+==========+==========================================================================================================================+ + | id | yes | The integral, unique identifier of the desired :abbr:`ASN (Autonomous System Number)`-to-:term:`Cache Group` association | + +------+----------+--------------------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/asns/1 HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 0 + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 02 Dec 2019 23:06:24 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: 6t3WA+DOcfPJB5UnvDpzEVx5ySfmJgEV9wgkO71U5k32L1VXpxcaTdDVLNGgDDl9sdNftmYnKXf5jpfWUuFYJQ== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 02 Dec 2019 22:06:24 GMT + Content-Length: 81 + + { "alerts": [ + { + "text": "asn was deleted.", + "level": "success" + } + ]} diff --git a/docs/source/api/v5/async_status.rst b/docs/source/api/v5/async_status.rst new file mode 100644 index 0000000000..6f1a1e9acc --- /dev/null +++ b/docs/source/api/v5/async_status.rst @@ -0,0 +1,64 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-async_status: + +*********************** +``async_status/{{id}}`` +*********************** + +``GET`` +======= +Returns a status update for an asynchronous task. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: ASYNC-STATUS:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +======+==========+======================================================================================================================================+ + | id | yes | The integral, unique identifier for the desired asynchronous job status. This will be provided when the asynchronous job is started. | + +------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + + +Response Structure +------------------ +:id: The integral, unique identifier for the asynchronous job status. +:status: The status of the asynchronous job. This will be `PENDING`, `SUCCEEDED`, or `FAILED`. +:start_time: The time the asynchronous job was started. +:end_time: The time the asynchronous job completed. This will be `null` if it has not completed yet. +:message: A message about the job status. + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Content-Type: application/json + + { "response": + { + "id":1, + "status":"PENDING", + "start_time":"2021-02-18T17:13:56.352261Z", + "end_time":null, + "message":"Async job has started." + } + } diff --git a/docs/source/api/v5/cache_stats.rst b/docs/source/api/v5/cache_stats.rst new file mode 100644 index 0000000000..3975d1e3b9 --- /dev/null +++ b/docs/source/api/v5/cache_stats.rst @@ -0,0 +1,171 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + + +.. _to-api-cache_stats: + +*************** +``cache_stats`` +*************** +Retrieves detailed, aggregated statistics for caches in a specific CDN. + +.. seealso:: This gives an aggregate of statistics for *all caches* within a particular CDN and time range. For statistics basic statistics from all caches regardless of CDN and at the current time, use :ref:`to-api-caches-stats`. + +``GET`` +------- +Retrieves statistics about the caches within the CDN + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: CDN:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Query Parameters + + +---------------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +=====================+===================+===========================================================================================================================================================================================+ + | cdnName | yes | The name of a CDN. Results will represent caches within this CDN | + +---------------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | endDate | yes | The date and time until which statistics shall be aggregated in :rfc:`3339` format (with or without sub-second precision), the number of nanoseconds since the Unix | + | | | Epoch, or in the same, proprietary format as the ``lastUpdated`` fields prevalent throughout the Traffic Ops API | + +---------------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | exclude | no | Either "series" to omit the data series from the result, or "summary" to omit the summary data from the result - directly corresponds to fields in the | + | | | `Response Structure`_ | + +---------------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | interval | no | Specifies the interval within which data will be "bucketed"; e.g. when requesting data from 2019-07-25T00:00:00Z to 2019-07-25T23:59:59Z with an interval of "1m", | + | | | the resulting data series (assuming it is not excluded) should contain | + | | | :math:`24\frac{\mathrm{hours}}{\mathrm{day}}\times60\frac{\mathrm{minutes}}{\mathrm{hour}}\times1\mathrm{day}\times1\frac{\mathrm{minute}}{\mathrm{data point}}=1440\mathrm{data\;points}`| + | | | The allowed values for this parameter are valid InfluxQL duration literal strings matching :regexp:`^\d+[mhdw]$` | + | | | | + +---------------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | limit | no | A natural number indicating the maximum amount of data points should be returned in the ``series`` object | + +---------------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | metricType | yes | The metric type being reported - one of: 'connections', 'bandwidth', 'maxkbps' | + +---------------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | offset | no | A natural number of data points to drop from the beginning of the returned data set | + +---------------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | orderby | no | Though one struggles to imagine why, this can be used to specify "time" to sort data points by their "time" (which is the default behavior) | + +---------------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | startDate | yes | The date and time from which statistics shall be aggregated in :rfc:`3339` format (with or without sub-second precision), the number of nanoseconds since the Unix | + | | | Epoch, or in the same, proprietary format as the ``lastUpdated`` fields prevalent throughout the Traffic Ops API | + +---------------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + +.. _cache_stats-get-request-example: +.. code-block:: http + :caption: Request Example + + GET /api/5.0/cache_stats?cdnName=CDN&endDate=2019-10-28T20:49:00Z&metricType=bandwidth&startDate=2019-10-28T20:45:00Z HTTP/1.1 + User-Agent: python-requests/2.20.1 + Accept-Encoding: gzip, deflate + Accept: application/json;timestamp=unix, application/json;timestamp=rfc;q=0.9, application/json;q=0.8, */*;q=0.7 + Connection: keep-alive + Cookie: mojolicious=... + +Content Format +"""""""""""""" +It's important to note in :ref:`cache_stats-get-request-example` the use of a complex "Accept" header. This endpoint accepts two special media types in the "Accept" header that instruct it on how to format the timestamps associated with the returned data. Specifically, Traffic Ops will recognize the special, optional, non-standard parameter of :mimetype:`application/json`: ``timestamp``. The values of this parameter are restricted to one of + +rfc + Returned timestamps will be formatted according to :rfc:`3339` (no sub-second precision). +unix + Returned timestamps will be formatted as the number of nanoseconds since the Unix Epoch (midnight on January 1\ :sup:`st` 1970 UTC). + + .. impl-detail:: The endpoint passes back nanoseconds, specifically, because that is the form used both by InfluxDB, which is used to store the data being served, and Go's standard library. Clients may need to convert the value to match their own standard libraries - e.g. the :js:class:`Date` class in Javascript expects milliseconds. + +The default behavior - when only e.g. :mimetype:`application/json` or :mimetype:`*/*` is given - is to use :rfc:`3339` formatting. It will, however, respect quality parameters. It is suggested that clients request timestamps they can handle specifically, rather than relying on this default behavior, as it **is subject to change** and is in fact **expected to invert in the next major release** as string-based time formats become deprecated. + +.. seealso:: For more information on the "Accept" HTTP header, consult `its dedicated page on MDN `_. + +Response Structure +------------------ +:series: An object containing the actual data series and information necessary for working with it. + + :columns: This is an array of names of the columns of the data contained in the "values" array - should always be ``["time", "sum_count"]`` + :count: The number of data points contained in the "values" array + :name: The name of the data set. Should always match :samp:`{metric}.ds.1min` where ``metric`` is the requested ``metricType`` + :values: The actual array of data points. Each represents a length of time specified by the ``interval`` query parameter + + :time: The time at which the measurement was taken. This corresponds to the *beginning* of the interval. This time comes in the format of either an :rfc:`3339`-formatted string, or a number containing the number of nanoseconds since the Unix Epoch depending on the "Accept" header sent by the client, according to the rules outlined in `Content Format`_. + :value: The value of the requested ``metricType`` at the time given by ``time``. This will always be a floating point number, unless no data is available for the data interval, in which case it will be ``null`` + +:summary: A summary of the data contained in the "series" object + + :average: The arithmetic mean of the data's values + :count: The number of measurements taken within the requested timespan. This is, in general, **not** the same as the ``count`` field of the ``series`` object, as it reflects the number of underlying, un-"bucketed" data points, and is therefore dependent on the implementation of Traffic Stats. + :fifthPercentile: Data points with values less than or equal to this number constitute the "bottom" 5% of the data set + :max: The maximum value that can be found in the requested data set + :min: The minimum value that can be found in the requested data set + :ninetyEighthPercentile: Data points with values greater than or equal to this number constitute the "top" 2% of the data set + :ninetyFifthPercentile: Data points with values greater than or equal to this number constitute the "top" 5% of the data set + + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: p4asf1n7fXGtgpW/dWgolJWdXjwDcCjyvjOPFqkckbgoXGUHEj5/wlz7brlQ48t3ZnOWCqOlbsu2eSiBssBtUQ== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 28 Oct 2019 20:49:51 GMT + + { "response": { + "series": { + "columns": [ + "time", + "sum_count" + ], + "count": 4, + "name": "bandwidth.cdn.1min", + "tags": { + "cdn": "CDN-in-a-Box" + }, + "values": [ + [ + 1572295500000000000, + null + ], + [ + 1572295560000000000, + 113.66666666666666 + ], + [ + 1572295620000000000, + 108.83333333333334 + ], + [ + 1572295680000000000, + 113 + ] + ] + }, + "summary": { + "average": 111.83333333333333, + "count": 3, + "fifthPercentile": 0, + "max": 113.66666666666666, + "min": 108.83333333333334, + "ninetyEighthPercentile": 113.66666666666666, + "ninetyFifthPercentile": 113.66666666666666 + } + }} diff --git a/docs/source/api/v5/cachegroups.rst b/docs/source/api/v5/cachegroups.rst new file mode 100644 index 0000000000..5c56b51f74 --- /dev/null +++ b/docs/source/api/v5/cachegroups.rst @@ -0,0 +1,242 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cachegroups: + +*************** +``cachegroups`` +*************** + +``GET`` +======= +Extract information about :term:`Cache Groups`. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: CACHE-GROUP:READ, TYPE:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +===========+==========+==========================================================================================================================+ + | id | no | Return the only :term:`Cache Group` that has this id | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------+ + | name | no | Return only the :term:`Cache Group` identified by this :ref:`cache-group-name` | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------+ + | type | no | Return only :term:`Cache Groups` that are of the :ref:`cache-group-type` identified by this integral, unique identifier | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------+ + | topology | no | Return only :term:`Cache Groups` that are used in the :term:`Topology` identified by this unique identifier | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------+ + | orderby | no | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` array | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long and the | + | | | first page is 1. If ``offset`` was defined, this query parameter has no effect. ``limit`` must be defined to make use of | + | | | ``page``. | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/cachegroups?type=23 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + + +Response Structure +------------------ +:fallbacks: An array of strings that are :ref:`Cache Group names ` that are registered as :ref:`cache-group-fallbacks` for this :term:`Cache Group`\ [#fallbacks]_ +:fallbackToClosest: A boolean value that defines the :ref:`cache-group-fallback-to-closest` behavior of this :term:`Cache Group`\ [#fallbacks]_ +:id: An integer that is the :ref:`cache-group-id` of the :term:`Cache Group` +:lastUpdated: The time and date at which this entry was last updated in :ref:`non-rfc-datetime` +:latitude: A floating-point :ref:`cache-group-latitude` for the :term:`Cache Group` +:localizationMethods: An array of :ref:`cache-group-localization-methods` as strings +:longitude: A floating-point :ref:`cache-group-longitude` for the :term:`Cache Group` +:name: A string containing the :ref:`cache-group-name` of the :term:`Cache Group` +:parentCachegroupId: An integer that is the :ref:`cache-group-id` of this :term:`Cache Group`'s :ref:`cache-group-parent` - or ``null`` if it doesn't have a :ref:`cache-group-parent` +:parentCachegroupName: A string containing the :ref:`cache-group-name` of this :term:`Cache Group`'s :ref:`cache-group-parent` - or ``null`` if it doesn't have a :ref:`cache-group-parent` +:secondaryParentCachegroupId: An integer that is the :ref:`cache-group-id` of this :term:`Cache Group`'s :ref:`cache-group-secondary-parent` - or ``null`` if it doesn't have a :ref:`cache-group-secondary-parent` +:secondaryParentCachegroupName: A string containing the :ref:`cache-group-name` of this :term:`Cache Group`'s :ref:`cache-group-secondary-parent` :term:`Cache Group` - or ``null`` if it doesn't have a :ref:`cache-group-secondary-parent` +:shortName: A string containing the :ref:`cache-group-short-name` of the :term:`Cache Group` +:typeId: An integral, unique identifier for the ':term:`Type`' of the :term:`Cache Group` +:typeName: A string that names the :ref:`cache-group-type` of this :term:`Cache Group` + +.. note:: The default value of ``fallbackToClosest`` is 'true', and if it is 'null' Traffic Control components will still interpret it as 'true'. + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: oV6ifEgoFy+v049tVjSsRdWQf4bxjrUvIYfDdgpUtlxiC7gzCv31m5bXQ8EUBW4eg2hfYM+BsGvJpnNDZB7pUg== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 07 Nov 2018 19:46:36 GMT + Content-Length: 379 + + { "response": [ + { + "id": 7, + "name": "CDN_in_a_Box_Edge", + "shortName": "ciabEdge", + "latitude": 38.897663, + "longitude": -77.036574, + "parentCachegroupName": "CDN_in_a_Box_Mid", + "parentCachegroupId": 6, + "secondaryParentCachegroupName": null, + "secondaryParentCachegroupId": null, + "fallbackToClosest": [], + "localizationMethods": [], + "typeName": "EDGE_LOC", + "typeId": 23, + "lastUpdated": "2018-11-07 14:45:43+00", + "fallbacks": [] + } + ]} + + +``POST`` +======== +Creates a :term:`Cache Group` + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: CACHE-GROUP:CREATE, CACHE-GROUP:READ, TYPE:READ +:Response Type: Object + +Request Structure +----------------- +:fallbacks: An optional field which, when present, should contain an array of strings that are the :ref:`Names ` of other :term:`Cache Groups` which will be the :ref:`cache-group-fallbacks`\ [#fallbacks]_ +:fallbackToClosest: A boolean that sets the :ref:`cache-group-fallback-to-closest` behavior of the :term:`Cache Group`\ [#fallbacks]_ + + .. note:: The default value of ``fallbackToClosest`` is ``true``, and if it is ``null`` Traffic Control components will still interpret it as though it were ``true``. + +:latitude: An optional field which, if present, should be a floating-point number that will define the :ref:`cache-group-latitude` for the :term:`Cache Group`\ [#optional]_ +:localizationMethods: Array of :ref:`cache-group-localization-methods` (as strings) + + .. tip:: This field has no defined meaning if the :ref:`cache-group-type` identified by ``typeId`` is not "EDGE_LOC". + +:longitude: An optional field which, if present, should be a floating-point number that will define the :ref:`cache-group-longitude` for the :term:`Cache Group`\ [#optional]_ +:name: The :ref:`cache-group-name` of the :term:`Cache Group` +:parentCachegroupId: An optional field which, if present, should be an integer that is the :ref:`cache-group-id` of a :ref:`cache-group-parent` for this :term:`Cache Group`. +:secondaryParentCachegroupId: An optional field which, if present, should be an integral, unique identifier for this :term:`Cache Group`'s secondary parent +:shortName: An abbreviation of the ``name`` +:typeId: An integral, unique identifier for the :ref:`Cache Group's Type ` + + .. note:: The actual, integral, unique identifiers for these :term:`Types` must first be obtained, generally via :ref:`to-api-types`. + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/cachegroups HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 252 + Content-Type: application/json + + { + "name": "test", + "shortName": "test", + "latitude": 0, + "longitude": 0, + "fallbackToClosest": true, + "localizationMethods": [ + "DEEP_CZ", + "CZ", + "GEO" + ], + "typeId": 23, + } + +Response Structure +------------------ +:fallbacks: An array of strings that are :ref:`Cache Group names ` that are registered as :ref:`cache-group-fallbacks` for this :term:`Cache Group`\ [#fallbacks]_ +:fallbackToClosest: A boolean value that defines the :ref:`cache-group-fallback-to-closest` behavior of this :term:`Cache Group`\ [#fallbacks]_ +:id: An integer that is the :ref:`cache-group-id` of the :term:`Cache Group` +:lastUpdated: The time and date at which this entry was last updated in :ref:`non-rfc-datetime` +:latitude: A floating-point :ref:`cache-group-latitude` for the :term:`Cache Group` +:localizationMethods: An array of :ref:`cache-group-localization-methods` as strings +:longitude: A floating-point :ref:`cache-group-longitude` for the :term:`Cache Group` +:name: A string containing the :ref:`cache-group-name` of the :term:`Cache Group` +:parentCachegroupId: An integer that is the :ref:`cache-group-id` of this :term:`Cache Group`'s :ref:`cache-group-parent` - or ``null`` if it doesn't have a :ref:`cache-group-parent` +:parentCachegroupName: A string containing the :ref:`cache-group-name` of this :term:`Cache Group`'s :ref:`cache-group-parent` - or ``null`` if it doesn't have a :ref:`cache-group-parent` +:secondaryParentCachegroupId: An integer that is the :ref:`cache-group-id` of this :term:`Cache Group`'s :ref:`cache-group-secondary-parent` - or ``null`` if it doesn't have a :ref:`cache-group-secondary-parent` +:secondaryParentCachegroupName: A string containing the :ref:`cache-group-name` of this :term:`Cache Group`'s :ref:`cache-group-secondary-parent` :term:`Cache Group` - or ``null`` if it doesn't have a :ref:`cache-group-secondary-parent` +:shortName: A string containing the :ref:`cache-group-short-name` of the :term:`Cache Group` +:typeId: An integral, unique identifier for the ':term:`Type`' of the :term:`Cache Group` +:typeName: A string that names the :ref:`cache-group-type` of this :term:`Cache Group` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: YvZlh3rpfl3nBq6SbNVhbkt3IvckbB9amqGW2JhLxWK9K3cxjBq5J2sIHBUhrLKUhE9afpxtvaYrLRxjt1/YMQ== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 07 Nov 2018 22:11:50 GMT + Content-Length: 379 + + { "alerts": [ + { + "text": "cachegroup was created.", + "level": "success" + } + ], + "response": { + "id": 8, + "name": "test", + "shortName": "test", + "latitude": 0, + "longitude": 0, + "parentCachegroupName": null, + "parentCachegroupId": null, + "secondaryParentCachegroupName": null, + "secondaryParentCachegroupId": null, + "fallbackToClosest": true, + "localizationMethods": [ + "DEEP_CZ", + "CZ", + "GEO" + ], + "typeName": "EDGE_LOC", + "typeId": 23, + "lastUpdated": "2019-12-02 22:21:08+00", + "fallbacks": [] + }} + +.. [#fallbacks] Traffic Router will first check for a ``fallbacks`` array and, when that is empty/unset/all the :term:`Cache Groups` in it are also unavailable, will subsequently check for ``fallbackToClosest``. If that is ``true``, then it falls back to the geographically closest :term:`Cache Group` capable of serving the same content or, when it is ``false``/no such :term:`Cache Group` exists/said :term:`Cache Group` is also unavailable, will respond to clients with a failure response indicating the problem. +.. [#optional] While these fields are technically optional, note that if they are not specified many things may break. For this reason, Traffic Portal requires them when creating or editing :term:`Cache Groups`. diff --git a/docs/source/api/v5/cachegroups_id.rst b/docs/source/api/v5/cachegroups_id.rst new file mode 100644 index 0000000000..eb14b0df63 --- /dev/null +++ b/docs/source/api/v5/cachegroups_id.rst @@ -0,0 +1,195 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cachegroups-id: + +********************** +``cachegroups/{{ID}}`` +********************** + +``PUT`` +======= +Update :term:`Cache Group` + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: CACHE-GROUP:UPDATE, CACHE-GROUP:READ, TYPE:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +-----------+----------------------------------------------------+ + | Parameter | Description | + +===========+====================================================+ + | ID | The :ref:`cache-group-id` of a :term:`Cache Group` | + +-----------+----------------------------------------------------+ + +:fallbacks: An optional field which, when present, should contain an array of strings that are the :ref:`Names ` of other :term:`Cache Groups` which will be the :ref:`cache-group-fallbacks`\ [#fallbacks]_ +:fallbackToClosest: A boolean that sets the :ref:`cache-group-fallback-to-closest` behavior of the :term:`Cache Group`\ [#fallbacks]_ + + .. note:: The default value of ``fallbackToClosest`` is ``true``, and if it is ``null`` Traffic Control components will still interpret it as though it were ``true``. + +:latitude: An optional field which, if present, should be a floating-point number that will define the :ref:`cache-group-latitude` for the :term:`Cache Group`\ [#optional]_ +:localizationMethods: Array of :ref:`cache-group-localization-methods` (as strings) + + .. tip:: This field has no defined meaning if the :ref:`cache-group-type` identified by ``typeId`` is not "EDGE_LOC". + +:longitude: An optional field which, if present, should be a floating-point number that will define the :ref:`cache-group-longitude` for the :term:`Cache Group`\ [#optional]_ +:name: The :ref:`cache-group-name` of the :term:`Cache Group` +:parentCachegroupId: An optional field which, if present, should be an integer that is the :ref:`cache-group-id` of a :ref:`cache-group-parent` for this :term:`Cache Group`. +:secondaryParentCachegroupId: An optional field which, if present, should be an integral, unique identifier for this :term:`Cache Group`'s secondary parent +:shortName: An abbreviation of the ``name`` +:typeId: An integral, unique identifier for the :ref:`Cache Group's Type ` + + .. note:: The actual, integral, unique identifiers for these :term:`Types` must first be obtained, generally via :ref:`to-api-types`. + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/cachegroups/8 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 118 + Content-Type: application/json + + { + "latitude": 0.0, + "longitude": 0.0, + "name": "test", + "fallbacks": [], + "fallbackToClosest": true, + "shortName": "test", + "typeId": 23, + "localizationMethods": ["GEO"] + } + +Response Structure +------------------ +:fallbacks: An array of strings that are :ref:`Cache Group names ` that are registered as :ref:`cache-group-fallbacks` for this :term:`Cache Group`\ [#fallbacks]_ +:fallbackToClosest: A boolean value that defines the :ref:`cache-group-fallback-to-closest` behavior of this :term:`Cache Group`\ [#fallbacks]_ +:id: An integer that is the :ref:`cache-group-id` of the :term:`Cache Group` +:lastUpdated: The time and date at which this entry was last updated in :ref:`non-rfc-datetime` +:latitude: A floating-point :ref:`cache-group-latitude` for the :term:`Cache Group` +:localizationMethods: An array of :ref:`cache-group-localization-methods` as strings +:longitude: A floating-point :ref:`cache-group-longitude` for the :term:`Cache Group` +:name: A string containing the :ref:`cache-group-name` of the :term:`Cache Group` +:parentCachegroupId: An integer that is the :ref:`cache-group-id` of this :term:`Cache Group`'s :ref:`cache-group-parent` - or ``null`` if it doesn't have a :ref:`cache-group-parent` +:parentCachegroupName: A string containing the :ref:`cache-group-name` of this :term:`Cache Group`'s :ref:`cache-group-parent` - or ``null`` if it doesn't have a :ref:`cache-group-parent` +:secondaryParentCachegroupId: An integer that is the :ref:`cache-group-id` of this :term:`Cache Group`'s :ref:`cache-group-secondary-parent` - or ``null`` if it doesn't have a :ref:`cache-group-secondary-parent` +:secondaryParentCachegroupName: A string containing the :ref:`cache-group-name` of this :term:`Cache Group`'s :ref:`cache-group-secondary-parent` :term:`Cache Group` - or ``null`` if it doesn't have a :ref:`cache-group-secondary-parent` +:shortName: A string containing the :ref:`cache-group-short-name` of the :term:`Cache Group` +:typeId: An integral, unique identifier for the ':term:`Type`' of the :term:`Cache Group` +:typeName: A string that names the :ref:`cache-group-type` of this :term:`Cache Group` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: t1W65/2kj25QyHt0Ib0xpBaAR2sXu2kOsRZ49WjKZp/AK5S1YWhX7VNWCuUGiN1VNM4QRNqODC/7ewhYDFUncA== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 14 Nov 2018 19:14:28 GMT + Content-Length: 385 + + { "alerts": [ + { + "text": "cachegroup was updated.", + "level": "success" + } + ], + "response": { + "id": 8, + "name": "test", + "shortName": "test", + "latitude": 0, + "longitude": 0, + "parentCachegroupName": null, + "parentCachegroupId": null, + "secondaryParentCachegroupName": null, + "secondaryParentCachegroupId": null, + "fallbacks": [], + "fallbackToClosest": true, + "localizationMethods": [ + "GEO" + ], + "typeName": "EDGE_LOC", + "typeId": 23, + "lastUpdated": "2018-11-14 19:14:28+00" + }} + + +``DELETE`` +========== +Delete a :term:`Cache Group`. A :term:`Cache Group` which has assigned servers or is the :ref:`cache-group-parent` of one or more other :term:`Cache Groups` cannot be deleted. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: CACHE-GROUP:DELETE, CACHE-GROUP:READ +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Path Parameters + + +-----------+------------------------------------------------------------------+ + | Parameter | Description | + +===========+==================================================================+ + | ID | The :ref:`cache-group-id` of a :term:`Cache Group` to be deleted | + +-----------+------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/cachegroups/42 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: 5jZBgO7h1eNF70J/cmlbi3Hf9KJPx+WLMblH/pSKF3FWb/10GUHIN35ZOB+lN5LZYCkmk3izGbTFkiruG8I41Q== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 14 Nov 2018 20:31:04 GMT + Content-Length: 57 + + { "alerts": [ + { + "text": "cachegroup was deleted.", + "level": "success" + } + ]} + +.. [#fallbacks] Traffic Router will first check for a ``fallbacks`` array and, when that is empty/unset/all the :term:`Cache Groups` in it are also unavailable, will subsequently check for ``fallbackToClosest``. If that is ``true``, then it falls back to the geographically closest :term:`Cache Group` capable of serving the same content or, when it is ``false``/no such :term:`Cache Group` exists/said :term:`Cache Group` is also unavailable, will respond to clients with a failure response indicating the problem. +.. [#optional] While these fields are technically optional, note that if they are not specified many things may break. For this reason, Traffic Portal requires them when creating or editing :term:`Cache Groups`. diff --git a/docs/source/api/v5/cachegroups_id_deliveryservices.rst b/docs/source/api/v5/cachegroups_id_deliveryservices.rst new file mode 100644 index 0000000000..777a215ec5 --- /dev/null +++ b/docs/source/api/v5/cachegroups_id_deliveryservices.rst @@ -0,0 +1,93 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cachegroups-id-deliveryservices: + +*************************************** +``cachegroups/{{ID}}/deliveryservices`` +*************************************** + +``POST`` +======== +Assigns all of the "assignable" servers within a :term:`Cache Group` to one or more :term:`Delivery Services`. + +.. note:: "Assignable" here means all of the :ref:`Cache Group's servers ` that have a :term:`Type` that matches one of the glob patterns ``EDGE*`` or ``ORG*``. If even one server of any :term:`Type` exists within the :term:`Cache Group` that is not assigned to the same CDN as the :term:`Delivery Service` to which an attempt is being made to assign them, the request will fail. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: CACHE-GROUP:UPDATE, DELIVERY-SERVICE:UPDATE, CACHE-GROUP:READ, DELIVERY-SERVICE:READ +:Response Type: Object + +Request Structure +----------------- +.. table::Request Path Parameters + + +------+-----------------------------------------------------------------------------------+ + | Name | Description | + +======+===================================================================================+ + | ID | The :ref:`cache-group-id` of the :term:`Cache Group` from which to assign servers | + +------+-----------------------------------------------------------------------------------+ + +:deliveryServices: The integral, unique identifiers of the :term:`Delivery Services` to which the :ref:`Cache Group's servers ` are being assigned + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/cachegroups/8/deliveryservices HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 25 + Content-Type: application/json + + {"deliveryServices": [2]} + +Response Structure +------------------ +:deliveryServices: An array of integral, unique identifiers for :term:`Delivery Services` to which the :ref:`Cache Group's servers ` have been assigned +:id: An integer that is the :ref:`Cache Group's ID ` +:serverNames: An array of the (short) hostnames of all of the :term:`Cache Group`'s "assignable" :ref:`cache-group-servers` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: j/yH0gvJoaGjiLZU/0MA8o5He20O4aJ5wh1eF9ex6F6IBO1liM9Wk9RkWCw7sdiUHoy13/mf7gDntisZwzP7yw== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 14 Nov 2018 19:54:17 GMT + Content-Length: 183 + + { "alerts": [ + { + "text": "Delivery services successfully assigned to all the servers of cache group 8.", + "level": "success" + } + ], + "response": { + "id": 8, + "serverNames": [ + "foo" + ], + "deliveryServices": [ + 2 + ] + }} diff --git a/docs/source/api/v5/cachegroups_id_queue_update.rst b/docs/source/api/v5/cachegroups_id_queue_update.rst new file mode 100644 index 0000000000..6f1ff3362e --- /dev/null +++ b/docs/source/api/v5/cachegroups_id_queue_update.rst @@ -0,0 +1,92 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cachegroups-id-queue_update: + +*********************************** +``cachegroups/{{ID}}/queue_update`` +*********************************** + +``POST`` +======== +:term:`Queue` or "dequeue" updates for all of a :ref:`Cache Group's servers `, limited to a specific CDN. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: CACHE-GROUP:READ, CDN:READ, SERVER:READ, SERVER:QUEUE +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+------------------------------------------------------------------------------------------------------------+ + | Name | Description | + +======+============================================================================================================+ + | ID | The :ref:`cache-group-id` of the :term:`Cache Group` for which updates are being :term:`Queue`\ d/dequeued | + +------+------------------------------------------------------------------------------------------------------------+ + +:action: The action to perform; one of "queue" or "dequeue" +:cdn: The full name of the CDN in need of :term:`Queue Updates`, or a "dequeue" thereof\ [#required]_ +:cdnId: The integral, unique identifier for the CDN in need of :term:`Queue Updates`, or a "dequeue" thereof\ [#required]_ + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/cachegroups/8/queue_update HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 42 + Content-Type: application/json + + {"action": "queue", "cdn": "CDN-in-a-Box"} + + +Response Structure +------------------ +:action: The action processed, one of "queue" or "dequeue" +:cachegroupId: An integer that is the :ref:`cache-group-id` of the :term:`Cache Group` for which :term:`Queue Updates` was performed or cleared +:cachegroupName: The name of the :term:`Cache Group` for which updates were queued/dequeued +:cdn: The name of the CDN to which the queue/dequeue operation was restricted +:serverNames: An array of the (short) hostnames of the :ref:`Cache Group's servers ` which are also assigned to the CDN specified in the ``"cdn"`` field + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: UAcP7LrflU1RnfR4UqbQrJczlk5rkrcLOtTXJTFvIUXxK1EklZkHkE4vewjDaVIhJJ6YQg8jmPGQpr+x1RHabw== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 14 Nov 2018 20:19:46 GMT + Content-Length: 115 + + { "response": { + "cachegroupName": "test", + "action": "queue", + "serverNames": [ + "foo" + ], + "cdn": "CDN-in-a-Box", + "cachegroupID": 8 + }} + +.. [#required] Either 'cdn' or 'cdnID' *must* be in the request data (but not both). diff --git a/docs/source/api/v5/caches_stats.rst b/docs/source/api/v5/caches_stats.rst new file mode 100644 index 0000000000..9b2238cff1 --- /dev/null +++ b/docs/source/api/v5/caches_stats.rst @@ -0,0 +1,81 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + + +.. _to-api-caches-stats: + +**************** +``caches/stats`` +**************** +An API endpoint that returns cache statistics using the :ref:`tm-api`. + +.. seealso:: This gives a set of basic statistics for *all* :term:`cache servers` at the current time. For statistics from time ranges and/or aggregated over a specific CDN, use :ref:`to-api-cache_stats`. + +``GET`` +======= +Retrieves cache stats from Traffic Monitor. Also includes rows for aggregates. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: CACHE-GROUP:READ, PROFILE:READ +:Response Type: Array + +Request Structure +----------------- +No parameters available. + +Response Structure +------------------ +:cachegroup: A string that is the :ref:`cache-group-name` of the :term:`Cache Group` to which this :term:`cache server` belongs +:connections: Current number of TCP connections maintained by the :term:`cache server` +:healthy: ``true`` if Traffic Monitor has marked the :term:`cache server` as "healthy", ``false`` otherwise + + .. seealso:: :ref:`health-proto` + +:hostname: The (short) hostname of the :term:`cache server` +:ip: The IP address of the :term:`cache server` +:kbps: The :term:`cache server`'s upload speed (to clients) in Kilobits per second +:profile: The :ref:`profile-name` of the :term:`Profile` in use by this :term:`cache server` +:status: The status of the :term:`cache server` + +.. 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 + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Cache-Control: no-cache, no-store, max-age=0, must-revalidate + Content-Type: application/json + Date: Wed, 14 Nov 2018 20:25:01 GMT + X-Server-Name: traffic_ops_golang/ + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: DqbLgitanS8q81/qKC1i+ImMiEMF+SW4G9rb79FWdeWcgwFjL810tlTRp1nNNfHV+tajgjyK+wMHobqVyaNEfA== + Content-Length: 133 + + { "response": [ + { + "profile": "ALL", + "connections": 0, + "ip": null, + "status": "ALL", + "healthy": true, + "kbps": 0, + "hostname": "ALL", + "cachegroup": "ALL" + } + ]} diff --git a/docs/source/api/v5/cdn_locks.rst b/docs/source/api/v5/cdn_locks.rst new file mode 100644 index 0000000000..4645746d56 --- /dev/null +++ b/docs/source/api/v5/cdn_locks.rst @@ -0,0 +1,213 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cdn-locks: + +***************** +``cdn_locks`` +***************** + +``GET`` +======= +Gets information for all CDN locks. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: CDN:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +---------------+----------+-----------------------------------------------------------------------------------+ + | Parameter | Required | Description | + +===============+==========+===================================================================================+ + | username | no | Return only the CDN lock that the user with ``username`` possesses | + +---------------+----------+-----------------------------------------------------------------------------------+ + | cdn | no | Return only the CDN lock for the CDN that has the name ``cdn`` | + +---------------+----------+-----------------------------------------------------------------------------------+ + +Response Structure +------------------ +:userName: The username for which the lock exists. +:cdn: The name of the CDN for which the lock exists. +:message: The message or reason that the user specified while acquiring the lock. +:soft: Whether or not this is a soft(shared) lock. +:sharedUserNames: An array of the usernames that the creator of the lock has shared their lock with. +:lastUpdated: Time that this lock was last updated(created). + +.. code-block:: http + :caption: Response Example + + HTTP/2 200 + Content-Type: application/json + + { "response": [ + { + "userName": "foo", + "cdn": "bar", + "message": "acquiring lock to snap CDN", + "soft": true, + "sharedUserNames": [ + "user1" + ], + "lastUpdated": "2021-05-26T09:31:57-06" + } + ]} + +``POST`` +======== +Allows user to acquire a lock on a CDN. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: CDN-LOCK:CREATE, CDN:READ +:Response Type: Object + +Request Structure +----------------- +The request body must be a single ``CDN Lock`` object with the following keys: +:cdn: The name of the CDN for which the user wants to acquire a lock. +:message: The message or reason for the user to acquire the lock. This is an optional field. +:sharedUserNames: An array of the usernames that the creator of the lock wants to share their lock with. This is an optional field. +:soft: Whether or not this is a soft(shared) lock. This is an optional field; ``soft`` will be set to ``true`` by default. + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/cdn_locks HTTP/2 + Host: localhost:8443 + User-Agent: curl/7.64.2 + Accept: */* + Cookie: mojolicious=... + Content-Type: application/json + Content-Length: 81 + + { + "cdn": "bar", + "message": "acquiring lock to snap CDN", + "sharedUserNames": [ + "user1" + ], + "soft": true + } + +Response Structure +------------------ +:userName: The username for which the lock was created. +:cdn: The name of the CDN for which the lock was created. +:message: The message or reason that the user specified while acquiring the lock. +:soft: Whether or not this is a soft(shared) lock. +:sharedUserNames: An array of the usernames that the creator of the lock has shared their lock with. +:lastUpdated: Time that this lock was last updated(created). + +.. code-block:: http + :caption: Response Example + + HTTP/2 201 + 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=/; Expires=Wed, 26 May 2021 17:59:10 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: IWjt4zhg4OlPDTfOebjMTS1uHsZ8LycEaHgSS3KHnmc6Vvmw5/S6q70CCnbAePV2x1bxKkVEifTIxfft8vq3sg== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 26 May 2021 16:59:10 GMT + Content-Length: 204 + + { "alerts": [ + { + "text": "soft CDN lock acquired!", + "level":"success" + } + ], + "response": { + "userName": "foo", + "cdn": "bar", + "message": "acquiring lock to snap CDN", + "soft": true, + "sharedUserNames": [ + "user1" + ], + "lastUpdated": "2021-05-26T10:59:10-06" + }} + +``DELETE`` +---------- +Deletes an existing ``CDN Lock``. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: CDN-LOCK:DELETE, CDN:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Query Parameters + + +---------------+----------+-----------------------------------------------------------------------------------+ + | Parameter | Required | Description | + +===============+==========+===================================================================================+ + | cdn | yes | Delete the CDN lock for the CDN that has the name ``cdn`` | + +---------------+----------+-----------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/cdn_locks?cdn=bar HTTP/2 + Host: localhost:8443 + User-Agent: curl/7.64.1 + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 0 + Content-Type: application/json + +Response Structure +------------------ + +.. code-block:: http + :caption: Response Example + + HTTP/2 200 + 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=/; Expires=Wed, 26 May 2021 22:20:10 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: p/M2OEmhaws6QLhzzoSBvpC5UnIM+/84RI1wO42PYXiyUKWnxoQQEtm4lkN+K5NOKIH+OkyUlI2ovQZP6lGOcg== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 26 May 2021 21:20:10 GMT + Content-Length: 202 + + { "alerts": [ + { + "text": "cdn lock deleted", + "level":"success" + } + ], + "response": { + "userName": "foo", + "cdn": "bar", + "message": "acquiring lock to snap CDN", + "soft": true, + "sharedUserNames": [ + "user1" + ], + "lastUpdated": "2021-05-26T10:59:10-06" + }} diff --git a/docs/source/api/v5/cdn_notifications.rst b/docs/source/api/v5/cdn_notifications.rst new file mode 100644 index 0000000000..f37108b83c --- /dev/null +++ b/docs/source/api/v5/cdn_notifications.rst @@ -0,0 +1,218 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + + +.. _to-api-cdn-notifications: + +********************* +``cdn_notifications`` +********************* + +``GET`` +======= +List CDN notifications. + +:Auth. Required: Yes +:Roles Required: Read-Only +:Permissions Required: CDN:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +------------+----------+-----------------------------------------------------------------------------------------------------+ + | Parameter | Required | Description | + +============+==========+=====================================================================================================+ + | cdn | no | The CDN name of the notifications you wish to retrieve. | + +------------+----------+-----------------------------------------------------------------------------------------------------+ + | id | no | The integral, unique identifier of the notification you wish to retrieve. | + +------------+----------+-----------------------------------------------------------------------------------------------------+ + | user | no | The username of the user responsible for creating the CDN notifications. | + +------------+----------+-----------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/cdn_notifications HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + +Response Structure +------------------ +:id: The integral, unique identifier of the notification +:cdn: The name of the CDN to which the notification belongs to +:lastUpdated: The time and date this server entry was last updated in :ref:`non-rfc-datetime` +:notification: The content of the notification +:user: The user responsible for creating the notification + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 02 Dec 2019 22:51:14 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: F2NmDbTpXqrIQDX7IBKH9+1drtTL4XedSfJv6klMgLEZwbLCkddIXuSLpmgVCID6kTVqy3fTKjZS3U+HJ3YUEQ== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 02 Dec 2019 21:51:14 GMT + Content-Length: 128 + + { "response": [ + { + "id": 42, + "cdn": "cdn1", + "lastUpdated": "2019-12-02 21:49:08+00", + "notification": "the content of the notification", + "user": "username123", + } + ]} + +``POST`` +======== +Creates a notification for a specific CDN. + +.. note:: Currently only one notification per CDN is supported. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: CDN:UPDATE +:Response Type: Object + +Request Structure +----------------- +:cdn: The name of the CDN to which the notification shall belong +:notification: The content of the notification + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/cdn_notifications HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 29 + + {"cdn": "cdn1", "notification": "the content of the notification"} + + +Response Structure +------------------ +:id: The integral, unique identifier of the notification +:cdn: The name of the CDN to which the notification belongs to +:lastUpdated: The time and date this server entry was last updated in :ref:`non-rfc-datetime` +:notification: The content of the notification +:user: The user responsible for creating the notification + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 02 Dec 2019 22:49:08 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: mx8b2GTYojz4QtMxXCMoQyZogCB504vs0yv6WGly4dwM81W3XiejWNuUwchRBYYi8QHaWsMZ3DaiGGfQi/8Giw== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 02 Dec 2019 21:49:08 GMT + Content-Length: 150 + + { + "alerts": + [ + { + "text": "notification was created.", + "level": "success" + } + ], + "response": + { + "id": 42, + "cdn": "cdn1", + "lastUpdated": "2019-12-02 21:49:08+00", + "notification": "the content of the notification", + "user": "username123", + } + } + +``DELETE`` +---------- +Deletes an existing CDN notification. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: CDN:UPDATE +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Query Parameters + + +------------+----------+-----------------------------------------------------------------------------------------------------+ + | Parameter | Required | Description | + +============+==========+=====================================================================================================+ + | id | yes | The integral, unique identifier of the notification you wish to delete. | + +------------+----------+-----------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/cdn_notifications?id=42 HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 0 + +Response Structure +------------------ + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Tue, 25 Feb 2020 08:27:33 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: Woz8NSHIYVpX4V5X4xZWZIX1hvGL2uian7nUhjZ8F23Nb9RWQRMIg/cc+1vXEzkT/ehKV9t11FKRLX+avSae0g== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 25 Feb 2020 07:27:33 GMT + Content-Length: 83 + + { + "alerts": [ + { + "text": "notification was deleted.", + "level": "success" + } + ] + } diff --git a/docs/source/api/v5/cdns.rst b/docs/source/api/v5/cdns.rst new file mode 100644 index 0000000000..d76df84255 --- /dev/null +++ b/docs/source/api/v5/cdns.rst @@ -0,0 +1,167 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cdns: + +******** +``cdns`` +******** +Extract information about all CDNs + +``GET`` +======= +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: CDN:READ +:Response Type: Array + +Request Structure +----------------- + +.. table:: Request Query Parameters + + +---------------+----------+-----------------------------------------------------------------------------------+ + | Parameter | Required | Description | + +===============+==========+===================================================================================+ + | domainName | no | Return only the CDN that has this domain name | + +---------------+----------+-----------------------------------------------------------------------------------+ + | dnssecEnabled | no | Return only the CDNs that are either dnssec enabled or not | + +---------------+----------+-----------------------------------------------------------------------------------+ + | id | no | Return only the CDN that has this id | + +---------------+----------+-----------------------------------------------------------------------------------+ + | name | no | Return only the CDN that has this name | + +---------------+----------+-----------------------------------------------------------------------------------+ + | orderby | no | Choose the ordering of the results - must be the name of one of the fields of the | + | | | objects in the ``response`` array | + +---------------+----------+-----------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending | + | | | ("desc") | + +---------------+----------+-----------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +---------------+----------+-----------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in | + | | | conjunction with limit | + +---------------+----------+-----------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this | + | | | parameter, pages are ``limit`` long and the first page is 1. If ``offset`` was | + | | | defined, this query parameter has no effect. ``limit`` must be defined to make | + | | | use of ``page``. | + +---------------+----------+-----------------------------------------------------------------------------------+ + +Response Structure +------------------ +:dnssecEnabled: ``true`` if DNSSEC is enabled on this CDN, otherwise ``false`` +:domainName: Top Level Domain name within which this CDN operates +:id: The integral, unique identifier for the CDN +:lastUpdated: Date and time when the CDN was last modified in :ref:`non-rfc-datetime` +:name: The name of the CDN + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: z9P1NkxGebPncUhaChDHtYKYI+XVZfhE6Y84TuwoASZFIMfISELwADLpvpPTN+wwnzBfREksLYn+0313QoBWhA== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 14 Nov 2018 20:46:57 GMT + Content-Length: 237 + + { "response": [ + { + "dnssecEnabled": false, + "domainName": "-", + "id": 1, + "lastUpdated": "2018-11-14 18:21:06+00", + "name": "ALL" + }, + { + "dnssecEnabled": false, + "domainName": "mycdn.ciab.test", + "id": 2, + "lastUpdated": "2018-11-14 18:21:14+00", + "name": "CDN-in-a-Box" + } + ]} + + +``POST`` +======== +Allows user to create a CDN + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: CDN:CREATE, CDN:READ +:Response Type: Object + +Request Structure +----------------- +:dnssecEnabled: If ``true``, this CDN will use DNSSEC, if ``false`` it will not +:domainName: The top-level domain (TLD) belonging to the new CDN +:name: Name of the new CDN + +.. code-block:: http + :caption: Request Structure + + POST /api/5.0/cdns HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 63 + Content-Type: application/json + + {"name": "test", "domainName": "quest", "dnssecEnabled": false} + +Response Structure +------------------ +:dnssecEnabled: ``true`` if the CDN uses DNSSEC, ``false`` otherwise +:domainName: The top-level domain (TLD) assigned to the newly created CDN +:id: An integral, unique identifier for the newly created CDN +:name: The newly created CDN's name + + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: 1rZRlOfQioGRrEb4nCfjGGx7y3Ub2h7BZ4z6NbhcY4acPslKSUNM8QLjWTVwLU4WpkfJNxcoyy8NlKULFrY9Bg== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 14 Nov 2018 20:49:28 GMT + Content-Length: 174 + + { "alerts": [ + { + "text": "cdn was created.", + "level": "success" + } + ], + "response": { + "dnssecEnabled": false, + "domainName": "quest", + "id": 3, + "lastUpdated": "2018-11-14 20:49:28+00", + "name": "test" + }} diff --git a/docs/source/api/v5/cdns_capacity.rst b/docs/source/api/v5/cdns_capacity.rst new file mode 100644 index 0000000000..70560fa8f5 --- /dev/null +++ b/docs/source/api/v5/cdns_capacity.rst @@ -0,0 +1,52 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cdns-capacity: + +***************** +``cdns/capacity`` +***************** + +``GET`` +======= +Retrieves the aggregate capacity percentages of all :term:`Cache Groups` for a given CDN. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: CDN:READ +:Response Type: Object + +Request Structure +----------------- +No parameters available. + +Response Structure +------------------ +:availablePercent: The percent of available (unused) bandwidth to 64 bits of precision\ [1]_ +:unavailablePercent: The percent of unavailable (used) bandwidth to 64 bits of precision\ [1]_ +:utilizedPercent: The percent of bandwidth currently in use to 64 bits of precision\ [1]_ +:maintenancePercent: The percent of bandwidth being used for administrative or analytical processes internal to the CDN to 64 bits of precision\ [1]_ + +.. code-block:: json + :caption: Response Example + + { "response": { + "availablePercent": 89.0939840205533, + "unavailablePercent": 0, + "utilizedPercent": 10.9060020300395, + "maintenancePercent": 0.0000139494071146245 + }} + +.. [1] Following `IEEE 754 `_ diff --git a/docs/source/api/v5/cdns_dnsseckeys_generate.rst b/docs/source/api/v5/cdns_dnsseckeys_generate.rst new file mode 100644 index 0000000000..a89ae202c2 --- /dev/null +++ b/docs/source/api/v5/cdns_dnsseckeys_generate.rst @@ -0,0 +1,78 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cdns-dnsseckeys-generate: + +**************************** +``cdns/dnsseckeys/generate`` +**************************** + +``POST`` +======== +Generates :abbr:`ZSK (Zone-Signing Key)` and :abbr:`KSK (Key-Signing Key)` keypairs for a CDN and all associated :term:`Delivery Services`. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: DNS-SEC:CREATE, CDN:UPDATE, CDN:READ +:Response Type: Object (string) + +Request Structure +----------------- +:effectiveDate: An optional string containing the date and time at which the newly-generated :abbr:`ZSK (Zone-Signing Key)` and :abbr:`KSK (Key-Signing Key)` become effective, in :RFC:`3339` format. Defaults to the current time if not specified. +:key: Name of the CDN +:kskExpirationDays: Expiration (in days) for the :abbr:`KSKs (Key-Signing Keys)` +:ttl: Time, in seconds, for which the keypairs shall remain valid +:zskExpirationDays: Expiration (in days) for the :abbr:`ZSKs (Zone-Signing Keys)` + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/cdns/dnsseckeys/generate HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 130 + + { + "key": "CDN-in-a-Box", + "kskExpirationDays": 1095, + "ttl": 3600, + "zskExpirationDays": 1095 + } + + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 24 Feb 2020 19:42:15 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: O9SPWzeMNFgg6I/PPeXittBIhdh3/zUKK1NwNlYIM9SszSrk0h/Dfz7tnwgnA7h/s6M4eYBJxykDpCfVC7xpeg== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 24 Feb 2020 18:42:15 GMT + Content-Length: 89 + + { + "response": "Successfully created dnssec keys for CDN-in-a-Box" + } diff --git a/docs/source/api/v5/cdns_dnsseckeys_refresh.rst b/docs/source/api/v5/cdns_dnsseckeys_refresh.rst new file mode 100644 index 0000000000..1dd23aeece --- /dev/null +++ b/docs/source/api/v5/cdns_dnsseckeys_refresh.rst @@ -0,0 +1,62 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cdns-dnsseckeys-refresh: + +*************************** +``cdns/dnsseckeys/refresh`` +*************************** + +``GET`` +======= +Refresh the DNSSEC keys for all CDNs. This call initiates a background process to refresh outdated keys, and immediately returns a response that the process has started. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: DNS-SEC:UPDATE, CDN:UPDATE, CDN:READ +:Response Type: Object (string) + +Request Structure +----------------- +No parameters available + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 202 Accepted + 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 + Location: /api/5.0/async_status/3 + Permissions-Policy: interest-cohort=() + Set-Cookie: mojolicious=...; Path=/; Expires=Tue, 20 Jul 2021 23:55:11 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: yJUGNCYygBYvHft4z0nxJ0/p230s3PdPT5Tld+8hIWfxmpmKDciY4D7+1Bf8S69ckmZR/yxY95kIZEbg9/jFgw== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 20 Jul 2021 22:55:11 GMT + Content-Length: 176 + + { + "alerts": [ + { + "text": "Starting DNSSEC key refresh in the background. This may take a few minutes. Status updates can be found here: /api/5.0/async_status/3", + "level": "success" + } + ] + } diff --git a/docs/source/api/v5/cdns_domains.rst b/docs/source/api/v5/cdns_domains.rst new file mode 100644 index 0000000000..4211a1cbc7 --- /dev/null +++ b/docs/source/api/v5/cdns_domains.rst @@ -0,0 +1,54 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cdns-domains: + +**************** +``cdns/domains`` +**************** + +``GET`` +======= +Gets a list of domains and their related Traffic Router :term:`Profiles` for all CDNs. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: CDN:READ, PROFILE:READ, PARAMETER:READ +:Response Type: Array + +Request Structure +----------------- +No parameters available. + +Response Structure +------------------ +:domainName: The :abbr:`TLD (Top-Level Domain)` assigned to this CDN +:parameterId: The :ref:`parameter-id` for the :term:`Parameter` that sets this :abbr:`TLD (Top-Level Domain)` on the Traffic Router +:profileDescription: A short, human-readable description of the Traffic Router's profile +:profileId: The :ref:`profile-id` of the :term:`Profile` assigned to the Traffic Router responsible for serving ``domainName`` +:profileName: The :ref:`profile-name` of the :term:`Profile` assigned to the Traffic Router responsible for serving ``domainName`` + +.. code-block:: json + :caption: Response Example + + { "response": [ + { + "profileId": 12, + "parameterId": -1, + "profileName": "CCR_CIAB", + "profileDescription": "Traffic Router for CDN-In-A-Box", + "domainName": "mycdn.ciab.test" + } + ]} diff --git a/docs/source/api/v5/cdns_health.rst b/docs/source/api/v5/cdns_health.rst new file mode 100644 index 0000000000..fde114c615 --- /dev/null +++ b/docs/source/api/v5/cdns_health.rst @@ -0,0 +1,60 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cdns-health: + +*************** +``cdns/health`` +*************** +Extract health information from all :term:`Cache Groups` across all CDNs + +.. seealso:: :ref:`health-proto` + +``GET`` +======= +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: CACHE-GROUP:READ +:Response Type: Object + +Request Structure +----------------- +No parameters available + +Response Structure +------------------ +:cachegroups: An array of objects describing the health of each Cache Group + + :name: The name of the Cache Group + :offline: The number of OFFLINE caches in the Cache Group + :online: The number of ONLINE caches in the Cache Group + +:totalOffline: Total number of OFFLINE caches across all Cache Groups which are assigned to any CDN +:totalOnline: Total number of ONLINE caches across all Cache Groups which are assigned to any CDN + +.. code-block:: json + :caption: Response Example + + { "response": { + "totalOffline": 0, + "totalOnline": 1, + "cachegroups": [ + { + "offline": 0, + "name": "CDN_in_a_Box_Edge", + "online": 1 + } + ] + }} diff --git a/docs/source/api/v5/cdns_id.rst b/docs/source/api/v5/cdns_id.rst new file mode 100644 index 0000000000..87949dd742 --- /dev/null +++ b/docs/source/api/v5/cdns_id.rst @@ -0,0 +1,136 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cdns-id: + +*************** +``cdns/{{ID}}`` +*************** + +``PUT`` +======= +Allows a user to edit a specific CDN + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: CDN:UPDATE, CDN:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+---------------------------------------------------+ + | Name | Description | + +======+===================================================+ + | ID | Integral, unique identifier for the CDN to update | + +------+---------------------------------------------------+ + +:dnssecEnabled: If ``true``, this CDN will use DNSSEC, if ``false`` it will not +:domainName: The top-level domain (TLD) belonging to the CDN +:name: Name of the new CDN + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/cdns/3 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 63 + Content-Type: application/json + + {"name": "quest", "domainName": "test", "dnssecEnabled": false} + +Response Structure +------------------ +:dnssecEnabled: ``true`` if the CDN uses DNSSEC, ``false`` otherwise +:domainName: The top-level domain (TLD) assigned to the newly created CDN +:id: An integral, unique identifier for the newly created CDN +:name: The newly created CDN's name + + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: sI1hzBwG+/VAzoFY20kqGFA2RgrUOThtMeeJqk0ZxH3TRxTWuA8BetACct/XICC3n7hPDLlRVpwckEyBdyJkXg== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 14 Nov 2018 20:54:33 GMT + Content-Length: 174 + + { "alerts": [ + { + "text": "cdn was updated.", + "level": "success" + } + ], + "response": { + "dnssecEnabled": false, + "domainName": "test", + "id": 4, + "lastUpdated": "2018-11-14 20:54:33+00", + "name": "quest" + }} + +``DELETE`` +========== +Allows a user to delete a specific CDN + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: CDN:DELETE, CDN:READ +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+------------------------------------------------------+ + | Name | Description | + +======+======================================================+ + | ID | The integral, unique identifier of the CDN to delete | + +------+------------------------------------------------------+ + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: Zy4cJN6BEct4ltFLN4e296mM8XnzOs0EQ3/jp4TA3L+g8qtkI0WrL+ThcFq4xbJPU+KHVDSi+b0JBav3xsYPqQ== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 14 Nov 2018 20:51:23 GMT + Content-Length: 58 + + { "alerts": [ + { + "text": "cdn was deleted.", + "level": "success" + } + ]} diff --git a/docs/source/api/v5/cdns_id_queue_update.rst b/docs/source/api/v5/cdns_id_queue_update.rst new file mode 100644 index 0000000000..17e457e070 --- /dev/null +++ b/docs/source/api/v5/cdns_id_queue_update.rst @@ -0,0 +1,89 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cdns-id-queue_update: + +**************************** +``cdns/{{ID}}/queue_update`` +**************************** + +``POST`` +======== +:term:`Queue` or "dequeue" updates for all servers assigned to a specific CDN. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: SERVER:QUEUE, CDN:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+---------------------------------------------------------------------------+ + | Name | Description | + +======+===========================================================================+ + | ID | The integral, unique identifier for the CDN on which to (de)queue updates | + +------+---------------------------------------------------------------------------+ + +.. table:: Request Query Parameters + + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +===========+==========+===============================================================================================================+ + | type | no | The name of the ``type`` of servers, for which the updates need to be queued or dequeued. | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | profile | no | The name of the ``profile`` of servers, for which the updates need to be queued or dequeued. | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + +:action: One of "queue" or "dequeue" as appropriate + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/cdns/2/queue_update?type=EDGE HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 19 + Content-Type: application/json + + {"action": "queue"} + +Response Structure +------------------ +:action: The action processed, either ``"queue"`` or ``"dequeue"`` +:cdnId: The integral, unique identifier for the CDN on which :term:`Queue Updates` was performed or cleared + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: rBpFfrrP+9IFkwsRloEM+v+I8MuBZDXqFu+WUTGtRGypnAn2gHooPoNQRyVvJGjyIQrLXLvqjEtve+lH2Tj4uw== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 14 Nov 2018 21:02:07 GMT + Content-Length: 41 + + { "response": { + "action": "queue", + "cdnId": 2 + }} diff --git a/docs/source/api/v5/cdns_name_configs_monitoring.rst b/docs/source/api/v5/cdns_name_configs_monitoring.rst new file mode 100644 index 0000000000..552be5f98c --- /dev/null +++ b/docs/source/api/v5/cdns_name_configs_monitoring.rst @@ -0,0 +1,295 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cdns-name-configs-monitoring: + +************************************ +``cdns/{{name}}/configs/monitoring`` +************************************ + +.. seealso:: :ref:`health-proto` + +``GET`` +======= +Retrieves information concerning the monitoring configuration for a specific CDN. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: MONITOR-CONFIG:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+------------------------------------------------------------------------+ + | Name | Description | + +======+========================================================================+ + | name | The name of the CDN for which monitoring configuration will be fetched | + +------+------------------------------------------------------------------------+ + +Response Structure +------------------ +:cacheGroups: An array of objects representing each of the :term:`Cache Groups` being monitored within this CDN + + :coordinates: An object representing the geographic location of this :term:`Cache Group` + + :latitude: This :ref:`Cache Group's latitude ` as a floating-point number + :longitude: This :ref:`Cache Group's longitude ` as a floating-point number + + :name: A string that is this :ref:`Cache Group's name ` + +:config: A collection of parameters used to configure the monitoring behaviour of Traffic Monitor + + :health.polling.interval: An interval in milliseconds on which to poll for cache statistics + :heartbeat.polling.interval: An interval in milliseconds on which to poll for health statistics. If missing, defaults to ``health.polling.interval``. + :tm.polling.interval: The interval at which to poll for configuration updates + +:deliveryServices: An array of objects representing each :term:`Delivery Service` provided by this CDN + + :hostRegexes: An array of strings which are the Delivery Service's HOST_REGEXP-type regexes + :status: The :term:`Delivery Service`'s status + :topology: A string that is the name of the Delivery Service's :term:`Topology` (if assigned one) + :totalKbpsThreshold: A threshold rate of data transfer this :term:`Delivery Service` is configured to handle, in Kilobits per second + :totalTpsThreshold: A threshold amount of transactions per second that this :term:`Delivery Service` is configured to handle + :type: A string that is the Delivery Service's type category (``"HTTP"`` or ``"DNS"``) + :xmlId: A string that is the :ref:`Delivery Service's XMLID ` + +:profiles: An array of the :term:`Profiles` in use by the :term:`cache servers` and :term:`Delivery Services` belonging to this CDN + + :name: A string that is the :ref:`Profile's Name ` + :parameters: An array of the :term:`Parameters` in this :term:`Profile` that relate to monitoring configuration. This can be ``null`` if the servers using this :term:`Profile` cannot be monitored (e.g. Traffic Routers) + + :health.connection.timeout: A timeout value, in milliseconds, to wait before giving up on a health check request + :health.polling.url: A URL to request for polling health. Substitutions can be made in a shell-like syntax using the properties of an object from the ``"trafficServers"`` array + :health.threshold.availableBandwidthInKbps: The total amount of bandwidth that servers using this profile are allowed - across all network interfaces - in Kilobits per second. This is a string and using comparison operators to specify ranges, e.g. ">10" means "more than 10 kbps" + :health.threshold.loadavg: The UNIX loadavg at which the server should be marked "unhealthy" + + .. seealso:: :manpage:`uptime(1)` + + :health.threshold.queryTime: The highest allowed length of time for completing health queries (after connection has been established) in milliseconds + :history.count: The number of past events to store; once this number is reached, the oldest event will be forgotten before a new one can be added + + :type: A string that names the :ref:`Profile's Type ` + +:topologies: A map of :term:`Topology` names to objects + + :nodes: An array of strings which are the names of the EDGE_LOC-type cache groups in the topology + +:trafficMonitors: An array of objects representing each Traffic Monitor that monitors this CDN (this is used by Traffic Monitor's "peer polling" function) + + :cachegroup: The name of the :term:`Cache Group` to which this Traffic Monitor belongs + :fqdn: An :abbr:`FQDN (Fully Qualified Domain Name)` that resolves to the IPv4 (and/or IPv6) address of the server running this Traffic Monitor instance + :hostname: The hostname of the server running this Traffic Monitor instance + :ip6: The IPv6 address of this Traffic Monitor - when applicable + :ip: The IPv4 address of this Traffic Monitor + :port: The port on which this Traffic Monitor listens for incoming connections + :profile: A string that is the :ref:`profile-name` of the :term:`Profile` assigned to this Traffic Monitor + :status: The status of the server running this Traffic Monitor instance + +:trafficServers: An array of objects that represent the :term:`cache servers` being monitored within this CDN + + :cachegroup: The :term:`Cache Group` to which this :term:`cache server` belongs + :deliveryServices: An array of objects which contain the XML IDs of the delivery services to which this cache server is assigned (this field is omitted entirely if no delivery services are assigned to this cache server) + + :xmlId: A string which is the XML ID of the delivery service + + :fqdn: An :abbr:`FQDN (Fully Qualified Domain Name)` that resolves to the :term:`cache server`'s IPv4 (or IPv6) address + :hashId: The (short) hostname for the :term:`cache server` - named "hashId" for legacy reasons + :hostName: The (short) hostname of the :term:`cache server` + :port: The port on which the :term:`cache server` listens for incoming connections + :profile: A string that is the :ref:`profile-name` of the :term:`Profile` assigned to this :term:`cache server` + :status: The status of the :term:`cache server` + :type: A string that names the :term:`Type` of the :term:`cache server` - should (ideally) be either ``EDGE`` or ``MID`` + :interfaces: A set of the network interfaces in use by the server. In most scenarios, only one will be present, but it is illegal for this set to be an empty collection. + + :ipAddresses: A set of objects representing IP Addresses assigned to this network interface. In most scenarios, only one or two (usually one IPv4 address and one IPv6 address) will be present, but it is illegal for this set to be an empty collection. + + :address: The actual IP address, including any mask as a CIDR-notation suffix + :gateway: Either the IP address of the network gateway for this address, or ``null`` to signify that no such gateway exists + :serviceAddress: A boolean that describes whether or not the server's main service is available at this IP address. When this property is ``true``, the IP address is referred to as a "service address". It is illegal for a server to not have at least one service address. It is also illegal for a server to have more than one service address of the same address family (i.e. more than one IPv4 service address and/or more than one IPv6 address). Finally, all service addresses for a server must be contained within one interface - which is therefore sometimes referred to as the "service interface" for the server. + + :maxBandwidth: The maximum healthy bandwidth allowed for this interface. If bandwidth exceeds this limit, Traffic Monitors will consider the entire server unhealthy - which includes *all* configured network interfaces. If this is ``null``, it has the meaning "no limit". It has no effect if ``monitor`` is not true for this interface. + + .. seealso:: :ref:`health-proto` + + :monitor: A boolean which describes whether or not this interface should be monitored by Traffic Monitor for statistics and health consideration. + :mtu: The :abbr:`MTU (Maximum Transmission Unit)` of this interface. If it is ``null``, it may be assumed that the information is either not available or not applicable for this interface. + :name: The name of the interface. No two interfaces of the same server may share a name. It is the same as the network interface's device name on the server, e.g. ``eth0``. + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: uLR+tRoqR8SYO38j3DV9wQ+IkJ7Kf+MCoFkcWZtsgbpLJ+0S6f+IiI8laNVeDgrM/P23MAQ6BSepm+EJRl1AXQ== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 14 Nov 2018 21:09:31 GMT + Transfer-Encoding: chunked + + { "response": { + "topologies": { + "example-topology": { + "nodes": [ + "CDN_in_a_Box_Edge" + ] + } + }, + "trafficServers": [ + { + "profile": "ATS_EDGE_TIER_CACHE", + "status": "REPORTED", + "port": 80, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "172.16.239.100", + "gateway": "172.16.239.0/24", + "serviceAddress": "true" + }, + { + "address": "fc01:9400:1000:8::100", + "gateway": "fc01::", + "serviceAddress": "true" + } + ], + "name": "eth0", + "monitor": "true", + "mtu": 9000, + "maxBandwidth": 150 + } + ], + "cachegroup": "CDN_in_a_Box_Edge", + "hostname": "edge", + "fqdn": "edge.infra.ciab.test", + "type": "EDGE", + "hashid": "edge" + }, + { + "profile": "ATS_MID_TIER_CACHE", + "status": "REPORTED", + "port": 80, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "172.16.239.120", + "gateway": "172.16.239.0/24", + "serviceAddress": "true" + }, + { + "address": "fc02:9400:1000:8::100", + "gateway": "fc02::", + "serviceAddress": "true" + } + ], + "name": "eth0", + "monitor": "true", + "mtu": 9000, + "maxBandwidth": 150 + } + ], + "cachegroup": "CDN_in_a_Box_Mid", + "hostname": "mid", + "fqdn": "mid.infra.ciab.test", + "type": "MID", + "hashid": "mid" + } + ], + "trafficMonitors": [ + { + "profile": "RASCAL-Traffic_Monitor", + "status": "ONLINE", + "ip": "172.16.239.40", + "ip6": "fc01:9400:1000:8::40", + "port": 80, + "cachegroup": "CDN_in_a_Box_Edge", + "hostname": "trafficmonitor", + "fqdn": "trafficmonitor.infra.ciab.test" + } + ], + "cacheGroups": [ + { + "name": "CDN_in_a_Box_Mid", + "coordinates": { + "latitude": 38.897663, + "longitude": -77.036574 + } + }, + { + "name": "CDN_in_a_Box_Edge", + "coordinates": { + "latitude": 38.897663, + "longitude": -77.036574 + } + } + ], + "profiles": [ + { + "name": "CCR_CIAB", + "type": "CCR", + "parameters": null + }, + { + "name": "ATS_EDGE_TIER_CACHE", + "type": "EDGE", + "parameters": { + "health.connection.timeout": 2000, + "health.polling.url": "http://${hostname}/_astats?application=&inf.name=${interface_name}", + "health.threshold.availableBandwidthInKbps": ">1750000", + "health.threshold.loadavg": "25.0", + "health.threshold.queryTime": 1000, + "history.count": 30 + } + }, + { + "name": "ATS_MID_TIER_CACHE", + "type": "MID", + "parameters": { + "health.connection.timeout": 2000, + "health.polling.url": "http://${hostname}/_astats?application=&inf.name=${interface_name}", + "health.threshold.availableBandwidthInKbps": ">1750000", + "health.threshold.loadavg": "25.0", + "health.threshold.queryTime": 1000, + "history.count": 30 + } + } + ], + "deliveryServices": [ + { + "xmlId": "example-ds", + "totalTpsThreshold": 0, + "status": "REPORTED", + "totalKbpsThreshold": 0, + "type": "DNS", + "topology": "example-topology", + "hostRegexes": [ + ".*\\.example-ds\\..*" + ] + } + ], + "config": { + "health.polling.interval": 6000, + "heartbeat.polling.interval": 3000, + "peers.polling.interval": 3000, + "tm.polling.interval": 2000 + } + }} diff --git a/docs/source/api/v5/cdns_name_dnsseckeys_ksk_generate.rst b/docs/source/api/v5/cdns_name_dnsseckeys_ksk_generate.rst new file mode 100644 index 0000000000..79fdd9b4ca --- /dev/null +++ b/docs/source/api/v5/cdns_name_dnsseckeys_ksk_generate.rst @@ -0,0 +1,49 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cdns-name-dnsseckeys-ksk-generate: + +***************************************** +``cdns/{{name}}/dnsseckeys/ksk/generate`` +***************************************** + +``POST`` +======== +Generates a new :abbr:`KSK (Key-Signing Key)` for a specific CDN. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: DNS-SEC:CREATE, CDN:UPDATE, CDN:READ +:Response Type: Object (string) + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+----------+-----------------------------------------------------------------------------------+ + | Name | Required | Description | + +======+==========+===================================================================================+ + | name | yes | The name of the CDN for which the :abbr:`KSK (Key-Signing Key)` will be generated | + +------+----------+-----------------------------------------------------------------------------------+ + +:expirationDays: The integral number of days until the newly generated :abbr:`KSK (Key-Signing Key)` expires +:effectiveDate: An optional string containing the date and time at which the newly generated :abbr:`KSK (Key-Signing Key)` becomes effective, in :RFC:`3339` format. Defaults to the current time if not specified + +Response Structure +------------------ +.. code-block:: json + :caption: Response Example + + { "response": "Successfully generated ksk dnssec keys for my-cdn-name" } diff --git a/docs/source/api/v5/cdns_name_federations.rst b/docs/source/api/v5/cdns_name_federations.rst new file mode 100644 index 0000000000..9bb5a00b52 --- /dev/null +++ b/docs/source/api/v5/cdns_name_federations.rst @@ -0,0 +1,197 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cdns-name-federations: + +***************************** +``cdns/{{name}}/federations`` +***************************** + +``GET`` +======= +Retrieves a list of federations in use by a specific CDN. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: CDN:READ, FEDERATION:READ, DELIVERY-SERVICE:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Path Parameters + + +-----------+---------------------------------------------------------------------------------------------------------------+ + | Name | Description | + +===========+===============================================================================================================+ + | name | The name of the CDN for which federations will be listed | + +-----------+---------------------------------------------------------------------------------------------------------------+ + +.. table:: Request Query Parameters + + +-----------+---------------------------------------------------------------------------------------------------------------+ + | Name | Description | + +===========+===============================================================================================================+ + | id | Return only the federation that has this id | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | orderby | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` | + | | array | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | sortOrder | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | limit | Choose the maximum number of results to return | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | offset | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | page | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long | + | | and the first page is 1. If ``offset`` was defined, this query parameter has no effect. ``limit`` must be | + | | defined to make use of ``page``. | + +-----------+---------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/cdns/CDN-in-a-Box/federations HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.62.0 + Accept: */* + Cookie: mojolicious=... + +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 + + :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. + +: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 + +.. 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": 48, + "description": "A test federation", + "lastUpdated": "2018-12-05 00:05:16+00", + "deliveryService": { + "id": 1, + "xmlId": "demo1" + } + } + ]} + +``POST`` +======== +Creates a new federation. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: FEDERATION:CREATE, FEDERATION:READ, CDN:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+----------------------------------------------------------------+ + | Name | Description | + +======+================================================================+ + | name | The name of the CDN for which a new federation will be created | + +------+----------------------------------------------------------------+ + +:cname: The Canonical Name (CNAME) used by the 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`` + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/cdns/CDN-in-a-Box/federations HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.62.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 72 + Content-Type: application/json + + { + "cname": "test.quest.", + "ttl": 48, + "description": "A test federation" + } + + +Response Structure +------------------ +: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 + +: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 + + +.. 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: rRsWAIhXzVlj8Hy+8aFjp4Jo1QGTK49m0N1AP5QDyyAZ1TfNIdgtcgiuehu7FiN1IPWRFiv6D9CygFYKGcVDOw== + x-server-name: traffic_ops_golang/ + content-length: 192 + date: Wed, 05 Dec 2018 00:05:16 GMT + + { "alerts": [ + { + "text": "cdnfederation was created.", + "level": "success" + } + ], + "response": { + "id": 1, + "cname": "test.quest.", + "ttl": 48, + "description": "A test federation", + "lastUpdated": "2018-12-05 00:05:16+00" + }} diff --git a/docs/source/api/v5/cdns_name_federations_id.rst b/docs/source/api/v5/cdns_name_federations_id.rst new file mode 100644 index 0000000000..6fee0b5b72 --- /dev/null +++ b/docs/source/api/v5/cdns_name_federations_id.rst @@ -0,0 +1,160 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cdns-name-federations-id: + +************************************ +``cdns/{{name}}/federations/{{ID}}`` +************************************ + +``PUT`` +======= +Updates a federation. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: FEDERATION:UPDATE, FEDERATION:READ, CDN:READ +:Response Type: Object + +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 | + +------+-------------------------------------------------------------------------------------+ + +:cname: The Canonical Name (CNAME) used by the 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`` + +.. code-block:: http + :caption: Request Example + + PUT /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=... + Content-Length: 33 + Content-Type: application/json + + { + "cname": "foo.bar.", + "ttl": 48 + } + + +Response Structure +------------------ +: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 + +: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 + + +.. 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: qcjfQ+gDjNxYQ1aq+dlddgrkFWnkFYxsFF+SHDqqH0uVHBVksmU0aTFgltozek/u6wbrGoR1LFf9Fr1C1SbigA== + x-server-name: traffic_ops_golang/ + content-length: 174 + date: Wed, 05 Dec 2018 01:03:40 GMT + + { "alerts": [ + { + "text": "cdnfederation was updated.", + "level": "success" + } + ], + "response": { + "id": 1, + "cname": "foo.bar.", + "ttl": 48, + "description": null, + "lastUpdated": "2018-12-05 01:03:40+00" + }} + + +``DELETE`` +========== +Deletes a specific federation. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: FEDERATION:DELETE, FEDERATION:READ, CDN:READ +:Response Type: ``undefined`` + +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 | + +------+-------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /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 +------------------ +.. 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: Cnkfj6dmzTD3if9oiDq33tqf7CnAflKK/SPgqJyfu6HUfOjLJOgKIZvkcs2wWY6EjLVdw5qsatsd4FPoCyjvcw== + x-server-name: traffic_ops_golang/ + content-length: 68 + date: Wed, 05 Dec 2018 01:17:24 GMT + + { "alerts": [ + { + "text": "cdnfederation was deleted.", + "level": "success" + } + ]} diff --git a/docs/source/api/v5/cdns_name_health.rst b/docs/source/api/v5/cdns_name_health.rst new file mode 100644 index 0000000000..6d2f506ed1 --- /dev/null +++ b/docs/source/api/v5/cdns_name_health.rst @@ -0,0 +1,90 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cdns-name-health: + +************************ +``cdns/{{name}}/health`` +************************ + +``GET`` +======= +Retrieves the health of all :term:`Cache Groups` for a given CDN. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: CDN:READ, CACHE-GROUP:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+-------------------------------------------------------+ + | Name | Description | + +======+=======================================================+ + | name | The name of the CDN for which health will be reported | + +------+-------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/cdns/CDN-in-a-Box/health HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + +Response Structure +------------------ +:cachegroups: An array of objects describing the health of each :term:`Cache Group` + + :name: A string that is the :ref:`Cache Group's Name ` + :offline: The number of OFFLINE :term:`cache servers` in the :term:`Cache Group` + :online: The number of ONLINE :term:`cache servers` in the :term:`Cache Group` + +:totalOffline: Total number of OFFLINE :term:`cache servers` across all :term:`Cache Groups` which are assigned to the CDN defined by the ``name`` request path parameter +:totalOnline: Total number of ONLINE :term:`cache servers` across all :term:`Cache Groups` which are assigned to the CDN defined by the ``name`` request path parameter + +.. 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 + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Cache-Control: no-cache, no-store, max-age=0, must-revalidate + Content-Encoding: gzip + Content-Length: 108 + Content-Type: application/json + Date: Tue, 03 Dec 2019 21:33:59 GMT + X-Server-Name: traffic_ops_golang/ + Set-Cookie: mojolicious=...; expires=Wed, 04 Dec 2019 01:33:59 GMT; path=/; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: KpXViXeAgch58ueQqdyU8NuINBw1EUedE6Rv2ewcLUajJp6kowdbVynpwW7XiSvAyHdtClIOuT3OkhIimghzSA== + + { "response": { + "totalOffline": 0, + "totalOnline": 1, + "cachegroups": [ + { + "offline": 0, + "name": "CDN_in_a_Box_Edge", + "online": 1 + } + ] + }} diff --git a/docs/source/api/v5/cdns_name_name.rst b/docs/source/api/v5/cdns_name_name.rst new file mode 100644 index 0000000000..0cd153dd7f --- /dev/null +++ b/docs/source/api/v5/cdns_name_name.rst @@ -0,0 +1,64 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cdns-name-name: + +********************** +``cdns/name/{{name}}`` +********************** + +``DELETE`` +========== +Allows a user to delete a CDN by name + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: CDN:DELETE +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+---------------------------------------------+ + | Name | Description | + +======+=============================================+ + | name | The name of the CDN to be deleted | + +------+---------------------------------------------+ + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: Zy4cJN6BEct4ltFLN4e296mM8XnzOs0EQ3/jp4TA3L+g8qtkI0WrL+ThcFq4xbJPU+KHVDSi+b0JBav3xsYPqQ== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 14 Nov 2018 20:59:22 GMT + Content-Length: 58 + + { "alerts": [ + { + "text": "cdn was deleted.", + "level": "success" + } + ]} + diff --git a/docs/source/api/v5/cdns_name_name_dnsseckeys.rst b/docs/source/api/v5/cdns_name_name_dnsseckeys.rst new file mode 100644 index 0000000000..bfa46790d3 --- /dev/null +++ b/docs/source/api/v5/cdns_name_name_dnsseckeys.rst @@ -0,0 +1,142 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cdns-name-name-dnsseckeys: + +********************************* +``cdns/name/{{name}}/dnsseckeys`` +********************************* + +``GET`` +======= +Gets a list of DNSSEC keys for CDN and all associated :term:`Delivery Services`. Before returning response to user, this will make sure DNSSEC keys for all :term:`Delivery Services` exist and are not expired. If they don't exist or are expired, they will be (re-)generated. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: DNS-SEC:READ, CDN:READ, DELIVERY-SERVICE:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+----------------------------------------------------+ + | Name | Description | + +======+====================================================+ + | name | The name of the CDN for which keys will be fetched | + +------+----------------------------------------------------+ + +Response Structure +------------------ +:name: The name of the CDN or :term:`Delivery Service` to which the enclosed keys belong + + :zsk: The short-term :abbr:`ZSK (Zone-Signing Key)` + + :expirationDate: A Unix epoch timestamp (in seconds) representing the date and time whereupon the key will expire + :inceptionDate: A Unix epoch timestamp (in seconds) representing the date and time when the key was created + :name: The name of the domain for which this key will be used + :private: Encoded private key + :public: Encoded public key + :ttl: The time for which the key should be trusted by the client + + :ksk: The long-term :abbr:`KSK (Key-Signing Key)` + + :dsRecord: An optionally present object containing information about the algorithm used to generate the key + + :algorithm: The name of the algorithm used to generate the key + :digest: A hash of the DNSKEY record + :digestType: The type of hash algorithm used to create the value of ``digest`` + + :expirationDate: A Unix epoch timestamp (in seconds) representing the date and time whereupon the key will expire + :inceptionDate: A Unix epoch timestamp (in seconds) representing the date and time when the key was created + :name: The name of the domain for which this key will be used + :private: Encoded private key + :public: Encoded public key + :ttl: The time for which the key should be trusted by the client + +.. code-block:: json + :caption: Response Example + + { "response": { + "cdn1": { + "zsk": { + "ttl": "60", + "inceptionDate": "1426196750", + "private": "zsk private key", + "public": "zsk public key", + "expirationDate": "1428788750", + "name": "foo.kabletown.com." + }, + "ksk": { + "name": "foo.kabletown.com.", + "expirationDate": "1457732750", + "public": "ksk public key", + "private": "ksk private key", + "inceptionDate": "1426196750", + "ttl": "60", + "dsRecord": { + "algorithm": "5", + "digestType": "2", + "digest": "abc123def456" + } + } + }, + "ds-01": { + "zsk": { + "ttl": "60", + "inceptionDate": "1426196750", + "private": "zsk private key", + "public": "zsk public key", + "expirationDate": "1428788750", + "name": "ds-01.foo.kabletown.com." + }, + "ksk": { + "name": "ds-01.foo.kabletown.com.", + "expirationDate": "1457732750", + "public": "ksk public key", + "private": "ksk private key", + "inceptionDate": "1426196750" + } + } + }} + +``DELETE`` +========== +Delete DNSSEC keys for a CDN and all associated :term:`Delivery Services`. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: DNS-SEC:DELETE, CDN:UPDATE, DELIVERY-SERVICE:UPDATE, CDN:READ +:Response Type: Object (string) + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+-----------------------------------------------------------+ + | Name | Description | + +======+===========================================================+ + | name | The name of the CDN for which DNSSEC keys will be deleted | + +------+-----------------------------------------------------------+ + +Response Structure +------------------ +.. code-block:: json + :caption: Response Example + + { + "response": "Successfully deleted dnssec keys for test" + } + diff --git a/docs/source/api/v5/cdns_name_name_sslkeys.rst b/docs/source/api/v5/cdns_name_name_sslkeys.rst new file mode 100644 index 0000000000..4418ed583f --- /dev/null +++ b/docs/source/api/v5/cdns_name_name_sslkeys.rst @@ -0,0 +1,68 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cdns-name-name-sslkeys: + +****************************** +``cdns/name/{{name}}/sslkeys`` +****************************** + +``GET`` +======= +Returns SSL certificates for all :term:`Delivery Services` that are a part of the CDN. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: DS-SECURITY-KEY:READ, CDN:READ, DELIVERY-SERVICE:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+----------------------------------------------------+ + | Name | Description | + +======+====================================================+ + | name | The name of the CDN for which keys will be fetched | + +------+----------------------------------------------------+ + +Response Structure +------------------ +:certificate: An object representing The SSL keys used for the :term:`Delivery Service` identified by ``deliveryservice`` + + :key: Base 64-encoded private key for SSL certificate + :crt: Base 64-encoded SSL certificate + +:deliveryservice: A string that is the :ref:`ds-xmlid` of the :term:`Delivery Service` using the SSL key within ``certificate`` + +.. code-block:: json + :caption: Response Example + + { "response": [ + { + "deliveryservice": "ds1", + "certificate": { + "crt": "base64encodedcrt1", + "key": "base64encodedkey1" + } + }, + { + "deliveryservice": "ds2", + "certificate": { + "crt": "base64encodedcrt2", + "key": "base64encodedkey2" + } + } + ]} diff --git a/docs/source/api/v5/cdns_name_snapshot.rst b/docs/source/api/v5/cdns_name_snapshot.rst new file mode 100644 index 0000000000..39b0b8d7c4 --- /dev/null +++ b/docs/source/api/v5/cdns_name_snapshot.rst @@ -0,0 +1,572 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cdns-name-snapshot: + +************************** +``cdns/{{name}}/snapshot`` +************************** +.. caution:: This page is a stub! Much of it may be missing or just downright wrong - it needs a lot of love from people with the domain knowledge required to update it. + +``GET`` +======= +Retrieves the *current* :term:`Snapshot` for a CDN, which represents the current *operating state* of the CDN, **not** the current *configuration* of the CDN. The contents of this :term:`Snapshot` are currently used by Traffic Monitor and Traffic Router. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: CDN-SNAPSHOT:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+--------------------------------------------------------------------+ + | Name | Description | + +======+====================================================================+ + | name | The name of the CDN for which a :term:`Snapshot` shall be returned | + +------+--------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/cdns/CDN-in-a-Box/snapshot HTTP/1.1 + User-Agent: python-requests/2.23.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + +Response Structure +------------------ +:config: An object containing basic configurations on the actual CDN object + + :api.cache-control.max-age: A string containing an integer which specifies the value of ``max-age`` in the :mailheader:`Cache-Control` header of some HTTP responses, likely the :ref:`tr-api` responses + :certificates.polling.interval: A string containing an integer which specifies the interval, in seconds, on which other Traffic Control components should check for updated SSL certificates + :consistent.dns.routing: A string containing a boolean which indicates whether DNS routing will use a consistent hashing method or "round-robin" + + "false" + The "round-robin" method will be used to define DNS routing + "true" + A consistent hashing method will be used to define DNS routing + + :coveragezone.polling.interval: A string containing an integer which specifies the interval, in seconds, on which Traffic Routers should check for a new Coverage Zone file + :coveragezone.polling.url: The URL where a :term:`Coverage Zone File` may be requested by Traffic Routers + :dnssec.dynamic.response.expiration: A string containing a number and unit suffix that specifies the length of time for which dynamic responses to DNSSEC lookup queries should remain valid + :dnssec.dynamic.concurrencylevel: An integer that defines the size of the concurrency level (threads) of the Guava cache used by ZoneManager to store zone material + :dnssec.dynamic.initialcapacity: An integer that defines the initial size of the Guava cache, default is 10000. Too low of a value can lead to expensive resizing + :dnssec.init.timeout: An integer that defines the number of minutes to allow for zone generation, this bounds the zone priming activity + :dnssec.enabled: A string that tells whether or not the CDN uses DNSSEC; one of: + + "false" + DNSSEC is not used within this CDN + "true" + DNSSEC is used within this CDN + + :domain_name: A string that is the :abbr:`TLD (Top-Level Domain)` served by the CDN + :federationmapping.polling.interval: A string containing an integer which specifies the interval, in seconds, on which other Traffic Control components should check for new federation mappings + :federationmapping.polling.url: The URL where Traffic Control components can request federation mappings + :geolocation.polling.interval: A string containing an integer which specifies the interval, in seconds, on which other Traffic Control components should check for new IP-to-geographic-location mapping databases + :geolocation.polling.url: The URL where Traffic Control components can request IP-to-geographic-location mapping database files + :keystore.maintenance.interval: A string containing an integer which specifies the interval, in seconds, on which Traffic Routers should refresh their zone caches + :neustar.polling.interval: A string containing an integer which specifies the interval, in seconds, on which other Traffic Control components should check for new "Neustar" databases + :neustar.polling.url: The URL where Traffic Control components can request "Neustar" databases + :soa: An object defining the :abbr:`SOA (Start of Authority)` for the CDN's :abbr:`TLD (Top-Level Domain)` (defined in ``domain_name``) + + :admin: The name of the administrator for this zone - i.e. the RNAME + + .. note:: This rarely represents a proper email address, unfortunately. + + :expire: A string containing an integer that sets the number of seconds after which secondary name servers should stop answering requests for this zone if the master does not respond + :minimum: A string containing an integer that sets the :abbr:`TTL (Time To Live)` - in seconds - of the record for the purpose of negative caching + :refresh: A string containing an integer that sets the number of seconds after which secondary name servers should query the master for the :abbr:`SOA (Start of Authority)` record, to detect zone changes + :retry: A string containing an integer that sets the number of seconds after which secondary name servers should retry to request the serial number from the master if the master does not respond + + .. note:: :rfc:`1035` dictates that this should always be less than ``refresh``. + + .. seealso:: `The Wikipedia page on Start of Authority records `_. + + :steeringmapping.polling.interval: A string containing an integer which specifies the interval, in seconds, on which Traffic Control components should check for new steering mappings + :ttls: An object that contains keys which are types of DNS records that have values which are strings containing integers that specify the time for which a response to the specific type of record request should remain valid + :zonemanager.cache.maintenance.interval: A configuration option for the ZoneManager Java class of Traffic Router + :zonemanager.threadpool.scale: A configuration option for the ZoneManager Java class of Traffic Router + +:contentRouters: An object containing keys which are the (short) hostnames of the Traffic Routers that serve requests for :term:`Delivery Services` in this CDN + + :api.port: A string containing the port number on which the :ref:`tr-api` is served by this Traffic Router via HTTP + :secure.api.port: An optionally present string containing the port number on which the :ref:`tr-api` is served by this Traffic Router via HTTPS + :fqdn: This Traffic Router's :abbr:`FQDN (Fully Qualified Domain Name)` + :httpsPort: The port number on which this Traffic Router listens for incoming HTTPS requests + :ip: This Traffic Router's IPv4 address + :ip6: This Traffic Router's IPv6 address + :location: A string which is the :ref:`cache-group-name` of the :term:`Cache Group` to which this Traffic Router belongs + :port: The port number on which this Traffic Router listens for incoming HTTP requests + :profile: The :ref:`profile-name` of the :term:`Profile` used by this Traffic Router + :status: The health status of this Traffic Router + + .. seealso:: :ref:`health-proto` + +:contentServers: An object containing keys which are the (short) hostnames of the :term:`Edge-tier cache servers` in the CDN; the values corresponding to those keys are routing information for said servers + + :cacheGroup: A string that is the :ref:`cache-group-name` of the :term:`Cache Group` to which the server belongs + :capabilities: An array of this :term:`Cache Server`'s :term:`Server Capabilities`. If the Cache Server has no Server Capabilities, this field is omitted. + :deliveryServices: An object containing keys which are the names of :term:`Delivery Services` to which this :term:`cache server` is assigned; the values corresponding to those keys are arrays of :abbr:`FQDNs (Fully Qualified Domain Names)` that resolve to this :term:`cache server` + + .. note:: Only :term:`Edge-tier cache servers` can be assigned to a :term:`Delivery Service`, and therefore this field will only be present when ``type`` is ``"EDGE"``. + + :fqdn: The server's :abbr:`FQDN (Fully Qualified Domain Name)` + :hashCount: The number of servers to be placed into a single "hash ring" in Traffic Router + :hashId: A unique string to be used as the key for hashing servers - as of version 3.0.0 of Traffic Control, this is always the same as the server's (short) hostname and only still exists for legacy compatibility reasons + :httpsPort: The port on which the :term:`cache server` listens for incoming HTTPS requests + :interfaceName: The name of the main network interface device used by this :term:`cache server` + :ip6: The server's IPv6 address + :ip: The server's IPv4 address + :locationId: This field is exactly the same as ``cacheGroup`` and only exists for legacy compatibility reasons + :port: The port on which this :term:`cache server` listens for incoming HTTP requests + :profile: The :ref:`profile-name` of the :term:`Profile` used by the :term:`cache server` + :routingDisabled: An integer representing the boolean concept of whether or not Traffic Routers should route client traffic to this :term:`cache server`; one of: + + 0 + Do not route traffic to this server + 1 + Route traffic to this server normally + + :status: This :term:`cache server`'s status + + .. seealso:: :ref:`health-proto` + + :type: The :term:`Type` of this :term:`cache server`; which ought to be one of (but in practice need not be in certain special circumstances): + + EDGE + This is an :term:`Edge-tier cache server` + MID + This is a :term:`Mid-tier cache server` + +:deliveryServices: An object containing keys which are the :ref:`xml_ids ` of all of the :term:`Delivery Services` within the CDN + + :anonymousBlockingEnabled: A string containing a boolean that tells whether or not :ref:`ds-anonymous-blocking` is set on this :term:`Delivery Service`; one of: + + "true" + Anonymized IP addresses are blocked by this :term:`Delivery Service` + "false" + Anonymized IP addresses are not blocked by this :term:`Delivery Service` + + .. seealso:: :ref:`anonymous_blocking-qht` + + :consistentHashQueryParameters: A set of query parameters that Traffic Router should consider when determining a consistent hash for a given client request. + + :consistentHashRegex: An optional regular expression that will ensure clients are consistently routed to a :term:`cache server` based on matches to it. + + :coverageZoneOnly: A string containing a boolean that tells whether or not this :term:`Delivery Service` routes traffic based only on its :term:`Coverage Zone File` + + .. seealso:: :ref:`ds-geo-limit` + + :deepCachingType: A string that defines the :ref:`ds-deep-caching` setting of this :term:`Delivery Service` + :dispersion: An object describing the "dispersion" - or number of :term:`cache servers` within a single :term:`Cache Group` across which the same content is spread - within the :term:`Delivery Service` + + :limit: The maximum number of :term:`cache servers` in which the response to a single request URL will be stored + + .. note:: If this is greater than the number of :term:`cache servers` in the :term:`Cache Group` chosen to service the request, then content will be spread across all of them. That is, it causes no problems. + + :shuffled: A string containing a boolean that tells whether the :term:`cache servers` chosen for content dispersion are chosen randomly or based on a consistent hash of the request URL; one of: + + "false" + :term:`cache servers` will be chosen consistently + "true" + :term:`cache servers` will be chosen at random + + :domains: An array of domains served by this :term:`Delivery Service` + :ecsEnabled: A string containing a boolean from :ref:`ds-ecs` that tells whether EDNS0 client subnet is enabled on this :term:`Delivery Service`; one of: + + "false" + EDNS0 client subnet is not enabled on this :term:`Delivery Service` + "true" + EDNS0 client subnet is enabled on this :term:`Delivery Service` + + :geolocationProvider: The name of a provider for IP-to-geographic-location mapping services - currently the only valid value is ``"maxmindGeolocationService"`` + :ip6RoutingEnabled: A string containing a boolean that defines the :ref:`ds-ipv6-routing` setting for this :term:`Delivery Service`; one of: + + "false" + IPv6 traffic will not be routed by this :term:`Delivery Service` + "true" + IPv6 traffic will be routed by this :term:`Delivery Service` + + :matchList: An array of methods used by Traffic Router to determine whether or not a request can be serviced by this :term:`Delivery Service` + + :pattern: A regular expression - the use of this pattern is dependent on the ``type`` field (backslashes are escaped) + :setNumber: An integral, unique identifier for the set of types to which the ``type`` field belongs + :type: The name of the :term:`Type` of match performed using ``pattern`` to determine whether or not to use this :term:`Delivery Service` + + HOST_REGEXP + Use the :term:`Delivery Service` if ``pattern`` matches the :mailheader:`Host` HTTP header of an HTTP request, or the name requested for resolution in a DNS request + HEADER_REGEXP + Use the :term:`Delivery Service` if ``pattern`` matches an HTTP header (both the name and value) in an HTTP request\ [#httpOnly]_ + PATH_REGEXP + Use the :term:`Delivery Service` if ``pattern`` matches the request path of this :term:`Delivery Service`'s URL\ [#httpOnly]_ + STEERING_REGEXP + Use the :term:`Delivery Service` if ``pattern`` matches the :ref:`ds-xmlid` of one of this :term:`Delivery Service`'s "Steering" target :term:`Delivery Services` + + :missLocation: An object representing the default geographic coordinates to use for a client when lookup of their IP has failed in both the :term:`Coverage Zone File` (and/or possibly the :term:`Deep Coverage Zone File`) and the IP-to-geographic-location database + + :lat: Geographic latitude as a floating point number + :long: Geographic longitude as a floating point number + + :protocol: An object that describes how the :term:`Delivery Service` ought to handle HTTP requests both with and without TLS encryption + + :acceptHttps: A string containing a boolean that tells whether HTTPS requests should be normally serviced by this :term:`Delivery Service`; one of: + + "false" + Refuse to service HTTPS requests + "true" + Service HTTPS requests normally + + :redirectToHttps: A string containing a boolean that tells whether HTTP requests ought to be re-directed to use HTTPS; one of: + + "false" + Do not redirect unencrypted traffic; service it normally + "true" + Respond to HTTP requests with instructions to use HTTPS instead + + .. seealso:: :ref:`ds-protocol` + + :regionalGeoBlocking: A string containing a boolean that defines the :ref:`ds-regionalgeo` setting of this :term:`Delivery Service`; one of: + + "false" + Regional Geographic Blocking is not used by this :term:`Delivery Service` + "true" + Regional Geographic Blocking is used by this :term:`Delivery Service` + + .. seealso:: :ref:`regionalgeo-qht` + :requiredCapabilities: An array of this Delivery Service's :term:`required capabilities `. If there are no required capabilities, this field is omitted. + :routingName: A string that is this :ref:`Delivery Service's Routing Name ` + :soa: An object defining the :abbr:`SOA (Start of Authority)` record for the :term:`Delivery Service`'s :abbr:`TLDs (Top-Level Domains)` (defined in ``domains``) + + :admin: The name of the administrator for this zone - i.e. the RNAME + + .. note:: This rarely represents a proper email address, unfortunately. + + :expire: A string containing an integer that sets the number of seconds after which secondary name servers should stop answering requests for this zone if the master does not respond + :minimum: A string containing an integer that sets the :abbr:`TTL (Time To Live)` - in seconds - of the record for the purpose of negative caching + :refresh: A string containing an integer that sets the number of seconds after which secondary name servers should query the master for the :abbr:`SOA (Start of Authority)` record, to detect zone changes + :retry: A string containing an integer that sets the number of seconds after which secondary name servers should retry to request the serial number from the master if the master does not respond + + .. note:: :rfc:`1035` dictates that this should always be less than ``refresh``. + + .. seealso:: `The Wikipedia page on Start of Authority records `_. + + :sslEnabled: A string containing a boolean that tells whether this :term:`Delivery Service` uses SSL; one of: + + "false" + SSL is not used by this :term:`Delivery Service` + "true" + SSL is used by this :term:`Delivery Service` + + .. seealso:: :ref:`ds-protocol` + + :topology: The name of the :term:`Topology` that this :term:`Delivery Service` is assigned to. If the Delivery Service is not assigned to a topology, this field is omitted. + :ttls: An object that contains keys which are types of DNS records that have values which are strings containing integers that specify the time for which a response to the specific type of record request should remain valid + + .. note:: This overrides ``config.ttls``. + +:edgeLocations: An object containing keys which are the names of Edge-Tier :term:`Cache Groups` within the CDN + + :backupLocations: An object that describes this :ref:`Cache Group's Fallbacks ` + + :fallbackToClosest: A string containing a boolean which defines the :ref:`cache-group-fallback-to-closest` behavior of this :term:`Cache Group`; one of: + + "false" + Do not fall back on the closest available :term:`Cache Group` + "true" + Fall back on the closest available :term:`Cache Group` + + :list: If this :term:`Cache Group` has any :ref:`cache-group-fallbacks`, this key will appear and will be an array of those :ref:`Cache Groups' Names ` + + :latitude: A floating point number that defines this :ref:`Cache Group's Latitude ` + :localizationMethods: An array of strings that represents this :ref:`Cache Group's Localization Methods ` + :longitude: A floating point number that defines this :ref:`Cache Group's Longitude ` + +:monitors: An object containing keys which are the (short) hostnames of Traffic Monitors within this CDN + + :fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of this Traffic Monitor + :httpsPort: The port number on which this Traffic Monitor listens for incoming HTTPS requests + :ip6: This Traffic Monitor's IPv6 address + :ip: This Traffic Monitor's IPv4 address + :location: A string which is the :ref:`cache-group-name` of the :term:`Cache Group` to which this Traffic Monitor belongs + :port: The port number on which this Traffic Monitor listens for incoming HTTP requests + :profile: A string which is the :ref:`profile-name` of the :term:`Profile` used by this Traffic Monitor + + .. note:: For legacy reasons, this must always start with "RASCAL-". + + :status: The health status of this Traffic Monitor + + .. seealso:: :ref:`health-proto` + +:stats: An object containing metadata information regarding the CDN + + :CDN_name: The name of this CDN + :date: The UNIX epoch timestamp date in the Traffic Ops server's own timezone + :tm_host: The :abbr:`FQDN (Fully Qualified Domain Name)` of the Traffic Ops server + :tm_user: The username of the currently logged-in user + :tm_version: The full version number of the Traffic Ops server, including release number, git commit hash, and supported Enterprise Linux version + +:topologies: An array of :term:`Topologies` where each key is the name of that Topology. + + :nodes: An array of the names of the :term:`Edge-Tier` :term:`Cache Groups` in this :term:`Topology`. :term:`Mid-Tier` Cache Groups in the topology are not included. + +:trafficRouterLocations: An object containing keys which are the :ref:`names of Cache Groups ` within the CDN which contain Traffic Routers + + :backupLocations: An object that describes this :ref:`Cache Group's Fallbacks ` + + :fallbackToClosest: A string containing a boolean which defines this :ref:`Cache Group's Fallback to Closest ` setting; one of: + + "false" + Do not fall back on the closest available :term:`Cache Group` + "true" + Fall back on the closest available :term:`Cache Group` + + :latitude: A floating point number that defines this :ref:`Cache Group's Latitude ` + :localizationMethods: An array of strings that represents this :ref:`Cache Group's Localization Methods ` + :longitude: A floating point number that defines this :ref:`Cache Group's Longitude ` + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Wed, 27 May 2020 18:33:17 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: B5qdN9URIfu11gQxPZ8YaaMvy2HMrzsnrpt6vF037yv6OQiKCRyrUMX6wYs7QW4YVaeUrvmS2ya5l2YC0kvNAg== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 27 May 2020 17:33:17 GMT + Content-Length: 1360 + + + { + "response": { + "config": { + "api.cache-control.max-age": "10", + "certificates.polling.interval": "300000", + "consistent.dns.routing": "true", + "coveragezone.polling.interval": "3600000", + "coveragezone.polling.url": "https://static.infra.ciab.test:443/coverage-zone.json", + "dnssec.dynamic.response.expiration": "300s", + "dnssec.enabled": "false", + "domain_name": "mycdn.ciab.test", + "federationmapping.polling.interval": "60000", + "federationmapping.polling.url": "https://${toHostname}/api/5.0/federations/all", + "geolocation.polling.interval": "86400000", + "geolocation.polling.url": "https://static.infra.ciab.test:443/GeoLite2-City.mmdb.gz", + "keystore.maintenance.interval": "300", + "neustar.polling.interval": "86400000", + "neustar.polling.url": "https://static.infra.ciab.test:443/neustar.tar.gz", + "soa": { + "admin": "twelve_monkeys", + "expire": "604800", + "minimum": "30", + "refresh": "28800", + "retry": "7200" + }, + "steeringmapping.polling.interval": "60000", + "ttls": { + "A": "3600", + "AAAA": "3600", + "DNSKEY": "30", + "DS": "30", + "NS": "3600", + "SOA": "86400" + }, + "zonemanager.cache.maintenance.interval": "300", + "zonemanager.threadpool.scale": "0.50" + }, + "contentRouters": { + "trafficrouter": { + "api.port": "3333", + "fqdn": "trafficrouter.infra.ciab.test", + "httpsPort": 443, + "ip": "172.26.0.15", + "ip6": "", + "location": "CDN_in_a_Box_Edge", + "port": 80, + "profile": "CCR_CIAB", + "secure.api.port": "3443", + "status": "ONLINE" + } + }, + "contentServers": { + "edge": { + "cacheGroup": "CDN_in_a_Box_Edge", + "capabilities": [ + "RAM_DISK_STORAGE" + ], + "fqdn": "edge.infra.ciab.test", + "hashCount": 999, + "hashId": "edge", + "httpsPort": 443, + "interfaceName": "eth0", + "ip": "172.26.0.3", + "ip6": "", + "locationId": "CDN_in_a_Box_Edge", + "port": 80, + "profile": "ATS_EDGE_TIER_CACHE", + "routingDisabled": 0, + "status": "REPORTED", + "type": "EDGE" + }, + "mid": { + "cacheGroup": "CDN_in_a_Box_Mid", + "capabilities": [ + "RAM_DISK_STORAGE" + ], + "fqdn": "mid.infra.ciab.test", + "hashCount": 999, + "hashId": "mid", + "httpsPort": 443, + "interfaceName": "eth0", + "ip": "172.26.0.4", + "ip6": "", + "locationId": "CDN_in_a_Box_Mid", + "port": 80, + "profile": "ATS_MID_TIER_CACHE", + "routingDisabled": 0, + "status": "REPORTED", + "type": "MID" + } + }, + "deliveryServices": { + "demo1": { + "anonymousBlockingEnabled": "false", + "consistentHashQueryParams": [ + "abc", + "pdq", + "xxx", + "zyx" + ], + "coverageZoneOnly": "false", + "deepCachingType": "NEVER", + "dispersion": { + "limit": 1, + "shuffled": "true" + }, + "domains": [ + "demo1.mycdn.ciab.test" + ], + "ecsEnabled": "false", + "geolocationProvider": "maxmindGeolocationService", + "ip6RoutingEnabled": "true", + "matchsets": [ + { + "matchlist": [ + { + "match-type": "HOST", + "regex": ".*\\.demo1\\..*" + } + ], + "protocol": "HTTP" + } + ], + "missLocation": { + "lat": 42, + "long": -88 + }, + "protocol": { + "acceptHttps": "true", + "redirectToHttps": "false" + }, + "regionalGeoBlocking": "false", + "requiredCapabilities": [ + "RAM_DISK_STORAGE" + ], + "routingName": "video", + "soa": { + "admin": "traffic_ops", + "expire": "604800", + "minimum": "30", + "refresh": "28800", + "retry": "7200" + }, + "sslEnabled": "true", + "topology": "my-topology", + "ttls": { + "A": "", + "AAAA": "", + "NS": "3600", + "SOA": "86400" + } + } + }, + "edgeLocations": { + "CDN_in_a_Box_Edge": { + "backupLocations": { + "fallbackToClosest": "true" + }, + "latitude": 38.897663, + "localizationMethods": [ + "GEO", + "CZ", + "DEEP_CZ" + ], + "longitude": -77.036574 + } + }, + "monitors": { + "trafficmonitor": { + "fqdn": "trafficmonitor.infra.ciab.test", + "httpsPort": 443, + "ip": "172.26.0.14", + "ip6": "", + "location": "CDN_in_a_Box_Edge", + "port": 80, + "profile": "RASCAL-Traffic_Monitor", + "status": "ONLINE" + } + }, + "stats": { + "CDN_name": "CDN-in-a-Box", + "date": 1590600715, + "tm_host": "trafficops.infra.ciab.test:443", + "tm_user": "admin", + "tm_version": "development" + }, + "topologies": { + "my-topology": { + "nodes": [ + "CDN_in_a_Box_Edge" + ] + } + }, + "trafficRouterLocations": { + "CDN_in_a_Box_Edge": { + "backupLocations": { + "fallbackToClosest": "false" + }, + "latitude": 38.897663, + "localizationMethods": [ + "GEO", + "CZ", + "DEEP_CZ" + ], + "longitude": -77.036574 + } + } + } + } + + +.. [#httpOnly] These only apply to HTTP-:ref:`routed ` :term:`Delivery Services` diff --git a/docs/source/api/v5/cdns_name_snapshot_new.rst b/docs/source/api/v5/cdns_name_snapshot_new.rst new file mode 100644 index 0000000000..bbea4f55f9 --- /dev/null +++ b/docs/source/api/v5/cdns_name_snapshot_new.rst @@ -0,0 +1,571 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cdns-name-snapshot-new: + +****************************** +``cdns/{{name}}/snapshot/new`` +****************************** + +``GET`` +======= +Retrieves the *pending* :term:`Snapshot` for a CDN, which represents the current *configuration* of the CDN, **not** the current *operating state* of the CDN. The contents of this :term:`Snapshot` are currently used by Traffic Monitor and Traffic Router. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: CDN-SNAPSHOT:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+--------------------------------------------------------------------+ + | Name | Description | + +======+====================================================================+ + | name | The name of the CDN for which a :term:`Snapshot` shall be returned | + +------+--------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/cdns/CDN-in-a-Box/snapshot/new HTTP/1.1 + User-Agent: python-requests/2.23.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + +Response Structure +------------------ +:config: An object containing basic configurations on the actual CDN object + + :api.cache-control.max-age: A string containing an integer which specifies the value of ``max-age`` in the :mailheader:`Cache-Control` header of some HTTP responses, likely the :ref:`tr-api` responses + :certificates.polling.interval: A string containing an integer which specifies the interval, in seconds, on which other Traffic Control components should check for updated SSL certificates + :consistent.dns.routing: A string containing a boolean which indicates whether DNS routing will use a consistent hashing method or "round-robin" + + "false" + The "round-robin" method will be used to define DNS routing + "true" + A consistent hashing method will be used to define DNS routing + + :coveragezone.polling.interval: A string containing an integer which specifies the interval, in seconds, on which Traffic Routers should check for a new Coverage Zone file + :coveragezone.polling.url: The URL where a :term:`Coverage Zone File` may be requested by Traffic Routers + :dnssec.dynamic.response.expiration: A string containing a number and unit suffix that specifies the length of time for which dynamic responses to DNSSEC lookup queries should remain valid + :dnssec.dynamic.concurrencylevel: An integer that defines the size of the concurrency level (threads) of the Guava cache used by ZoneManager to store zone material + :dnssec.dynamic.initialcapacity: An integer that defines the initial size of the Guava cache, default is 10000. Too low of a value can lead to expensive resizing + :dnssec.init.timeout: An integer that defines the number of minutes to allow for zone generation, this bounds the zone priming activity + :dnssec.enabled: A string that tells whether or not the CDN uses DNSSEC; one of: + + "false" + DNSSEC is not used within this CDN + "true" + DNSSEC is used within this CDN + + :domain_name: A string that is the :abbr:`TLD (Top-Level Domain)` served by the CDN + :federationmapping.polling.interval: A string containing an integer which specifies the interval, in seconds, on which other Traffic Control components should check for new federation mappings + :federationmapping.polling.url: The URL where Traffic Control components can request federation mappings + :geolocation.polling.interval: A string containing an integer which specifies the interval, in seconds, on which other Traffic Control components should check for new IP-to-geographic-location mapping databases + :geolocation.polling.url: The URL where Traffic Control components can request IP-to-geographic-location mapping database files + :keystore.maintenance.interval: A string containing an integer which specifies the interval, in seconds, on which Traffic Routers should refresh their zone caches + :neustar.polling.interval: A string containing an integer which specifies the interval, in seconds, on which other Traffic Control components should check for new "Neustar" databases + :neustar.polling.url: The URL where Traffic Control components can request "Neustar" databases + :soa: An object defining the :abbr:`SOA (Start of Authority)` for the CDN's :abbr:`TLD (Top-Level Domain)` (defined in ``domain_name``) + + :admin: The name of the administrator for this zone - i.e. the RNAME + + .. note:: This rarely represents a proper email address, unfortunately. + + :expire: A string containing an integer that sets the number of seconds after which secondary name servers should stop answering requests for this zone if the master does not respond + :minimum: A string containing an integer that sets the :abbr:`TTL (Time To Live)` - in seconds - of the record for the purpose of negative caching + :refresh: A string containing an integer that sets the number of seconds after which secondary name servers should query the master for the :abbr:`SOA (Start of Authority)` record, to detect zone changes + :retry: A string containing an integer that sets the number of seconds after which secondary name servers should retry to request the serial number from the master if the master does not respond + + .. note:: :rfc:`1035` dictates that this should always be less than ``refresh``. + + .. seealso:: `The Wikipedia page on Start of Authority records `_. + + :steeringmapping.polling.interval: A string containing an integer which specifies the interval, in seconds, on which Traffic Control components should check for new steering mappings + :ttls: An object that contains keys which are types of DNS records that have values which are strings containing integers that specify the time for which a response to the specific type of record request should remain valid + :zonemanager.cache.maintenance.interval: A configuration option for the ZoneManager Java class of Traffic Router + :zonemanager.threadpool.scale: A configuration option for the ZoneManager Java class of Traffic Router + +:contentRouters: An object containing keys which are the (short) hostnames of the Traffic Routers that serve requests for :term:`Delivery Services` in this CDN + + :api.port: A string containing the port number on which the :ref:`tr-api` is served by this Traffic Router via HTTP + :secure.api.port: An optionally present string containing the port number on which the :ref:`tr-api` is served by this Traffic Router via HTTPS + :fqdn: This Traffic Router's :abbr:`FQDN (Fully Qualified Domain Name)` + :httpsPort: The port number on which this Traffic Router listens for incoming HTTPS requests + :ip: This Traffic Router's IPv4 address + :ip6: This Traffic Router's IPv6 address + :location: A string which is the :ref:`cache-group-name` of the :term:`Cache Group` to which this Traffic Router belongs + :port: The port number on which this Traffic Router listens for incoming HTTP requests + :profile: The :ref:`profile-name` of the :term:`Profile` used by this Traffic Router + :status: The health status of this Traffic Router + + .. seealso:: :ref:`health-proto` + +:contentServers: An object containing keys which are the (short) hostnames of the :term:`Edge-tier cache servers` in the CDN; the values corresponding to those keys are routing information for said servers + + :cacheGroup: A string that is the :ref:`cache-group-name` of the :term:`Cache Group` to which the server belongs + :capabilities: An array of this :term:`Cache Server`'s :term:`Server Capabilities`. If the Cache Server has no Server Capabilities, this field is omitted. + :deliveryServices: An object containing keys which are the names of :term:`Delivery Services` to which this :term:`cache server` is assigned; the values corresponding to those keys are arrays of :abbr:`FQDNs (Fully Qualified Domain Names)` that resolve to this :term:`cache server` + + .. note:: Only :term:`Edge-tier cache servers` can be assigned to a :term:`Delivery Service`, and therefore this field will only be present when ``type`` is ``"EDGE"``. + + :fqdn: The server's :abbr:`FQDN (Fully Qualified Domain Name)` + :hashCount: The number of servers to be placed into a single "hash ring" in Traffic Router + :hashId: A unique string to be used as the key for hashing servers - as of version 3.0.0 of Traffic Control, this is always the same as the server's (short) hostname and only still exists for legacy compatibility reasons + :httpsPort: The port on which the :term:`cache server` listens for incoming HTTPS requests + :interfaceName: The name of the main network interface device used by this :term:`cache server` + :ip6: The server's IPv6 address + :ip: The server's IPv4 address + :locationId: This field is exactly the same as ``cacheGroup`` and only exists for legacy compatibility reasons + :port: The port on which this :term:`cache server` listens for incoming HTTP requests + :profile: The :ref:`profile-name` of the :term:`Profile` used by the :term:`cache server` + :routingDisabled: An integer representing the boolean concept of whether or not Traffic Routers should route client traffic to this :term:`cache server`; one of: + + 0 + Do not route traffic to this server + 1 + Route traffic to this server normally + + :status: This :term:`cache server`'s status + + .. seealso:: :ref:`health-proto` + + :type: The :term:`Type` of this :term:`cache server`; which ought to be one of (but in practice need not be in certain special circumstances): + + EDGE + This is an :term:`Edge-tier cache server` + MID + This is a :term:`Mid-tier cache server` + +:deliveryServices: An object containing keys which are the :ref:`xml_ids ` of all of the :term:`Delivery Services` within the CDN + + :anonymousBlockingEnabled: A string containing a boolean that tells whether or not :ref:`ds-anonymous-blocking` is set on this :term:`Delivery Service`; one of: + + "true" + Anonymized IP addresses are blocked by this :term:`Delivery Service` + "false" + Anonymized IP addresses are not blocked by this :term:`Delivery Service` + + .. seealso:: :ref:`anonymous_blocking-qht` + + :consistentHashQueryParameters: A set of query parameters that Traffic Router should consider when determining a consistent hash for a given client request. + + :consistentHashRegex: An optional regular expression that will ensure clients are consistently routed to a :term:`cache server` based on matches to it. + + :coverageZoneOnly: A string containing a boolean that tells whether or not this :term:`Delivery Service` routes traffic based only on its :term:`Coverage Zone File` + + .. seealso:: :ref:`ds-geo-limit` + + :deepCachingType: A string that defines the :ref:`ds-deep-caching` setting of this :term:`Delivery Service` + :dispersion: An object describing the "dispersion" - or number of :term:`cache servers` within a single :term:`Cache Group` across which the same content is spread - within the :term:`Delivery Service` + + :limit: The maximum number of :term:`cache servers` in which the response to a single request URL will be stored + + .. note:: If this is greater than the number of :term:`cache servers` in the :term:`Cache Group` chosen to service the request, then content will be spread across all of them. That is, it causes no problems. + + :shuffled: A string containing a boolean that tells whether the :term:`cache servers` chosen for content dispersion are chosen randomly or based on a consistent hash of the request URL; one of: + + "false" + :term:`cache servers` will be chosen consistently + "true" + :term:`cache servers` will be chosen at random + + :domains: An array of domains served by this :term:`Delivery Service` + :ecsEnabled: A string containing a boolean from :ref:`ds-ecs` that tells whether EDNS0 client subnet is enabled on this :term:`Delivery Service`; one of: + + "false" + EDNS0 client subnet is not enabled on this :term:`Delivery Service` + "true" + EDNS0 client subnet is enabled on this :term:`Delivery Service` + + :geolocationProvider: The name of a provider for IP-to-geographic-location mapping services - currently the only valid value is ``"maxmindGeolocationService"`` + :ip6RoutingEnabled: A string containing a boolean that defines the :ref:`ds-ipv6-routing` setting for this :term:`Delivery Service`; one of: + + "false" + IPv6 traffic will not be routed by this :term:`Delivery Service` + "true" + IPv6 traffic will be routed by this :term:`Delivery Service` + + :matchList: An array of methods used by Traffic Router to determine whether or not a request can be serviced by this :term:`Delivery Service` + + :pattern: A regular expression - the use of this pattern is dependent on the ``type`` field (backslashes are escaped) + :setNumber: An integral, unique identifier for the set of types to which the ``type`` field belongs + :type: The name of the :term:`Type` of match performed using ``pattern`` to determine whether or not to use this :term:`Delivery Service` + + HOST_REGEXP + Use the :term:`Delivery Service` if ``pattern`` matches the :mailheader:`Host` HTTP header of an HTTP request, or the name requested for resolution in a DNS request + HEADER_REGEXP + Use the :term:`Delivery Service` if ``pattern`` matches an HTTP header (both the name and value) in an HTTP request\ [#httpOnly]_ + PATH_REGEXP + Use the :term:`Delivery Service` if ``pattern`` matches the request path of this :term:`Delivery Service`'s URL\ [#httpOnly]_ + STEERING_REGEXP + Use the :term:`Delivery Service` if ``pattern`` matches the :ref:`ds-xmlid` of one of this :term:`Delivery Service`'s "Steering" target :term:`Delivery Services` + + :missLocation: An object representing the default geographic coordinates to use for a client when lookup of their IP has failed in both the :term:`Coverage Zone File` (and/or possibly the :term:`Deep Coverage Zone File`) and the IP-to-geographic-location database + + :lat: Geographic latitude as a floating point number + :long: Geographic longitude as a floating point number + + :protocol: An object that describes how the :term:`Delivery Service` ought to handle HTTP requests both with and without TLS encryption + + :acceptHttps: A string containing a boolean that tells whether HTTPS requests should be normally serviced by this :term:`Delivery Service`; one of: + + "false" + Refuse to service HTTPS requests + "true" + Service HTTPS requests normally + + :redirectToHttps: A string containing a boolean that tells whether HTTP requests ought to be re-directed to use HTTPS; one of: + + "false" + Do not redirect unencrypted traffic; service it normally + "true" + Respond to HTTP requests with instructions to use HTTPS instead + + .. seealso:: :ref:`ds-protocol` + + :regionalGeoBlocking: A string containing a boolean that defines the :ref:`ds-regionalgeo` setting of this :term:`Delivery Service`; one of: + + "false" + Regional Geographic Blocking is not used by this :term:`Delivery Service` + "true" + Regional Geographic Blocking is used by this :term:`Delivery Service` + + .. seealso:: :ref:`regionalgeo-qht` + + :requiredCapabilities: An array of this Delivery Service's :term:`required capabilities `. If there are no required capabilities, this field is omitted. + :routingName: A string that is this :ref:`Delivery Service's Routing Name ` + :soa: An object defining the :abbr:`SOA (Start of Authority)` record for the :term:`Delivery Service`'s :abbr:`TLDs (Top-Level Domains)` (defined in ``domains``) + + :admin: The name of the administrator for this zone - i.e. the RNAME + + .. note:: This rarely represents a proper email address, unfortunately. + + :expire: A string containing an integer that sets the number of seconds after which secondary name servers should stop answering requests for this zone if the master does not respond + :minimum: A string containing an integer that sets the :abbr:`TTL (Time To Live)` - in seconds - of the record for the purpose of negative caching + :refresh: A string containing an integer that sets the number of seconds after which secondary name servers should query the master for the :abbr:`SOA (Start of Authority)` record, to detect zone changes + :retry: A string containing an integer that sets the number of seconds after which secondary name servers should retry to request the serial number from the master if the master does not respond + + .. note:: :rfc:`1035` dictates that this should always be less than ``refresh``. + + .. seealso:: `The Wikipedia page on Start of Authority records `_. + + :sslEnabled: A string containing a boolean that tells whether this :term:`Delivery Service` uses SSL; one of: + + "false" + SSL is not used by this :term:`Delivery Service` + "true" + SSL is used by this :term:`Delivery Service` + + .. seealso:: :ref:`ds-protocol` + + :topology: The name of the :term:`Topology` that this :term:`Delivery Service` is assigned to. If the Delivery Service is not assigned to a topology, this field is omitted. + :ttls: An object that contains keys which are types of DNS records that have values which are strings containing integers that specify the time for which a response to the specific type of record request should remain valid + + .. note:: This overrides ``config.ttls``. + +:edgeLocations: An object containing keys which are the names of Edge-Tier :term:`Cache Groups` within the CDN + + :backupLocations: An object that describes this :ref:`Cache Group's Fallbacks ` + + :fallbackToClosest: A string containing a boolean which defines the :ref:`cache-group-fallback-to-closest` behavior of this :term:`Cache Group`; one of: + + "false" + Do not fall back on the closest available :term:`Cache Group` + "true" + Fall back on the closest available :term:`Cache Group` + + :list: If this :term:`Cache Group` has any :ref:`cache-group-fallbacks`, this key will appear and will be an array of those :ref:`Cache Groups' Names ` + + :latitude: A floating point number that defines this :ref:`Cache Group's Latitude ` + :localizationMethods: An array of strings that represents this :ref:`Cache Group's Localization Methods ` + :longitude: A floating point number that defines this :ref:`Cache Group's Longitude ` + +:monitors: An object containing keys which are the (short) hostnames of Traffic Monitors within this CDN + + :fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of this Traffic Monitor + :httpsPort: The port number on which this Traffic Monitor listens for incoming HTTPS requests + :ip6: This Traffic Monitor's IPv6 address + :ip: This Traffic Monitor's IPv4 address + :location: A string which is the :ref:`cache-group-name` of the :term:`Cache Group` to which this Traffic Monitor belongs + :port: The port number on which this Traffic Monitor listens for incoming HTTP requests + :profile: A string which is the :ref:`profile-name` of the :term:`Profile` used by this Traffic Monitor + + .. note:: For legacy reasons, this must always start with "RASCAL-". + + :status: The health status of this Traffic Monitor + + .. seealso:: :ref:`health-proto` + +:stats: An object containing metadata information regarding the CDN + + :CDN_name: The name of this CDN + :date: The UNIX epoch timestamp date in the Traffic Ops server's own timezone + :tm_host: The :abbr:`FQDN (Fully Qualified Domain Name)` of the Traffic Ops server + :tm_user: The username of the currently logged-in user + :tm_version: The full version number of the Traffic Ops server, including release number, git commit hash, and supported Enterprise Linux version + +:topologies: An array of :term:`Topologies` where each key is the name of that Topology. + + :nodes: An array of the names of the :term:`Edge-Tier` :term:`Cache Groups` in this :term:`Topology`. :term:`Mid-Tier` Cache Groups in the topology are not included. + +:trafficRouterLocations: An object containing keys which are the :ref:`names of Cache Groups ` within the CDN which contain Traffic Routers + + :backupLocations: An object that describes this :ref:`Cache Group's Fallbacks ` + + :fallbackToClosest: A string containing a boolean which defines this :ref:`Cache Group's Fallback to Closest ` setting; one of: + + "false" + Do not fall back on the closest available :term:`Cache Group` + "true" + Fall back on the closest available :term:`Cache Group` + + :latitude: A floating point number that defines this :ref:`Cache Group's Latitude ` + :localizationMethods: An array of strings that represents this :ref:`Cache Group's Localization Methods ` + :longitude: A floating point number that defines this :ref:`Cache Group's Longitude ` + + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Wed, 27 May 2020 20:31:13 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: M6uhE2oPpjpTUR7gALsPOnM2CepD+VCAjp4dj5Xnppo0G5zL31PQgiteD23q67r7/bq/JJpMvIvdaENVYFtrqQ== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 27 May 2020 19:31:13 GMT + Content-Length: 1374 + + { + "response": { + "config": { + "api.cache-control.max-age": "10", + "certificates.polling.interval": "300000", + "consistent.dns.routing": "true", + "coveragezone.polling.interval": "3600000", + "coveragezone.polling.url": "https://static.infra.ciab.test:443/coverage-zone.json", + "dnssec.dynamic.response.expiration": "300s", + "dnssec.enabled": "false", + "domain_name": "mycdn.ciab.test", + "federationmapping.polling.interval": "60000", + "federationmapping.polling.url": "https://${toHostname}/api/5.0/federations/all", + "geolocation.polling.interval": "86400000", + "geolocation.polling.url": "https://static.infra.ciab.test:443/GeoLite2-City.mmdb.gz", + "keystore.maintenance.interval": "300", + "neustar.polling.interval": "86400000", + "neustar.polling.url": "https://static.infra.ciab.test:443/neustar.tar.gz", + "soa": { + "admin": "twelve_monkeys", + "expire": "604800", + "minimum": "30", + "refresh": "28800", + "retry": "7200" + }, + "steeringmapping.polling.interval": "60000", + "ttls": { + "A": "3600", + "AAAA": "3600", + "DNSKEY": "30", + "DS": "30", + "NS": "3600", + "SOA": "86400" + }, + "zonemanager.cache.maintenance.interval": "300", + "zonemanager.threadpool.scale": "0.50" + }, + "contentServers": { + "edge": { + "cacheGroup": "CDN_in_a_Box_Edge", + "capabilities": [ + "RAM_DISK_STORAGE" + ], + "fqdn": "edge.infra.ciab.test", + "hashCount": 999, + "hashId": "edge", + "httpsPort": 443, + "interfaceName": "eth0", + "ip": "172.26.0.3", + "ip6": "", + "locationId": "CDN_in_a_Box_Edge", + "port": 80, + "profile": "ATS_EDGE_TIER_CACHE", + "status": "REPORTED", + "type": "EDGE", + "routingDisabled": 0 + }, + "mid": { + "cacheGroup": "CDN_in_a_Box_Mid", + "capabilities": [ + "RAM_DISK_STORAGE" + ], + "fqdn": "mid.infra.ciab.test", + "hashCount": 999, + "hashId": "mid", + "httpsPort": 443, + "interfaceName": "eth0", + "ip": "172.26.0.4", + "ip6": "", + "locationId": "CDN_in_a_Box_Mid", + "port": 80, + "profile": "ATS_MID_TIER_CACHE", + "status": "REPORTED", + "type": "MID", + "routingDisabled": 0 + } + }, + "contentRouters": { + "trafficrouter": { + "api.port": "3333", + "fqdn": "trafficrouter.infra.ciab.test", + "httpsPort": 443, + "ip": "172.26.0.15", + "ip6": "", + "location": "CDN_in_a_Box_Edge", + "port": 80, + "profile": "CCR_CIAB", + "secure.api.port": "3443", + "status": "ONLINE" + } + }, + "deliveryServices": { + "demo1": { + "anonymousBlockingEnabled": "false", + "consistentHashQueryParams": [ + "abc", + "pdq", + "xxx", + "zyx" + ], + "coverageZoneOnly": "false", + "deepCachingType": "NEVER", + "dispersion": { + "limit": 1, + "shuffled": "true" + }, + "domains": [ + "demo1.mycdn.ciab.test" + ], + "ecsEnabled": "false", + "geolocationProvider": "maxmindGeolocationService", + "ip6RoutingEnabled": "true", + "matchsets": [ + { + "protocol": "HTTP", + "matchlist": [ + { + "regex": ".*\\.demo1\\..*", + "match-type": "HOST" + } + ] + } + ], + "missLocation": { + "lat": 42, + "long": -88 + }, + "protocol": { + "acceptHttps": "true", + "redirectToHttps": "false" + }, + "regionalGeoBlocking": "false", + "requiredCapabilities": [ + "RAM_DISK_STORAGE" + ], + "routingName": "video", + "soa": { + "admin": "traffic_ops", + "expire": "604800", + "minimum": "30", + "refresh": "28800", + "retry": "7200" + }, + "sslEnabled": "true", + "topology": "my-topology", + "ttls": { + "A": "", + "AAAA": "", + "NS": "3600", + "SOA": "86400" + } + } + }, + "edgeLocations": { + "CDN_in_a_Box_Edge": { + "latitude": 38.897663, + "longitude": -77.036574, + "backupLocations": { + "fallbackToClosest": "true" + }, + "localizationMethods": [ + "GEO", + "CZ", + "DEEP_CZ" + ] + } + }, + "trafficRouterLocations": { + "CDN_in_a_Box_Edge": { + "latitude": 38.897663, + "longitude": -77.036574, + "backupLocations": { + "fallbackToClosest": "false" + }, + "localizationMethods": [ + "GEO", + "CZ", + "DEEP_CZ" + ] + } + }, + "monitors": { + "trafficmonitor": { + "fqdn": "trafficmonitor.infra.ciab.test", + "httpsPort": 443, + "ip": "172.26.0.14", + "ip6": "", + "location": "CDN_in_a_Box_Edge", + "port": 80, + "profile": "RASCAL-Traffic_Monitor", + "status": "ONLINE" + } + }, + "stats": { + "CDN_name": "CDN-in-a-Box", + "date": 1590607873, + "tm_host": "trafficops.infra.ciab.test:443", + "tm_user": "admin", + "tm_version": "development" + }, + "topologies": { + "my-topology": { + "nodes": [ + "CDN_in_a_Box_Edge" + ] + } + } + } + } + +.. [#httpOnly] These only apply to HTTP-:ref:`routed ` :term:`Delivery Services` diff --git a/docs/source/api/v5/cdns_routing.rst b/docs/source/api/v5/cdns_routing.rst new file mode 100644 index 0000000000..1d00ff9c27 --- /dev/null +++ b/docs/source/api/v5/cdns_routing.rst @@ -0,0 +1,76 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-cdns-routing: + +**************** +``cdns/routing`` +**************** + +``GET`` +======= +Retrieves the aggregated routing percentages across all CDNs. This is accomplished by polling stats from all online Traffic Routers via the ``/crs/stats`` route. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: CDN:READ +:Response Type: Object + +Request Structure +----------------- +No parameters available + +Response Structure +------------------ +:cz: The percent of requests to online Traffic Routers that were satisfied by a :term:`Coverage Zone File` +:deepCz: The percent of requests to online Traffic Routers that were satisfied by a :term:`Deep Coverage Zone File` +:dsr: The percent of requests to online Traffic Routers that were satisfied by sending the client to an overflow :term:`Delivery Service` +:err: The percent of requests to online Traffic Routers that resulted in an error +:fed: The percent of requests to online Traffic Routers that were satisfied by sending the client to a federated CDN +:geo: The percent of requests to online Traffic Routers that were satisfied using 3rd party geographic IP mapping +:miss: The percent of requests to online Traffic Routers that could not be satisfied +:regionalAlternate: The percent of requests to online Traffic Routers that were satisfied by sending the client to the alternate, Regional Geo-blocking URL +:regionalDenied: The percent of requests to online Traffic Routers that were denied due to geographic location policy +:staticRoute: The percent of requests to online Traffic Routers that were satisfied with :ref:`ds-static-dns-entries` + +.. 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 + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Cache-Control: no-cache, no-store, max-age=0, must-revalidate + Content-Type: application/json + Date: Wed, 14 Nov 2018 21:29:32 GMT + X-Server-Name: traffic_ops_golang/ + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: 7LjytwKyRzSKM4cRIol4OMIJxApFpTWJaSK73rbtUIQdASZjI64XxLVzZP0OGRU7XeJ22YKUyQ30qbKHDRv7FQ== + Content-Length: 130 + + { "response": { + "cz": 79, + "deepCz": 0.50, + "dsr": 0, + "err": 0, + "fed": 0.25, + "geo": 20, + "miss": 0.25, + "regionalAlternate": 0, + "regionalDenied": 0, + "staticRoute": 0 + }} diff --git a/docs/source/api/v5/consistenthash.rst b/docs/source/api/v5/consistenthash.rst new file mode 100644 index 0000000000..ecc6abbfc3 --- /dev/null +++ b/docs/source/api/v5/consistenthash.rst @@ -0,0 +1,76 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-consistenthash: + +****************** +``consistenthash`` +****************** +Test Pattern-Based Consistent Hashing for a :term:`Delivery Service` using a regular expression and a request path + +``POST`` +======== +Queries database for an active Traffic Router on a given CDN and sends GET request to get the resulting path to consistent hash with a given regex and request path. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: CDN:READ +:Response Type: Object + +Request Structure +----------------- +:regex: The regular expression to apply to the request path to get a resulting path that will be used for consistent hashing +:requestPath: The request path to use to test the regular expression against +:cdnId: The unique identifier of a CDN that will be used to query for an active Traffic Router + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/consistenthash HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.54.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 80 + Content-Type: application/x-www-form-urlencoded + + {"regex":"/.*?(/.*?/).*?(m3u8)","requestPath":"/test/path/asset.m3u8","cdnId":2} + +Response Structure +------------------ +:resultingPathToConsistentHash: The resulting path that Traffic Router will use for consistent hashing +:consistentHashRegex: The regex used by Traffic Router derived from POST 'regex' parameter +:requestPath: The request path used by Traffic Router to test regex against + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: QMDFOnUfqH4TcZ4YnUQyqnXDier0YiUMIfwBGDcT7ySjw9uASBGsLQW35lpnKFi4as0vYlHuSSGpe4hHGsladQ== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 12 Feb 2019 21:32:05 GMT + Content-Length: 142 + + { "response": { + "resultingPathToConsistentHash":"/path/m3u8", + "consistentHashRegex":"/.*?(/.*?/).*?(m3u8)", + "requestPath":"/test/path/asset.m3u8" + }} diff --git a/docs/source/api/v5/coordinates.rst b/docs/source/api/v5/coordinates.rst new file mode 100644 index 0000000000..6f624cad50 --- /dev/null +++ b/docs/source/api/v5/coordinates.rst @@ -0,0 +1,311 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-coordinates: + +*************** +``coordinates`` +*************** + +``GET`` +======= +Gets a list of all coordinates in the Traffic Ops database + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: COORDINATE:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +===========+==========+===============================================================================================================+ + | id | no | Return only coordinates that have this integral, unique identifier | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | name | no | Return only coordinates with this name | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | orderby | no | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` | + | | | array | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long | + | | | and the first page is 1. If ``offset`` was defined, this query parameter has no effect. ``limit`` must be | + | | | defined to make use of ``page``. | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + +Response Structure +------------------ +:id: Integral, unique, identifier for this coordinate pair +:lastUpdated: The time and date at which this entry was last updated, in a ``ctime``-like 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 + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: Y2vxC3hpxIg6aRNBBT7i2hbAViIJp+dJoqHIzu3acFM+vGay/I5E+eZYOC9RY8hcJPrKNXysZOD8DOb9KsFgaw== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 14 Nov 2018 21:32:28 GMT + Content-Length: 942 + + { "response": [ + { + "id": 1, + "name": "from_cachegroup_TRAFFIC_ANALYTICS", + "latitude": 38.897663, + "longitude": -77.036574, + "lastUpdated": "2018-10-24 16:07:04+00" + }, + { + "id": 2, + "name": "from_cachegroup_TRAFFIC_OPS", + "latitude": 38.897663, + "longitude": -77.036574, + "lastUpdated": "2018-10-24 16:07:04+00" + }, + { + "id": 3, + "name": "from_cachegroup_TRAFFIC_OPS_DB", + "latitude": 38.897663, + "longitude": -77.036574, + "lastUpdated": "2018-10-24 16:07:04+00" + }, + { + "id": 4, + "name": "from_cachegroup_TRAFFIC_PORTAL", + "latitude": 38.897663, + "longitude": -77.036574, + "lastUpdated": "2018-10-24 16:07:04+00" + }, + { + "id": 5, + "name": "from_cachegroup_TRAFFIC_STATS", + "latitude": 38.897663, + "longitude": -77.036574, + "lastUpdated": "2018-10-24 16:07:04+00" + }, + { + "id": 6, + "name": "from_cachegroup_CDN_in_a_Box_Mid", + "latitude": 38.897663, + "longitude": -77.036574, + "lastUpdated": "2018-10-24 16:07:04+00" + }, + { + "id": 7, + "name": "from_cachegroup_CDN_in_a_Box_Edge", + "latitude": 38.897663, + "longitude": -77.036574, + "lastUpdated": "2018-10-24 16:07:05+00" + } + ]} + +``POST`` +======== +Creates a new coordinate pair + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: COORDINATE:CREATE, COORDINATE:READ +:Response Type: Object + +Request Structure +----------------- +:name: The name of the new coordinate +:latitude: The desired latitude of the new coordinate (must be on the interval [-180, 180]) +:longitude: The desired longitude of the new coordinate (must be on the interval [-90, 90]) + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/coordinates HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 47 + Content-Type: application/json + + {"name": "test", "latitude": 0, "longitude": 0} + +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 a ``ctime``-like format +:latitude: Latitude of the newly created coordinate +:longitude: Longitude of the newly created coordinate +:name: The name of the coordinate + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: 7pWdeZyIIXE1P7o/JVon+5eSCbDw+FGamAzdXzWHXJ8IhF+Vh+/tWFCkzHYw3rP2kBVwZu+gqLffjQpBCMjt7A== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 15 Nov 2018 17:48:55 GMT + Content-Length: 165 + + { "alerts": [ + { + "text": "coordinate was created.", + "level": "success" + } + ], + "response": { + "id": 9, + "name": "test", + "latitude": 0, + "longitude": 0, + "lastUpdated": "2018-11-15 17:48:55+00" + }} + + +``PUT`` +======= +Updates a coordinate + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: COORDINATE:UPDATE, COORDINATE:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Query Parameters + + +------+----------+------------------------------------------------------------+ + | Name | Required | Description | + +======+==========+============================================================+ + | id | yes | The integral, unique identifier of the coordinate to edit | + +------+----------+------------------------------------------------------------+ + +:name: The name of the new coordinate +:latitude: The desired new latitude of the coordinate (must be on the interval [-180, 180]) +:longitude: The desired new longitude of the coordinate (must be on the interval [-90, 90]) + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/coordinates?id=9 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 48 + Content-Type: application/json + + {"name": "quest", "latitude": 0, "longitude": 0} + +Response Structure +------------------ +:id: Integral, unique, identifier for the coordinate pair +:lastUpdated: The time and date at which this entry was last updated, in a ``ctime``-like format +:latitude: Latitude of the coordinate +:longitude: Longitude of the coordinate +:name: The name of the coordinate + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: zd03Uvbnv8EbSZZ75Xp5tnnYStZsZTdyPxXnoqK4QZ5WhELLPL8iHlRfOaiLTbrUWUeJ8ue2HRz6aBS/iXCCGA== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 15 Nov 2018 17:54:30 GMT + Content-Length: 166 + + { "alerts": [ + { + "text": "coordinate was updated.", + "level": "success" + } + ], + "response": { + "id": 9, + "name": "quest", + "latitude": 0, + "longitude": 0, + "lastUpdated": "2018-11-15 17:54:30+00" + }} + +``DELETE`` +========== +Deletes a coordinate + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: COORDINATE:DELETE, COORDINATE:READ +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Query Parameters + + +------+----------+-------------------------------------------------------------+ + | Name | Required | Description | + +======+==========+=============================================================+ + | id | yes | The integral, unique identifier of the coordinate to delete | + +------+----------+-------------------------------------------------------------+ + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: 82x/Wdckqgk4LN5LIlZfBJ26xkDrUVUGDjs5QFa/Lzap7dU3OZkjv8XW41xeFYj8PDmxHIpb7hiVObvLaxnEDA== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 15 Nov 2018 17:57:42 GMT + Content-Length: 65 + + { "alerts": [ + { + "text": "coordinate was deleted.", + "level": "success" + } + ]} diff --git a/docs/source/api/v5/current_stats.rst b/docs/source/api/v5/current_stats.rst new file mode 100644 index 0000000000..2ccf0c5685 --- /dev/null +++ b/docs/source/api/v5/current_stats.rst @@ -0,0 +1,91 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + + +.. _to-api-current-stats: + +***************** +``current_stats`` +***************** +An API endpoint that returns current statistics for each CDN and an aggregate across them all. + +``GET`` +======= +Retrieves current stats for each CDN. Also includes aggregate stats across them. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: CDN:READ +:Response Type: Array + +Request Structure +----------------- +No parameters available. + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/current_stats HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:cdn: The name of the CDN +:connections: Current number of TCP connections maintained +:capacity: 85 percent capacity of the CDN in Gps +:bandwidth: The total amount of bandwidth in Gbs + +.. note:: If ``cdn`` name is total and capacity is omitted it represents the aggregate stats across CDNs + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=; Path=/; HttpOnly + Whole-Content-Sha512: Rs3wgd7v5dP0bOQs4I3J1q6mnWIMSM2AKSAWirK1kymvDYOoFISArF7Kkypgy10I34yn7FtFdMh6U7ABaS1Tjw== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 14 Nov 2019 15:35:31 GMT + Content-Length: 138 + + {"response": { + "currentStats": [ + { + "bandwidth": null, + "capacity": null, + "cdn": "ALL", + "connections": null + }, + { + "bandwidth": 0.000104, + "capacity": 17, + "cdn": "CDN-in-a-Box", + "connections": 4 + }, + { + "bandwidth": 0.000104, + "cdn": "total", + "connections": 4 + } + ] + }} diff --git a/docs/source/api/v5/dbdump.rst b/docs/source/api/v5/dbdump.rst new file mode 100644 index 0000000000..fc7ea53edc --- /dev/null +++ b/docs/source/api/v5/dbdump.rst @@ -0,0 +1,67 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-dbdump: + +********** +``dbdump`` +********** +.. caution:: This is an extremely dangerous thing to do, as it exposes the entirety of the database, including possibly sensitive information. Administrators and systems engineers are advised to instead use database-specific tools to make server transitions more securely. + +Dumps the Traffic Ops database as an SQL script that should recreate its schema and contents exactly. + +.. impl-detail:: The script is output using the :manpage:`pg_dump(1)` utility, and is thus compatible for use with the :manpage:`pg_restore(1)` utility. + +``GET`` +======= +Fetches the database dump. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: DBDUMP:READ +:Response Type: ``undefined`` - outputs an SQL script, not JSON + +Request Structure +----------------- +No parameters available + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/dbdump HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/sql + Content-Disposition: attachment + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: YwvPB0ZToyzT8ilBnDlWWdwV+E3f2Xgus1OKrkNaipQqgrw5zGwq0rC1U9TZ8Zl6kAGcRZgCYnr1EWfHXpJRkg== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 09 Sep 2019 21:08:28 GMT + Transfer-Encoding: chunked + + -- Actual text omitted - it's huge diff --git a/docs/source/api/v5/deliveryservice_request_comments.rst b/docs/source/api/v5/deliveryservice_request_comments.rst new file mode 100644 index 0000000000..d546ecbcc1 --- /dev/null +++ b/docs/source/api/v5/deliveryservice_request_comments.rst @@ -0,0 +1,325 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservice_request_comments: + +************************************ +``deliveryservice_request_comments`` +************************************ + +``GET`` +======= +Gets delivery service request comments. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: DS-REQUEST:READ, DELIVERY-SERVICE:READ, USER:READ +:Response Type: Array + +Request Structure +----------------- + +.. table:: Request Query Parameters + + +--------------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +==========================+==========+=====================================================================================================================================================+ + | author | no | Filter for :ref:`Delivery Service Request ` comments submitted by the user identified by this username | + +--------------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------------------+ + | authorId | no | Filter for :ref:`Delivery Service Request ` comments submitted by the user identified by this integral, unique identifier | + +--------------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------------------+ + | deliveryServiceRequestId | no | Filter for :ref:`Delivery Service Request ` comments submitted for the delivery service identified by this integral, unique identifier | + +--------------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------------------+ + | id | no | Filter for the :ref:`Delivery Service Request ` comment identified by this integral, unique identifier | + +--------------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/deliveryservice_request_comments HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + +Response Structure +------------------ +:author: The username of the user who created the comment. +:authorId: The integral, unique identifier of the user who created the comment. +:deliveryServiceRequestId: The integral, unique identifier of the :term:`Delivery Service Request` that the comment was posted on. +:id: The integral, unique identifier of the :term:`DSR` comment. +:lastUpdated: The date and time at which the user was last modified, in :ref:`non-rfc-datetime` +:value: The text of the comment that was posted. +:xmlId: This is the ``xmlId`` value that you provided in the request. + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 24 Feb 2020 21:00:26 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: RaJZS1XFJ4oIxVKyyDjTuoQY7gPOmm5EuIL4AgHpyQpuaaNviw0XhGC4V/AKf/Ws6zXLgIUc4OyvMsTxnrilww== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 24 Feb 2020 20:00:26 GMT + Content-Length: 207 + + { + "response": [ + { + "authorId": 2, + "author": "admin", + "deliveryServiceRequestId": 2, + "id": 3, + "lastUpdated": "2020-02-24 19:59:46+00", + "value": "Changing to a different origin for now.", + "xmlId": "demo1" + }, + { + "authorId": 2, + "author": "admin", + "deliveryServiceRequestId": 2, + "id": 4, + "lastUpdated": "2020-02-24 19:59:55+00", + "value": "Using HTTPS.", + "xmlId": "demo1" + } + ] + } + +``POST`` +======== +Allows user to create a :term:`Delivery Service Request` comment. + +:Auth. Required: Yes +:Roles Required: "admin", "Federation", "operations", "Portal", or "Steering" +:Permissions Required: DS-REQUEST:UPDATE, DELIVERY-SERVICE:READ, USER:READ +:Response Type: Object + +Request Structure +----------------- +:deliveryServiceRequestId: The integral, unique identifier of the delivery service that you are commenting on. +:value: The comment text itself. +:xmlId: This can be any string. It is not validated or used, though it is returned in the response. + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/deliveryservice_request_comments HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 111 + + { + "deliveryServiceRequestId": 2, + "value": "Does anyone have time to review my delivery service request?" + } + +Response Structure +------------------ +:author: The username of the user who created the comment. +:authorId: The integral, unique identifier of the user who created the comment. +:deliveryServiceRequestId: The integral, unique identifier of the :term:`Delivery Service Request` that the comment was posted on. +:id: The integral, unique identifier of the :term:`DSR` comment. +:lastUpdated: The date and time at which the user was last modified, in :ref:`non-rfc-datetime` +:value: The text of the comment that was posted. +:xmlId: This is the ``xmlId`` value that you provided in the request. + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 24 Feb 2020 21:02:20 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: LiakFP6L7PrnFO5kLXftx7WQoKn3bGpIJT5N15PvNG2sHridRMV3k23eRJM66ET0LcRfMOrQgRiydE+XgA8h0A== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 24 Feb 2020 20:02:20 GMT + Content-Length: 223 + + { + "alerts": [ + { + "text": "deliveryservice_request_comment was created.", + "level": "success" + } + ], + "response": { + "authorId": 2, + "author": null, + "deliveryServiceRequestId": 2, + "id": 6, + "lastUpdated": "2020-02-24 20:02:20+00", + "value": "Does anyone have time to review my delivery service request?", + "xmlId": null + } + } + +``PUT`` +======= +Updates a delivery service request comment. + +:Auth. Required: Yes +:Roles Required: "admin", "Federation", "operations", "Portal", or "Steering" +:Permissions Required: DS-REQUEST:UPDATE, DELIVERY-SERVICE:READ, USER:READ +:Response Type: Object + + +Request Structure +----------------- +:deliveryServiceRequestId: The integral, unique identifier of the :term:`Delivery Service Request` that the comment was posted on. +:value: The comment text itself. +:xmlId: This can be any string. It is not validated or used, though it is returned in the response. + +.. table:: Request Query Parameters + + +-----------+----------+-----------------------------------------------------------------------------------+ + | Parameter | Required | Description | + +===========+==========+===================================================================================+ + | id | yes | The integral, unique identifier of the :term:`Delivery Service Request` comment | + | | | that you wish to update. | + +-----------+----------+-----------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/deliveryservice_request_comments?id=6 HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 166 + + { + "deliveryServiceRequestId": 2, + "value": "Update: We no longer need this, feel free to reject.\n\nDoes anyone have time to review my delivery service request?" + } + +Response Structure +------------------ +:author: The username of the user who created the comment. +:authorId: The integral, unique identifier of the user who created the comment. +:deliveryServiceRequestId: The integral, unique identifier of the :term:`Delivery Service Request` that the comment was posted on. +:id: The integral, unique identifier of the :term:`DSR` comment. +:lastUpdated: The date and time at which the user was last modified, in :ref:`non-rfc-datetime` +:value: The text of the comment that was posted. +:xmlId: This is the ``xmlId`` value that you provided in the request. + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 24 Feb 2020 21:05:46 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: RalS34imPw7c42nlnu5eTuv6FSxuGcAvxEdeIyNma1zpE3ZojAMFbhj8qi1s+hOVDYybfFPzMz82c+xc1qrMHg== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 24 Feb 2020 20:05:46 GMT + Content-Length: 255 + + { + "alerts": [ + { + "text": "deliveryservice_request_comment was updated.", + "level": "success" + } + ], + "response": { + "authorId": null, + "author": null, + "deliveryServiceRequestId": 2, + "id": 6, + "lastUpdated": "2020-02-24 20:05:46+00", + "value": "Update: We no longer need this, feel free to reject.\n\nDoes anyone have time to review my delivery service request?", + "xmlId": null + } + } + +``DELETE`` +========== +Deletes a delivery service request comment. + +:Auth. Required: Yes +:Roles Required: "admin", "Federation", "operations", "Portal", or "Steering" +:Permissions Required: DS-REQUEST:UPDATE, DELIVERY-SERVICE:READ, USER:READ +:Response Type: ``undefined`` + +Request Structure +----------------- + +.. table:: Request Query Parameters + + +-----------+----------+-----------------------------------------------------------------------------------+ + | Parameter | Required | Description | + +===========+==========+===================================================================================+ + | id | yes | The integral, unique identifier of the :term:`Delivery Service Request` comment | + | | | that you wish to delete. | + +-----------+----------+-----------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/deliveryservice_request_comments?id=6 HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 0 + +Response Structure +------------------ + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 24 Feb 2020 21:07:40 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: lOpGzqeIh/1JAx85mz3MI/5A1i1g5beTSLtfvgcfQmCjNKQvOMs/4TLviuVzOCRrEIPmNcjy35tmvfxwlv7RMQ== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 24 Feb 2020 20:07:40 GMT + Content-Length: 101 + + { + "alerts": [ + { + "text": "deliveryservice_request_comment was deleted.", + "level": "success" + } + ] + } diff --git a/docs/source/api/v5/deliveryservice_requests.rst b/docs/source/api/v5/deliveryservice_requests.rst new file mode 100644 index 0000000000..a3c62ddc35 --- /dev/null +++ b/docs/source/api/v5/deliveryservice_requests.rst @@ -0,0 +1,831 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservice-requests: + +**************************** +``deliveryservice_requests`` +**************************** + +``GET`` +======= +Retrieves :term:`Delivery Service Requests`. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: DS-REQUEST:READ, DELIVERY-SERVICE:READ, USER:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +===========+==========+========================================================================================================================================================+ + | assignee | no | Filter for :term:`Delivery Service Requests` that are assigned to the user identified by this username. | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ + | assigneeId| no | Filter for :term:`Delivery Service Requests` that are assigned to the user identified by this integral, unique identifier | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ + | author | no | Filter for :term:`Delivery Service Requests` submitted by the user identified by this username | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ + | authorId | no | Filter for :term:`Delivery Service Requests` submitted by the user identified by this integral, unique identifier | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ + | changeType| no | Filter for :term:`Delivery Service Requests` of the change type specified. Can be ``create``, ``update``, or ``delete``. | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ + | createdAt | no | Filter for :term:`Delivery Service Requests` created on a certain date/time. Value must be :rfc:`3339` compliant. Eg. ``2019-09-19T19:35:38.828535Z`` | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ + | id | no | Filter for the :term:`Delivery Service Requests` identified by this integral, unique identifier. | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ + | status | no | Filter for :term:`Delivery Service Requests` whose status is the status specified. The status can be ``draft``, ``submitted``, ``pending``, | + | | | ``rejected``, or ``complete``. | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ + | xmlId | no | Filter for :term:`Delivery Service Requests` that have the given :ref:`ds-xmlid`. | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ + | orderby | no | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` array | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long and the first page is 1. If ``offset`` | + | | | was defined, this query parameter has no effect. ``limit`` must be defined to make use of ``page``. | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------------------------+ + +.. versionadded:: ATCv6 + The ``createdAt`` query parameter was added to this in endpoint across all API versions in :abbr:`ATC (Apache Traffic Control)` version 6.0.0. + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/deliveryservice_requests?status=draft HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + +Response Structure +------------------ +The response is an array of representations of :term:`Delivery Service Requests`. + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 24 Feb 2020 20:14:07 GMT; Max-Age=3600; HttpOnly + X-Server-Name: traffic_ops_golang/ + Date: Mon, 24 Feb 2020 19:14:07 GMT + Content-Length: 872 + + { "response": [{ + "authorId": 2, + "author": "admin", + "changeType": "update", + "createdAt": "2020-02-24 19:11:12+00", + "id": 1, + "lastEditedBy": "admin", + "lastEditedById": 2, + "lastUpdated": "2020-02-24 19:11:12+00", + "requested": { + "active": false, + "anonymousBlockingEnabled": false, + "cacheurl": null, + "ccrDnsTtl": null, + "cdnId": 2, + "cdnName": "CDN-in-a-Box", + "checkPath": null, + "displayName": "Demo 1", + "dnsBypassCname": null, + "dnsBypassIp": null, + "dnsBypassIp6": null, + "dnsBypassTtl": null, + "dscp": 0, + "edgeHeaderRewrite": null, + "firstHeaderRewrite": null, + "geoLimit": 0, + "geoLimitCountries": null, + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": null, + "globalMaxTps": null, + "httpBypassFqdn": null, + "id": 1, + "infoUrl": null, + "initialDispersion": 1, + "innerHeaderRewrite": null, + "ipv6RoutingEnabled": true, + "lastHeaderRewrite": null, + "lastUpdated": "0001-01-01T00:00:00Z", + "logsEnabled": true, + "longDesc": "Apachecon North America 2018", + "matchList": [ + { + "type": "HOST_REGEXP", + "setNumber": 0, + "pattern": ".*\\.demo1\\..*" + } + ], + "maxDnsAnswers": null, + "midHeaderRewrite": null, + "missLat": 42, + "missLong": -88, + "multiSiteOrigin": false, + "originShield": null, + "orgServerFqdn": "http://origin.infra.ciab.test", + "profileDescription": null, + "profileId": null, + "profileName": null, + "protocol": 2, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "video", + "signed": false, + "sslKeyVersion": 1, + "tenantId": 1, + "topology": null, + "type": "HTTP", + "typeId": 1, + "xmlId": "demo1", + "exampleURLs": [ + "http://video.demo1.mycdn.ciab.test", + "https://video.demo1.mycdn.ciab.test" + ], + "deepCachingType": "NEVER", + "fqPacingRate": null, + "signingAlgorithm": null, + "tenant": "root", + "trResponseHeaders": null, + "trRequestHeaders": null, + "consistentHashRegex": null, + "consistentHashQueryParams": [ + "abc", + "pdq", + "xxx", + "zyx" + ], + "maxOriginConnections": 0, + "ecsEnabled": false, + "tlsVersions": null + }, + "status": "draft" + }]} + +.. _to-api-deliveryservice-requests-post: + +``POST`` +======== +Creates a new :term:`Delivery Service Request`. "Closed" :term:`Delivery Service Requests` cannot be created, an existing :term:`Delivery Service Request` must be placed into a closed :ref:`dsr-status`. A :term:`Delivery Service Request` to create, modify or delete a :term:`Delivery Service` cannot be created if an open :term:`Delivery Service Request` exists for a :term:`Delivery Service` with the same :ref:`ds-xmlid`. Because of this, :term:`Delivery Service Requests` cannot be used to change a :term:`Delivery Service`'s :ref:`ds-xmlid`. + +:Auth. Required: Yes +:Roles Required: "admin", "Federation", "operations", "Portal", or "Steering" +:Permissions Required: DS-REQUEST:CREATE, DELIVERY-SERVICE:READ, USER:READ +:Response Type: Object + +Request Structure +----------------- +The request must be a well-formed representation of a :term:`Delivery Service Request`, without any response-only fields, of course. + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/deliveryservice_requests HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 1979 + + { + "changeType": "update", + "status": "draft", + "requested": { + "active": false, + "anonymousBlockingEnabled": false, + "cacheurl": null, + "ccrDnsTtl": null, + "cdnId": 2, + "cdnName": "CDN-in-a-Box", + "checkPath": null, + "displayName": "Demo 1", + "dnsBypassCname": null, + "dnsBypassIp": null, + "dnsBypassIp6": null, + "dnsBypassTtl": null, + "dscp": 0, + "edgeHeaderRewrite": null, + "firstHeaderRewrite": null, + "geoLimit": 0, + "geoLimitCountries": null, + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": null, + "globalMaxTps": null, + "httpBypassFqdn": null, + "id": 1, + "infoUrl": null, + "initialDispersion": 1, + "innerHeaderRewrite": null, + "ipv6RoutingEnabled": true, + "lastHeaderRewrite": null, + "lastUpdated": "2020-02-13T16:43:54Z", + "logsEnabled": true, + "longDesc": "Apachecon North America 2018", + "matchList": [ + { + "type": "HOST_REGEXP", + "setNumber": 0, + "pattern": ".*\\.demo1\\..*" + } + ], + "maxDnsAnswers": null, + "midHeaderRewrite": null, + "missLat": 42, + "missLong": -88, + "multiSiteOrigin": false, + "originShield": null, + "orgServerFqdn": "http://origin.infra.ciab.test", + "profileDescription": null, + "profileId": null, + "profileName": null, + "protocol": 2, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "video", + "signed": false, + "sslKeyVersion": 1, + "tenantId": 1, + "type": "HTTP", + "typeId": 1, + "xmlId": "demo1", + "exampleURLs": [ + "http://video.demo1.mycdn.ciab.test", + "https://video.demo1.mycdn.ciab.test" + ], + "deepCachingType": "NEVER", + "fqPacingRate": null, + "signingAlgorithm": null, + "tenant": "root", + "topology": null, + "trResponseHeaders": null, + "trRequestHeaders": null, + "consistentHashRegex": null, + "consistentHashQueryParams": [ + "abc", + "pdq", + "xxx", + "zyx" + ], + "maxOriginConnections": 0, + "ecsEnabled": false, + "serviceCategory": null, + "tlsVersions": null + } + } + + +Response Structure +------------------ +The response will be a representation of the created :term:`Delivery Service Request`. + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 201 CREATED + 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 24 Feb 2020 20:11:12 GMT; Max-Age=3600; HttpOnly + Location: /api/5.0/deliveryservice_requests/2 + X-Server-Name: traffic_ops_golang/ + Date: Mon, 24 Feb 2020 19:11:12 GMT + Content-Length: 901 + + { + "alerts": [ + { + "text": "deliveryservice_request was created.", + "level": "success" + } + ], + "response": { + "authorId": 2, + "author": null, + "changeType": "update", + "createdAt": null, + "id": 2, + "lastEditedBy": null, + "lastEditedById": 2, + "lastUpdated": "2020-02-24 19:11:12+00", + "requested": { + "active": false, + "anonymousBlockingEnabled": false, + "cacheurl": null, + "ccrDnsTtl": null, + "cdnId": 2, + "cdnName": "CDN-in-a-Box", + "checkPath": null, + "displayName": "Demo 1", + "dnsBypassCname": null, + "dnsBypassIp": null, + "dnsBypassIp6": null, + "dnsBypassTtl": null, + "dscp": 0, + "edgeHeaderRewrite": null, + "firstHeaderRewrite": null, + "geoLimit": 0, + "geoLimitCountries": null, + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": null, + "globalMaxTps": null, + "httpBypassFqdn": null, + "id": 1, + "infoUrl": null, + "initialDispersion": 1, + "innerHeaderRewrite": null, + "ipv6RoutingEnabled": true, + "lastHeaderRewrite": null, + "lastUpdated": "0001-01-01T00:00:00Z", + "logsEnabled": true, + "longDesc": "Apachecon North America 2018", + "matchList": [ + { + "type": "HOST_REGEXP", + "setNumber": 0, + "pattern": ".*\\.demo1\\..*" + } + ], + "maxDnsAnswers": null, + "midHeaderRewrite": null, + "missLat": 42, + "missLong": -88, + "multiSiteOrigin": false, + "originShield": null, + "orgServerFqdn": "http://origin.infra.ciab.test", + "profileDescription": null, + "profileId": null, + "profileName": null, + "protocol": 2, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "video", + "signed": false, + "sslKeyVersion": 1, + "tenantId": 1, + "topology": null, + "type": "HTTP", + "typeId": 1, + "xmlId": "demo1", + "exampleURLs": [ + "http://video.demo1.mycdn.ciab.test", + "https://video.demo1.mycdn.ciab.test" + ], + "deepCachingType": "NEVER", + "fqPacingRate": null, + "signingAlgorithm": null, + "tenant": "root", + "trResponseHeaders": null, + "trRequestHeaders": null, + "consistentHashRegex": null, + "consistentHashQueryParams": [ + "abc", + "pdq", + "xxx", + "zyx" + ], + "maxOriginConnections": 0, + "ecsEnabled": false, + "tlsVersions": null + }, + "original": { + "active": true, + "anonymousBlockingEnabled": false, + "cacheurl": null, + "ccrDnsTtl": null, + "cdnId": 2, + "cdnName": "CDN-in-a-Box", + "checkPath": null, + "displayName": "Demo 1", + "dnsBypassCname": null, + "dnsBypassIp": null, + "dnsBypassIp6": null, + "dnsBypassTtl": null, + "dscp": 0, + "edgeHeaderRewrite": null, + "firstHeaderRewrite": null, + "geoLimit": 0, + "geoLimitCountries": null, + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": null, + "globalMaxTps": null, + "httpBypassFqdn": null, + "id": 1, + "infoUrl": null, + "initialDispersion": 1, + "innerHeaderRewrite": null, + "ipv6RoutingEnabled": true, + "lastHeaderRewrite": null, + "lastUpdated": "2020-02-13T16:43:54Z", + "logsEnabled": true, + "longDesc": "Apachecon North America 2018", + "matchList": [ + { + "type": "HOST_REGEXP", + "setNumber": 0, + "pattern": ".*\\.demo1\\..*" + } + ], + "maxDnsAnswers": null, + "midHeaderRewrite": null, + "missLat": 42, + "missLong": -88, + "multiSiteOrigin": false, + "originShield": null, + "orgServerFqdn": "http://origin.infra.ciab.test", + "profileDescription": null, + "profileId": null, + "profileName": null, + "protocol": 2, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "video", + "signed": false, + "sslKeyVersion": 1, + "tenantId": 1, + "type": "HTTP", + "typeId": 1, + "xmlId": "demo1", + "exampleURLs": [ + "http://video.demo1.mycdn.ciab.test", + "https://video.demo1.mycdn.ciab.test" + ], + "deepCachingType": "NEVER", + "fqPacingRate": null, + "signingAlgorithm": null, + "tenant": "root", + "topology": null, + "trResponseHeaders": null, + "trRequestHeaders": null, + "consistentHashRegex": null, + "consistentHashQueryParams": [ + "abc", + "pdq", + "xxx", + "zyx" + ], + "maxOriginConnections": 0, + "ecsEnabled": false, + "serviceCategory": null, + "tlsVersions": null + }, + "status": "draft" + } + } + +``PUT`` +======= +Updates an existing :term:`Delivery Service Request`. Note that "closed" :term:`Delivery Service Requests` are uneditable. + +.. seealso:: The proper way to change a :term:`Delivery Service Request`'s :ref:`dsr-status` is by using the :ref:`to-api-deliveryservice_requests-id-status` endpoint's ``PUT`` handler. + +:Auth. Required: Yes +:Roles Required: "admin", "Federation", "operations", "Portal", or "Steering" +:Permissions Required: DS-REQUEST:UPDATE, DELIVERY-SERVICE:READ, USER:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-----------+----------+--------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +===========+==========+==================================================================================================+ + | id | yes | The integral, unique identifier of the :term:`Delivery Service Request` that you want to update. | + +-----------+----------+--------------------------------------------------------------------------------------------------+ + +The request body must be a representation of a :term:`Delivery Service Request` without any response-only fields. + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/deliveryservice_requests?id=1 HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 2256 + + { + "changeType": "update", + "requested": { + "active": true, + "cdnId": 2, + "ccrDnsTtl": 30, + "deepCachingType": "NEVER", + "displayName": "Demo 1 but I modified the DSR", + "dscp": 0, + "geoLimit": 0, + "geoProvider": 0, + "initialDispersion": 3, + "logsEnabled": false, + "longDesc": "long desc", + "regionalGeoBlocking": false, + "tenantId": 1, + "typeId": 8, + "xmlId": "demo1", + "id": 1 + }, + "status": "draft" + } + +Response Structure +------------------ +The response is a full representation of the edited :term:`Delivery Service Request`. + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 24 Feb 2020 20:36:16 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: +W0vFm96yFkZUJqa0GAX7uzIpRKh/ohyBm0uH3egpiERTcxy5OfVVtoP3h8Ee2teLu8KFooDYXJ6rpQg6UhbNQ== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 24 Feb 2020 19:36:16 GMT + Content-Length: 913 + + { "alerts": [{ + "text": "Delivery Service Request #2 updated", + "level": "success" + }], + "response": { + "assignee": null, + "author": "", + "changeType": "update", + "createdAt": "2020-09-25T06:23:30.683058Z", + "id": null, + "lastEditedBy": "admin", + "lastUpdated": "2020-09-25T02:38:04.180237Z", + "original": { + "active": true, + "anonymousBlockingEnabled": false, + "cacheurl": null, + "ccrDnsTtl": null, + "cdnId": 2, + "cdnName": "CDN-in-a-Box", + "checkPath": null, + "displayName": "Demo 1", + "dnsBypassCname": null, + "dnsBypassIp": null, + "dnsBypassIp6": null, + "dnsBypassTtl": null, + "dscp": 0, + "edgeHeaderRewrite": null, + "geoLimit": 0, + "geoLimitCountries": null, + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": null, + "globalMaxTps": null, + "httpBypassFqdn": null, + "id": 1, + "infoUrl": null, + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "lastUpdated": "2020-09-25T02:09:54Z", + "logsEnabled": true, + "longDesc": "Apachecon North America 2018", + "matchList": [ + { + "type": "HOST_REGEXP", + "setNumber": 0, + "pattern": ".*\\.demo1\\..*" + } + ], + "maxDnsAnswers": null, + "midHeaderRewrite": null, + "missLat": 42, + "missLong": -88, + "multiSiteOrigin": false, + "originShield": null, + "orgServerFqdn": "http://origin.infra.ciab.test", + "profileDescription": null, + "profileId": null, + "profileName": null, + "protocol": 2, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "video", + "signed": false, + "sslKeyVersion": 1, + "tenantId": 1, + "type": "HTTP", + "typeId": 1, + "xmlId": "demo1", + "exampleURLs": [ + "http://video.demo1.mycdn.ciab.test", + "https://video.demo1.mycdn.ciab.test" + ], + "deepCachingType": "NEVER", + "fqPacingRate": null, + "signingAlgorithm": null, + "tenant": "root", + "trResponseHeaders": null, + "trRequestHeaders": null, + "consistentHashRegex": null, + "consistentHashQueryParams": [ + "abc", + "pdq", + "xxx", + "zyx" + ], + "maxOriginConnections": 0, + "ecsEnabled": false, + "rangeSliceBlockSize": null, + "topology": "demo1-top", + "firstHeaderRewrite": null, + "innerHeaderRewrite": null, + "lastHeaderRewrite": null, + "serviceCategory": null, + "tlsVersions": null + }, + "requested": { + "active": true, + "anonymousBlockingEnabled": false, + "cacheurl": null, + "ccrDnsTtl": 30, + "cdnId": 2, + "cdnName": null, + "checkPath": null, + "displayName": "Demo 1 but I modified the DSR", + "dnsBypassCname": null, + "dnsBypassIp": null, + "dnsBypassIp6": null, + "dnsBypassTtl": null, + "dscp": 0, + "edgeHeaderRewrite": null, + "geoLimit": 0, + "geoLimitCountries": null, + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": null, + "globalMaxTps": null, + "httpBypassFqdn": null, + "id": 1, + "infoUrl": null, + "initialDispersion": 3, + "ipv6RoutingEnabled": null, + "lastUpdated": null, + "logsEnabled": false, + "longDesc": "long desc", + "matchList": null, + "maxDnsAnswers": null, + "midHeaderRewrite": null, + "missLat": null, + "missLong": null, + "multiSiteOrigin": null, + "originShield": null, + "orgServerFqdn": null, + "profileDescription": null, + "profileId": null, + "profileName": null, + "protocol": null, + "qstringIgnore": null, + "rangeRequestHandling": null, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "cdn", + "signed": false, + "sslKeyVersion": null, + "tenantId": 1, + "type": null, + "typeId": 8, + "xmlId": "demo1", + "exampleURLs": null, + "deepCachingType": "NEVER", + "fqPacingRate": null, + "signingAlgorithm": null, + "tenant": null, + "trResponseHeaders": null, + "trRequestHeaders": null, + "consistentHashRegex": null, + "consistentHashQueryParams": null, + "maxOriginConnections": 0, + "ecsEnabled": false, + "rangeSliceBlockSize": null, + "topology": null, + "firstHeaderRewrite": null, + "innerHeaderRewrite": null, + "lastHeaderRewrite": null, + "serviceCategory": null, + "tlsVersions": null + }, + "status": "draft" + }} + + +``DELETE`` +========== +Deletes a :term:`Delivery Service Request`. + +:Auth. Required: Yes +:Roles Required: "admin", "Federation", "operations", "Portal", or "Steering" +:Permissions Required: DS-REQUEST:DELETE, DELIVERY-SERVICE:READ, USER:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-----------+----------+--------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +===========+==========+==================================================================================================+ + | id | yes | The integral, unique identifier of the :term:`Delivery Service Request` that you want to delete. | + +-----------+----------+--------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/deliveryservice_requests?id=1 HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 0 + +Response Structure +------------------ +The response is a full representation of the deleted :term:`Delivery Service Request`. + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 24 Feb 2020 20:48:55 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: jNCbNo8Tw+JMMaWpAYQgntSXPq2Xuj+n2zSEVRaDQFWMV1SYbT9djes6SPdwiBoKq6W0lNE04hOE92jBVcjtEw== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 24 Feb 2020 19:48:55 GMT + Content-Length: 96 + + { + "alerts": [ + { + "text": "deliveryservice_request was deleted.", + "level": "success" + } + ] + } diff --git a/docs/source/api/v5/deliveryservice_requests_id_assign.rst b/docs/source/api/v5/deliveryservice_requests_id_assign.rst new file mode 100644 index 0000000000..660c05a737 --- /dev/null +++ b/docs/source/api/v5/deliveryservice_requests_id_assign.rst @@ -0,0 +1,302 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservice_requests-id-assign: + +****************************************** +``deliveryservice_requests/{{ID}}/assign`` +****************************************** +Assign a :term:`Delivery Service Request` to a user. + +``GET`` +======= +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: DS-REQUEST:READ, USER:READ +:Response Type: Object (string) + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+-----------------------------------------------------------------------------------------------------------------+ + | Name | Description | + +======+=================================================================================================================+ + | ID | The integral, unique identifier of the :term:`Delivery Service Request` for which assignment is being retrieved | + +------+-----------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/deliveryservice_requests/1/assign HTTP/1.1 + User-Agent: python-requests/2.24.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + +Response Structure +------------------ +The response is the username of the user to whom the :term:`Delivery Service Request` is assigned - or ``null`` if it is unassigned. + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Tue, 02 Feb 2021 22:48:48 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + X-Server-Name: traffic_ops_golang/ + Date: Tue, 02 Feb 2021 21:48:48 GMT + Content-Length: 45 + + { "response": "admin" } + + +``PUT`` +======= +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: DS-REQUEST:UPDATE, DS-REQUEST:READ, USER:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+----------------------------------------------------------------------------------------+ + | Name | Description | + +======+========================================================================================+ + | ID | The integral, unique identifier of the :term:`Delivery Service Request` being assigned | + +------+----------------------------------------------------------------------------------------+ + +:assignee: The username of the user to whom the :term:`Delivery Service Request` is assigned +:assigneeId: The integral, unique identifier of the user to whom the :term:`Delivery Service Request` is assigned + + .. note:: + It is not required to send both of these; either property is sufficient to determine an :ref:`dsr-assignee`. In most cases, it's easier to use just `assignee`. If both *are* given, then `assigneeId` will take precedence in the event that the two properties do not refer to the same user. Sending a request that sets the assignee to ``null`` un-assigns the :term:`DSR` from any assignees it previously had\ [#implicit-null]_. + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/deliveryservice_requests/1/assign HTTP/1.1 + User-Agent: python-requests/2.24.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 20 + + {"assignee": "admin"} + +Response Structure +------------------ +The response contains a full representation of the newly assigned :term:`Delivery Service Request`. + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Sun, 23 Feb 2020 14:45:51 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: h7uBZHLQtRYbOSOR5AtQQrZ4uMeEWivWNT74fCf6WtLbAMwGpRrMjNmBYKduv48DEnRqG6WVM/4nBu3AkCUqPw== + X-Server-Name: traffic_ops_golang/ + Date: Sun, 23 Feb 2020 13:45:51 GMT + Content-Length: 931 + + { "alerts": [{ + "text": "Changed assignee of 'demo1' Delivery Service Request to 'admin'", + "level": "success" + }], + "response": { + "assignee": "admin", + "author": "admin", + "changeType": "update", + "createdAt": "2020-09-25T06:52:23.758877Z", + "id": 6, + "lastEditedBy": "admin", + "lastUpdated": "2020-09-25T07:01:24.600029Z", + "original": { + "active": true, + "anonymousBlockingEnabled": false, + "cacheurl": null, + "ccrDnsTtl": null, + "cdnId": 2, + "cdnName": "CDN-in-a-Box", + "checkPath": null, + "displayName": "Demo 1", + "dnsBypassCname": null, + "dnsBypassIp": null, + "dnsBypassIp6": null, + "dnsBypassTtl": null, + "dscp": 0, + "edgeHeaderRewrite": null, + "geoLimit": 0, + "geoLimitCountries": null, + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": null, + "globalMaxTps": null, + "httpBypassFqdn": null, + "id": 1, + "infoUrl": null, + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "lastUpdated": "2020-09-25T02:09:54Z", + "logsEnabled": true, + "longDesc": "Apachecon North America 2018", + "matchList": [ + { + "type": "HOST_REGEXP", + "setNumber": 0, + "pattern": ".*\\.demo1\\..*" + } + ], + "maxDnsAnswers": null, + "midHeaderRewrite": null, + "missLat": 42, + "missLong": -88, + "multiSiteOrigin": false, + "originShield": null, + "orgServerFqdn": "http://origin.infra.ciab.test", + "profileDescription": null, + "profileId": null, + "profileName": null, + "protocol": 2, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "video", + "signed": false, + "sslKeyVersion": 1, + "tenantId": 1, + "type": "HTTP", + "typeId": 1, + "xmlId": "demo1", + "exampleURLs": [ + "http://video.demo1.mycdn.ciab.test", + "https://video.demo1.mycdn.ciab.test" + ], + "deepCachingType": "NEVER", + "fqPacingRate": null, + "signingAlgorithm": null, + "tenant": "root", + "trResponseHeaders": null, + "trRequestHeaders": null, + "consistentHashRegex": null, + "consistentHashQueryParams": [ + "abc", + "pdq", + "xxx", + "zyx" + ], + "maxOriginConnections": 0, + "ecsEnabled": false, + "rangeSliceBlockSize": null, + "topology": "demo1-top", + "firstHeaderRewrite": null, + "innerHeaderRewrite": null, + "lastHeaderRewrite": null, + "serviceCategory": null, + "tlsVersions": null + }, + "requested": { + "active": true, + "anonymousBlockingEnabled": false, + "cacheurl": null, + "ccrDnsTtl": 30, + "cdnId": 2, + "cdnName": null, + "checkPath": null, + "displayName": "Demo 1 but modified by a DSR", + "dnsBypassCname": null, + "dnsBypassIp": null, + "dnsBypassIp6": null, + "dnsBypassTtl": null, + "dscp": 0, + "edgeHeaderRewrite": null, + "geoLimit": 0, + "geoLimitCountries": null, + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": null, + "globalMaxTps": null, + "httpBypassFqdn": null, + "id": 1, + "infoUrl": null, + "initialDispersion": 3, + "ipv6RoutingEnabled": null, + "lastUpdated": null, + "logsEnabled": false, + "longDesc": "long desc", + "matchList": null, + "maxDnsAnswers": null, + "midHeaderRewrite": null, + "missLat": null, + "missLong": null, + "multiSiteOrigin": null, + "originShield": null, + "orgServerFqdn": null, + "profileDescription": null, + "profileId": null, + "profileName": null, + "protocol": null, + "qstringIgnore": null, + "rangeRequestHandling": null, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "cdn", + "signed": false, + "sslKeyVersion": null, + "tenantId": 1, + "type": null, + "typeId": 8, + "xmlId": "demo1", + "exampleURLs": null, + "deepCachingType": "NEVER", + "fqPacingRate": null, + "signingAlgorithm": null, + "tenant": null, + "trResponseHeaders": null, + "trRequestHeaders": null, + "consistentHashRegex": null, + "consistentHashQueryParams": null, + "maxOriginConnections": 0, + "ecsEnabled": false, + "rangeSliceBlockSize": null, + "topology": null, + "firstHeaderRewrite": null, + "innerHeaderRewrite": null, + "lastHeaderRewrite": null, + "serviceCategory": null, + "tlsVersions": null + }, + "status": "draft" + }} + +.. [#implicit-null] Because of how the Traffic Ops API parses requests, there is no distinction between ``null`` and ``undefined``/missing properties. This means that sending the request payload ``{}`` in this case will result in the :term:`DSR` being unassigned. diff --git a/docs/source/api/v5/deliveryservice_requests_id_status.rst b/docs/source/api/v5/deliveryservice_requests_id_status.rst new file mode 100644 index 0000000000..c01cc9c77b --- /dev/null +++ b/docs/source/api/v5/deliveryservice_requests_id_status.rst @@ -0,0 +1,302 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservice_requests-id-status: + +****************************************** +``deliveryservice_requests/{{ID}}/status`` +****************************************** +Get or set the status of a :term:`Delivery Service Request`. + +``GET`` +======= +Gets the status of a :term:`DSR`. + +:Auth. Required: Yes +:Roles Required: "admin", "Federation", "operations", "Portal", or "Steering" +:Permissions Required: DS-REQUEST:READ +:Response Type: Object (string) + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+-----------------------------------------------------------------------------------------+ + | Name | Description | + +======+=========================================================================================+ + | ID | The integral, unique identifier of the :term:`Delivery Service Request` being inspected | + +------+-----------------------------------------------------------------------------------------+ + + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/deliveryservice_requests/1/status HTTP/1.1 + User-Agent: python-requests/2.24.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + +Response Structure +------------------ +The response is the status of the requested :term:`DSR`. + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Tue, 02 Feb 2021 22:56:47 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + X-Server-Name: traffic_ops_golang/ + Date: Tue, 02 Feb 2021 21:56:47 GMT + Content-Length: 45 + + { "response": "draft" } + + +``PUT`` +======= +:Auth. Required: Yes +:Roles Required: "admin", "Federation", "operations", "Portal", or "Steering" +:Permissions Required: DS-REQUEST:UPDATE, DS-REQUEST:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+-----------------------------------------------------------------------------------------+ + | Name | Description | + +======+=========================================================================================+ + | ID | The integral, unique identifier of the :term:`Delivery Service Request` being modified | + +------+-----------------------------------------------------------------------------------------+ + + +:status: The status of the :term:`DSR`. Can be "draft", "submitted", "rejected", "pending", or "complete". + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/deliveryservice_requests/1/status HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 28 + + { + "status": "rejected" + } + +Response Structure +------------------ +The response is a full representation of the modified :term:`DSR`. + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Sun, 23 Feb 2020 15:54:53 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: C8Nhciy1jv5X7CGgHwAnLp1qmLIzHq+4dvlAApb3cFSz5V2dABl7+N1Z4ndzB7GertB7rNLP31pVcat8vEz6rA== + X-Server-Name: traffic_ops_golang/ + Date: Sun, 23 Feb 2020 14:54:53 GMT + Content-Length: 930 + + { "alerts": [{ + "text": "Changed status of 'demo1' Delivery Service Request from 'draft' to 'submitted'", + "level": "success" + }], + "response": { + "assignee": "admin", + "author": "admin", + "changeType": "update", + "createdAt": "2020-09-25T06:52:23.758877Z", + "id": 6, + "lastEditedBy": "admin", + "lastUpdated": "2020-09-25T07:13:28.753352Z", + "original": { + "active": true, + "anonymousBlockingEnabled": false, + "cacheurl": null, + "ccrDnsTtl": null, + "cdnId": 2, + "cdnName": "CDN-in-a-Box", + "checkPath": null, + "displayName": "Demo 1", + "dnsBypassCname": null, + "dnsBypassIp": null, + "dnsBypassIp6": null, + "dnsBypassTtl": null, + "dscp": 0, + "edgeHeaderRewrite": null, + "geoLimit": 0, + "geoLimitCountries": null, + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": null, + "globalMaxTps": null, + "httpBypassFqdn": null, + "id": 1, + "infoUrl": null, + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "lastUpdated": "2020-09-25T02:09:54Z", + "logsEnabled": true, + "longDesc": "Apachecon North America 2018", + "matchList": [ + { + "type": "HOST_REGEXP", + "setNumber": 0, + "pattern": ".*\\.demo1\\..*" + } + ], + "maxDnsAnswers": null, + "midHeaderRewrite": null, + "missLat": 42, + "missLong": -88, + "multiSiteOrigin": false, + "originShield": null, + "orgServerFqdn": "http://origin.infra.ciab.test", + "profileDescription": null, + "profileId": null, + "profileName": null, + "protocol": 2, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "video", + "signed": false, + "sslKeyVersion": 1, + "tenantId": 1, + "type": "HTTP", + "typeId": 1, + "xmlId": "demo1", + "exampleURLs": [ + "http://video.demo1.mycdn.ciab.test", + "https://video.demo1.mycdn.ciab.test" + ], + "deepCachingType": "NEVER", + "fqPacingRate": null, + "signingAlgorithm": null, + "tenant": "root", + "trResponseHeaders": null, + "trRequestHeaders": null, + "consistentHashRegex": null, + "consistentHashQueryParams": [ + "abc", + "pdq", + "xxx", + "zyx" + ], + "maxOriginConnections": 0, + "ecsEnabled": false, + "rangeSliceBlockSize": null, + "topology": "demo1-top", + "firstHeaderRewrite": null, + "innerHeaderRewrite": null, + "lastHeaderRewrite": null, + "serviceCategory": null, + "tlsVersions": null + }, + "requested": { + "active": true, + "anonymousBlockingEnabled": false, + "cacheurl": null, + "ccrDnsTtl": 30, + "cdnId": 2, + "cdnName": null, + "checkPath": null, + "displayName": "Demo 1 but modified by a DSR", + "dnsBypassCname": null, + "dnsBypassIp": null, + "dnsBypassIp6": null, + "dnsBypassTtl": null, + "dscp": 0, + "edgeHeaderRewrite": null, + "geoLimit": 0, + "geoLimitCountries": null, + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": null, + "globalMaxTps": null, + "httpBypassFqdn": null, + "id": 1, + "infoUrl": null, + "initialDispersion": 3, + "ipv6RoutingEnabled": null, + "lastUpdated": null, + "logsEnabled": false, + "longDesc": "long desc", + "matchList": null, + "maxDnsAnswers": null, + "midHeaderRewrite": null, + "missLat": null, + "missLong": null, + "multiSiteOrigin": null, + "originShield": null, + "orgServerFqdn": null, + "profileDescription": null, + "profileId": null, + "profileName": null, + "protocol": null, + "qstringIgnore": null, + "rangeRequestHandling": null, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "cdn", + "signed": false, + "sslKeyVersion": null, + "tenantId": 1, + "type": null, + "typeId": 8, + "xmlId": "demo1", + "exampleURLs": null, + "deepCachingType": "NEVER", + "fqPacingRate": null, + "signingAlgorithm": null, + "tenant": null, + "trResponseHeaders": null, + "trRequestHeaders": null, + "consistentHashRegex": null, + "consistentHashQueryParams": null, + "maxOriginConnections": 0, + "ecsEnabled": false, + "rangeSliceBlockSize": null, + "topology": null, + "firstHeaderRewrite": null, + "innerHeaderRewrite": null, + "lastHeaderRewrite": null, + "serviceCategory": null, + "tlsVersions": null + }, + "status": "submitted" + }} diff --git a/docs/source/api/v5/deliveryservice_stats.rst b/docs/source/api/v5/deliveryservice_stats.rst new file mode 100644 index 0000000000..ddd59803f0 --- /dev/null +++ b/docs/source/api/v5/deliveryservice_stats.rst @@ -0,0 +1,182 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservice_stats: + +************************* +``deliveryservice_stats`` +************************* + +``GET`` +======= +Retrieves time-aggregated statistics on a specific :term:`Delivery Service`. + +:Auth. Required: Yes +:Roles Required: None\ [#tenancy]_ +:Permissions Required: STAT:READ, DELIVERY-SERVICE:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Query Parameters + + +---------------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +=====================+===================+===========================================================================================================================================================================================+ + | deliveryService | yes\ [#ds-param]_ | Either the :ref:`ds-xmlid` of a :term:`Delivery Service` for which statistics will be aggregated or the integral, unique identifier of said :term:`Delivery Service` | + +---------------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | deliveryServiceName | yes\ [#ds-param]_ | The :ref:`ds-xmlid` of the :term:`Delivery Service` for which statistics will be aggregated | + +---------------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | endDate | yes | The date and time until which statistics shall be aggregated in :rfc:`3339` format (with or without sub-second precision), the number of nanoseconds since the Unix | + | | | Epoch, or in the same, proprietary format as the ``lastUpdated`` fields prevalent throughout the Traffic Ops API | + +---------------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | exclude | no | Either "series" to omit the data series from the result, or "summary" to omit the summary data from the result - directly corresponds to fields in the | + | | | `Response Structure`_ | + +---------------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | interval | no | Specifies the interval within which data will be "bucketed"; e.g. when requesting data from 2019-07-25T00:00:00Z to 2019-07-25T23:59:59Z with an interval of "1m", | + | | | the resulting data series (assuming it is not excluded) should contain | + | | | :math:`24\frac{\mathrm{hours}}{\mathrm{day}}\times60\frac{\mathrm{minutes}}{\mathrm{hour}}\times1\mathrm{day}\times1\frac{\mathrm{minute}}{\mathrm{data point}}=1440\mathrm{data\;points}`| + | | | The allowed values for this parameter are valid InfluxQL duration literal strings matching :regexp:`^\d+[mhdw]$` | + | | | | + +---------------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | limit | no | A natural number indicating the maximum amount of data points should be returned in the ``series`` object | + +---------------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | metricType | yes | The metric type being reported - one of: | + | | | | + | | | kbps | + | | | The total traffic rate in kilobytes per second served by the :term:`Delivery Service` | + | | | tps_total | + | | | The total traffic rate in transactions per second served by the :term:`Delivery Service` | + | | | tps_2xx | + | | | The total traffic rate in transactions per second serviced with 200-299 HTTP status codes | + | | | tps_3xx | + | | | The total traffic rate in transactions per second serviced with 300-399 HTTP status codes | + | | | tps_4xx | + | | | The total traffic rate in transactions per second serviced with 400-499 HTTP status codes | + | | | tps_5xx | + | | | The total traffic rate in transactions per second serviced with 500-599 HTTP status codes | + | | | | + +---------------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | offset | no | A natural number of data points to drop from the beginning of the returned data set | + +---------------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | orderby | no | Though one struggles to imagine why, this can be used to specify "time" to sort data points by their "time" (which is the default behavior) | + +---------------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | startDate | yes | The date and time from which statistics shall be aggregated in :rfc:`3339` format (with or without sub-second precision), the number of nanoseconds since the Unix | + | | | Epoch, or in the same, proprietary format as the ``lastUpdated`` fields prevalent throughout the Traffic Ops API | + +---------------------+-------------------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + +.. _deliveryservice_stats-get-request-example: +.. code-block:: http + :caption: Request Example + + GET /api/5.0/deliveryservice_stats?deliveryServiceName=demo1&startDate=2019-07-22T17:55:00Z&endDate=2019-07-22T17:56:00.000Z&metricType=tps_total HTTP/1.1 + User-Agent: python-requests/2.20.1 + Accept-Encoding: gzip, deflate + Accept: application/json;timestamp=unix, application/json;timestamp=rfc;q=0.9, application/json;q=0.8, */*;q=0.7 + Connection: keep-alive + Cookie: mojolicious=... + +Content Format +"""""""""""""" +It's important to note in :ref:`deliveryservice_stats-get-request-example` the use of a complex "Accept" header. This endpoint accepts two special media types in the "Accept" header that instruct it on how to format the timestamps associated with the returned data. Specifically, Traffic Ops will recognize the special, optional, non-standard parameter of :mimetype:`application/json`: ``timestamp``. The values of this parameter are restricted to one of + +rfc + Returned timestamps will be formatted according to :rfc:`3339` (no sub-second precision). +unix + Returned timestamps will be formatted as the number of nanoseconds since the Unix Epoch (midnight on January 1\ :sup:`st` 1970 UTC). + + .. impl-detail:: The endpoint passes back nanoseconds, specifically, because that is the form used both by InfluxDB, which is used to store the data being served, and Go's standard library. Clients may need to convert the value to match their own standard libraries - e.g. the :js:class:`Date` class in Javascript expects milliseconds. + +The default behavior - when only e.g. :mimetype:`application/json` or :mimetype:`*/*` is given - is to use :rfc:`3339` formatting. It will, however, respect quality parameters. It is suggested that clients request timestamps they can handle specifically, rather than relying on this default behavior, as it **is subject to change** and is in fact **expected to invert in the next major release** as string-based time formats become deprecated. + +.. seealso:: For more information on the "Accept" HTTP header, consult `its dedicated page on MDN `_. + +Response Structure +------------------ +:series: An object containing the actual data series and information necessary for working with it. + + :columns: This is an array of names of the columns of the data contained in the "values" array - should always be ``["time", "sum_count"]`` + :count: The number of data points contained in the "values" array + :name: The name of the data set. Should always match :samp:`{metric}.ds.1min` where ``metric`` is the requested ``metricType`` + :values: The actual array of data points. Each represents a length of time specified by the ``interval`` query parameter + + :time: The time at which the measurement was taken. This corresponds to the *beginning* of the interval. This time comes in the format of either an :rfc:`3339`-formatted string, or a number containing the number of nanoseconds since the Unix Epoch depending on the "Accept" header sent by the client, according to the rules outlined in `Content Format`_. + :value: The value of the requested ``metricType`` at the time given by ``time``. This will always be a floating point number, unless no data is available for the data interval, in which case it will be ``null`` + +:summary: An object containing summary statistics describing the data series + + :average: The arithmetic mean of the data's values + :count: The number of measurements taken within the requested timespan. This is, in general, **not** the same as the ``count`` field of the ``series`` object, as it reflects the number of underlying, un-"bucketed" data points, and is therefore dependent on the implementation of Traffic Stats. + :fifthPercentile: Data points with values less than or equal to this number constitute the "bottom" 5% of the data set + :max: The maximum value that can be found in the requested data set + :min: The minimum value that can be found in the requested data set + :ninetyEighthPercentile: Data points with values greater than or equal to this number constitute the "top" 2% of the data set + :ninetyFifthPercentile: Data points with values greater than or equal to this number constitute the "top" 5% of the data set + :totalBytes: When the ``metricType`` requested is ``kbps``, this will contain the total number of bytes transferred by the :term:`Delivery Service` within the requested time window. Note that fractional amounts are possible, as the data transfer rate will almost certainly not be cleanly divided by the requested time range. + :totalTransactions: When the ``metricType`` requested is **not** ``kbps``, this will contain the total number of transactions completed by the :term:`Delivery Service` within the requested time window. Note that fractional amounts are possible, as the transaction rate will almost certainly not be cleanly divided by the requested time range. + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: zXJGjcYuu6HxWINVp8HA1gL31J3ukry5wCsTDNxtP/waC6rSD8h10KJ9jEAtRzJ9owOSVPvKaA/2bRu/QeuCpQ== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 22 Jul 2019 17:57:14 GMT + Transfer-Encoding: chunked + + { "response": { + "series": { + "columns": [ + "time", + "sum_count" + ], + "count": 2, + "name": "tps_total.ds.1min", + "tags": { + "cachegroup": "total" + }, + "values": [ + [ + 1563818100000000000, + 0 + ], + [ + 1563818160000000000, + 0 + ] + ] + }, + "summary": { + "average": 0, + "count": 2, + "fifthPercentile": 0, + "max": 0, + "min": 0, + "ninetyEighthPercentile": 0, + "ninetyFifthPercentile": 0, + "totalBytes": null, + "totalTransactions": 0 + } + }} + +.. [#tenancy] This endpoint respects :term:`Tenancy`, and users whose :term:`Tenant` does not have access to a :term:`Delivery Service` will be unable to view the statistics of said :term:`Delivery Service`. +.. [#ds-param] Either ``deliveryServiceName`` or ``deliveryService`` *must* be present, but if both are ``deliveryServiceName`` will be used and ``deliveryService`` will be ignored. diff --git a/docs/source/api/v5/deliveryservices.rst b/docs/source/api/v5/deliveryservices.rst new file mode 100644 index 0000000000..35e592ee2a --- /dev/null +++ b/docs/source/api/v5/deliveryservices.rst @@ -0,0 +1,607 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservices: + +******************** +``deliveryservices`` +******************** + +``GET`` +======= +Retrieves :term:`Delivery Services` + +:Auth. Required: Yes +:Roles Required: None\ [#tenancy]_ +:Permissions Required: DELIVERY-SERVICE:READ, CDN:READ, TYPE:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +===================+==========+=========================================================================================================================================+ + | cdn | no | Show only the :term:`Delivery Services` belonging to the :ref:`ds-cdn` identified by this integral, unique identifier | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | id | no | Show only the :term:`Delivery Service` that has this integral, unique identifier | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | logsEnabled | no | Show only the :term:`Delivery Services` that have :ref:`ds-logs-enabled` set or not based on this boolean | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | profile | no | Return only :term:`Delivery Services` using the :term:`Profile` that has this :ref:`profile-id` | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | tenant | no | Show only the :term:`Delivery Services` belonging to the :term:`Tenant` identified by this integral, unique identifier | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | topology | no | Show only the :term:`Delivery Services` assigned to the :term:`Topology` identified by this unique name | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | type | no | Return only :term:`Delivery Services` of the :term:`Delivery Service` :ref:`ds-types` identified by this integral, unique identifier | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | accessibleTo | no | Return the :term:`Delivery Services` accessible from a :term:`Tenant` *or it's children* identified by this integral, unique identifier | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | serviceCategory | no | Show only the :term:`Delivery Services` belonging to the :term:`Service Category` that has this name | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | xmlId | no | Show only the :term:`Delivery Service` that has this text-based, unique identifier | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | orderby | no | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` | + | | | array | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long and the first page is 1. | + | | | If ``offset`` was defined, this query parameter has no effect. ``limit`` must be defined to make use of ``page``. | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + | active | no | Show only the :term:`Delivery Services` that have :ref:`ds-active` set or not based on this boolean (whether or not they are active) | + +-------------------+----------+-----------------------------------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/deliveryservices?xmlId=demo2 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: python-requests/2.24.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + +Response Structure +------------------ +:active: A boolean that defines :ref:`ds-active`. +:anonymousBlockingEnabled: A boolean that defines :ref:`ds-anonymous-blocking` +:ccrDnsTtl: The :ref:`ds-dns-ttl` - named "ccrDnsTtl" for legacy reasons +:cdnId: The integral, unique identifier of the :ref:`ds-cdn` to which the :term:`Delivery Service` belongs +:cdnName: Name of the :ref:`ds-cdn` to which the :term:`Delivery Service` belongs +:checkPath: A :ref:`ds-check-path` +:consistentHashRegex: A :ref:`ds-consistent-hashing-regex` +:consistentHashQueryParams: An array of :ref:`ds-consistent-hashing-qparams` +:deepCachingType: The :ref:`ds-deep-caching` setting for this :term:`Delivery Service` +:displayName: The :ref:`ds-display-name` +:dnsBypassCname: A :ref:`ds-dns-bypass-cname` +:dnsBypassIp: A :ref:`ds-dns-bypass-ip` +:dnsBypassIp6: A :ref:`ds-dns-bypass-ipv6` +:dnsBypassTtl: The :ref:`ds-dns-bypass-ttl` +:dscp: A :ref:`ds-dscp` to be used within the :term:`Delivery Service` +:ecsEnabled: A boolean that defines the :ref:`ds-ecs` setting on this :term:`Delivery Service` +:edgeHeaderRewrite: A set of :ref:`ds-edge-header-rw-rules` +:exampleURLs: An array of :ref:`ds-example-urls` +:firstHeaderRewrite: A set of :ref:`ds-first-header-rw-rules` +:fqPacingRate: The :ref:`ds-fqpr` +:geoLimit: An integer that defines the :ref:`ds-geo-limit` +:geoLimitCountries: An array of strings defining the :ref:`ds-geo-limit-countries` +:geoLimitRedirectUrl: A :ref:`ds-geo-limit-redirect-url` +:geoProvider: The :ref:`ds-geo-provider` +:globalMaxMbps: The :ref:`ds-global-max-mbps` +:globalMaxTps: The :ref:`ds-global-max-tps` +:httpBypassFqdn: A :ref:`ds-http-bypass-fqdn` +:id: An integral, unique identifier for this :term:`Delivery Service` +:infoUrl: An :ref:`ds-info-url` +:initialDispersion: The :ref:`ds-initial-dispersion` +: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 +: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` + + :pattern: A regular expression - the use of this pattern is dependent on the ``type`` field (backslashes are escaped) + :setNumber: An integer that provides explicit ordering of :ref:`ds-matchlist` items - this is used as a priority ranking by Traffic Router, and is not guaranteed to correspond to the ordering of items in the array. + :type: The type of match performed using ``pattern``. + +:maxDnsAnswers: The :ref:`ds-max-dns-answers` allowed for this :term:`Delivery Service` +:maxOriginConnections: The :ref:`ds-max-origin-connections` +:maxRequestHeaderBytes: The :ref:`ds-max-request-header-bytes` +:midHeaderRewrite: A set of :ref:`ds-mid-header-rw-rules` +:missLat: The :ref:`ds-geo-miss-default-latitude` used by this :term:`Delivery Service` +:missLong: The :ref:`ds-geo-miss-default-longitude` used by this :term:`Delivery Service` +:multiSiteOrigin: A boolean that defines the use of :ref:`ds-multi-site-origin` by this :term:`Delivery Service` +:orgServerFqdn: The :ref:`ds-origin-url` +:originShield: A :ref:`ds-origin-shield` string +:profileDescription: The :ref:`profile-description` of the :ref:`ds-profile` with which this :term:`Delivery Service` is associated +:profileId: The :ref:`profile-id` of the :ref:`ds-profile` with which this :term:`Delivery Service` is associated +:profileName: The :ref:`profile-name` of the :ref:`ds-profile` with which this :term:`Delivery Service` is associated +:protocol: An integral, unique identifier that corresponds to the :ref:`ds-protocol` used by this :term:`Delivery Service` +:qstringIgnore: An integral, unique identifier that corresponds to the :ref:`ds-qstring-handling` setting on this :term:`Delivery Service` +:rangeRequestHandling: An integral, unique identifier that corresponds to the :ref:`ds-range-request-handling` setting on this :term:`Delivery Service` +:regexRemap: A :ref:`ds-regex-remap` +:regionalGeoBlocking: A boolean defining the :ref:`ds-regionalgeo` setting on this :term:`Delivery Service` +:remapText: :ref:`ds-raw-remap` +:serviceCategory: The name of the :ref:`ds-service-category` with which the :term:`Delivery Service` is associated +:signed: ``true`` if and only if ``signingAlgorithm`` is not ``null``, ``false`` otherwise +:signingAlgorithm: Either a :ref:`ds-signing-algorithm` or ``null`` to indicate URL/URI signing is not implemented on this :term:`Delivery Service` +:rangeSliceBlockSize: An integer that defines the byte block size for the ATS Slice Plugin. It can only and must be set if ``rangeRequestHandling`` is set to 3. +:sslKeyVersion: This integer indicates the :ref:`ds-ssl-key-version` +:tenantId: The integral, unique identifier of the :ref:`ds-tenant` who owns this :term:`Delivery Service` +:tlsVersions: A list of explicitly supported :ref:`ds-tls-versions` +:topology: The unique name of the :term:`Topology` that this :term:`Delivery Service` is assigned to +:trRequestHeaders: If defined, this defines the :ref:`ds-tr-req-headers` used by Traffic Router for this :term:`Delivery Service` +:trResponseHeaders: If defined, this defines the :ref:`ds-tr-resp-headers` used by Traffic Router for this :term:`Delivery Service` +:type: The :ref:`ds-types` of this :term:`Delivery Service` +:typeId: The integral, unique identifier of the :ref:`ds-types` of this :term:`Delivery Service` +:xmlId: This :term:`Delivery Service`'s :ref:`ds-xmlid` + +.. 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-Encoding: gzip + Content-Type: application/json + Permissions-Policy: interest-cohort=() + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 07 Jun 2021 22:52:20 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + X-Server-Name: traffic_ops_golang/ + Date: Mon, 07 Jun 2021 21:52:20 GMT + Content-Length: 847 + + { "response": [ + { + "active": true, + "anonymousBlockingEnabled": false, + "ccrDnsTtl": null, + "cdnId": 2, + "cdnName": "CDN-in-a-Box", + "checkPath": null, + "consistentHashQueryParams": [], + "consistentHashRegex": null, + "deepCachingType": "NEVER", + "displayName": "Demo 2", + "dnsBypassCname": null, + "dnsBypassIp": null, + "dnsBypassIp6": null, + "dnsBypassTtl": null, + "dscp": 0, + "ecsEnabled": false, + "edgeHeaderRewrite": null, + "exampleURLs": [ + "http://video.demo2.mycdn.ciab.test", + "https://video.demo2.mycdn.ciab.test" + ], + "firstHeaderRewrite": null, + "fqPacingRate": null, + "geoLimit": 0, + "geoLimitCountries": null, + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": null, + "globalMaxTps": null, + "httpBypassFqdn": null, + "id": 1, + "infoUrl": null, + "initialDispersion": 1, + "innerHeaderRewrite": null, + "ipv6RoutingEnabled": true, + "lastHeaderRewrite": null, + "lastUpdated": "2021-06-07T21:50:03.009954Z", + "logsEnabled": true, + "longDesc": "DNS Delivery Service for use with a Federation", + "matchList": [ + { + "type": "HOST_REGEXP", + "setNumber": 0, + "pattern": ".*\\.demo2\\..*" + } + ], + "maxDnsAnswers": null, + "maxOriginConnections": 0, + "maxRequestHeaderBytes": 0, + "midHeaderRewrite": null, + "missLat": 42, + "missLong": -88, + "multiSiteOrigin": true, + "originShield": null, + "orgServerFqdn": "http://origin.infra.ciab.test", + "profileDescription": null, + "profileId": null, + "profileName": null, + "protocol": 2, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "rangeSliceBlockSize": null, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "video", + "serviceCategory": null, + "signed": false, + "signingAlgorithm": null, + "sslKeyVersion": null, + "tenant": "root", + "tenantId": 1, + "tlsVersions": null, + "topology": "demo1-top", + "trResponseHeaders": null, + "trRequestHeaders": null, + "type": "DNS", + "typeId": 5, + "xmlId": "demo2" + } + ]} + + +``POST`` +======== +Allows users to create :term:`Delivery Service`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations"\ [#tenancy]_ +:Permissions Required: DELIVERY-SERVICE:CREATE, DELIVERY-SERVICE:READ, CDN:READ, TYPE:READ +:Response Type: Array + +Request Structure +----------------- +:active: A boolean that defines :ref:`ds-active`. +:anonymousBlockingEnabled: A boolean that defines :ref:`ds-anonymous-blocking` +:ccrDnsTtl: The :ref:`ds-dns-ttl` - named "ccrDnsTtl" for legacy reasons +:cdnId: The integral, unique identifier of the :ref:`ds-cdn` to which the :term:`Delivery Service` belongs +:checkPath: A :ref:`ds-check-path` +:consistentHashRegex: A :ref:`ds-consistent-hashing-regex` +:consistentHashQueryParams: An array of :ref:`ds-consistent-hashing-qparams` +:deepCachingType: The :ref:`ds-deep-caching` setting for this :term:`Delivery Service` +:displayName: The :ref:`ds-display-name` +:dnsBypassCname: A :ref:`ds-dns-bypass-cname` +:dnsBypassIp: A :ref:`ds-dns-bypass-ip` +:dnsBypassIp6: A :ref:`ds-dns-bypass-ipv6` +:dnsBypassTtl: The :ref:`ds-dns-bypass-ttl` +:dscp: A :ref:`ds-dscp` to be used within the :term:`Delivery Service` +:ecsEnabled: A boolean that defines the :ref:`ds-ecs` setting on this :term:`Delivery Service` +:edgeHeaderRewrite: A set of :ref:`ds-edge-header-rw-rules` +:firstHeaderRewrite: A set of :ref:`ds-first-header-rw-rules` +:fqPacingRate: The :ref:`ds-fqpr` +:geoLimit: An integer that defines the :ref:`ds-geo-limit` +:geoLimitCountries: A string containing a comma-separated list, or an array of strings defining the :ref:`ds-geo-limit-countries`\ [#geolimit]_ +:geoLimitRedirectUrl: A :ref:`ds-geo-limit-redirect-url`\ [#geolimit]_ +:geoProvider: The :ref:`ds-geo-provider` +:globalMaxMbps: The :ref:`ds-global-max-mbps` +:globalMaxTps: The :ref:`ds-global-max-tps` +:httpBypassFqdn: A :ref:`ds-http-bypass-fqdn` +:infoUrl: An :ref:`ds-info-url` +:initialDispersion: The :ref:`ds-initial-dispersion` +: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` +: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` +:maxDnsAnswers: The :ref:`ds-max-dns-answers` allowed for this :term:`Delivery Service` +:maxOriginConnections: The :ref:`ds-max-origin-connections` +:maxRequestHeaderBytes: The :ref:`ds-max-request-header-bytes` +:midHeaderRewrite: A set of :ref:`ds-mid-header-rw-rules` +:missLat: The :ref:`ds-geo-miss-default-latitude` used by this :term:`Delivery Service` +:missLong: The :ref:`ds-geo-miss-default-longitude` used by this :term:`Delivery Service` +:multiSiteOrigin: A boolean that defines the use of :ref:`ds-multi-site-origin` by this :term:`Delivery Service` +:orgServerFqdn: The :ref:`ds-origin-url` +:originShield: A :ref:`ds-origin-shield` string +:profileId: An optional :ref:`profile-id` of a :ref:`ds-profile` with which this :term:`Delivery Service` shall be associated +:protocol: An integral, unique identifier that corresponds to the :ref:`ds-protocol` used by this :term:`Delivery Service` +:qstringIgnore: An integral, unique identifier that corresponds to the :ref:`ds-qstring-handling` setting on this :term:`Delivery Service` +:rangeRequestHandling: An integral, unique identifier that corresponds to the :ref:`ds-range-request-handling` setting on this :term:`Delivery Service` +:regexRemap: A :ref:`ds-regex-remap` +:regionalGeoBlocking: A boolean defining the :ref:`ds-regionalgeo` setting on this :term:`Delivery Service` +:remapText: :ref:`ds-raw-remap` +:serviceCategory: The name of the :ref:`ds-service-category` with which the :term:`Delivery Service` is associated - or ``null`` if there is to be no such category +:signed: ``true`` if and only if ``signingAlgorithm`` is not ``null``, ``false`` otherwise +:signingAlgorithm: Either a :ref:`ds-signing-algorithm` or ``null`` to indicate URL/URI signing is not implemented on this :term:`Delivery Service` +:rangeSliceBlockSize: An integer that defines the byte block size for the ATS Slice Plugin. It can only and must be set if ``rangeRequestHandling`` is set to 3. It can only be between (inclusive) 262144 (256KB) - 33554432 (32MB). +:sslKeyVersion: This integer indicates the :ref:`ds-ssl-key-version` +:tenantId: The integral, unique identifier of the :ref:`ds-tenant` who owns this :term:`Delivery Service` +:tlsVersions: An array of explicitly supported :ref:`ds-tls-versions` +:topology: The unique name of the :term:`Topology` that this :term:`Delivery Service` is assigned to +:trRequestHeaders: If defined, this defines the :ref:`ds-tr-req-headers` used by Traffic Router for this :term:`Delivery Service` +:trResponseHeaders: If defined, this defines the :ref:`ds-tr-resp-headers` used by Traffic Router for this :term:`Delivery Service` +:type: The :ref:`ds-types` of this :term:`Delivery Service` +:typeId: The integral, unique identifier of the :ref:`ds-types` of this :term:`Delivery Service` +:xmlId: This :term:`Delivery Service`'s :ref:`ds-xmlid` + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/deliveryservices HTTP/1.1 + User-Agent: python-requests/2.24.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 1602 + Content-Type: application/json + Host: trafficops.infra.ciab.test + + { + "active": false, + "anonymousBlockingEnabled": false, + "ccrDnsTtl": null, + "cdnId": 2, + "checkPath": null, + "consistentHashRegex": null, + "consistentHashQueryParams": [], + "deepCachingType": "NEVER", + "displayName": "test", + "dnsBypassCname": null, + "dnsBypassIp": null, + "dnsBypassIp6": null, + "dnsBypassTtl": null, + "dscp": 0, + "ecsEnabled": true, + "edgeHeaderRewrite": null, + "firstHeaderRewrite": null, + "fqPacingRate": null, + "geoLimit": 0, + "geoLimitCountries": null, + "geoLimitRedirectUrl": null, + "geoProvider": 0, + "globalMaxMbps": null, + "globalMaxTps": null, + "httpBypassFqdn": null, + "infoUrl": null, + "initialDispersion": 1, + "innerHeaderRewrite": null, + "ipv6RoutingEnabled": false, + "lastHeaderRewrite": null, + "logsEnabled": true, + "longDesc": "A Delivery Service created expressly for API documentation examples", + "maxDnsAnswers": null, + "missLat": 0, + "missLong": 0, + "maxOriginConnections": 0, + "maxRequestHeaderBytes": 131072, + "midHeaderRewrite": null, + "multiSiteOrigin": false, + "orgServerFqdn": "http://origin.infra.ciab.test", + "originShield": null, + "profileId": null, + "protocol": 0, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regexRemap": null, + "regionalGeoBlocking": false, + "routingName": "test", + "serviceCategory": null, + "signed": false, + "signingAlgorithm": null, + "rangeSliceBlockSize": null, + "sslKeyVersion": null, + "tenant": "root", + "tenantId": 1, + "tlsVersions": [ + "1.2", + "1.3" + ], + "topology": null, + "trRequestHeaders": null, + "trResponseHeaders": null, + "type": "HTTP", + "typeId": 1, + "xmlId": "test" + } + + +Response Structure +------------------ +:active: A boolean that defines :ref:`ds-active`. +:anonymousBlockingEnabled: A boolean that defines :ref:`ds-anonymous-blocking` +:ccrDnsTtl: The :ref:`ds-dns-ttl` - named "ccrDnsTtl" for legacy reasons +:cdnId: The integral, unique identifier of the :ref:`ds-cdn` to which the :term:`Delivery Service` belongs +:cdnName: Name of the :ref:`ds-cdn` to which the :term:`Delivery Service` belongs +:checkPath: A :ref:`ds-check-path` +:consistentHashRegex: A :ref:`ds-consistent-hashing-regex` +:consistentHashQueryParams: An array of :ref:`ds-consistent-hashing-qparams` +:deepCachingType: The :ref:`ds-deep-caching` setting for this :term:`Delivery Service` +:displayName: The :ref:`ds-display-name` +:dnsBypassCname: A :ref:`ds-dns-bypass-cname` +:dnsBypassIp: A :ref:`ds-dns-bypass-ip` +:dnsBypassIp6: A :ref:`ds-dns-bypass-ipv6` +:dnsBypassTtl: The :ref:`ds-dns-bypass-ttl` +:dscp: A :ref:`ds-dscp` to be used within the :term:`Delivery Service` +:ecsEnabled: A boolean that defines the :ref:`ds-ecs` setting on this :term:`Delivery Service` +:edgeHeaderRewrite: A set of :ref:`ds-edge-header-rw-rules` +:exampleURLs: An array of :ref:`ds-example-urls` +:firstHeaderRewrite: A set of :ref:`ds-first-header-rw-rules` +:fqPacingRate: The :ref:`ds-fqpr` +:geoLimit: An integer that defines the :ref:`ds-geo-limit` +:geoLimitCountries: An array of strings defining the :ref:`ds-geo-limit-countries` +:geoLimitRedirectUrl: A :ref:`ds-geo-limit-redirect-url` +:geoProvider: The :ref:`ds-geo-provider` +:globalMaxMbps: The :ref:`ds-global-max-mbps` +:globalMaxTps: The :ref:`ds-global-max-tps` +:httpBypassFqdn: A :ref:`ds-http-bypass-fqdn` +:id: An integral, unique identifier for this :term:`Delivery Service` +:infoUrl: An :ref:`ds-info-url` +:initialDispersion: The :ref:`ds-initial-dispersion` +: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 +: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` + + :pattern: A regular expression - the use of this pattern is dependent on the ``type`` field (backslashes are escaped) + :setNumber: An integer that provides explicit ordering of :ref:`ds-matchlist` items - this is used as a priority ranking by Traffic Router, and is not guaranteed to correspond to the ordering of items in the array. + :type: The type of match performed using ``pattern``. + +:maxDnsAnswers: The :ref:`ds-max-dns-answers` allowed for this :term:`Delivery Service` +:maxOriginConnections: The :ref:`ds-max-origin-connections` +:maxRequestHeaderBytes: The :ref:`ds-max-request-header-bytes` +:midHeaderRewrite: A set of :ref:`ds-mid-header-rw-rules` +:missLat: The :ref:`ds-geo-miss-default-latitude` used by this :term:`Delivery Service` +:missLong: The :ref:`ds-geo-miss-default-longitude` used by this :term:`Delivery Service` +:multiSiteOrigin: A boolean that defines the use of :ref:`ds-multi-site-origin` by this :term:`Delivery Service` +:orgServerFqdn: The :ref:`ds-origin-url` +:originShield: A :ref:`ds-origin-shield` string +:profileDescription: The :ref:`profile-description` of the :ref:`ds-profile` with which this :term:`Delivery Service` is associated +:profileId: The :ref:`profile-id` of the :ref:`ds-profile` with which this :term:`Delivery Service` is associated +:profileName: The :ref:`profile-name` of the :ref:`ds-profile` with which this :term:`Delivery Service` is associated +:protocol: An integral, unique identifier that corresponds to the :ref:`ds-protocol` used by this :term:`Delivery Service` +:qstringIgnore: An integral, unique identifier that corresponds to the :ref:`ds-qstring-handling` setting on this :term:`Delivery Service` +:rangeRequestHandling: An integral, unique identifier that corresponds to the :ref:`ds-range-request-handling` setting on this :term:`Delivery Service` +:regexRemap: A :ref:`ds-regex-remap` +:regionalGeoBlocking: A boolean defining the :ref:`ds-regionalgeo` setting on this :term:`Delivery Service` +:remapText: :ref:`ds-raw-remap` +:serviceCategory: The name of the :ref:`ds-service-category` with which the :term:`Delivery Service` is associated +:signed: ``true`` if and only if ``signingAlgorithm`` is not ``null``, ``false`` otherwise +:signingAlgorithm: Either a :ref:`ds-signing-algorithm` or ``null`` to indicate URL/URI signing is not implemented on this :term:`Delivery Service` +:rangeSliceBlockSize: An integer that defines the byte block size for the ATS Slice Plugin. It can only and must be set if ``rangeRequestHandling`` is set to 3. +:sslKeyVersion: This integer indicates the :ref:`ds-ssl-key-version` +:tenantId: The integral, unique identifier of the :ref:`ds-tenant` who owns this :term:`Delivery Service` +:tlsVersions: An array of explicitly supported :ref:`ds-tls-versions` +:topology: The unique name of the :term:`Topology` that this :term:`Delivery Service` is assigned to +:trRequestHeaders: If defined, this defines the :ref:`ds-tr-req-headers` used by Traffic Router for this :term:`Delivery Service` +:trResponseHeaders: If defined, this defines the :ref:`ds-tr-resp-headers` used by Traffic Router for this :term:`Delivery Service` +:type: The :ref:`ds-types` of this :term:`Delivery Service` +:typeId: The integral, unique identifier of the :ref:`ds-types` of this :term:`Delivery Service` +:xmlId: This :term:`Delivery Service`'s :ref:`ds-xmlid` + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 201 Created + 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-Encoding: gzip + Content-Type: application/json + Location: /api/5.0/deliveryservices?id=6 + Permissions-Policy: interest-cohort=() + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 07 Jun 2021 23:37:37 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + X-Server-Name: traffic_ops_golang/ + Date: Mon, 07 Jun 2021 22:37:37 GMT + Content-Length: 903 + + { "alerts": [ + { + "text": "tlsVersions has no effect on 'HTTP' Delivery Services", + "level": "warning" + }, + { + "text": "Delivery Service creation was successful", + "level": "success" + } + ], + "response": [{ + "active": false, + "anonymousBlockingEnabled": false, + "ccrDnsTtl": null, + "cdnId": 2, + "cdnName": null, + "checkPath": null, + "consistentHashQueryParams": [], + "consistentHashRegex": null, + "deepCachingType": "NEVER", + "displayName": "test", + "dnsBypassCname": null, + "dnsBypassIp": null, + "dnsBypassIp6": null, + "dnsBypassTtl": null, + "dscp": 0, + "ecsEnabled": true, + "edgeHeaderRewrite": null, + "exampleURLs": [ + "http://test.test.mycdn.ciab.test" + ], + "firstHeaderRewrite": null, + "fqPacingRate": null, + "geoLimit": 0, + "geoLimitCountries": null, + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": null, + "globalMaxTps": null, + "httpBypassFqdn": null, + "id": 6, + "infoUrl": null, + "initialDispersion": 1, + "innerHeaderRewrite": null, + "ipv6RoutingEnabled": false, + "lastHeaderRewrite": null, + "lastUpdated": "2021-06-07T22:37:37.187822Z", + "logsEnabled": true, + "longDesc": "A Delivery Service created expressly for API documentation examples", + "matchList": [ + { + "type": "HOST_REGEXP", + "setNumber": 0, + "pattern": ".*\\.test\\..*" + } + ], + "maxDnsAnswers": null, + "maxOriginConnections": 0, + "maxRequestHeaderBytes": 131072, + "midHeaderRewrite": null, + "missLat": 0, + "missLong": 0, + "multiSiteOrigin": false, + "originShield": null, + "orgServerFqdn": "http://origin.infra.ciab.test", + "profileDescription": null, + "profileId": null, + "profileName": null, + "protocol": 0, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "rangeSliceBlockSize": null, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "test", + "serviceCategory": null, + "signed": false, + "signingAlgorithm": null, + "sslKeyVersion": null, + "tenant": "root", + "tenantId": 1, + "tlsVersions": [ + "1.2", + "1.3" + ], + "topology": null, + "trResponseHeaders": null, + "trRequestHeaders": null, + "type": "HTTP", + "typeId": 1, + "xmlId": "test" + }]} + + +.. [#tenancy] Only those :term:`Delivery Services` assigned to :term:`Tenants` that are the requesting user's :term:`Tenant` or children thereof will appear in the output of a ``GET`` request, and the same constraints are placed on the allowed values of the ``tenantId`` field of a ``POST`` request to create a new :term:`Delivery Service` +.. [#geoLimit] These fields must be defined if and only if ``geoLimit`` is non-zero diff --git a/docs/source/api/v5/deliveryservices_id.rst b/docs/source/api/v5/deliveryservices_id.rst new file mode 100644 index 0000000000..97f66e9205 --- /dev/null +++ b/docs/source/api/v5/deliveryservices_id.rst @@ -0,0 +1,415 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservices-id: + +*************************** +``deliveryservices/{{ID}}`` +*************************** + +``PUT`` +======= +Allows users to edit an existing :term:`Delivery Service`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations"\ [#tenancy]_ +:Permissions Required: DELIVERY-SERVICE:UPDATE, DELIVERY-SERVICE:READ, CDN:READ, TYPE:READ +:Response Type: Array (should always have a length of exactly one on success) + +Request Structure +----------------- +:active: A boolean that defines :ref:`ds-active`. +:anonymousBlockingEnabled: A boolean that defines :ref:`ds-anonymous-blocking` +:ccrDnsTtl: The :ref:`ds-dns-ttl` - named "ccrDnsTtl" for legacy reasons +:cdnId: The integral, unique identifier of the :ref:`ds-cdn` to which the :term:`Delivery Service` belongs + + .. note:: If the Delivery Service has SSL Keys, then cdnId is not allowed to change as that would invalidate the SSL Key + +:checkPath: A :ref:`ds-check-path` +:consistentHashRegex: A :ref:`ds-consistent-hashing-regex` +:consistentHashQueryParams: An array of :ref:`ds-consistent-hashing-qparams` +:deepCachingType: The :ref:`ds-deep-caching` setting for this :term:`Delivery Service` +:displayName: The :ref:`ds-display-name` +:dnsBypassCname: A :ref:`ds-dns-bypass-cname` +:dnsBypassIp: A :ref:`ds-dns-bypass-ip` +:dnsBypassIp6: A :ref:`ds-dns-bypass-ipv6` +:dnsBypassTtl: The :ref:`ds-dns-bypass-ttl` +:dscp: A :ref:`ds-dscp` to be used within the :term:`Delivery Service` +:ecsEnabled: A boolean that defines the :ref:`ds-ecs` setting on this :term:`Delivery Service` +:edgeHeaderRewrite: A set of :ref:`ds-edge-header-rw-rules` +:firstHeaderRewrite: A set of :ref:`ds-first-header-rw-rules` +:fqPacingRate: The :ref:`ds-fqpr` +:geoLimit: An integer that defines the :ref:`ds-geo-limit` +:geoLimitCountries: A string containing a comma-separated list, or an array of strings defining the :ref:`ds-geo-limit-countries`\ [#geolimit]_ +:geoLimitRedirectUrl: A :ref:`ds-geo-limit-redirect-url`\ [#geolimit]_ +:geoProvider: The :ref:`ds-geo-provider` +:globalMaxMbps: The :ref:`ds-global-max-mbps` +:globalMaxTps: The :ref:`ds-global-max-tps` +:httpBypassFqdn: A :ref:`ds-http-bypass-fqdn` +:infoUrl: An :ref:`ds-info-url` +:initialDispersion: The :ref:`ds-initial-dispersion` +: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` +: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` +:maxDnsAnswers: The :ref:`ds-max-dns-answers` allowed for this :term:`Delivery Service` +:maxOriginConnections: The :ref:`ds-max-origin-connections` +:midHeaderRewrite: A set of :ref:`ds-mid-header-rw-rules` +:missLat: The :ref:`ds-geo-miss-default-latitude` used by this :term:`Delivery Service` +:missLong: The :ref:`ds-geo-miss-default-longitude` used by this :term:`Delivery Service` +:multiSiteOrigin: A boolean that defines the use of :ref:`ds-multi-site-origin` by this :term:`Delivery Service` +:orgServerFqdn: The :ref:`ds-origin-url` +:originShield: A :ref:`ds-origin-shield` string +:profileId: An optional :ref:`profile-id` of the :ref:`ds-profile` with which this :term:`Delivery Service` will be associated +:protocol: An integral, unique identifier that corresponds to the :ref:`ds-protocol` used by this :term:`Delivery Service` +:qstringIgnore: An integral, unique identifier that corresponds to the :ref:`ds-qstring-handling` setting on this :term:`Delivery Service` +:rangeRequestHandling: An integral, unique identifier that corresponds to the :ref:`ds-range-request-handling` setting on this :term:`Delivery Service` +:regexRemap: A :ref:`ds-regex-remap` +:regionalGeoBlocking: A boolean defining the :ref:`ds-regionalgeo` setting on this :term:`Delivery Service` +:remapText: :ref:`ds-raw-remap` +:routingName: The :ref:`ds-routing-name` of this :term:`Delivery Service` + + .. note:: If the Delivery Service has SSL Keys, then ``routingName`` is not allowed to change as that would invalidate the SSL Key + +:signed: ``true`` if and only if ``signingAlgorithm`` is not ``null``, ``false`` otherwise +:signingAlgorithm: Either a :ref:`ds-signing-algorithm` or ``null`` to indicate URL/URI signing is not implemented on this :term:`Delivery Service` +:rangeSliceBlockSize: An integer that defines the byte block size for the ATS Slice Plugin. It can only and must be set if ``rangeRequestHandling`` is set to 3. It can only be between (inclusive) 262144 (256KB) - 33554432 (32MB). +:sslKeyVersion: This integer indicates the :ref:`ds-ssl-key-version` +:tenantId: The integral, unique identifier of the :ref:`ds-tenant` who owns this :term:`Delivery Service` +:tlsVersions: An array of explicitly supported :ref:`ds-tls-versions` +:topology: The unique name of the :term:`Topology` that this :term:`Delivery Service` is assigned to +:trRequestHeaders: If defined, this defines the :ref:`ds-tr-req-headers` used by Traffic Router for this :term:`Delivery Service` +:trResponseHeaders: If defined, this defines the :ref:`ds-tr-resp-headers` used by Traffic Router for this :term:`Delivery Service` +:typeId: The integral, unique identifier of the :ref:`ds-types` of this :term:`Delivery Service` +:xmlId: This :term:`Delivery Service`'s :ref:`ds-xmlid` + + .. note:: While this field **must** be present, it is **not** allowed to change; this must be the same as the ``xml_id`` the :term:`Delivery Service` already has. This should almost never be different from the :term:`Delivery Service`'s ``displayName``. + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/deliveryservices/6 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: python-requests/2.24.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 1585 + Content-Type: application/json + + { + "active": false, + "anonymousBlockingEnabled": false, + "ccrDnsTtl": null, + "cdnId": 2, + "checkPath": null, + "consistentHashRegex": null, + "consistentHashQueryParams": [], + "deepCachingType": "NEVER", + "displayName": "test", + "dnsBypassCname": null, + "dnsBypassIp": null, + "dnsBypassIp6": null, + "dnsBypassTtl": null, + "dscp": 0, + "ecsEnabled": true, + "edgeHeaderRewrite": null, + "firstHeaderRewrite": null, + "fqPacingRate": null, + "geoLimit": 0, + "geoLimitCountries": null, + "geoLimitRedirectUrl": null, + "geoProvider": 0, + "globalMaxMbps": null, + "globalMaxTps": null, + "httpBypassFqdn": null, + "infoUrl": null, + "initialDispersion": 1, + "innerHeaderRewrite": null, + "ipv6RoutingEnabled": false, + "lastHeaderRewrite": null, + "logsEnabled": true, + "longDesc": "A Delivery Service created expressly for API documentation examples", + "maxDnsAnswers": null, + "missLat": 0, + "missLong": 0, + "maxOriginConnections": 0, + "maxRequestHeaderBytes": 131072, + "midHeaderRewrite": null, + "multiSiteOrigin": false, + "orgServerFqdn": "http://origin.infra.ciab.test", + "originShield": null, + "profileId": null, + "protocol": 0, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regexRemap": null, + "regionalGeoBlocking": false, + "routingName": "test", + "serviceCategory": null, + "signed": false, + "signingAlgorithm": null, + "rangeSliceBlockSize": null, + "sslKeyVersion": null, + "tenant": "root", + "tenantId": 1, + "tlsVersions": null, + "topology": null, + "trRequestHeaders": null, + "trResponseHeaders": null, + "type": "HTTP", + "typeId": 1, + "xmlId": "test" + } + + +Response Structure +------------------ +:active: A boolean that defines :ref:`ds-active`. +:anonymousBlockingEnabled: A boolean that defines :ref:`ds-anonymous-blocking` +:ccrDnsTtl: The :ref:`ds-dns-ttl` - named "ccrDnsTtl" for legacy reasons +:cdnId: The integral, unique identifier of the :ref:`ds-cdn` to which the :term:`Delivery Service` belongs +:cdnName: Name of the :ref:`ds-cdn` to which the :term:`Delivery Service` belongs +:checkPath: A :ref:`ds-check-path` +:consistentHashRegex: A :ref:`ds-consistent-hashing-regex` +:consistentHashQueryParams: An array of :ref:`ds-consistent-hashing-qparams` +:deepCachingType: The :ref:`ds-deep-caching` setting for this :term:`Delivery Service` +:displayName: The :ref:`ds-display-name` +:dnsBypassCname: A :ref:`ds-dns-bypass-cname` +:dnsBypassIp: A :ref:`ds-dns-bypass-ip` +:dnsBypassIp6: A :ref:`ds-dns-bypass-ipv6` +:dnsBypassTtl: The :ref:`ds-dns-bypass-ttl` +:dscp: A :ref:`ds-dscp` to be used within the :term:`Delivery Service` +:ecsEnabled: A boolean that defines the :ref:`ds-ecs` setting on this :term:`Delivery Service` +:edgeHeaderRewrite: A set of :ref:`ds-edge-header-rw-rules` +:exampleURLs: An array of :ref:`ds-example-urls` +:firstHeaderRewrite: A set of :ref:`ds-first-header-rw-rules` +:fqPacingRate: The :ref:`ds-fqpr` +:geoLimit: An integer that defines the :ref:`ds-geo-limit` +:geoLimitCountries: An array of strings defining the :ref:`ds-geo-limit-countries` +:geoLimitRedirectUrl: A :ref:`ds-geo-limit-redirect-url` +:geoProvider: The :ref:`ds-geo-provider` +:globalMaxMbps: The :ref:`ds-global-max-mbps` +:globalMaxTps: The :ref:`ds-global-max-tps` +:httpBypassFqdn: A :ref:`ds-http-bypass-fqdn` +:id: An integral, unique identifier for this :term:`Delivery Service` +:infoUrl: An :ref:`ds-info-url` +:initialDispersion: The :ref:`ds-initial-dispersion` +: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 +: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` + + :pattern: A regular expression - the use of this pattern is dependent on the ``type`` field (backslashes are escaped) + :setNumber: An integer that provides explicit ordering of :ref:`ds-matchlist` items - this is used as a priority ranking by Traffic Router, and is not guaranteed to correspond to the ordering of items in the array. + :type: The type of match performed using ``pattern``. + +:maxDnsAnswers: The :ref:`ds-max-dns-answers` allowed for this :term:`Delivery Service` +:maxOriginConnections: The :ref:`ds-max-origin-connections` +:maxRequestHeaderBytes: The :ref:`ds-max-request-header-bytes` +:midHeaderRewrite: A set of :ref:`ds-mid-header-rw-rules` +:missLat: The :ref:`ds-geo-miss-default-latitude` used by this :term:`Delivery Service` +:missLong: The :ref:`ds-geo-miss-default-longitude` used by this :term:`Delivery Service` +:multiSiteOrigin: A boolean that defines the use of :ref:`ds-multi-site-origin` by this :term:`Delivery Service` +:orgServerFqdn: The :ref:`ds-origin-url` +:originShield: A :ref:`ds-origin-shield` string +:profileDescription: The :ref:`profile-description` of the :ref:`ds-profile` with which this :term:`Delivery Service` is associated +:profileId: The :ref:`profile-id` of the :ref:`ds-profile` with which this :term:`Delivery Service` is associated +:profileName: The :ref:`profile-name` of the :ref:`ds-profile` with which this :term:`Delivery Service` is associated +:protocol: An integral, unique identifier that corresponds to the :ref:`ds-protocol` used by this :term:`Delivery Service` +:qstringIgnore: An integral, unique identifier that corresponds to the :ref:`ds-qstring-handling` setting on this :term:`Delivery Service` +:rangeRequestHandling: An integral, unique identifier that corresponds to the :ref:`ds-range-request-handling` setting on this :term:`Delivery Service` +:regexRemap: A :ref:`ds-regex-remap` +:regionalGeoBlocking: A boolean defining the :ref:`ds-regionalgeo` setting on this :term:`Delivery Service` +:remapText: :ref:`ds-raw-remap` +:serviceCategory: The name of the :ref:`ds-service-category` with which the :term:`Delivery Service` is associated +:signed: ``true`` if and only if ``signingAlgorithm`` is not ``null``, ``false`` otherwise +:signingAlgorithm: Either a :ref:`ds-signing-algorithm` or ``null`` to indicate URL/URI signing is not implemented on this :term:`Delivery Service` +:rangeSliceBlockSize: An integer that defines the byte block size for the ATS Slice Plugin. It can only and must be set if ``rangeRequestHandling`` is set to 3. +:sslKeyVersion: This integer indicates the :ref:`ds-ssl-key-version` +:tenantId: The integral, unique identifier of the :ref:`ds-tenant` who owns this :term:`Delivery Service` +:tlsVersions: An array of explicitly supported :ref:`ds-tls-versions` +:topology: The unique name of the :term:`Topology` that this :term:`Delivery Service` is assigned to +:trRequestHeaders: If defined, this defines the :ref:`ds-tr-req-headers` used by Traffic Router for this :term:`Delivery Service` +:trResponseHeaders: If defined, this defines the :ref:`ds-tr-resp-headers` used by Traffic Router for this :term:`Delivery Service` +:type: The :ref:`ds-types` of this :term:`Delivery Service` +:typeId: The integral, unique identifier of the :ref:`ds-types` of this :term:`Delivery Service` +:xmlId: This :term:`Delivery Service`'s :ref:`ds-xmlid` + +.. 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-Encoding: gzip + Content-Type: application/json + Permissions-Policy: interest-cohort=() + Set-Cookie: mojolicious=...; Path=/; Expires=Tue, 08 Jun 2021 00:34:04 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: tTncbRoJR+pyykVbEc6nWyoFnhlJzsbge9hVZfw+WK28rzSGECZ/Q4zXTQtFjHWY5G+0Rk4w9GKrSFK3k+u5Ng== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 07 Jun 2021 23:34:04 GMT + Content-Length: 840 + + { "alerts": [ + { + "text": "Delivery Service update was successful", + "level": "success" + } + ], + "response": [{ + "active": false, + "anonymousBlockingEnabled": false, + "ccrDnsTtl": null, + "cdnId": 2, + "cdnName": null, + "checkPath": null, + "consistentHashQueryParams": [], + "consistentHashRegex": null, + "deepCachingType": "NEVER", + "displayName": "test", + "dnsBypassCname": null, + "dnsBypassIp": null, + "dnsBypassIp6": null, + "dnsBypassTtl": null, + "dscp": 0, + "ecsEnabled": true, + "edgeHeaderRewrite": null, + "exampleURLs": null, + "firstHeaderRewrite": null, + "fqPacingRate": null, + "geoLimit": 0, + "geoLimitCountries": null, + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": null, + "globalMaxTps": null, + "httpBypassFqdn": null, + "id": 6, + "infoUrl": null, + "initialDispersion": 1, + "innerHeaderRewrite": null, + "ipv6RoutingEnabled": false, + "lastHeaderRewrite": null, + "lastUpdated": "2021-06-07T23:34:04.831215Z", + "logsEnabled": true, + "longDesc": "A Delivery Service created expressly for API documentation examples", + "matchList": [ + { + "type": "HOST_REGEXP", + "setNumber": 0, + "pattern": ".*\\.test\\..*" + } + ], + "maxDnsAnswers": null, + "maxOriginConnections": 0, + "maxRequestHeaderBytes": 131072, + "midHeaderRewrite": null, + "missLat": 0, + "missLong": 0, + "multiSiteOrigin": false, + "originShield": null, + "orgServerFqdn": "http://origin.infra.ciab.test", + "profileDescription": null, + "profileId": null, + "profileName": null, + "protocol": 0, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "rangeSliceBlockSize": null, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "test", + "serviceCategory": null, + "signed": false, + "signingAlgorithm": null, + "sslKeyVersion": null, + "tenant": "root", + "tenantId": 1, + "tlsVersions": null, + "topology": null, + "trResponseHeaders": null, + "trRequestHeaders": null, + "type": "HTTP", + "typeId": 1, + "xmlId": "test" + }]} + + +``DELETE`` +========== +Deletes the target :term:`Delivery Service` + +:Auth. Required: Yes +:Roles Required: "admin" or "operations"\ [#tenancy]_ +:Permissions Required: DELIVERY-SERVICE:DELETE, DELIVERY-SERVICE:READ, CDN:READ, TYPE:READ +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+-------------------------------------------------------------------------------+ + | Name | Description | + +======+===============================================================================+ + | ID | The integral, unique identifier of the :term:`Delivery Service` to be deleted | + +------+-------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/deliveryservices/2 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: w9NlQpJJEl56r6iYq/fk8o5WfAXeUS5XR9yDHvKUgPO8lYEo8YyftaSF0MPFseeOk60dk6kQo+MLYTDIAhhRxw== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 20 Nov 2018 14:56:37 GMT + Content-Length: 57 + + { "alerts": [ + { + "text": "ds was deleted.", + "level": "success" + } + ]} + + +.. [#tenancy] Only those :term:`Delivery Services` assigned to :term:`Tenants` that are the requesting user's :term:`Tenant` or children thereof will appear in the output of a ``GET`` request, and the same constraints are placed on the allowed values of the ``tenantId`` field of a ``PUT`` request to update a new :term:`Delivery Service`. Furthermore, the only :term:`Delivery Services` a user may delete are those assigned to a :term:`Tenant` that is either the same :term:`Tenant` as the user's :term:`Tenant`, or a descendant thereof. +.. [#geoLimit] These fields must be defined if and only if ``geoLimit`` is non-zero diff --git a/docs/source/api/v5/deliveryservices_id_capacity.rst b/docs/source/api/v5/deliveryservices_id_capacity.rst new file mode 100644 index 0000000000..90f86119cf --- /dev/null +++ b/docs/source/api/v5/deliveryservices_id_capacity.rst @@ -0,0 +1,74 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservices-id-capacity: + +************************************ +``deliveryservices/{{ID}}/capacity`` +************************************ + +.. seealso:: :ref:`health-proto` + +``GET`` +======= +Retrieves the usage percentages of a servers associated with a :term:`Delivery Service` + +:Auth. Required: Yes +:Roles Required: None\ [#tenancy]_ +:Permissions Required: DELIVERY-SERVICE:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+------------------------------------------------------------------------------+ + | Name | Description | + +======+==============================================================================+ + | ID | The integral, unique identifier for the :term:`Delivery Service` of interest | + +------+------------------------------------------------------------------------------+ + +Response Structure +------------------ +:availablePercent: The percent of servers assigned to this :term:`Delivery Service` that is available - the allowed traffic level in terms of data per time period for all :term:`cache servers` that remains unused +:unavailablePercent: The percent of servers assigned to this :term:`Delivery Service` that is unavailable - the allowed traffic level in terms of data per time period for all :term:`cache servers` that can't be used because the servers are deemed unhealthy +:utilizedPercent: The percent of servers assigned to this :term:`Delivery Service` that is currently in use - the allowed traffic level in terms of data per time period that is currently devoted to servicing requests +:maintenancePercent: The percent of servers assigned to this :term:`Delivery Service` that is unavailable due to server maintenance - the allowed traffic level in terms of data per time period that is unavailable because servers have intentionally been marked offline by administrators + +.. 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 + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Cache-Control: no-cache, no-store, max-age=0, must-revalidate + Content-Type: application/json + Date: Thu, 15 Nov 2018 14:41:27 GMT + X-Server-Name: traffic_ops_golang/ + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: ++dFR9V1c60CHGNwMjX6JSFEjHreXcL4QnhTO3hiv04ByY379aLpL4OrOzX2bPgJgpR94+f6jZ0+iDIyTMwtFQ== + Content-Length: 134 + + { "response": { + "availablePercent": 99.9993696969697, + "unavailablePercent": 0, + "utilizedPercent": 0.00063030303030303, + "maintenancePercent": 0 + }} + +.. [#tenancy] Users will only be able to see capacity details for the :term:`Delivery Services` their :term:`Tenant` is allowed to see. diff --git a/docs/source/api/v5/deliveryservices_id_health.rst b/docs/source/api/v5/deliveryservices_id_health.rst new file mode 100644 index 0000000000..7ff51196d1 --- /dev/null +++ b/docs/source/api/v5/deliveryservices_id_health.rst @@ -0,0 +1,84 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservices-id-health: + +********************************** +``deliveryservices/{{ID}}/health`` +********************************** + +.. seealso:: :ref:`health-proto` + +``GET`` +======= +Retrieves the health of all :term:`Cache Groups` assigned to a particular :term:`Delivery Service` + +:Auth. Required: Yes +:Roles Required: None\ [#tenancy]_ +:Permissions Required: DELIVERY-SERVICE:READ, CACHE-GROUP:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+------------------------------------------------------------------------------------------------------------+ + | Name | Description | + +======+============================================================================================================+ + | ID | The integral, unique identifier of the Delivery service for which :term:`Cache Groups` will be displayed | + +------+------------------------------------------------------------------------------------------------------------+ + + +Response Structure +------------------ +:cachegroups: An array of objects that represent the health of each :term:`Cache Group` assigned to this :term:`Delivery Service` + + :name: A string that is the :ref:`name of the Cache Group ` represented by this object + :offline: The number of OFFLINE :term:`cache servers` within this :term:`Cache Group` + :online: The number of ONLINE :term:`cache servers` within this :term:`Cache Group` + +:totalOffline: Total number of OFFLINE :term:`cache servers` assigned to this :term:`Delivery Service` +:totalOnline: Total number of ONLINE :term:`cache servers` assigned to this :term:`Delivery Service` + +.. 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 + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Cache-Control: no-cache, no-store, max-age=0, must-revalidate + Content-Type: application/json + Date: Thu, 15 Nov 2018 14:43:43 GMT + X-Server-Name: traffic_ops_golang/ + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: KpXViXeAgch58ueQqdyU8NuINBw1EUedE6Rv2ewcLUajJp6kowdbVynpwW7XiSvAyHdtClIOuT3OkhIimghzSA== + Content-Length: 115 + + { "response": { + "totalOffline": 0, + "totalOnline": 1, + "cachegroups": [ + { + "offline": 0, + "name": "CDN_in_a_Box_Edge", + "online": 1 + } + ] + }} + +.. [#tenancy] Users will only be able to see :term:`Cache Group` health details for the :term:`Delivery Services` their :term:`Tenant` is allowed to see. diff --git a/docs/source/api/v5/deliveryservices_id_regexes.rst b/docs/source/api/v5/deliveryservices_id_regexes.rst new file mode 100644 index 0000000000..9106efb12d --- /dev/null +++ b/docs/source/api/v5/deliveryservices_id_regexes.rst @@ -0,0 +1,180 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservices-id-regexes: + +*********************************** +``deliveryservices/{{ID}}/regexes`` +*********************************** + +``GET`` +======= +Retrieves routing regular expressions for a specific :term:`Delivery Service`. + +:Auth. Required: Yes +:Roles Required: None\ [#tenancy]_ +:Permissions Required: DELIVERY-SERVICE:READ, TYPE:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+---------------------------------------------------------------------------------+ + | Name | Description | + +======+=================================================================================+ + | ID | The integral, unique identifier of the :term:`Delivery Service` being inspected | + +------+---------------------------------------------------------------------------------+ + +.. table:: Request Query Parameters + + +-------------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +=============+==========+======================================================================================================================================+ + | id | no | Show only the :term:`Delivery Service` regular expression that has this integral, unique identifier | + +-------------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +-------------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +-------------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long and the first page is 1. | + | | | If ``offset`` was defined, this query parameter has no effect. ``limit`` must be defined to make use of ``page``. | + +-------------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/deliveryservices/1/regexes HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:id: The integral, unique identifier of this regular expression +:pattern: The actual regular expression - ``\``\ s are escaped +:setNumber: The order in which the regular expression is evaluated against requests +:type: The integral, unique identifier of the :term:`Type` of this regular expression +:typeName: The :term:`Type` of regular expression - determines that against which it will be evaluated + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: fW9Fde4WRpp2ShRAC41P9s/PhU71LI/SEzHgYjGqfzhk45wq0kpaWy76JvPfLpowY8eDTp8Y8TL5rNGEc+bM+A== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 27 Nov 2018 20:56:43 GMT + Content-Length: 100 + + { "response": [ + { + "id": 1, + "type": 31, + "typeName": "HOST_REGEXP", + "setNumber": 0, + "pattern": ".*\\.demo1\\..*" + } + ]} + + +``POST`` +======== +Creates a routing regular expression for a :term:`Delivery Service`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations"\ [#tenancy]_ +:Permissions Required: DELIVERY-SERVICE:UPDATE, DELIVERY-SERVICE:READ, TYPE:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+---------------------------------------------------------------------------------+ + | Name | Description | + +======+=================================================================================+ + | ID | The integral, unique identifier of the :term:`Delivery Service` being inspected | + +------+---------------------------------------------------------------------------------+ + +:pattern: The actual regular expression + + .. warning:: Be sure that ``\``\ s are escaped, or the expression may not work as intended! + +:setNumber: The order in which this regular expression should be checked +:type: The integral, unique identifier of a routing regular expression type + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/deliveryservices/1/regexes HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 55 + Content-Type: application/json + + { + "pattern": ".*\\.foo-bar\\..*", + "type": 31, + "setNumber": 1 + } + +Response Structure +------------------ +:id: The integral, unique identifier of this regular expression +:pattern: The actual regular expression - ``\``\ s are escaped +:setNumber: The order in which the regular expression is evaluated against requests +:type: The integral, unique identifier of the type of this regular expression +:typeName: The type of regular expression - determines that against which it will be evaluated + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: kS5dRzAhFKE7vfzHK7XVIwpMOjztksk9MU+qtj5YU/1oxVHmqNbJ12FeOOIJsZJCXbYlnBS04sCI95Sz5wed1Q== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 28 Nov 2018 17:00:42 GMT + Content-Length: 188 + + { "alerts": [ + { + "text": "Delivery service regex creation was successful.", + "level": "success" + } + ], + "response": { + "id": 2, + "type": 31, + "typeName": "HOST_REGEXP", + "setNumber": 1, + "pattern": ".*\\.foo-bar\\..*" + }} + + +.. [#tenancy] Users will only be able to view and create regular expressions for the :term:`Delivery Services` their :term:`Tenant` is allowed to see. diff --git a/docs/source/api/v5/deliveryservices_id_regexes_rid.rst b/docs/source/api/v5/deliveryservices_id_regexes_rid.rst new file mode 100644 index 0000000000..1b8b909f15 --- /dev/null +++ b/docs/source/api/v5/deliveryservices_id_regexes_rid.rst @@ -0,0 +1,160 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservices-id-regexes-rid: + +******************************************* +``deliveryservices/{{ID}}/regexes/{{rID}}`` +******************************************* + +``PUT`` +======= +Updates a routing regular expression. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations"\ [#tenancy]_ +:Permissions Required: DELIVERY-SERVICE:UPDATE, DELIVERY-SERVICE:READ, TYPE:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+-----------------------------------------------------------------------------------+ + | Name | Description | + +======+===================================================================================+ + | ID | The integral, unique identifier of the :term:`Delivery Service` being inspected | + +------+-----------------------------------------------------------------------------------+ + | rID | The integral, unique identifier of the routing regular expression being inspected | + +------+-----------------------------------------------------------------------------------+ + +:pattern: The actual regular expression + + .. warning:: Be sure that ``\``\ s are escaped, or the expression may not work as intended! + +:setNumber: The order in which this regular expression should be checked +:type: The integral, unique identifier of a routing regular expression type + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/deliveryservices/1/regexes/2 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 55 + Content-Type: application/json + + { + "pattern": ".*\\.foo-bar\\..*", + "type": 33, + "setNumber": 1 + } + +Response Structure +------------------ +:id: The integral, unique identifier of this regular expression +:pattern: The actual regular expression - ``\``\ s are escaped +:setNumber: The order in which the regular expression is evaluated against requests +:type: The integral, unique identifier of the type of this regular expression +:typeName: The type of regular expression - determines that against which it will be evaluated + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: kS5dRzAhFKE7vfzHK7XVIwpMOjztksk9MU+qtj5YU/1oxVHmqNbJ12FeOOIJsZJCXbYlnBS04sCI95Sz5wed1Q== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 29 Nov 2018 17:54:58 GMT + Content-Length: 188 + + { "alerts": [ + { + "text": "Delivery service regex creation was successful.", + "level": "success" + } + ], + "response": { + "id": 2, + "type": 33, + "typeName": "PATH_REGEXP", + "setNumber": 1, + "pattern": ".*\\.foo-bar\\..*" + }} + + + +``DELETE`` +========== +Deletes a routing regular expression. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations"\ [#tenancy]_ +:Permissions Required: DELIVERY-SERVICE:UPDATE, DELIVERY-SERVICE:READ, TYPE:READ +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+-----------------------------------------------------------------------------------+ + | Name | Description | + +======+===================================================================================+ + | ID | The integral, unique identifier of the :term:`Delivery Service` being inspected | + +------+-----------------------------------------------------------------------------------+ + | rID | The integral, unique identifier of the routing regular expression being inspected | + +------+-----------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/deliveryservices/1/regexes/2 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: 8oEa78x7f/o39LIS98W6G+UqE6cX/Iw4v3mMHvbAs1iWHALuDYRz3VOtA6jzfGQKpB04Om8qaVG+zWRrBVoCmQ== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 29 Nov 2018 18:44:00 GMT + Content-Length: 76 + + { "alerts": [ + { + "text": "deliveryservice_regex was deleted.", + "level": "success" + } + ]} + +.. [#tenancy] Users will only be able to view, delete and update regular expressions for the :term:`Delivery Services` their :term:`Tenant` is allowed to see. diff --git a/docs/source/api/v5/deliveryservices_id_routing.rst b/docs/source/api/v5/deliveryservices_id_routing.rst new file mode 100644 index 0000000000..132cf1aae2 --- /dev/null +++ b/docs/source/api/v5/deliveryservices_id_routing.rst @@ -0,0 +1,95 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservices-id-routing: + +*********************************** +``deliveryservices/{{ID}}/routing`` +*********************************** + +``GET`` +======= +Retrieves the aggregated routing percentages for a given :term:`Delivery Service`. This is accomplished by polling stats from all online Traffic Routers via the :ref:`tr-api-crs-stats` route. + +:Auth. Required: Yes +:Roles Required: None\ [#tenancy]_ +:Permissions Required: DELIVERY-SERVICE:READ +:Response Type: Object + +.. note:: Only HTTP or DNS :term:`Delivery Services` can be requested. + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+------------------------------------------------------------------------------+ + | Name | Description | + +======+==============================================================================+ + | ID | The integral, unique identifier for the :term:`Delivery Service` of interest | + +------+------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/deliveryservices/1/routing HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:cz: The percent of requests to online Traffic Routers for this :term:`Delivery Service` that were satisfied by a :term:`Coverage Zone File` +:deepCz: The percent of requests to online Traffic Routers for this :term:`Delivery Service` that were satisfied by a :term:`Deep Coverage Zone File` +:dsr: The percent of requests to online Traffic Routers for this :term:`Delivery Service` that were satisfied by sending the client to an overflow :term:`Delivery Service` +:err: The percent of requests to online Traffic Routers for this :term:`Delivery Service` that resulted in an error +:fed: The percent of requests to online Traffic Routers for this :term:`Delivery Service` that were satisfied by sending the client to a federated CDN +:geo: The percent of requests to online Traffic Routers for this :term:`Delivery Service` that were satisfied using 3rd party geographic IP mapping +:miss: The percent of requests to online Traffic Routers for this :term:`Delivery Service` that could not be satisfied +:regionalAlternate: The percent of requests to online Traffic Routers for this :term:`Delivery Service` that were satisfied by sending the client to the alternate, Regional Geo-blocking URL +:regionalDenied: The percent of requests to online Traffic Routers for this :term:`Delivery Service` that were denied due to geographic location policy +:staticRoute: The percent of requests to online Traffic Routers for this :term:`Delivery Service` that were satisfied with :ref:`ds-static-dns-entries` + +.. 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 + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Cache-Control: no-cache, no-store, max-age=0, must-revalidate + Content-Type: application/json + Date: Fri, 30 Nov 2018 15:08:07 GMT + X-Server-Name: traffic_ops_golang/ + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: UgPziRC/5u4+CfkZ9xm0EkEzjjJVu6cwBrFd/n3xH/ZmlkaXkQaa1y4+B7DyE46vxFLYE0ODOcQchyn7JkoQOg== + Content-Length: 132 + + { "response": { + "cz": 79, + "deepCz": 0.50, + "dsr": 0, + "err": 0, + "fed": 0.25, + "geo": 20, + "miss": 0.25, + "regionalAlternate": 0, + "regionalDenied": 0, + "staticRoute": 0 + }} + +.. [#tenancy] Users will only be able to view routing details for the :term:`Delivery Services` their :term:`Tenant` is allowed to see. diff --git a/docs/source/api/v5/deliveryservices_id_safe.rst b/docs/source/api/v5/deliveryservices_id_safe.rst new file mode 100644 index 0000000000..84b3edc629 --- /dev/null +++ b/docs/source/api/v5/deliveryservices_id_safe.rst @@ -0,0 +1,238 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservices-id-safe: + +******************************** +``deliveryservices/{{ID}}/safe`` +******************************** + +``PUT`` +======= +Allows a user to edit metadata fields of a :term:`Delivery Service`. + +:Auth. Required: Yes +:Roles Required: None\ [#tenancy]_ +:Permissions Required: DELIVERY-SERVICE-SAFE:UPDATE, DELIVERY-SERVICE:READ, TYPE:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+--------------------------------------------------------------------------------+ + | Name | Description | + +======+================================================================================+ + | ID | The integral, unique identifier of the :term:`Delivery Service` being modified | + +------+--------------------------------------------------------------------------------+ + +:displayName: A string that is the :ref:`ds-display-name` +:infoUrl: An optional\ [#optional]_ string containing the :ref:`ds-info-url` +:longDesc: An optional\ [#optional]_ string containing the :ref:`ds-longdesc` of this :term:`Delivery Service` + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/deliveryservices/1/safe HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 132 + Content-Type: application/json + + { + "displayName": "test", + "infoUrl": "this is not even a real URL", + "longDesc": "this is a description of the delivery service" + } + +Response Structure +------------------ +:active: A boolean that defines :ref:`ds-active`. +:anonymousBlockingEnabled: A boolean that defines :ref:`ds-anonymous-blocking` +:ccrDnsTtl: The :ref:`ds-dns-ttl` - named "ccrDnsTtl" for legacy reasons +:cdnId: The integral, unique identifier of the :ref:`ds-cdn` to which the :term:`Delivery Service` belongs +:cdnName: Name of the :ref:`ds-cdn` to which the :term:`Delivery Service` belongs +:checkPath: A :ref:`ds-check-path` +:consistentHashRegex: A :ref:`ds-consistent-hashing-regex` +:consistentHashQueryParams: An array of :ref:`ds-consistent-hashing-qparams` +:deepCachingType: The :ref:`ds-deep-caching` setting for this :term:`Delivery Service` +:displayName: The :ref:`ds-display-name` +:dnsBypassCname: A :ref:`ds-dns-bypass-cname` +:dnsBypassIp: A :ref:`ds-dns-bypass-ip` +:dnsBypassIp6: A :ref:`ds-dns-bypass-ipv6` +:dnsBypassTtl: The :ref:`ds-dns-bypass-ttl` +:dscp: A :ref:`ds-dscp` to be used within the :term:`Delivery Service` +:ecsEnabled: A boolean that defines the :ref:`ds-ecs` setting on this :term:`Delivery Service` +:edgeHeaderRewrite: A set of :ref:`ds-edge-header-rw-rules` +:exampleURLs: An array of :ref:`ds-example-urls` +:firstHeaderRewrite: A set of :ref:`ds-first-header-rw-rules` +:fqPacingRate: The :ref:`ds-fqpr` +:geoLimit: An integer that defines the :ref:`ds-geo-limit` +:geoLimitCountries: An array of strings defining the :ref:`ds-geo-limit-countries` +:geoLimitRedirectUrl: A :ref:`ds-geo-limit-redirect-url` +:geoProvider: The :ref:`ds-geo-provider` +:globalMaxMbps: The :ref:`ds-global-max-mbps` +:globalMaxTps: The :ref:`ds-global-max-tps` +:httpBypassFqdn: A :ref:`ds-http-bypass-fqdn` +:id: An integral, unique identifier for this :term:`Delivery Service` +:infoUrl: An :ref:`ds-info-url` +:initialDispersion: The :ref:`ds-initial-dispersion` +: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 +: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` + + :pattern: A regular expression - the use of this pattern is dependent on the ``type`` field (backslashes are escaped) + :setNumber: An integer that provides explicit ordering of :ref:`ds-matchlist` items - this is used as a priority ranking by Traffic Router, and is not guaranteed to correspond to the ordering of items in the array. + :type: The type of match performed using ``pattern``. + +:maxDnsAnswers: The :ref:`ds-max-dns-answers` allowed for this :term:`Delivery Service` +:maxOriginConnections: The :ref:`ds-max-origin-connections` +:midHeaderRewrite: A set of :ref:`ds-mid-header-rw-rules` +:missLat: The :ref:`ds-geo-miss-default-latitude` used by this :term:`Delivery Service` +:missLong: The :ref:`ds-geo-miss-default-longitude` used by this :term:`Delivery Service` +:multiSiteOrigin: A boolean that defines the use of :ref:`ds-multi-site-origin` by this :term:`Delivery Service` +:orgServerFqdn: The :ref:`ds-origin-url` +:originShield: A :ref:`ds-origin-shield` string +:profileDescription: The :ref:`profile-description` of the :ref:`ds-profile` with which this :term:`Delivery Service` is associated +:profileId: The :ref:`profile-id` of the :ref:`ds-profile` with which this :term:`Delivery Service` is associated +:profileName: The :ref:`profile-name` of the :ref:`ds-profile` with which this :term:`Delivery Service` is associated +:protocol: An integral, unique identifier that corresponds to the :ref:`ds-protocol` used by this :term:`Delivery Service` +:qstringIgnore: An integral, unique identifier that corresponds to the :ref:`ds-qstring-handling` setting on this :term:`Delivery Service` +:rangeRequestHandling: An integral, unique identifier that corresponds to the :ref:`ds-range-request-handling` setting on this :term:`Delivery Service` +:regexRemap: A :ref:`ds-regex-remap` +:regionalGeoBlocking: A boolean defining the :ref:`ds-regionalgeo` setting on this :term:`Delivery Service` +:remapText: :ref:`ds-raw-remap` +:signed: ``true`` if and only if ``signingAlgorithm`` is not ``null``, ``false`` otherwise +:signingAlgorithm: Either a :ref:`ds-signing-algorithm` or ``null`` to indicate URL/URI signing is not implemented on this :term:`Delivery Service` +:rangeSliceBlockSize: An integer that defines the byte block size for the ATS Slice Plugin. It can only and must be set if ``rangeRequestHandling`` is set to 3. +:sslKeyVersion: This integer indicates the :ref:`ds-ssl-key-version` +:tenantId: The integral, unique identifier of the :ref:`ds-tenant` who owns this :term:`Delivery Service` +:tlsVersions: A list of explicitly supported :ref:`ds-tls-versions` +:topology: The unique name of the :term:`Topology` that this :term:`Delivery Service` is assigned to +:trRequestHeaders: If defined, this defines the :ref:`ds-tr-req-headers` used by Traffic Router for this :term:`Delivery Service` +:trResponseHeaders: If defined, this defines the :ref:`ds-tr-resp-headers` used by Traffic Router for this :term:`Delivery Service` +:type: The :ref:`ds-types` of this :term:`Delivery Service` +:typeId: The integral, unique identifier of the :ref:`ds-types` of this :term:`Delivery Service` +:xmlId: This :term:`Delivery Service`'s :ref:`ds-xmlid` + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Content-Encoding: gzip + Content-Type: application/json + Permissions-Policy: interest-cohort=() + Set-Cookie: mojolicious=...; Path=/; Expires=Tue, 08 Jun 2021 00:53:26 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: Ys/SfWWijsXCNXEqZ84oldfyXTgMe8UE/wWb53VU39OH7kWOXF1BH5Hg7Y40nCgXoWEqcaBq5+WCZg0bYuJdAA== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 07 Jun 2021 23:53:26 GMT + Content-Length: 903 + + { "alerts": [{ + "text": "Delivery Service safe update successful.", + "level": "success" + }], + "response": [{ + "active": true, + "anonymousBlockingEnabled": false, + "ccrDnsTtl": null, + "cdnId": 2, + "cdnName": "CDN-in-a-Box", + "checkPath": null, + "consistentHashQueryParams": [], + "consistentHashRegex": null, + "deepCachingType": "NEVER", + "displayName": "test", + "dnsBypassCname": null, + "dnsBypassIp": null, + "dnsBypassIp6": null, + "dnsBypassTtl": null, + "dscp": 0, + "ecsEnabled": false, + "edgeHeaderRewrite": null, + "exampleURLs": [ + "http://video.demo2.mycdn.ciab.test", + "https://video.demo2.mycdn.ciab.test" + ], + "firstHeaderRewrite": null, + "fqPacingRate": null, + "geoLimit": 0, + "geoLimitCountries": null, + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": null, + "globalMaxTps": null, + "httpBypassFqdn": null, + "id": 1, + "infoUrl": "this is not even a real URL", + "initialDispersion": 1, + "innerHeaderRewrite": null, + "ipv6RoutingEnabled": true, + "lastHeaderRewrite": null, + "lastUpdated": "2021-06-07T23:53:26.139899Z", + "logsEnabled": true, + "longDesc": "this is a description of the delivery service", + "matchList": [ + { + "type": "HOST_REGEXP", + "setNumber": 0, + "pattern": ".*\\.demo2\\..*" + } + ], + "maxDnsAnswers": null, + "maxOriginConnections": 0, + "maxRequestHeaderBytes": 0, + "midHeaderRewrite": null, + "missLat": 42, + "missLong": -88, + "multiSiteOrigin": true, + "originShield": null, + "orgServerFqdn": "http://origin.infra.ciab.test", + "profileDescription": null, + "profileId": null, + "profileName": null, + "protocol": 2, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "rangeSliceBlockSize": null, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "video", + "serviceCategory": null, + "signed": false, + "signingAlgorithm": null, + "sslKeyVersion": null, + "tenant": "root", + "tenantId": 1, + "tlsVersions": null, + "topology": "demo1-top", + "trResponseHeaders": null, + "trRequestHeaders": null, + "type": "DNS", + "typeId": 5, + "xmlId": "demo2" + ]} + +.. [#tenancy] Only those :term:`Delivery Services` assigned to :term:`Tenants` that are the requesting user's :term:`Tenant` or children thereof may be modified with this endpoint. +.. [#optional] If these fields are not present in the request body they are *implicitly set to* ``null``. diff --git a/docs/source/api/v5/deliveryservices_id_servers.rst b/docs/source/api/v5/deliveryservices_id_servers.rst new file mode 100644 index 0000000000..02e578982a --- /dev/null +++ b/docs/source/api/v5/deliveryservices_id_servers.rst @@ -0,0 +1,167 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservices-id-servers: + +*********************************** +``deliveryservices/{{ID}}/servers`` +*********************************** + +``GET`` +======= +Retrieves properties of Edge-Tier servers assigned to a :term:`Delivery Service`. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: CACHE-GROUP:READ, CDN:READ, TYPE:READ, PROFILE:READ, DELIVERY-SERVICE:READ, SERVER:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+---------------------------------------------------------------------------------------------+ + | Name | Description | + +======+=============================================================================================+ + | ID | The integral, unique identifier of the Delivery service for which servers will be displayed | + +------+---------------------------------------------------------------------------------------------+ + +Response Structure +------------------ +:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the server belongs +:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the server belongs +:cdnId: An integral, unique identifier the CDN to which the server belongs +:cdnName: The name of the CDN to which the server belongs +:domainName: The domain name part of the :abbr:`FQDN (Fully Qualified Domain Name)` of the server +:guid: Optionally represents an identifier used to uniquely identify the server +:hostName: The (short) hostname of the server +:httpsPort: The port on which the server listens for incoming HTTPS requests - 443 in most cases +:id: An integral, unique identifier for the server +:iloIpAddress: The IPv4 address of the lights-out-management port\ [#ilowikipedia]_ +:iloIpGateway: The IPv4 gateway address of the lights-out-management port\ [#ilowikipedia]_ +:iloIpNetmask: The IPv4 subnet mask of the lights-out-management port\ [#ilowikipedia]_ +:iloPassword: The password of the of the lights-out-management user - displays as ``******`` unless the requesting user has the 'admin' role)\ [#ilowikipedia]_ +:iloUsername: The user name for lights-out-management\ [#ilowikipedia]_ +:interfaces: An array of interface and IP address information + + :max_bandwidth: The maximum allowed bandwidth for this interface to be considered "healthy" by Traffic Monitor. This has no effect if `monitor` is not true. Values are in kb/s. The value `null` means "no limit". + :monitor: A boolean indicating if Traffic Monitor should monitor this interface + :mtu: The :abbr:`MTU (Maximum Transmission Unit)` to configure for ``interfaceName`` + + .. seealso:: `The Wikipedia article on Maximum Transmission Unit `_ + + :name: The network interface name used by the server. + + :ipAddresses: An array of the IP address information for the interface + + :address: The IPv4 or IPv6 address and subnet mask of the server - applicable for the interface ``name`` + :gateway: The IPv4 or IPv6 gateway address of the server - applicable for the interface ``name`` + :service_address: A boolean determining if content will be routed to the IP address + +:lastUpdated: The time and date at which this server was last updated, in :ref:`non-rfc-datetime` +:mgmtIpAddress: The IPv4 address of the server's management port +:mgmtIpGateway: The IPv4 gateway of the server's management port +:mgmtIpNetmask: The IPv4 subnet mask of the server's management port +:offlineReason: A user-entered reason why the server is in ADMIN_DOWN or OFFLINE status (will be empty if not offline) +:physLocation: The name of the :term:`Physical Location` at which the server resides +:physLocationId: An integral, unique identifier for the :term:`Physical Location` at which the server resides +:profile: List of :ref:`profile-name` of the :term:`Profiles` assigned to this server +:rack: A string indicating "rack" location +:routerHostName: The human-readable name of the router +:routerPortName: The human-readable name of the router port +:status: The Status of the server + + .. seealso:: :ref:`health-proto` + +:statusId: An integral, unique identifier for the status of the server + + .. seealso:: :ref:`health-proto` + +:tcpPort: The default port on which the main application listens for incoming TCP connections - 80 in most cases +:type: The name of the type of this server +:typeId: An integral, unique identifier for the type of this server +:updPending: ``true`` if the server has updates pending, ``false`` otherwise + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: MaIvaO8OSjysr4bCkuXFEMf3o6mOqga1aM4IHN/tcP2aa1iXEmA5IrHB7DaqNX/2vGHLXvN+01FEAR/lRNqr1w== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 14 Nov 2018 21:28:23 GMT + Content-Length: 891 + + { "response": [ + { + "cachegroup": "CDN_in_a_Box_Edge", + "cachegroupId": 7, + "cdnId": 2, + "cdnName": "CDN-in-a-Box", + "domainName": "infra.ciab.test", + "guid": null, + "hostName": "edge", + "httpsPort": 443, + "id": 10, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "lastUpdated": "2018-11-14 21:08:44+00", + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": "", + "physLocation": "Apachecon North America 2018", + "physLocationId": 1, + "profileNames": ["ATS_EDGE_TIER_CACHE"], + "rack": "", + "routerHostName": "", + "routerPortName": "", + "status": "REPORTED", + "statusId": 3, + "tcpPort": 80, + "type": "EDGE", + "typeId": 11, + "updPending": false, + "interfaces": [{ + "ipAddresses": [ + { + "address": "172.16.239.100", + "gateway": "172.16.239.1", + "service_address": true + }, + { + "address": "fc01:9400:1000:8::100", + "gateway": "fc01:9400:1000:8::1", + "service_address": true + } + ], + "max_bandwidth": 0, + "monitor": true, + "mtu": 1500, + "name": "eth0" + }] + } + ]} + + +.. [#ilowikipedia] See `the Wikipedia article on Out-of-Band Management `_ for more information. diff --git a/docs/source/api/v5/deliveryservices_id_servers_eligible.rst b/docs/source/api/v5/deliveryservices_id_servers_eligible.rst new file mode 100644 index 0000000000..f51c34f946 --- /dev/null +++ b/docs/source/api/v5/deliveryservices_id_servers_eligible.rst @@ -0,0 +1,164 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservices-id-servers-eligible: + +******************************************** +``deliveryservices/{{ID}}/servers/eligible`` +******************************************** + +.. caution:: This endpoint may not work as advertised, and its use is therefore discouraged! + +``GET`` +======= +Retrieves properties of :term:`Edge-tier cache servers` eligible for assignment to a particular :term:`Delivery Service`. Eligibility is determined based on the following properties: + +- The name of the server's :term:`Type` must match one of the glob patterns ``EDGE*``, ``ORG*`` +- The server and :term:`Delivery Service` must belong to the same CDN +- If the :term:`Delivery Service` has :ref:`ds-required-capabilities`, an :term:`Edge-tier cache server` must have all of those defined capabilities + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: DELIVERY-SERVICE:READ, SERVER:READ, CACHE-GROUP:READ, TYPE:READ, CDN:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+---------------------------------------------------------------------------------------------+ + | Name | Description | + +======+=============================================================================================+ + | ID | The integral, unique identifier of the Delivery service for which servers will be displayed | + +------+---------------------------------------------------------------------------------------------+ + +Response Structure +------------------ +:cachegroup: A string which is the :ref:`Name of the Cache Group ` to which the server belongs +:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the server belongs +:cdnId: An integral, unique identifier the CDN to which the server belongs +:cdnName: The name of the CDN to which the server belongs +:domainName: The domain name part of the :abbr:`FQDN (Fully Qualified Domain Name)` of the server +:guid: Optionally represents an identifier used to uniquely identify the server +:hostName: The (short) hostname of the server +:httpsPort: The port on which the server listens for incoming HTTPS requests - 443 in most cases +:id: An integral, unique identifier for the server +:iloIpAddress: The IPv4 address of the lights-out-management port\ [#ilowikipedia]_ +:iloIpGateway: The IPv4 gateway address of the lights-out-management port\ [#ilowikipedia]_ +:iloIpNetmask: The IPv4 subnet mask of the lights-out-management port\ [#ilowikipedia]_ +:iloPassword: The password of the of the lights-out-management user - displays as ``******`` unless the requesting user has the 'admin' role)\ [#ilowikipedia]_ +:iloUsername: The user name for lights-out-management\ [#ilowikipedia]_ +:interfaces: An array of interface and IP address information + + :max_bandwidth: The maximum allowed bandwidth for this interface to be considered "healthy" by Traffic Monitor. This has no effect if `monitor` is not true. Values are in kb/s. The value `null` means "no limit". + :monitor: A boolean indicating if Traffic Monitor should monitor this interface + :mtu: The :abbr:`MTU (Maximum Transmission Unit)` to configure for ``interfaceName`` + + .. seealso:: `The Wikipedia article on Maximum Transmission Unit `_ + + :name: The network interface name used by the server. + + :ipAddresses: An array of the IP address information for the interface + + :address: The IPv4 or IPv6 address and subnet mask of the server - applicable for the interface ``name`` + :gateway: The IPv4 or IPv6 gateway address of the server - applicable for the interface ``name`` + :service_address: A boolean determining if content will be routed to the IP address + +:lastUpdated: The time and date at which this server was last updated, in :ref:`non-rfc-datetime` +:mgmtIpAddress: The IPv4 address of the server's management port +:mgmtIpGateway: The IPv4 gateway of the server's management port +:mgmtIpNetmask: The IPv4 subnet mask of the server's management port +:offlineReason: A user-entered reason why the server is in ADMIN_DOWN or OFFLINE status (will be empty if not offline) +:physLocation: The name of the :term:`Physical Location` at which the server resides +:physLocationId: An integral, unique identifier for the :term:`Physical Location` at which the server resides +:profile: The :ref:`profile-name` of the :term:`Profile` assigned to this server +:profileDesc: A :ref:`profile-description` of the :term:`Profile` assigned to this server +:profileId: The :ref:`profile-id` of the :term:`Profile` assigned to this server +:rack: A string indicating "rack" location +:routerHostName: The human-readable name of the router +:routerPortName: The human-readable name of the router port +:status: The Status of the server + + .. seealso:: :ref:`health-proto` + +:statusId: An integral, unique identifier for the status of the server + + .. seealso:: :ref:`health-proto` + +:tcpPort: The default port on which the main application listens for incoming TCP connections - 80 in most cases +:type: The name of the :term:`Type` of this server +:typeId: An integral, unique identifier for the :term:`Type` of this server +:updPending: ``true`` if the server has updates pending, ``false`` otherwise + +.. code-block:: json + :caption: Response Example + + { "response": [ + { + "cachegroup": "CDN_in_a_Box_Edge", + "cachegroupId": 7, + "cdnId": 2, + "cdnName": "CDN-in-a-Box", + "domainName": "infra.ciab.test", + "guid": null, + "hostName": "edge", + "httpsPort": 443, + "id": 10, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "lastUpdated": "2018-10-30 16:01:12+00", + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": "", + "physLocation": "Apachecon North America 2018", + "physLocationId": 1, + "profile": "ATS_EDGE_TIER_CACHE", + "profileDesc": "Edge Cache - Apache Traffic Server", + "profileId": 9, + "rack": "", + "routerHostName": "", + "routerPortName": "", + "status": "REPORTED", + "statusId": 3, + "tcpPort": 80, + "type": "EDGE", + "typeId": 11, + "updPending": false, + "interfaces": [{ + "ipAddresses": [ + { + "address": "172.16.239.100", + "gateway": "172.16.239.1", + "service_address": true + }, + { + "address": "fc01:9400:1000:8::100", + "gateway": "fc01:9400:1000:8::1", + "service_address": true + } + ], + "max_bandwidth": 0, + "monitor": true, + "mtu": 1500, + "name": "eth0" + }] + } + ]} + +.. [#ilowikipedia] See `the Wikipedia article on Out-of-Band Management `_ for more information. diff --git a/docs/source/api/v5/deliveryservices_id_urlkeys.rst b/docs/source/api/v5/deliveryservices_id_urlkeys.rst new file mode 100644 index 0000000000..6a227c4812 --- /dev/null +++ b/docs/source/api/v5/deliveryservices_id_urlkeys.rst @@ -0,0 +1,128 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservices-id-urlkeys: + +*********************************** +``deliveryservices/{{ID}}/urlkeys`` +*********************************** + +``GET`` +======= +.. seealso:: :ref:`to-api-deliveryservices-xmlid-xmlid-urlkeys` + +Retrieves URL signing keys for a :term:`Delivery Service`. + +.. caution:: This method will return the :term:`Delivery Service`'s **PRIVATE** URL signing keys! Be wary of using this endpoint and **NEVER** share the output with anyone who would be unable to see it on their own. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: DS-SECURITY-KEY:READ, DELIVERY-SERVICE:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+----------------------------------------------------------------------------------------+ + | Name | Description | + +======+========================================================================================+ + | id | Filter for the :term:`Delivery Service` identified by this integral, unique identifier | + +------+----------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/deliveryservices/1/urlkeys HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + +Response Structure +------------------ +:key: The private URL signing key for this :term:`Delivery Service` as a base-64-encoded string, where ```` is the "generation" of the key e.g. the first key will always be named ``"key0"``. Up to 16 concurrent generations are retained at any time (```` is always on the interval [0,15]) + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Sun, 23 Feb 2020 16:34:56 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: cTc5OPE3hM+CiyQPCy36zD2tsQcfkvIqQ7/D82WGMWHm+ACW3YbcKhgPnSQU6+Tuj4jya52Kx9+nw5+OonFvPQ== + X-Server-Name: traffic_ops_golang/ + Date: Sun, 23 Feb 2020 15:34:56 GMT + Content-Length: 533 + + { + "response": { + "key0": "...", + "key1": "...", + "key2": "...", + "key3": "...", + "key4": "...", + "key5": "...", + "key6": "...", + "key7": "...", + "key8": "...", + "key9": "...", + "key10": "...", + "key11": "...", + "key12": "...", + "key13": "...", + "key14": "...", + "key15": "..." + } + } + + +``DELETE`` +========== +.. seealso:: :ref:`to-api-deliveryservices-xmlid-xmlid-urlkeys` + +Deletes URL signing keys for a :term:`Delivery Service`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: DS-SECURITY-KEY:DELETE, DELIVERY-SERVICE:READ, DELIVERY-SERVICE:UPDATE +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+----------------------------------------------------------------------------------------+ + | Name | Description | + +======+========================================================================================+ + | id | Filter for the :term:`Delivery Service` identified by this integral, unique identifier | + +------+----------------------------------------------------------------------------------------+ + +Response Structure +------------------ +.. code-block:: json + :caption: Response Example + + { + "alerts": [{ + "level": "success", + "text": "Successfully deleted URL Sig keys from Traffic Vault" + }] + } diff --git a/docs/source/api/v5/deliveryservices_regexes.rst b/docs/source/api/v5/deliveryservices_regexes.rst new file mode 100644 index 0000000000..2bff8db293 --- /dev/null +++ b/docs/source/api/v5/deliveryservices_regexes.rst @@ -0,0 +1,73 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + + +.. _to-api-deliveryservices_regexes: + +**************************** +``deliveryservices_regexes`` +**************************** + +``GET`` +======= +Retrieves routing regular expressions for all :term:`Delivery Services`. + +:Auth. Required: Yes +:Roles Required: None\ [1]_ +:Permissions Required: DELIVERY-SERVICE:READ +:Response Type: Array + +Request Structure +----------------- +No parameters available + +Response Structure +------------------ +:dsName: The name of the :term:`Delivery Service` represented by this object +:regexes: An array of objects that represent various routing regular expressions used by ``dsName`` + + :pattern: The actual regular expression - ``\``\ s are escaped + :setNumber: The order in which the regular expression is evaluated against requests + :type: The type of regular expression - determines that against which it will be evaluated + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: +2MI+Q/NJqTizlMR/MhPAL+yu6/z/Yqvo5fDO8F593RMOmK6dX/Al4wARbEG+HQaJNgSCRPsiLVATusrmnnCMA== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 27 Nov 2018 19:22:59 GMT + Content-Length: 110 + + { "response": [ + { + "regexes": [ + { + "type": "HOST_REGEXP", + "setNumber": 0, + "pattern": ".*\\.demo1\\..*" + } + ], + "dsName": "demo1" + } + ]} + +.. [1] If tenancy is used, then users (regardless of role) will only be able to see the routing regular expressions used by :term:`Delivery Services` their tenant has permissions to see. diff --git a/docs/source/api/v5/deliveryservices_required_capabilities.rst b/docs/source/api/v5/deliveryservices_required_capabilities.rst new file mode 100644 index 0000000000..6761728b5f --- /dev/null +++ b/docs/source/api/v5/deliveryservices_required_capabilities.rst @@ -0,0 +1,227 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservices-required-capabilities: + +****************************************** +``deliveryservices_required_capabilities`` +****************************************** + +``GET`` +======= +Gets all associations of :term:`Server Capability` to :term:`Delivery Services`. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: DELIVERY-SERVICE:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +--------------------+----------+---------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +====================+==========+===============================================================================================================+ + | deliveryServiceID | no | Filter :term:`Server Capability` associations by :term:`Delivery Service` integral, unique identifier | + +--------------------+----------+---------------------------------------------------------------------------------------------------------------+ + | xmlID | no | Filter :term:`Server Capability` associations by :term:`Delivery Service` :ref:`ds-xmlid` | + +--------------------+----------+---------------------------------------------------------------------------------------------------------------+ + | requiredCapability | no | Filter :term:`Server Capability` associations by :term:`Server Capability` name | + +--------------------+----------+---------------------------------------------------------------------------------------------------------------+ + | orderby | no | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` | + | | | array | + +--------------------+----------+---------------------------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +--------------------+----------+---------------------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +--------------------+----------+---------------------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit. | + +--------------------+----------+---------------------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long | + | | | and the first page is 1. If ``offset`` was defined, this query parameter has no effect. ``limit`` must be | + | | | defined to make use of ``page``. | + +--------------------+----------+---------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/deliveryservices_required_capabilities HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:deliveryServiceID: The associated :term:`Delivery Service`'s integral, unique identifier +:xmlID: The associated :term:`Delivery Service`'s :ref:`ds-xmlid` +:lastUpdated: The date and time at which this association between the :term:`Delivery Service` and the :term:`Server Capability` was last updated, in :ref:`non-rfc-datetime` +:requiredCapability: The :term:`Server Capability`'s name + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: UFO3/jcBFmFZM7CsrsIwTfPc5v8gUiXqJm6BNp1boPb4EQBnWNXZh/DbBwhMAOJoeqDImoDlrLnrVjQGO4AooA== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 07 Oct 2019 22:15:11 GMT + Content-Length: 396 + + { + "response": [ + { + "deliveryServiceID": 1, + "lastUpdated": "2019-10-07 22:05:31+00", + "requiredCapability": "ram", + "xmlId": "example_ds-1" + }, + { + "deliveryServiceID": 2, + "lastUpdated": "2019-10-07 22:05:31+00", + "requiredCapability": "disk", + "xmlId": "example_ds-2" + } + ] + } + +``POST`` +======== +Associates a :term:`Server Capability` with a :term:`Delivery Service`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: DELIVERY-SERVICE:READ, DELIVERY-SERVICE:UPDATE +:Response Type: Object + +.. note:: A :term:`Server Capability` can only be made required on a :term:`Delivery Service` if its associated Servers already have that :term:`Server Capability` assigned. + +Request Structure +----------------- +:deliveryServiceID: The integral, unique identifier of the :term:`Delivery Service` to be associated +:requiredCapability: The name of the :term:`Server Capability` to be associated + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/deliveryservices_required_capabilities HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 56 + Content-Type: application/json + + { + "deliveryServiceID": 1, + "requiredCapability": "disk" + } + +Response Structure +------------------ +:deliveryServiceID: The newly associated :term:`Delivery Service`'s integral, unique identifier +:lastUpdated: The date and time at which this association between the :term:`Delivery Service` and the :term:`Server Capability` was last updated, in :ref:`non-rfc-datetime` +:requiredCapability: The newly associated :term:`Server Capability`'s name + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: eQrl48zWids0kDpfCYmmtYMpegjnFxfOVvlBYxxLSfp7P7p6oWX4uiC+/Cfh2X9i3G+MQ36eH95gukJqOBOGbQ== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 07 Oct 2019 22:15:11 GMT + Content-Length: 287 + + { + "alerts": [ + { + "level": "success", + "text": "deliveryservice.RequiredCapability was created." + } + ], + "response": { + "deliveryServiceID": 1, + "lastUpdated": "2019-10-07 22:15:11+00", + "requiredCapability": "disk" + } + } + +``DELETE`` +========== +Dissociate a :term:`Server Capability` from a :term:`Delivery Service`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: DELIVERY-SERVICE:READ, DELIVERY-SERVICE:UPDATE +:Response Type: ``undefined`` + +Request Structure +----------------- +:deliveryServiceID: The integral, unique identifier of the :term:`Delivery Service` from which a :term:`Server Capability` will be dissociated +:requiredCapability: The name of the :term:`Server Capability` to dissociate + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/deliveryservices_required_capabilities HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 56 + Content-Type: application/json + + { + "deliveryServiceID": 1, + "requiredCapability": "disk" + } + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: eQrl48zWids0kDpfCYmmtYMpegjnFxfOVvlBYxxLSfp7P7p6oWX4uiC+/Cfh2X9i3G+MQ36eH95gukJqOBOGbQ== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 07 Oct 2019 22:15:11 GMT + Content-Length: 127 + + { + "alerts": [ + { + "level": "success", + "text": "deliveryservice.RequiredCapability was deleted." + } + ] + } diff --git a/docs/source/api/v5/deliveryservices_sslkeys_add.rst b/docs/source/api/v5/deliveryservices_sslkeys_add.rst new file mode 100644 index 0000000000..3f1840fc9a --- /dev/null +++ b/docs/source/api/v5/deliveryservices_sslkeys_add.rst @@ -0,0 +1,72 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservices-sslkeys-add: + +******************************** +``deliveryservices/sslkeys/add`` +******************************** + +.. seealso:: In most cases it is preferable to allow Traffic Ops to generate the keys via :ref:`to-api-deliveryservices-sslkeys-generate`, rather than uploading them manually using this endpoint. + +``POST`` +======== +Allows user to upload an SSL certificate, csr, and private key for a :term:`Delivery Service`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: DS-SECURITY-KEY:CREATE, DELIVERY-SERVICE:READ +:Response Type: Object (string) + +Request Structure +----------------- +:cdn: The name of the CDN to which the :term:`Delivery Service` belongs +:certificate: An object that contains the actual components of the SSL key + + :crt: The certificate for the :term:`Delivery Service` identified by ``key`` + :csr: The csr file for the :term:`Delivery Service` identified by ``key`` + :key: The private key for the :term:`Delivery Service` identified by ``key`` + +:key: The :ref:`ds-xmlid` of the :term:`Delivery Service` to which these keys will be assigned +:version: An integer that defines the "version" of the key - which may be thought of as the sequential generation; that is, the higher the number the more recent the key + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/deliveryservices/sslkeys/add HTTP/1.1 + Host: trafficops.infra.ciab.test + Content-Type: application/json + + { + "key": "ds-01", + "version": "1", + "certificate": { + "key": "some_key", + "csr": "some_csr", + "crt": "some_crt" + } + } + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Content-Type: application/json + + { + "response": "Successfully added ssl keys for ds-01" + } diff --git a/docs/source/api/v5/deliveryservices_sslkeys_generate.rst b/docs/source/api/v5/deliveryservices_sslkeys_generate.rst new file mode 100644 index 0000000000..a383fd79a9 --- /dev/null +++ b/docs/source/api/v5/deliveryservices_sslkeys_generate.rst @@ -0,0 +1,70 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservices-sslkeys-generate: + +************************************* +``deliveryservices/sslkeys/generate`` +************************************* + +``POST`` +======== +Generates an SSL certificate, csr, and private key for a :term:`Delivery Service` + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: DS-SECURITY-KEY:CREATE, DELIVERY-SERVICE:READ, DELIVERY-SERVICE:UPDATE +:Response Type: Object (string) + +Request Structure +----------------- +:city: An optional field which, if present, will represent the resident city of the generated SSL certificate +:country: An optional field which, if present, will represent the resident country of the generated SSL certificate +:hostname: The desired hostname of the :term:`Delivery Service` + + .. note:: In most cases, this must be the same as the :term:`Delivery Service` URL' + +:key: The :ref:`ds-xmlid` of the :term:`Delivery Service` for which keys will be generated +:organization: An optional field which, if present, will represent the organization for which the SSL certificate was generated +:state: An optional field which, if present, will represent the resident state or province of the generated SSL certificate +:businessUnit: An optional field which, if present, will represent the business unit for which the SSL certificate was generated +:version: An integer that defines the "version" of the key - which may be thought of as the sequential generation; that is, the higher the number the more recent the key + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/deliveryservices/sslkeys/generate HTTP/1.1 + Content-Type: application/json + + { + "key": "ds-01", + "businessUnit": "CDN Engineering", + "version": "3", + "hostname": "tr.ds-01.ott.kabletown.com", + "country": "US", + "organization": "Kabletown", + "city": "Denver", + "state": "Colorado" + } + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Content-Type: application/json + + { "response": "Successfully created ssl keys for ds-01" } diff --git a/docs/source/api/v5/deliveryservices_sslkeys_generate_acme.rst b/docs/source/api/v5/deliveryservices_sslkeys_generate_acme.rst new file mode 100644 index 0000000000..35827e22c9 --- /dev/null +++ b/docs/source/api/v5/deliveryservices_sslkeys_generate_acme.rst @@ -0,0 +1,69 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservices-sslkeys-generate-acme: + +****************************************** +``deliveryservices/sslkeys/generate/acme`` +****************************************** + +``POST`` +======== +Generates an SSL certificate and private key using :abbr:`ACME (Automatic Certificate Management Environment)` protocol for a :term:`Delivery Service` + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: DS-SECURITY-KEY:UPDATE, ACME:READ, DELIVERY-SERVICE:READ, DELIVERY-SERVICE:UPDATE +:Response Type: Object (string) + +Request Structure +----------------- +:authType: The certificate provider correlating to an :abbr:`ACME (Automatic Certificate Management Environment)` account in :ref:`cdn.conf` or Let's Encrypt. +:key: The :ref:`ds-xmlid` of the :term:`Delivery Service` for which keys will be generated [#needOne]_ +:deliveryservice: The :ref:`ds-xmlid` of the :term:`Delivery Service` for which keys will be generated [#needOne]_ +:version: An integer that defines the "version" of the key - which may be thought of as the sequential generation; that is, the higher the number the more recent the key +:hostname: The desired hostname of the :term:`Delivery Service` + + .. note:: In most cases, this must be the same as the :ref:`ds-example-urls`. + +:cdn: The name of the CDN of the :term:`Delivery Service` for which the certs will be generated + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/deliveryservices/sslkeys/generate/acme HTTP/1.1 + Content-Type: application/json + + { + "authType": "Lets Encrypt", + "key": "ds-01", + "deliveryservice": "ds-01", + "version": "3", + "hostname": "tr.ds-01.ott.kabletown.com", + "cdn":"test-cdn" + } + + +Response Structure +------------------ +.. code-block:: json + :caption: Response Example + + { "alerts": [{ + "level": "success", + "text": "Beginning async ACME call for demo1 using Lets Encrypt. This may take a few minutes. Status updates can be found here: /api/5.0/async_status/1" + }]} + +.. [#needOne] Either the ``key`` or the ``deliveryservice`` field must be provided. If both are provided, then they must match. diff --git a/docs/source/api/v5/deliveryservices_sslkeys_generate_letsencrypt.rst b/docs/source/api/v5/deliveryservices_sslkeys_generate_letsencrypt.rst new file mode 100644 index 0000000000..424f6988e2 --- /dev/null +++ b/docs/source/api/v5/deliveryservices_sslkeys_generate_letsencrypt.rst @@ -0,0 +1,72 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservices-sslkeys-generate-letsencrypt: + +************************************************* +``deliveryservices/sslkeys/generate/letsencrypt`` +************************************************* + +.. deprecated:: ATCv6 + +``POST`` +======== +Generates an SSL certificate and private key using Let's Encrypt for a :term:`Delivery Service` + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: DS-SECURITY-KEY:CREATE, DELIVERY-SERVICE:READ +:Response Type: Object (string) + +Request Structure +----------------- +:key: The :ref:`ds-xmlid` of the :term:`Delivery Service` for which keys will be generated [#needOne]_ +:deliveryservice: The :ref:`ds-xmlid` of the :term:`Delivery Service` for which keys will be generated [#needOne]_ +:version: An integer that defines the "version" of the key - which may be thought of as the sequential generation; that is, the higher the number the more recent the key +:hostname: The desired hostname of the :term:`Delivery Service` + + .. note:: In most cases, this must be the same as the :ref:`ds-example-urls`. + +:cdn: The name of the CDN of the :term:`Delivery Service` for which the certs will be generated + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/deliveryservices/sslkeys/generate/letsencrypt HTTP/1.1 + Content-Type: application/json + + { + "key": "ds-01", + "deliveryservice": "ds-01", + "version": "3", + "hostname": "tr.ds-01.ott.kabletown.com", + "cdn":"test-cdn" + } + + +Response Structure +------------------ +.. code-block:: json + :caption: Response Example + + { "alerts": [{ + "level": "warning", + "text": "This endpoint is deprecated, please use /deliveryservices/sslkeys/generate/acme instead." + },{ + "level": "success", + "text": "Beginning async ACME call for demo1 using Lets Encrypt. This may take a few minutes. Status updates can be found here: /api/5.0/async_status/1" + }]} + +.. [#needOne] Either the ``key`` or the ``deliveryservice`` field must be provided. If both are provided, then they must match. diff --git a/docs/source/api/v5/deliveryservices_xmlid_servers.rst b/docs/source/api/v5/deliveryservices_xmlid_servers.rst new file mode 100644 index 0000000000..7790933b04 --- /dev/null +++ b/docs/source/api/v5/deliveryservices_xmlid_servers.rst @@ -0,0 +1,83 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservices-xmlid-servers: + +*************************************** +``deliveryservices/{{xml_id}}/servers`` +*************************************** + +``POST`` +======== +Assigns :term:`cache servers` to a :term:`Delivery Service`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations"\ [#tenancy]_ +:Permissions Required: DELIVERY-SERVICE:UPDATE, SERVER:UPDATE, DELIVERY-SERVICE:READ, SERVER:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +--------+----------------------------------------------------------------------------------------+ + | Name | Description | + +========+========================================================================================+ + | xml_id | The 'xml_id' of the :term:`Delivery Service` whose server assignments are being edited | + +--------+----------------------------------------------------------------------------------------+ + +:serverNames: An array of hostname of :term:`cache servers` to assign to this :term:`Delivery Service` + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/deliveryservices/test/servers HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 24 + Content-Type: application/json + + { "serverNames": [ "edge" ] } + +Response Structure +------------------ +:xml_id: The :ref:`ds-xmlid` of the :term:`Delivery Service` to which the servers in ``serverNames`` have been assigned +:serverNames: An array of hostnames of :term:`cache servers` assigned to :term:`Delivery Service` identified by ``xml_id`` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: zTpLrWiLM4xRsm8mlBQFB5KzT478AjloSyXHgtyWhebCv1YIwWltmkjr0HFgc3GMGZODt+fyzkOYy5Zl/yBtJw== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 20 Nov 2018 15:21:50 GMT + Content-Length: 52 + + { "response": { + "serverNames": [ + "edge" + ], + "xmlId": "test" + }} + +.. [#tenancy] Users can only assign servers to :term:`Delivery Services` that are visible to their :term:`Tenant`. diff --git a/docs/source/api/v5/deliveryservices_xmlid_urisignkeys.rst b/docs/source/api/v5/deliveryservices_xmlid_urisignkeys.rst new file mode 100644 index 0000000000..9e4f0ca9d7 --- /dev/null +++ b/docs/source/api/v5/deliveryservices_xmlid_urisignkeys.rst @@ -0,0 +1,198 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservices-xmlid-urisignkeys: + +******************************************* +``deliveryservices/{{xml_id}}/urisignkeys`` +******************************************* + +``DELETE`` +========== +Deletes URISigning objects for a :term:`Delivery Service`. + +:Auth. Required: Yes +:Roles Required: admin\ [#tenancy]_ +:Permissions Required: DS-SECURITY-KEY:DELETE, DS-SECURITY-KEY:READ, DELIVERY-SERVICE:READ, DELIVERY-SERVICE:UPDATE +:Response Type: ``undefined`` + +Request Structure +----------------- + +.. table:: Request Path Parameters + + +-----------+----------+----------------------------------------+ + | Name | Required | Description | + +===========+==========+========================================+ + | xml_id | yes | xml_id of the desired delivery service | + +-----------+----------+----------------------------------------+ + +Response Structure +------------------ +TBD + +``GET`` +======= +Retrieves one or more URISigning objects for a delivery service. + +:Auth. Required: Yes +:Roles Required: admin\ [#tenancy]_ +:Permissions Required: DS-SECURITY-KEY:READ +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Route Parameters + + +-----------+----------+----------------------------------------+ + | Name | Required | Description | + +===========+==========+========================================+ + | xml_id | yes | xml_id of the desired delivery service | + +-----------+----------+----------------------------------------+ + +Response Structure +------------------ + +:Issuer: a string describing the issuer of the URI signing object. Multiple URISigning objects may be returned in a response, see example +:renewal_kid: a string naming the jwt key used for renewals +:keys: json array of jwt symmetric keys +:alg: this parameter repeats for each jwt key in the array and specifies the jwa encryption algorithm to use with this key, :rfc:`7518` +:kid: this parameter repeats for each jwt key in the array and specifies the unique id for the key as defined in :rfc:`7516` +:kty: this parameter repeats for each jwt key in the array and specifies the key type as defined in :rfc:`7516` +:k: this parameter repeats for each jwt key in the array and specifies the base64 encoded symmetric key see :rfc:`7516` + +.. code-block:: json + :caption: Response Example + + { "Kabletown URI Authority": { + "renewal_kid": "Second Key", + "keys": [ + { + "alg": "HS256", + "kid": "First Key", + "kty": "oct", + "k": "Kh_RkUMj-fzbD37qBnDf_3e_RvQ3RP9PaSmVEpE24AM" + }, + { + "alg": "HS256", + "kid": "Second Key", + "kty": "oct", + "k": "fZBpDBNbk2GqhwoB_DGBAsBxqQZVix04rIoLJ7p_RlE" + } + ] + }} + + +``POST`` +======== +Assigns URISigning objects to a delivery service. + +:Auth. Required: Yes +:Roles Required: admin\ [#tenancy]_ +:Permissions Required: DS-SECURITY-KEY:CREATE +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Path Parameters + + +-----------+----------+----------------------------------------+ + | Name | Required | Description | + +===========+==========+========================================+ + | xml_id | yes | xml_id of the desired delivery service | + +-----------+----------+----------------------------------------+ + +Request Structure +----------------- +:Issuer: a string describing the issuer of the URI signing object. Multiple URISigning objects may be returned in a response, see example +:renewal_kid: a string naming the jwt key used for renewals +:keys: json array of jwt symmetric keys +:alg: this parameter repeats for each jwt key in the array and specifies the jwa encryption algorithm to use with this key, :rfc:`7518` +:kid: this parameter repeats for each jwt key in the array and specifies the unique id for the key as defined in :rfc:`7516` +:kty: this parameter repeats for each jwt key in the array and specifies the key type as defined in :rfc:`7516` +:k: this parameter repeats for each jwt key in the array and specifies the base64 encoded symmetric key see :rfc:`7516` + +.. code-block:: json + :caption: Request Example + + { "Kabletown URI Authority": { + "renewal_kid": "Second Key", + "keys": [ + { + "alg": "HS256", + "kid": "First Key", + "kty": "oct", + "k": "Kh_RkUMj-fzbD37qBnDf_3e_RvQ3RP9PaSmVEpE24AM" + }, + { + "alg": "HS256", + "kid": "Second Key", + "kty": "oct", + "k": "fZBpDBNbk2GqhwoB_DGBAsBxqQZVix04rIoLJ7p_RlE" + } + ] + }} + +``PUT`` +======= +updates URISigning objects on a delivery service. + +:Auth. Required: Yes +:Roles Required: admin\ [#tenancy]_ +:Permissions Required: DS-SECURITY-KEY:UPDATE +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Path Parameters + + +-----------+----------+----------------------------------------+ + | Name | Required | Description | + +===========+==========+========================================+ + | xml_id | yes | xml_id of the desired delivery service | + +-----------+----------+----------------------------------------+ + +Request Structure +----------------- +:Issuer: a string describing the issuer of the URI signing object. Multiple URISigning objects may be returned in a response, see example +:renewal_kid: a string naming the jwt key used for renewals +:keys: json array of jwt symmetric keys +:alg: this parameter repeats for each jwt key in the array and specifies the jwa encryption algorithm to use with this key, :rfc:`7518` +:kid: this parameter repeats for each jwt key in the array and specifies the unique id for the key as defined in :rfc:`7516` +:kty: this parameter repeats for each jwt key in the array and specifies the key type as defined in :rfc:`7516` +:k: this parameter repeats for each jwt key in the array and specifies the base64 encoded symmetric key see :rfc:`7516` + +.. code-block:: json + :caption: Request Example + + { "Kabletown URI Authority": { + "renewal_kid": "Second Key", + "keys": [ + { + "alg": "HS256", + "kid": "First Key", + "kty": "oct", + "k": "Kh_RkUMj-fzbD37qBnDf_3e_RvQ3RP9PaSmVEpE24AM" + }, + { + "alg": "HS256", + "kid": "Second Key", + "kty": "oct", + "k": "fZBpDBNbk2GqhwoB_DGBAsBxqQZVix04rIoLJ7p_RlE" + } + ] + }} + +.. [#tenancy] URI Signing Keys can only be created, viewed, deleted, or modified on :term:`Delivery Services` that either match the requesting user's :term:`Tenant` or are descendants thereof. diff --git a/docs/source/api/v5/deliveryservices_xmlid_xmlid_sslkeys.rst b/docs/source/api/v5/deliveryservices_xmlid_xmlid_sslkeys.rst new file mode 100644 index 0000000000..bd00ddd4ec --- /dev/null +++ b/docs/source/api/v5/deliveryservices_xmlid_xmlid_sslkeys.rst @@ -0,0 +1,148 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservices-xmlid-xmlid-sslkeys: + +******************************************** +``deliveryservices/xmlId/{{XMLID}}/sslkeys`` +******************************************** + +``GET`` +======= +Retrieves SSL keys for a :term:`Delivery Service`. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: DS-SECURITY-KEY:READ, DELIVERY-SERVICE:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +-------+------------------------------------------------------+ + | Name | Description | + +=======+======================================================+ + | XMLID | The 'xml_id' of the desired :term:`Delivery Service` | + +-------+------------------------------------------------------+ + + +.. table:: Request Query Parameters + + +---------+----------+-----------------------------------------------------------------------------------------+ + | Name | Required | Description | + +=========+==========+=========================================================================================+ + | version | no | The version number of the SSL keys to retrieve | + +---------+----------+-----------------------------------------------------------------------------------------+ + | decode | no | If ``true``, the returned keys will be decoded - if ``false``, they will not be decoded | + +---------+----------+-----------------------------------------------------------------------------------------+ + +.. caution:: There's almost certainly no good reason to request the private key! Even when "base 64-encoded" do not let **ANYONE** see this who would be unable to request it themselves! + +Response Structure +------------------ +:businessUnit: An optional field which, if present, contains the business unit entered by the user when generating the SSL certificate\ [1]_ +:certificate: An object containing the actual generated key, certificate, and signature of the SSL keys + + :crt: Base 64-encoded (or not if the ``decode`` query parameter was given and ``true``) certificate for the :term:`Delivery Service` identified by ``deliveryservice`` + :csr: Base 64-encoded (or not if the ``decode`` query parameter was given and ``true``) csr file for the :term:`Delivery Service` identified by ``deliveryservice`` + :key: Base 64-encoded (or not if the ``decode`` query parameter was given and ``true``) private key for the :term:`Delivery Service` identified by ``deliveryservice`` + + .. caution:: There's almost certainly no good reason to request the private key! Even when "base 64-encoded" do not let **ANYONE** see this who would be unable to request it themselves! + +:cdn: The CDN of the :term:`Delivery Service` for which the certs were generated +:city: An optional field which, if present, contains the city entered by the user when generating the SSL certificate\ [1]_ +:country: An optional field which, if present, contains the country entered by the user when generating the SSL certificate\ [1]_ +:deliveryservice: The 'xml_id' of the :term:`Delivery Service` for which the certificate was generated +:expiration: The expiration date of the certificate for the :term:`Delivery Service` in :rfc:`3339` format +:hostname: The hostname generated by Traffic Ops that is used as the common name when generating the certificate - this will be a FQDN for DNS :term:`Delivery Services` and a wildcard URL for HTTP :term:`Delivery Services` +:organization: An optional field which, if present, contains the organization entered by the user when generating certificate\ [1]_ +:sans: The :abbr:`SANs (Subject Alternate Names)` from the SSL certificate. +:state: An optional field which, if present, contains the state entered by the user when generating certificate\ [1]_ +:version: An integer that defines the "version" of the key - which may be thought of as the sequential generation; that is, the higher the number the more recent the key + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Content-Type: application/json + + { "response": { + "certificate": { + "crt": "crt", + "key": "key", + "csr": "csr" + }, + "deliveryservice": "my-ds", + "cdn": "qa", + "businessUnit": "CDN_Eng", + "city": "Denver", + "organization": "KableTown", + "hostname": "foober.com", + "country": "US", + "state": "Colorado", + "version": "1", + "expiration": "2020-08-18T13:53:06Z", + "sans": ["*.foober.com", "*.foober2.com"] + }} + +``DELETE`` +========== +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: DS-SECURITY-KEY:DELETE, DELIVERY-SERVICE:READ, DS-SECURITY-KEY:READ, DELIVERY-SERVICE:UPDATE +:Response Type: Object (string) + +Request Structure +----------------- +.. table:: Request Path Parameters + + +-------+----------+-------------------------------------------------------------+ + | Name | Required | Description | + +=======+==========+=============================================================+ + | xmlId | yes | The :ref:`ds-xmlid` of the desired :term:`Delivery Service` | + +-------+----------+-------------------------------------------------------------+ + +.. table:: Request Query Parameters + + +---------+----------+------------------------------------------------------------+ + | Name | Required | Description | + +=========+==========+============================================================+ + | version | no | The version number of the SSL keys that shall be retrieved | + +---------+----------+------------------------------------------------------------+ + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Wed, 18 Mar 2020 17:36:10 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: Pj+zCoOXg19nGNxcSkjib2iDjG062Y3RcEEV+OYnwbGIsLcpa0BKZleY/qJOKT5DkSoX2qQkckUxUqdDxjVorQ== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 18 Mar 2020 16:36:10 GMT + Content-Length: 79 + + { + "response": "Successfully deleted ssl keys for demo1" + } + +.. [1] These optional fields will be present in the response if and only if they were specified during key generation; they are optional during key generation and thus cannot be guaranteed to exist or not exist. diff --git a/docs/source/api/v5/deliveryservices_xmlid_xmlid_urlkeys.rst b/docs/source/api/v5/deliveryservices_xmlid_xmlid_urlkeys.rst new file mode 100644 index 0000000000..a1ed4921db --- /dev/null +++ b/docs/source/api/v5/deliveryservices_xmlid_xmlid_urlkeys.rst @@ -0,0 +1,103 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservices-xmlid-xmlid-urlkeys: + +******************************************** +``deliveryservices/xmlId/{{xmlid}}/urlkeys`` +******************************************** + +``GET`` +======= +.. seealso:: :ref:`to-api-deliveryservices-id-urlkeys` + +Retrieves URL signing keys for a :term:`Delivery Service`. + +.. caution:: This method will return the :term:`Delivery Service`'s **PRIVATE** URL signing keys! Be wary of using this endpoint and **NEVER** share the output with anyone who would be unable to see it on their own. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: DS-SECURITY-KEY:READ, DELIVERY-SERVICE:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +-------+------------------------------------------------------+ + | Name | Description | + +=======+======================================================+ + | xmlid | The 'xml_id' of the desired :term:`Delivery Service` | + +-------+------------------------------------------------------+ + +Response Structure +------------------ +:key: The private URL signing key for this :term:`Delivery Service` as a base-64-encoded string, where ```` is the "generation" of the key e.g. the first key will always be named ``"key0"``. Up to 16 concurrent generations are retained at any time (```` is always on the interval [0,15]) + +.. code-block:: json + :caption: Response Example + + { "response": { + "key9":"ZvVQNYpPVQWQV8tjQnUl6osm4y7xK4zD", + "key6":"JhGdpw5X9o8TqHfgezCm0bqb9SQPASWL", + "key8":"ySXdp1T8IeDEE1OCMftzZb9EIw_20wwq", + "key0":"D4AYzJ1AE2nYisA9MxMtY03TPDCHji9C", + "key3":"W90YHlGc_kYlYw5_I0LrkpV9JOzSIneI", + "key12":"ZbtMb3mrKqfS8hnx9_xWBIP_OPWlUpzc", + "key2":"0qgEoDO7sUsugIQemZbwmMt0tNCwB1sf", + "key4":"aFJ2Gb7atmxVB8uv7T9S6OaDml3ycpGf", + "key1":"wnWNR1mCz1O4C7EFPtcqHd0xUMQyNFhA", + "key11":"k6HMzlBH1x6htKkypRFfWQhAndQqe50e", + "key10":"zYONfdD7fGYKj4kLvIj4U0918csuZO0d", + "key15":"3360cGaIip_layZMc_0hI2teJbazxTQh", + "key5":"SIwv3GOhWN7EE9wSwPFj18qE4M07sFxN", + "key13":"SqQKBR6LqEOzp8AewZUCVtBcW_8YFc1g", + "key14":"DtXsu8nsw04YhT0kNoKBhu2G3P9WRpQJ", + "key7":"cmKoIIxXGAxUMdCsWvnGLoIMGmNiuT5I" + }} + + +``DELETE`` +========== +.. seealso:: :ref:`to-api-deliveryservices-id-urlkeys` + +Deletes URL signing keys for a :term:`Delivery Service`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: DS-SECURITY-KEY:DELETE, DS-SECURITY-KEY:READ, DELIVERY-SERVICE:READ, DELIVERY-SERVICE:UPDATE +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +-------+------------------------------------------------------+ + | Name | Description | + +=======+======================================================+ + | xmlid | The 'xml_id' of the desired :term:`Delivery Service` | + +-------+------------------------------------------------------+ + +Response Structure +------------------ +.. code-block:: json + :caption: Response Example + + { + "alerts": [{ + "level": "success", + "text": "Successfully deleted URL Sig keys from Traffic Vault" + }] + } diff --git a/docs/source/api/v5/deliveryservices_xmlid_xmlid_urlkeys_copyfromxmlid_copyfromxmlid.rst b/docs/source/api/v5/deliveryservices_xmlid_xmlid_urlkeys_copyfromxmlid_copyfromxmlid.rst new file mode 100644 index 0000000000..0a9505b431 --- /dev/null +++ b/docs/source/api/v5/deliveryservices_xmlid_xmlid_urlkeys_copyfromxmlid_copyfromxmlid.rst @@ -0,0 +1,50 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservices-xmlid-xml_id-urlkeys-copyFrom_xml_id: + +******************************************************************************* +``deliveryservices/xmlId/{{xml_id}}/urlkeys/copyFromXmlId/{{copyFrom_xml_id}}`` +******************************************************************************* + +``POST`` +======== +Allows a user to copy URL signing keys from a specified :term:`Delivery Service` to another :term:`Delivery Service`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: DS-SECURITY-KEY:READ, DS-SECURITY-KEY:CREATE, DELIVERY-SERVICE:READ, DELIVERY-SERVICE:UPDATE +:Response Type: Object (string) + +Request Structure +----------------- +.. table:: Request Path Parameters + + +-----------------+--------------------------------------------------------------------------------------+ + | Name | Description | + +=================+======================================================================================+ + | xml_id | The :ref:`ds-xmlid` of the :term:`Delivery Service` *to* which keys will be copied | + +-----------------+--------------------------------------------------------------------------------------+ + | copyFrom_xml_id | The :ref:`ds-xmlid` of the :term:`Delivery Service` *from* which keys will be copied | + +-----------------+--------------------------------------------------------------------------------------+ + +Response Structure +------------------ +.. code-block:: json + :caption: Response Example + + { + "response": "Successfully copied and stored keys" + } diff --git a/docs/source/api/v5/deliveryservices_xmlid_xmlid_urlkeys_generate.rst b/docs/source/api/v5/deliveryservices_xmlid_xmlid_urlkeys_generate.rst new file mode 100644 index 0000000000..62f2dedab3 --- /dev/null +++ b/docs/source/api/v5/deliveryservices_xmlid_xmlid_urlkeys_generate.rst @@ -0,0 +1,48 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryservices-xmlid-xmlid-urlkeys-generate: + +****************************************************** +``deliveryservices/xmlId/{{xml_id}}/urlkeys/generate`` +****************************************************** + +``POST`` +======== +Generates URL signing keys for a :term:`Delivery Service` + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: DS-SECURITY-KEY:CREATE, DELIVERY-SERVICE:READ, DELIVERY-SERVICE:UPDATE +:Response Type: Object (string) + +Request Structure +----------------- +.. table:: Request Path Parameters + + +--------+-------------------------------------------------------------+ + | Name | Description | + +========+=============================================================+ + | xml_id | The :ref:`ds-xmlid` of the desired :term:`Delivery Service` | + +--------+-------------------------------------------------------------+ + +Response Structure +------------------ +.. code-block:: json + :caption: Response Example + + { + "response": "Successfully generated and stored keys" + } diff --git a/docs/source/api/v5/deliveryserviceserver.rst b/docs/source/api/v5/deliveryserviceserver.rst new file mode 100644 index 0000000000..e8269574b1 --- /dev/null +++ b/docs/source/api/v5/deliveryserviceserver.rst @@ -0,0 +1,165 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryserviceserver: + +************************* +``deliveryserviceserver`` +************************* + +``GET`` +======= +Retrieve information about the assignment of servers to :term:`Delivery Services` + +:Auth. Required: Yes +:Roles Required: None\ [1]_ +:Permissions Required: SERVER:READ, DELIVERY-SERVICE:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-----------+----------+-------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | Name | Required | Default | Description | + +===========+==========+===================+==============================================================================================================================================================================+ + | cdn | no | None | Limit the results to delivery service servers for the given CDN name | + +-----------+----------+-------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | page | no | 0 | The page number for use in pagination - ``0`` means "no pagination" | + +-----------+----------+-------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | limit | no | 20 | Limits the results to a maximum of this number - if pagination is used, this defines the number of results per page | + +-----------+----------+-------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | orderby | no | "deliveryService" | Choose the ordering of the results - the value must either be the name of one of the fields of the objects in the ``response`` array or be empty to skip ordering altogether | + +-----------+----------+-------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/deliveryserviceserver?page=1&limit=2&orderby=lastUpdated HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + + +Response Structure +------------------ +Unlike most API endpoints, this will return a JSON response body containing both a "response" object as well as other, top-level fields (besides the optional "alerts" field). For this reason, this section contains a "response" key, which normally is implicit. + +.. seealso:: :ref:`to-api-response-structure` + +:limit: The maximum size of the ``response`` array, also indicative of the number of results per page using the pagination requested by the query parameters (if any) - this should be the same as the ``limit`` query parameter (if given) +:orderby: A string that names the field by which the elements of the ``response`` array are ordered - should be the same as the ``orderby`` request query parameter (if given) +:response: An array of objects, each of which represents a server's :term:`Delivery Service` assignment + + :deliveryService: The integral, unique identifier of the :term:`Delivery Service` to which the server identified by ``server`` is assigned + :lastUpdated: The date and time at which the server's assignment to a :term:`Delivery Service` was last updated + :server: The integral, unique identifier of a server which is assigned to the :term:`Delivery Service` identified by ``deliveryService`` + +:size: The page number - if pagination was requested in the query parameters, else ``0`` to indicate no pagination - of the results represented by the ``response`` array. This is named "size" for legacy reasons + + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: J7sK8PohQWyTpTrMjjrWdlJwPj+Zyep/xutM25uVosL6cHgi30nXa6VMyOC5Y3vd9r5KLES8rTgR+qUQcZcJ/A== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 01 Nov 2018 14:27:45 GMT + Content-Length: 129 + + { "orderby": "lastUpdated", + "response": [ + { + "server": 8, + "deliveryService": 1, + "lastUpdated": "2018-11-01 14:10:38+00" + } + ], + "size": 1, + "limit": 2 + } + +.. [1] While no roles are required, this endpoint *does* respect tenancy permissions (pending `GitHub Issue #2978 `_\ ). + +``POST`` +======== +Assign a set of one or more servers to a :term:`Delivery Service` + +:Auth. Required: Yes +:Roles Required: "admin" or "operations"\ [2]_ +:Permissions Required: DELIVERY-SERVICE:READ, SERVER:READ, SERVER:UPDATE, DELIVERY-SERVICE:UPDATE +:Response Type: Object + +Request Structure +----------------- +:dsId: The integral, unique identifier of the :term:`Delivery Service` to which the servers identified in the ``servers`` array will be assigned +:replace: If ``true``, any existing assignments for a server identified in the ``servers`` array will be overwritten by this request +:servers: An array of integral, unique identifiers for servers which are to be assigned to the :term:`Delivery Service` identified by ``deliveryService`` + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/deliveryserviceserver HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 46 + Content-Type: application/x-www-form-urlencoded + + dsId=1&replace=true&servers=12 + +Response Structure +------------------ +:dsId: The integral, unique identifier of the :term:`Delivery Service` to which the servers identified by the elements of the ``servers`` array have been assigned +:replace: If ``true``, any existing assignments for a server identified in the ``servers`` array have been overwritten by this request +:servers: An array of integral, unique identifiers for servers which have been assigned to the :term:`Delivery Service` identified by ``deliveryService`` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: D+HhGhoxzaxvka9vZIStoaOZUpX23nz7zZnMbpFHNRO3MawyEaSb3GVUHQyCv6sDgwhpZZjRggDmctGCw88flg== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 01 Nov 2018 14:12:49 GMT + Content-Length: 123 + + { "alerts": [ + { + "text": "server assignements complete", + "level": "success" + } + ], + "response": { + "dsId": 1, + "replace": false, + "servers": [ 12 ] + }} + + +.. [2] Users with the "admin" or "operations" roles will be able to modify ALL server-to-Delivery-Service assignments, whereas all other users can only assign servers to the :term:`Delivery Services` their Tenant has permissions to edit. diff --git a/docs/source/api/v5/deliveryserviceserver_dsid_serverid.rst b/docs/source/api/v5/deliveryserviceserver_dsid_serverid.rst new file mode 100644 index 0000000000..f23ee1584f --- /dev/null +++ b/docs/source/api/v5/deliveryserviceserver_dsid_serverid.rst @@ -0,0 +1,69 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-deliveryserviceserver-dsid-serverid: + +*********************************************** +``deliveryserviceserver/{{DSID}}/{{serverID}}`` +*********************************************** + +``DELETE`` +========== +Removes a :term:`cache server` from a :term:`Delivery Service`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations"\ [1]_ +:Permissions Required: DELIVERY-SERVICE:READ, DELIVERY-SERVICE:UPDATE, SERVER:READ, SERVER:UPDATE +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Path Parameters + + +----------+----------+---------------------------------------------------------------+ + | Name | Required | Description | + +==========+==========+===============================================================+ + | dsId | yes | An integral, unique identifier for a :term:`Delivery Service` | + +----------+----------+---------------------------------------------------------------+ + | serverID | yes | An integral, unique identifier for a server | + +----------+----------+---------------------------------------------------------------+ + +.. note:: The server identified by ``serverID`` must be a :term:`cache server`, or the assignment will fail. + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: rGD2sOMHYF0sga1zuTytyLHCUkkc3ZwQRKvZ/HuPzObOP4WztKTOVXB4uhs3iJqBg9zRB2TucMxONHN+3/yShQ== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 01 Nov 2018 14:24:34 GMT + Content-Length: 80 + + { "alerts": [ + { + "text": "Server unlinked from delivery service.", + "level": "success" + } + ]} + +.. [1] Users with the "admin" or "operations" roles will be able to delete *any*:term:`Delivery Service`, whereas other users will only be able to delete :term:`Delivery Services` that their tenant has permissions to delete. diff --git a/docs/source/api/v5/divisions.rst b/docs/source/api/v5/divisions.rst new file mode 100644 index 0000000000..ba6edf6188 --- /dev/null +++ b/docs/source/api/v5/divisions.rst @@ -0,0 +1,148 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-divisions: + +************* +``divisions`` +************* + +``GET`` +======= +Returns a JSON representation of all configured :term:`Divisions`. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: DIVISION:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-----------+---------------------------------------------------------------------------------------------------------------+ + | Name | Description | + +===========+===============================================================================================================+ + | id | Filter for :term:`Divisions` having this integral, unique identifier | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | name | Filter for :term:`Divisions` with this name | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | orderby | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` | + | | array | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | sortOrder | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | limit | Choose the maximum number of results to return | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | offset | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | page | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long | + | | and the first page is 1. If ``offset`` was defined, this query parameter has no effect. ``limit`` must be | + | | defined to make use of ``page``. | + +-----------+---------------------------------------------------------------------------------------------------------------+ + +Response Structure +------------------ +:id: An integral, unique identifier for this Division +:lastUpdated: The date and time at which this Division was last modified, in :ref:`non-rfc-datetime` +:name: The Division name + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: SLKi9RHa67sGoSz62IDcQsk7KZjTXKfonqMoCUFPXGcNUdhBssvUjc1G7KkWK8X1Ny16geMx2BN8Hm/3dQ75GA== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 29 Nov 2018 19:44:03 GMT + Content-Length: 139 + + { "response": [ + { + "id": 1, + "lastUpdated": "2018-11-29 18:38:28+00", + "name": "Quebec" + }, + { + "id": 2, + "lastUpdated": "2018-11-29 18:38:28+00", + "name": "USA" + } + ]} + + +``POST`` +======== +Creates a new Division. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: DIVISION:CREATE, DIVISION:READ +:Response Type: Object + +Request Structure +----------------- +:name: The name of the new Division + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/divisions HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 16 + Content-Type: application/json + + {"name": "test"} + +Response Structure +------------------ +:id: An integral, unique identifier for this Division +:lastUpdated: The date and time at which this Division was last modified, in :ref:`non-rfc-datetime` +:name: The Division name + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: +pJm4c3O+JTaSXNt+LP+u240Ba/SsvSSDOQ4rDc6hcyZ0FIL+iY/WWrMHhpLulRGKGY88bM4YPCMaxGn3FZ9yQ== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 29 Nov 2018 19:52:06 GMT + Content-Length: 136 + + { "alerts": [ + { + "text": "division was created.", + "level": "success" + } + ], + "response": { + "id": 3, + "lastUpdated": "2018-11-29 19:52:06+00", + "name": "test" + }} diff --git a/docs/source/api/v5/divisions_id.rst b/docs/source/api/v5/divisions_id.rst new file mode 100644 index 0000000000..b6e18cc697 --- /dev/null +++ b/docs/source/api/v5/divisions_id.rst @@ -0,0 +1,149 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-divisions-id: + +******************** +``divisions/{{ID}}`` +******************** + +``PUT`` +======= +Updates a specific Division + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: DIVISION:UPDATE, DIVISION:READ + + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+-----------------------------------------------------------+ + | Name | Description | + +======+===========================================================+ + | ID | The integral, unique identifier of the requested Division | + +------+-----------------------------------------------------------+ + + +:name: The new name of the Division + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/divisions/3 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 17 + Content-Type: application/json + + {"name": "quest"} + +Response Structure +------------------ +:id: An integral, unique identifier for this Division +:lastUpdated: The date and time at which this Division was last modified, in :ref:`non-rfc-datetime` +:name: The Division name + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: yBd8MzBR/Qbc/xts44WEIFRTrqeMKZwUe2ufpm6JH6frh1UjFmYRs3/B7E5FTruFWRTuvEIlx5EpDmp3f9LjzA== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 29 Nov 2018 20:10:36 GMT + Content-Length: 137 + + { "alerts": [ + { + "text": "division was updated.", + "level": "success" + } + ], + "response": { + "id": 3, + "lastUpdated": "2018-11-29 20:10:36+00", + "name": "quest" + }} + +``DELETE`` +============ +Deletes a specific Division + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: DIVISION:DELETE, DIVISION:READ + + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+-----------------------------------------------------------+ + | Name | Description | + +======+===========================================================+ + | ID | The integral, unique identifier of the requested Division | + +------+-----------------------------------------------------------+ + + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/divisions/3 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 2 + Content-Type: application/json + + {} + +Response Structure +------------------ +:id: An integral, unique identifier for this Division +:lastUpdated: The date and time at which this Division was last modified, in :ref:`non-rfc-datetime` +:name: The Division name + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: yBd8MzBR/Qbc/xts44WEIFRTrqeMKZwUe2ufpm6JH6frh1UjFmYRs3/B7E5FTruFWRTuvEIlx5EpDmp3f9LjzA== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 29 Nov 2018 20:10:36 GMT + Content-Length: 83 + + { "alerts": [ + { + "text": "division was deleted.", + "level": "success" + } + ]} diff --git a/docs/source/api/v5/federation_resolvers.rst b/docs/source/api/v5/federation_resolvers.rst new file mode 100644 index 0000000000..5753dc5b88 --- /dev/null +++ b/docs/source/api/v5/federation_resolvers.rst @@ -0,0 +1,233 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-federation_resolvers: + +************************ +``federation_resolvers`` +************************ + +``GET`` +======= +Retrieves :term:`Federation` Resolvers. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: FEDERATION-RESOLVER:READ, TYPE:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +------------+----------+-----------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +============+==========+=====================================================================================================+ + | id | no | Return only the Federation Resolver identified by this integral, unique identifier | + +------------+----------+-----------------------------------------------------------------------------------------------------+ + | ipAddress | no | Return only the Federation Resolver(s) that has/have this IP Address | + +------------+----------+-----------------------------------------------------------------------------------------------------+ + | type | no | Return only the Federation Resolvers of this :term:`Type` | + +------------+----------+-----------------------------------------------------------------------------------------------------+ + | orderby | no | Choose the ordering of the results - must be the name of one of the fields of the objects in the | + | | | ``response`` array | + +------------+----------+-----------------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +------------+----------+-----------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +------------+----------+-----------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with | + | | | limit | + +------------+----------+-----------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are | + | | | ``limit`` long and the first page is 1. If ``offset`` was defined, this query parameter has no | + | | | effect. ``limit`` must be defined to make use of ``page``. | + +------------+----------+-----------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/federation_resolvers?type=RESOLVE6 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.63.0 + Accept: */* + Cookie: mojolicious=... + + +Response Structure +------------------ +:id: The integral, unique identifier of the resolver +:ipAddress: The IP address or :abbr:`CIDR (Classless Inter-Domain Routing)`-notation subnet of the resolver - may be IPv4 or IPv6 +:lastUpdated: The date and time at which this resolver was last updated, in :ref:`non-rfc-datetime` +:type: The :term:`Type` of the resolver + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: 4TLkULAOAuap47H+hpwyf2lHjDbHbSNQHLMj7BCTHtps2CQxCuq7mwctbwqmPdmAjLOUXAIRsHmvSuAp4K64jw== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 06 Nov 2019 00:03:56 GMT + Content-Length: 101 + + { "response": [ + { + "id": 1, + "ipAddress": "::1/1", + "lastUpdated": "2019-11-06 00:00:40+00", + "type": "RESOLVE6" + } + ]} + + +``POST`` +======== +Creates a new federation resolver. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: FEDERATION-RESOLVER:CREATE, TYPE:READ +:Response Type: Object + +Request Structure +----------------- +:ipAddress: The IP address of the resolver - may be IPv4 or IPv6 +:typeId: The integral, unique identifier of the :term:`Type` of resolver being created + + .. caution:: This field should only ever be an identifier for one of the :term:`Types` "RESOLVE4" or "RESOLVE6", but there is **no protection for this built into Traffic Ops** and therefore **any valid** :term:`Type` **identifier will be silently accepted by Traffic Ops** and so care should be taken to ensure that these :term:`Types` are properly identified. If any :term:`Type` besides "RESOLVE4" or "RESOLVE6" is identified, the resulting resolver *will* **not** *work*. + + .. seealso:: :ref:`to-api-types` is the endpoint that can be used to determine the identifier for various :term:`Types` + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/federation_resolvers HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.63.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 36 + Content-Type: application/json + + { + "ipAddress": "::1/1", + "typeId": 37 + } + +Response Structure +------------------ +:id: The integral, unique identifier of the resolver +:ipAddress: The IP address or :abbr:`CIDR (Classless Inter-Domain Routing)`-notation subnet of the resolver - may be IPv4 or IPv6 +:type: The :term:`Type` of the resolver +:typeId: The integral, unique identifier of the :term:`Type` of the resolver + + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: e9D8JNrQb64xpuDwoBwbISSWUkDGCL2l37NuDXsXsPYof2EqmeHondD8NzxDSwWNJ8d9B9DXpZDbRUtgdXR8BQ== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 06 Nov 2019 00:00:40 GMT + Content-Length: 153 + + { "alerts": [ + { + "text": "Federation Resolver created [ IP = ::1/1 ] with id: 1", + "level": "success" + } + ], + "response": { + "id": 1, + "ipAddress": "::1/1", + "type": "RESOLVE6", + "typeId": 37 + }} + +``DELETE`` +========== +Deletes a federation resolver. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: FEDERATION-RESOLVER:DELETE, TYPE:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Query Parameters + + +------+----------------------------------------------------------------------------------+ + | Name | Required | Description | + +======+==========+=======================================================================+ + | id | yes | Integral, unique identifier for the federation resolver to be deleted | + +------+----------+-----------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/federation_resolvers?id=4 HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 0 + +Response Structure +------------------ +:id: The integral, unique identifier of the resolver +:ipAddress: The IP address or :abbr:`CIDR (Classless Inter-Domain Routing)`-notation subnet of the resolver - may be IPv4 or IPv6 +:type: The :term:`Type` of the resolver + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; HttpOnly + Whole-Content-Sha512: 2v4LYQdRVhaFJVd86Iv1BWVYzNPSlzpQ222bUB7Zz+Ss8A48FNyHZjPlq5a+a4g9KAQCTUIytWnIQk+L1fF6FQ== + X-Server-Name: traffic_ops_golang/ + Date: Fri, 08 Nov 2019 23:19:01 GMT + Content-Length: 161 + + { "alerts": [ + { + "text": "Federation resolver deleted [ IP = 1.2.6.4/22 ] with id: 4", + "level": "success" + } + ], + "response": { + "id": 4, + "ipAddress": "1.2.6.4/22", + "type": "RESOLVE6" + }} diff --git a/docs/source/api/v5/federations.rst b/docs/source/api/v5/federations.rst new file mode 100644 index 0000000000..cfe218c6e2 --- /dev/null +++ b/docs/source/api/v5/federations.rst @@ -0,0 +1,263 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-federations: + +*************** +``federations`` +*************** + +``GET`` +======= +Retrieves a list of :term:`Federation` mappings (i.e. :term:`Federation` Resolvers) for the current user. + +:Auth. Required: Yes +:Roles Required: "admin", "Federation", "operations", "Portal", or "Steering" +:Permissions Required: FEDERATION-RESOLVER:READ, DELIVERY-SERVICE:READ +:Response Type: Array + +Request Structure +----------------- +No parameters available. + +Response Structure +------------------ +:deliveryService: The ``xml_id`` that uniquely identifies the :term:`Delivery Service` that uses the federation mappings in ``mappings`` +:mappings: An array of objects that represent the mapping of a :term:`Federation`'s :abbr:`CNAME (Canonical Name)` to one or more Resolvers + + :cname: The actual CNAME used by the :term:`Federation` + :resolve4: An array of IPv4 addresses (or subnets in :abbr:`CIDR (Classless Inter-Domain Routing)` notation) capable of resolving the :term:`Federation`'s CNAME + :resolve6: An array of IPv6 addresses (or subnets in :abbr:`CIDR (Classless Inter-Domain Routing)` notation) capable of resolving the :term:`Federation`'s CNAME + :ttl: The :abbr:`TTL (Time To Live)` of 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: d6Llm5qNc2sfgVH9IimW7hA4wvtBUq6EzUmpJf805kB0k6v2WysNgFEWK4hBXNdAYkr8hYuKPrwDy3tCx0OZ8Q== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 03 Dec 2018 17:19:13 GMT + Content-Length: 136 + + { "response": [ + { + "mappings": [ + { + "ttl": 300, + "cname": "blah.blah.", + "resolve4": [ + "0.0.0.0/32" + ], + "resolve6": [ + "::/128" + ] + } + ], + "deliveryService": "demo1" + } + ]} + + +``POST`` +======== +Allows a user to create :term:`Federation` Resolvers for :term:`Delivery Services`, providing the :term:`Delivery Service` is within a CDN that has some associated :term:`Federation`. + +.. warning:: Confusingly, this method of this endpoint does **not** create a new :term:`Federation`; to do that, the :ref:`to-api-cdns-name-federations` endpoint must be used. Furthermore, the :term:`Federation` must properly be assigned to a :term:`Delivery Service` using the :ref:`to-api-federations-id-deliveryservices` and assigned to the user creating Resolvers using :ref:`to-api-federations-id-users`. + +.. seealso:: The :ref:`to-api-federations-id-federation_resolvers` endpoint duplicates this functionality. + +:Auth. Required: Yes +:Roles Required: "admin", "Federation", "operations", "Portal", or "Steering" +:Permissions Required: FEDERATION-RESOLVER:CREATE, FEDERATION-RESOLVER:READ, DELIVERY-SERVICE:READ +:Response Type: Object (string) + +Request Structure +----------------- + +The request payload is an array of objects that describe Delivery Service :term:`Federation` Resolver mappings. Each object in the array must be in the following format. + +:deliveryService: The :ref:`ds-xmlid` of the :term:`Delivery Service` which will use the :term:`Federation` Resolvers specified in ``mappings`` +:mappings: An object containing two arrays of IP addresses (or subnets in :abbr:`CIDR (Classless Inter-Domain Routing)` notation) to use as :term:`Federation` Resolvers + + :resolve4: An array of IPv4 addresses (or subnets in :abbr:`CIDR (Classless Inter-Domain Routing)` notation) that can resolve the :term:`Delivery Service`'s :term:`Federation` + :resolve6: An array of IPv6 addresses (or subnets in :abbr:`CIDR (Classless Inter-Domain Routing)` notation) that can resolve the :term:`Delivery Service`'s :term:`Federation` + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/federations HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 118 + Content-Type: application/json + + + [{ + "deliveryService":"demo1", + "mappings":{ + "resolve4":["127.0.0.1", "0.0.0.0/32"], + "resolve6":["::1", "5efa::ff00/128"] + } + }] + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: B7TSUOYZPRPyi3mVy+CuxiXR5k/d0s07w4i6kYzpWS+YL79juEfkuSqfedaYG/kMA8O9XbjkWRjcBAdxOVrdTQ== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 23 Oct 2019 22:28:02 GMT + Content-Length: 152 + + { "alerts": [ + { + "text": "admin successfully created federation resolvers.", + "level": "success" + } + ], + "response": "admin successfully created federation resolvers." + } + + +``DELETE`` +========== +Deletes **all** :term:`Federation` Resolvers associated with the logged-in user's :term:`Federations`. + +:Auth. Required: Yes +:Roles Required: "admin", "Federation", "operations", "Portal", or "Steering" +:Permissions Required: FEDERATION-RESOLVER:DELETE +:Response Type: Object (string) + +Request Structure +----------------- +No parameters available + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/federations HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: fd7P45mIiHuYqZZW6+8K+YjY1Pe504Aaw4J4Zp9AhrqLX72ERytTqWtAp1msutzNSRUdUSC72+odNPtpv3O8uw== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 23 Oct 2019 23:34:53 GMT + Content-Length: 184 + + { "alerts": [ + { + "text": "admin successfully deleted all federation resolvers: [ 8.8.8.8 ]", + "level": "success" + } + ], + "response": "admin successfully deleted all federation resolvers: [ 8.8.8.8 ]" + } + +``PUT`` +======= +Replaces **all** :term:`Federations` Resolvers associated with a user's :term:`Delivery Service`\ (s) with those defined inside the request payload. + +:Auth. Required: Yes +:Roles Required: "admin", "Federation", "operations", "Portal", or "Steering" +:Permissions Required: FEDERATION-RESOLVER:DELETE, FEDERATION-RESOLVER:CREATE, DELIVERY-SERVICE:READ +:Response Type: Object (string) + +Request Structure +----------------- +The request payload is an array of objects that describe Delivery Service :term:`Federation` Resolver mappings. Each object in the array must be in the following format. + +:deliveryService: The :ref:`ds-xmlid` of the :term:`Delivery Service` which will use the :term:`Federation` Resolvers specified in ``mappings`` +:mappings: An object containing two arrays of IP addresses (or subnets in :abbr:`CIDR (Classless Inter-Domain Routing)` notation) to use as :term:`Federation` Resolvers + + :resolve4: An array of IPv4 addresses (or subnets in :abbr:`CIDR (Classless Inter-Domain Routing)` notation) that can resolve the :term:`Delivery Service`'s :term:`Federation` + :resolve6: An array of IPv6 addresses (or subnets in :abbr:`CIDR (Classless Inter-Domain Routing)` notation) that can resolve the :term:`Delivery Service`'s :term:`Federation` + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/federations HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 95 + Content-Type: application/json + + [{ "mappings": { + "resolve4": ["8.8.8.8"], + "resolve6": [] + }, + "deliveryService":"demo1" + }] + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: dQ5AvQULhc254zQwgUpBl1/CHbLr/clKtkbs0Ju9f1BM4xIfbbO3puFNN9zaEaZ1iz0lBvHFp/PgfUqisD3QHA== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 23 Oct 2019 23:22:03 GMT + Content-Length: 258 + Content-Type: application/json + + { "alerts": [ + { + "text": "admin successfully deleted all federation resolvers: [ 8.8.8.8 ]", + "level": "success" + }, + { + "text": "admin successfully created federation resolvers.", + "level": "success" + } + ], + "response": "admin successfully created federation resolvers." + } diff --git a/docs/source/api/v5/federations_all.rst b/docs/source/api/v5/federations_all.rst new file mode 100644 index 0000000000..d0e7acc265 --- /dev/null +++ b/docs/source/api/v5/federations_all.rst @@ -0,0 +1,94 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-federations-all: + +******************* +``federations/all`` +******************* + +``GET`` +======= +Retrieves a list of :term:`Federation` mappings (also called :term:`Federation` Resolvers) for the current user. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: FEDERATION-RESOLVER:READ, DELIVERY-SERVICE:READ +:Response Type: Array + +Request Structure +----------------- +No parameters available. + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/federations/all HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + +Response Structure +------------------ +:deliveryService: The :ref:`ds-xmlid` of the delivery service. +:mappings: An array of objects that represent the mapping of a :term:`Federation`'s :abbr:`CNAME (Canonical Name)` to one or more Resolvers + + :cname: The actual CNAME used by the :term:`Federation` + :ttl: The :abbr:`TTL (Time To Live)` of 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Sun, 23 Feb 2020 21:38:06 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: UQBlGVPJytYMkv0V42EAIoJUnXjBTCXnOGpOberxte6TtnX63LTAKFfD2LejBVYXkKtnCdkBbs+SzhA0H1zdog== + X-Server-Name: traffic_ops_golang/ + Date: Sun, 23 Feb 2020 20:38:06 GMT + Content-Length: 138 + + { + "response": [ + { + "mappings": [ + { + "ttl": 60, + "cname": "img1.mcdn.ciab.test." + }, + { + "ttl": 60, + "cname": "img2.mycdn.ciab.test." + } + ], + "deliveryService": "demo1" + }, + { + "mappings": [ + { + "ttl": 60, + "cname": "static.mycdn.ciab.test." + } + ], + "deliveryService": "demo2" + } + ] + } diff --git a/docs/source/api/v5/federations_id_deliveryservices.rst b/docs/source/api/v5/federations_id_deliveryservices.rst new file mode 100644 index 0000000000..2bf70f5b10 --- /dev/null +++ b/docs/source/api/v5/federations_id_deliveryservices.rst @@ -0,0 +1,174 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-federations-id-deliveryservices: + +*************************************** +``federations/{{ID}}/deliveryservices`` +*************************************** + +``GET`` +======= +Retrieves :term:`Delivery Services` assigned to a :term:`Federation`. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: FEDERATION:READ, DELIVERY-SERVICE:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+--------------------------------------------------------------------+ + | Name | Description | + +======+====================================================================+ + | ID | The integral, unique identifier for the federation to be inspected | + +------+--------------------------------------------------------------------+ + +.. table:: Request Query Parameters + + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +===========+==========+======================================================================================================================================+ + | dsID | no | Show only the :term:`Delivery Service` identified by this integral, unique identifier | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | orderby | no | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` | + | | | array | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long and the first page is 1. | + | | | If ``offset`` was defined, this query parameter has no effect. ``limit`` must be defined to make use of ``page``. | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/federations/1/deliveryservices HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.62.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:cdn: The CDN to which this :term:`Delivery Service` Belongs +:id: The integral, unique identifier for the :term:`Delivery Service` +:type: The routing type used by this :term:`Delivery Service` +:xmlId: The 'xml_id' which uniquely identifies this :term:`Delivery Service` + +.. 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 + access-control-allow-methods: POST,GET,OPTIONS,PUT,DELETE + access-control-allow-origin: * + cache-control: no-cache, no-store, max-age=0, must-revalidate + content-type: application/json + date: Wed, 05 Dec 2018 00:44:13 GMT + X-Server-Name: traffic_ops_golang/ + set-cookie: mojolicious=...; expires=Wed, 05 Dec 2018 04:44:13 GMT; path=/; HttpOnly + vary: Accept-Encoding + whole-content-sha512: 7Y9Q/qHeXfbjJduvucRCR85wf4VRfyYhlK59sNRkzIJuwnsMhFcEfYfNqrvELwfexOum/VEX2f/1oa+I/edGfw== + content-length: 74 + + { "response": [ + { + "xmlId": "demo1", + "cdn": "CDN-in-a-Box", + "type": "HTTP", + "id": 1 + } + ]} + +``POST`` +======== +Assigns one or more :term:`Delivery Services` to a federation. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: FEDERATION:UPDATE, DELIVERY-SERVICE:UPDATE, FEDERATION:READ, DELIVERY-SERVICE:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+--------------------------------------------------------------------+ + | Name | Description | + +======+====================================================================+ + | ID | The integral, unique identifier for the federation to be inspected | + +------+--------------------------------------------------------------------+ + +:dsIds: An array of integral, unique identifiers for :term:`Delivery Services` which will be assigned to this federation +:replace: An optional boolean (default: ``false``) which, if ``true``, will cause any conflicting assignments already in place to be overridden by this request + + .. note:: If ``replace`` is not given (and/or not ``true``), then any conflicts with existing assignments will cause the entire operation to fail. + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/federations/1/deliveryservices HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.62.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 32 + Content-Type: application/json + + { + "dsIds": [1], + "replace": true + } + +Response Structure +------------------ +:dsIds: An array of integral, unique identifiers for :term:`Delivery Services` which are now assigned to this federation +:replace: An optional boolean (default: ``false``) which, if ``true``, means any conflicting assignments already in place were overridden by this request + +.. 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: rVd0nx8G3bRI8ub1zw6FTdmwQ7jer4zoqzOZf5tC1ckrR0HEIOH1Azdcmvv0FVE5I0omcHVnrYbzab7tUtmnog== + x-server-name: traffic_ops_golang/ + content-length: 137 + date: Wed, 05 Dec 2018 00:34:06 GMT + + { "alerts": [ + { + "text": "1 delivery service(s) were assigned to the federation 1", + "level": "success" + } + ], + "response": { + "dsIds": [ + 1 + ], + "replace": true + }} diff --git a/docs/source/api/v5/federations_id_deliveryservices_id.rst b/docs/source/api/v5/federations_id_deliveryservices_id.rst new file mode 100644 index 0000000000..88154e5cf0 --- /dev/null +++ b/docs/source/api/v5/federations_id_deliveryservices_id.rst @@ -0,0 +1,76 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-federations-id-deliveryservices-id: + +************************************************ +``federations/{{ID}}/deliveryservices/{{dsID}}`` +************************************************ + +``DELETE`` +========== +Removes a :term:`Delivery Service` from a federation. A :term:`Delivery Service` cannot be removed from a federation if it is the only :term:`Delivery Service` assigned to said federation + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: FEDERATION:UPDATE, DELIVERY-SERVICE:UPDATE, FEDERATION:READ, DELIVERY-SERVICE:READ +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+----------------------------------------------------------------------------------------------------------------------------------+ + | Name | Description | + +======+==================================================================================================================================+ + | ID | The integral, unique identifier of the federation from which the :term:`Delivery Service` identified by ``dsID`` will be removed | + +------+----------------------------------------------------------------------------------------------------------------------------------+ + | dsID | The integral, unique identifier of the :term:`Delivery Service` which will be removed from the federation identified by ``ID`` | + +------+----------------------------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/federations/1/deliveryservices/1 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.62.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + access-control-allow-credentials: true + access-control-allow-headers: Origin, X-Requested-With, Content-Type, Accept + access-control-allow-methods: POST,GET,OPTIONS,PUT,DELETE + access-control-allow-origin: * + cache-control: no-cache, no-store, max-age=0, must-revalidate + content-type: application/json + date: Wed, 05 Dec 2018 01:06:51 GMT + X-Server-Name: traffic_ops_golang/ + set-cookie: mojolicious=...; expires=Wed, 05 Dec 2018 05:06:51 GMT; path=/; HttpOnly + vary: Accept-Encoding + whole-content-sha512: NqAZuZYlF1UWOaazbj/j4gWX7ye0kGGakRRFEkK6ShxqXvCxE0dCTyu75qiLPN2wSgr3FGQnp2Sq345sE7In9g== + content-length: 98 + + { "alerts": [ + { + "level": "success", + "text": "federation deliveryservice was deleted." + } + ]} diff --git a/docs/source/api/v5/federations_id_federation_resolvers.rst b/docs/source/api/v5/federations_id_federation_resolvers.rst new file mode 100644 index 0000000000..9847cbbb97 --- /dev/null +++ b/docs/source/api/v5/federations_id_federation_resolvers.rst @@ -0,0 +1,159 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-federations-id-federation_resolvers: + +******************************************* +``federations/{{ID}}/federation_resolvers`` +******************************************* + +``GET`` +======= +Retrieves federation resolvers assigned to a federation. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: FEDERATION:READ, FEDERATION-RESOLVER:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+------------------------------------------------------------------------------------------+ + | Name | Description | + +======+==========================================================================================+ + | ID | The integral, unique identifier for the federation for which resolvers will be retrieved | + +------+------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/federations/1/federation_resolvers HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.62.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:id: The integral, unique identifier of this federation resolver +:ipAddress: The IP address of the federation resolver - may be IPv4 or IPv6 +:type: The type of resolver - one of: + + RESOLVE4 + This resolver is for IPv4 addresses (and ``ipAddress`` is IPv4) + RESOLVE6 + This resolver is for IPv6 addresses (and ``ipAddress`` is IPv6) + +.. 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 + access-control-allow-methods: POST,GET,OPTIONS,PUT,DELETE + access-control-allow-origin: * + cache-control: no-cache, no-store, max-age=0, must-revalidate + content-type: application/json + date: Wed, 05 Dec 2018 00:49:50 GMT + X-Server-Name: traffic_ops_golang/ + set-cookie: mojolicious=...; expires=Wed, 05 Dec 2018 04:49:50 GMT; path=/; HttpOnly + vary: Accept-Encoding + whole-content-sha512: csC18kE3YjiILHP1wmJg7V4h/XWY8HUMKyPuZWnde2g7HJ4gTY51HfjCSqhyKvIJQ8Rl7uEqshF3Ey6xIMOX4A== + content-length: 63 + + { "response": [ + { + "ipAddress": "0.0.0.0", + "type": "RESOLVE4", + "id": 1 + } + ]} + +``POST`` +======== +Assigns one or more resolvers to a federation. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: FEDERATION:UPDATE, FEDERATION:READ, FEDERATION-RESOLVER:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+------------------------------------------------------------------------------------------+ + | Name | Description | + +======+==========================================================================================+ + | ID | The integral, unique identifier for the federation for which resolvers will be retrieved | + +------+------------------------------------------------------------------------------------------+ + +:fedResolverIds: An array of integral, unique identifiers for federation resolvers +:replace: An optional boolean (default: ``false``) which, if ``true``, will cause any conflicting assignments already in place to be overridden by this request + + .. note:: If ``replace`` is not given (and/or not ``true``), then any conflicts with existing assignments will cause the entire operation to fail. + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/federations/1/federation_resolvers HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.62.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 41 + Content-Type: application/json + + { + "fedResolverIds": [1], + "replace": true + } + +Response Structure +------------------ +:fedResolverIds: An array of integral, unique identifiers for federation resolvers +:replace: An optionally-present boolean (default: ``false``) which, if ``true``, any conflicting assignments already in place were overridden by this request + +.. 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 + access-control-allow-methods: POST,GET,OPTIONS,PUT,DELETE + access-control-allow-origin: * + cache-control: no-cache, no-store, max-age=0, must-revalidate + content-type: application/json + date: Wed, 05 Dec 2018 00:47:47 GMT + X-Server-Name: traffic_ops_golang/ + set-cookie: mojolicious=...; expires=Wed, 05 Dec 2018 04:47:47 GMT; path=/; HttpOnly + vary: Accept-Encoding + whole-content-sha512: +JDcRByS3HO6pMg3Gzkvn0w7/v5oRul9e+RxyFIOKJKNHOkZILyQBS+PJpxDeCgwI19+0poW5dyHPPR9SwbNCA== + content-length: 148 + + { "alerts": [ + { + "level": "success", + "text": "1 resolver(s) were assigned to the test.quest. federation" + } + ], + "response": { + "replace": true, + "fedResolverIds": [ + 1 + ] + }} diff --git a/docs/source/api/v5/federations_id_users.rst b/docs/source/api/v5/federations_id_users.rst new file mode 100644 index 0000000000..9eccda0d31 --- /dev/null +++ b/docs/source/api/v5/federations_id_users.rst @@ -0,0 +1,166 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-federations-id-users: + +**************************** +``federations/{{ID}}/users`` +**************************** + +``GET`` +======= +Retrieves users assigned to a federation. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: FEDERATION:READ, USER:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+-------------------------------------------------------------------------------------+ + | Name | Description | + +======+=====================================================================================+ + | ID | The integral, unique identifier of the federation for which users will be retrieved | + +------+-------------------------------------------------------------------------------------+ + +.. table:: Request Query Parameters + + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +===========+==========+======================================================================================================================================+ + | userID | no | Show only the user that has this integral, unique identifier | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | role | no | Show only the users that have this role | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | orderby | no | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` | + | | | array | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long and the first page is 1. | + | | | If ``offset`` was defined, this query parameter has no effect. ``limit`` must be defined to make use of ``page``. | + +-----------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + +Response Structure +------------------ +:company: The company to which the user belongs +:email: The user's email address +:fullName: The user's full name +:id: An integral, unique identifier for the user +:role: The user's highest role +:username: The user's short "username" + +.. 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 + access-control-allow-methods: POST,GET,OPTIONS,PUT,DELETE + access-control-allow-origin: * + cache-control: no-cache, no-store, max-age=0, must-revalidate + content-type: application/json + date: Wed, 05 Dec 2018 00:31:34 GMT + X-Server-Name: traffic_ops_golang/ + set-cookie: mojolicious=...; expires=Wed, 05 Dec 2018 04:31:34 GMT; path=/; HttpOnly + vary: Accept-Encoding + whole-content-sha512: eQQoF2xlbK2I2oTja7zrt/FlkLzCgwpU2zb2+rmIjHbHJ3MnmsSczSamIAAyTzs5gDaqcuUX1G35ZB8d7Bj82g== + content-length: 101 + + { "response": [ + { + "fullName": null, + "email": null, + "id": 2, + "role": "admin", + "company": null, + "username": "admin" + } + ]} + + +``POST`` +======== +Assigns one or more users to a federation. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: FEDERATION:UPDATE, USER:READ, FEDERATION:READ +:Response Type: Object + +Request Structure +----------------- +:userIds: An array of integral, unique identifiers for users which will be assigned to this federation +:replace: An optional boolean (default: ``false``) which, if ``true``, will cause any conflicting assignments already in place to be overridden by this request + + .. note:: If ``replace`` is not given (and/or not ``true``), then any conflicts with existing assignments will cause the entire operation to fail. + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/federations/1/users HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.62.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 34 + Content-Type: application/json + + { + "userIds": [2], + "replace": true + } + +Response Structure +------------------ +:userIds: An array of integral, unique identifiers for users which have been assigned to this federation +:replace: An optional boolean (default: ``false``) which, if ``true``, caused any conflicting assignments already in place to be overridden by this request + +.. 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 + access-control-allow-methods: POST,GET,OPTIONS,PUT,DELETE + access-control-allow-origin: * + cache-control: no-cache, no-store, max-age=0, must-revalidate + content-type: application/json + date: Wed, 05 Dec 2018 00:29:19 GMT + X-Server-Name: traffic_ops_golang/ + set-cookie: mojolicious=...; expires=Wed, 05 Dec 2018 04:29:19 GMT; path=/; HttpOnly + vary: Accept-Encoding + whole-content-sha512: MvPmgOAs58aSOGvh+iEilflgOexbaexg+qE2IPrQZX0H4iSX4JvEys9adbGE9a9yaLj9uUMxg77N6ZyDhVqsbQ== + content-length: 137 + + { "alerts": [ + { + "level": "success", + "text": "1 user(s) were assigned to the test.quest. federation" + } + ], + "response": { + "userIds": [ + 2 + ], + "replace": true + }} diff --git a/docs/source/api/v5/federations_id_users_id.rst b/docs/source/api/v5/federations_id_users_id.rst new file mode 100644 index 0000000000..4df1bff953 --- /dev/null +++ b/docs/source/api/v5/federations_id_users_id.rst @@ -0,0 +1,76 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-federations-id-users-id: + +*************************************** +``federations/{{ID}}/users/{{userID}}`` +*************************************** + +``DELETE`` +========== +Removes a user from a federation. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: FEDERATION:UPDATE, FEDERATION:READ, USER:READ +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Path Parameters + + +--------+----------------------------------------------------------------------------------------------------------------+ + | Name | Description | + +========+================================================================================================================+ + | ID | An integral, unique identifier for the federation from which the user identified by ``userID`` will be removed | + +--------+----------------------------------------------------------------------------------------------------------------+ + | userID | An integral, unique identifier for the user who will be removed from the federation identified by ``ID`` | + +--------+----------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Structure + + DELETE /api/5.0/federations/1/users/2 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.62.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + access-control-allow-credentials: true + access-control-allow-headers: Origin, X-Requested-With, Content-Type, Accept + access-control-allow-methods: POST,GET,OPTIONS,PUT,DELETE + access-control-allow-origin: * + cache-control: no-cache, no-store, max-age=0, must-revalidate + content-type: application/json + date: Wed, 05 Dec 2018 01:14:04 GMT + X-Server-Name: traffic_ops_golang/ + set-cookie: mojolicious=...; expires=Wed, 05 Dec 2018 05:14:04 GMT; path=/; HttpOnly + vary: Accept-Encoding + whole-content-sha512: xdF6l7jdd2t8au6lh4pFtDqYxTfehzke2aDBuytL7I74hK9KCT7ssLuYbfvD8ejdqqF3+jiBiFk7neQ8c4vVUQ== + content-length: 93 + + { "alerts": [ + { + "level": "success", + "text": "Removed user [ admin ] from federation [ foo.bar. ]" + } + ]} diff --git a/docs/source/api/v5/isos.rst b/docs/source/api/v5/isos.rst new file mode 100644 index 0000000000..4b04d62cdd --- /dev/null +++ b/docs/source/api/v5/isos.rst @@ -0,0 +1,109 @@ +.. +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-isos: + +******** +``isos`` +******** + +``POST`` +======== +Generates an ISO from the requested ISO source. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: ISO:GENERATE, ISO:READ +:Response Type: undefined - ISO image as a streaming download + +Request Structure +----------------- +:dhcp: A string that specifies whether the generated system image will use DHCP IP address leasing; one of: + + yes + DHCP will be used, and other network configuration keys need not be present in the request (and are ignored if they are) + no + DHCP will not be used, and the desired network configuration **must** be specified manually in the request body + +:disk: An optional string that names the block device (under ``/dev/``) used for the boot media, e.g. "sda" +:domainName: The domain part of the system image's Fully Qualified Domain Name (FQDN) +:hostName: The host name part of the system image's FQDN +:interfaceMtu: A number that specifies the Maximum Transmission Unit (MTU) for the system image's network interface card - the only valid values of which I'm aware are 1500 or 9000, and this should almost always just be 1500 +:interfaceName: An optional string naming the network interface to be used by the generated system image e.g. "bond0", "eth0", etc. If the special name "bond0" is used, an :abbr:`LACP (Link Aggregation Control Protocol)` binding configuration will be created and included in the system image + + .. seealso:: `The Link Aggregation Wikipedia page `_\ . + +:ip6Address: An optional string containing the IPv6 address of the generated system image +:ip6Gateway: An optional string specifying the IPv6 address of the generated system image's network gateway - this will be ignored if ``ipGateway`` is specified +:ipAddress: An optional\ [1]_ string containing the IP address of the generated system image +:ipGateway: An optional\ [1]_ string specifying the IP address of the generated system image's network gateway +:ipNetmask: An optional\ [1]_ string specifying the subnet mask of the generated system image +:osversionDir: The name of the directory containing the ISO source + + .. seealso:: :ref:`to-api-osversions` + +:rootPass: The password used by the generated system image's ``root`` user + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/isos HTTP/1.1 + Host: some.trafficops.host + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 334 + Content-Type: application/json + + { + "osversionDir": "centos72", + "hostName": "test", + "domainName": "quest", + "rootPass": "twelve", + "dhcp": "no", + "interfaceMtu": 1500, + "ipAddress": "1.3.3.7", + "ipNetmask": "255.255.255.255", + "ipGateway": "8.0.0.8", + "ip6Address": "1::3:3:7", + "ip6Gateway": "8::8", + "interfaceName": "eth0", + "disk": "hda" + } + +.. [1] This optional key is required if and only if ``dhcp`` is "no". + +Response Structure +------------------ +ISO image as a streaming download. + +.. 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: * + Connection: keep-alive + Content-Disposition: attachment; filename="test-centos72_centos72-netinstall.iso" + Content-Encoding: gzip + Content-Type: application/download + Date: Wed, 05 Feb 2020 21:59:15 GMT + Set-Cookie: mojolicious=...; Path=/; Expires=Wed, 05 Feb 2020 22:59:11 GMT; Max-Age=3600; HttpOnly + Transfer-Encoding: chunked + Whole-Content-sha512: sLSVQGrLCQ4hGQhv2reragQHWNi2aKMcz2c/HMAH45tLcZ1LenPyOzWRcRfHUNbV4PEEKOoiTfwE2HlA+WtRIQ== + X-Server-Name: traffic_ops_golang/ diff --git a/docs/source/api/v5/jobs.rst b/docs/source/api/v5/jobs.rst new file mode 100644 index 0000000000..5fc8aba2e9 --- /dev/null +++ b/docs/source/api/v5/jobs.rst @@ -0,0 +1,358 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-jobs: + +******** +``jobs`` +******** + +``GET`` +======= +Retrieve :term:`Content Invalidation Jobs`. + +:Auth. Required: Yes +:Roles Required: None\ [#tenancy]_ +:Permissions Required: JOB:READ, DELIVERY-SERVICE:READ\ [#tenancy]_ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +----------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +======================+==========+======================================================================================================================================+ + | assetUrl | no | Return only :term:`Content Invalidation Jobs` with this :ref:`job-asset-url` | + +----------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | cdn | no | Return only :term:`Content Invalidation Jobs` for :term:`Delivery Services` within the CDN with this name | + +----------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | createdBy | no | Return only :term:`Content Invalidation Jobs` that were created by the user with this username | + +----------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | deliveryService | no | Return only :term:`Content Invalidation Jobs` that operate on the :term:`Delivery Service` with this :ref:`ds-xmlid` | + +----------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | dsId | no | Return only :term:`Content Invalidation Jobs` pending on the :term:`Delivery Service` identified by this integral, unique identifier | + +----------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | id | no | Return only the single :term:`Content Invalidation Job` with this :ref:`job-id` | + +----------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | maxRevalDurationDays | no | Return only :term:`Content Invalidation Jobs` with a :ref:`job-start-time` that is within the window defined by the | + | | | ``maxRevalDurationDays`` :term:`Parameter` in :ref:`the-global-profile` | + +----------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | userId | no | Return only :term:`Content Invalidation Jobs` created by the user identified by this integral, unique identifier | + +----------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------+ + + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/jobs?id=1&dsId=1&userId=2 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: python-requests/2.20.1 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + +Response Structure +------------------ +:assetUrl: A regular expression - matching URLs will be operated upon according to ``keyword`` +:createdBy: The username of the user who initiated the job +:deliveryService: The :ref:`ds-xmlid` of the :term:`Delivery Service` on which this job operates +:id: An integral, unique identifier for this job +:keyword: A keyword that represents the operation being performed by the job: + + PURGE + This job will prevent caching of URLs matching the ``assetUrl`` until it is removed (or its Time to Live expires) + +:parameters: A string containing key/value pairs representing parameters associated with the job - currently only uses Time to Live e.g. ``"TTL:48h"`` +:startTime: The date and time at which the job began, in a non-standard format + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Content-Encoding: gzip + Content-Type: application/json + Permissions-Policy: interest-cohort=() + Set-Cookie: mojolicious=... + Vary: Accept-Encoding + X-Server-Name: traffic_ops_golang/ + Date: Fri, 12 Nov 2021 19:30:36 GMT + Content-Length: 206 + + { "response": [{ + "id": 1, + "assetUrl": "http://origin.infra.ciab.test/.+", + "createdBy": "admin", + "deliveryService": "demo1", + "ttlHours": 72, + "invalidationType": "REFETCH", + "startTime": "2021-11-09T01:02:03Z" + }]} + + + +``POST`` +======== +Creates a new :term:`Content Invalidation Jobs`. + +.. caution:: Creating a :term:`Content Invalidation Job` immediately triggers a CDN-wide revalidation update. In the case that the global :term:`Parameter` ``use_reval_pending`` has a value of exactly ``"0"``, this will instead trigger a CDN-wide "Queue Updates". This means that :term:`Content Invalidation Jobs` become active **immediately** at their ``startTime`` - unlike most other configuration changes they do not wait for a :term:`Snapshot` or a "Queue Updates". Furthermore, if the global :term:`Parameter` ``use_reval_pending`` *is* ``"0"``, this will cause all pending configuration changes to propagate to all :term:`cache servers` in the CDN. Take care when using this endpoint. + +:Auth. Required: Yes +:Roles Required: "operations" or "admin"\ [#tenancy]_ +:Permissions Required: JOB:CREATE, JOB:READ, DELIVERY-SERVICE:READ, DELIVERY-SERVICE:UPDATE\ [#tenancy]_ +:Response Type: Object + +Request Structure +----------------- +:deliveryService: The :ref:`job-ds` +:invalidationType: The :ref:`job-invalidation-type` +:regex: The :ref:`job-regex` +:startTime: The :ref:`job-start-time` +:ttl: The :ref:`job-ttl` + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/jobs HTTP/1.1 + User-Agent: python-requests/2.25.1 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Transfer-Encoding: chunked + Content-Type: application/json + + { + "deliveryService": "demo1", + "invalidationType": "REFRESH", + "regex": "/.+", + "startTime": "2021-11-09T01:02:03Z", + "ttlHours": 72 + } + + +Response Structure +------------------ +:assetUrl: The :ref:`job-asset-url` +:createdBy: The :ref:`job-created-by` +:deliveryService: The :ref:`job-ds` +:id: The :ref:`job-id`. +:invalidationType: The :ref:`job-invalidation-type` +:ttlHours: The :ref:`job-ttl` +:startTime: The :ref:`job-start-time` + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Content-Encoding: gzip + Content-Type: application/json + Location: https://localhost:6443/api/5.0/jobs?id=1 + Permissions-Policy: interest-cohort=() + Set-Cookie: mojolicious=... + Vary: Accept-Encoding + X-Server-Name: traffic_ops_golang/ + Date: Mon, 08 Nov 2021 15:44:46 GMT + Content-Length: 265 + + { + "alerts": [ + { + "text": "Invalidation (REFRESH) request created for http://origin.infra.ciab.test/.+, start:2021-11-09 01:02:03 +0000 UTC end 2021-11-12 01:02:03 +0000 UTC", + "level": "success" + } + ], + "response": { + "id": 1, + "assetUrl": "http://origin.infra.ciab.test/.+", + "createdBy": "admin", + "deliveryService": "demo1", + "ttlHours": 72, + "invalidationType": "REFRESH", + "startTime": "2021-11-09T01:02:03Z" + } + } + + + +``PUT`` +======= +Replaces an existing :term:`Content Invalidation Job` with a new one provided in the request. This method of editing a :term:`Content Invalidation Job` does not prevent the requesting user from changing fields that normally only have one value. Use with care. + +.. caution:: Modifying a :term:`Content Invalidation Job` immediately triggers a CDN-wide revalidation update. In the case that the global :term:`Parameter` ``use_reval_pending`` has a value of exactly ``"0"``, this will instead trigger a CDN-wide "Queue Updates". This means that :term:`Content Invalidation Jobs` become active **immediately** at their ``startTime`` - unlike most other configuration changes they do not wait for a :term:`Snapshot` or a "Queue Updates". Furthermore, if the global :term:`Parameter` ``use_reval_pending`` *is* ``"0"``, this will cause all pending configuration changes to propagate to all :term:`cache servers` in the CDN. Take care when using this endpoint. + +:Auth. Required: Yes +:Roles Required: "operations" or "admin"\ [#tenancy]_ +:Permissions Required: JOB:UPDATE, DELIVERY-SERVICE:UPDATE, JOB:READ, DELIVERY-SERVICE:READ\ [#tenancy]_ +:Response Type: Object + +Request Structure +----------------- +.. table:: Query Parameters + + +------+----------+----------------------------------------------------------------------------------------+ + | Name | Required | Description | + +======+==========+========================================================================================+ + | id | yes | The integral, unique identifier of the :term:`Content Invalidation Job` being modified | + +------+----------+----------------------------------------------------------------------------------------+ + +:assetUrl: The :ref:`job-asset-url` - the scheme and authority parts of the regular expression cannot be changed +:createdBy: The :ref:`job-created-by`\ [#immutable]_ +:deliveryService: The :ref:`job-ds`\ [#immutable]_ +:id: The :ref:`job-id`\ [#immutable]_ +:invalidationType: The :ref:`job-invalidation-type` +:ttlHours: The :ref:`job-ttl` +:startTime: The :ref:`job-start-time` + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/jobs?id=1 HTTP/1.1 + User-Agent: python-requests/2.25.1 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 191 + + { + "assetUrl": "http://origin.infra.ciab.test/.+", + "createdBy": "admin", + "deliveryService": "demo1", + "id": 1, + "invalidationType": "REFETCH", + "startTime": "2021-11-09T01:02:03Z", + "ttlHours": 72 + } + + +Response Structure +------------------ +:assetUrl: The :ref:`job-asset-url` +:createdBy: The :ref:`job-created-by` +:deliveryService: The :ref:`job-ds` +:id: The :ref:`job-id` +:invalidationType: The :ref:`job-invalidation-type` +:ttlHours: The :ref:`job-ttl` +:startTime: The :ref:`job-start-time` + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Content-Encoding: gzip + Content-Type: application/json + Permissions-Policy: interest-cohort=() + Set-Cookie: mojolicious=... + Vary: Accept-Encoding + X-Server-Name: traffic_ops_golang/ + Date: Mon, 08 Nov 2021 16:43:35 GMT + Content-Length: 266 + + { "alerts": [ + { + "text": "Invalidation request created for http://origin.infra.ciab.test/.+, start: 2021-11-09 01:02:03 +0000 UTC end: 2021-11-12 01:02:03 +0000 UTC invalidation type: REFETCH", + "level": "success" + } + ], + "response": { + "assetUrl": "http://origin.infra.ciab.test/.+", + "createdBy": "admin", + "deliveryService": "demo1", + "id": 1, + "invalidationType": "REFETCH", + "startTime": "2021-11-09T01:02:03Z", + "ttlHours": 72 + }} + + +``DELETE`` +========== +Deletes a :term:`Content Invalidation Job`. + +.. tip:: :term:`Content Invalidation Jobs` that have passed their :abbr:`TTL (Time To Live)` are not automatically deleted - for record-keeping purposes - so use this to clean up old jobs that are no longer useful. + +.. caution:: Deleting a :term:`Content Invalidation Job` immediately triggers a CDN-wide revalidation update. In the case that the global :term:`Parameter` ``use_reval_pending`` has a value of exactly ``"0"``, this will instead trigger a CDN-wide "Queue Updates". This means that :term:`Content Invalidation Jobs` become active **immediately** at their ``startTime`` - unlike most other configuration changes they do not wait for a :term:`Snapshot` or a "Queue Updates". Furthermore, if the global :term:`Parameter` ``use_reval_pending`` *is* ``"0"``, this will cause all pending configuration changes to propagate to all :term:`cache servers` in the CDN. Take care when using this endpoint. + +:Auth. Required: Yes +:Roles Required: "operations" or "admin"\ [#tenancy]_ +:Permissions Required: JOB:DELETE, JOB:READ, DELIVERY-SERVICE:UPDATE, DELIVERY-SERVICE:READ\ [#tenancy]_ +:Response Type: Object + +Request Structure +----------------- +.. table:: Query Parameters + + +------+----------+----------------------------------------------------------------------------------------+ + | Name | Required | Description | + +======+==========+========================================================================================+ + | id | yes | The integral, unique identifier of the :term:`Content Invalidation Job` being modified | + +------+----------+----------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/jobs?id=1 HTTP/1.1 + User-Agent: python-requests/2.25.1 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 0 + + +Response Structure +------------------ +:assetUrl: The :ref:`job-asset-url` of the deleted :term:`Content Invalidation Job` +:createdBy: The :ref:`job-created-by` of the deleted :term:`Content Invalidation Job` +:deliveryService: The :ref:`job-ds` of the deleted :term:`Content Invalidation Job` +:id: The :ref:`job-id`. of the deleted :term:`Content Invalidation Job` +:invalidationType: The :ref:`job-invalidation-type` of the deleted :term:`Content Invalidation Job` +:ttlHours: The :ref:`job-ttl` of the deleted :term:`Content Invalidation Job` +:startTime: The :ref:`job-start-time` of the deleted :term:`Content Invalidation Job` + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Content-Encoding: gzip + Content-Type: application/json + Permissions-Policy: interest-cohort=() + Set-Cookie: mojolicious=... + Vary: Accept-Encoding + X-Server-Name: traffic_ops_golang/ + Date: Mon, 08 Nov 2021 16:54:32 GMT + Content-Length: 230 + + { "alerts": [ + { + "text": "Content invalidation job was deleted", + "level": "success" + } + ], + "response": { + "assetUrl": "http://origin.infra.ciab.test/.+", + "createdBy": "admin", + "deliveryService": "demo1", + "id": 1, + "invalidationType": "REFETCH", + "startTime": "2021-11-09T01:02:03Z", + "ttlHours": 72 + }} + + +.. [#tenancy] When viewing :term:`Content Invalidation Jobs`, only those jobs that operate on a :term:`Delivery Service` visible to the requesting user's :term:`Tenant` will be returned. Likewise, creating a new :term:`Content Invalidation Jobs` requires that the target :term:`Delivery Service` is modifiable by the requesting user's :term:`Tenant`. However, when modifying or deleting an existing :term:`Content Invalidation Jobs`, the operation can be completed if and only if the requesting user's :term:`Tenant` is the same as the job's :term:`Delivery Service`'s :term:`Tenant` or a descendant thereof, **and** if the requesting user's :term:`Tenant` is the same as the :term:`Tenant` of the *user who initially created the job* or a descendant thereof. +.. [#immutable] This field must exist, but it must *not* be different than the same field of the existing job (i.e. as seen in a GET_ response). That is, this cannot be changed. diff --git a/docs/source/api/v5/letsencrypt_autorenew.rst b/docs/source/api/v5/letsencrypt_autorenew.rst new file mode 100644 index 0000000000..d79a3673f6 --- /dev/null +++ b/docs/source/api/v5/letsencrypt_autorenew.rst @@ -0,0 +1,54 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-letsencrypt-autorenew: + +************************* +``letsencrypt/autorenew`` +************************* + +``POST`` +======== +Generates an SSL certificate and private key using Let's Encrypt for a :term:`Delivery Service` + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: DS-SECURITY-KEY:CREATE, DELIVERY-SERVICE:READ, DELIVERY-SERVICE:UPDATE +:Response Type: Object + +Request Structure +----------------- +No parameters available + + +Response Structure +------------------ + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Content-Type: application/json + + { "alerts": [ + { + "text": "This endpoint is deprecated, please use letsencrypt/autorenew instead", + "level": "warning" + }, + { + "text": "Beginning async call to renew certificates. This may take a few minutes.", + "level": "success" + } + ]} diff --git a/docs/source/api/v5/letsencrypt_dnsrecords.rst b/docs/source/api/v5/letsencrypt_dnsrecords.rst new file mode 100644 index 0000000000..6db008ab83 --- /dev/null +++ b/docs/source/api/v5/letsencrypt_dnsrecords.rst @@ -0,0 +1,67 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-letsencrypt-dnsrecord: + +************************** +``letsencrypt/dnsrecords`` +************************** + +``GET`` +======== +Gets DNS challenge records for Let's Encrypt DNS challenge for a specified :abbr:`FQDN (Fully Qualified Domain Name)`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: DS-SECURITY-KEY:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +------+----------+--------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +======+==========+==================================================================================================+ + | fqdn | no | Return only DNS challenge records for the specified :abbr:`FQDN (Fully Qualified Domain Name)` | + +------+----------+--------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/letsencrypt/dnsrecord?fqdn=_acme-challenge.demo1.example.com. HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + + +Response Structure +------------------ +:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` for the TXT record location to complete the DNS challenge +:record: The record provided by Let's Encrypt to complete the DNS challenge + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Content-Type: application/json + + { "response": [ + { + "fqdn":"_acme-challenge.demo1.example.com.", + "record":"testRecord" + } + ]} diff --git a/docs/source/api/v5/logs.rst b/docs/source/api/v5/logs.rst new file mode 100644 index 0000000000..84f7afe912 --- /dev/null +++ b/docs/source/api/v5/logs.rst @@ -0,0 +1,114 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. +.. _to-api-logs: + +******** +``logs`` +******** + +.. note:: This endpoint's responses will contain a cookie (``last_seen_log``) that is used by :ref:`to-api-logs-newcount` to determine the time of last access. Be sure your client uses cookies properly if you intend to use :ref:`to-api-logs-newcount` in concert with this endpoint! + +``GET`` +======= +Fetches a list of changes that have been made to the Traffic Control system + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: LOG:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-----------+----------+-------------------------------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +===========+==========+=====================================================================================================================================+ + | days | no | An integer number of days of change logs to return | + +-----------+----------+-------------------------------------------------------------------------------------------------------------------------------------+ + | limit | no | The number of records to which to limit the response, by default there is no limit applied | + +-----------+----------+-------------------------------------------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +-----------+----------+-------------------------------------------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long and the first page is 1.| + | | | If ``offset`` was defined, this query parameter has no effect. ``limit`` must be defined to make use of ``page``. | + +-----------+----------+-------------------------------------------------------------------------------------------------------------------------------------+ + | username | no | A name to which to limit the response too | + +-----------+----------+-------------------------------------------------------------------------------------------------------------------------------------+ + +.. versionadded:: ATCv6 + The ``username``, ``page``, ``offset`` query parameters were added to this in endpoint across across all API versions in :abbr:`ATC (Apache Traffic Control)` version 6.0.0. + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/logs?days=1&limit=2&username=admin HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:id: Integral, unique identifier for the Log entry +:lastUpdated: Date and time at which the change was made, in :ref:`non-rfc-datetime` +:level: Log categories for each entry, e.g. 'UICHANGE', 'OPER', 'APICHANGE' +:message: Log detail about what occurred +:ticketNum: Optional field to cross reference with any bug tracking systems +:user: Name of the user who made the change + +.. 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 + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Cache-Control: no-cache, no-store, max-age=0, must-revalidate + Content-Type: application/json + Date: Thu, 15 Nov 2018 15:11:38 GMT + X-Server-Name: traffic_ops_golang/ + Set-Cookie: last_seen_log="2018-11-15% 15:11:38"; path=/; Max-Age=604800 + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: 40dV+azaZ3b6F30y6YHVbV3H2a3ekZrdoxICupwaxQnj62pwYfb7YCM7Qhe3OAItmB77Tbg9INy27ymaz3hr9A== + Content-Length: 357 + + { "response": [ + { + "ticketNum": null, + "level": "APICHANGE", + "lastUpdated": "2018-11-14 21:40:06.493975+00", + "user": "admin", + "id": 444, + "message": "User [ test ] unlinked from deliveryservice [ 1 | demo1 ]." + }, + { + "ticketNum": null, + "level": "APICHANGE", + "lastUpdated": "2018-11-14 21:37:30.707571+00", + "user": "admin", + "id": 443, + "message": "1 delivery services were assigned to test" + }], + "summary": { + "count": 2 + } + } + +Summary Fields +"""""""""""""" +The ``summary`` object returned by this method of this endpoint uses only the ``count`` :ref:`standard property `. diff --git a/docs/source/api/v5/logs_newcount.rst b/docs/source/api/v5/logs_newcount.rst new file mode 100644 index 0000000000..b74573f09d --- /dev/null +++ b/docs/source/api/v5/logs_newcount.rst @@ -0,0 +1,61 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + + +.. _to-api-logs-newcount: + +***************** +``logs/newcount`` +***************** + +``GET`` +======= +Gets the number of new changes made to the Traffic Control system - "new" being defined as the last time the client requested either :ref:`to-api-logs` + +.. note:: This endpoint's functionality is implemented by the :ref:`to-api-logs` endpoint's response setting cookies for the client to use when requesting _this_ endpoint. Take care that your client respects cookies! + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: LOG:READ +:Response Type: Object + +Request Structure +----------------- +No parameters available + +Response Structure +------------------ +:newLogcount: The integer number of new changes + +.. 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 + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Cache-Control: no-cache, no-store, max-age=0, must-revalidate + Content-Type: application/json + Date: Thu, 15 Nov 2018 15:17:35 GMT + X-Server-Name: traffic_ops_golang/ + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: Ugdqe8GXKSOExphwbDX/Gli+2vBpubttbpfYMbJaCP7adox3MzmVRi2RxTDL5kwPewrcL1CO88zGITskhOsc9g== + Content-Length: 30 + + { "response": { + "newLogcount": 4 + }} diff --git a/docs/source/api/v5/multiple_server_capabilities.rst b/docs/source/api/v5/multiple_server_capabilities.rst new file mode 100644 index 0000000000..b631807553 --- /dev/null +++ b/docs/source/api/v5/multiple_server_capabilities.rst @@ -0,0 +1,81 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-multiple_server_capabilities: + +******************************** +``multiple_server_capabilities`` +******************************** + +``PUT`` +======== +Associates a list of :term:`Server Capability` to a server. The API call replaces all the server capabilities assigned to a server with the ones specified in the serverCapabilities field. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: SERVER:UPDATE, SERVER:READ, SERVER-CAPABILITY:READ +:Response Type: Object + +Request Structure +----------------- +:serverId: The integral, unique identifier of a server to be associated with a :term:`Server Capability` +:serverCapabilities: List of :term:`Server Capability`'s name to associate + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/multiple_server_capabilities/ HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 84 + Content-Type: application/json + + { + "serverId": 1, + "serverCapabilities": ["test", "disk"] + } + +Response Structure +------------------ +:serverId: The integral, unique identifier of the newly associated server +:serverCapabilities: List of :term:`Server Capability`'s name + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 8 Aug 2022 22:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: eQrl48zWids0kDpfCYmmtYMpegjnFxfOVvlBYxxLSfp7P7p6oWX4uiC+/Cfh2X9i3G+MQ36eH95gukJqOBOGbQ== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 08 Aug 2022 16:15:11 GMT + Content-Length: 157 + + { + "alerts": [{ + "text": "Multiple Server Capabilities assigned to a server", + "level": "success" + }], + "response": { + "serverId": 1, + "serverCapabilities": ["test", "disk"] + } + } diff --git a/docs/source/api/v5/oc_ci_configuration.rst b/docs/source/api/v5/oc_ci_configuration.rst new file mode 100644 index 0000000000..ad2127be01 --- /dev/null +++ b/docs/source/api/v5/oc_ci_configuration.rst @@ -0,0 +1,107 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-oc-fci-configuration: + +*********************** +``OC/CI/configuration`` +*********************** + +``PUT`` +======= +Triggers an asynchronous task to update the configuration for the :abbr:`uCDN (Upstream Content Delivery Network)` by adding the request to a queue to be reviewed later. This returns a 202 Accepted status and an endpoint to be used for status updates. + +.. note:: Users with the ``ICDN:UCDN-OVERRIDE`` permission will need to provide a "ucdn" query parameter to bypass the need for :abbr:`uCDN (Upstream Content Delivery Network)` information in the :abbr:`JWT (JSON Web Token)` and allow them to view all :abbr:`CDNi (Content Delivery Network Interconnect)` information. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: CDNI:UPDATE +:Response Type: Object + +Request Structure +----------------- +This requires authorization using a :abbr:`JWT (JSON Web Token)` provided by the :abbr:`dCDN (Downstream Content Delivery Network)` to identify the :abbr:`uCDN (Upstream Content Delivery Network)`. This token must include the following claims: + +.. table:: Required JWT claims + + +-----------------+--------------------------------------------------------------------------------------------------------------------+ + | Name | Description | + +=================+====================================================================================================================+ + | iss | Issuer claim as a string key for the :abbr:`uCDN (Upstream Content Delivery Network)` | + +-----------------+--------------------------------------------------------------------------------------------------------------------+ + | aud | Audience claim as a string key for the :abbr:`dCDN (Downstream Content Delivery Network)` | + +-----------------+--------------------------------------------------------------------------------------------------------------------+ + | exp | Expiration claim as the expiration date as a Unix epoch timestamp (in seconds) | + +-----------------+--------------------------------------------------------------------------------------------------------------------+ + +:type: A string of the type of metadata to follow. See :rfc:`8006` for possible values. Only a selection of these are supported. +:host: A string of the domain that the requested updates will change. +:metadata: An array of generic metadata objects that conform to :rfc:`8006`. +:generic-metadata-type: A string of the type of metadata to follow conforming to :rfc:`8006`. +:generic-metadata-value: An array of generic metadata value objects conforming to :rfc:`8006` and :abbr:`SVA (Streaming Video Alliance)` specifications. + +.. code-block:: http + :caption: Example /OC/CI/configuration Request + + PUT /api/5.0/oc/ci/configuration HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 181 + Content-Type: application/json + + { + "type": "MI.HostMetadata", + "host": "example.com", + "metadata": [ + { + "generic-metadata-type": "MI.RequestedCapacityLimits", + "generic-metadata-value": { + "requested-limits": [ + { + "limit-type": "egress", + "limit-value": 20000, + "footprints": [ + { + "footprint-type": "ipv4cidr", + "footprint-value": [ + "127.0.0.1", + "127.0.0.2" + ] + } + ] + } + ] + } + } + ] + } + +Response Structure +------------------ + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 202 Accepted + Content-Type: application/json + + { "alerts": [ + { + "text": "CDNi configuration update request received. Status updates can be found here: /api/5.0/async_status/1", + "level": "success" + } + ]} diff --git a/docs/source/api/v5/oc_ci_configuration_host.rst b/docs/source/api/v5/oc_ci_configuration_host.rst new file mode 100644 index 0000000000..48f1919341 --- /dev/null +++ b/docs/source/api/v5/oc_ci_configuration_host.rst @@ -0,0 +1,113 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-oc-fci-configuration-host: + +******************************** +``OC/CI/configuration/{{host}}`` +******************************** + +``PUT`` +======= +Triggers an asynchronous task to update the configuration for the :abbr:`uCDN (Upstream Content Delivery Network)` and the specified host by adding the request to a queue to be reviewed later. This returns a 202 Accepted status and an endpoint to be used for status updates. + +.. note:: Users with the ``ICDN:UCDN-OVERRIDE`` permission will need to provide a "ucdn" query parameter to bypass the need for :abbr:`uCDN (Upstream Content Delivery Network)` information in the :abbr:`JWT (JSON Web Token)` and allow them to view all :abbr:`CDNi (Content Delivery Network Interconnect)` information. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: CDNI:UPDATE +:Response Type: Object + +Request Structure +----------------- +This requires authorization using a :abbr:`JWT (JSON Web Token)` provided by the :abbr:`dCDN (Downstream Content Delivery Network)` to identify the :abbr:`uCDN (Upstream Content Delivery Network)`. This token must include the following claims: + +.. table:: Required JWT claims + + +-----------------+--------------------------------------------------------------------------------------------------------------------+ + | Name | Description | + +=================+====================================================================================================================+ + | iss | Issuer claim as a string key for the :abbr:`uCDN (Upstream Content Delivery Network)` | + +-----------------+--------------------------------------------------------------------------------------------------------------------+ + | aud | Audience claim as a string key for the :abbr:`dCDN (Downstream Content Delivery Network)` | + +-----------------+--------------------------------------------------------------------------------------------------------------------+ + | exp | Expiration claim as the expiration date as a Unix epoch timestamp (in seconds) | + +-----------------+--------------------------------------------------------------------------------------------------------------------+ + +.. table:: Request Path Parameters + + +-------+-----------------------------------------------------------------------------------+ + | Name | Description | + +=======+===================================================================================+ + | host | The text identifier for the host domain to be updated with the new configuration. | + +-------+-----------------------------------------------------------------------------------+ + +:type: A string of the type of metadata to follow. See :rfc:`8006` for possible values. Only a selection of these are supported. +:host-metadata: An array of generic metadata objects that conform to :rfc:`8006`. +:generic-metadata-type: A string of the type of metadata to follow conforming to :rfc:`8006`. +:generic-metadata-value: An array of generic metadata value objects conforming to :rfc:`8006` and :abbr:`SVA (Streaming Video Alliance)` specifications. + +.. code-block:: http + :caption: Example /OC/CI/configuration Request + + PUT /api/5.0/oc/ci/configuration/example.com HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 181 + Content-Type: application/json + + { + "type": "MI.HostMetadata", + "host-metadata": [ + { + "generic-metadata-type": "MI.RequestedCapacityLimits", + "generic-metadata-value": { + "requested-limits": [ + { + "limit-type": "egress", + "limit-value": 20000, + "footprints": [ + { + "footprint-type": "ipv4cidr", + "footprint-value": [ + "127.0.0.1", + "127.0.0.2" + ] + } + ] + } + ] + } + } + ] + } + +Response Structure +------------------ + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 202 Accepted + Content-Type: application/json + + { "alerts": [ + { + "text": "CDNi configuration update request received. Status updates can be found here: /api/5.0/async_status/1", + "level": "success" + } + ]} diff --git a/docs/source/api/v5/oc_ci_configuration_request_id_approved.rst b/docs/source/api/v5/oc_ci_configuration_request_id_approved.rst new file mode 100644 index 0000000000..7c4ab4cf71 --- /dev/null +++ b/docs/source/api/v5/oc_ci_configuration_request_id_approved.rst @@ -0,0 +1,60 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-oc-fci-configuration-request-id-approved: + +*************************************************** +``OC/CI/configuration/request/{{id}}/{{approved}}`` +*************************************************** + +``PUT`` +======= +Triggers an asynchronous task to update the configuration for the :abbr:`uCDN (Upstream Content Delivery Network)` and the specified host by adding the request to a queue to be reviewed later. This returns a 202 Accepted status and an endpoint to be used for status updates. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: CDNI-ADMIN:READ, CDNI-ADMIN:UPDATE +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +-----------+----------------------------------------------------------------------------------------+ + | Name | Description | + +===========+========================================================================================+ + | id | The integral identifier for the configuration update request to be approved or denied. | + +-----------+----------------------------------------------------------------------------------------+ + | approved | A boolean for whether to approve a configuration change request or not. | + +-----------+----------------------------------------------------------------------------------------+ + +Response Structure +------------------ + +.. code-block:: http + :caption: Response Example For Approved Change + + HTTP/1.1 200 OK + Content-Type: application/json + + { "response": "Successfully updated configuration." } + +.. code-block:: http + :caption: Response Example For Denied Change + + HTTP/1.1 200 OK + Content-Type: application/json + + { "response": "Successfully denied configuration update request." } diff --git a/docs/source/api/v5/oc_ci_configuration_requests.rst b/docs/source/api/v5/oc_ci_configuration_requests.rst new file mode 100644 index 0000000000..cab8c5e20f --- /dev/null +++ b/docs/source/api/v5/oc_ci_configuration_requests.rst @@ -0,0 +1,96 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-oc-ci-configuration_requests: + +******************************** +``OC/CI/configuration/requests`` +******************************** + +``GET`` +======= +Returns the requested updates for :abbr:`CDNi (Content Delivery Network Interconnect)` configurations. An optional ``id`` parameter will return only information for a specific request. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: CDNI-ADMIN:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +===========+==========+===============================================================================================================+ + | id | no | Return only the configuration requests identified by this integral, unique identifier | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + +Response Structure +------------------ +:id: An integral, unique identifier for the requested configuration updates. +:ucdn: The name of the :abbr:`uCDN (Upstream Content Delivery Network)` to which the requested changes apply. +:data: An array of generic :abbr:`FCI (Footprint and Capabilities Advertisement Interface)` base objects. +:host: The domain to which the requested changes apply. +:requestType: A string of the type of configuration update request. +:asyncStatusId: An integral, unique identifier for the associated asynchronous status. +:generic-metadata-type: A string of the type of metadata to follow conforming to :rfc:`8006`. +:generic-metadata-value: An array of generic metadata value objects conforming to :rfc:`8006` and :abbr:`SVA (Streaming Video Alliance)` specifications. +:footprints: An array of footprints impacted by this generic base object. + +.. note:: These are meant to be generic and therefore there is not much information in these documents. For further information please see :rfc:`8006`, :rfc:`8007`, :rfc:`8008`, and the :abbr:`SVA (Streaming Video Alliance)` documents titled `Footprint and Capabilities Interface: Open Caching API`, `Open Caching API Implementation Guidelines`, `Configuration Interface: Part 1 Specification - Overview & Architecture`, `Configuration Interface: Part 2 Specification – CDNi Metadata Model Extensions`, and `Configuration Interface: Part 3 Specification – Publishing Layer APIs`. + +.. code-block:: json + :caption: Example /OC/CI/configuration/requests Response + + { + "response": [ + { + "id": 1, + "ucdn": "ucdn1", + "data": [ + { + "generic-metadata-type": "MI.RequestedCapacityLimits", + "generic-metadata-value": { + "requested-limits": [ + { + "limit-type": "egress", + "limit-value": 232323, + "footprints": [ + { + "footprint-type": "ipv4cidr", + "footprint-value": [ + "127.0.0.1", + "127.0.0.2" + ] + }, + { + "footprint-type": "countrycode", + "footprint-value": [ + "us" + ] + } + ] + } + ] + } + } + ], + "host": "example.com", + "requestType": "hostConfigUpdate", + "asyncStatusId": 0 + } + ] + } diff --git a/docs/source/api/v5/oc_fci_advertisement.rst b/docs/source/api/v5/oc_fci_advertisement.rst new file mode 100644 index 0000000000..beb3bbc81f --- /dev/null +++ b/docs/source/api/v5/oc_fci_advertisement.rst @@ -0,0 +1,138 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-oc-fci-advertisement: + +************************ +``OC/FCI/advertisement`` +************************ + +``GET`` +======= +Returns the complete footprint and capabilities information structure the :abbr:`dCDN (Downstream Content Delivery Network)` wants to expose to a given :abbr:`uCDN (Upstream Content Delivery Network)`. + +.. note:: Users with the ``ICDN:UCDN-OVERRIDE`` permission will need to provide a "ucdn" query parameter to bypass the need for :abbr:`uCDN (Upstream Content Delivery Network)` information in the :abbr:`JWT (JSON Web Token)` and allow them to view all :abbr:`CDNi (Content Delivery Network Interconnect)` information. + +:Auth. Required: No +:Roles Required: "admin" or "operations" +:Permissions Required: CDNI:READ +:Response Type: Array + +Request Structure +----------------- +This requires authorization using a :abbr:`JWT (JSON Web Token)` provided by the :abbr:`dCDN (Downstream Content Delivery Network)` to identify the :abbr:`uCDN (Upstream Content Delivery Network)`. This token must include the following claims: + +.. table:: Required JWT claims + + +-----------------+--------------------------------------------------------------------------------------------------------------------+ + | Name | Description | + +=================+====================================================================================================================+ + | iss | Issuer claim as a string key for the :abbr:`uCDN (Upstream Content Delivery Network)` | + +-----------------+--------------------------------------------------------------------------------------------------------------------+ + | aud | Audience claim as a string key for the :abbr:`dCDN (Downstream Content Delivery Network)` | + +-----------------+--------------------------------------------------------------------------------------------------------------------+ + | exp | Expiration claim as the expiration date as a Unix epoch timestamp (in seconds) | + +-----------------+--------------------------------------------------------------------------------------------------------------------+ + +Response Structure +------------------ +:capabilities: An array of generic :abbr:`FCI (Footprint and Capabilities Advertisement Interface)` base objects. +:capability-type: A string of the type of base object. +:capability-value: An array of the value for the base object. +:footprints: An array of footprints impacted by this generic base object. + + .. note:: These are meant to be generic and therefore there is not much information in these documents. For further information please see :rfc:`8006`, :rfc:`8007`, :rfc:`8008`, and the :abbr:`SVA (Streaming Video Alliance)` documents titled `Footprint and Capabilities Interface: Open Caching API`, `Open Caching API Implementation Guidelines`, `Configuration Interface: Part 1 Specification - Overview & Architecture`, `Configuration Interface: Part 2 Specification – CDNi Metadata Model Extensions`, and `Configuration Interface: Part 3 Specification – Publishing Layer APIs`. + +.. code-block:: json + :caption: Example /OC/FCI/advertisement Response + + { + "capabilities": [ + { + "capability-type": "FCI.CapacityLimits", + "capability-value": [ + { + "limits": [ + { + "id": "host_limit_requests_requests", + "scope": { + "type": "testScope", + "value": [ + "test.com" + ] + }, + "limit-type": "requests", + "maximum-hard": 20, + "maximum-soft": 15, + "telemetry-source": { + "id": "request_metrics", + "metric": "requests" + } + }, + { + "id": "total_limit_egress_capacity", + "limit-type": "egress", + "maximum-hard": 202020, + "maximum-soft": 500, + "telemetry-source": { + "id": "capacity_metrics", + "metric": "capacity" + } + } + ] + } + ], + "footprints": [ + { + "footprint-type": "countrycode", + "footprint-value": [ + "us" + ] + } + ] + }, + { + "capability-type": "FCI.Telemetry", + "capability-value": { + "sources": [ + { + "id": "capacity_metrics", + "type": "generic", + "metrics": [ + { + "name": "capacity", + "time-granularity": 0, + "data-percentile": 50, + "latency": 0 + } + ], + "configuration": { + "url": "example.com/telemetry1" + } + } + ] + }, + "footprints": [ + { + "footprint-type": "countrycode", + "footprint-value": [ + "us" + ] + } + ] + } + ] + } + diff --git a/docs/source/api/v5/origins.rst b/docs/source/api/v5/origins.rst new file mode 100644 index 0000000000..c503ae8258 --- /dev/null +++ b/docs/source/api/v5/origins.rst @@ -0,0 +1,425 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-origins: + +*********** +``origins`` +*********** + +``GET`` +======= +Gets all requested :term:`Origins`. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: ORIGIN:READ, DELIVERY-SERVICE:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +=================+==========+===================================================================================================================================================================+ + | cachegroup | no | Return only :term:`Origins` within the :term:`Cache Group` that has this :ref:`cache-group-id` | + +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | coordinate | no | Return only :term:`Origins` located at the geographic coordinates identified by this integral, unique identifier | + +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | deliveryservice | no | Return only :term:`Origins` that belong to the :term:`Delivery Service` identified by this integral, unique identifier | + +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | id | no | Return only the :term:`Origin` that has this integral, unique identifier | + +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | name | no | Return only :term:`Origins` by this name | + +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | profileId | no | Return only :term:`Origins` which use the :term:`Profile` that has this :ref:`profile-id` | + +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | primary | no | If ``true``, return only :term:`Origins` which are the the primary :term:`Origin` of the :term:`Delivery Service` to which they belong - if ``false`` return only | + | | | :term:`Origins` which are *not* the primary :term:`Origin` of the :term:`Delivery Service` to which they belong | + +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | tenant | no | Return only :term:`Origins` belonging to the tenant identified by this integral, unique identifier | + +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | orderby | no | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` | + | | | array | + +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long and the first page is 1. If ``offset`` was defined, | + | | | this query parameter has no effect. ``limit`` must be defined to make use of ``page``. | + +-----------------+----------+-------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + +.. note:: Several fields of origin definitions which are filterable by Query Parameters are allowed to be ``null``. ``null`` values in these fields will be filtered *out* appropriately by such Query Parameters, but do note that ``null`` is not a valid value accepted by any of these Query Parameters, and attempting to pass it will result in an error. + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/origins?name=demo1 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the :term:`Origin` belongs +:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the :term:`Origin` belongs +:coordinate: The name of a coordinate pair that defines the origin's geographic location +:coordinateId: An integral, unique identifier for the coordinate pair that defines the :term:`Origin`'s geographic location +:deliveryService: A string that is the :ref:`ds-xmlid` of the :term:`Delivery Service` to which the :term:`Origin` belongs +:deliveryServiceId: An integral, unique identifier for the :term:`Delivery Service` to which the :term:`Origin` belongs +:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`Origin` +:id: An integral, unique identifier for this :term:`Origin` +:ip6Address: The IPv6 address of the :term:`Origin` +:ipAddress: The IPv4 address of the :term:`Origin` +:isPrimary: A boolean value which, when ``true`` specifies this :term:`Origin` as the 'primary' :term:`Origin` served by ``deliveryService`` +:lastUpdated: The date and time at which this :term:`Origin` was last modified +:name: The name of the :term:`Origin` +:port: The TCP port on which the :term:`Origin` listens +:profile: The :ref:`profile-name` of the :term:`Profile` used by this :term:`Origin` +:profileId: The :ref:`profile-id` of the :term:`Profile` used by this :term:`Origin` +:protocol: The protocol used by this origin - will be one of 'http' or 'https' +:tenant: The name of the :term:`Tenant` that owns this :term:`Origin` +:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`Origin` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: sm8DpvdvrfdSVLtmXTdfjsZbTlbc+pI40Gy0aj00XIURTPfFXuv/4LgHb6A3r92iymbRHvFrH6qdB2g97U2sBg== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 11 Dec 2018 15:43:41 GMT + Content-Length: 376 + + { "response": [ + { + "cachegroup": null, + "cachegroupId": null, + "coordinate": null, + "coordinateId": null, + "deliveryService": "demo1", + "deliveryServiceId": 1, + "fqdn": "origin.infra.ciab.test", + "id": 1, + "ip6Address": null, + "ipAddress": null, + "isPrimary": true, + "lastUpdated": "2018-12-10 19:11:32+00", + "name": "demo1", + "port": null, + "profile": null, + "profileId": null, + "protocol": "http", + "tenant": "root", + "tenantId": 1 + } + ]} + +``POST`` +======== +Creates a new origin definition. + +.. warning:: At the time of this writing it is possible to create and/or modify origin definitions assigned to STEERING and CLIENT_STEERING :term:`Delivery Services` - despite that an origin has no meaning in those contexts. In these cases, the API responses may give incorrect output - see `GitHub Issue #3107 `_ for details and updates. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: ORIGIN:CREATE, ORIGIN:READ, DELIVERY-SERVICE:READ, DELIVERY-SERVICE:UPDATE +:Response Type: Object + +Request Structure +----------------- +:cachegroupId: An optional, integer which, if present, should be the :ref:`Cache Group ID ` that identifies a :term:`Cache Group` to which the new :term:`Origin` shall belong +:coordinateId: An optional, integral, unique identifier of a coordinate pair that shall define the :term:`Origin`'s geographic location +:deliveryServiceId: The integral, unique identifier of the :term:`Delivery Service` to which the new :term:`Origin` shall belong +:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`Origin` +:ip6Address: An optional string containing the IPv6 address of the :term:`Origin` +:ipAddress: An optional string containing the IPv4 address of the :term:`Origin` +:isPrimary: An optional boolean which, if ``true`` will set this :term:`Origin` as the 'primary' :term:`Origin` served by the :term:`Delivery Service` identified by ``deliveryServiceID`` + + .. note:: Though not specifying this field in this request will leave it as ``null`` in the output, Traffic Ops will silently coerce that to its default value: ``false``. + +:name: A human-friendly name of the :term:`Origin` +:port: An optional port number on which the :term:`Origin` listens for incoming TCP connections +:profileId: An optional :ref:`profile-id` ofa :term:`Profile` that shall be used by this :term:`Origin` +:protocol: The protocol used by the origin - must be one of 'http' or 'https' +:tenantId: An optional\ [1]_, integral, unique identifier for the :term:`Tenant` which shall own the new :term:`Origin` + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/origins HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 114 + Content-Type: application/json + + { + "deliveryServiceId": 2, + "fqdn": "example.com", + "name": "example", + "port": 80, + "protocol": "http", + "tenantId": 1 + } + +.. [1] The ``tenantId`` field is required if and only if tenancy is enabled within Traffic Ops. + +Response Structure +------------------ +:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the :term:`Origin` belongs +:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the :term:`Origin` belongs +:coordinate: The name of a coordinate pair that defines the origin's geographic location +:coordinateId: An integral, unique identifier for the coordinate pair that defines the :term:`Origin`'s geographic location +:deliveryService: The 'xml_id' of the :term:`Delivery Service` to which the :term:`Origin` belongs +:deliveryServiceId: An integral, unique identifier for the :term:`Delivery Service` to which the :term:`Origin` belongs +:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`Origin` +:id: An integral, unique identifier for this :term:`Origin` +:ip6Address: The IPv6 address of the :term:`Origin` +:ipAddress: The IPv4 address of the :term:`Origin` +:isPrimary: A boolean value which, when ``true`` specifies this :term:`Origin` as the 'primary' :term:`Origin` served by ``deliveryService`` +:lastUpdated: The date and time at which this :term:`Origin` was last modified +:name: The name of the :term:`Origin` +:port: The TCP port on which the :term:`Origin` listens +:profile: The :ref:`profile-name` of the :term:`Profile` used by this :term:`Origin` +:profileId: The :ref:`profile-id` the :term:`Profile` used by this :term:`Origin` +:protocol: The protocol used by this origin - will be one of 'http' or 'https' +:tenant: The name of the :term:`Tenant` that owns this :term:`Origin` +:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`Origin` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: z4gp0MaqYu+gSRORhKT2eObVBuVDVx1rdteRaN5kRL9uJ3hNzUCi4dSKIt0rgNgOEDt6x/iTYrmVhr/TSHYtmA== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 11 Dec 2018 15:14:27 GMT + Content-Length: 418 + + { "alerts": [ + { + "text": "origin was created.", + "level": "success" + } + ], + "response": { + "cachegroup": null, + "cachegroupId": null, + "coordinate": null, + "coordinateId": null, + "deliveryService": null, + "deliveryServiceId": 2, + "fqdn": "example.com", + "id": 2, + "ip6Address": null, + "ipAddress": null, + "isPrimary": null, + "lastUpdated": "2018-12-11 15:14:27+00", + "name": "example", + "port": 80, + "profile": null, + "profileId": null, + "protocol": "http", + "tenant": null, + "tenantId": 1 + }} + +``PUT`` +======= +Updates an :term:`Origin` definition. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: ORIGIN:UPDATE, ORIGIN:READ, DELIVERY-SERVICE:READ, DELIVERY-SERVICE:UPDATE +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Query Parameters + + +------+----------+-------------------------------------------------------------------------------+ + | Name | Required | Description | + +======+==========+===============================================================================+ + | id | yes | The integral, unique identifier of the :term:`Origin` definition being edited | + +------+----------+-------------------------------------------------------------------------------+ + +:cachegroupId: An optional, integer which, if present, should be the :ref:`Cache Group ID ` that identifies a :term:`Cache Group` to which the new :term:`Origin` shall belong +:coordinateId: An optional, integral, unique identifier of a coordinate pair that shall define the :term:`Origin`'s geographic location +:deliveryServiceId: The integral, unique identifier of the :term:`Delivery Service` to which the :term:`Origin` shall belong +:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`Origin` +:ip6Address: An optional string containing the IPv6 address of the :term:`Origin` +:ipAddress: An optional string containing the IPv4 address of the :term:`Origin` +:isPrimary: An optional boolean which, if ``true`` will set this :term:`Origin` as the 'primary' origin served by the :term:`Delivery Service` identified by ``deliveryServiceID`` +:name: A human-friendly name of the :term:`Origin` +:port: An optional port number on which the :term:`Origin` listens for incoming TCP connections +:profileId: An optional :ref:`profile-id` of the :term:`Profile` that shall be used by this :term:`Origin` +:protocol: The protocol used by the :term:`Origin` - must be one of 'http' or 'https' +:tenantId: An optional\ [1]_, integral, unique identifier for the :term:`Tenant` which shall own the new :term:`Origin` + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/origins?id=2 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 135 + Content-Type: application/json + + { + "deliveryServiceId": 2, + "fqdn": "example.com", + "isprimary": true, + "name": "example", + "port": 443, + "protocol": "https", + "tenantId": 1 + } + + +Response Structure +------------------ +:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the :term:`Origin` belongs +:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the :term:`Origin` belongs +:coordinate: The name of a coordinate pair that defines the origin's geographic location +:coordinateId: An integral, unique identifier for the coordinate pair that defines the :term:`Origin`'s geographic location +:deliveryService: The 'xml_id' of the :term:`Delivery Service` to which the :term:`Origin` belongs +:deliveryServiceId: An integral, unique identifier for the :term:`Delivery Service` to which the :term:`Origin` belongs +:fqdn: The :abbr:`FQDN (Fully Qualified Domain Name)` of the :term:`Origin` +:id: An integral, unique identifier for this :term:`Origin` +:ip6Address: The IPv6 address of the :term:`Origin` +:ipAddress: The IPv4 address of the :term:`Origin` +:isPrimary: A boolean value which, when ``true`` specifies this :term:`Origin` as the 'primary' :term:`Origin` served by ``deliveryService`` +:lastUpdated: The date and time at which this :term:`Origin` was last modified +:name: The name of the :term:`Origin` +:port: The TCP port on which the :term:`Origin` listens +:profile: The :ref:`profile-name` of the :term:`Profile` used by this :term:`Origin` +:profileId: The :ref:`profile-id` the :term:`Profile` used by this :term:`Origin` +:protocol: The protocol used by this origin - will be one of 'http' or 'https' +:tenant: The name of the :term:`Tenant` that owns this :term:`Origin` +:tenantId: An integral, unique identifier for the :term:`Tenant` that owns this :term:`Origin` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: Zx7jOa7UAQxRtDenYodvGQSoooPj4m0yY0AIeUpbdelmYMiNdPYtW82BCmMesFXkmP74nV4HbTUyDHVMuJxZ7g== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 11 Dec 2018 15:40:53 GMT + Content-Length: 420 + + { "alerts": [ + { + "text": "origin was updated.", + "level": "success" + } + ], + "response": { + "cachegroup": null, + "cachegroupId": null, + "coordinate": null, + "coordinateId": null, + "deliveryService": null, + "deliveryServiceId": 2, + "fqdn": "example.com", + "id": 2, + "ip6Address": null, + "ipAddress": null, + "isPrimary": true, + "lastUpdated": "2018-12-11 15:40:53+00", + "name": "example", + "port": 443, + "profile": null, + "profileId": null, + "protocol": "https", + "tenant": null, + "tenantId": 1 + }} + +``DELETE`` +========== +Deletes an :term:`Origin` definition. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: ORIGIN:DELETE, DELIVERY-SERVICE:UPDATE +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Query Parameters + + +------+----------+--------------------------------------------------------------------------------+ + | Name | Required | Description | + +======+==========+================================================================================+ + | id | yes | The integral, unique identifier of the :term:`Origin` definition being deleted | + +------+----------+--------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/origins?id=2 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: fLaY4/nh0yR38xq5weBKYg02+aQV6Z1ZroOq9UqUCHLMMrH1NMyhOHx+EphPq7JxkjmGY04WCt6VvDyjGWcgfQ== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 11 Dec 2018 17:04:14 GMT + Content-Length: 61 + + { "alerts": [ + { + "text": "origin was deleted.", + "level": "success" + } + ]} diff --git a/docs/source/api/v5/osversions.rst b/docs/source/api/v5/osversions.rst new file mode 100644 index 0000000000..e4b4cfb589 --- /dev/null +++ b/docs/source/api/v5/osversions.rst @@ -0,0 +1,83 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-osversions: + +************** +``osversions`` +************** +.. seealso:: :ref:`tp-tools-generate-iso` + +``GET`` +======= +Gets all available :abbr:`OS (Operating System)` versions for ISO generation, as well as the name of the directory where the "kickstarter" files are found. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: ISO:READ +:Response Type: Object + +Request Structure +----------------- +No parameters available. + +.. _response-structure: + +Response Structure +------------------ +This endpoint has no constant keys in its ``response``. Instead, each key in the ``response`` object is the name of an OS, and the value is a string that names the directory where the ISO source can be found. These directories sit under ``/var/www/files/`` on the Traffic Ops host machine by default, or at the location defined by the ``kickstart.files.location`` :term:`Parameter` of the Traffic Ops server's :term:`Profile`, if it is defined. + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: RxbRY2DZ+lYOdTzzUETEZ3wtLBiD2BwXMVuaZjhe4a4cwgcZKRBWxZ6Qy5YYujFe1+UBiTG4sML/Amn27F4AVg== + X-Server-Name: traffic_ops_golang/ + Date: Fri, 30 Nov 2018 19:14:36 GMT + Content-Length: 38 + + { "response": { + "CentOS 7.2": "centos72" + }} + + +Configuration File +------------------ +The data returned from the endpoint comes directly from a configuration file. By default, the file is located at ``/var/www/files/osversions.json``. +The **directory** of the file can be changed by creating a specific :term:`Parameter` named ``kickstart.files.location`` in configuration file ``mkisofs``. + +The format of the file is a JSON object as described in :ref:`response-structure`. + +.. code-block:: json + :caption: Example osversions.json file + + { + "CentOS 7.2": "centos72" + } + + +The legacy Perl Traffic Ops used a Perl configuration file located by default at ``/var/www/files/osversions.cfg``. A Perl script is provided +to convert the legacy configuration file to the new JSON format. The script is located within the Traffic Control repository at ``traffic_ops/app/bin/osversions-convert.pl``. + +.. code-block:: shell + :caption: Example usage of conversion script + + ./osversions-convert.pl < /var/www/files/osversions.cfg > /var/www/files/osversions.json diff --git a/docs/source/api/v5/parameterprofile.rst b/docs/source/api/v5/parameterprofile.rst new file mode 100644 index 0000000000..9a930534e0 --- /dev/null +++ b/docs/source/api/v5/parameterprofile.rst @@ -0,0 +1,87 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-parameterprofile: + +******************** +``parameterprofile`` +******************** + +``POST`` +======== +Create one or more :term:`Parameter`/:term:`Profile` assignments. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: PROFILE:UPDATE, PROFILE:READ, PARAMETER:READ +:Response Type: Object + +Request Structure +----------------- +:paramId: The :ref:`parameter-id` of the :term:`Parameter` to be assigned to the :term:`Profiles` identified within the ``profileIds`` array +:profileIds: An array of :term:`Profile` :ref:`IDs ` to which the :term:`Parameter` identified by ``paramId`` shall be assigned +:replace: An optional boolean (default: false) which, if ``true``, will cause any conflicting :term:`Profile`/:term:`Parameter` assignments to be overridden. + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/parameterprofile HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 38 + Content-Type: application/json + + { + "paramId": 4, + "profileIds": [18] + } + +Response Structure +------------------ +:paramId: The :ref:`parameter-id` of the :term:`Parameter` which has been assigned to the :term:`Profiles` identified within the ``profileIds`` array +:profileIds: An array of :term:`Profile` :ref:`IDs ` to which the :term:`Parameter` identified by ``paramId`` has been assigned +:replace: An optional boolean (default: false) which, if ``true``, caused any conflicting :term:`Profile`/:term:`Parameter` assignments to be overridden. + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: N2ahnhEnfZ0UqnjylN6Vu3HaOZk340YuiuyiqkhTbk0pENp+kwBPYu4Z/sqBAloCfXSQaWlJzaeXw4uOD5heWw== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 10 Dec 2018 15:18:23 GMT + Content-Length: 147 + + { "alerts": [ + { + "text": "2 parameters were assigned to the 18 profile", + "level": "success" + } + ], + "response": { + "profileId": 18, + "paramIds": [ + 2, + 3 + ], + "replace": false + }} diff --git a/docs/source/api/v5/parameters.rst b/docs/source/api/v5/parameters.rst new file mode 100644 index 0000000000..803c6e1573 --- /dev/null +++ b/docs/source/api/v5/parameters.rst @@ -0,0 +1,208 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-parameters: + +************** +``parameters`` +************** + +``GET`` +======= +Gets all :term:`Parameters` configured in Traffic Ops + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: PARAMETER:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-------------+----------+---------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +=============+==========+===============================================================================================================+ + | configFile | no | Filter :term:`Parameters` by :ref:`parameter-config-file` | + +-------------+----------+---------------------------------------------------------------------------------------------------------------+ + | id | no | Filters :term:`Parameters` by :ref:`parameter-id` | + +-------------+----------+---------------------------------------------------------------------------------------------------------------+ + | name | no | Filter :term:`Parameters` by :ref:`parameter-name` | + +-------------+----------+---------------------------------------------------------------------------------------------------------------+ + | value | no | Filter :term:`Parameters` by :ref:`parameter-value` | + +-------------+----------+---------------------------------------------------------------------------------------------------------------+ + | orderby | no | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` | + | | | array | + +-------------+----------+---------------------------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +-------------+----------+---------------------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +-------------+----------+---------------------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit. | + +-------------+----------+---------------------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long | + | | | and the first page is 1. If ``offset`` was defined, this query parameter has no effect. ``limit`` must be | + | | | defined to make use of ``page``. | + +-------------+----------+---------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/parameters?configFile=records.config&name=location HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:configFile: The :term:`Parameter`'s :ref:`parameter-config-file` +:id: The :term:`Parameter`'s :ref:`parameter-id` +:lastUpdated: The date and time at which this :term:`Parameter` was last updated, in :ref:`non-rfc-datetime` +:name: :ref:`parameter-name` of the :term:`Parameter` +:profiles: An array of :term:`Profile` :ref:`Names ` that use this :term:`Parameter` +:secure: A boolean value that describes whether or not the :term:`Parameter` is :ref:`parameter-secure` +:value: The :term:`Parameter`'s :ref:`parameter-value` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: UFO3/jcBFmFZM7CsrsIwTfPc5v8gUiXqJm6BNp1boPb4EQBnWNXZh/DbBwhMAOJoeqDImoDlrLnrVjQGO4AooA== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 05 Dec 2018 18:23:39 GMT + Content-Length: 212 + + { "response": [ + { + "configFile": "records.config", + "id": 29, + "lastUpdated": "2018-12-05 17:51:02+00", + "name": "location", + "profiles": [ + "ATS_EDGE_TIER_CACHE", + "ATS_MID_TIER_CACHE" + ], + "secure": false, + "value": "/etc/trafficserver/" + } + ]} + +``POST`` +======== +Creates one or more new :term:`Parameters`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: PARAMETER:CREATE, PARAMETER:READ +:Response Type: Array + +Request Structure +----------------- +The request body may be in one of two formats, a single :term:`Parameter` object or an array of :term:`Parameter` objects. Each :term:`Parameter` object shall have the following keys: + +:configFile: The :term:`Parameter`'s :ref:`parameter-config-file` +:name: :ref:`parameter-name` of the :term:`Parameter` +:secure: A boolean value that describes whether or not the :term:`Parameter` is :ref:`parameter-secure` +:value: The :term:`Parameter`'s :ref:`parameter-value` + +.. code-block:: http + :caption: Request Example - Single Object Format + + POST /api/5.0/parameters HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 84 + Content-Type: application/json + + { + "name": "test", + "value": "quest", + "configFile": "records.config", + "secure": false + } + +.. code-block:: http + :caption: Request Example - Array Format + + POST /api/5.0/parameters HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 180 + Content-Type: application/json + + [{ + "name": "test", + "value": "quest", + "configFile": "records.config", + "secure": false + }, + { + "name": "foo", + "value": "bar", + "configFile": "records.config", + "secure": false + }] + +Response Structure +------------------ +:configFile: The :term:`Parameter`'s :ref:`parameter-config-file` +:id: The :term:`Parameter`'s :ref:`parameter-id` +:lastUpdated: The date and time at which this :term:`Parameter` was last updated, in :ref:`non-rfc-datetime` +:name: :ref:`parameter-name` of the :term:`Parameter` +:profiles: An array of :term:`Profile` :ref:`Names ` that use this :term:`Parameter` +:secure: A boolean value that describes whether or not the :term:`Parameter` is :ref:`parameter-secure` +:value: The :term:`Parameter`'s :ref:`parameter-value` + +.. code-block:: http + :caption: Response Example - Single Object Format + + 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: eQrl48zWids0kDpfCYmmtYMpegjnFxfOVvlBYxxLSfp7P7p6oWX4uiC+/Cfh2X9i3G+MQ36eH95gukJqOBOGbQ== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 05 Dec 2018 19:18:21 GMT + Content-Length: 212 + + { "alerts": [ + { + "text": "param was created.", + "level": "success" + } + ], + "response": { + "configFile": "records.config", + "id": 124, + "lastUpdated": "2018-12-05 19:18:21+00", + "name": "test", + "profiles": null, + "secure": false, + "value": "quest" + }} diff --git a/docs/source/api/v5/parameters_id.rst b/docs/source/api/v5/parameters_id.rst new file mode 100644 index 0000000000..592673e098 --- /dev/null +++ b/docs/source/api/v5/parameters_id.rst @@ -0,0 +1,155 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-parameters-id: + +********************* +``parameters/{{ID}}`` +********************* + +``PUT`` +======= +Replaces a :term:`Parameter`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: PARAMETER:UPDATE, PARAMETER:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+------------------------------------------------------------------------+ + | Name | Description | + +======+========================================================================+ + | ID | The :ref:`parameter-id` of the :term:`Parameter` which will be deleted | + +------+------------------------------------------------------------------------+ + +:configFile: The :term:`Parameter`'s :ref:`parameter-config-file` +:name: :ref:`parameter-name` of the :term:`Parameter` +:secure: A boolean value that describes whether or not the :term:`Parameter` is :ref:`parameter-secure` +:value: The :term:`Parameter`'s :ref:`parameter-value` + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/parameters/124 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 81 + Content-Type: application/json + + { + "name": "foo", + "value": "bar", + "configFile": "records.config", + "secure": false + } + +Response Structure +------------------ +:configFile: The :term:`Parameter`'s :ref:`parameter-config-file` +:id: The :term:`Parameter`'s :ref:`parameter-id` +:lastUpdated: The date and time at which this :term:`Parameter` was last updated, in :ref:`non-rfc-datetime` +:name: :ref:`parameter-name` of the :term:`Parameter` +:profiles: An array of :term:`Profile` :ref:`Names ` that use this :term:`Parameter` +:secure: A boolean value that describes whether or not the :term:`Parameter` is :ref:`parameter-secure` +:value: The :term:`Parameter`'s :ref:`parameter-value` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: DMxS2gKceFVKRtezON/vsnrC+zI8onASSHaGv5i3wwvUvyt9KEe72gxQd6ZgVcSq3K8ZpkH6g3UI/WtEfdp5vA== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 05 Dec 2018 20:21:07 GMT + Content-Length: 209 + + { "alerts": [ + { + "text": "param was updated.", + "level": "success" + } + ], + "response": { + "configFile": "records.config", + "id": 125, + "lastUpdated": "2018-12-05 20:21:07+00", + "name": "foo", + "profiles": null, + "secure": false, + "value": "bar" + }} + +``DELETE`` +========== +Deletes the specified :term:`Parameter`. If, however, the :term:`Parameter` is associated with one or more :term:`Profiles`, deletion will fail. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: PARAMETER:DELETE, PARAMETER:READ +:Response TYpe: ``undefined`` + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+------------------------------------------------------------------------+ + | Name | Description | + +======+========================================================================+ + | ID | The :ref:`parameter-id` of the :term:`Parameter` which will be deleted | + +------+------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/parameters/124 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: hJjQq2Seg7sqWt+jKgp6gwRxUtoVU34PFoc9wEaweXdaIBTn/BscoUuyw2/n+V8GZPqpeQcihZE50/0oQhdtHw== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 05 Dec 2018 19:20:30 GMT + Content-Length: 60 + + { "alerts": [ + { + "text": "param was deleted.", + "level": "success" + } + ]} diff --git a/docs/source/api/v5/phys_locations.rst b/docs/source/api/v5/phys_locations.rst new file mode 100644 index 0000000000..9aae8be2b8 --- /dev/null +++ b/docs/source/api/v5/phys_locations.rst @@ -0,0 +1,222 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-phys_locations: + +****************** +``phys_locations`` +****************** + +``GET`` +======= +Retrieves :term:`Physical Locations` + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: PHYSICAL-LOCATION:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-----------+---------------------------------------------------------------------------------------------------------------+ + | Name | Description | + +===========+===============================================================================================================+ + | id | Filter by integral, unique identifier | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | region | Filter by integral, unique identifier of containing :term:`Region` | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | name | Filter by name | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | orderby | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` | + | | array | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | sortOrder | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | limit | Choose the maximum number of results to return | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | offset | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | page | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long | + | | and the first page is 1. If ``offset`` was defined, this query parameter has no effect. ``limit`` must be | + | | defined to make use of ``page``. | + +-----------+---------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/phys_locations?name=CDN_in_a_Box HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:address: The physical location's street address +:city: The name of the city in which the physical location lies +:comments: Any and all human-readable comments +:email: The email address of the physical location's ``poc`` +:id: An integral, unique identifier for the physical location +:lastUpdated: The date and time at which the physical location was last updated, in :ref:`non-rfc-datetime` +:name: The name of the physical location +:phone: A phone number where the the physical location's ``poc`` might be reached +:poc: The name of a "point of contact" for the physical location +:region: The name of the region within which the physical location lies +:regionId: An integral, unique identifier for the region within which the physical location lies +:shortName: An abbreviation of the ``name`` +:state: An abbreviation of the name of the state or province within which this physical location lies +:zip: The zip code of the physical location + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: 0g4b3W1AwXytCnBo8TReQQij2v9oHAl7MG9KuwMig5V4sFcMM5qP8dgPsFTunFr00DPI20c7BpUbZsvJtsYTEQ== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 05 Dec 2018 22:19:52 GMT + Content-Length: 275 + + { "response": [ + { + "address": "1600 Pennsylvania Avenue NW", + "city": "Washington", + "comments": "", + "email": "", + "id": 2, + "lastUpdated": "2018-12-05 17:50:58+00", + "name": "CDN_in_a_Box", + "phone": "", + "poc": "", + "regionId": 1, + "region": "Washington, D.C", + "shortName": "ciab", + "state": "DC", + "zip": "20500" + } + ]} + +``POST`` +======== +Creates a new physical location + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: PHYSICAL-LOCATION:CREATE, PHYSICAL-LOCATION:READ +:Response Type: Object + +Request Structure +----------------- +:address: The physical location's street address +:city: The name of the city in which the physical location lies +:comments: An optional string for containing any and all human-readable comments +:email: An optional string containing email address of the physical location's ``poc`` +:name: An optional name of the physical location +:phone: An optional string containing the phone number where the the physical location's ``poc`` might be reached +:poc: The name of a "point of contact" for the physical location +:region: An optional string naming the region that contains this physical location\ [1]_ +:regionId: An integral, unique identifier for the region within which the physical location lies\ [1]_ +:shortName: An abbreviation of the ``name`` +:state: An abbreviation of the name of the state or province within which this physical location lies +:zip: The zip code of the physical location + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/phys_locations HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 326 + Content-Type: application/json + + { + "address": "Buckingham Palace", + "city": "London", + "comments": "Buckingham Palace", + "email": "steve.kingstone@royal.gsx.gov.uk", + "name": "Great_Britain", + "phone": "0-843-816-6276", + "poc": "Her Majesty The Queen Elizabeth Alexandra Mary Windsor II", + "regionId": 3, + "shortName": "uk", + "state": "Westminster", + "zip": "SW1A 1AA" + } + +.. [1] The only "region" key that actually matters in the request body is ``regionId``; ``region`` is not validated and has no effect - particularly not the effect of re-naming the region - beyond changing the name in the API response to this request. Subsequent requests will reveal the true name of the region. Note that if ``region`` is not present in the request body it will be ``null`` in the response, but again further requests will show the true region name. + +Response Structure +------------------ +:address: The physical location's street address +:city: The name of the city in which the physical location lies +:comments: Any and all human-readable comments +:email: The email address of the physical location's ``poc`` +:id: An integral, unique identifier for the physical location +:lastUpdated: The date and time at which the physical location was last updated, in :ref:`non-rfc-datetime` +:name: The name of the physical location +:phone: A phone number where the the physical location's ``poc`` might be reached +:poc: The name of a "point of contact" for the physical location +:region: The name of the region within which the physical location lies +:regionId: An integral, unique identifier for the region within which the physical location lies +:shortName: An abbreviation of the ``name`` +:state: An abbreviation of the name of the state or province within which this physical location lies +:zip: The zip code of the physical location + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: GZ/BC+AgGpOQNfd9oiZy19jtsD8MPOdeyi7PVdz+9YSiLYP44gmn5K+Xi1yS0l59yjHf7O+C1loVQPSlIeP9fg== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 06 Dec 2018 00:14:47 GMT + Content-Length: 443 + + { "alerts": [ + { + "text": "physLocation was created.", + "level": "success" + } + ], + "response": { + "address": "Buckingham Palace", + "city": "London", + "comments": "Buckingham Palace", + "email": "steve.kingstone@royal.gsx.gov.uk", + "id": 3, + "lastUpdated": "2018-12-06 00:14:47+00", + "name": "Great_Britain", + "phone": "0-843-816-6276", + "poc": "Her Majesty The Queen Elizabeth Alexandra Mary Windsor II", + "regionId": 3, + "region": null, + "shortName": "uk", + "state": "Westminster", + "zip": "SW1A 1AA" + }} diff --git a/docs/source/api/v5/phys_locations_id.rst b/docs/source/api/v5/phys_locations_id.rst new file mode 100644 index 0000000000..b945bf2177 --- /dev/null +++ b/docs/source/api/v5/phys_locations_id.rst @@ -0,0 +1,186 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-phys_locations-id: + +************************* +``phys_locations/{{ID}}`` +************************* + +``PUT`` +======= +Updates a :term:`Physical Location` + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: PHYSICAL-LOCATION:UPDATE, PHYSICAL-LOCATION:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+----------------------------------------------------------------------------------+ + | Name | Description | + +======+==================================================================================+ + | ID | The integral, unique identifier of the :term:`Physical Location` being modified | + +------+----------------------------------------------------------------------------------+ + +:address: The physical location's street address +:city: The name of the city in which the physical location lies +:comments: An optional string for containing any and all human-readable comments +:email: An optional string containing email address of the physical location's ``poc`` +:name: An optional name of the physical location +:phone: An optional string containing the phone number where the the physical location's ``poc`` might be reached +:poc: The name of a "point of contact" for the physical location +:region: An optional string naming the region that contains this physical location\ [1]_ +:regionId: An integral, unique identifier for the region within which the physical location lies\ [1]_ +:shortName: An abbreviation of the ``name`` +:state: An abbreviation of the name of the state or province within which this physical location lies +:zip: The zip code of the physical location + +.. code-block:: http + :caption: Request Structure + + PUT /api/5.0/phys_locations/2 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 268 + Content-Type: application/json + + { + "address": "1600 Pennsylvania Avenue NW", + "city": "Washington", + "comments": "The White House", + "email": "the@white.house", + "name": "CDN_in_a_Box", + "phone": "1-202-456-1414", + "poc": "Donald J. Trump", + "regionId": 2, + "shortName": "ciab", + "state": "DC", + "zip": "20500" + } + +.. [1] The only "region" key that actually matters in the request body is ``regionId``; ``region`` is not validated and has no effect - particularly not the effect of re-naming the region - beyond changing the name in the API response to this request. Subsequent requests will reveal the true name of the region. Note that if ``region`` is not present in the request body it will be ``null`` in the response, but again further requests will show the true region name. + +Response Structure +------------------ +:address: The physical location's street address +:city: The name of the city in which the physical location lies +:comments: Any and all human-readable comments +:email: The email address of the physical location's ``poc`` +:id: An integral, unique identifier for the physical location +:lastUpdated: The date and time at which the physical location was last updated, in :ref:`non-rfc-datetime` +:name: The name of the physical location +:phone: A phone number where the the physical location's ``poc`` might be reached +:poc: The name of a "point of contact" for the physical location +:region: The name of the region within which the physical location lies +:regionId: An integral, unique identifier for the region within which the physical location lies +:shortName: An abbreviation of the ``name`` +:state: An abbreviation of the name of the state or province within which this physical location lies +:zip: The zip code of the physical location + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: qnMe6OqxjSU8H1njlh00HWNR20YnVlOCufqCTdMBcdC1322jk2ICFQsQQ3XuOOR0WSb7h7OHCfXqDC1/jA1xjA== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 05 Dec 2018 23:39:17 GMT + Content-Length: 385 + + { "alerts": [ + { + "text": "physLocation was updated.", + "level": "success" + } + ], + "response": { + "address": "1600 Pennsylvania Avenue NW", + "city": "Washington", + "comments": "The White House", + "email": "the@white.house", + "id": 2, + "lastUpdated": "2018-12-05 23:39:17+00", + "name": "CDN_in_a_Box", + "phone": "1-202-456-1414", + "poc": "Donald J. Trump", + "regionId": 2, + "region": null, + "shortName": "ciab", + "state": "DC", + "zip": "20500" + }} + +``DELETE`` +========== +Deletes a :term:`Physical Location` + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: PHYSICAL-LOCATION:DELETE, PHYSICAL-LOCATION:READ +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+--------------------------------------------------------------------------------+ + | Name | Description | + +======+================================================================================+ + | ID | The integral, unique identifier of the :term:`Physical Location` being deleted | + +------+--------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/phys_locations/3 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: KeW/tEmICwpCGC8F0YMTqHdeR9J6W6Z3w/U+HOSbeCGyaEheCIhIsWlngT3dyfH1tiu8UyzaPB6QrJyXdybBkw== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 06 Dec 2018 00:28:48 GMT + Content-Length: 67 + + { "alerts": [ + { + "text": "physLocation was deleted.", + "level": "success" + } + ]} diff --git a/docs/source/api/v5/ping.rst b/docs/source/api/v5/ping.rst new file mode 100644 index 0000000000..24e6d4b788 --- /dev/null +++ b/docs/source/api/v5/ping.rst @@ -0,0 +1,63 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-ping: + +******** +``ping`` +******** +Checks whether Traffic Ops is online. + +``GET`` +======= +:Auth. Required: No +:Response Type: ``undefined`` + +Request Structure +----------------- +No parameters available. + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/ping HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + +Response Structure +------------------ +:ping: Returns an object containing only the ``"ping"`` property, which always has the value ``"pong"``. + +.. 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-Encoding: gzip + Whole-Content-Sha512: 0HqcLcYHCB4AFYGFzcAsP2h+PCMlYxk/TqMajcy3fWCzY730Tv32k5trUaJLeSBbRx2FUi7z/sTAuzikdg0E4g== + X-Server-Name: traffic_ops_golang/ + Date: Sun, 23 Feb 2020 20:52:01 GMT + Content-Length: 40 + Content-Type: application/x-gzip + + { + "ping": "pong" + } diff --git a/docs/source/api/v5/plugins.rst b/docs/source/api/v5/plugins.rst new file mode 100644 index 0000000000..677fcce25c --- /dev/null +++ b/docs/source/api/v5/plugins.rst @@ -0,0 +1,72 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-plugins: + +*********** +``plugins`` +*********** +.. seealso:: :ref:`to_go_plugins` + +``GET`` +======= +Retrieves the list of configured enabled Traffic Ops plugins. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: PLUGIN:READ +:Response Type: Array + +Request Structure +----------------- +.. code-block:: http + :caption: Request Example + + GET /api/5.0/plugins HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:description: A short description of the plugin. +:name: The name of the plugin. +:version: A (hopefully) semantic version number describing the version of the plugin. + +.. 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 + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Cache-Control: no-cache, no-store, max-age=0, must-revalidate + Content-Type: application/json + Date: Tue, 11 Dec 2018 20:51:48 GMT + X-Server-Name: traffic_ops_golang/ + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: n73jg9XR4V5Cwqq56Rf3wuIi99k3mM5u2NAjcZ/gQBu8jvAFymDlnZqKeJ+wTll1vjIsHpXCOVXV7+5UGakLgA== + Transfer-Encoding: chunked + + { "response": [ + { + "name": "helloworld", + "version": "1.0.0", + "description": "configuration plugin to run at startup" + } + ]} diff --git a/docs/source/api/v5/profileparameter.rst b/docs/source/api/v5/profileparameter.rst new file mode 100644 index 0000000000..594be261f7 --- /dev/null +++ b/docs/source/api/v5/profileparameter.rst @@ -0,0 +1,88 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-profileparameter: + +******************** +``profileparameter`` +******************** +.. seealso:: :ref:`to-api-profileparameters`. + +``POST`` +======== +Create one or more :term:`Profile`/:term:`Parameter` assignments. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: PROFILE:READ, PARAMETER:READ, PROFILE:UPDATE +:Response Type: Object + +Request Structure +----------------- +:profileId: The :ref:`profile-id` of the :term:`Profile` to which the :term:`Parameters` identified within the ``parameterIds`` array will be assigned +:paramIds: An array of :term:`Parameter` :ref:`IDs ` which shall be assigned to the :term:`Profile` identified by ``profileId`` +:replace: An optional boolean (default: false) which, if ``true``, will cause any conflicting :term:`Profile`/:term:`Parameter` assignments to be overridden. + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/profileparameter HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 38 + Content-Type: application/json + + { + "profileId": 18, + "paramIds": [2, 3] + } + +Response Structure +------------------ +:profileId: The :ref:`profile-id` of the :term:`Profile` to which the :term:`Parameters` identified within the ``parameterIds`` array are assigned +:paramIds: An array of :term:`Parameter` :ref:`IDs ` which have been assigned to the :term:`Profile` identified by ``profileId`` +:replace: An optional boolean (default: false) which, if ``true``, indicates that any conflicting :term:`Profile`/:term:`Parameter` assignments have been overridden. + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: N2ahnhEnfZ0UqnjylN6Vu3HaOZk340YuiuyiqkhTbk0pENp+kwBPYu4Z/sqBAloCfXSQaWlJzaeXw4uOD5heWw== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 10 Dec 2018 15:18:23 GMT + Content-Length: 147 + + { "alerts": [ + { + "text": "2 parameters were assigned to the 18 profile", + "level": "success" + } + ], + "response": { + "profileId": 18, + "paramIds": [ + 2, + 3 + ], + "replace": false + }} diff --git a/docs/source/api/v5/profileparameters.rst b/docs/source/api/v5/profileparameters.rst new file mode 100644 index 0000000000..58e184d3ae --- /dev/null +++ b/docs/source/api/v5/profileparameters.rst @@ -0,0 +1,188 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-profileparameters: + +********************* +``profileparameters`` +********************* + +``GET`` +======= + +Retrieves all :term:`Parameter`/:term:`Profile` assignments. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: PROFILE:READ, PARAMETER:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +===========+==========+===============================================================================================================+ + | orderby | no | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` | + | | | array | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long | + | | | and the first page is 1. If ``offset`` was defined, this query parameter has no effect. ``limit`` must be | + | | | defined to make use of ``page``. | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + +Response Structure +------------------ +:lastUpdated: The date and time at which this :term:`Profile`/:term:`Parameter` association was last modified, in :ref:`non-rfc-datetime` +:parameter: The :ref:`parameter-id` of a :term:`Parameter` assigned to ``profile`` +:profile: The :ref:`profile-name` of the :term:`Profile` to which the :term:`Parameter` identified by ``parameter`` is assigned + +.. code-block:: http + :caption: Response Structure + + 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: +bnMkRgdx4bJoGGlr3mZl539obj3aQAP8e65FAXgywdRAUfXZCFM6VNDn7wScXBmvF2SFXo9F+MhuSwrtB9mPg== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 10 Dec 2018 15:09:13 GMT + Transfer-Encoding: chunked + + { "response": [ + { + "lastUpdated": "2018-12-05 17:50:49+00", + "profile": "GLOBAL", + "parameter": 4 + }, + { + "lastUpdated": "2018-12-05 17:50:49+00", + "profile": "GLOBAL", + "parameter": 5 + } + ]} + +.. note:: The response example for this endpoint has been truncated to only the first two elements of the resulting array, as the output was hundreds of lines long. + +``POST`` +======== +Associate a :term:`Parameter` to a :term:`Profile`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Response Type: Object +:Permissions Required: PROFILE:READ, PARAMETER:READ, PROFILE:UPDATE + +Request Structure +----------------- +This endpoint accepts two formats for the request payload: + +Single Object Format + For assigning a single :term:`Parameter` to a single :term:`Profile` +Array Format + For making multiple assignments of :term:`Parameters` to :term:`Profiles` simultaneously + +Single Object Format +"""""""""""""""""""" +:parameterId: The :ref:`parameter-id` of a :term:`Parameter` to assign to some :term:`Profile` +:profileId: The :ref:`profile-id` of the :term:`Profile` to which the :term:`Parameter` identified by ``parameterId`` will be assigned + +.. code-block:: http + :caption: Request Example - Single Object Format + + POST /api/5.0/profileparameters HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 36 + Content-Type: application/json + + { + "profileId": 18, + "parameterId": 1 + } + +Array Format +"""""""""""" +:parameterId: The :ref:`parameter-id` of a :term:`Parameter` to assign to some :term:`Profile` +:profileId: The :ref:`profile-id` of the :term:`Profile` to which the :term:`Parameter` identified by ``parameterId`` will be assigned + +.. code-block:: http + :caption: Request Example - Array Format + + POST /api/5.0/profileparameters HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 88 + Content-Type: application/json + + [{ + "profileId": 18, + "parameterId": 2 + }, + { + "profileId": 18, + "parameterId": 3 + }] + +Response Structure +------------------ +:lastUpdated: The date and time at which the :term:`Profile`/:term:`Parameter` assignment was last modified, in :ref:`non-rfc-datetime` +:parameter: :ref:`parameter-name` of the :term:`Parameter` which is assigned to ``profile`` +:parameterId: The :ref:`parameter-id` of the assigned :term:`Parameter` +:profile: :ref:`profile-name` of the :term:`Profile` to which the :term:`Parameter` is assigned +:profileId: The :ref:`profile-id` of the :term:`Profile` to which the :term:`Parameter` identified by ``parameterId`` is assigned + +.. code-block:: http + :caption: Response Example - Single Object Format + + 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: eDmIwlzX44fZdxLRPHMNa8aoGAK5fQv9Y70A2eeQHfEkliU4evwcsQ4WeHcH0l3/wPTGlpyC0gwLo8LQQpUxWQ== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 10 Dec 2018 13:50:11 GMT + Content-Length: 166 + + { "alerts": [ + { + "text": "profileParameter was created.", + "level": "success" + } + ], + "response": { + "lastUpdated": null, + "profile": null, + "profileId": 18, + "parameter": null, + "parameterId": 1 + }} diff --git a/docs/source/api/v5/profileparameters_profileID_parameterID.rst b/docs/source/api/v5/profileparameters_profileID_parameterID.rst new file mode 100644 index 0000000000..18fd0aff2d --- /dev/null +++ b/docs/source/api/v5/profileparameters_profileID_parameterID.rst @@ -0,0 +1,74 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-profileparameters-profileID-parameterID: + +*************************************************** +``profileparameters/{{profileID}}/{{parameterID}}`` +*************************************************** + +``DELETE`` +========== +Deletes a :term:`Profile`/:term:`Parameter` association. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: PROFILE:UPDATE, PROFILE:READ, PARAMETER:READ +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Path Parameters + + +-------------+------------------------------------------------------------------------------------------------------------------------------+ + | Name | Description | + +=============+==============================================================================================================================+ + | profileID | The :ref:`profile-id` of the :term:`Profile` from which a :term:`Parameter` shall be removed | + +-------------+------------------------------------------------------------------------------------------------------------------------------+ + | parameterID | The :ref:`parameter-id` of the :term:`Parameter` which shall be removed from the :term:`Profile` identified by ``profileID`` | + +-------------+------------------------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/profileparameters/18/129 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: JQuBqHyT9MnNwO9NSIDVQhkRtXdeAJc95W1pF2dwQeoBFmf0Y8knXm3/O/rbJDEoUC7DhUQN1aoYIsqqmz4qQQ== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 10 Dec 2018 15:00:15 GMT + Content-Length: 71 + + { "alerts": [ + { + "text": "profileParameter was deleted.", + "level": "success" + } + ]} diff --git a/docs/source/api/v5/profiles.rst b/docs/source/api/v5/profiles.rst new file mode 100644 index 0000000000..d00c2febec --- /dev/null +++ b/docs/source/api/v5/profiles.rst @@ -0,0 +1,170 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-profiles: + +************ +``profiles`` +************ + +``GET`` +======= +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: PROFILE:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-------+----------+--------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +=======+==========+========================================================================================================+ + | cdn | no | Used to filter :term:`Profiles` by the integral, unique identifier of the CDN to which they belong | + +-------+----------+--------------------------------------------------------------------------------------------------------+ + | id | no | Filters :term:`Profiles` by :ref:`profile-id` | + +-------+----------+--------------------------------------------------------------------------------------------------------+ + | name | no | Filters :term:`Profiles` by :ref:`profile-name` | + +-------+----------+--------------------------------------------------------------------------------------------------------+ + | param | no | Used to filter :term:`Profiles` by the :ref:`parameter-id` of a :term:`Parameter` associated with them | + +-------+----------+--------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/profiles?name=ATS_EDGE_TIER_CACHE HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.62.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:cdn: The integral, unique identifier of the :ref:`profile-cdn` to which this :term:`Profile` belongs +:cdnName: The name of the :ref:`profile-cdn` to which this :term:`Profile` belongs +:description: The :term:`Profile`'s :ref:`profile-description` +:id: The :term:`Profile`'s :ref:`profile-id` +:lastUpdated: The date and time at which this :term:`Profile` was last updated, in :ref:`non-rfc-datetime` +:name: The :term:`Profile`'s :ref:`profile-name` +:routingDisabled: The :term:`Profile`'s :ref:`profile-routing-disabled` setting +:type: The :term:`Profile`'s :ref:`profile-type` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: QEpKM/DwHBRvue9K7XKrpwKFKhW6yCMQ2vSQgxE7dWFGJaqC4KOUO92bsJU/5fjI9qlB+1uMT2kz6mFb1Wzp/w== + X-Server-Name: traffic_ops_golang/ + Date: Fri, 07 Dec 2018 20:40:31 GMT + Content-Length: 220 + + { "response": [ + { + "id": 9, + "lastUpdated": "2018-12-05 17:51:00+00", + "name": "ATS_EDGE_TIER_CACHE", + "description": "Edge Cache - Apache Traffic Server", + "cdnName": "CDN-in-a-Box", + "cdn": 2, + "routingDisabled": false, + "type": "ATS_PROFILE" + } + ]} + +``POST`` +======== +Creates a new :term:`Profile`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: PROFILE:CREATE, PROFILE:READ +:Response Type: Object + +Request Structure +----------------- +:cdn: The integral, unique identifier of the :ref:`profile-cdn` to which this :term:`Profile` shall belong +:description: The :term:`Profile`'s :ref:`profile-description` +:name: The :term:`Profile`'s :ref:`profile-name` +:routingDisabled: The :term:`Profile`'s :ref:`profile-routing-disabled` setting +:type: The :term:`Profile`'s :ref:`profile-type` + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/profiles HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.62.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 125 + Content-Type: application/json + + { + "name": "test", + "description": "A test profile for API examples", + "cdn": 2, + "type": "UNK_PROFILE", + "routingDisabled": true + } + +Response Structure +------------------ +:cdn: The integral, unique identifier of the :ref:`profile-cdn` to which this :term:`Profile` belongs +:cdnName: The name of the :ref:`profile-cdn` to which this :term:`Profile` belongs +:description: The :term:`Profile`'s :ref:`profile-description` +:id: The :term:`Profile`'s :ref:`profile-id` +:lastUpdated: The date and time at which this :term:`Profile` was last updated, in :ref:`non-rfc-datetime` +:name: The :term:`Profile`'s :ref:`profile-name` +:routingDisabled: The :term:`Profile`'s :ref:`profile-routing-disabled` setting +:type: The :term:`Profile`'s :ref:`profile-type` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: UGV3PCnYBY0J3siICR0f9VVRNdUK1+9zsDDP6T9yt6t+AoHckHe6bvzOli9to/fGhC2zz5l9Nc1ro4taJUDD8g== + X-Server-Name: traffic_ops_golang/ + Date: Fri, 07 Dec 2018 21:24:49 GMT + Content-Length: 251 + + { "alerts": [ + { + "text": "profile was created.", + "level": "success" + } + ], + "response": { + "id": 16, + "lastUpdated": "2018-12-07 21:24:49+00", + "name": "test", + "description": "A test profile for API examples", + "cdnName": null, + "cdn": 2, + "routingDisabled": true, + "type": "UNK_PROFILE" + }} diff --git a/docs/source/api/v5/profiles_id.rst b/docs/source/api/v5/profiles_id.rst new file mode 100644 index 0000000000..29f970774f --- /dev/null +++ b/docs/source/api/v5/profiles_id.rst @@ -0,0 +1,162 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-profiles-id: + +******************* +``profiles/{{ID}}`` +******************* + +``PUT`` +======= +Replaces the specified :term:`Profile` with the one in the request payload + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: PROFILE:UPDATE, PROFILE:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+-------------------------------------------------------------+ + | Name | Description | + +======+=============================================================+ + | ID | The :ref:`profile-id` of the :term:`Profile` being modified | + +------+-------------------------------------------------------------+ + +:cdn: The integral, unique identifier of the :ref:`profile-cdn` to which this :term:`Profile` will belong +:description: The :term:`Profile`'s new :ref:`profile-description` +:name: The :term:`Profile`'s new :ref:`profile-name` +:routingDisabled: The :term:`Profile`'s new :ref:`profile-routing-disabled` setting +:type: The :term:`Profile`'s new :ref:`profile-type` + + .. warning:: Changing this will likely break something, be **VERY** careful when modifying this value + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/profiles/16 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.62.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 125 + Content-Type: application/json + + { + "name": "test", + "description": "A test profile for API examples", + "cdn": 2, + "type": "UNK_PROFILE", + "routingDisabled": true + } + +Response Structure +------------------ +:cdn: The integral, unique identifier of the :ref:`profile-cdn` to which this :term:`Profile` belongs +:cdnName: The name of the :ref:`profile-cdn` to which this :term:`Profile` belongs +:description: The :term:`Profile`'s :ref:`profile-description` +:id: The :term:`Profile`'s :ref:`profile-id` +:lastUpdated: The date and time at which this :term:`Profile` was last updated, in :ref:`non-rfc-datetime` +:name: The :term:`Profile`'s :ref:`profile-name` +:routingDisabled: The :term:`Profile`'s :ref:`profile-routing-disabled` setting +:type: The :term:`Profile`'s :ref:`profile-type` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: Pnf+G9G3/+edt4b8PVsyGZHsNzaFEgphaGSminjRlRmMpWtuLAA20WZDUo3nX0QO81c2GCuFuEh9uMF2Vjeppg== + X-Server-Name: traffic_ops_golang/ + Date: Fri, 07 Dec 2018 21:45:06 GMT + Content-Length: 251 + + { "alerts": [ + { + "text": "profile was updated.", + "level": "success" + } + ], + "response": { + "id": 16, + "lastUpdated": "2018-12-07 21:45:06+00", + "name": "test", + "description": "A test profile for API examples", + "cdnName": null, + "cdn": 2, + "routingDisabled": true, + "type": "UNK_PROFILE" + }} + + +``DELETE`` +========== +Allows user to delete a :term:`Profile`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: PROFILE:DELETE, PROFILE:READ +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+------------------------------------------------------------+ + | Name | Description | + +======+============================================================+ + | ID | The :ref:`profile-id` of the :term:`Profile` being deleted | + +------+------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/profiles/16 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.62.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: HNmJkZaNW9yil08/3TnqZ5FllH6Rp+jgp3KI46FZdojLYcu+8jEhDLl1okoirdrHyU4R1c3hjCI0urN7PVvWDA== + X-Server-Name: traffic_ops_golang/ + Date: Fri, 07 Dec 2018 21:55:33 GMT + Content-Length: 62 + + { "alerts": [ + { + "text": "profile was deleted.", + "level": "success" + } + ]} diff --git a/docs/source/api/v5/profiles_id_export.rst b/docs/source/api/v5/profiles_id_export.rst new file mode 100644 index 0000000000..fd851433e2 --- /dev/null +++ b/docs/source/api/v5/profiles_id_export.rst @@ -0,0 +1,98 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-profiles-id-export: + +************************** +``profiles/{{ID}}/export`` +************************** + +``GET`` +======= + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: PROFILE:READ, PARAMETER:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +-----------+--------------------------------------------------------------+ + | Parameter | Description | + +===========+==============================================================+ + | id | The :ref:`profile-id` of the :term:`Profile` to be exported | + +-----------+--------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/profiles/3/export HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.62.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:profile: The exported :term:`Profile` + + :cdn: The name of the :ref:`profile-cdn` to which this :term:`Profile` belongs + :description: The :term:`Profile`'s :ref:`profile-description` + :name: The :term:`Profile`'s :ref:`profile-name` + :type: The :term:`Profile`'s :ref:`profile-type` + +:parameters: An array of :term:`Parameters` in use by this :term:`Profile` + + :config_file: The :term:`Parameter`'s :ref:`parameter-config-file` + :name: :ref:`parameter-name` of the :term:`Parameter` + :value: The :term:`Parameter`'s :ref:`parameter-value` + +.. 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-Disposition: attachment; filename="GLOBAL.json" + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: mzP7DVxFAGhICxqagwDyBDRea7oBZPMAx7NCDeOBVCRqlcCFFe7XL3JP58b80aaVOW/2ZGfg/jpYF70cdDfzQA== + X-Server-Name: traffic_ops_golang/ + Date: Fri, 13 Sep 2019 20:14:42 GMT + Transfer-Encoding: gzip + + + { "profile": { + "name": "GLOBAL", + "description": "Global Traffic Ops profile", + "cdn": "ALL", + "type": "UNK_PROFILE" + }, + "parameters": [ + { + "config_file": "global", + "name": "tm.instance_name", + "value": "Traffic Ops CDN" + }, + { + "config_file": "global", + "name": "tm.toolname", + "value": "Traffic Ops" + } + ]} diff --git a/docs/source/api/v5/profiles_id_parameters.rst b/docs/source/api/v5/profiles_id_parameters.rst new file mode 100644 index 0000000000..3181404734 --- /dev/null +++ b/docs/source/api/v5/profiles_id_parameters.rst @@ -0,0 +1,236 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-profiles-id-parameters: + +****************************** +``profiles/{{ID}}/parameters`` +****************************** + +``GET`` +======= + +Retrieves all :term:`Parameters` assigned to the :term:`Profile`. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: PROFILE:READ, PARAMETER:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+------------------------------------------------------------------------------------------+ + | Name | Description | + +======+==========================================================================================+ + | ID | The :ref:`profile-id` of the :term:`Profile` for which :term:`Parameters` will be listed | + +------+------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/parameters/profile/GLOBAL HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:configFile: The :term:`Parameter`'s :ref:`parameter-config-file` +:id: The :term:`Parameter`'s :ref:`parameter-id` +:lastUpdated: The date and time at which this :term:`Parameter` was last updated, in :ref:`non-rfc-datetime` +:name: :ref:`parameter-name` of the :term:`Parameter` +:profiles: An array of :term:`Profile` :ref:`Names ` that use this :term:`Parameter` +:secure: A boolean value that describes whether or not the :term:`Parameter` is :ref:`parameter-secure` +:value: The :term:`Parameter`'s :ref:`parameter-value` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: NudgZXUNyKNpmSFf856KEjyy+Pin/bFhG9NoRBDAxYbRKt2T5fF5Ze7sUNZfFI5n/ZZsgbx6Tsgtfd7oM6j+eg== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 05 Dec 2018 21:08:56 GMT + Content-Length: 542 + + { "response": [ + { + "configFile": "global", + "id": 4, + "lastUpdated": "2018-12-05 17:50:49+00", + "name": "tm.instance_name", + "secure": false, + "value": "Traffic Ops CDN" + }, + { + "configFile": "global", + "id": 5, + "lastUpdated": "2018-12-05 17:50:49+00", + "name": "tm.toolname", + "secure": false, + "value": "Traffic Ops" + }, + { + "configFile": "regex_revalidate.config", + "id": 7, + "lastUpdated": "2018-12-05 17:50:49+00", + "name": "maxRevalDurationDays", + "secure": false, + "value": "90" + } + ]} + +``POST`` +======== + +Associates :term:`Parameters` to a :term:`Profile`. If the :term:`Parameter` does not exist, creates it and associates it to the :term:`Profile`. If the :term:`Parameter` already exists, associates it to the :term:`Profile`. If the :term:`Parameter` is already associated with the :term:`Profile`, keep the association. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: PROFILE:UPDATE, PROFILE:READ, PARAMETER:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+-------------------------------------------------------------------------------------------+ + | Name | Description | + +======+===========================================================================================+ + | ID | The :ref:`profile-id` of the :term:`Profile` to which :term:`Parameters` will be assigned | + +------+-------------------------------------------------------------------------------------------+ + +This endpoint accepts two formats for the request payload: + +Single Object Format + For assigning a single :term:`Parameter` to a single :term:`Profile` +Parameter Array Format + For making multiple assignments of :term:`Parameters` to :term:`Profiles` simultaneously + +.. warning:: Most API endpoints dealing with :term:`Parameters` treat :ref:`parameter-secure` as a boolean value, whereas this endpoint takes the legacy approach of treating it as an integer. Be careful when passing data back and forth, as boolean values will **not** be accepted by this endpoint! + +Single Parameter Format +""""""""""""""""""""""" +:configFile: The :term:`Parameter`'s :ref:`parameter-config-file` +:name: :ref:`parameter-name` of the :term:`Parameter` +:secure: A boolean value that describes whether or not the :term:`Parameter` is :ref:`parameter-secure` +:value: The :term:`Parameter`'s :ref:`parameter-value` + +.. code-block:: http + :caption: Response Example - Single Parameter Format + + POST /api/5.0/profiles/18/parameters HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 99 + Content-Type: application/json + + { + "name": "test", + "configFile": "quest", + "value": "A test parameter for API examples", + "secure": 0 + } + + +Parameter Array Format +"""""""""""""""""""""" +:configFile: The :term:`Parameter`'s :ref:`parameter-config-file` +:name: :ref:`parameter-name` of the :term:`Parameter` +:secure: A boolean value that describes whether or not the :term:`Parameter` is :ref:`parameter-secure` +:value: The :term:`Parameter`'s :ref:`parameter-value` + +.. code-block:: http + :caption: Request Example - Parameter Array Format + + POST /api/5.0/profiles/18/parameters HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 212 + Content-Type: application/json + + [{ + "name": "test", + "configFile": "quest", + "value": "A test parameter for API examples", + "secure": 0 + }, + { + "name": "foo", + "configFile": "bar", + "value": "Another test parameter for API examples", + "secure": 0 + }] + +Response Structure +------------------ +:parameters: An array of objects representing the :term:`Parameters` which have been assigned + + :configFile: The :term:`Parameter`'s :ref:`parameter-config-file` + :name: :ref:`parameter-name` of the :term:`Parameter` + :secure: A boolean value that describes whether or not the :term:`Parameter` is :ref:`parameter-secure` + :value: The :term:`Parameter`'s :ref:`parameter-value` + +:profileId: The :ref:`profile-id` of the :term:`Profile` to which the :term:`Parameter`\ (s) have been assigned +:profileName: :ref:`profile-name` of the :term:`Profile` to which the :term:`Parameter`\ (s) have been assigned + +.. code-block:: http + :caption: Response Example - Single Parameter Format + + 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: R2QUyCaNvKvVv/PNVNmEd/ma5h/iP1fMJlqhv+x2jE/zNpHJ1KVXt6s3btB8nnHv6IDF/gt5kIzQ0mbW5U8bpg== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 10 Dec 2018 14:45:28 GMT + Content-Length: 253 + + { "alerts": [ + { + "text": "Assign parameters successfully to profile test", + "level": "success" + } + ], + "response": { + "parameters": [ + { + "configFile": "quest", + "name": "test", + "secure": 0, + "value": "A test parameter for API examples", + "id": 126 + } + ], + "profileId": 18, + "profileName": "test" + }} + +.. note:: The format of the request does not affect the format of the response. ``parameters`` will be an array either way. diff --git a/docs/source/api/v5/profiles_import.rst b/docs/source/api/v5/profiles_import.rst new file mode 100644 index 0000000000..85b6d2e3ca --- /dev/null +++ b/docs/source/api/v5/profiles_import.rst @@ -0,0 +1,115 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-profiles-import: + +******************* +``profiles/import`` +******************* + +``POST`` +======== + +Imports a :term:`Profile` that was exported via :ref:`to-api-profiles-id-export` + +.. note:: On import of the :term:`Profile` :term:`Parameters` if a :term:`Parameter` already exists with the same :ref:`parameter-name`, :ref:`parameter-config-file` and :ref:`parameter-value` it will link that to the :term:`Profile` instead of creating it. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: PROFILE:CREATE, PARAMETER:CREATE, PROFILE:READ, PARAMETER:READ +:Response Type: Object + +Request Structure +----------------- + +:profile: The exported :term:`Profile` + + :cdn: The name of the :ref:`profile-cdn` to which this :term:`Profile` belongs + :description: The :term:`Profile`'s :ref:`profile-description` + :name: The :term:`Profile`'s :ref:`profile-name` + :type: The :term:`Profile`'s :ref:`profile-type` + +:parameters: An array of :term:`Parameters` in use by this :term:`Profile` + + :config_file: The :term:`Parameter`'s :ref:`parameter-config-file` + :name: :ref:`parameter-name` of the :term:`Parameter` + :value: The :term:`Parameter`'s :ref:`parameter-value` + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/profiles/import HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.62.0 + Accept: */* + Cookie: mojolicious=... + Content-Type: application/json + + { "profile": { + "name": "GLOBAL", + "description": "Global Traffic Ops profile", + "cdn": "ALL", + "type": "UNK_PROFILE" + }, + "parameters": [ + { + "config_file": "global", + "name": "tm.instance_name", + "value": "Traffic Ops CDN" + }, + { + "config_file": "global", + "name": "tm.toolname", + "value": "Traffic Ops" + } + ]} + +Response Structure +------------------ +:cdn: The name of the :ref:`profile-cdn` to which this :term:`Profile` belongs +:description: The :term:`Profile`'s :ref:`profile-description` +:name: The :term:`Profile`'s :ref:`profile-name` +:type: The :term:`Profile`'s :ref:`profile-type` +:id: The :term:`Profile`'s :ref:`profile-id` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: mzP7DVxFAGhICxqagwDyBDRea7oBZPMAx7NCDeOBVCRqlcCFFe7XL3JP58b80aaVOW/2ZGfg/jpYF70cdDfzQA== + X-Server-Name: traffic_ops_golang/ + Date: Fri, 13 Sep 2019 20:14:42 GMT + Transfer-Encoding: gzip + + + { "alerts": [ + { + "level": "success", + "text": "Profile imported [ Global ] with 2 new and 0 existing parameters" + } + ], + "response": { + "cdn": "ALL", + "name": "Global", + "id": 18, + "type": "UNK_PROFILE", + "description": "Global Traffic Ops profile" + }} diff --git a/docs/source/api/v5/profiles_name_name_copy_copy.rst b/docs/source/api/v5/profiles_name_name_copy_copy.rst new file mode 100644 index 0000000000..ad24a258a7 --- /dev/null +++ b/docs/source/api/v5/profiles_name_name_copy_copy.rst @@ -0,0 +1,89 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-profiles-name-name-copy-copy: + +**************************************** +``profiles/name/{{name}}/copy/{{copy}}`` +**************************************** + +``POST`` +======== +Copy :term:`Profile` to a new :term:`Profile`. The new :term:`Profile`'s :ref:`profile-name` must not already exist. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: PROFILE:CREATE, PROFILE:READ, PARAMETER:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+-----------------------------------------------------------------------------+ + | Name | Description | + +======+=============================================================================+ + | name | The :ref:`profile-name` of the new :term:`Profile` | + +------+-----------------------------------------------------------------------------+ + | copy | The :ref:`profile-name` of :term:`Profile` from which the copy will be made | + +------+-----------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/profiles/name/GLOBAL_copy/copy/GLOBAL HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.62.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:description: The new :term:`Profile`'s :ref:`profile-description` +:id: The :ref:`profile-id` of the new :term:`Profile` +:idCopyFrom: The :ref:`profile-id` of the :term:`Profile` from which the copy was made +:name: The :ref:`profile-name` of the new :term:`Profile` +:profileCopyFrom: The :ref:`profile-name` of the :term:`Profile` from which the copy was made + +.. 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 + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Cache-Control: no-cache, no-store, max-age=0, must-revalidate + Content-Type: application/json + Date: Fri, 07 Dec 2018 22:03:54 GMT + X-Server-Name: traffic_ops_golang/ + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: r6V9viEZui1WCns0AUGEx1MtxjjXiU8SZVOtSQjeq7ZJDLl5s8fMmjJdR/HRWduHn7Ax6GzYhoKwnIjMyc7ZWg== + Content-Length: 252 + + { "alerts": [ + { + "level": "success", + "text": "Created new profile [ GLOBAL_copy ] from existing profile [ GLOBAL ]" + } + ], + "response": { + "idCopyFrom": 1, + "name": "GLOBAL_copy", + "profileCopyFrom": "GLOBAL", + "id": 17, + "description": "Global Traffic Ops profile, DO NOT DELETE" + }} diff --git a/docs/source/api/v5/profiles_name_name_parameters.rst b/docs/source/api/v5/profiles_name_name_parameters.rst new file mode 100644 index 0000000000..0ad2ff7a52 --- /dev/null +++ b/docs/source/api/v5/profiles_name_name_parameters.rst @@ -0,0 +1,240 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-profiles-name-name-parameters: + +************************************* +``profiles/name/{{name}}/parameters`` +************************************* + +``GET`` +======= +Retrieves all :term:`Parameters` associated with a given :term:`Profile` + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: PROFILE:READ, PARAMETER:READ +:Response Type: None + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+--------------------------------------------------------------------------------------------+ + | Name | Description | + +======+============================================================================================+ + | name | The :ref:`profile-name` of the :term:`Profile` for which :term:`Parameters` will be listed | + +------+--------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/profiles/name/GLOBAL/parameters HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:configFile: The :term:`Parameter`'s :ref:`parameter-config-file` +:id: The :term:`Parameter`'s :ref:`parameter-id` +:lastUpdated: The date and time at which this :term:`Parameter` was last updated, in :ref:`non-rfc-datetime` +:name: :ref:`parameter-name` of the :term:`Parameter` +:profiles: An array of :term:`Profile` :ref:`Names ` that use this :term:`Parameter` +:secure: A boolean value that describes whether or not the :term:`Parameter` is :ref:`parameter-secure` +:value: The :term:`Parameter`'s :ref:`parameter-value` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: NudgZXUNyKNpmSFf856KEjyy+Pin/bFhG9NoRBDAxYbRKt2T5fF5Ze7sUNZfFI5n/ZZsgbx6Tsgtfd7oM6j+eg== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 05 Dec 2018 21:52:08 GMT + Content-Length: 542 + + { "response": [ + { + "configFile": "global", + "id": 4, + "lastUpdated": "2018-12-05 17:50:49+00", + "name": "tm.instance_name", + "secure": false, + "value": "Traffic Ops CDN" + }, + { + "configFile": "global", + "id": 5, + "lastUpdated": "2018-12-05 17:50:49+00", + "name": "tm.toolname", + "secure": false, + "value": "Traffic Ops" + }, + { + "configFile": "regex_revalidate.config", + "id": 7, + "lastUpdated": "2018-12-05 17:50:49+00", + "name": "maxRevalDurationDays", + "secure": false, + "value": "90" + } + ]} + +``POST`` +======== +Associates :term:`Parameters` to a :term:`Profile`. If the :term:`Parameter` does not exist, creates it and associates it to the :term:`Profile`. If the :term:`Parameter` already exists, associates it to the :term:`Profile`. If the :term:`Parameter` is already associated with the :term:`Profile`, keep the association. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: PROFILE:UPDATE, PROFILE:READ, PARAMETER:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+---------------------------------------------------------------------------------------------+ + | Name | Description | + +======+=============================================================================================+ + | name | The :ref:`profile-name` of the :term:`Profile` to which :term:`Parameters` will be assigned | + +------+---------------------------------------------------------------------------------------------+ + +This endpoint accepts two formats for the request payload: + +Single Parameter Format + Specify a single parameter to assign to the specified profile +Parameter Array Format + Specify multiple parameters to assign to the specified profile + +.. warning:: Most API endpoints dealing with parameters treat ``secure`` as a boolean value, whereas this endpoint takes the legacy approach of treating it as an integer. Be careful when passing data back and forth, as boolean values will **not** be accepted by this endpoint! + +Single Parameter Format +""""""""""""""""""""""" +:configFile: The :term:`Parameter`'s :ref:`parameter-config-file` +:name: :ref:`parameter-name` of the :term:`Parameter` +:secure: A boolean value that describes whether or not the :term:`Parameter` is :ref:`parameter-secure` +:value: The :term:`Parameter`'s :ref:`parameter-value` + +.. code-block:: http + :caption: Request Example - Single Parameter Format + + POST /api/5.0/profiles/name/test/parameters HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 99 + Content-Type: application/json + + { + "name": "test", + "configFile": "quest", + "value": "A test parameter for API examples", + "secure": 0 + } + +Parameter Array Format +"""""""""""""""""""""" +:configFile: The :term:`Parameter`'s :ref:`parameter-config-file` +:name: :ref:`parameter-name` of the :term:`Parameter` +:secure: A boolean value that describes whether or not the :term:`Parameter` is :ref:`parameter-secure` +:value: The :term:`Parameter`'s :ref:`parameter-value` + +.. code-block:: http + :caption: Request Example - Parameter Array Format + + POST /api/5.0/profiles/name/test/parameters HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 212 + Content-Type: application/json + + [{ + "name": "test", + "configFile": "quest", + "value": "A test parameter for API examples", + "secure": 0 + }, + { + "name": "foo", + "configFile": "bar", + "value": "Another test parameter for API examples", + "secure": 0 + }] + +Response Structure +------------------ +:parameters: An array of objects representing the :term:`Parameters` which have been assigned + + :configFile: The :term:`Parameter`'s :ref:`parameter-config-file` + :name: :ref:`parameter-name` of the :term:`Parameter` + :secure: A boolean value that describes whether or not the :term:`Parameter` is :ref:`parameter-secure` + :value: The :term:`Parameter`'s :ref:`parameter-value` + +:profileId: The :ref:`profile-id` of the :term:`Profile` to which the :term:`Parameter`\ (s) have been assigned +:profileName: :ref:`profile-name` of the :term:`Profile` to which the :term:`Parameter`\ (s) have been assigned + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: HQWqOkoYHjvcxheWPrHOb0oZnUC+qLG1LO4OjtsLLnZYVUIu/qgJrzvziPnKq3FEHUWaZrnDCZM/iZD8AXOKBw== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 10 Dec 2018 14:20:22 GMT + Content-Length: 357 + + { "alerts": [ + { + "text": "Assign parameters successfully to profile test", + "level": "success" + } + ], + "response": { + "parameters": [ + { + "configFile": "quest", + "name": "test", + "secure": 0, + "value": "A test parameter for API examples", + "id": 126 + }, + { + "configFile": "bar", + "name": "foo", + "secure": 0, + "value": "Another test parameter for API examples", + "id": 129 + } + ], + "profileId": 18, + "profileName": "test" + }} + +.. note:: The format of the request does not affect the format of the response. ``parameters`` will be an array either way. diff --git a/docs/source/api/v5/regions.rst b/docs/source/api/v5/regions.rst new file mode 100644 index 0000000000..6f13234871 --- /dev/null +++ b/docs/source/api/v5/regions.rst @@ -0,0 +1,229 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-regions: + +*********** +``regions`` +*********** + +``GET`` +======= +Retrieves information about :term:`Regions` + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: REGION:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +===========+==========+===============================================================================================================+ + | division | no | Filter :term:`Regions` by the integral, unique identifier of the :term:`Division` which contains them | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | id | no | Filter :term:`Regions` by integral, unique identifier | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | name | no | Filter :term:`Regions` by name | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | orderby | no | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` | + | | | array | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long | + | | | and the first page is 1. If ``offset`` was defined, this query parameter has no effect. ``limit`` must be | + | | | defined to make use of ``page``. | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/regions?division=1 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------- +:divisionName: The name of the division which contains this region +:divisionId: The integral, unique identifier of the division which contains this region +:id: An integral, unique identifier for this region +:lastUpdated: The date and time at which this region was last updated, in :ref:`non-rfc-datetime` +:name: The region name + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: nSYbR+fRXaxhYl7dWgf0Udo2AsiXEnwvED1CPbk7ZNWK03I3TOhtmCQx9ABnJJ6xKYnlt6EKMeopVTK0nKU+SQ== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 06 Dec 2018 01:58:38 GMT + Content-Length: 117 + + { "response": [ + { + "divisionName": "Quebec", + "division": 1, + "id": 2, + "lastUpdated": "2018-12-05 17:50:58+00", + "name": "Montreal" + } + ]} + +.. _to-api-regions-post: + +``POST`` +======== +Creates a new region + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: REGION:CREATE, REGION:READ +:Response Type: Object + +Request Structure +----------------- +:division: The integral, unique identifier of the division which shall contain the new region\ [1]_ +:divisionName: The name of the division which shall contain the new region\ [1]_ +:name: The name of the region + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/regions HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 65 + Content-Type: application/json + + { + "name": "Manchester", + "division": "4", + "divisionName": "England" + } + +.. [1] The only "division" key that actually matters in the request body is ``division``; ``divisionName`` is not validated and has no effect - particularly not the effect of re-naming the division - beyond changing the name in the API response to this request. Subsequent requests will reveal the true name of the division. Note that if ``divisionName`` is not present in the request body it will be ``null`` in the response, but again further requests will show the true division name (provided it has been assigned to a division). + +Response Structure +------------------ +:divisionName: The name of the division which contains this region +:divisionId: The integral, unique identifier of the division which contains this region +:id: An integral, unique identifier for this region +:lastUpdated: The date and time at which this region was last updated, in :ref:`non-rfc-datetime` +:name: The region name + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: ezxk+iP7o7KE7zpWmGc0j8nz5k+1wAzY0HiNiA2xswTQrt+N+6CgQqUV2r9G1HAsPNr0HF2PhYs/Xr7DrYOw0A== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 06 Dec 2018 02:14:45 GMT + Content-Length: 178 + + { "alerts": [ + { + "text": "region was created.", + "level": "success" + } + ], + "response": { + "divisionName": "England", + "division": 3, + "id": 5, + "lastUpdated": "2018-12-06 02:14:45+00", + "name": "Manchester" + }} + +``DELETE`` +========== +Deletes a region. If no query parameter is specified, a ``400 Bad Request`` response is returned. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: REGION:DELETE +:Response Type: Object + +Request Structure +----------------- + +.. table:: Request Query Parameters + + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +===========+==========+===============================================================================================================+ + | id | no | Delete :term:`Region` by integral, unique identifier | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | name | no | Delete :term:`Region` by name | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/regions?name=Manchester HTTP/1.1 + User-Agent: curl/7.29.0 + Host: trafficops.infra.ciab.test + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Fri, 07 Feb 2020 13:56:24 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: yNqXKcoiohEbJrEkH8LD1zifh87dIusuvUqgQnYueyKqCXkfd5bQvQ0OhQ2AAdSZa/oe2SAqMjojGsUlxHtIQw== + X-Server-Name: traffic_ops_golang/ + Date: Fri, 07 Feb 2020 12:56:24 GMT + Content-Length: 62 + + { + "alerts": [ + { + "text": "region was deleted.", + "level": "success" + } + ] + } diff --git a/docs/source/api/v5/regions_id.rst b/docs/source/api/v5/regions_id.rst new file mode 100644 index 0000000000..ed92a29d8c --- /dev/null +++ b/docs/source/api/v5/regions_id.rst @@ -0,0 +1,100 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-regions-id: + +****************** +``regions/{{ID}}`` +****************** + +``PUT`` +======= +Updates a :term:`Region`. + +:Auth. Required: Yes +:Role(s) Required: "admin" or "operator" +:Permissions Required: REGION:UPDATE, REGION:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+---------------------------------------------------------+ + | Name | Description | + +======+=========================================================+ + | ID | The integral, unique identifier of the region to update | + +------+---------------------------------------------------------+ + +:division: The new integral, unique identifier of the division which shall contain the region\ [1]_ +:divisionName: The new name of the division which shall contain the region\ [1]_ +:name: The new name of the region + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/regions/5 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 60 + Content-Type: application/json + + { + "name": "Leeds", + "division": 3, + "divisionName": "England" + } + +.. [1] The only "division" key that actually matters in the request body is ``division``; ``divisionName`` is not validated and has no effect - particularly not the effect of re-naming the division - beyond changing the name in the API response to this request. Subsequent requests will reveal the true name of the division. Note that if ``divisionName`` is not present in the request body it will be ``null`` in the response, but again further requests will show the true division name (provided it has been assigned to a division). + + +Response Structure +------------------ +:divisionName: The name of the division which contains this region +:divisionId: The integral, unique identifier of the division which contains this region +:id: An integral, unique identifier for this region +:lastUpdated: The date and time at which this region was last updated, in :ref:`non-rfc-datetime` +:name: The region name + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: 7SVj4q7dtSTNQEJlBApEwlad28WBVFnpdHaatoIpNfeLltfcpcdVTcOKB4JXQv7rlSD2p/TxBQC6EXpxwYTnKQ== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 06 Dec 2018 02:23:40 GMT + Content-Length: 173 + + { "alerts": [ + { + "text": "region was updated.", + "level": "success" + } + ], + "response": { + "divisionName": "England", + "division": 3, + "id": 5, + "lastUpdated": "2018-12-06 02:23:40+00", + "name": "Leeds" + }} diff --git a/docs/source/api/v5/roles.rst b/docs/source/api/v5/roles.rst new file mode 100644 index 0000000000..9c66fcc11b --- /dev/null +++ b/docs/source/api/v5/roles.rst @@ -0,0 +1,336 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-roles: + +********* +``roles`` +********* + +``GET`` +======= +Retrieves all user :term:`Roles`. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: ROLE:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +===========+==========+===============================================================================================================+ + | id | no | Return only the :term:`Role` identified by this integral, unique identifier | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | name | no | Return only the :term:`Role` with this name | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | orderby | no | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` | + | | | array | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long | + | | | and the first page is 1. If ``offset`` was defined, this query parameter has no effect. ``limit`` must be | + | | | defined to make use of ``page``. | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/roles?name=read-only HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:permissions: An array of the names of the Permissions given to this :term:`Role` +:description: A description of the :term:`Role` +:id: The integral, unique identifier for this :term:`Role` +:name: The name of the :term:`Role` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: TEDXlQqWMSnJbL10JtFdbw0nqciNpjc4bd6m7iAB8aymakWeF+ghs1k5LayjdzHcjeDE8UNF/HXSxOFvoLFEuA== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 25 Aug 2021 20:10:34 GMT + Content-Length: 888 + + { "response": [ + { + "name": "read-only", + "description": "Has access to all read capabilities", + "permissions": [ + "auth", + "api-endpoints-read", + "asns-read", + "cache-config-files-read", + "cache-groups-read", + "capabilities-read", + "cdns-read", + "cdn-security-keys-read", + "change-logs-read", + "consistenthash-read", + "coordinates-read", + "delivery-services-read", + "delivery-service-security-keys-read", + "delivery-service-requests-read", + "delivery-service-servers-read", + "divisions-read", + "to-extensions-read", + "federations-read", + "hwinfo-read", + "jobs-read", + "origins-read", + "parameters-read", + "phys-locations-read", + "profiles-read", + "regions-read", + "roles-read", + "server-capabilities-read", + "servers-read", + "service-categories-read", + "stats-read", + "statuses-read", + "static-dns-entries-read", + "steering-read", + "steering-targets-read", + "system-info-read", + "tenants-read", + "types-read", + "users-read" + ], + "lastUpdated": "2021-05-03T14:50:18.93513-06:00", + } + ]} + +``POST`` +======== +Creates a new :term:`Role`. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: ROLE:CREATE, ROLE:READ +:Response Type: Object + +Request Structure +----------------- +:permissions: An optional array of permission names that will be granted to the new :term:`Role`\ [#permissions]_ +:description: A helpful description of the :term:`Role`'s purpose. +:name: The name of the new :term:`Role` + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/roles HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 56 + Content-Type: application/json + + { + "name": "test", + "description": "quest" + } + + +Response Structure +------------------ +:permissions: An array of the names of the Permissions given to this :term:`Role` + + .. tip:: This can be ``null`` *or* empty, depending on whether it was present in the request body, or merely empty. Obviously, it can also be a populated array. + +:description: A description of the :term:`Role` +:id: The integral, unique identifier for this :term:`Role` +:name: The name of the :term:`Role` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: gzfc7m/in5vVsVP+Y9h6JJfDhgpXKn9VAzoiPENhKbQfP8Q6jug08Rt2AK/3Nz1cx5zZ8P9IjVxDdIg7mlC8bw== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 04 Sep 2019 17:44:42 GMT + Content-Length: 128 + + { "alerts": [{ + "text": "role was created.", + "level": "success" + }], + "response": { + "name": "test", + "description": "quest", + "permissions": null + }} + +``PUT`` +======= +Replaces an existing :term:`Role` with one provided by the request\ [#admin]_. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: ROLE:UPDATE, ROLE:READ +:Response Type: + +Request Structure +----------------- +.. table:: Request Query Parameters + + +------+----------+--------------------------------------------------------------------+ + | Name | Required | Description | + +======+==========+====================================================================+ + | name | yes | The name of the :term:`Role` to be updated | + +------+----------+--------------------------------------------------------------------+ + +:permissions: An optional array of permission names that will be granted to the new :term:`Role` + + .. warning:: When not present, the affected :term:`Role`'s Permissions will be unchanged - *not* removed, unlike when the array is empty. + +:description: A helpful description of the :term:`Role`'s purpose. +:name: The new name of the :term:`Role` + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/roles?name=test HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 56 + Content-Type: application/json + + { + "name":"test", + "description": "quest_updated" + } + +Response Structure +------------------ +:permissions: An array of the names of the Permissions given to this :term:`Role` + + .. tip:: This can be ``null`` *or* empty, depending on whether it was present in the request body, or merely empty. Obviously, it can also be a populated array. + + .. warning:: If no ``permissions`` array was given in the request, this will *always* be ``null``, even if the :term:`Role` has Permissions that would have gone unchanged. + +:description: A description of the :term:`Role` +:name: The name of the :term:`Role` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: mlHQenE1Q3gjrIK2lC2hfueQOaTCpdYEfboN0A9vYPUIwTiaF5ZaAMPQBdfGyiAhgHRxowITs3bR7s1L++oFTQ== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 05 Sep 2019 12:56:46 GMT + Content-Length: 136 + + { + "alerts": [ + { + "text": "role was updated.", + "level": "success" + } + ], + "response": { + "name": "test", + "description": "quest_updated", + "permissions": null + } + } + + +``DELETE`` +========== +Deletes a :term:`Role`\ [#admin]_. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: ROLE:DELETE, ROLE:READ +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Query Parameters + + +------+----------+--------------------------------------------------------------------+ + | Name | Required | Description | + +======+==========+====================================================================+ + | name | yes | The name of the :term:`Role` to be deleted | + +------+----------+--------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/roles?name=test HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: 10jeFZihtbvAus/XyHAW8rhgS9JBD+X/ezCp1iExYkEcHxN4gjr1L6x8zDFXORueBSlFldgtbWKT7QsmwCHUWA== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 05 Sep 2019 13:02:06 GMT + Content-Length: 60 + + { "alerts": [{ + "text": "role was deleted.", + "level": "success" + }]} + +.. [#permissions] ``permissions`` cannot include permissions that are not included in the permissions of the requesting user. In POST requests, if ``permissions`` is omitted or explicitly ``null``, it is treated as an empty set/array. +.. [#admin] The special :term:`Role` with the name "admin" cannot be modified or deleted - regardless of user Permissions. diff --git a/docs/source/api/v5/server_capabilities.rst b/docs/source/api/v5/server_capabilities.rst new file mode 100644 index 0000000000..a6d99a6145 --- /dev/null +++ b/docs/source/api/v5/server_capabilities.rst @@ -0,0 +1,254 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-server_capabilities: + +*********************** +``server_capabilities`` +*********************** + +``GET`` +======= +Retrieves :term:`Server Capabilities`. + +:Auth. Required: Yes +:Roles Required: "read-only" +:Permissions Required: SERVER-CAPABILITY:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +------+----------+-----------------------------------------------------+ + | Name | Required | Description | + +======+==========+=====================================================+ + | name | no | Return the :term:`Server Capability` with this name | + +------+----------+-----------------------------------------------------+ + +.. code-block:: http + :caption: Request Structure + + GET /api/5.0/server_capabilities?name=RAM HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:name: The name of this :term:`Server Capability` +:lastUpdated: The date and time at which this :term:`Server Capability` was last updated, in ISO-like format + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: EH8jo8OrCu79Tz9xpgT3YRyKJ/p2NcTmbS3huwtqRByHz9H6qZLQjA59RIPaVSq3ZxsU6QhTaox5nBkQ9LPSAA== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 07 Oct 2019 21:36:13 GMT + Content-Length: 68 + + { + "response": [ + { + "name": "RAM", + "lastUpdated": "2019-10-07 20:38:24+00" + } + ] + } + +``POST`` +======== +Create a new :term:`Server Capability`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: SERVER-CAPABILITY:CREATE, SERVER-CAPABILITY:READ +:Response Type: Object + +Request Structure +----------------- +:name: The name of the :term:`Server Capability` + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/server_capabilities HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 15 + Content-Type: application/json + + { + "name": "RAM" + } + +Response Structure +------------------ +:name: The name of this :term:`Server Capability` +:lastUpdated: The date and time at which this :term:`Server Capability` was last updated, in ISO-like format + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: ysdopC//JQI79BRUa61s6M2HzHxYHpo5RdcuauOoqCYxiVOoUhNZfOVydVkv8zDN2qA374XKnym4kWj3VzQIXg== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 07 Oct 2019 22:10:00 GMT + Content-Length: 137 + + { + "alerts": [ + { + "text": "server capability was created.", + "level": "success" + } + ], + "response": { + "name": "RAM", + "lastUpdated": "2019-10-07 22:10:00+00" + } + } + +``PUT`` +======== +Update an existing :term:`Server Capability`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: SERVER-CAPABILITY:UPDATE, SERVER-CAPABILITY:READ +:Response Type: Object + +Request Structure +----------------- +:name: The name of the :term:`Server Capability` + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/server_capabilities?name=RAM HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 15 + Content-Type: application/json + + { + "name": "HDD" + } + +Response Structure +------------------ +:name: The name of this :term:`Server Capability` +:lastUpdated: The date and time at which this :term:`Server Capability` was last updated, in ISO-like format + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: ysdopC//JQI79BRUa61s6M2HzHxYHpo5RdcuauOoqCYxiVOoUhNZfOVydVkv8zDN2qA374XKnym4kWj3VzQIXg== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 03 March 2021 21:22:08 GMT + Content-Length: 137 + + { + "alerts": [ + { + "text": "server capability was updated.", + "level": "success" + } + ], + "response": { + "name": "HDD", + "lastUpdated": "2021-03-03 21:22:08+00" + } + } + +``DELETE`` +========== +Deletes a specific :term:`Server Capability`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: SERVER-CAPABILITY:DELETE, SERVER-CAPABILITY:READ +:Response Type: ``undefined`` + + +Request Structure +----------------- +.. table:: Request Query Parameters + + +------+----------+---------------------------------------------------------+ + | Name | Required | Description | + +======+==========+=========================================================+ + | name | yes | The name of the :term:`Server Capability` to be deleted | + +------+----------+---------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/server_capabilities?name=RAM HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: 8zCAATbCzcqiqigGVBy7WF1duDuXu1Wg2DBe9yfqTw/c+yhE2eUk73hFTA/Oqt0kocaN7+1GkbFdPkQPvbnRaA== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 07 Oct 2019 20:44:40 GMT + Content-Length: 72 + + { + "alerts": [ + { + "text": "server capability was deleted.", + "level": "success" + } + ] + } diff --git a/docs/source/api/v5/server_server_capabilities.rst b/docs/source/api/v5/server_server_capabilities.rst new file mode 100644 index 0000000000..07af380aec --- /dev/null +++ b/docs/source/api/v5/server_server_capabilities.rst @@ -0,0 +1,228 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-server-server-capabilities: + +****************************** +``server_server_capabilities`` +****************************** + +``GET`` +======= +Gets all associations of :term:`Server Capabilities` to :term:`cache servers`. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: SERVER:READ, SERVER-CAPABILITY:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +------------------+----------+------------------------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +==================+==========+==============================================================================================================================+ + | serverId | no | Filter :term:`Server Capability` associations by the integral, unique identifier of the server to which they are assigned | + +------------------+----------+------------------------------------------------------------------------------------------------------------------------------+ + | serverHostName | no | Filter :term:`Server Capability` associations by the host name of the server to which they are assigned | + +------------------+----------+------------------------------------------------------------------------------------------------------------------------------+ + | serverCapability | no | Filter :term:`Server Capability` associations by :term:`Server Capability` name | + +------------------+----------+------------------------------------------------------------------------------------------------------------------------------+ + | orderby | no | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` array | + +------------------+----------+------------------------------------------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +------------------+----------+------------------------------------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +------------------+----------+------------------------------------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit. | + +------------------+----------+------------------------------------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long and the first | + | | | page is 1. If ``offset`` was defined, this query parameter has no effect. ``limit`` must be defined to make use of ``page``. | + +------------------+----------+------------------------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/server_server_capabilities HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:serverHostName: The server's host name +:serverId: The server's integral, unique identifier +:lastUpdated: The date and time at which this association between the server and the :term:`Server Capability` was last updated, in :ref:`non-rfc-datetime` +:serverCapability: The :term:`Server Capability`'s name + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: UFO3/jcBFmFZM7CsrsIwTfPc5v8gUiXqJm6BNp1boPb4EQBnWNXZh/DbBwhMAOJoeqDImoDlrLnrVjQGO4AooA== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 07 Oct 2019 22:15:11 GMT + Content-Length: 150 + + { + "response": [ + { + "lastUpdated": "2019-10-07 22:05:31+00", + "serverHostName": "atlanta-org-1", + "serverId": 260, + "serverCapability": "ram" + }, + { + "lastUpdated": "2019-10-07 22:05:31+00", + "serverHostName": "atlanta-org-2", + "serverId": 261, + "serverCapability": "disk" + } + ] + } + +``POST`` +======== +Associates a :term:`Server Capability` to a server. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: SERVER:UPDATE, SERVER:READ, SERVER-CAPABILITY:READ +:Response Type: Object + +Request Structure +----------------- +:serverId: The integral, unique identifier of a server to be associated with a :term:`Server Capability` +:serverCapability: The :term:`Server Capability`'s name to associate + +.. note:: The server referenced must be either an :term:`Edge-tier` or :term:`Mid-tier cache server`. + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/server_server_capabilities HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 84 + Content-Type: application/json + + { + "serverId": 1, + "serverCapability": "disk" + } + +Response Structure +------------------ +:serverId: The integral, unique identifier of the newly associated server +:lastUpdated: The date and time at which this association between the server and the :term:`Server Capability` was last updated, in :ref:`non-rfc-datetime` +:serverCapability: The :term:`Server Capability`'s name + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: eQrl48zWids0kDpfCYmmtYMpegjnFxfOVvlBYxxLSfp7P7p6oWX4uiC+/Cfh2X9i3G+MQ36eH95gukJqOBOGbQ== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 07 Oct 2019 22:15:11 GMT + Content-Length: 157 + + { + "alerts": [ + { + "text": "server server_capability was created.", + "level": "success" + } + ], + "response": { + "lastUpdated": "2019-10-07 22:15:11+00", + "serverId": 1, + "serverCapability": "disk" + } + } + +``DELETE`` +========== +Disassociate a server from a :term:`Server Capability`. + + .. note:: If the ``serverCapability`` is a :term:`Server Capability` required by a :term:`Delivery Service` that to which the server is assigned the DELETE will be blocked until either the server is unassigned from the :term:`Delivery Service` or the :term:`Server Capability` is no longer required by the :term:`Delivery Service`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: SERVER:UPDATE, SERVER:READ, SERVER-CAPABILITY:READ +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Query Parameters + + +------------------+----------+-----------------------------------------------------------------+ + | Name | Required | Description | + +==================+==========+=================================================================+ + | serverId | yes | The integral, unique identifier of the server to disassociate | + +------------------+----------+-----------------------------------------------------------------+ + | serverCapability | yes | term:`Server Capability` name to disassociate from given server | + +------------------+----------+-----------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/server_server_capabilities?serverId=1&serverCapability=disk HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: UFO3/jcBFmFZM7CsrsIwTfPc5v8gUiXqJm6BNp1boPb4EQBnWNXZh/DbBwhMAOJoeqDImoDlrLnrVjQGO4AooA== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 07 Oct 2019 22:15:11 GMT + Content-Length: 96 + + { + "alerts": [ + { + "text": "server server_capability was deleted.", + "level": "success" + } + ] + } diff --git a/docs/source/api/v5/servercheck.rst b/docs/source/api/v5/servercheck.rst new file mode 100644 index 0000000000..93f7483810 --- /dev/null +++ b/docs/source/api/v5/servercheck.rst @@ -0,0 +1,151 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-servercheck: + +*************** +``servercheck`` +*************** + +.. seealso:: :ref:`to-check-ext` + +``GET`` +======= +Fetches identifying and meta information as well as "check" values regarding all servers that have a :term:`Type` with a name beginning with "EDGE" or "MID" (ostensibly this is equivalent to all :term:`cache servers`). + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: SERVER-CHECK:READ, SERVER:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-----------+----------+------------------------------------------------------------------------------------+ + | Name | Required | Description | + +===========+==========+====================================================================================+ + | id | no | Return only :term:`cache servers` with this integral, unique identifier (id) | + +-----------+----------+------------------------------------------------------------------------------------+ + | hostName | no | Return only :term:`cache servers` with this host_name | + +-----------+----------+------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example with ``hostName`` query param + + GET /api/5.0/servercheck?hostName=edge HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +.. code-block:: http + :caption: Request Example with ``id`` query param + + GET /api/5.0/servercheck?id=12 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:adminState: The name of the server's :term:`Status` - called "adminState" for legacy reasons +:cacheGroup: The name of the :term:`Cache Group` to which the server belongs +:checks: An optionally present map of the names of "checks" to their values. Only numeric and boolean checks are represented, and boolean checks are represented as integers with ``0`` meaning "false" and ``1`` meaning "true". Will not appear if the server in question has no valued "checks". +:hostName: The (short) hostname of the server +:id: The server's integral, unique identifier +:profile: The name of the :term:`Profile` used by the server +:revalPending: A boolean that indicates whether or not the server has pending revalidations +:type: The name of the server's :term:`Type` +:updPending: A boolean that indicates whether or not the server has pending updates + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Thu, 18 Feb 2021 20:00:19 GMT; Max-Age=3600; HttpOnly + X-Server-Name: traffic_ops_golang/ + Date: Thu, 18 Feb 2021 19:00:19 GMT + Content-Length: 352 + + { "response": [ + { + "adminState": "REPORTED", + "cacheGroup": "CDN_in_a_Box_Edge", + "id": 12, + "hostName": "edge", + "revalPending": false, + "profile": "ATS_EDGE_TIER_CACHE", + "type": "EDGE", + "updPending": false + } + ]} + +``POST`` +======== +Post a server check result to the "serverchecks" table. Updates the resulting value from running a given check extension on a server. + +:Auth. Required: Yes +:Roles Required: None\ [1]_ +:Permissions Required: SERVER-CHECK:CREATE, SERVER-CHECK:READ, SERVER:READ +:Response Type: Object + +Request Structure +----------------- +The request only requires to have either ``host_name`` or ``id`` defined. + +:host_name: The hostname of the server to which this "servercheck" refers. +:id: The id of the server to which this "servercheck" refers. +:servercheck_short_name: The short name of the "servercheck". +:value: The value of the "servercheck" + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/servercheck HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 113 + Content-Type: application/json + + { + "id": 1, + "host_name": "edge", + "servercheck_short_name": "test", + "value": 1 + } + +Response Structure +------------------ +.. code-block:: json + :caption: Response Example + + { "alerts": [ + { + "level": "success", + "text": "Server Check was successfully updated." + } + ]} + +.. [1] No roles are required to use this endpoint, however access is controlled by username. Only the reserved user ``extension`` is permitted the use of this endpoint. diff --git a/docs/source/api/v5/servercheck_extensions.rst b/docs/source/api/v5/servercheck_extensions.rst new file mode 100644 index 0000000000..e46f93f709 --- /dev/null +++ b/docs/source/api/v5/servercheck_extensions.rst @@ -0,0 +1,215 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-servercheck_extensions: + +************************** +``servercheck/extensions`` +************************** +.. seealso:: :ref:`admin-to-ext-script` + +``GET`` +======= +Retrieves the list of Traffic Ops extensions. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: SERVER-CHECK:READ, SERVER:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +------------------+----------+------------------------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +==================+==========+==============================================================================================================================+ + | id | no | Filter TO Extensions by the integral, unique identifier of an Extension | + +------------------+----------+------------------------------------------------------------------------------------------------------------------------------+ + | name | no | Filter TO Extensions by the name of an Extension | + +------------------+----------+------------------------------------------------------------------------------------------------------------------------------+ + | script_file | no | Filter TO Extensions by the base filename of the script that runs for the Extension | + +------------------+----------+------------------------------------------------------------------------------------------------------------------------------+ + | isactive | no | Boolean used to return either only active (1) or inactive(0) TO Extensions | + +------------------+----------+------------------------------------------------------------------------------------------------------------------------------+ + | type | no | Filter TO Extensions by the type of Extension | + +------------------+----------+------------------------------------------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +------------------+----------+------------------------------------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +------------------+----------+------------------------------------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit. | + +------------------+----------+------------------------------------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long and the first | + | | | page is 1. If ``offset`` was defined, this query parameter has no effect. ``limit`` must be defined to make use of ``page``. | + +------------------+----------+------------------------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/servercheck/extensions HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:additional_config_json: A string containing a JSON-encoded object with extra configuration options... inside a JSON object... +:description: A short description of the extension + + .. note:: This is, unfortunately, ``null`` for all default extensions + +:id: An integral, unique identifier for this extension definition +:info_url: A URL where info about this extension may be found +:isactive: An integer describing the boolean notion of whether or not the extension is active; one of: + + 0 + disabled + 1 + enabled + +:name: The name of the extension +:script_file: The base filename of the script that runs for the extension +:servercheck_shortname: The name of the column in the table at 'Monitor' -> 'Cache Checks' in Traffic Portal, where "Check Extension" output is displayed +:type: The Check :term:`Type` of the extension. This will always be a CHECK_EXTENSION type with the naming convention of ``CHECK_EXTENSION_*``. +:version: A (hopefully) semantic version number describing the version of the plugin + +.. 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 + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Cache-Control: no-cache, no-store, max-age=0, must-revalidate + Content-Type: application/json + Date: Tue, 11 Dec 2018 20:51:48 GMT + X-Server-Name: traffic_ops_golang/ + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: n73jg9XR4V5Cwqq56Rf3wuIi99k3mM5u2NAjcZ/gQBu8jvAFymDlnZqKeJ+wTll1vjIsHpXCOVXV7+5UGakLgA== + Transfer-Encoding: chunked + + { "response": [ + { + "script_file": "ToPingCheck.pl", + "version": "1.0.0", + "name": "ILO_PING", + "description": null, + "info_url": "-", + "additional_config_json": "{ check_name: \"ILO\", \"base_url\": \"https://localhost\", \"select\": \"ilo_ip_address\", \"cron\": \"9 * * * *\" }", + "isactive": 1, + "type": "CHECK_EXTENSION_BOOL", + "id": 1, + "servercheck_short_name": "ILO" + }, + { + "script_file": "ToPingCheck.pl", + "version": "1.0.0", + "name": "10G_PING", + "description": null, + "info_url": "-", + "additional_config_json": "{ check_name: \"10G\", \"base_url\": \"https://localhost\", \"select\": \"ip_address\", \"cron\": \"18 * * * *\" }", + "isactive": 1, + "type": "CHECK_EXTENSION_BOOL", + "id": 2, + "servercheck_short_name": "10G" + } + ]} + +``POST`` +======== +Creates a new Traffic Ops check extension. + +:Auth. Required: Yes +:Roles Required: None\ [1]_ +:Permissions Required: SERVER-CHECK:CREATE, SERVER-CHECK:READ, SERVER:READ +:Response Type: ``undefined`` + +Request Structure +----------------- +:additional_config_json: An optional string containing a JSON-encoded object with extra configuration options... inside a JSON object... +:description: A short description of the extension +:info_url: A URL where info about this extension may be found +:isactive: An integer describing the boolean notion of whether or not the extension is active; one of: + + 0 + disabled + 1 + enabled + +:name: The name of the extension +:script_file: The base filename of the script that runs for the extension + + .. seealso:: :ref:`admin-to-ext-script` for details on where the script should be located on the Traffic Ops server + +:servercheck_shortname: The name of the column in the table at 'Monitor' -> 'Cache Checks' in Traffic Portal, where "Check Extension" output is displayed +:type: The :term:`Type` of extension. Must be CHECK_EXTENSION type with the naming convention of ``CHECK_EXTENSION_*``. +:version: A (hopefully) semantic version number describing the version of the plugin + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/servercheck/extensions HTTP/1.1 + Host: cache1.example.com:6443 + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 208 + Content-Type: application/json + + { + "name": "test", + "version": "0.0.1-1", + "info_url": "", + "script_file": "", + "isactive": 0, + "description": "A test extension for API examples", + "servercheck_short_name": "test", + "type": "CHECK_EXTENSION_NUM" + } + + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Cache-Control: no-cache, no-store, max-age=0, must-revalidate + Content-Type: application/json + Date: Wed, 12 Dec 2018 16:37:44 GMT + X-Server-Name: traffic_ops_golang/ + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: 7M67PYnli6WzGQFS3g8Gh1SOyq6VENZMqm/kUffOTLLFfuWSEuSLA65R5R+VyJiNjdqOG5Bp78mk+JYcqhtVGw== + Content-Length: 89 + + { "supplemental": + { + "id": 5 + }, + "alerts": [{ + "level": "success", + "text": "Check Extension Loaded." + }]} + +.. [1] No roles are required to use this endpoint, however access is controlled by username. Only the reserved user ``extension`` is permitted the use of this endpoint. diff --git a/docs/source/api/v5/servercheck_extensions_id.rst b/docs/source/api/v5/servercheck_extensions_id.rst new file mode 100644 index 0000000000..9e567b6462 --- /dev/null +++ b/docs/source/api/v5/servercheck_extensions_id.rst @@ -0,0 +1,76 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-servercheck_extensions-id: + +********************************* +``servercheck/extensions/{{ID}}`` +********************************* + +``DELETE`` +========== +Deletes a Traffic Ops server check extension definition. This does **not** delete the actual extension file. + +:Auth. Required: Yes +:Roles Required: None\ [1]_ +:Permissions Required: SERVER-CHECK:DELETE, SERVER-CHECK:READ, SERVER:READ +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+---------------------------------------------------------------------------+ + | Name | Description | + +======+===========================================================================+ + | ID | The integral, unique identifier of the extension definition to be deleted | + +------+---------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/servercheck/extensions/16 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Cache-Control: no-cache, no-store, max-age=0, must-revalidate + Content-Type: application/json + Date: Wed, 12 Dec 2018 16:33:52 GMT + X-Server-Name: traffic_ops_golang/ + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: EB0Nu85azbGzaehDTAODP3NPqWbByIza1XQhgwtsW2WTXyK/dxQtncp0YiJXyO0tH9H+n+6BBfojBOb5h0dFPA== + Content-Length: 60 + + { "alerts": [ + { + "level": "success", + "text": "Extension deleted." + } + ]} + +.. [1] No roles are required to use this endpoint, however access is controlled by username. Only the reserved user ``extension`` is permitted the use of this endpoint. diff --git a/docs/source/api/v5/servers.rst b/docs/source/api/v5/servers.rst new file mode 100644 index 0000000000..ed9024fe6d --- /dev/null +++ b/docs/source/api/v5/servers.rst @@ -0,0 +1,529 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-servers: + +*********** +``servers`` +*********** + +``GET`` +======= +Retrieves properties of all servers across all CDNs. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: SERVER:READ, DELIVERY-SERVICE:READ, CDN:READ, PHYSICAL-LOCATION:READ, CACHE-GROUP:READ, TYPE:READ, PROFILE:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +----------------+----------+-------------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +================+==========+===================================================================================================================+ + | cachegroup | no | Return only those servers within the :term:`Cache Group` that has this :ref:`cache-group-id` | + +----------------+----------+-------------------------------------------------------------------------------------------------------------------+ + | cachegroupName | no | Return only those servers within the :term:`Cache Group` that has this :ref:`cache-group-name` | + +----------------+----------+-------------------------------------------------------------------------------------------------------------------+ + | dsId | no | Return only those servers assigned to the :term:`Delivery Service` identified by this integral, unique identifier.| + | | | If the Delivery Service has a :term:`Topology` assigned to it, the :ref:`to-api-servers` endpoint will return | + | | | each server whose :term:`Cache Group` is associated with a :term:`Topology Node` of that Topology and has the | + | | | :term:`Server Capabilities` that are | + | | | :term:`required by the Delivery Service ` but excluding | + | | | :term:`Origin Servers` that are not assigned to the Delivery Service. For more information, see | + | | | :ref:`multi-site-origin-qht`. | + +----------------+----------+-------------------------------------------------------------------------------------------------------------------+ + | hostName | no | Return only those servers that have this (short) hostname | + +----------------+----------+-------------------------------------------------------------------------------------------------------------------+ + | id | no | Return only the server with this integral, unique identifier | + +----------------+----------+-------------------------------------------------------------------------------------------------------------------+ + | profileName | no | Return only those servers that are using the :term:`Profile` that has this :ref:`profile-name` | + +----------------+----------+-------------------------------------------------------------------------------------------------------------------+ + | status | no | Return only those servers with this status - see :ref:`health-proto` | + +----------------+----------+-------------------------------------------------------------------------------------------------------------------+ + | type | no | Return only servers of this :term:`Type` | + +----------------+----------+-------------------------------------------------------------------------------------------------------------------+ + | topology | no | Return only servers who belong to cachegroups assigned to the :term:`Topology` identified by this name | + +----------------+----------+-------------------------------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +----------------+----------+-------------------------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +----------------+----------+-------------------------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +----------------+----------+-------------------------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long and | + | | | the first page is 1. If ``offset`` was defined, this query parameter has no effect. ``limit`` must be defined to | + | | | make use of ``page``. | + +----------------+----------+-------------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/servers?hostName=mid HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the server belongs +:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the server belongs +:cdnId: The integral, unique identifier of the CDN to which the server belongs +:cdnName: Name of the CDN to which the server belongs +:configUpdateTime: The last time an update was requested for this server. This field defaults to standard epoch +:configApplyTime: The last time an update was applied for this server. This field defaults to standard epoch +:domainName: The domain part of the server's :abbr:`FQDN (Fully Qualified Domain Name)` +:guid: An identifier used to uniquely identify the server + + .. note:: This is a legacy key which only still exists for compatibility reasons - it should always be ``null`` + +:hostName: The (short) hostname of the server +:httpsPort: The port on which the server listens for incoming HTTPS connections/requests +:id: An integral, unique identifier for this server +:iloIpAddress: The IPv4 address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpGateway: The IPv4 gateway address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpNetmask: The IPv4 subnet mask of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloPassword: The password of the of the server's :abbr:`ILO (Integrated Lights-Out)` service user\ [#ilo]_ - displays as simply ``******`` if the currently logged-in user does not have the 'admin' or 'operations' :term:`Role(s) ` +:iloUsername: The user name for the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:interfaces: A set of the network interfaces in use by the server. In most scenarios, only one will be present, but it is illegal for this set to be an empty collection. + + :ipAddresses: A set of objects representing IP Addresses assigned to this network interface. In most scenarios, only one or two (usually one IPv4 address and one IPv6 address) will be present, but it is illegal for this set to be an empty collection. + + :address: The actual IP address, including any mask as a CIDR-notation suffix + :gateway: Either the IP address of the network gateway for this address, or ``null`` to signify that no such gateway exists + :serviceAddress: A boolean that describes whether or not the server's main service is available at this IP address. When this property is ``true``, the IP address is referred to as a "service address". It is illegal for a server to not have at least one service address. It is also illegal for a server to have more than one service address of the same address family (i.e. more than one IPv4 service address and/or more than one IPv6 address). Finally, all service addresses for a server must be contained within one interface - which is therefore sometimes referred to as the "service interface" for the server. + + :maxBandwidth: The maximum healthy bandwidth allowed for this interface. If bandwidth exceeds this limit, Traffic Monitors will consider the entire server unhealthy - which includes *all* configured network interfaces. If this is ``null``, it has the meaning "no limit". It has no effect if ``monitor`` is not true for this interface. + + .. seealso:: :ref:`health-proto` + + :monitor: A boolean which describes whether or not this interface should be monitored by Traffic Monitor for statistics and health consideration. + :mtu: The :abbr:`MTU (Maximum Transmission Unit)` of this interface. If it is ``null``, it may be assumed that the information is either not available or not applicable for this interface. + :name: The name of the interface. No two interfaces of the same server may share a name. It is the same as the network interface's device name on the server, e.g. ``eth0``. + :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 +:mgmtIpAddress: The IPv4 address of some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpGateway: The IPv4 address of a gateway used by some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpNetmask: The IPv4 subnet mask used by some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:offlineReason: A user-entered reason why the server is in ADMIN_DOWN or OFFLINE status +:physLocation: The name of the physical location where the server resides +:physLocationId: An integral, unique identifier for the physical location where the server resides +:profileNames: List of :ref:`profile-name` of the :term:`Profiles` used by this server +:revalPending: A boolean value which, if ``true`` indicates that this server has pending content invalidation/revalidation +:revalUpdateTime: The last time a content invalidation/revalidation request was submitted for this server. This field defaults to standard epoch +:revalApplyTime: The last time a content invalidation/revalidation request was applied by this server. This field defaults to standard epoch +:rack: A string indicating "server rack" location +:status: The :term:`Status` of the server + + .. seealso:: :ref:`health-proto` + +:statusId: The integral, unique identifier of the status of this server + + .. seealso:: :ref:`health-proto` + +:tcpPort: The port on which this server listens for incoming TCP connections + + .. note:: This is typically thought of as synonymous with "HTTP port", as the port specified by ``httpsPort`` may also be used for incoming TCP connections. + +:type: The name of the :term:`Type` of this server +:typeId: The integral, unique identifier of the 'type' of this server +:updPending: A boolean value which, if ``true``, indicates that the server has updates of some kind pending, typically to be acted upon by Traffic Control Cache Config (:term:`t3c`, formerly ORT) +:xmppId: A system-generated UUID used to generate a server hashId for use in Traffic Router's consistent hashing algorithm. This value is set when a server is created and cannot be changed afterwards. +:xmppPasswd: The password used in XMPP communications with the server + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Content-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Tue, 19 May 2020 17:06:25 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + X-Server-Name: traffic_ops_golang/ + Date: Tue, 19 May 2020 16:06:25 GMT + Content-Length: 538 + + { "response": [{ + "cachegroup": "CDN_in_a_Box_Mid", + "cachegroupId": 6, + "cdnId": 2, + "cdnName": "CDN-in-a-Box", + "configUpdateTime": "1969-12-31T17:00:00-07:00", + "configApplyTime": "1969-12-31T17:00:00-07:00", + "domainName": "infra.ciab.test", + "guid": null, + "hostName": "mid", + "httpsPort": 443, + "id": 12, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "lastUpdated": "2020-05-19 14:49:39+00", + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": "", + "physLocation": "Apachecon North America 2018", + "physLocationId": 1, + "profileNames": ["ATS_MID_TIER_CACHE"], + "rack": "", + "revalPending": false, + "revalUpdateTime": "1969-12-31T17:00:00-07:00", + "revalApplyTime": "1969-12-31T17:00:00-07:00", + "status": "REPORTED", + "statusId": 3, + "tcpPort": 80, + "type": "MID", + "typeId": 12, + "updPending": false, + "xmppId": "", + "xmppPasswd": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "172.26.0.4/16", + "gateway": "172.26.0.1", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": false, + "mtu": 1500, + "name": "eth0", + "routerHostName": "", + "routerPortName": "" + } + ] + }], + "summary": { + "count": 13 + }} + +Summary Fields +"""""""""""""" +The ``summary`` object returned by this method of this endpoint uses only the ``count`` :ref:`standard property `. + +``POST`` +======== +Allows a user to create a new server. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: SERVER:CREATE, SERVER:READ, DELIVERY-SERVICE:READ, CDN:READ, PHYSICAL-LOCATION:READ, CACHE-GROUP:READ, TYPE:READ, PROFILE:READ +:Response Type: Object + +Request Structure +----------------- +:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the server shall belong +:cdnId: The integral, unique identifier of the CDN to which the server shall belong +:domainName: The domain part of the server's :abbr:`FQDN (Fully Qualified Domain Name)` +:hostName: The (short) hostname of the server +:httpsPort: An optional port number on which the server listens for incoming HTTPS connections/requests +:iloIpAddress: An optional IPv4 address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpGateway: An optional IPv4 gateway address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpNetmask: An optional IPv4 subnet mask of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloPassword: An optional string containing the password of the of the server's :abbr:`ILO (Integrated Lights-Out)` service user\ [#ilo]_ - displays as simply ``******`` if the currently logged-in user does not have the 'admin' or 'operations' :term:`Role(s) ` +:iloUsername: An optional string containing the user name for the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:interfaces: A set of the network interfaces in use by the server. In most scenarios, only one will be necessary, but it is illegal for this set to be an empty collection. + + :ipAddresses: A set of objects representing IP Addresses assigned to this network interface. In most scenarios, only one or two (usually one IPv4 address and one IPv6 address) will be necessary, but it is illegal for this set to be an empty collection. + + :address: The actual IP address, including any mask as a CIDR-notation suffix + :gateway: Either the IP address of the network gateway for this address, or ``null`` to signify that no such gateway exists + :serviceAddress: A boolean that describes whether or not the server's main service is available at this IP address. When this property is ``true``, the IP address is referred to as a "service address". It is illegal for a server to not have at least one service address. It is also illegal for a server to have more than one service address of the same address family (i.e. more than one IPv4 service address and/or more than one IPv6 address). Finally, all service addresses for a server must be contained within one interface - which is therefore sometimes referred to as the "service interface" for the server. + + :maxBandwidth: The maximum healthy bandwidth allowed for this interface. If bandwidth exceeds this limit, Traffic Monitors will consider the entire server unhealthy - which includes *all* configured network interfaces. If this is ``null``, it has the meaning "no limit". It has no effect if ``monitor`` is not true for this interface. + + .. seealso:: :ref:`health-proto` + + :monitor: A boolean which describes whether or not this interface should be monitored by Traffic Monitor for statistics and health consideration. + :mtu: The :abbr:`MTU (Maximum Transmission Unit)` of this interface. If it is ``null``, it may be assumed that the information is either not available or not applicable for this interface. + :name: The name of the interface. No two interfaces of the same server may share a name. It is the same as the network interface's device name on the server, e.g. ``eth0``. + :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. + +:mgmtIpAddress: The IPv4 address of some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpGateway: The IPv4 address of a gateway used by some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpNetmask: The IPv4 subnet mask used by some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:physLocationId: An integral, unique identifier for the physical location where the server resides +:profileNames: List of :ref:`profile-name` of the :term:`Profiles` that shall be used by this server +:rack: An optional string indicating "server rack" location +:statusId: The integral, unique identifier of the status of this server + + .. seealso:: :ref:`health-proto` + +:tcpPort: An optional port number on which this server listens for incoming TCP connections + + .. note:: This is typically thought of as synonymous with "HTTP port", as the port specified by ``httpsPort`` may also be used for incoming TCP connections. + +:typeId: The integral, unique identifier of the 'type' of this server +:xmppId: A system-generated UUID used to generate a server hashId for use in Traffic Router's consistent hashing algorithm. This value is set when a server is created and cannot be changed afterwards. +:xmppPasswd: An optional password used in XMPP communications with the server + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/servers HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 599 + Content-Type: application/json + + { + "cachegroupId": 6, + "cdnId": 2, + "domainName": "infra.ciab.test", + "hostName": "test", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "::1", + "gateway": "::2", + "serviceAddress": true + }, + { + "address": "0.0.0.1/24", + "gateway": "0.0.0.2", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 1500, + "name": "eth0", + "routerHostName": "", + "routerPortName": "" + } + ], + "interfaceMtu": 1500, + "interfaceName": "eth0", + "ip6Address": "::1", + "ip6Gateway": "::2", + "ipAddress": "0.0.0.1", + "ipGateway": "0.0.0.2", + "ipNetmask": "255.255.255.0", + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": "", + "physLocationId": 1, + "profileNames": ["ATS_MID_TIER_CACHE"], + "statusId": 3, + "tcpPort": 80, + "typeId": 12 + } + +Response Structure +------------------ +:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the server belongs +:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the server belongs +:cdnId: The integral, unique identifier of the CDN to which the server belongs +:cdnName: Name of the CDN to which the server belongs +:configUpdateTime: The last time an update was requested for this server. This field defaults to standard epoch +:configApplyTime: The last time an update was applied for this server. This field defaults to standard epoch +:domainName: The domain part of the server's :abbr:`FQDN (Fully Qualified Domain Name)` +:guid: An identifier used to uniquely identify the server + + .. note:: This is a legacy key which only still exists for compatibility reasons - it should always be ``null`` + +:hostName: The (short) hostname of the server +:httpsPort: The port on which the server listens for incoming HTTPS connections/requests +:id: An integral, unique identifier for this server +:iloIpAddress: The IPv4 address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpGateway: The IPv4 gateway address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpNetmask: The IPv4 subnet mask of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloPassword: The password of the of the server's :abbr:`ILO (Integrated Lights-Out)` service user\ [#ilo]_ - displays as simply ``******`` if the currently logged-in user does not have the 'admin' or 'operations' :abbr:`Role(s) ` +:iloUsername: The user name for the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:interfaces: A set of the network interfaces in use by the server. In most scenarios, only one will be present, but it is illegal for this set to be an empty collection. + + :ipAddresses: A set of objects representing IP Addresses assigned to this network interface. In most scenarios, only one or two (usually one IPv4 address and one IPv6 address) will be present, but it is illegal for this set to be an empty collection. + + :address: The actual IP address, including any mask as a CIDR-notation suffix + :gateway: Either the IP address of the network gateway for this address, or ``null`` to signify that no such gateway exists + :serviceAddress: A boolean that describes whether or not the server's main service is available at this IP address. When this property is ``true``, the IP address is referred to as a "service address". It is illegal for a server to not have at least one service address. It is also illegal for a server to have more than one service address of the same address family (i.e. more than one IPv4 service address and/or more than one IPv6 address). Finally, all service addresses for a server must be contained within one interface - which is therefore sometimes referred to as the "service interface" for the server. + + :maxBandwidth: The maximum healthy bandwidth allowed for this interface. If bandwidth exceeds this limit, Traffic Monitors will consider the entire server unhealthy - which includes *all* configured network interfaces. If this is ``null``, it has the meaning "no limit". It has no effect if ``monitor`` is not true for this interface. + + .. seealso:: :ref:`health-proto` + + :monitor: A boolean which describes whether or not this interface should be monitored by Traffic Monitor for statistics and health consideration. + :mtu: The :abbr:`MTU (Maximum Transmission Unit)` of this interface. If it is ``null``, it may be assumed that the information is either not available or not applicable for this interface. + :name: The name of the interface. No two interfaces of the same server may share a name. It is the same as the network interface's device name on the server, e.g. ``eth0``. + :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 +:mgmtIpAddress: The IPv4 address of some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpGateway: The IPv4 address of a gateway used by some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpNetmask: The IPv4 subnet mask used by some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:offlineReason: A user-entered reason why the server is in ADMIN_DOWN or OFFLINE status +:physLocation: The name of the :term:`Physical Location` where the server resides +:physLocationId: An integral, unique identifier for the :term:`Physical Location` where the server resides +:profileNames: List of :ref:`profile-name` of the :term:`Profiles` used by this server +:revalPending: A boolean value which, if ``true`` indicates that this server has pending content invalidation/revalidation +:revalUpdateTime: The last time a content invalidation/revalidation request was submitted for this server. This field defaults to standard epoch +:revalApplyTime: The last time a content invalidation/revalidation request was applied by this server. This field defaults to standard epoch +:rack: A string indicating "server rack" location +:status: The status of the server + + .. seealso:: :ref:`health-proto` + +:statusId: The integral, unique identifier of the status of this server + + .. seealso:: :ref:`health-proto` + +:tcpPort: The port on which this server listens for incoming TCP connections + + .. note:: This is typically thought of as synonymous with "HTTP port", as the port specified by ``httpsPort`` may also be used for incoming TCP connections. + +:type: The name of the 'type' of this server +:typeId: The integral, unique identifier of the 'type' of this server +:updPending: A boolean value which, if ``true``, indicates that the server has updates of some kind pending, typically to be acted upon by Traffic Control Cache Config (T3C, formerly ORT) +:xmppId: A system-generated UUID used to generate a server hashId for use in Traffic Router's consistent hashing algorithm. This value is set when a server is created and cannot be changed afterwards. +:xmppPasswd: The password used in XMPP communications with the server + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 201 Created + Content-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Tue, 19 May 2020 17:34:40 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + X-Server-Name: traffic_ops_golang/ + Date: Tue, 19 May 2020 16:34:40 GMT + Content-Length: 562 + + { "alerts": [ + { + "text": "Server created", + "level": "success" + } + ], + "response": { + "cachegroup": "CDN_in_a_Box_Mid", + "cachegroupId": 6, + "cdnId": 2, + "cdnName": "CDN-in-a-Box", + "configUpdateTime": "1969-12-31T17:00:00-07:00", + "configApplyTime": "1969-12-31T17:00:00-07:00", + "domainName": "infra.ciab.test", + "guid": null, + "hostName": "test", + "httpsPort": 443, + "id": 14, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "lastUpdated": "2020-05-19 16:34:40+00", + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": "", + "physLocation": "Apachecon North America 2018", + "physLocationId": 1, + "profileNames": ["ATS_MID_TIER_CACHE"], + "rack": null, + "revalPending": false, + "revalUpdateTime": "1969-12-31T17:00:00-07:00", + "revalApplyTime": "1969-12-31T17:00:00-07:00", + "status": "REPORTED", + "statusId": 3, + "tcpPort": 80, + "type": "MID", + "typeId": 12, + "updPending": false, + "xmppId": null, + "xmppPasswd": null, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "::1", + "gateway": "::2", + "serviceAddress": true + }, + { + "address": "0.0.0.1/24", + "gateway": "0.0.0.2", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 1500, + "name": "eth0", + "routerHostName": "", + "routerPortName": "" + } + ] + }} + +.. [#ilo] For more information see the `Wikipedia page on Lights-Out management `_\ . diff --git a/docs/source/api/v5/servers_hostname_update.rst b/docs/source/api/v5/servers_hostname_update.rst new file mode 100644 index 0000000000..6caf870f41 --- /dev/null +++ b/docs/source/api/v5/servers_hostname_update.rst @@ -0,0 +1,94 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-servers-hostname-update: + +************************************* +``servers/{{HostName-Or-ID}}/update`` +************************************* + +``POST`` +======== +:term:`Queue` or dequeue updates and revalidation updates for a specific server. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: SERVER:UPDATE, SERVER:READ +:Response Type: undefined + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------------------+---------------------------------------------------------------------------------------------------------+ + | Name | Description | + +==================+=========================================================================================================+ + | HostName-OR-ID | The hostName or integral, unique identifier of the server on which updates are being queued or dequeued | + +------------------+---------------------------------------------------------------------------------------------------------+ + +.. table:: Request Query Parameters + + +----------------------------+----------+--------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +============================+==========+==============================================================================================================+ + | updated | no | The value to set for the queue update flag on this server. May be 'true' or 'false'. | + +----------------------------+----------+--------------------------------------------------------------------------------------------------------------+ + | reval_updated | no | The value to set for the reval update flag on this server. May be 'true' or 'false'. | + +----------------------------+----------+--------------------------------------------------------------------------------------------------------------+ + | config_apply_time | no | The value to set for when a queue update is applied for this server. Must be a valid RFC333Nano timestamp. | + +----------------------------+----------+--------------------------------------------------------------------------------------------------------------+ + | revalidate_apply_time | no | The value to set for when a reval update is applied for this server. Must be a valid RFC333Nano timestamp. | + +----------------------------+----------+--------------------------------------------------------------------------------------------------------------+ + +.. note:: While none of the timestamps is required individually, at least one must be sent to the API. + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/servers/my-edge/update?config_apply_time=2022-01-31T12%3A00%3A00.123456-07%3A00&revalidate_apply_time=2022-01-31T12%3A00%3A00.123456-07%3A00 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Cache-Control: no-cache, no-store, max-age=0, must-revalidate + Content-Type: application/json + Date: Mon, 10 Dec 2018 18:20:04 GMT + X-Server-Name: traffic_ops_golang/ + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: 9Mmo9hIFZyF5gAvfdJD//VH9eNgiHVLinXt88H0GlJSHhwND8gMxaFyC+f9XZfiNAoGd1MKi1934ZJGmaIR6qQ== + Content-Length: 49 + + { + "alerts" : + [ + { + "text" : "successfully set server 'my-edge' config_apply_time=2022-01-31T12:00:00.123456-07:00 revalidate_apply_time=2022-01-31T12:00:00.123456-07:00", + "level" : "success" + } + ] + } diff --git a/docs/source/api/v5/servers_hostname_update_status.rst b/docs/source/api/v5/servers_hostname_update_status.rst new file mode 100644 index 0000000000..5e35da9d75 --- /dev/null +++ b/docs/source/api/v5/servers_hostname_update_status.rst @@ -0,0 +1,104 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-servers-hostname-update_status: + +************************************** +``servers/{{hostname}}/update_status`` +************************************** + +.. note:: This endpoint only truly has meaning for :term:`cache servers`, though it will return a valid response for any server configured in Traffic Ops. + +``GET`` +======= +Retrieves information regarding pending updates and :term:`Content Invalidation Jobs` for a given server + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: SERVER:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Path Parameters + + +----------+----------------------------------------------------+ + | Name | Description | + +==========+====================================================+ + | hostname | The (short) hostname of the server being inspected | + +----------+----------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/servers/edge/update_status HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +Each object in the returned array\ [#uniqueness]_ will contain the following fields: + +:configUpdateTime: The last time an update was requested for this server. This field defaults to standard epoch +:configApplyTime: The last time an update was applied for this server. This field defaults to standard epoch +:host_id: The integral, unique identifier for the server for which the other fields in this object represent the pending updates and revalidation status +:host_name: The (short) hostname of the server for which the other fields in this object represent the pending updates and revalidation status +:parent_pending: A boolean telling whether or not any :term:`Topology` ancestor or :term:`parent` of this server has pending updates +:parent_reval_pending: A boolean telling whether or not any :term:`Topology` ancestor or :term:`parent` of this server has pending :term:`Content Invalidation Jobs` +:reval_pending: ``true`` if the server has pending :term:`Content Invalidation Jobs`, ``false`` otherwise +:revalUpdateTime: The last time a content invalidation/revalidation request was submitted for this server. This field defaults to standard epoch +:revalApplyTime: The last time a content invalidation/revalidation request was applied by this server. This field defaults to standard epoch +:status: The name of the status of this server + + .. seealso:: :ref:`health-proto` gives more information on how these statuses are used, and the ``GET`` method of the :ref:`to-api-statuses` endpoint can be used to retrieve information about all server statuses configured in Traffic Ops. + +:upd_pending: ``true`` if the server has pending updates, ``false`` otherwise +:use_reval_pending: A boolean which tells :term:`ORT` whether or not this version of Traffic Ops should use pending :term:`Content Invalidation Jobs` + + .. note:: This field was introduced to give :term:`ORT` the ability to work with Traffic Control versions 1.x and 2.x seamlessly - as of Traffic Control v3.0 there is no reason for this field to ever be ``false``. + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: R6BjNVrcecHGn3eGDqQ1yDiBnEDGQe7QtOMIsRwlpck9SZR8chRQznrkTF3YdROAZ1l8BxR3fXTIvKHIzK2/dA== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 04 Feb 2019 16:24:01 GMT + Content-Length: 174 + + { "response": [{ + "host_name": "edge", + "upd_pending": false, + "reval_pending": false, + "use_reval_pending": true, + "host_id": 10, + "status": "REPORTED", + "parent_pending": false, + "parent_reval_pending": false, + "config_update_time": "2022-02-18T13:52:47.129174-07:00", + "config_apply_time": "2022-02-18T13:52:47.129174-07:00", + "revalidate_update_time": "2022-02-28T15:44:15.895145-07:00", + "revalidate_apply_time": "2022-02-18T13:52:47.129174-07:00" + }]} + +.. [#uniqueness] The returned object is an array, and there is no guarantee that one server exists for a given hostname. However, for each server in the array, that server's update status will be accurate for the server with that particular server ID. diff --git a/docs/source/api/v5/servers_id.rst b/docs/source/api/v5/servers_id.rst new file mode 100644 index 0000000000..712b1fb209 --- /dev/null +++ b/docs/source/api/v5/servers_id.rst @@ -0,0 +1,516 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-servers-id: + +****************** +``servers/{{ID}}`` +****************** + +``PUT`` +======= +Allow user to edit a server. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: SERVER:UPDATE, SERVER:READ, DELIVERY-SERVICE:READ, CDN:READ, PHYSICAL-LOCATION:READ, CACHE-GROUP:READ, TYPE:READ, PROFILE:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+---------------------------------------------+ + | Name | Description | + +======+=============================================+ + | ID | The integral, unique identifier of a server | + +------+---------------------------------------------+ + +:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the server shall belong +:cdnId: The integral, unique identifier of the CDN to which the server shall belong +:domainName: The domain part of the server's :abbr:`FQDN (Fully Qualified Domain Name)` +:hostName: The (short) hostname of the server +:httpsPort: An optional port number on which the server listens for incoming HTTPS connections/requests +:iloIpAddress: An optional IPv4 address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpGateway: An optional IPv4 gateway address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpNetmask: An optional IPv4 subnet mask of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloPassword: An optional string containing the password of the of the server's :abbr:`ILO (Integrated Lights-Out)` service user\ [#ilo]_ - displays as simply ``******`` if the currently logged-in user does not have the 'admin' or 'operations' :abbr:`Role(s) ` +:iloUsername: An optional string containing the user name for the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:interfaces: A set of the network interfaces in use by the server. In most scenarios, only one will be necessary, but it is illegal for this set to be an empty collection. + + :ipAddresses: A set of objects representing IP Addresses assigned to this network interface. In most scenarios, only one or two (usually one IPv4 address and one IPv6 address) will be necessary, but it is illegal for this set to be an empty collection. + + :address: The actual IP address, including any mask as a CIDR-notation suffix + :gateway: Either the IP address of the network gateway for this address, or ``null`` to signify that no such gateway exists + :serviceAddress: A boolean that describes whether or not the server's main service is available at this IP address. When this property is ``true``, the IP address is referred to as a "service address". It is illegal for a server to not have at least one service address. It is also illegal for a server to have more than one service address of the same address family (i.e. more than one IPv4 service address and/or more than one IPv6 address). Finally, all service addresses for a server must be contained within one interface - which is therefore sometimes referred to as the "service interface" for the server. + + :maxBandwidth: The maximum healthy bandwidth allowed for this interface. If bandwidth exceeds this limit, Traffic Monitors will consider the entire server unhealthy - which includes *all* configured network interfaces. If this is ``null``, it has the meaning "no limit". It has no effect if ``monitor`` is not true for this interface. + + .. seealso:: :ref:`health-proto` + + :monitor: A boolean which describes whether or not this interface should be monitored by Traffic Monitor for statistics and health consideration. + :mtu: The :abbr:`MTU (Maximum Transmission Unit)` of this interface. If it is ``null``, it may be assumed that the information is either not available or not applicable for this interface. This unsigned integer must not be less than 1280. + :name: The name of the interface. No two interfaces of the same server may share a name. It is the same as the network interface's device name on the server, e.g. ``eth0``. + :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. + +:mgmtIpAddress: The IPv4 address of some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpGateway: The IPv4 address of a gateway used by some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpNetmask: The IPv4 subnet mask used by some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:physLocationId: An integral, unique identifier for the physical location where the server resides +:profileNames: List of :ref:`profile-name` of the :term:`Profiles` that shall be used by this server +:rack: An optional string indicating "server rack" location +:statusId: The integral, unique identifier of the status of this server + + .. seealso:: :ref:`health-proto` + +:tcpPort: An optional port number on which this server listens for incoming TCP connections + + .. note:: This is typically thought of as synonymous with "HTTP port", as the port specified by ``httpsPort`` may also be used for incoming TCP connections. + +:typeId: The integral, unique identifier of the 'type' of this server +:xmppId: A system-generated UUID used to generate a server hashId for use in Traffic Router's consistent hashing algorithm. This value is set when a server is created and cannot be changed afterwards. +:xmppPasswd: An optional password used in XMPP communications with the server + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/servers/14 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 599 + Content-Type: application/json + + { + "cachegroupId": 6, + "cdnId": 2, + "domainName": "infra.ciab.test", + "hostName": "quest", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "::1", + "gateway": "::2", + "serviceAddress": true + }, + { + "address": "0.0.0.1/24", + "gateway": "0.0.0.2", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 1500, + "name": "bond0", + "routerHostName": "", + "routerPortName": "" + } + ], + "interfaceMtu": 1500, + "interfaceName": "eth0", + "ip6Address": "::1", + "ip6Gateway": "::2", + "ipAddress": "0.0.0.1", + "ipGateway": "0.0.0.2", + "ipNetmask": "255.255.255.0", + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": "", + "physLocationId": 1, + "profileNames": ["ATS_MID_TIER_CACHE"], + "statusId": 3, + "tcpPort": 80, + "typeId": 12 + } + +Response Structure +------------------ +:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the server belongs +:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the server belongs +:cdnId: The integral, unique identifier of the CDN to which the server belongs +:configUpdateTime: The last time an update was requested for this server. This field defaults to standard epoch +:configApplyTime: The last time an update was applied for this server. This field defaults to standard epoch +:cdnName: Name of the CDN to which the server belongs +:domainName: The domain part of the server's :abbr:`FQDN (Fully Qualified Domain Name)` +:guid: An identifier used to uniquely identify the server + + .. note:: This is a legacy key which only still exists for compatibility reasons - it should always be ``null`` + +:hostName: The (short) hostname of the server +:httpsPort: The port on which the server listens for incoming HTTPS connections/requests +:id: An integral, unique identifier for this server +:iloIpAddress: The IPv4 address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpGateway: The IPv4 gateway address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpNetmask: The IPv4 subnet mask of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloPassword: The password of the of the server's :abbr:`ILO (Integrated Lights-Out)` service user\ [#ilo]_ - displays as simply ``******`` if the currently logged-in user does not have the 'admin' or 'operations' :abbr:`Role(s) ` +:iloUsername: The user name for the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:interfaces: A set of the network interfaces in use by the server. In most scenarios, only one will be present, but it is illegal for this set to be an empty collection. + + :ipAddresses: A set of objects representing IP Addresses assigned to this network interface. In most scenarios, only one or two (usually one IPv4 address and one IPv6 address) will be present, but it is illegal for this set to be an empty collection. + + :address: The actual IP address, including any mask as a CIDR-notation suffix + :gateway: Either the IP address of the network gateway for this address, or ``null`` to signify that no such gateway exists + :serviceAddress: A boolean that describes whether or not the server's main service is available at this IP address. When this property is ``true``, the IP address is referred to as a "service address". It is illegal for a server to not have at least one service address. It is also illegal for a server to have more than one service address of the same address family (i.e. more than one IPv4 service address and/or more than one IPv6 address). Finally, all service addresses for a server must be contained within one interface - which is therefore sometimes referred to as the "service interface" for the server. + + :maxBandwidth: The maximum healthy bandwidth allowed for this interface. If bandwidth exceeds this limit, Traffic Monitors will consider the entire server unhealthy - which includes *all* configured network interfaces. If this is ``null``, it has the meaning "no limit". It has no effect if ``monitor`` is not true for this interface. + + .. seealso:: :ref:`health-proto` + + :monitor: A boolean which describes whether or not this interface should be monitored by Traffic Monitor for statistics and health consideration. + :mtu: The :abbr:`MTU (Maximum Transmission Unit)` of this interface. If it is ``null``, it may be assumed that the information is either not available or not applicable for this interface. + :name: The name of the interface. No two interfaces of the same server may share a name. It is the same as the network interface's device name on the server, e.g. ``eth0``. + :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 +:mgmtIpAddress: The IPv4 address of some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpGateway: The IPv4 address of a gateway used by some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpNetmask: The IPv4 subnet mask used by some network interface on the server used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:offlineReason: A user-entered reason why the server is in ADMIN_DOWN or OFFLINE status +:physLocation: The name of the :term:`Physical Location` where the server resides +:physLocationId: An integral, unique identifier for the :term:`Physical Location` where the server resides +:profileNames: List of :ref:`profile-name` of the :term:`Profiles` used by this server +:revalPending: A boolean value which, if ``true`` indicates that this server has pending content invalidation/revalidation +:revalUpdateTime: The last time a content invalidation/revalidation request was submitted for this server. This field defaults to standard epoch +:revalApplyTime: The last time a content invalidation/revalidation request was applied by this server. This field defaults to standard epoch +:rack: A string indicating "server rack" location +:status: The status of the server + + .. seealso:: :ref:`health-proto` + +:statusId: The integral, unique identifier of the status of this server + + .. seealso:: :ref:`health-proto` + +:tcpPort: The port on which this server listens for incoming TCP connections + + .. note:: This is typically thought of as synonymous with "HTTP port", as the port specified by ``httpsPort`` may also be used for incoming TCP connections. + +:type: The name of the 'type' of this server +:typeId: The integral, unique identifier of the 'type' of this server +:updPending: A boolean value which, if ``true``, indicates that the server has updates of some kind pending, typically to be acted upon by Traffic Control Cache Config (:term:`t3c`, formerly ORT) +:xmppId: A system-generated UUID used to generate a server hashId for use in Traffic Router's consistent hashing algorithm. This value is set when a server is created and cannot be changed afterwards. +:xmppPasswd: The password used in XMPP communications with the server + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Tue, 19 May 2020 17:46:33 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + X-Server-Name: traffic_ops_golang/ + Date: Tue, 19 May 2020 16:46:33 GMT + Content-Length: 566 + + { "alerts": [ + { + "text": "Server updated", + "level": "success" + } + ], + "response": { + "cachegroup": "CDN_in_a_Box_Mid", + "cachegroupId": 6, + "cdnId": 2, + "cdnName": "CDN-in-a-Box", + "configUpdateTime": "2022-02-28T15:44:15.895145-07:00", + "configApplyTime": "2022-02-18T13:52:47.129174-07:00", + "domainName": "infra.ciab.test", + "guid": null, + "hostName": "quest", + "httpsPort": 443, + "id": 14, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "lastUpdated": "2020-05-19 16:46:33+00", + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": "", + "physLocation": "Apachecon North America 2018", + "physLocationId": 1, + "profileNames": ["ATS_MID_TIER_CACHE"], + "rack": null, + "revalPending": false, + "revalUpdateTime": "1969-12-31T17:00:00-07:00", + "revalApplyTime": "1969-12-31T17:00:00-07:00", + "status": "REPORTED", + "statusId": 3, + "tcpPort": 80, + "type": "MID", + "typeId": 12, + "updPending": true, + "xmppId": null, + "xmppPasswd": null, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "::1", + "gateway": "::2", + "serviceAddress": true + }, + { + "address": "0.0.0.1/24", + "gateway": "0.0.0.2", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 1500, + "name": "bond0", + "routerHostName": "", + "routerPortName": "" + } + ] + }} + +``DELETE`` +========== +Allow user to delete server through api. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: SERVER:DELETE, SERVER:READ, DELIVERY-SERVICE:READ, CDN:READ, PHYSICAL-LOCATION:READ, CACHE-GROUP:READ, TYPE:READ, PROFILE:READ +:Response Type: Object + + .. versionchanged:: 3.0 + In older versions of the API, this endpoint did not return a response object. It now returns a representation of the deleted server. + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+---------------------------------------------+ + | Name | Description | + +======+=============================================+ + | ID | The integral, unique identifier of a server | + +------+---------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/servers/14 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:cachegroup: A string that is the :ref:`name of the Cache Group ` to which the server belonged +:cachegroupId: An integer that is the :ref:`ID of the Cache Group ` to which the server belonged +:cdnId: The integral, unique identifier of the CDN to which the server belonged +:cdnName: Name of the CDN to which the server belonged +:configUpdateTime: The last time an update was requested for this server. This field defaults to standard epoch +:configApplyTime: The last time an update was applied for this server. This field defaults to standard epoch +:domainName: The domain part of the server's :abbr:`FQDN (Fully Qualified Domain Name)` +:guid: An identifier used to uniquely identify the server + + .. note:: This is a legacy key which only still exists for compatibility reasons - it should always be ``null`` + +:hostName: The (short) hostname of the server +:httpsPort: The port on which the server listened for incoming HTTPS connections/requests +:id: An integral, unique identifier for this server +:iloIpAddress: The IPv4 address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpGateway: The IPv4 gateway address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloIpNetmask: The IPv4 subnet mask of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:iloPassword: The password of the of the server's :abbr:`ILO (Integrated Lights-Out)` service user\ [#ilo]_ - displays as simply ``******`` if the currently logged-in user does not have the 'admin' or 'operations' :term:`Role(s) ` +:iloUsername: The user name for the server's :abbr:`ILO (Integrated Lights-Out)` service\ [#ilo]_ +:interfaces: A set of the network interfaces that were in use by the server + + :ipAddresses: A set of objects representing IP Addresses that were assigned to this network interface + + :address: The actual IP address, including any mask as a CIDR-notation suffix + :gateway: Either the IP address of the network gateway for this address, or ``null`` to signify that no such gateway exists + :serviceAddress: A boolean that describes whether or not the server's main service is available at this IP address. When this property is ``true``, the IP address is referred to as a "service address". + + :maxBandwidth: The maximum healthy bandwidth allowed for this interface. If bandwidth exceeds this limit, Traffic Monitors would have considered the entire server unhealthy - which includes *all* configured network interfaces. If this was ``null``, it has the meaning "no limit". It had no effect if ``monitor`` was not true for this interface. + + .. seealso:: :ref:`health-proto` + + :monitor: A boolean which describes whether or not this interface should have been monitored by Traffic Monitor for statistics and health consideration + :mtu: The :abbr:`MTU (Maximum Transmission Unit)` of this interface. If it is ``null``, it may be assumed that the information was either not available or not applicable for this interface. + :name: The name of the interface. It is the same as the network interface's device name on the server, e.g. ``eth0``. + :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 +:mgmtIpAddress: The IPv4 address of some network interface on the server that was used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpGateway: The IPv4 address of a gateway used by some network interface on the server that was used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:mgmtIpNetmask: The IPv4 subnet mask used by some network interface on the server that was used for 'management' + + .. deprecated:: 3.0 + This field is deprecated and will be removed in a future API version. Operators should migrate this data into the ``interfaces`` property of the server. + +:offlineReason: A user-entered reason why the server was in ADMIN_DOWN or OFFLINE status +:physLocation: The name of the physical location where the server resided +:physLocationId: An integral, unique identifier for the physical location where the server resided +:profileNames: List of :ref:`profile-name` of the :term:`Profiles` which was used by this server +:revalPending: A boolean value which, if ``true`` indicates that this server has pending content invalidation/revalidation +:revalUpdateTime: The last time a content invalidation/revalidation request was submitted for this server. This field defaults to standard epoch +:revalApplyTime: The last time a content invalidation/revalidation request was applied by this server. This field defaults to standard epoch +:rack: A string indicating "server rack" location +:status: The :term:`Status` of the server + + .. seealso:: :ref:`health-proto` + +:statusId: The integral, unique identifier of the status of this server + + .. seealso:: :ref:`health-proto` + +:tcpPort: The port on which this server listened for incoming TCP connections + + .. note:: This is typically thought of as synonymous with "HTTP port", as the port specified by ``httpsPort`` may also be used for incoming TCP connections. + +:type: The name of the :term:`Type` of this server +:typeId: The integral, unique identifier of the 'type' of this server +:updPending: A boolean value which, if ``true``, indicates that the server has updates of some kind pending, typically to be acted upon by Traffic Control Cache Config (T3C, formerly ORT) +:xmppId: A system-generated UUID used to generate a server hashId for use in Traffic Router's consistent hashing algorithm. This value is set when a server is created and cannot be changed afterwards. +:xmppPasswd: The password used in XMPP communications with the server + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Content-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Tue, 19 May 2020 17:50:13 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + X-Server-Name: traffic_ops_golang/ + Date: Tue, 19 May 2020 16:50:13 GMT + Content-Length: 568 + + { "alerts": [ + { + "text": "Server deleted", + "level": "success" + } + ], + "response": { + "cachegroup": "CDN_in_a_Box_Mid", + "cachegroupId": 6, + "cdnId": 2, + "cdnName": "CDN-in-a-Box", + "configUpdateTime": "1969-12-31T17:00:00-07:00", + "configApplyTime": "1969-12-31T17:00:00-07:00", + "domainName": "infra.ciab.test", + "guid": null, + "hostName": "quest", + "httpsPort": 443, + "id": 14, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "lastUpdated": "2020-05-19 16:46:33+00", + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": "", + "physLocation": "Apachecon North America 2018", + "physLocationId": 1, + "profileNames": ["ATS_MID_TIER_CACHE"], + "rack": null, + "revalPending": false, + "revalUpdateTime": "1969-12-31T17:00:00-07:00", + "revalApplyTime": "1969-12-31T17:00:00-07:00", + "status": "REPORTED", + "statusId": 3, + "tcpPort": 80, + "type": "MID", + "typeId": 12, + "updPending": false, + "xmppId": null, + "xmppPasswd": null, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "0.0.0.1/24", + "gateway": "0.0.0.2", + "serviceAddress": false + }, + { + "address": "::1", + "gateway": "::2", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 1500, + "name": "bond0", + "routerHostName": "", + "routerPortName": "" + } + ] + }} + +.. [#ilo] For more information see the `Wikipedia page on Lights-Out management `_\ . diff --git a/docs/source/api/v5/servers_id_deliveryservices.rst b/docs/source/api/v5/servers_id_deliveryservices.rst new file mode 100644 index 0000000000..e6f1d74f6f --- /dev/null +++ b/docs/source/api/v5/servers_id_deliveryservices.rst @@ -0,0 +1,327 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-servers-id-deliveryservices: + +*********************************** +``servers/{{ID}}/deliveryservices`` +*********************************** + +``GET`` +======= +Retrieves all :term:`Delivery Services` assigned to a specific server. + +:Auth. Required: Yes +:Roles Required: None\ [#tenancy]_ +:Permissions Required: DELIVERY-SERVICE:READ, SERVER:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+------------------------------------------------------------------------------------------------------------+ + | Name | Description | + +======+============================================================================================================+ + | ID | The integral, unique identifier of the server for which assigned :term:`Delivery Services` shall be listed | + +------+------------------------------------------------------------------------------------------------------------+ + +.. table:: Request Query Parameters + + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +===========+==========+===============================================================================================================+ + | orderby | no | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` | + | | | array | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long | + | | | and the first page is 1. If ``offset`` was defined, this query parameter has no effect. ``limit`` must be | + | | | defined to make use of ``page``. | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/servers/10/deliveryservices HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: python-requests/2.24.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + +Response Structure +------------------ +:active: A boolean that defines :ref:`ds-active`. +:anonymousBlockingEnabled: A boolean that defines :ref:`ds-anonymous-blocking` +:ccrDnsTtl: The :ref:`ds-dns-ttl` - named "ccrDnsTtl" for legacy reasons +:cdnId: The integral, unique identifier of the :ref:`ds-cdn` to which the :term:`Delivery Service` belongs +:cdnName: Name of the :ref:`ds-cdn` to which the :term:`Delivery Service` belongs +:checkPath: A :ref:`ds-check-path` +:consistentHashRegex: A :ref:`ds-consistent-hashing-regex` +:consistentHashQueryParams: An array of :ref:`ds-consistent-hashing-qparams` +:deepCachingType: The :ref:`ds-deep-caching` setting for this :term:`Delivery Service` +:displayName: The :ref:`ds-display-name` +:dnsBypassCname: A :ref:`ds-dns-bypass-cname` +:dnsBypassIp: A :ref:`ds-dns-bypass-ip` +:dnsBypassIp6: A :ref:`ds-dns-bypass-ipv6` +:dnsBypassTtl: The :ref:`ds-dns-bypass-ttl` +:dscp: A :ref:`ds-dscp` to be used within the :term:`Delivery Service` +:ecsEnabled: A boolean that defines the :ref:`ds-ecs` setting on this :term:`Delivery Service` +:edgeHeaderRewrite: A set of :ref:`ds-edge-header-rw-rules` +:exampleURLs: An array of :ref:`ds-example-urls` +:firstHeaderRewrite: A set of :ref:`ds-first-header-rw-rules` +:fqPacingRate: The :ref:`ds-fqpr` +:geoLimit: An integer that defines the :ref:`ds-geo-limit` +:geoLimitCountries: An array of strings defining the :ref:`ds-geo-limit-countries` +:geoLimitRedirectUrl: A :ref:`ds-geo-limit-redirect-url` +:geoProvider: The :ref:`ds-geo-provider` +:globalMaxMbps: The :ref:`ds-global-max-mbps` +:globalMaxTps: The :ref:`ds-global-max-tps` +:httpBypassFqdn: A :ref:`ds-http-bypass-fqdn` +:id: An integral, unique identifier for this :term:`Delivery Service` +:infoUrl: An :ref:`ds-info-url` +:initialDispersion: The :ref:`ds-initial-dispersion` +: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 +: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` + + :pattern: A regular expression - the use of this pattern is dependent on the ``type`` field (backslashes are escaped) + :setNumber: An integer that provides explicit ordering of :ref:`ds-matchlist` items - this is used as a priority ranking by Traffic Router, and is not guaranteed to correspond to the ordering of items in the array. + :type: The type of match performed using ``pattern``. + +:maxDnsAnswers: The :ref:`ds-max-dns-answers` allowed for this :term:`Delivery Service` +:maxOriginConnections: The :ref:`ds-max-origin-connections` +:midHeaderRewrite: A set of :ref:`ds-mid-header-rw-rules` +:missLat: The :ref:`ds-geo-miss-default-latitude` used by this :term:`Delivery Service` +:missLong: The :ref:`ds-geo-miss-default-longitude` used by this :term:`Delivery Service` +:multiSiteOrigin: A boolean that defines the use of :ref:`ds-multi-site-origin` by this :term:`Delivery Service` +:orgServerFqdn: The :ref:`ds-origin-url` +:originShield: A :ref:`ds-origin-shield` string +:profileDescription: The :ref:`profile-description` of the :ref:`ds-profile` with which this :term:`Delivery Service` is associated +:profileId: The :ref:`profile-id` of the :ref:`ds-profile` with which this :term:`Delivery Service` is associated +:profileName: The :ref:`profile-name` of the :ref:`ds-profile` with which this :term:`Delivery Service` is associated +:protocol: An integral, unique identifier that corresponds to the :ref:`ds-protocol` used by this :term:`Delivery Service` +:qstringIgnore: An integral, unique identifier that corresponds to the :ref:`ds-qstring-handling` setting on this :term:`Delivery Service` +:rangeRequestHandling: An integral, unique identifier that corresponds to the :ref:`ds-range-request-handling` setting on this :term:`Delivery Service` +:regexRemap: A :ref:`ds-regex-remap` +:regionalGeoBlocking: A boolean defining the :ref:`ds-regionalgeo` setting on this :term:`Delivery Service` +:remapText: :ref:`ds-raw-remap` +:signed: ``true`` if and only if ``signingAlgorithm`` is not ``null``, ``false`` otherwise +:signingAlgorithm: Either a :ref:`ds-signing-algorithm` or ``null`` to indicate URL/URI signing is not implemented on this :term:`Delivery Service` +:rangeSliceBlockSize: An integer that defines the byte block size for the ATS Slice Plugin. It can only and must be set if ``rangeRequestHandling`` is set to 3. +:sslKeyVersion: This integer indicates the :ref:`ds-ssl-key-version` +:tenantId: The integral, unique identifier of the :ref:`ds-tenant` who owns this :term:`Delivery Service` +:tlsVersions: A list of explicitly supported :ref:`ds-tls-versions` +:topology: The unique name of the :term:`Topology` that this :term:`Delivery Service` is assigned to +:trRequestHeaders: If defined, this defines the :ref:`ds-tr-req-headers` used by Traffic Router for this :term:`Delivery Service` +:trResponseHeaders: If defined, this defines the :ref:`ds-tr-resp-headers` used by Traffic Router for this :term:`Delivery Service` +:type: The :ref:`ds-types` of this :term:`Delivery Service` +:typeId: The integral, unique identifier of the :ref:`ds-types` of this :term:`Delivery Service` +:xmlId: This :term:`Delivery Service`'s :ref:`ds-xmlid` + +.. 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-Encoding: gzip + Content-Type: application/json + Permissions-Policy: interest-cohort=() + Set-Cookie: mojolicious=...; Path=/; Expires=Tue, 08 Jun 2021 01:15:07 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: RO4tVfDdqx0rEU9BqlRmvsYXmVgVNkivqr6LhJlMulfR+1bLGivP8z93jy3N9bejcMdQwl1RwJojM3MbwgXcqA== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 08 Jun 2021 00:15:07 GMT + Content-Length: 806 + + { "response": [{ + "active": false, + "anonymousBlockingEnabled": false, + "ccrDnsTtl": 3600, + "cdnId": 2, + "cdnName": "CDN-in-a-Box", + "checkPath": null, + "consistentHashQueryParams": [], + "consistentHashRegex": null, + "deepCachingType": "NEVER", + "displayName": "test", + "dnsBypassCname": null, + "dnsBypassIp": null, + "dnsBypassIp6": null, + "dnsBypassTtl": null, + "dscp": 0, + "ecsEnabled": false, + "edgeHeaderRewrite": null, + "exampleURLs": [ + "http://cdn.test.mycdn.ciab.test" + ], + "firstHeaderRewrite": null, + "fqPacingRate": null, + "geoLimit": 0, + "geoLimitCountries": null, + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": null, + "globalMaxTps": null, + "httpBypassFqdn": null, + "id": 7, + "infoUrl": null, + "initialDispersion": 1, + "innerHeaderRewrite": null, + "ipv6RoutingEnabled": true, + "lastHeaderRewrite": null, + "lastUpdated": "2021-06-08T00:14:04.959292Z", + "logsEnabled": false, + "longDesc": "Apachecon North America 2018", + "matchList": [ + { + "type": "HOST_REGEXP", + "setNumber": 0, + "pattern": ".*\\.test\\..*" + } + ], + "maxDnsAnswers": null, + "maxOriginConnections": 0, + "maxRequestHeaderBytes": 0, + "midHeaderRewrite": null, + "missLat": 41.881944, + "missLong": -87.627778, + "multiSiteOrigin": false, + "originShield": null, + "orgServerFqdn": "http://origin.infra.ciab.test", + "profileDescription": null, + "profileId": null, + "profileName": null, + "protocol": 0, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "rangeSliceBlockSize": null, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "cdn", + "serviceCategory": null, + "signed": false, + "signingAlgorithm": null, + "sslKeyVersion": null, + "tenant": "root", + "tenantId": 1, + "tlsVersions": null, + "topology": null, + "trResponseHeaders": null, + "trRequestHeaders": null, + "type": "HTTP", + "typeId": 1, + "xmlId": "test" + }]} + + +.. [#tenancy] Only the :term:`Delivery Services` visible to the requesting user's :term:`Tenant` will appear, regardless of their :term:`Role` or the :term:`Delivery Services`' actual 'server assignment' status. + +``POST`` +======== +Assign an arbitrary number of :term:`Delivery Services` to a single server. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: DELIVERY-SERVICE:READ, SERVER:READ, DELIVERY-SERVICE:UPDATE, SERVER:UPDATE +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+----------+---------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +------+----------+---------------------------------------------------------------------------------------------+ + | ID | Yes | The integral, unique identifier of the server that you want to assign delivery services to. | + +------+----------+---------------------------------------------------------------------------------------------+ + +.. table:: Request Query Parameters + + +---------+----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +---------+----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + | replace | Yes | Whether the list of :term:`Delivery Services` you provide should replace the existing list or be merged with the existing list. Must be a 1, or true, or 0, or false. | + +---------+----------+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------+ + +The request body is an array of IDs of :term:`Delivery Services` that you want to assign to the server. The array can be empty, but it must be provided. + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/servers/6/deliveryservices?replace=1 HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 3 + + [ + 1 + ] + +Response Structure +------------------ +:dsIds: An array of integral, unique identifiers for :term:`Delivery Services` which the request added to server. If ``:replace:`` is ``false``, :term:`Delivery Services` that are already assigned will remain, though they are not listed by ``:dsIds:``. +:replace: The ``:replace:`` value you provided in the body of the request, or ``null`` if none was provided. +:serverId: The server's integral, unique identifier + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Tue, 25 Feb 2020 09:08:32 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: iV+JzAZSsmlxRZsNtIRg3oA9470hAwrMpq5xhcYVi0Y831Trx2YRlsyhYpOPqHg5+QPoXHGF0nx8uso0fuNarw== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 25 Feb 2020 08:08:32 GMT + Content-Length: 129 + + { + "alerts": [ + { + "text": "successfully assigned dses to server", + "level": "success" + } + ], + "response": { + "serverId": 6, + "dsIds": [ + 1 + ], + "replace": true + } + } diff --git a/docs/source/api/v5/servers_id_queue_update.rst b/docs/source/api/v5/servers_id_queue_update.rst new file mode 100644 index 0000000000..abf0aa6866 --- /dev/null +++ b/docs/source/api/v5/servers_id_queue_update.rst @@ -0,0 +1,97 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-servers-id-queue_update: + +******************************* +``servers/{{ID}}/queue_update`` +******************************* +.. caution:: In the vast majority of cases, it is advisable that the ``PUT`` method of the :ref:`to-api-servers-id` endpoint be used instead. + +``POST`` +======== +:term:`Queue` or dequeue updates for a specific server. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: SERVER:QUEUE, SERVER:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+---------------------------------------------------------------------------------------------+ + | Name | Description | + +======+=============================================================================================+ + | ID | The integral, unique identifier of the server on which updates are being queued or dequeued | + +------+---------------------------------------------------------------------------------------------+ + +:action: A string describing what action to take regarding server updates; one of: + + queue + :term:`Queue Updates` for the server, propagating configuration changes to the actual server + dequeue + Cancels any pending updates on the server + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/servers/13/queue_update HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 22 + Content-Type: application/json + + { + "action": "dequeue" + } + +Response Structure +------------------ +:action: The action processed, one of: + + queue + :term:`Queue Updates` was performed on the server, propagating configuration changes to the actual server + dequeue + Canceled any pending updates on the server + +:serverId: The integral, unique identifier of the server on which ``action`` was taken + +.. 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 + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Cache-Control: no-cache, no-store, max-age=0, must-revalidate + Content-Type: application/json + Date: Mon, 10 Dec 2018 18:20:04 GMT + X-Server-Name: traffic_ops_golang/ + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: 9Mmo9hIFZyF5gAvfdJD//VH9eNgiHVLinXt88H0GlJSHhwND8gMxaFyC+f9XZfiNAoGd1MKi1934ZJGmaIR6qQ== + Content-Length: 49 + + { + "response": { + "serverId": "13", + "action": "dequeue" + } + } diff --git a/docs/source/api/v5/servers_id_status.rst b/docs/source/api/v5/servers_id_status.rst new file mode 100644 index 0000000000..5575267968 --- /dev/null +++ b/docs/source/api/v5/servers_id_status.rst @@ -0,0 +1,84 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-servers-id-status: + +************************* +``servers/{{ID}}/status`` +************************* + +``PUT`` +======= +Updates server status and queues updates on all descendant :term:`Topology` nodes or child caches if server type is EDGE or MID. Also, captures offline reason if status is set to ADMIN_DOWN or OFFLINE and prepends offline reason with the user that initiated the status change. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: SERVER:UPDATE, SERVER:READ, STATUS:READ +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+-----------------------------------------------------------------------------+ + | Name | Description | + +======+=============================================================================+ + | ID | The integral, unique identifier of the server whose status is being changed | + +------+-----------------------------------------------------------------------------+ + +:offlineReason: A string containing the reason for the status change +:status: The name or integral, unique identifier of the server's new status + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/servers/13/status HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 56 + Content-Type: application/json + + { + "status": "ADMIN_DOWN", + "offlineReason": "Bad drives" + } + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Cache-Control: no-cache, no-store, max-age=0, must-revalidate + Content-Type: application/json + Date: Mon, 10 Dec 2018 18:08:44 GMT + X-Server-Name: traffic_ops_golang/ + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: LS1jCo5eMVKxmeYDol0I2LgLYazocSggR5hynNoLcPmMov9u2s3ulksPdQtG1N3aS+VM9tdMsCrahFPraLJVwg== + Content-Length: 158 + + { "alerts": [ + { + "level": "success", + "text": "Updated status [ ADMIN_DOWN ] for quest.infra.ciab.test [ admin: Bad drives ] and queued updates on all child caches" + } + ]} diff --git a/docs/source/api/v5/service_categories.rst b/docs/source/api/v5/service_categories.rst new file mode 100644 index 0000000000..7f6aa563b3 --- /dev/null +++ b/docs/source/api/v5/service_categories.rst @@ -0,0 +1,152 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-service-categories: + +********************** +``service_categories`` +********************** + + +``GET`` +======= +Get all requested :term:`Service Categories`. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: SERVICE-CATEGORY:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-----------+---------------------------------------------------------------------------------------------------------------+ + | Name | Description | + +===========+===============================================================================================================+ + | name | Filter for :term:`Service Categories` with this name | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | orderby | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` | + | | array | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | sortOrder | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | limit | Choose the maximum number of results to return | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | offset | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +-----------+---------------------------------------------------------------------------------------------------------------+ + | page | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long | + | | and the first page is 1. If ``offset`` was defined, this query parameter has no effect. ``limit`` must be | + | | defined to make use of ``page``. | + +-----------+---------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/service_categories?name=SERVICE_CATEGORY_NAME HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:name: This :term:`Service Category`'s name +:lastUpdated: The date and time at which this :term:`Service Category` was last modified, in :ref:`non-rfc-datetime` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: Yzr6TfhxgpZ3pbbrr4TRG4wC3PlnHDDzgs2igtz/1ppLSy2MzugqaGW4y5yzwzl5T3+7q6HWej7GQZt1XIVeZQ== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 11 Mar 2020 20:02:47 GMT + Content-Length: 102 + + { + "response": [ + { + "lastUpdated": "2020-03-04 15:46:20-07", + "name": "SERVICE_CATEGORY_NAME" + } + ] + } + +``POST`` +======== +Create a new service category. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: SERVICE-CATEGORY:CREATE, SERVICE-CATEGORY:READ +:Response Type: Object + +Request Structure +----------------- +:name: This :term:`Service Category`'s name + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/service_categories HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 48 + Content-Type: application/json + + { + "name": "SERVICE_CATEGORY_NAME", + } + +Response Structure +------------------ +:name: This :term:`Service Category`'s name +:lastUpdated: The date and time at which this :term:`Service Category` was last modified, in :ref:`non-rfc-datetime` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: +pJm4c3O+JTaSXNt+LP+u240Ba/SsvSSDOQ4rDc6hcyZ0FIL+iY/WWrMHhpLulRGKGY88bM4YPCMaxGn3FZ9yQ== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 11 Mar 2020 20:12:20 GMT + Content-Length: 154 + + { + "alerts": [ + { + "text": "serviceCategory was created.", + "level": "success" + } + ], + "response": { + "lastUpdated": "2020-03-11 14:12:20-06", + "name": "SERVICE_CATEGORY_NAME" + } + } diff --git a/docs/source/api/v5/service_categories_name.rst b/docs/source/api/v5/service_categories_name.rst new file mode 100644 index 0000000000..ba16efd3d2 --- /dev/null +++ b/docs/source/api/v5/service_categories_name.rst @@ -0,0 +1,149 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-service-categories-name: + +******************************* +``service_categories/{{name}}`` +******************************* + +``PUT`` +======== +Update a service category. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: SERVICE-CATEGORY:UPDATE, SERVICE-CATEGORY:READ +:Response Type: Object + +Request Structure +----------------- +:name: The :term:`Service Category`'s new name + +.. table:: Request Path Parameters + + +------------+------------------------------------------------------------------------+ + | Name | Description | + +============+========================================================================+ + | name | The current name of the :term:`Service Category` | + +------------+------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/service_categories/sc-name HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 48 + Content-Type: application/json + + { + "name": "New Name", + } + +Response Structure +------------------ +:name: This :term:`Service Category`'s name +:lastUpdated: The date and time at which this :term:`Service Category` was last modified, in :ref:`non-rfc-datetime` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: +pJm4c3O+JTaSXNt+LP+u240Ba/SsvSSDOQ4rDc6hcyZ0FIL+iY/WWrMHhpLulRGKGY88bM4YPCMaxGn3FZ9yQ== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 11 Mar 2020 20:12:20 GMT + Content-Length: 189 + + { + "alerts": [ + { + "text": "Service Category was updated.", + "level": "success" + } + ], + "response": { + "lastUpdated": "2020-03-11 14:12:20-06", + "name": "New Name" + } + } + +``DELETE`` +========== +Deletes a specific :term:`Service Category`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: SERVICE-CATEGORY:DELETE, SERVICE-CATEGORY:READ +:Response Type: ``undefined`` + + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------------+------------------------------------------------------------------------+ + | Name | Description | + +============+========================================================================+ + | name | The current name of the :term:`Service Category` to be deleted | + +------------+------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/service_categories/my-service-category HTTP/1.1 + User-Agent: python-requests/2.23.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 0 + + +Response Structure +------------------ + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 17 Aug 2020 16:13:31 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: yErJobzG9IA0khvqZQK+Yi7X4pFVvOqxn6PjrdzN5DnKVm/K8Kka3REul1XmKJnMXVRY8RayoEVGDm16mBFe4Q== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 17 Aug 2020 15:13:31 GMT + Content-Length: 103 + + { + "alerts": [ + { + "text": "my-service-category was deleted.", + "level": "success" + } + ] + } diff --git a/docs/source/api/v5/snapshot.rst b/docs/source/api/v5/snapshot.rst new file mode 100644 index 0000000000..043fa2059c --- /dev/null +++ b/docs/source/api/v5/snapshot.rst @@ -0,0 +1,77 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-snapshot: + +************ +``snapshot`` +************ + +``PUT`` +======= +Performs a CDN :term:`Snapshot`. Effectively, this propagates the new *configuration* of the CDN to its *operating state*, which replaces the output of the :ref:`to-api-cdns-name-snapshot` endpoint with the output of the :ref:`to-api-cdns-name-snapshot-new` endpoint. +This also changes the output of the :ref:`to-api-cdns-name-configs-monitoring` endpoint since that endpoint returns the latest monitoring information from the *operating state*. + +.. Note:: By default, snapshotting the CDN also deletes all HTTPS certificates for every :term:`Delivery Service` which has been deleted since the last :term:`Snapshot`. In order to disable this behavior, set ``disable_auto_cert_deletion`` in :ref:`cdn.conf` to ``true``. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: CDN-SNAPSHOT:CREATE, CDN-SNAPSHOT:READ +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-------+-----------------------------------------------------------------+ + | Name | Description | + +=======+=================================================================+ + | cdn | The name of the CDN for which a :term:`Snapshot` shall be taken | + +-------+-----------------------------------------------------------------+ + | cdnID | The id of the CDN for which a :term:`Snapshot` shall be taken | + +-------+-----------------------------------------------------------------+ + +.. Note:: At least one query parameter must be given. + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/snapshot?cdn=CDN-in-a-Box HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: gmaWI0tKgNFPYO0zMrLCGDosBJkPbeIvW4BH9tEh96VjBqyWqzjgPySoMa3ViM1BQXA6VAUOGmc76VyhBsaTzA== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 18 Mar 2020 15:51:48 GMT + Content-Length: 47 + + { + "response": "SUCCESS" + } diff --git a/docs/source/api/v5/sslkey_expirations.rst b/docs/source/api/v5/sslkey_expirations.rst new file mode 100644 index 0000000000..704947df34 --- /dev/null +++ b/docs/source/api/v5/sslkey_expirations.rst @@ -0,0 +1,90 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-sslkey_expirations: + +********************** +``sslkey_expirations`` +********************** + +``GET`` +======= +Retrieves SSL certificate expiration information. + +:Auth. Required: Yes +:Roles Required: "admin" +:Permissions Required: ACME:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-------------------+----------+--------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +===================+==========+========================================================================================================+ + | days | no | Return only the expiration information for SSL certificates expiring in the next given number of days. | + +-------------------+----------+--------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/sslkey_expirations?days=30 HTTP/1.1 + Host: trafficops.infra.ciab.test + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + +Response Structure +------------------ +:deliveryservice: The :ref:`ds-xmlid` for the :term:`Delivery Service` corresponding to this SSL certificate. +:cdn: The ID for the :abbr:`CDN (Content Delivery Network)` corresponding to this SSL certificate. +:provider: The provider of this SSL certificate, generally the name of the Certificate Authority or the :abbr:`ACME (Automatic Certificate Management Environment)` account. +:expiration: The expiration date of this SSL certificate. +:federated: A boolean indicating if this SSL certificate is use in a federated :term:`Delivery Service`. + +.. 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-Encoding: gzip + Content-Type: application/json + Permissions-Policy: interest-cohort=() + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 07 Jun 2021 22:52:20 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + X-Server-Name: traffic_ops_golang/ + Date: Mon, 07 Jun 2021 21:52:20 GMT + Content-Length: 384 + + { "response": [ + { + "deliveryservice": "foo1", + "cdn": "cdn1", + "provider": "Self Signed", + "expiration": "2022-08-02T15:38:06-06:00", + "federated": false + }, + { + "deliveryservice": "foo2", + "cdn": "cdn2", + "provider": "Lets Encrypt", + "expiration": "2022-07-12T12:14:00-06:00", + "federated": true + } + ]} diff --git a/docs/source/api/v5/staticdnsentries.rst b/docs/source/api/v5/staticdnsentries.rst new file mode 100644 index 0000000000..7c0a35fe42 --- /dev/null +++ b/docs/source/api/v5/staticdnsentries.rst @@ -0,0 +1,374 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-staticdnsentries: + +******************** +``staticdnsentries`` +******************** + +``GET`` +======= +Retrieve all static DNS entries configured within Traffic Control + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: STATIC-DN:READ, CACHE-GROUP:READ, DELIVERY-SERVICE:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +===================+==========+============================================================================================================================================+ + | address | no | Return only static DNS entries that operate on this address/:abbr:`CNAME (Canonical Name)` | + +-------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------------+ + | cachegroup | no | Return only static DNS entries assigned to the :term:`Cache Group` that has this :ref:`cache-group-name` | + +-------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------------+ + | cachegroupId | no | Return only static DNS entries assigned to the :term:`Cache Group` that has this :ref:`cache-group-id` | + +-------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------------+ + | deliveryservice | no | Return only static DNS entries that apply within the domain of the :term:`Delivery Service` with this :ref:`ds-xmlid` | + +-------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------------+ + | deliveryserviceId | no | Return only static DNS entries that apply within the domain of the :term:`Delivery Service` identified by this integral, unique identifier | + +-------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------------+ + | host | no | Return only static DNS entries that resolve this :abbr:`FQDN (Fully Qualified Domain Name)` | + +-------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------------+ + | id | no | Return only the static DNS entry with this integral, unique identifier | + +-------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------------+ + | ttl | no | Return only static DNS entries with this :abbr:`TTL (Time To Live)` | + +-------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------------+ + | type | no | Return only static DNS entries of this type | + +-------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------------+ + | typeId | no | Return only static DNS entries of the type identified by this integral, unique identifier | + +-------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +-------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +-------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +-------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long and the first page is 1. | + | | | If ``offset`` was defined, this query parameter has no effect. ``limit`` must be defined to make use of ``page``. | + +-------------------+----------+--------------------------------------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/staticdnsentries?address=foo.bar HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:address: If ``typeId`` identifies a ``CNAME`` type record, this is the Canonical Name (CNAME) of the server with a trailing period, otherwise it is the IP address to which ``host`` shall be resolved +:cachegroup: An optional string containing the :ref:`Name of a Cache Group ` which will service this static DNS entry + + .. note:: This field has no effect, and is not used by any part of Traffic Control. It exists for legacy compatibility reasons. + +:cachegroupId: An optional, integer that is the :ref:`ID of a Cache Group ` which will service this static DNS entry + + .. note:: This field has no effect, and is not used by any part of Traffic Control. It exists for legacy compatibility reasons. + +:deliveryservice: The name of a :term:`Delivery Service` under the domain of which this static DNS entry shall be active +:deliveryserviceId: The integral, unique identifier of a :term:`Delivery Service` under the domain of which this static DNS entry shall be active +:host: If ``typeId`` identifies a ``CNAME`` type record, this is an alias for the CNAME of the server, otherwise it is the Fully Qualified Domain Name (FQDN) which shall resolve to ``address`` +:id: An integral, unique identifier for this static DNS entry +:ttl: The :abbr:`TTL (Time To Live)` of this static DNS entry in seconds +:type: The name of the type of this static DNS entry +:typeId: The integral, unique identifier of the :term:`Type` of this static DNS entry + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: Px1zTH3ihg+hfmdADGcap0Juuud39fGsx5Y3CzqaFNmRwFu1ZLMzOsy0EN2pb7vpOtpI6/zeIUYAC3dbsBwOmA== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 10 Dec 2018 20:04:33 GMT + Content-Length: 226 + + { "response": [ + { + "address": "foo.bar.", + "cachegroup": null, + "cachegroupId": null, + "deliveryservice": "demo1", + "deliveryserviceId": 1, + "host": "test", + "id": 2, + "lastUpdated": "2018-12-10 19:59:56+00", + "ttl": 300, + "type": "CNAME_RECORD", + "typeId": 41 + } + ]} + +``POST`` +======== +Creates a new, static DNS entry. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: STATIC-DN:CREATE, STATIC-DN:READ, CACHE-GROUP:READ, DELIVERY-SERVICE:READ +:Response Type: Object + +Request Structure +----------------- +:address: If ``typeId`` identifies a ``CNAME`` type record, this is the Canonical Name (CNAME) of the server with a trailing period, otherwise it is the IP address to which ``host`` shall be resolved +:cachegroupId: An optional, integer that is the :ref:`ID of a Cache Group ` which will service this static DNS entry + + .. note:: This field has no effect, and is not used by any part of Traffic Control. It exists for legacy compatibility reasons. + +:deliveryserviceId: The integral, unique identifier of a :term:`Delivery Service` under the domain of which this static DNS entry shall be active +:host: If ``typeId`` identifies a ``CNAME`` type record, this is an alias for the CNAME of the server, otherwise it is the :abbr:`FQDN (Fully Qualified Domain Name)` which shall resolve to ``address`` +:ttl: The :abbr:`TTL (Time To Live)` of this static DNS entry in seconds +:typeId: The integral, unique identifier of the :term:`Type` of this static DNS entry + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/staticdnsentries HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 92 + Content-Type: application/json + + { + "address": "test.quest.", + "deliveryserviceId": 1, + "host": "test", + "ttl": 300, + "typeId": 41 + } + +Response Structure +------------------ +:address: If ``typeId`` identifies a ``CNAME`` type record, this is the Canonical Name (CNAME) of the server with a trailing period, otherwise it is the IP address to which ``host`` shall be resolved +:cachegroup: An optional string containing the :ref:`Name of a Cache Group ` which will service this static DNS entry + + .. note:: This field has no effect, and is not used by any part of Traffic Control. It exists for legacy compatibility reasons. + +:cachegroupId: An optional, integer that is the :ref:`ID of a Cache Group ` which will service this static DNS entry + + .. note:: This field has no effect, and is not used by any part of Traffic Control. It exists for legacy compatibility reasons. + +:deliveryservice: The name of a :term:`Delivery Service` under the domain of which this static DNS entry shall be active +:deliveryserviceId: The integral, unique identifier of a :term:`Delivery Service` under the domain of which this static DNS entry shall be active +:host: If ``typeId`` identifies a ``CNAME`` type record, this is an alias for the CNAME of the server, otherwise it is the Fully Qualified Domain Name (FQDN) which shall resolve to ``address`` +:id: An integral, unique identifier for this static DNS entry +:ttl: The :abbr:`TTL (Time To Live)` of this static DNS entry in seconds +:type: The name of the :term:`Type` of this static DNS entry +:typeId: The integral, unique identifier of the :term:`Type` of this static DNS entry + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: 8dcJyjw2NJZx0L9Oz16P7g/7j5A1jlpyiY6Y+rRVQ2wGcwYI3yiGPrz6ur0qKzgqEBBsh8aPF44WTHAR9jUJdg== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 10 Dec 2018 19:54:19 GMT + Content-Length: 282 + + { "alerts": [ + { + "text": "staticDNSEntry was created.", + "level": "success" + } + ], + "response": { + "address": "test.quest.", + "cachegroup": null, + "cachegroupId": null, + "deliveryservice": null, + "deliveryserviceId": 1, + "host": "test", + "id": 2, + "lastUpdated": "2018-12-10 19:54:19+00", + "ttl": 300, + "type": "CNAME_RECORD", + "typeId": 41 + }} + +``PUT`` +======= +Updates a static DNS entry. + +:Auth. Required: Yes +:Role(s) Required: "admin" or "operator" +:Permissions Required: STATIC-DN:UPDATE, STATIC-DN:READ, CACHE-GROUP:READ, DELIVERY-SERVICE:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Query Parameters + + +------+-------------------------------------------------------------------+ + | Name | Description | + +======+===================================================================+ + | id | The integral, unique identifier of the static DNS entry to modify | + +------+-------------------------------------------------------------------+ + +:address: If ``typeId`` identifies a ``CNAME`` type record, this is the Canonical Name (CNAME) of the server with a trailing period, otherwise it is the IP address to which ``host`` shall be resolved +:cachegroupId: An optional, integer that is the :ref:`ID of a Cache Group ` which will service this static DNS entry + + .. note:: This field has no effect, and is not used by any part of Traffic Control. It exists for legacy compatibility reasons. + +:deliveryserviceId: The integral, unique identifier of a :term:`Delivery Service` under the domain of which this static DNS entry shall be active +:host: If ``typeId`` identifies a ``CNAME`` type record, this is an alias for the CNAME of the server, otherwise it is the Fully Qualified Domain Name (FQDN) which shall resolve to ``address`` +:ttl: The :abbr:`TTL (Time To Live)` of this static DNS entry in seconds +:typeId: The integral, unique identifier of the :term:`Type` of this static DNS entry + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/staticdnsentries?id=2 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 89 + Content-Type: application/json + + { + "address": "foo.bar.", + "deliveryserviceId": 1, + "host": "test", + "ttl": 300, + "typeId": 41 + } + +Response Structure +------------------ +:address: If ``typeId`` identifies a ``CNAME`` type record, this is the Canonical Name (CNAME) of the server with a trailing period, otherwise it is the IP address to which ``host`` shall be resolved +:cachegroup: An optional string containing the :ref:`Name of a Cache Group ` which will service this static DNS entry + + .. note:: This field has no effect, and is not used by any part of Traffic Control. It exists for legacy compatibility reasons. + +:cachegroupId: An optional, integer that is the :ref:`ID of a Cache Group ` which will service this static DNS entry + + .. note:: This field has no effect, and is not used by any part of Traffic Control. It exists for legacy compatibility reasons. + +:deliveryservice: The name of a :term:`Delivery Service` under the domain of which this static DNS entry shall be active +:deliveryserviceId: The integral, unique identifier of a :term:`Delivery Service` under the domain of which this static DNS entry shall be active +:host: If ``typeId`` identifies a ``CNAME`` type record, this is an alias for the CNAME of the server, otherwise it is the :abbr:`FQDN (Fully Qualified Domain Name)` which shall resolve to ``address`` +:id: An integral, unique identifier for this static DNS entry +:ttl: The :abbr:`TTL (Time To Live)` of this static DNS entry in seconds +:type: The name of the :term:`Type` of this static DNS entry +:typeId: The integral, unique identifier of the :term:`Type` of this static DNS entry + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: +FaYmpnlIIzVSBq0nosw29NZcV9xFhlVgWuUqXUyiDihVUSzX4jrdAloRDgzDvKsYQB8LSkPdGHwt1zjgSzUtA== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 10 Dec 2018 19:59:56 GMT + Content-Length: 279 + + { "alerts": [ + { + "text": "staticDNSEntry was updated.", + "level": "success" + } + ], + "response": { + "address": "foo.bar.", + "cachegroup": null, + "cachegroupId": null, + "deliveryservice": null, + "deliveryserviceId": 1, + "host": "test", + "id": 2, + "lastUpdated": "2018-12-10 19:59:56+00", + "ttl": 300, + "type": "CNAME_RECORD", + "typeId": 41 + }} + + +``DELETE`` +========== +Delete staticdnsentries. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: STATIC-DN:DELETE, STATIC-DN:READ, DELIVERY-SERVICE:READ, CACHE-GROUP:READ +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Query Parameters + + +------+-------------------------------------------------------------------+ + | Name | Description | + +======+===================================================================+ + | id | The integral, unique identifier of the static DNS entry to delete | + +------+-------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/staticdnsentries?id=2 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: g6uqHPU44LuTtqU2ahtazrVCpcpNWVc9kWJQOYRuiVLDnsm39KOB/xt3XM6j0/X3WYiIawnNspkxRC85LJHwFA== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 10 Dec 2018 20:05:52 GMT + Content-Length: 69 + + { "alerts": [ + { + "text": "staticDNSEntry was deleted.", + "level": "success" + } + ]} diff --git a/docs/source/api/v5/stats_summary.rst b/docs/source/api/v5/stats_summary.rst new file mode 100644 index 0000000000..60a45ee582 --- /dev/null +++ b/docs/source/api/v5/stats_summary.rst @@ -0,0 +1,251 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-stats-summary: + +***************** +``stats_summary`` +***************** + +``GET`` +======= +Either retrieve a list of summary stats or the timestamp of the latest recorded stats summary. + +What is returned is driven by the query parameter ``lastSummaryDate``. + +If the parameter is set it will return an object with the latest timestamp, else an array of summary stats will be returned. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: STAT:READ, CDN:READ, DELIVERY-SERVICE:READ +:Response Type: Array or Object + +Request Structure +----------------- + +Summary Stats +""""""""""""" + +.. table:: Request Query Parameters + + +---------------------+----------+-------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +=====================+==========+=======================================================================================================+ + | deliveryServiceName | no | Return only summary stats that were reported for :term:`Delivery Service` with the given display name | + +---------------------+----------+-------------------------------------------------------------------------------------------------------+ + | cdnName | no | Return only summary stats that were reported for CDN with the given name | + +---------------------+----------+-------------------------------------------------------------------------------------------------------+ + | statName | no | Return only summary stats that were reported for given stat name | + +---------------------+----------+-------------------------------------------------------------------------------------------------------+ + | orderby | no | Choose the ordering of the results - can only be one of deliveryServiceName, statName or cdnName | + +---------------------+----------+-------------------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or | + | | | descending ("desc") | + +---------------------+----------+-------------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +---------------------+----------+-------------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +---------------------+----------+-------------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are | + | | | ``limit`` long and the first page is 1. If ``offset`` was defined, this query parameter has no | + | | | effect. ``limit`` must be defined to make use of ``page``. | + +---------------------+----------+-------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/stats_summary HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Last Updated Summary Stat +"""""""""""""""""""""""""" + +.. table:: Request Query Parameters + + +-----------------+----------+---------------------------------------------------+ + | Name | Required | Description | + +=================+==========+===================================================+ + | statName | no | Get lastest updated date for the given stat | + +-----------------+----------+---------------------------------------------------+ + | lastSummaryDate | yes | Tells route to get only lastest updated timestamp | + +-----------------+----------+---------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/stats_summary?lastSummaryDate=true HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ + +Summary Stats +""""""""""""" + +:cdnName: The CDN name for which the summary stat was taken for + + .. note:: If the ``cdn`` is equal to ``all`` it represents summary_stats across all delivery services across all CDNs + +:deliveryServiceName: The :term:`Delivery Service` display name for which the summary stat was taken for + + .. note:: If the ``deliveryServiceName`` is equal to ``all`` it represents summary_stats across all delivery services within the given CDN + +:statName: Stat name summary stat represents +:statValue: Summary stat value +:summaryTime: Timestamp of summary, in :ref:`non-rfc-datetime` +:statDate: Date stat was taken, in ``YYYY-MM-DD`` format + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: dHNip9kpTGGS1w39/fWcFehNktgmXZus8XaufnmDpv0PyG/3fK/KfoCO3ZOj9V74/CCffps7doEygWeL/xRtKA== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 10 Dec 2018 20:56:59 GMT + Content-Length: 150 + + { "response": [ + { + "cdnName": "CDN-in-a-Box", + "deliveryServiceName": "all", + "statName": "daily_maxgbps", + "statValue": 5, + "summaryTime": "2019-11-19 00:04:06+00", + "statDate": "2019-11-19" + }, + { + "cdnName": "CDN-in-a-Box", + "deliveryServiceName": "all", + "statName": "daily_maxgbps", + "statValue": 3, + "summaryTime": "2019-11-18 00:04:06+00", + "statDate": "2019-11-18" + }, + { + "cdnName": "CDN-in-a-Box", + "deliveryServiceName": "all", + "statName": "daily_bytesserved", + "statValue": 1000, + "summaryTime": "2019-11-19 00:04:06+00", + "statDate": "2019-11-19" + } + ]} + +Last Updated Summary Stat +""""""""""""""""""""""""" + +:summaryTime: Timestamp of the last updated summary, in :ref:`non-rfc-datetime` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: dHNip9kpTGGS1w39/fWcFehNktgmXZus8XaufnmDpv0PyG/3fK/KfoCO3ZOj9V74/CCffps7doEygWeL/xRtKA== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 10 Dec 2018 20:56:59 GMT + Content-Length: 150 + + { "response": { + "summaryTime": "2019-11-19 00:04:06+00" + }} + +``POST`` +======== + +Post a stats summary for a given stat. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: STAT:CREATE, STAT:READ, CDN:READ, DELIVERY-SERVICE:READ +:Response Type: Object + +Request Structure +----------------- +:cdnName: The CDN name for which the summary stat was taken for + + .. note:: If the ``cdn`` is equal to ``all`` it represents summary_stats across all delivery services across all CDNs + +:deliveryServiceName: The :term:`Delivery Service` display name for which the summary stat was taken for + + .. note:: If the ``deliveryServiceName`` is equal to ``all`` it represents summary_stats across all delivery services within the given CDN + +:statName: Stat name summary stat represents +:statValue: Summary stat value +:summaryTime: Timestamp of summary, in :ref:`non-rfc-datetime` +:statDate: Date stat was taken, in ``YYYY-MM-DD`` format + +.. note:: ``statName``, ``statValue`` and ``summaryTime`` are required. If ``cdnName`` and ``deliveryServiceName`` are not given they will default to ``all``. + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/stats_summary HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 113 + Content-Type: application/json + + { + "cdnName": "CDN-in-a-Box", + "deliveryServiceName": "all", + "statName": "daily_maxgbps", + "statValue": 10, + "summaryTime": "2019-12-05 00:03:57+00", + "statDate": "2019-12-05" + } + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: ezxk+iP7o7KE7zpWmGc0j8nz5k+1wAzY0HiNiA2xswTQrt+N+6CgQqUV2r9G1HAsPNr0HF2PhYs/Xr7DrYOw0A== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 06 Dec 2018 02:14:45 GMT + Content-Length: 97 + + { "alerts": [ + { + "text": "Stats Summary was successfully created", + "level": "success" + }] + } diff --git a/docs/source/api/v5/statuses.rst b/docs/source/api/v5/statuses.rst new file mode 100644 index 0000000000..3ccf081e72 --- /dev/null +++ b/docs/source/api/v5/statuses.rst @@ -0,0 +1,158 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-statuses: + +************ +``statuses`` +************ + +``GET`` +======= +Retrieves a list of all server :term:`Statuses`. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: STATUS:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-------------+----------+------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +=============+==========+======================================================================================================+ + | description | no | Return only :term:`Statuses` with this *exact* description | + +-------------+----------+------------------------------------------------------------------------------------------------------+ + | id | no | Return only the :term:`Status` with this integral, unique identifier | + +-------------+----------+------------------------------------------------------------------------------------------------------+ + | name | no | Return only :term:`Statuses` with this name | + +-------------+----------+------------------------------------------------------------------------------------------------------+ + | orderby | no | Choose the ordering of the results - must be the name of one | + | | | of the fields of the objects in the ``response`` array | + +-------------+----------+------------------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or | + | | | descending ("desc") | + +-------------+----------+------------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +-------------+----------+------------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +-------------+----------+------------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are | + | | | ``limit`` long and the first page is 1. If ``offset`` was defined, this query parameter has no | + | | | effect. ``limit`` must be defined to make use of ``page``. | + +-------------+----------+------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/statuses?name=REPORTED HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +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 :ref:`non-rfc-datetime` +:name: The name of the status + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: dHNip9kpTGGS1w39/fWcFehNktgmXZus8XaufnmDpv0PyG/3fK/KfoCO3ZOj9V74/CCffps7doEygWeL/xRtKA== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 10 Dec 2018 20:56:59 GMT + Content-Length: 150 + + { "response": [ + { + "description": "Server is online and reported in the health protocol.", + "id": 3, + "lastUpdated": "2018-12-10 19:11:17+00", + "name": "REPORTED" + } + ]} + +``POST`` +========== +Creates a Server :term:`Status`. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: STATUS:CREATE, STATUS:READ +:Response Type: Array + +Request Structure +----------------- +:description: Create a :term:`Status` with this description +:name: Create a :term:`Status` with this name + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/statuses HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + + { "description": "test", "name": "example" } + +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 :ref:`non-rfc-datetime` +:name: The name of the status + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: dHNip9kpTGGS1w39/fWcFehNktgmXZus8XaufnmDpv0PyG/3fK/KfoCO3ZOj9V74/CCffps7doEygWeL/xRtKA== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 10 Dec 2018 20:56:59 GMT + Content-Length: 167 + + { "alerts": [ + { + "text": "status was created.", + "level": "success" + } + ],"response": [ + { + "description": "test", + "name": "example" + "id": 3, + "lastUpdated": "2018-12-10 19:11:17+00", + } + ]} diff --git a/docs/source/api/v5/statuses_id.rst b/docs/source/api/v5/statuses_id.rst new file mode 100644 index 0000000000..159bd43bca --- /dev/null +++ b/docs/source/api/v5/statuses_id.rst @@ -0,0 +1,158 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-statuses-id: + +********************* +``statuses/{{ID}}`` +********************* + +``GET`` +======= +Retrieves a list of all server :term:`Statuses`. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: STATUS:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-------------+----------+------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +=============+==========+======================================================================================================+ + | description | no | Return only :term:`Statuses` with this *exact* description | + +-------------+----------+------------------------------------------------------------------------------------------------------+ + | id | no | Return only the :term:`Status` with this integral, unique identifier | + +-------------+----------+------------------------------------------------------------------------------------------------------+ + | name | no | Return only :term:`Statuses` with this name | + +-------------+----------+------------------------------------------------------------------------------------------------------+ + | orderby | no | Choose the ordering of the results - must be the name of one | + | | | of the fields of the objects in the ``response`` array | + +-------------+----------+------------------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or | + | | | descending ("desc") | + +-------------+----------+------------------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +-------------+----------+------------------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +-------------+----------+------------------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are | + | | | ``limit`` long and the first page is 1. If ``offset`` was defined, this query parameter has no | + | | | effect. ``limit`` must be defined to make use of ``page``. | + +-------------+----------+------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/statuses/3 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +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 :ref:`non-rfc-datetime` +:name: The name of the status + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: dHNip9kpTGGS1w39/fWcFehNktgmXZus8XaufnmDpv0PyG/3fK/KfoCO3ZOj9V74/CCffps7doEygWeL/xRtKA== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 10 Dec 2018 20:56:59 GMT + Content-Length: 150 + + { "response": [ + { + "description": "Server is online and reported in the health protocol.", + "id": 3, + "lastUpdated": "2018-12-10 19:11:17+00", + "name": "REPORTED" + } + ]} + +``PUT`` +======= +Updates a :term:`Status`. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: STATUS:UPDATE, STATUS:READ +:Response Type: Array + +Request Structure +----------------- +:description: The description of the updated :term:`Status` +:name: The name of the updated :term:`Status` + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/statuses/3 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + + { "description": "test", "name": "example" } + +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 :ref:`non-rfc-datetime` +:name: The name of the status + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: dHNip9kpTGGS1w39/fWcFehNktgmXZus8XaufnmDpv0PyG/3fK/KfoCO3ZOj9V74/CCffps7doEygWeL/xRtKA== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 10 Dec 2018 20:56:59 GMT + Content-Length: 167 + + { "alerts": [ + { + "text": "status was created.", + "level": "success" + } + ],"response": [ + { + "description": "test", + "name": "example" + "id": 3, + "lastUpdated": "2018-12-10 19:11:17+00", + } + ]} diff --git a/docs/source/api/v5/steering.rst b/docs/source/api/v5/steering.rst new file mode 100644 index 0000000000..dadfaa20fe --- /dev/null +++ b/docs/source/api/v5/steering.rst @@ -0,0 +1,105 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-steering: + +************ +``steering`` +************ + +``GET`` +======= +Gets a list of all :ref:`Steering Targets ` in the Traffic Ops database. + +:Auth. Required: Yes +:Roles Required: "Portal", "Steering", "Federation", "operations" or "admin" +:Permissions Required: STEERING:READ, DELIVERY-SERVICE:READ +:Response Type: Array + +Request Structure +----------------- +No parameters available. + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/steering HTTP/1.1 + User-Agent: python-requests/2.22.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + +Response Structure +------------------ +:deliveryService: A string that is the :ref:`ds-xmlid` of the steering :term:`Delivery Service` +:clientSteering: Whether this is a :ref:`client steering ` Delivery Service. +:targets: The delivery services that the :ref:`Steering Delivery Service ` targets. + + :order: If this is a :ref:`STEERING_ORDER ` target, this is the value of the order. Otherwise, ``0``. + :weight: If this is a :ref:`STEERING_WEIGHT ` target, this is the value of the weight. Otherwise, ``0``. + :deliveryService: A string that is the :ref:`ds-xmlid` of the steering :term:`Delivery Service` + +:filters: Filters of type :ref:`STEERING_REGEXP ` that exist on either of the targets. + + :deliveryService: A string that is the :ref:`ds-xmlid` of the steering :term:`Delivery Service` + :pattern: A regular expression - the use of this pattern is dependent on the ``type`` field (backslashes are escaped) + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 24 Feb 2020 18:56:57 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: hcJa4xVLDx7bxBmoSjYo5YUwdSBWQr9GlqRYrc6ZU7LeenjiV3go22YlIHt/GtjLcHQjJ5DulKRhdsvFMq7Fng== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 24 Feb 2020 17:56:57 GMT + Content-Length: 167 + + { + "response": [ + { + "deliveryService": "steering1", + "clientSteering": true, + "targets": [ + { + "order": 0, + "weight": 1, + "deliveryService": "demo1" + }, + { + "order": 0, + "weight": 2, + "deliveryService": "demo2" + } + ], + "filters": [ + { + "deliveryService": "demo1", + "pattern": ".*\\.demo1\\..*" + }, + { + "deliveryService": "demo2", + "pattern": ".*\\.demo2*\\..*" + } + ] + } + ] + } diff --git a/docs/source/api/v5/steering_id_targets.rst b/docs/source/api/v5/steering_id_targets.rst new file mode 100644 index 0000000000..c91cc7d83e --- /dev/null +++ b/docs/source/api/v5/steering_id_targets.rst @@ -0,0 +1,186 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-steering-id-targets: + +*************************** +``steering/{{ID}}/targets`` +*************************** + +``GET`` +======= +Get all targets for a steering :term:`Delivery Service`. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: STEERING:READ, DELIVERY-SERVICE:READ, TYPE:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+----------------------------------------------------------------------------------------------------------+ + | Name | Description | + +======+==========================================================================================================+ + | ID | The integral, unique identifier of a steering :term:`Delivery Service` for which targets shall be listed | + +------+----------------------------------------------------------------------------------------------------------+ + +.. table:: Request Query Parameters + + +-----------+-------------------------------------------------------------------------------------------------------------------------+ + | Name | Description | + +===========+=========================================================================================================================+ + | target | Return only the target mappings that target the :term:`Delivery Service` identified by this integral, unique identifier | + +-----------+-------------------------------------------------------------------------------------------------------------------------+ + | orderby | Choose the ordering of the results - must be the name of one of the fields of the objects in the ``response`` array | + +-----------+-------------------------------------------------------------------------------------------------------------------------+ + | sortOrder | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +-----------+-------------------------------------------------------------------------------------------------------------------------+ + | limit | Choose the maximum number of results to return | + +-----------+-------------------------------------------------------------------------------------------------------------------------+ + | offset | The number of results to skip before beginning to return results. Must use in conjunction with limit | + +-----------+-------------------------------------------------------------------------------------------------------------------------+ + | page | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages are ``limit`` long and the | + | | first page is 1. If ``offset`` was defined, this query parameter has no effect. ``limit`` must be defined to make use | + | | of ``page``. | + +-----------+-------------------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Structure + + GET /api/5.0/steering/2/targets?target=1 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:deliveryService: A string that is the :ref:`ds-xmlid` of the steering :term:`Delivery Service` +:deliveryServiceId: An integral, unique identifier for the steering :term:`Delivery Service` +:target: A string that is the :ref:`ds-xmlid` of this target :term:`Delivery Service` +:targetId: An integral, unique identifier for this target :term:`Delivery Service` +:type: The steering type of this target :term:`Delivery Service`. This should be one of ``STEERING_WEIGHT``, ``STEERING_ORDER``, ``STEERING_GEO_ORDER`` or ``STEERING_GEO_WEIGHT`` +:typeId: An integral, unique identifier for the :ref:`steering type ` of this target :term:`Delivery Service` +:value: The 'weight', 'order', 'geo_order' or 'geo_weight' attributed to this steering target as an integer + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: utlJK4oYS2l6Ff7NzAqRuQeMEtazYn3rM3Nlux2XgTLxvSyslHy0mJrwDExSU05gVMdrgYCLZrZEvPHlENT1nA== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 11 Dec 2018 14:09:23 GMT + Content-Length: 130 + + { "response": [ + { + "deliveryService": "test", + "deliveryServiceId": 2, + "target": "demo1", + "targetId": 1, + "type": "STEERING_ORDER", + "typeId": 44, + "value": 100 + } + ]} + +``POST`` +======== +Create a steering target. + +:Auth. Required: Yes +:Roles Required: Portal, Steering, Federation, "operations" or "admin" +:Permissions Required: STEERING:CREATE, STEERING:READ, DELIVERY-SERVICE:READ, TYPE:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+---------------------------------------------------------------------------------------------------------+ + | Name | Description | + +======+=========================================================================================================+ + | ID | The integral, unique identifier of a steering :term:`Delivery Service` to which a target shall be added | + +------+---------------------------------------------------------------------------------------------------------+ + +:targetId: The integral, unique identifier of a :term:`Delivery Service` which shall be a new steering target for the :term:`Delivery Service` identified by the ``ID`` path parameter +:typeId: The integral, unique identifier of the steering type of the new target :term:`Delivery Service`. This should be corresponding to one of ``STEERING_WEIGHT``, ``STEERING_ORDER``, ``STEERING_GEO_ORDER`` or ``STEERING_GEO_WEIGHT`` +:value: The 'weight', 'order', 'geo_order' or 'geo_weight' which shall be attributed to the new target :term:`Delivery Service` + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/steering/2/targets HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 43 + Content-Type: application/json + + { + "targetId": 1, + "value": 100, + "typeId": 43 + } + +Response Structure +------------------ +:deliveryService: A string that is the :ref:`ds-xmlid` of the steering :term:`Delivery Service` +:deliveryServiceId: An integral, unique identifier for the steering :term:`Delivery Service` +:target: A string that is the :ref:`ds-xmlid` of this target :term:`Delivery Service` +:targetId: An integral, unique identifier for this target :term:`Delivery Service` +:type: The steering type of this target :term:`Delivery Service`. This should be one of ``STEERING_WEIGHT``, ``STEERING_ORDER``, ``STEERING_GEO_ORDER`` or ``STEERING_GEO_WEIGHT`` +:typeId: An integral, unique identifier for the :ref:`steering type ` of this target :term:`Delivery Service` +:value: The 'weight', 'order', 'geo_order' or 'geo_weight' attributed to this steering target as an integer + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: +dTvfzrnOhdwAOMmY28r0+gFV5z+3aABI2FfAMziTYcU+pZrDanrJzMXpKWIL5Q/oCUBZpJDRt9hRCFkT4oGYw== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 10 Dec 2018 21:22:17 GMT + Content-Length: 196 + + { "alerts": [ + { + "text": "steeringtarget was created.", + "level": "success" + } + ], + "response": { + "deliveryService": "test", + "deliveryServiceId": 2, + "target": "demo1", + "targetId": 1, + "type": "HTTP", + "typeId": 1, + "value": 100 + }} diff --git a/docs/source/api/v5/steering_id_targets_targetID.rst b/docs/source/api/v5/steering_id_targets_targetID.rst new file mode 100644 index 0000000000..70bbda77fb --- /dev/null +++ b/docs/source/api/v5/steering_id_targets_targetID.rst @@ -0,0 +1,155 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-steering-id-targets-targetID: + +**************************************** +``steering/{{ID}}/targets/{{targetID}}`` +**************************************** + +``PUT`` +======= +Updates a steering target. + +:Auth. Required: Yes +:Roles Required: Portal, Steering, Federation, "operations" or "admin" +:Permissions Required: STEERING:UPDATE, STEERING:READ, DELIVERY-SERVICE:READ, TYPE:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | Name | Description | + +==========+======================================================================================================================================+ + | ID | The integral, unique identifier of a steering :term:`Delivery Service` | + +----------+--------------------------------------------------------------------------------------------------------------------------------------+ + | targetID | The integral, unique identifier of a :term:`Delivery Service` which is a target of the :term:`Delivery Service` identified by ``ID`` | + +----------+--------------------------------------------------------------------------------------------------------------------------------------+ + +:typeId: The integral, unique identifier of the :ref:`steering type ` of the target :term:`Delivery Service`. This should be corresponding to one of ``STEERING_WEIGHT``, ``STEERING_ORDER``, ``STEERING_GEO_ORDER`` or ``STEERING_GEO_WEIGHT`` +:value: The 'weight', 'order', 'geo_order' or 'geo_weight' which shall be attributed to the target :term:`Delivery Service` + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/steering/2/targets/1 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 26 + Content-Type: application/json + + { + "value": 1, + "typeId": 43 + } + +Response Structure +------------------ +:deliveryService: A string that is the :ref:`ds-xmlid` of the steering :term:`Delivery Service` +:deliveryServiceId: An integral, unique identifier for the steering :term:`Delivery Service` +:target: A string that is the :ref:`ds-xmlid` of this target :term:`Delivery Service` +:targetId: An integral, unique identifier for this target :term:`Delivery Service` +:type: The steering type of this target :term:`Delivery Service` +:typeId: An integral, unique identifier for the :ref:`steering type ` of this target :term:`Delivery Service` +:value: The 'weight' attributed to this steering target as an integer + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: AfXsIRzdtU3HZYkr93qBMVTZRJ5oTF2u5sKYnd+DSqxZ+RQxY6vXtCupnnXCf9dxMt5QXRW1EFOW/FBG6lFrTg== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 11 Dec 2018 14:34:22 GMT + Content-Length: 194 + + { "alerts": [ + { + "text": "steeringtarget was updated.", + "level": "success" + } + ], + "response": { + "deliveryService": "test", + "deliveryServiceId": 2, + "target": "demo1", + "targetId": 1, + "type": "HTTP", + "typeId": 1, + "value": 1 + }} + +``DELETE`` +========== +Removes a specific target mapping from a specific :term:`Delivery Service` + +:Auth. Required: Yes +:Roles Required: Portal, Steering, Federation, "operations" or "admin" +:Permissions Required: STEERING:DELETE, STEERING:READ, DELIVERY-SERVICE:READ, TYPE:READ +:Response Type: ``undefined`` + +Request Structure +----------------- +.. table:: Request Path Parameters + + +----------+----------------------------------------------------------------------------------------------------------------------------------------------------+ + | Name | Description | + +==========+====================================================================================================================================================+ + | ID | The integral, unique identifier of a steering :term:`Delivery Service` - a target of which shall be deleted | + +----------+----------------------------------------------------------------------------------------------------------------------------------------------------+ + | targetID | The integral, unique identifier of a :term:`Delivery Service` which is a target to be removed of the :term:`Delivery Service` identified by ``ID`` | + +----------+----------------------------------------------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/steering/2/targets/1 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: N6h8Kl7uQveqpTc3fmKXFDY2yYe5smApNcaTow4ab0DHGFdJfqQh89I4nvvaXvmVNhxVAqX3UE/6blbO8/9Xqg== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 11 Dec 2018 14:42:54 GMT + Content-Length: 69 + + { "alerts": [ + { + "text": "steeringtarget was deleted.", + "level": "success" + } + ]} diff --git a/docs/source/api/v5/system_info.rst b/docs/source/api/v5/system_info.rst new file mode 100644 index 0000000000..2531a5e8f6 --- /dev/null +++ b/docs/source/api/v5/system_info.rst @@ -0,0 +1,75 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-system-info: + +*************** +``system/info`` +*************** + +``GET`` +======= +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: None +:Response Type: Object + +Request Structure +----------------- +No parameters available. + +Response Structure +------------------ +:parameters: An object containing information about the Traffic Ops server + + .. note:: These are all the :term:`Parameters` in :ref:`the-global-profile`, so the keys below are merely those present by default required for Traffic Control to operate + + :default_geo_miss_latitude: The default latitude used when geographic lookup of an IP address fails + :default_geo_miss_longitude: The default longitude used when geographic lookup of an IP address fails + :tm.instance_name: The name of the Traffic Ops instance; typically used when multiple instances are active + :tm.toolname: The name of the Traffic Ops tool (usually "Traffic Ops") - used in several API endpoints and written in comment headers on most Apache Traffic Server (ATS) configuration files generated by Traffic Ops + :tm.url: The URL for this Traffic Ops instance ) - used in several API endpoints and written in comment headers on most Apache Traffic Server (ATS) configuration files generated by Traffic Ops + :use_reval_pending: A string containing an integer which represents a boolean value (hold your applause); one of: + + "0" + Do not use pending revalidations - this effectively prohibits the use of "Content Invalidation Jobs" + "1" + Use pending revalidations - this effectively enables the use of "Content Invalidation Jobs" + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: ObxOXk1jrC1/JtrqElUICceyx9iJKJxZydEIHvAU7khTTQwt0QGvSO4ELDkdrbu3ctFo3pf3NAMaMM9tAkNokg== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 11 Dec 2018 19:06:01 GMT + Content-Length: 285 + + { "response": { + "parameters": { + "default_geo_miss_latitude": "0", + "default_geo_miss_longitude": "-1", + "tm.instance_name": "CDN-In-A-Box", + "tm.toolname": "Traffic Ops", + "tm.url": "https://trafficops.infra.ciab.test:443/", + "use_reval_pending": "0" + } + }} diff --git a/docs/source/api/v5/tenants.rst b/docs/source/api/v5/tenants.rst new file mode 100644 index 0000000000..2d07db1664 --- /dev/null +++ b/docs/source/api/v5/tenants.rst @@ -0,0 +1,169 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-tenants: + +*********** +``tenants`` +*********** + +``GET`` +======= +Get all requested :term:`Tenants`. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: TENANT:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-----------+------------------------------------------------------------------------------------+ + | Name | Description | + +===========+====================================================================================+ + | active | If ``true``, return only active :term:`Tenants`; if ``false`` return only inactive | + | | :term:`Tenants` | + +-----------+------------------------------------------------------------------------------------+ + | id | Return only :term:`Tenants` with this integral, unique identifier | + +-----------+------------------------------------------------------------------------------------+ + | name | Return only :term:`Tenants` with this name | + +-----------+------------------------------------------------------------------------------------+ + | orderby | Choose the ordering of the results - must be the name of one of the fields of the | + | | objects in the ``response`` array | + +-----------+------------------------------------------------------------------------------------+ + | sortOrder | Changes the order of sorting. Either ascending (default or "asc") or descending | + | | ("desc") | + +-----------+------------------------------------------------------------------------------------+ + | limit | Choose the maximum number of results to return | + +-----------+------------------------------------------------------------------------------------+ + | offset | The number of results to skip before beginning to return results. Must use in | + | | conjunction with limit | + +-----------+------------------------------------------------------------------------------------+ + | page | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, | + | | pages are ``limit`` long and the first page is 1. If ``offset`` was defined, this | + | | query parameter has no effect. ``limit`` must be defined to make use of ``page``. | + +-----------+------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/tenants?name=root HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:active: A boolean which indicates whether or not the :term:`Tenant` is active +:id: The integral, unique identifier of this :term:`Tenant` +:name: This :term:`Tenant`'s name +:parentId: The integral, unique identifier of this :term:`Tenant`'s parent +:parentName: The name of the parent of this :term:`Tenant` + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: Yzr6TfhxgpZ3pbbrr4TRG4wC3PlnHDDzgs2igtz/1ppLSy2MzugqaGW4y5yzwzl5T3+7q6HWej7GQZt1XIVeZQ== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 11 Dec 2018 19:57:58 GMT + Content-Length: 106 + + { "response": [ + { + "id": 1, + "name": "root", + "active": true, + "lastUpdated": "2018-12-10 19:11:17+00", + "parentId": null + } + ]} + +``POST`` +======== +Create a new tenant. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: TENANT:CREATE, TENANT:READ +:Response Type: Object + +Request Structure +----------------- +:active: An optional boolean - default: ``false`` - which indicates whether or not the tenant shall be immediately active +:name: The name of the tenant +:parentId: The integral, unique identifier of the parent of this tenant + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/tenants HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 48 + Content-Type: application/json + + { + "active": true, + "name": "test", + "parentId": 1 + } + +Response Structure +------------------ +:active: A boolean which indicates whether or not the tenant is active +:id: The integral, unique identifier of this tenant +:name: This tenant's name +:parentId: The integral, unique identifier of this tenant's parent + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: ysdopC//JQI79BRUa61s6M2HzHxYHpo5RdcuauOoqCYxiVOoUhNZfOVydVkv8zDN2qA374XKnym4kWj3VzQIXg== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 11 Dec 2018 19:37:16 GMT + Content-Length: 162 + + { "alerts": [ + { + "text": "tenant was created.", + "level": "success" + } + ], + "response": { + "id": 9, + "name": "test", + "active": true, + "lastUpdated": "2018-12-11 19:37:16+00", + "parentId": 1 + }} diff --git a/docs/source/api/v5/tenants_id.rst b/docs/source/api/v5/tenants_id.rst new file mode 100644 index 0000000000..3746a4577c --- /dev/null +++ b/docs/source/api/v5/tenants_id.rst @@ -0,0 +1,149 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-tenants-id: + +****************** +``tenants/{{ID}}`` +****************** + +``PUT`` +======= +Updates a specific tenant. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: TENANT:UPDATE, TENANT:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+---------------------------------------------------------------+ + | Name | Description | + +======+===============================================================+ + | ID | The integral, unique identifier for the tenant being modified | + +------+---------------------------------------------------------------+ + +:active: An optional boolean - default: ``false`` - which indicates whether or not the tenant shall be immediately active +:name: The name of the tenant +:parentId: The integral, unique identifier of the parent of this tenant + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/tenants/9 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 59 + Content-Type: application/json + + { + "active": true, + "name": "quest", + "parentId": 3 + } + +Response Structure +------------------ +:active: A boolean which indicates whether or not the tenant is active +:id: The integral, unique identifier of this tenant +:name: This tenant's name +:parentId: The integral, unique identifier of this tenant's parent + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: 5soYQFrG2x5ZJ1e5UZIOLUv/928qyd2Bfgw21Wv85rqjLpyeT3djkfRVD1/xpKConulNrZs2czJKrrwZA7X61w== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 11 Dec 2018 20:30:54 GMT + Content-Length: 163 + + { "alerts": [ + { + "text": "tenant was updated.", + "level": "success" + } + ], + "response": { + "id": 9, + "name": "quest", + "active": true, + "lastUpdated": "2018-12-11 20:30:54+00", + "parentId": 3 + }} + +``DELETE`` +========== +Deletes a specific tenant. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: TENANT:DELETE, TENANT:READ +:Response Type: ``undefined`` + + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+--------------------------------------------------------------+ + | Name | Description | + +======+==============================================================+ + | ID | The integral, unique identifier for the tenant being deleted | + +------+--------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/tenants/9 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: KU0XIbFoD0Cy06kzH2Gl59pBqie/TEFJgh33mssGNwXJZlRkTLaSTHT8Df4X+pOs7UauZH10akGvaA0UTiN/vg== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 11 Dec 2018 20:40:31 GMT + Content-Length: 61 + + { "alerts": [ + { + "text": "tenant was deleted.", + "level": "success" + } + ]} diff --git a/docs/source/api/v5/topologies.rst b/docs/source/api/v5/topologies.rst new file mode 100644 index 0000000000..a0eb11e5b8 --- /dev/null +++ b/docs/source/api/v5/topologies.rst @@ -0,0 +1,601 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-topologies: + +************** +``topologies`` +************** + +``GET`` +======= +Retrieves :term:`Topologies`. + +:Auth. Required: Yes +:Roles Required: "read-only" +:Permissions Required: TOPOLOGY:READ, CACHE-GROUP:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +------+----------+-----------------------------------------------------+ + | Name | Required | Description | + +======+==========+=====================================================+ + | name | no | Return the :term:`Topology` with this name | + +------+----------+-----------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/topologies HTTP/1.1 + User-Agent: python-requests/2.23.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + +Response Structure +------------------ +:description: A short sentence that describes the :term:`Topology`. +:lastUpdated: The date and time at which this :term:`Topology` was last updated, in ISO-like format +:name: The name of the :term:`Topology`. This can only be letters, numbers, and dashes. +:nodes: An array of nodes in the :term:`Topology` + + :cachegroup: The name of a :term:`Cache Group` + :parents: The indices of the parents of this node in the nodes array, 0-indexed. 2 parents max + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 13 Apr 2020 18:22:32 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: lF4MCJCinuQWz0flLAAZBrzbuPVsHrNn2BtTozRZojEjGpm76IsXBQK5QOwSwBoHac+D0C1Z3p7M8kdjcfgIIg== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 13 Apr 2020 17:22:32 GMT + Content-Length: 205 + + { + "response": [ + { + "description": "This is my topology", + "name": "my-topology", + "nodes": [ + { + "cachegroup": "edge1", + "parents": [ + 7 + ] + }, + { + "cachegroup": "edge2", + "parents": [ + 7, + 8 + ] + }, + { + "cachegroup": "edge3", + "parents": [ + 8, + 9 + ] + }, + { + "cachegroup": "edge4", + "parents": [ + 9 + ] + }, + { + "cachegroup": "mid1", + "parents": [] + }, + { + "cachegroup": "mid2", + "parents": [ + 4 + ] + }, + { + "cachegroup": "mid3", + "parents": [ + 4 + ] + }, + { + "cachegroup": "mid4", + "parents": [ + 5 + ] + }, + { + "cachegroup": "mid5", + "parents": [ + 5, + 6 + ] + }, + { + "cachegroup": "mid6", + "parents": [ + 6 + ] + } + ], + "lastUpdated": "2020-04-13 17:12:34+00" + } + ] + } + +``POST`` +======== +Create a new :term:`Topology`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: TOPOLOGY:CREATE, TOPOLOGY:READ, CACHE-GROUP:READ +:Response Type: Object + +Request Structure +----------------- +:description: A short sentence that describes the topology. +:name: The name of the topology. This can only be letters, numbers, and dashes. +:nodes: An array of nodes in the :term:`Topology` + + :cachegroup: The name of a :term:`Cache Group` with at least 1 server in it + :parents: The indices of the parents of this node in the nodes array, 0-indexed. 2 parents max + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/topologies HTTP/1.1 + User-Agent: python-requests/2.23.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 924 + Content-Type: application/json + + { + "name": "my-topology", + "description": "This is my topology", + "nodes": [ + { + "cachegroup": "edge1", + "parents": [ + 7 + ] + }, + { + "cachegroup": "edge2", + "parents": [ + 7, + 8 + ] + }, + { + "cachegroup": "edge3", + "parents": [ + 8, + 9 + ] + }, + { + "cachegroup": "edge4", + "parents": [ + 9 + ] + }, + { + "cachegroup": "mid1", + "parents": [] + }, + { + "cachegroup": "mid2", + "parents": [ + 4 + ] + }, + { + "cachegroup": "mid3", + "parents": [ + 4 + ] + }, + { + "cachegroup": "mid4", + "parents": [ + 5 + ] + }, + { + "cachegroup": "mid5", + "parents": [ + 5, + 6 + ] + }, + { + "cachegroup": "mid6", + "parents": [ + 6 + ] + } + ] + } + +Response Structure +------------------ +:description: A short sentence that describes the topology. +:lastUpdated: The date and time at which this :term:`Topology` was last updated, in ISO-like format +:name: The name of the topology. This can only be letters, numbers, and dashes. +:nodes: An array of nodes in the :term:`Topology` + + :cachegroup: The name of a :term:`Cache Group` + :parents: The indices of the parents of this node in the nodes array, 0-indexed. 2 parents max + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 13 Apr 2020 18:12:34 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: ftNcDRjYCDMkQM+o/szayKZriQZHGpcT0vNY0HpKgy88i0pXeEEeLGbUPh6LXtK7TvL76EgGECTzvCkcm+2LVA== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 13 Apr 2020 17:12:34 GMT + Content-Length: 239 + + { + "alerts": [ + { + "text": "topology was created.", + "level": "success" + } + ], + "response": { + "description": "This is my topology", + "name": "my-topology", + "nodes": [ + { + "cachegroup": "edge1", + "parents": [ + 7 + ] + }, + { + "cachegroup": "edge2", + "parents": [ + 7, + 8 + ] + }, + { + "cachegroup": "edge3", + "parents": [ + 8, + 9 + ] + }, + { + "cachegroup": "edge4", + "parents": [ + 9 + ] + }, + { + "cachegroup": "mid1", + "parents": [] + }, + { + "cachegroup": "mid2", + "parents": [ + 4 + ] + }, + { + "cachegroup": "mid3", + "parents": [ + 4 + ] + }, + { + "cachegroup": "mid4", + "parents": [ + 5 + ] + }, + { + "cachegroup": "mid5", + "parents": [ + 5, + 6 + ] + }, + { + "cachegroup": "mid6", + "parents": [ + 6 + ] + } + ], + "lastUpdated": "2020-04-13 17:12:34+00" + } + } + +``PUT`` +======= +Updates a specific :term:`Topology`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: TOPOLOGY:UPDATE, TOPOLOGY:READ, CACHE-GROUP:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Query Parameters + + +------+----------+---------------------------------------------------------+ + | Name | Required | Description | + +======+==========+=========================================================+ + | name | yes | The name of the :term:`Topology` to be updated | + +------+----------+---------------------------------------------------------+ + +:description: A short sentence that describes the :term:`Topology`. +:name: The name of the :term:`Topology`. This can only be letters, numbers, and dashes. +:nodes: An array of nodes in the :term:`Topology` + + :cachegroup: The name of a :term:`Cache Group` with at least 1 server in it + :parents: The indices of the parents of this node in the nodes array, 0-indexed. 2 parents max + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/topologies?name=my-topology HTTP/1.1 + User-Agent: python-requests/2.23.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 853 + Content-Type: application/json + + { + "name": "my-topology", + "description": "The description is updated, too", + "nodes": [ + { + "cachegroup": "edge1", + "parents": [ + 6 + ] + }, + { + "cachegroup": "edge2", + "parents": [ + 6, + 7 + ] + }, + { + "cachegroup": "edge3", + "parents": [ + 7, + 8 + ] + }, + { + "cachegroup": "edge4", + "parents": [ + 8 + ] + }, + { + "cachegroup": "mid2", + "parents": [] + }, + { + "cachegroup": "mid3", + "parents": [] + }, + { + "cachegroup": "mid4", + "parents": [ + 4 + ] + }, + { + "cachegroup": "mid5", + "parents": [ + 4, + 5 + ] + }, + { + "cachegroup": "mid6", + "parents": [ + 5 + ] + } + ] + } + +Response Structure +------------------ +:description: A short sentence that describes the :term:`Topology`. +:lastUpdated: The date and time at which this :term:`Topology` was last updated, in ISO-like format +:name: The name of the :term:`Topology`. This can only be letters, numbers, and dashes. +:nodes: An array of nodes in the :term:`Topology` + + :cachegroup: The name of a :term:`Cache Group` + :parents: The indices of the parents of this node in the nodes array, 0-indexed. 2 parents max + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 13 Apr 2020 18:33:13 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: WVOtsoOVrEWcVjWM2TmT5DXy/a5Q0ygTZEQRhbkHHUmz9dgVLK1F5Joc9jtKA8gZu8/eM5+Tqqguh3mzrhAy/Q== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 13 Apr 2020 17:33:13 GMT + Content-Length: 237 + + { + "alerts": [ + { + "text": "topology was updated.", + "level": "success" + } + ], + "response": { + "description": "The description is updated, too", + "name": "my-topology", + "nodes": [ + { + "cachegroup": "edge1", + "parents": [ + 6 + ] + }, + { + "cachegroup": "edge2", + "parents": [ + 6, + 7 + ] + }, + { + "cachegroup": "edge3", + "parents": [ + 7, + 8 + ] + }, + { + "cachegroup": "edge4", + "parents": [ + 8 + ] + }, + { + "cachegroup": "mid2", + "parents": [] + }, + { + "cachegroup": "mid3", + "parents": [] + }, + { + "cachegroup": "mid4", + "parents": [ + 4 + ] + }, + { + "cachegroup": "mid5", + "parents": [ + 4, + 5 + ] + }, + { + "cachegroup": "mid6", + "parents": [ + 5 + ] + } + ], + "lastUpdated": "2020-04-13 17:33:13+00" + } + } + +``DELETE`` +========== +Deletes a specific :term:`Topology`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: TOPOLOGY:DELETE, TOPOLOGY:READ, CACHE-GROUP:READ +:Response Type: ``undefined`` + + +Request Structure +----------------- +.. table:: Request Query Parameters + + +------+----------+---------------------------------------------------------+ + | Name | Required | Description | + +======+==========+=========================================================+ + | name | yes | The name of the :term:`Topology` to be deleted | + +------+----------+---------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/topologies?name=my-topology HTTP/1.1 + User-Agent: python-requests/2.23.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 0 + +Response Structure +------------------ + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 13 Apr 2020 18:35:32 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: yErJobzG9IA0khvqZQK+Yi7X4pFVvOqxn6PjrdzN5DnKVm/K8Kka3REul1XmKJnMXVRY8RayoEVGDm16mBFe4Q== + X-Server-Name: traffic_ops_golang/ + Date: Mon, 13 Apr 2020 17:35:32 GMT + Content-Length: 87 + + { + "alerts": [ + { + "text": "topology was deleted.", + "level": "success" + } + ] + } diff --git a/docs/source/api/v5/topologies_name_queue_update.rst b/docs/source/api/v5/topologies_name_queue_update.rst new file mode 100644 index 0000000000..43b1255a99 --- /dev/null +++ b/docs/source/api/v5/topologies_name_queue_update.rst @@ -0,0 +1,89 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-topologies-name-queue_update: + +************************************ +``topologies/{{name}}/queue_update`` +************************************ + +``POST`` +======== +:term:`Queue` or "dequeue" updates for all servers assigned to the :term:`Cache Groups` in a specific :term:`Topology`. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: SERVER:QUEUE, TOPOLOGY:READ, SERVER:READ, CACHE-GROUP:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+---------------------------------------------------------------------------+ + | Name | Description | + +======+===========================================================================+ + | name | The name of the :term:`Topology` on which to queue or dequeue updates. | + +------+---------------------------------------------------------------------------+ + +:action: One of "queue" or "dequeue" as appropriate +:cdnId: The integral, unique identifier for the CDN on which to (de)queue updates + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/topologies/demo1-top/queue_update HTTP/1.1 + User-Agent: python-requests/2.24.0 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 28 + + { + "action": "queue", + "cdnId": 1 + } + +Response Structure +------------------ +:action: The action processed, either ``"queue"`` or ``"dequeue"`` +:cdnId: The CDN ID on which :term:`Queue Updates` was performed or cleared +:topology: The name of the :term:`Topology` on which :term:`Queue Updates` was performed or cleared + +.. 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-Encoding: gzip + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Tue, 08 Sep 2020 17:35:42 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: nmu3TMVmllcHeEstLuiqPsEpypNV2jcs5PyrqsqJKkexxxb8N7qk84AWzTJWUpsfdVWrj/JzRiCPGJS4hw0phQ== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 08 Sep 2020 16:35:42 GMT + Content-Length: 79 + + { + "response": { + "action": "queue", + "cdnId": 1, + "topology": "demo1-top" + } + } diff --git a/docs/source/api/v5/types.rst b/docs/source/api/v5/types.rst new file mode 100644 index 0000000000..d45201d789 --- /dev/null +++ b/docs/source/api/v5/types.rst @@ -0,0 +1,160 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-types: + +********* +``types`` +********* + +``GET`` +======= +Retrieves all of the :term:`Types` of things configured in Traffic Ops. Yes, that is as specific as a description of a 'type' can be. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: TYPE:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +------------+----------+--------------------------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +============+==========+================================================================================================================================+ + | id | no | Return only the type that is identified by this integral, unique identifier | + +------------+----------+--------------------------------------------------------------------------------------------------------------------------------+ + | name | no | Return only types with this name | + +------------+----------+--------------------------------------------------------------------------------------------------------------------------------+ + | useInTable | no | Return only types that are used to identify the type of the object stored in the Traffic Ops database table that has this name | + +------------+----------+--------------------------------------------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Structure + + GET /api/5.0/types?name=TC_LOC HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + +Response Structure +------------------ +:description: A short description of this type +:id: An integral, unique identifier for this type +:lastUpdated: The date and time at which this type was last updated, in :ref:`non-rfc-datetime` +:name: The name of this type +:useInTable: The name of the Traffic Ops database table that contains objects which are grouped, identified, or described by this type + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: EH8jo8OrCu79Tz9xpgT3YRyKJ/p2NcTmbS3huwtqRByHz9H6qZLQjA59RIPaVSq3ZxsU6QhTaox5nBkQ9LPSAA== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 12 Dec 2018 22:59:22 GMT + Content-Length: 168 + + { "response": [ + { + "id": 48, + "lastUpdated": "2018-12-12 16:26:41+00", + "name": "TC_LOC", + "description": "Location for Traffic Control Component Servers", + "useInTable": "cachegroup" + } + ]} + +``POST`` +======== +Creates a type + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: TYPE:CREATE, TYPE:READ +:Response Type: Object + +Request Structure +----------------- + +:description: A short description of this type +:name: The name of this type +:useInTable: The name of the Traffic Ops database table that contains objects which are grouped, identified, or described by this type. + + .. note:: The only useInTable value that is allowed to be created dynamically is 'server' + +.. code-block:: http + :caption: Request Structure + + POST /api/5.0/type HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 67 + Content-Type: application/json + + { + "name": "Example01", + "description": "Example", + "useInTable": "server" + } + +Response Structure +------------------ + +:description: A short description of this type +:id: An integral, unique identifier for this type +:lastUpdated: The date and time at which this type was last updated, in :ref:`non-rfc-datetime` +:name: The name of this type +:useInTable: The name of the Traffic Ops database table that contains objects which are grouped, identified, or described by this type + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: EH8jo8OrCu79Tz9xpgT3YRyKJ/p2NcTmbS3huwtqRByHz9H6qZLQjA59RIPaVSq3ZxsU6QhTaox5nBkQ9LPSAA== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 26 Feb 2020 18:58:41 GMT + Content-Length: 171 + + { + "alerts": [ + { + "text": "type was created.", + "level": "success" + }], + "response": [ + { + "id": 3004, + "lastUpdated": "2020-02-26 18:58:41+00", + "name": "Example01", + "description": "Example" + "useInTable": "server" + }] + } diff --git a/docs/source/api/v5/types_id.rst b/docs/source/api/v5/types_id.rst new file mode 100644 index 0000000000..598476d81c --- /dev/null +++ b/docs/source/api/v5/types_id.rst @@ -0,0 +1,158 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-types-id: + +**************** +``types/{{ID}}`` +**************** + +``PUT`` +======= +Updates a type + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: TYPE:UPDATE, TYPE:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+-----------------------------------------------------------+ + | Name | Description | + +======+===========================================================+ + | ID | The integral, unique identifier of the type being updated | + +------+-----------------------------------------------------------+ + +:description: A short description of this type +:name: The name of this type +:useInTable: The name of the Traffic Ops database table that contains objects which are grouped, identified, or described by this type. + +.. note:: Only types with useInTable set to 'server' are allowed to be updated. + +.. code-block:: http + :caption: Request Structure + + PUT /api/5.0/type/3004 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 68 + Content-Type: application/json + + { + "name": "Example01", + "description": "Example2", + "useInTable": "server" + } + +Response Structure +------------------ +:description: A short description of this type +:id: An integral, unique identifier for this type +:lastUpdated: The date and time at which this type was last updated, in :ref:`non-rfc-datetime` +:name: The name of this type +:useInTable: The name of the Traffic Ops database table that contains objects which are grouped, identified, or described by this type + +.. 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=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: EH8jo8OrCu79Tz9xpgT3YRyKJ/p2NcTmbS3huwtqRByHz9H6qZLQjA59RIPaVSq3ZxsU6QhTaox5nBkQ9LPSAA== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 26 Feb 2020 18:58:41 GMT + Content-Length: 172 + + { + "alerts": [ + { + "text": "type was updated.", + "level": "success" + }], + "response": [ + { + "id": 3004, + "lastUpdated": "2020-02-26 18:58:41+00", + "name": "Example02", + "description": "Example" + "useInTable": "server" + }] + } + +``DELETE`` +========== +Deletes a type + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: TYPE:DELETE, TYPE:READ +:Response Type: Object + + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+-----------------------------------------------------------+ + | Name | Description | + +======+===========================================================+ + | ID | The integral, unique identifier of the type being deleted | + +------+-----------------------------------------------------------+ + +.. note:: Only types with useInTable set to "server" are allowed to be deleted. + +.. code-block:: http + :caption: Request Structure + + DELETE /api/5.0/type/3004 HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 0 + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: EH8jo8OrCu79Tz9xpgT3YRyKJ/p2NcTmbS3huwtqRByHz9H6qZLQjA59RIPaVSq3ZxsU6QhTaox5nBkQ9LPSAA== + X-Server-Name: traffic_ops_golang/ + Date: Wed, 26 Feb 2020 18:58:41 GMT + Content-Length: 84 + + { + "alerts": [ + { + "text": "type was deleted.", + "level": "success" + }], + } diff --git a/docs/source/api/v5/user_current.rst b/docs/source/api/v5/user_current.rst new file mode 100644 index 0000000000..903582064b --- /dev/null +++ b/docs/source/api/v5/user_current.rst @@ -0,0 +1,287 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-user-current: + +**************** +``user/current`` +**************** + +``GET`` +======= +.. caution:: As a username is needed to log in, any administrator or application must necessarily know the current username at any given time. Thus it's generally better to use the ``username`` query parameter of a ``GET`` request to :ref:`to-api-users` instead. + +Retrieves the details of the authenticated user. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: None +:Response Type: Object + +Request Structure +----------------- +No parameters available. + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/user/current HTTP/1.1 + User-Agent: python-requests/2.25.1 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + + +Response Structure +------------------ +:addressLine1: The user's address - including street name and number +:addressLine2: An additional address field for e.g. apartment number +:changeLogCount: The number of change log entries created by the user +:city: The name of the city wherein the user resides +:company: The name of the company for which the user works +:country: The name of the country wherein the user resides +:email: The user's email address +:fullName: The user's full name, e.g. "John Quincy Adams" +:gid: A deprecated field only kept for legacy compatibility reasons that used to contain the UNIX group ID of the user + + .. deprecated:: 4.0 + This field is serves no known purpose, and shouldn't be used for anything so it can be removed in the future. + +:id: An integral, unique identifier for this user +:lastAuthenticated: The date and time at which the user was last authenticated, in :rfc:`3339` format +:lastUpdated: The date and time at which the user was last modified, in :rfc:`3339` format +:newUser: A meta field with no apparent purpose that is usually ``null`` unless explicitly set during creation or modification of a user via some API endpoint +:phoneNumber: The user's phone number +:postalCode: The postal code of the area in which the user resides +:publicSshKey: The user's public key used for the SSH protocol +:registrationSent: If the user was created using the :ref:`to-api-users-register` endpoint, this will be the date and time at which the registration email was sent - otherwise it will be ``null`` +:role: The name of the :term:`Role` assigned to this user +:stateOrProvince: The name of the state or province where this user resides +:tenant: The name of the :term:`Tenant` to which this user belongs +:tenantId: The integral, unique identifier of the :term:`Tenant` to which this user belongs +:ucdn: The name of the :abbr:`uCDN (Upstream Content Delivery Network)` to which the user belongs +:uid: A deprecated field only kept for legacy compatibility reasons that used to contain the UNIX user ID of the user + + .. deprecated:: 4.0 + This field is serves no known purpose, and shouldn't be used for anything so it can be removed in the future. + +:username: The user's username + +.. code-block:: http + :caption: Response Example + + + HTTP/1.1 200 OK + Content-Encoding: gzip + Content-Type: application/json + Permissions-Policy: interest-cohort=() + Set-Cookie: mojolicious=...; Path=/; Expires=Fri, 13 May 2022 23:42:05 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + X-Server-Name: traffic_ops_golang/ + Date: Fri, 13 May 2022 22:42:05 GMT + Content-Length: 311 + + { "response": { + "addressLine1": null, + "addressLine2": null, + "changeLogCount": 1, + "city": null, + "company": null, + "country": null, + "email": "admin@no-reply.atc.test", + "fullName": "Development Admin User", + "gid": null, + "id": 2, + "lastAuthenticated": "2022-05-13T22:42:05.495439Z", + "lastUpdated": "2022-05-13T22:42:05.495439Z", + "newUser": false, + "phoneNumber": null, + "postalCode": null, + "publicSshKey": null, + "registrationSent": null, + "role": "admin", + "stateOrProvince": null, + "tenant": "root", + "tenantId": 1, + "ucdn": "", + "uid": null, + "username": "admin" + }} + +``PUT`` +======= +.. warning:: Assuming the current user's integral, unique identifier is known, it's generally better to use the ``PUT`` method of the :ref:`to-api-users` instead. + +.. warning:: Users that login via LDAP pass-back cannot be modified + +Updates the date for the authenticated user. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: None +:Response Type: Object + +Request Structure +----------------- +:addressLine1: The user's address - including street name and number +:addressLine2: An additional address field for e.g. apartment number +:city: The name of the city wherein the user resides +:company: The name of the company for which the user works +:country: The name of the country wherein the user resides +:email: The user's email address - cannot be an empty string\ [#notnull]_. The given email is validated (circuitously) by `GitHub user asaskevich's regular expression `_ . Note that it can't actually distinguish a valid, deliverable, email address but merely ensure the email is in a commonly-found format. +:fullName: The user's full name, e.g. "John Quincy Adams" +:gid: A legacy field only kept for legacy compatibility reasons that used to contain the UNIX group ID of the user - please don't use this + + .. deprecated:: 4.0 + This field is serves no known purpose, and shouldn't be used for anything so it can be removed in the future. + +:id: The user's integral, unique, identifier - this cannot be changed\ [#notnull]_ +:localPasswd: Optionally, the user's password. This should never be given if it will not be changed. An empty string or ``null`` can be used to explicitly specify no change. +:phoneNumber: The user's phone number +:postalCode: The user's postal code +:publicSshKey: The user's public encryption key used for the SSH protocol +:role: The integral, unique identifier of the highest permission :term:`Role` which will be permitted to the user - this cannot be altered from the user's current :term:`Role`\ [#notnull]_ +:stateOrProvince: The state or province in which the user resides +:tenantId: The integral, unique identifier of the :term:`Tenant` to which the new user shall belong\ [#tenancy]_\ [#notnull]_ +:ucdn: The name of the :abbr:`uCDN (Upstream Content Delivery Network)` to which the user belongs +:uid: A legacy field only kept for legacy compatibility reasons that used to contain the UNIX user ID of the user - please don't use this + + .. deprecated:: 4.0 + This field is serves no known purpose, and shouldn't be used for anything so it can be removed in the future. + +:username: The user's new username\ [#notnull]_ + +.. code-block:: http + :caption: Request Example + + PUT /api/5.0/user/current HTTP/1.1 + User-Agent: python-requests/2.25.1 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 562 + + { + "addressLine1": null, + "addressLine2": null, + "changeLogCount": 1, + "city": null, + "company": null, + "country": null, + "email": "admin@no-reply.atc.test", + "fullName": "Development Admin User", + "gid": null, + "id": 2, + "lastAuthenticated": "2022-05-13T22:42:05.495439Z", + "lastUpdated": "2022-05-13T22:42:05.495439Z", + "newUser": false, + "phoneNumber": null, + "postalCode": null, + "publicSshKey": null, + "registrationSent": null, + "role": "admin", + "stateOrProvince": null, + "tenant": "root", + "tenantId": 1, + "ucdn": "", + "uid": null, + "username": "admin" + } + +Response Structure +------------------ +:addressLine1: The user's address - including street name and number +:addressLine2: An additional address field for e.g. apartment number +:changeLogCount: The number of change log entries created by the user +:city: The name of the city wherein the user resides +:company: The name of the company for which the user works +:country: The name of the country wherein the user resides +:email: The user's email address validated (circuitously) by `GitHub user asaskevich's regular expression `_ . Note that it can't actually distinguish a valid, deliverable, email address but merely ensure the email is in a commonly-found format. +:fullName: The user's full name, e.g. "John Quincy Adams" +:gid: A legacy field only kept for legacy compatibility reasons that used to contain the UNIX group ID of the user + + .. deprecated:: 4.0 + This field is serves no known purpose, and shouldn't be used for anything so it can be removed in the future. + +:id: An integral, unique identifier for this user +:lastAuthenticated: The date and time at which the user was last authenticated, in :rfc:`3339` +:lastUpdated: The date and time at which the user was last modified, in :ref:`non-rfc-datetime` +:newUser: A meta field with no apparent purpose +:phoneNumber: The user's phone number +:postalCode: The postal code of the area in which the user resides +:publicSshKey: The user's public key used for the SSH protocol +:registrationSent: If the user was created using the :ref:`to-api-users-register` endpoint, this will be the date and time at which the registration email was sent - otherwise it will be ``null`` +:role: The name of the :term:`Role` assigned to this user +:stateOrProvince: The name of the state or province where this user resides +:tenant: The name of the :term:`Tenant` to which this user belongs +:tenantId: The integral, unique identifier of the :term:`Tenant` to which this user belongs +:ucdn: The name of the :abbr:`uCDN (Upstream Content Delivery Network)` to which the user belongs +:uid: A legacy field only kept for legacy compatibility reasons that used to contain the UNIX user ID of the user + + .. deprecated:: 4.0 + This field is serves no known purpose, and shouldn't be used for anything so it can be removed in the future. + +:username: The user's username + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Content-Encoding: gzip + Content-Type: application/json + Permissions-Policy: interest-cohort=() + Set-Cookie: mojolicious=...; Path=/; Expires=Fri, 13 May 2022 23:45:22 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + X-Server-Name: traffic_ops_golang/ + Date: Fri, 13 May 2022 22:45:22 GMT + Content-Length: 370 + + { "alerts": [ + { + "text": "User profile was successfully updated", + "level": "success" + } + ], + "response": { + "addressLine1": null, + "addressLine2": null, + "changeLogCount": 1, + "city": null, + "company": null, + "country": null, + "email": "admin@no-reply.atc.test", + "fullName": "Development Admin User", + "gid": null, + "id": 2, + "lastAuthenticated": "2022-05-13T22:44:55.973452Z", + "lastUpdated": "2022-05-13T22:45:22.505401Z", + "newUser": false, + "phoneNumber": null, + "postalCode": null, + "publicSshKey": null, + "registrationSent": null, + "role": "admin", + "stateOrProvince": null, + "tenant": "root", + "tenantId": 1, + "ucdn": "", + "uid": null, + "username": "admin" + }} + +.. [#notnull] This field cannot be ``null``. +.. [#tenancy] This endpoint respects tenancy; a user cannot assign itself to a :term:`Tenant` that is not the same :term:`Tenant` to which it was previously assigned or a descendant thereof. diff --git a/docs/source/api/v5/user_login.rst b/docs/source/api/v5/user_login.rst new file mode 100644 index 0000000000..671331e567 --- /dev/null +++ b/docs/source/api/v5/user_login.rst @@ -0,0 +1,74 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-user-login: + +************** +``user/login`` +************** + +``POST`` +======== +Authentication of a user using username and password. Traffic Ops will send back a session cookie. + +:Auth. Required: No +:Roles Required: None +:Permissions Required: None +:Response Type: ``undefined`` + +Request Structure +----------------- +:p: Password +:u: Username + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/user/login HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 26 + Content-Type: application/json + + { + "u": "admin", + "p": "twelve" + } + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: UdO6T3tMNctnVusDXzRjVwwYOnD7jmnBzPEB9PvOt2bHajTv3SKTPiIZjDzvhU6EX4p+JoG4fA5wlhgxpsejIw== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 13 Dec 2018 15:21:33 GMT + Content-Length: 65 + + { "alerts": [ + { + "text": "Successfully logged in.", + "level": "success" + } + ]} diff --git a/docs/source/api/v5/user_login_oauth.rst b/docs/source/api/v5/user_login_oauth.rst new file mode 100644 index 0000000000..5a3c5ece95 --- /dev/null +++ b/docs/source/api/v5/user_login_oauth.rst @@ -0,0 +1,78 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-user-login-oauth: + +******************** +``user/login/oauth`` +******************** + +``POST`` +======== +Authentication of a user by exchanging a code for an encrypted JSON Web Token from an OAuth service. Traffic Ops will ``POST`` to the ``authCodeTokenUrl`` to exchange the code for an encrypted JSON Web Token. It will then decode and validate the token, validate the key set domain, and send back a session cookie. + +:Auth. Required: No +:Roles Required: None +:Permissions Required: None +:Response Type: ``undefined`` + +Request Structure +----------------- +:authCodeTokenUrl: URL for code-to-token conversion +:code: Code +:clientId: Client Id +:redirectUri: Redirect URI + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/user/login/oauth HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 26 + Content-Type: application/json + + { + "authCodeTokenUrl": "https://url-to-convert-code-to-token.example.com", + "code": "AbCd123", + "clientId": "oauthClientId", + "redirectUri": "https://traffic-portal.example.com/sso" + } + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: UdO6T3tMNctnVusDXzRjVwwYOnD7jmnBzPEB9PvOt2bHajTv3SKTPiIZjDzvhU6EX4p+JoG4fA5wlhgxpsejIw== + X-Server-Name: traffic_ops_golang/ + Date: Thu, 13 Dec 2018 15:21:33 GMT + Content-Length: 65 + + { "alerts": [ + { + "text": "Successfully logged in.", + "level": "success" + } + ]} diff --git a/docs/source/api/v5/user_login_token.rst b/docs/source/api/v5/user_login_token.rst new file mode 100644 index 0000000000..fcdca87bf3 --- /dev/null +++ b/docs/source/api/v5/user_login_token.rst @@ -0,0 +1,74 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-user-login-token: + +******************** +``user/login/token`` +******************** + +``POST`` +======== +Authentication of a user using a token. Normally, the token is obtained via a call to either :ref:`to-api-user-reset_password` or :ref:`to-api-users-register`. + +:Auth. Required: No +:Roles Required: None +:Permissions Required: None +:Response Type: ``undefined`` + +Request Structure +----------------- +:t: A :abbr:`UUID (Universal Unique Identifier)` generated for the user. + + .. impl-detail:: Though not strictly necessary for authentication provided direct database access, the tokens generated for use with this endpoint are compliant with :RFC:`4122`. + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/user/login/token HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 44 + Content-Type: application/json + + { + "t": "18EE200C-FF24-11E8-BF01-870C776752A3" + } + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: FuS3TkVosxHtpxRGMJ2on+WnFdYTNSPjxz/Gh1iT4UCJ2/P0twUbAGQ3tTx9EfGiAzg9CNQiVUFGnYjJZ6NCpg== + X-Server-Name: traffic_ops_golang/ + Date: Fri, 20 Sep 2019 15:02:43 GMT + Content-Length: 66 + + { "alerts": [ + { + "text": "Successfully logged in.", + "level": "success" + } + ]} diff --git a/docs/source/api/v5/user_logout.rst b/docs/source/api/v5/user_logout.rst new file mode 100644 index 0000000000..c6efff0cd1 --- /dev/null +++ b/docs/source/api/v5/user_logout.rst @@ -0,0 +1,59 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-user-logout: + +*************** +``user/logout`` +*************** + +``POST`` +======== +User logout. Invalidates the session cookie of the currently logged-in user. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: None +:Response Type: ``undefined`` + +Request Structure +----------------- +No parameters available + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Cache-Control: no-cache, no-store, max-age=0, must-revalidate + Content-Type: application/json + Date: Thu, 13 Dec 2018 21:25:36 GMT + X-Server-Name: traffic_ops_golang/ + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: 6KEdr1ZC512zkOl03KwvQE0L7qrJ/+ek6ztymkYy9p8gdPUyYyzGEAJ/Ldb8GY0UBFYmgqeZ3yWHvTcEsOQMiw== + Content-Length: 61 + + { "alerts": [ + { + "level": "success", + "text": "You are logged out." + } + ]} diff --git a/docs/source/api/v5/user_reset_password.rst b/docs/source/api/v5/user_reset_password.rst new file mode 100644 index 0000000000..712d2b3c86 --- /dev/null +++ b/docs/source/api/v5/user_reset_password.rst @@ -0,0 +1,74 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-user-reset_password: + +*********************** +``user/reset_password`` +*********************** + +``POST`` +======== +Sends an email to reset a user's password. + +:Auth. Required: No +:Roles Required: None +:Permissions Required: None +:Response Type: ``undefined`` + +Request Structure +----------------- +:email: The email address of the user to initiate password reset + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/user/reset_password HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 35 + Content-Type: application/json + + { + "email": "test@example.com" + } + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Cache-Control: no-cache, no-store, max-age=0, must-revalidate + Content-Type: application/json + Date: Thu, 13 Dec 2018 22:11:53 GMT + X-Server-Name: traffic_ops_golang/ + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: lKWwVYbgKklk7bljnQJZwWV5bljIk+GkooD6SAc3CSexVKvfbL9dgL5iBc/BNNRk2pIU5I/1GgldcDLrXsF1ZA== + Content-Length: 109 + + { "alerts": [ + { + "level": "success", + "text": "Successfully sent password reset to email 'test@example.com'" + } + ]} diff --git a/docs/source/api/v5/users.rst b/docs/source/api/v5/users.rst new file mode 100644 index 0000000000..d6fad87ece --- /dev/null +++ b/docs/source/api/v5/users.rst @@ -0,0 +1,296 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-users: + +********* +``users`` +********* + +``GET`` +======= +Retrieves all requested users. + +:Auth. Required: Yes +:Roles Required: None\ [#tenancy]_ +:Permissions Required: USER:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Query Parameters + + +-----------+----------+------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +===========+==========+==========================================================================================+ + | id | no | Return only the user identified by this integral, unique identifier | + +-----------+----------+------------------------------------------------------------------------------------------+ + | tenant | no | Return only users belonging to the :term:`Tenant` identified by tenant name | + +-----------+----------+------------------------------------------------------------------------------------------+ + | role | no | Return only users belonging to the :term:`Role` identified by role name | + +-----------+----------+------------------------------------------------------------------------------------------+ + | username | no | Return only the user with this username | + +-----------+----------+------------------------------------------------------------------------------------------+ + | orderby | no | Choose the ordering of the results - must be the name of one of the fields of the | + | | | objects in the ``response`` array | + +-----------+----------+------------------------------------------------------------------------------------------+ + | sortOrder | no | Changes the order of sorting. Either ascending (default or "asc") or descending ("desc") | + +-----------+----------+------------------------------------------------------------------------------------------+ + | limit | no | Choose the maximum number of results to return | + +-----------+----------+------------------------------------------------------------------------------------------+ + | offset | no | The number of results to skip before beginning to return results. Must use in | + | | | conjunction with limit | + +-----------+----------+------------------------------------------------------------------------------------------+ + | page | no | Return the n\ :sup:`th` page of results, where "n" is the value of this parameter, pages | + | | | are ``limit`` long and the first page is 1. If ``offset`` was defined, this query | + | | | parameter has no effect. ``limit`` must be defined to make use of ``page``. | + +-----------+----------+------------------------------------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/users?username=mike HTTP/1.1 + User-Agent: python-requests/2.25.1 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + + +Response Structure +------------------ +:addressLine1: The user's address - including street name and number +:addressLine2: An additional address field for e.g. apartment number +:changeLogCount: The number of change log entries created by the user +:city: The name of the city wherein the user resides +:company: The name of the company for which the user works +:country: The name of the country wherein the user resides +:email: The user's email address +:fullName: The user's full name, e.g. "John Quincy Adams" +:gid: A deprecated field only kept for legacy compatibility reasons that used to contain the UNIX group ID of the user - now it is always ``null`` + + .. deprecated:: 4.0 + This field is serves no known purpose, and shouldn't be used for anything so it can be removed in the future. + +:id: An integral, unique identifier for this user +:lastAuthenticated: The date and time at which the user was last authenticated, in :rfc:`3339` +:lastUpdated: The date and time at which the user was last modified, in :ref:`non-rfc-datetime` +:newUser: A meta field with no apparent purpose that is usually ``null`` unless explicitly set during creation or modification of a user via some API endpoint +:phoneNumber: The user's phone number +:postalCode: The postal code of the area in which the user resides +:publicSshKey: The user's public key used for the SSH protocol +:registrationSent: If the user was created using the :ref:`to-api-users-register` endpoint, this will be the date and time at which the registration email was sent - otherwise it will be ``null`` +:role: The name of the role assigned to this user +:stateOrProvince: The name of the state or province where this user resides +:tenant: The name of the tenant to which this user belongs +:tenantId: The integral, unique identifier of the tenant to which this user belongs +:ucdn: The name of the :abbr:`uCDN (Upstream Content Delivery Network)` to which the user belongs +:uid: A deprecated field only kept for legacy compatibility reasons that used to contain the UNIX user ID of the user - now it is always ``null`` + + .. deprecated:: 4.0 + This field is serves no known purpose, and shouldn't be used for anything so it can be removed in the future. + +:username: The user's username + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Content-Encoding: gzip + Content-Type: application/json + Permissions-Policy: interest-cohort=() + Set-Cookie: mojolicious=...; Path=/; Expires=Fri, 13 May 2022 23:16:14 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + X-Server-Name: traffic_ops_golang/ + Date: Fri, 13 May 2022 22:16:14 GMT + Content-Length: 350 + + { "response": [ + { + "addressLine1": "22 Mike Wazowski You've Got Your Life Back Lane", + "addressLine2": null, + "changeLogCount": 0, + "city": "Monstropolis", + "company": null, + "country": null, + "email": "mwazowski@minc.biz", + "fullName": "Mike Wazowski", + "gid": null, + "id": 3, + "lastAuthenticated": null, + "lastUpdated": "2022-05-13T22:13:54.605052Z", + "newUser": true, + "phoneNumber": null, + "postalCode": null, + "publicSshKey": null, + "registrationSent": null, + "role": "admin", + "stateOrProvince": null, + "tenant": "root", + "tenantId": 1, + "ucdn": "", + "uid": null, + "username": "mike" + } + ]} + +``POST`` +======== +Creates a new user. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations"\ [#tenancy]_ +:Permissions Required: USER:CREATE, USER:READ +:Response Type: Object + +Request Structure +----------------- +:addressLine1: An optional field which should contain the user's address - including street name and number +:addressLine2: An optional field which should contain an additional address field for e.g. apartment number +:city: An optional field which should contain the name of the city wherein the user resides +:company: An optional field which should contain the name of the company for which the user works +:country: An optional field which should contain the name of the country wherein the user resides +:email: The user's email address The given email is validated (circuitously) by `GitHub user asaskevich's regular expression `_ . Note that it can't actually distinguish a valid, deliverable, email address but merely ensure the email is in a commonly-found format. +:fullName: The user's full name, e.g. "John Quincy Adams" +:gid: A deprecated field only kept for legacy compatibility reasons that used to contain the UNIX group ID of the user + + .. deprecated:: 4.0 + This field is serves no known purpose, and shouldn't be used for anything so it can be removed in the future. + +:localPasswd: The user's password +:newUser: An optional meta field with no apparent purpose - don't use this +:phoneNumber: An optional field which should contain the user's phone number +:postalCode: An optional field which should contain the user's postal code +:publicSshKey: An optional field which should contain the user's public encryption key used for the SSH protocol +:role: The name that corresponds to the highest permission role which will be permitted to the user +:stateOrProvince: An optional field which should contain the name of the state or province in which the user resides +:tenantId: The integral, unique identifier of the tenant to which the new user shall belong +:ucdn: The name of the :abbr:`uCDN (Upstream Content Delivery Network)` to which the user belongs +:uid: A deprecated field only kept for legacy compatibility reasons that used to contain the UNIX user ID of the user + + .. deprecated:: 4.0 + This field is serves no known purpose, and shouldn't be used for anything so it can be removed in the future. + +:username: The new user's username + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/users HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 304 + Content-Type: application/json + + { + "username": "mike", + "addressLine1": "22 Mike Wazowski You've Got Your Life Back Lane", + "city": "Monstropolis", + "compary": "Monsters Inc.", + "email": "mwazowski@minc.biz", + "fullName": "Mike Wazowski", + "localPasswd": "BFFsully", + "confirmLocalPasswd": "BFFsully", + "newUser": true, + "role": "admin", + "tenantId": 1 + } + +Response Structure +------------------ +:addressLine1: The user's address - including street name and number +:addressLine2: An additional address field for e.g. apartment number +:changeLogCount: The number of change log entries created by the user +:city: The name of the city wherein the user resides +:company: The name of the company for which the user works +:country: The name of the country wherein the user resides +:email: The user's email address +:fullName: The user's full name, e.g. "John Quincy Adams" +:gid: A deprecated field only kept for legacy compatibility reasons that used to contain the UNIX group ID of the user - now it is always ``null`` + + .. deprecated:: 4.0 + This field is serves no known purpose, and shouldn't be used for anything so it can be removed in the future. + +:id: An integral, unique identifier for this user +:lastAuthenticated: The date and time at which the user was last authenticated, in :rfc:`3339` +:lastUpdated: The date and time at which the user was last modified, in :ref:`non-rfc-datetime` +:newUser: A meta field with no apparent purpose that is usually ``null`` unless explicitly set during creation or modification of a user via some API endpoint +:phoneNumber: The user's phone number +:postalCode: The postal code of the area in which the user resides +:publicSshKey: The user's public key used for the SSH protocol +:registrationSent: If the user was created using the :ref:`to-api-users-register` endpoint, this will be the date and time at which the registration email was sent - otherwise it will be ``null`` +:role: The name of the role assigned to this user +:stateOrProvince: The name of the state or province where this user resides +:tenant: The name of the tenant to which this user belongs +:tenantId: The integral, unique identifier of the tenant to which this user belongs +:ucdn: The name of the :abbr:`uCDN (Upstream Content Delivery Network)` to which the user belongs +:uid: A deprecated field only kept for legacy compatibility reasons that used to contain the UNIX user ID of the user - now it is always ``null`` + + .. deprecated:: 4.0 + This field is serves no known purpose, and shouldn't be used for anything so it can be removed in the future. + + +:username: The user's username + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 201 Created + Content-Encoding: gzip + Content-Type: application/json + Location: /api/5.0/users?id=3 + Permissions-Policy: interest-cohort=() + Set-Cookie: mojolicious=...; Path=/; Expires=Fri, 13 May 2022 23:13:54 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + X-Server-Name: traffic_ops_golang/ + Date: Fri, 13 May 2022 22:13:54 GMT + Content-Length: 382 + + { "alerts": [ + { + "text": "user was created.", + "level": "success" + } + ], + "response": { + "addressLine1": "22 Mike Wazowski You've Got Your Life Back Lane", + "addressLine2": null, + "changeLogCount": null, + "city": "Monstropolis", + "company": null, + "country": null, + "email": "mwazowski@minc.biz", + "fullName": "Mike Wazowski", + "gid": null, + "id": 3, + "lastAuthenticated": null, + "lastUpdated": "2022-05-13T22:13:54.605052Z", + "newUser": true, + "phoneNumber": null, + "postalCode": null, + "publicSshKey": null, + "registrationSent": null, + "role": "admin", + "stateOrProvince": null, + "tenant": "root", + "tenantId": 1, + "ucdn": "", + "uid": null, + "username": "mike" + }} + +.. [#tenancy] While no roles are required, this endpoint does respect tenancy. A user will only be able to see, create, delete or modify other users belonging to the same tenant, or its descendants. diff --git a/docs/source/api/v5/users_id.rst b/docs/source/api/v5/users_id.rst new file mode 100644 index 0000000000..3c2aa5378b --- /dev/null +++ b/docs/source/api/v5/users_id.rst @@ -0,0 +1,284 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-users-id: + +**************** +``users/{{ID}}`` +**************** + +``GET`` +======= +Retrieves a specific user. + +:Auth. Required: Yes +:Roles Required: None +:Permissions Required: USER:READ +:Response Type: Array + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+-------------------------------------------------------------+ + | Name | Description | + +======+=============================================================+ + | ID | The integral, unique identifier of the user to be retrieved | + +------+-------------------------------------------------------------+ + +.. code-block:: http + :caption: Request Example + + GET /api/5.0/users/3 HTTP/1.1 + User-Agent: python-requests/2.25.1 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + + +Response Structure +------------------ +:addressLine1: The user's address - including street name and number +:addressLine2: An additional address field for e.g. apartment number +:changeLogCount: The number of change log entries created by the user +:city: The name of the city wherein the user resides +:company: The name of the company for which the user works +:country: The name of the country wherein the user resides +:email: The user's email address +:fullName: The user's full name, e.g. "John Quincy Adams" +:gid: A deprecated field only kept for legacy compatibility reasons that used to contain the UNIX group ID of the user - now it is always ``null`` + + .. deprecated:: 4.0 + This field is serves no known purpose, and shouldn't be used for anything so it can be removed in the future. + +:id: An integral, unique identifier for this user +:lastAuthenticated: The date and time at which the user was last authenticated, in :rfc:`3339` +:lastUpdated: The date and time at which the user was last modified, in :ref:`non-rfc-datetime` +:newUser: A meta field with no apparent purpose that is usually ``null`` unless explicitly set during creation or modification of a user via some API endpoint +:phoneNumber: The user's phone number +:postalCode: The postal code of the area in which the user resides +:publicSshKey: The user's public key used for the SSH protocol +:registrationSent: If the user was created using the :ref:`to-api-users-register` endpoint, this will be the date and time at which the registration email was sent - otherwise it will be ``null`` +:role: The name of the role assigned to this user +:stateOrProvince: The name of the state or province where this user resides +:tenant: The name of the tenant to which this user belongs +:tenantId: The integral, unique identifier of the tenant to which this user belongs +:ucdn: The name of the :abbr:`uCDN (Upstream Content Delivery Network)` to which the user belongs +:uid: A deprecated field only kept for legacy compatibility reasons that used to contain the UNIX user ID of the user - now it is always ``null`` + + .. deprecated:: 4.0 + This field is serves no known purpose, and shouldn't be used for anything so it can be removed in the future. + +:username: The user's username + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Content-Encoding: gzip + Content-Type: application/json + Permissions-Policy: interest-cohort=() + Set-Cookie: mojolicious=...; Path=/; Expires=Fri, 13 May 2022 23:48:14 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + X-Server-Name: traffic_ops_golang/ + Date: Fri, 13 May 2022 22:48:14 GMT + Content-Length: 350 + + { "response": [ + { + "addressLine1": "22 Mike Wazowski You've Got Your Life Back Lane", + "addressLine2": null, + "changeLogCount": 0, + "city": "Monstropolis", + "company": null, + "country": null, + "email": "mwazowski@minc.biz", + "fullName": "Mike Wazowski", + "gid": null, + "id": 3, + "lastAuthenticated": null, + "lastUpdated": "2022-05-13T22:13:54.605052Z", + "newUser": true, + "phoneNumber": null, + "postalCode": null, + "publicSshKey": null, + "registrationSent": null, + "role": "admin", + "stateOrProvince": null, + "tenant": "root", + "tenantId": 1, + "ucdn": "", + "uid": null, + "username": "mike" + } + ]} + +``PUT`` +======= + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: USER:UPDATE, USER:READ +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +------+------------------------------------------------------------+ + | Name | Description | + +======+============================================================+ + | ID | The integral, unique identifier of the user to be modified | + +------+------------------------------------------------------------+ + +:addressLine1: An optional field which should contain the user's address - including street name and number +:addressLine2: An optional field which should contain an additional address field for e.g. apartment number +:city: An optional field which should contain the name of the city wherein the user resides +:company: An optional field which should contain the name of the company for which the user works +:country: An optional field which should contain the name of the country wherein the user resides +:email: The user's email address The given email is validated (circuitously) by `GitHub user asaskevich's regular expression `_ . Note that it can't actually distinguish a valid, deliverable, email address but merely ensure the email is in a commonly-found format. +:fullName: The user's full name, e.g. "John Quincy Adams" +:gid: A deprecated field only kept for legacy compatibility reasons that used to contain the UNIX group ID of the user - now it is always ``null`` + + .. deprecated:: 4.0 + This field is serves no known purpose, and shouldn't be used for anything so it can be removed in the future. + +:id: This field *may* optionally be given, but **must** match the user's existing ID as IDs are immutable +:localPasswd: The user's password +:newUser: An optional meta field with no apparent purpose - don't use this +:phoneNumber: An optional field which should contain the user's phone number +:postalCode: An optional field which should contain the user's postal code +:publicSshKey: An optional field which should contain the user's public encryption key used for the SSH protocol +:role: The name of the Role which will be granted to the user +:stateOrProvince: An optional field which should contain the name of the state or province in which the user resides +:tenantId: The integral, unique identifier of the tenant to which the new user shall belong +:ucdn: The name of the :abbr:`uCDN (Upstream Content Delivery Network)` to which the user belongs +:uid: A deprecated field only kept for legacy compatibility reasons that used to contain the UNIX user ID of the user - now it is always ``null`` + + .. deprecated:: 4.0 + This field is serves no known purpose, and shouldn't be used for anything so it can be removed in the future. + +:username: The user's username + +.. code-block:: http + :caption: Request Structure + + PUT /api/5.0/users/3 HTTP/1.1 + User-Agent: python-requests/2.25.1 + Accept-Encoding: gzip, deflate + Accept: */* + Connection: keep-alive + Cookie: mojolicious=... + Content-Length: 476 + + { + "addressLine1": "not a real address", + "addressLine2": "not a real address either", + "city": "not a real city", + "company": "not a real company", + "country": "not a real country", + "email": "mwazowski@minc.biz", + "fullName": "Mike Wazowski", + "phoneNumber": "not a real phone number", + "postalCode": "not a real postal code", + "publicSshKey": "not a real ssh key", + "stateOrProvince": "not a real state or province", + "tenantId": 1, + "role": "admin", + "username": "mike" + } + + +Response Structure +------------------ +:addressLine1: The user's address - including street name and number +:addressLine2: An additional address field for e.g. apartment number +:changeLogCount: The number of change log entries created by the user +:city: The name of the city wherein the user resides +:company: The name of the company for which the user works +:country: The name of the country wherein the user resides +:email: The user's email address +:fullName: The user's full name, e.g. "John Quincy Adams" +:gid: A deprecated field only kept for legacy compatibility reasons that used to contain the UNIX group ID of the user - now it is always ``null`` + + .. deprecated:: 4.0 + This field is serves no known purpose, and shouldn't be used for anything so it can be removed in the future. + +:id: An integral, unique identifier for this user +:lastAuthenticated: The date and time at which the user was last authenticated, in :rfc:`3339` +:lastUpdated: The date and time at which the user was last modified, in :ref:`non-rfc-datetime` +:newUser: A meta field with no apparent purpose that is usually ``null`` unless explicitly set during creation or modification of a user via some API endpoint +:phoneNumber: The user's phone number +:postalCode: The postal code of the area in which the user resides +:publicSshKey: The user's public key used for the SSH protocol +:registrationSent: If the user was created using the :ref:`to-api-users-register` endpoint, this will be the date and time at which the registration email was sent - otherwise it will be ``null`` +:role: The name of the role assigned to this user +:stateOrProvince: The name of the state or province where this user resides +:tenant: The name of the tenant to which this user belongs +:tenantId: The integral, unique identifier of the tenant to which this user belongs +:ucdn: The name of the :abbr:`uCDN (Upstream Content Delivery Network)` to which the user belongs +:uid: A deprecated field only kept for legacy compatibility reasons that used to contain the UNIX user ID of the user - now it is always ``null`` + + .. deprecated:: 4.0 + This field is serves no known purpose, and shouldn't be used for anything so it can be removed in the future. + +:username: The user's username + +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Content-Encoding: gzip + Content-Type: application/json + Permissions-Policy: interest-cohort=() + Set-Cookie: mojolicious=...; Path=/; Expires=Fri, 13 May 2022 23:50:25 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + X-Server-Name: traffic_ops_golang/ + Date: Fri, 13 May 2022 22:50:25 GMT + Content-Length: 399 + + { "alerts": [ + { + "text": "user was updated.", + "level": "success" + } + ], + "response": { + "addressLine1": "not a real address", + "addressLine2": "not a real address either", + "changeLogCount": 0, + "city": "not a real city", + "company": "not a real company", + "country": "not a real country", + "email": "mwazowski@minc.biz", + "fullName": "Mike Wazowski", + "gid": null, + "id": 3, + "lastAuthenticated": null, + "lastUpdated": "2022-05-13T22:50:25.965004Z", + "newUser": false, + "phoneNumber": "not a real phone number", + "postalCode": "not a real postal code", + "publicSshKey": "not a real ssh key", + "registrationSent": null, + "role": "admin", + "stateOrProvince": "not a real state or province", + "tenant": "root", + "tenantId": 1, + "ucdn": "", + "uid": null, + "username": "mike" + }} diff --git a/docs/source/api/v5/users_register.rst b/docs/source/api/v5/users_register.rst new file mode 100644 index 0000000000..3fc72bf1ce --- /dev/null +++ b/docs/source/api/v5/users_register.rst @@ -0,0 +1,78 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. + +.. _to-api-users-register: + +****************** +``users/register`` +****************** + +``POST`` +======== +Register a user and send registration email. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: USER:CREATE, USER:READ +:Response Type: ``undefined`` + +Request Structure +----------------- +:email: Email address of the new user The given email is validated (circuitously) by `GitHub user asaskevich's regular expression `_ . Note that it can't actually distinguish a valid, deliverable, email address but merely ensure the email is in a commonly-found format. +:role: The integral, unique identifier of the highest permissions :term:`Role` which will be afforded to the new user. It restricts the allowed values to identifiers for :term:`Roles` with at most the same permissions level as the requesting user. +:tenantId: A field containing the integral, unique identifier of the :term:`Tenant` to which the new user will belong. It restricts the allowed values to identifiers for :term:`Tenants` within the requesting user's :term:`Tenant`'s permissions. + +.. code-block:: http + :caption: Request Example + + POST /api/5.0/users/register HTTP/1.1 + Host: trafficops.infra.ciab.test + User-Agent: curl/7.47.0 + Accept: */* + Cookie: mojolicious=... + Content-Length: 59 + Content-Type: application/json + + { + "email": "test@example.com", + "role": 3, + "tenantId": 1 + } + +Response Structure +------------------ +.. code-block:: http + :caption: Response Example + + HTTP/1.1 200 OK + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Cache-Control: no-cache, no-store, max-age=0, must-revalidate + Content-Type: application/json + Date: Thu, 13 Dec 2018 22:03:22 GMT + X-Server-Name: traffic_ops_golang/ + Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly + Vary: Accept-Encoding + Whole-Content-Sha512: yvf++Oqxvu3uOIAYbWLUgJKxZ4T60Mi5H9eGTxrKLxnRsHw0PdDIrThbTnWtATBkak4vU/dPHLLXKW85LUTEWg== + Content-Length: 160 + + { "alerts": [ + { + "level": "success", + "text": "Sent user registration to test@example.com with the following permissions [ role: read-only | tenant: root ]" + } + ]} diff --git a/docs/source/api/v5/vault_ping.rst b/docs/source/api/v5/vault_ping.rst new file mode 100644 index 0000000000..e5d86ee41d --- /dev/null +++ b/docs/source/api/v5/vault_ping.rst @@ -0,0 +1,59 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +.. See the License for the specific language governing permissions and +.. limitations under the License. +.. +.. _to-api-vault-ping: + +************** +``vault/ping`` +************** + +``GET`` +======= +Pings Traffic Vault to retrieve status. + +:Auth. Required: Yes +:Roles Required: "read-only" +:Permissions Required: TRAFFIC-VAULT:READ +:Response Type: Object + +Request Structure +----------------- +No parameters available. + +Response Properties +------------------- +:status: The status returned from the ping request to the Traffic Vault server +:server: The Traffic Vault server that was pinged + +.. 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=/; Expires=Tue, 25 Feb 2020 15:37:54 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: z9P1NkxGebPncUhaChDHtYKYI+XVZfhE6Y84TuwoASZFIMfISELwADLpvpPTN+wwnzBfREksLYn+0313QoBWhA== + X-Server-Name: traffic_ops_golang/ + Date: Tue, 25 Feb 2020 14:37:55 GMT + Content-Length: 90 + + { "response": + { + "status": "OK", + "server": "trafficvault.infra.ciab.test:8087" + } + } diff --git a/docs/source/development/traffic_ops.rst b/docs/source/development/traffic_ops.rst index 859e542e16..f79ce8d709 100644 --- a/docs/source/development/traffic_ops.rst +++ b/docs/source/development/traffic_ops.rst @@ -169,6 +169,7 @@ Traffic Ops Project Tree Overview - v3-client - The official Traffic Ops Go client package for working with the version 3 :ref:`to-api`. - v4-client - The official Traffic Ops Go client package for working with the version 4 :ref:`to-api`. + - v5-client - The official Traffic Ops Go client package for working with the version 5 :ref:`to-api`. - vendor/ - contains "vendored" Go packages from third party sources .. _database-management: diff --git a/docs/source/overview/delivery_services.rst b/docs/source/overview/delivery_services.rst index 42743ac876..79be7168f2 100644 --- a/docs/source/overview/delivery_services.rst +++ b/docs/source/overview/delivery_services.rst @@ -1018,7 +1018,7 @@ The following :term:`Parameters` must have the :ref:`Config File Tuple[Union[Dict[str, Any], List[Dict[str, Any]]], Response]: """ Create a topology @@ -1897,7 +1897,7 @@ def create_topology(self, data: Dict[str, Any]=None) -> Tuple[Union[Dict[str, An :raises: Union[LoginError, OperationError] """ - @api_request('get', 'topologies', ('3.0',)) + @api_request('get', 'topologies', ('3.0', '4.0', '4.1', '5.0')) def get_topologies(self, query_params: Dict[str, Any]=None) -> Tuple[Union[Dict[str, Any], List[Dict[str, Any]]], Response]: """ Get Topologies. @@ -1908,7 +1908,7 @@ def get_topologies(self, query_params: Dict[str, Any]=None) -> Tuple[Union[Dict[ :raises: Union[LoginError, OperationError] """ - @api_request('put', 'topologies?name={name:s}', ('3.0',)) + @api_request('put', 'topologies?name={name:s}', ('3.0', '4.0', '4.1', '5.0')) def update_topology(self, name: str=None, data: Dict[str, Any]=None) -> Tuple[Union[Dict[str, Any], List[Dict[str, Any]]], Response]: """ Update a Topology @@ -1921,7 +1921,7 @@ def update_topology(self, name: str=None, data: Dict[str, Any]=None) -> Tuple[Un :raises: Union[LoginError, OperationError] """ - @api_request('delete', 'topologies?name={name:s}', ('3.0',)) + @api_request('delete', 'topologies?name={name:s}', ('3.0', '4.0', '4.1', '5.0')) def delete_topology(self, name: str=None) -> Tuple[Union[Dict[str, Any], List[Dict[str, Any]]], Response]: """ Delete a Topology @@ -1932,7 +1932,7 @@ def delete_topology(self, name: str=None) -> Tuple[Union[Dict[str, Any], List[Di :raises: Union[LoginError, OperationError] """ - @api_request('post', 'topologies/{name:s}/queue_update', ('3.0',)) + @api_request('post', 'topologies/{name:s}/queue_update', ('3.0', '4.0', '4.1', '5.0')) def topologies_queue_update(self, name=None, data=None): """ Queue Updates by Topology name. @@ -1948,7 +1948,7 @@ def topologies_queue_update(self, name=None, data=None): # # Types # - @api_request('get', 'types', ('3.0',)) + @api_request('get', 'types', ('3.0', '4.0', '4.1', '5.0')) def get_types(self, query_params=None): """ Get Data Types. @@ -1960,7 +1960,7 @@ def get_types(self, query_params=None): # # Users # - @api_request('get', 'users', ('3.0',)) + @api_request('get', 'users', ('3.0', '4.0', '4.1', '5.0')) def get_users(self): """ Retrieves all users. @@ -1969,7 +1969,7 @@ def get_users(self): :raises: Union[LoginError, OperationError] """ - @api_request('get', 'users/{user_id:d}', ('3.0',)) + @api_request('get', 'users/{user_id:d}', ('3.0', '4.0', '4.1', '5.0')) def get_user_by_id(self, user_id=None): """ Retrieves user by ID. @@ -1980,7 +1980,7 @@ def get_user_by_id(self, user_id=None): :raises: Union[LoginError, OperationError] """ - @api_request('post', 'users', ('3.0',)) + @api_request('post', 'users', ('3.0', '4.0', '4.1', '5.0')) def create_user(self, data=None): """ Create a user. @@ -1991,7 +1991,7 @@ def create_user(self, data=None): :raises: Union[LoginError, OperationError] """ - @api_request('put', 'users/{user_id:d}', ('3.0',)) + @api_request('put', 'users/{user_id:d}', ('3.0', '4.0', '4.1', '5.0')) def update_user_by_id(self, user_id=None, data=None): """ Update a user. @@ -2002,7 +2002,7 @@ def update_user_by_id(self, user_id=None, data=None): :raises: Union[LoginError, OperationError] """ - @api_request('post', 'users/register', ('3.0',)) + @api_request('post', 'users/register', ('3.0', '4.0', '4.1', '5.0')) def create_user_with_registration(self, data=None): """ Register a user and send registration email @@ -2013,7 +2013,7 @@ def create_user_with_registration(self, data=None): :raises: Union[LoginError, OperationError] """ - @api_request('get', 'user/current', ('3.0',)) + @api_request('get', 'user/current', ('3.0', '4.0', '4.1', '5.0')) def get_authenticated_user(self): """ Retrieves the profile for the authenticated user. @@ -2022,7 +2022,7 @@ def get_authenticated_user(self): :raises: Union[LoginError, OperationError] """ - @api_request('put', 'user/current', ('3.0',)) + @api_request('put', 'user/current', ('3.0', '4.0', '4.1', '5.0')) def replace_authenticated_user(self, data=None): """ Updates the currently authenticated user. @@ -2036,7 +2036,7 @@ def replace_authenticated_user(self, data=None): # # Snapshot CRConfig # - @api_request('get', 'cdns/{cdn_name}/snapshot', ('3.0',)) + @api_request('get', 'cdns/{cdn_name}/snapshot', ('3.0', '4.0', '4.1', '5.0')) def get_current_snapshot_crconfig(self, cdn_name=None): """ Retrieves the CURRENT snapshot for a CDN which doesn't necessarily represent the current @@ -2049,7 +2049,7 @@ def get_current_snapshot_crconfig(self, cdn_name=None): :raises: Union[LoginError, OperationError] """ - @api_request('get', 'cdns/{cdn_name}/snapshot/new', ('3.0',)) + @api_request('get', 'cdns/{cdn_name}/snapshot/new', ('3.0', '4.0', '4.1', '5.0')) def get_pending_snapshot_crconfig(self, cdn_name=None): """ Retrieves a PENDING snapshot for a CDN which represents the current state of the CDN. The @@ -2063,7 +2063,7 @@ def get_pending_snapshot_crconfig(self, cdn_name=None): :raises: Union[LoginError, OperationError] """ - @api_request('put', 'snapshot', ('3.0',)) + @api_request('put', 'snapshot', ('3.0', '4.0', '4.1', '5.0')) def snapshot_crconfig(self, query_params=None): """ Snapshot CRConfig by CDN Name or ID. @@ -2077,7 +2077,7 @@ def snapshot_crconfig(self, query_params=None): # # Coordinate # - @api_request('get', 'coordinates', ('3.0',)) + @api_request('get', 'coordinates', ('3.0', '4.0', '4.1', '5.0')) def get_coordinates(self, query_params=None): """ Get all coordinates associated with the cdn @@ -2088,7 +2088,7 @@ def get_coordinates(self, query_params=None): :raises: Union[LoginError, OperationError] """ - @api_request('post', 'coordinates', ('3.0',)) + @api_request('post', 'coordinates', ('3.0', '4.0', '4.1', '5.0')) def create_coordinates(self, data=None): """ Create coordinates @@ -2099,7 +2099,7 @@ def create_coordinates(self, data=None): :raises: Union[LoginError, OperationError] """ - @api_request('put', 'coordinates', ('3.0',)) + @api_request('put', 'coordinates', ('3.0', '4.0', '4.1', '5.0')) def update_coordinates(self, query_params=None, data=None): """ Update coordinates @@ -2112,7 +2112,7 @@ def update_coordinates(self, query_params=None, data=None): :raises: Union[LoginError, OperationError] """ - @api_request('delete', 'coordinates', ('3.0',)) + @api_request('delete', 'coordinates', ('3.0', '4.0', '4.1', '5.0')) def delete_coordinates(self, query_params=None): """ Delete coordinates @@ -2126,7 +2126,7 @@ def delete_coordinates(self, query_params=None): # # Origin # - @api_request('get', 'origins', ('3.0',)) + @api_request('get', 'origins', ('3.0', '4.0', '4.1', '5.0')) def get_origins(self, query_params=None): """ Get origins associated with the delivery service @@ -2137,7 +2137,7 @@ def get_origins(self, query_params=None): :raises: Union[LoginError, OperationError] """ - @api_request('post', 'origins', ('3.0',)) + @api_request('post', 'origins', ('3.0', '4.0', '4.1', '5.0')) def create_origins(self, data=None): """ Creates origins associated with a delivery service @@ -2148,7 +2148,7 @@ def create_origins(self, data=None): :raises: Union[LoginError, OperationError] """ - @api_request('put', 'origins', ('3.0',)) + @api_request('put', 'origins', ('3.0', '4.0', '4.1', '5.0')) def update_origins(self, query_params=None): """ Updates origins associated with a delivery service @@ -2161,7 +2161,7 @@ def update_origins(self, query_params=None): :raises: Union[LoginError, OperationError] """ - @api_request('delete', 'origins', ('3.0',)) + @api_request('delete', 'origins', ('3.0', '4.0', '4.1', '5.0')) def delete_origins(self, query_params=None): """ Updates origins associated with a delivery service diff --git a/traffic_ops/testing/api/utils/utils.go b/traffic_ops/testing/api/utils/utils.go index fb69e73573..13481a087e 100644 --- a/traffic_ops/testing/api/utils/utils.go +++ b/traffic_ops/testing/api/utils/utils.go @@ -29,6 +29,7 @@ import ( "github.com/apache/trafficcontrol/traffic_ops/toclientlib" v3client "github.com/apache/trafficcontrol/traffic_ops/v3-client" v4client "github.com/apache/trafficcontrol/traffic_ops/v4-client" + v5client "github.com/apache/trafficcontrol/traffic_ops/v5-client" ) type ErrorAndMessage struct { @@ -98,13 +99,11 @@ func CreateV4Session(t *testing.T, TrafficOpsURL string, username string, passwo return userSession } -// V3TestCase is the type of the V3TestData struct. -// Uses nested map to represent the method being tested and the test's description. -type V3TestCase map[string]map[string]V3TestData - -// V4TestCase is the type of the V4TestData struct. -// Uses nested map to represent the method being tested and the test's description. -type V4TestCase map[string]map[string]V4TestData +func CreateV5Session(t *testing.T, TrafficOpsURL, username, password string, toReqTimeout int) *v5client.Session { + userSession, _, err := v5client.LoginWithAgent(TrafficOpsURL, username, password, true, "to-api-v5-client-tests", false, time.Second*time.Duration(toReqTimeout)) + assert.RequireNoError(t, err, "Could not login with user %v: %v", username, err) + return userSession +} // V3TestData represents the data needed for testing the v3 api endpoints. type V3TestData struct { @@ -125,6 +124,28 @@ type V4TestData struct { Expectations []CkReqFunc } +// V5TestData represents the data needed for testing the v5 api endpoints. +type V5TestData struct { + EndpointId func() int + ClientSession *v5client.Session + RequestOpts v5client.RequestOptions + RequestBody map[string]interface{} + Expectations []CkReqFunc +} + +// V3TestCase is the type of the V3TestData struct. +// Uses nested map to represent the method being tested and the test's description. +type V3TestCase map[string]map[string]V3TestData + +// V4TestCase is the type of the V4TestData struct. +// Uses nested map to represent the method being tested and the test's description. +type V4TestCase map[string]map[string]V4TestData + +// V5TestCase is a map of test names to maps of HTTP request method descriptions +// to V5TestData structures. +// Uses nested map to represent the method being tested and the test's description. +type V5TestCase map[string]map[string]V5TestData + // CkReqFunc defines the reusable signature for all other functions that perform checks. // Common parameters that are checked include the request's info, response, alerts, and errors. type CkReqFunc func(*testing.T, toclientlib.ReqInf, interface{}, tc.Alerts, error) diff --git a/traffic_ops/testing/api/v5/.gitignore b/traffic_ops/testing/api/v5/.gitignore new file mode 100644 index 0000000000..9d8757a3c8 --- /dev/null +++ b/traffic_ops/testing/api/v5/.gitignore @@ -0,0 +1,18 @@ +# 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. +# +traffic-ops-test*.conf diff --git a/traffic_ops/testing/api/v5/about_test.go b/traffic_ops/testing/api/v5/about_test.go new file mode 100644 index 0000000000..2eea4de3cc --- /dev/null +++ b/traffic_ops/testing/api/v5/about_test.go @@ -0,0 +1,53 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/http" + "testing" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" +) + +func TestAbout(t *testing.T) { + + methodTests := utils.V5TestCase{ + "GET": { + "OK when VALID request": { + ClientSession: TOSession, Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "UNAUTHORIZED when NOT LOGGED IN": { + ClientSession: NoAuthTOSession, Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusUnauthorized)), + }, + }, + } + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetAbout(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp, tc.Alerts{}, err) + } + }) + } + } + }) + } +} diff --git a/traffic_ops/testing/api/v5/acme_test.go b/traffic_ops/testing/api/v5/acme_test.go new file mode 100644 index 0000000000..f83e5b277a --- /dev/null +++ b/traffic_ops/testing/api/v5/acme_test.go @@ -0,0 +1,49 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/http" + "testing" + + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" +) + +func TestAcmeAutoRenew(t *testing.T) { + + methodTests := utils.V5TestCase{ + "POST": { + "OK when VALID request": { + ClientSession: TOSession, Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusAccepted)), + }, + }, + } + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + switch method { + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.AutoRenew(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } +} diff --git a/traffic_ops/testing/api/v5/asns_test.go b/traffic_ops/testing/api/v5/asns_test.go new file mode 100644 index 0000000000..bef797f8c5 --- /dev/null +++ b/traffic_ops/testing/api/v5/asns_test.go @@ -0,0 +1,187 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "sort" + "strconv" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestASN(t *testing.T) { + WithObjs(t, []TCObj{Types, CacheGroups, ASN}, func() { + tomorrow := time.Now().AddDate(0, 0, 1).Format(time.RFC1123) + currentTime := time.Now().UTC().Add(-5 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID request": { + ClientSession: TOSession, Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateSorted()), + }, + "OK when VALID ASN PARAMETER": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"asn": {"9999"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1)), + }, + }, + "PUT": { + "OK when VALID request": { + ClientSession: TOSession, EndpointId: GetASNId(t, "8888"), + RequestBody: map[string]interface{}{ + "asn": 7777, + "cachegroupName": "originCachegroup", + "cachegroupId": -1, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + "GET AFTER CHANGES": { + "OK when CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{ + Header: http.Header{ + rfc.IfModifiedSince: {currentTimeRFC}, rfc.IfUnmodifiedSince: {currentTimeRFC}, + }, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + asn := tc.ASN{} + + if testCase.RequestBody != nil { + if cgId, ok := testCase.RequestBody["cachegroupId"]; ok { + if cgId == -1 { + if cgName, ok := testCase.RequestBody["cachegroupName"]; ok { + testCase.RequestBody["cachegroupId"] = GetCacheGroupId(t, cgName.(string))() + } + } + } + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &asn) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET", "GET AFTER CHANGES": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetASNs(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.UpdateASN(testCase.EndpointId(), asn, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateSorted() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + asnResp := resp.([]tc.ASN) + var sortedList []string + assert.RequireGreaterOrEqual(t, len(asnResp), 2, "Need at least 2 ASNs in Traffic Ops to test sorted, found: %d", len(asnResp)) + + for _, asn := range asnResp { + sortedList = append(sortedList, strconv.Itoa(asn.ASN)) + } + + res := sort.SliceIsSorted(sortedList, func(p, q int) bool { + return sortedList[p] < sortedList[q] + }) + assert.Equal(t, res, true, "List is not sorted by their names: %v", sortedList) + } +} + +func GetASNId(t *testing.T, ASN string) func() int { + return func() int { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("asn", ASN) + + resp, _, err := TOSession.GetASNs(opts) + assert.RequireNoError(t, err, "Get ASNs Request failed with error: %v", err) + assert.RequireEqual(t, len(resp.Response), 1, "Expected response object length 1, but got %d", len(resp.Response)) + assert.RequireNotNil(t, &resp.Response[0].ID, "Expected id to not be nil") + + return resp.Response[0].ID + } +} + +func CreateTestASNs(t *testing.T) { + assert.RequireGreaterOrEqual(t, len(testData.CacheGroups), 1, "Need at least one Cache Group to test creating ASNs") + + cg := testData.CacheGroups[0] + assert.RequireNotNil(t, cg.Name, "Cache Group found in the test data with null or undefined name") + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", *cg.Name) + resp, _, err := TOSession.GetCacheGroups(opts) + assert.RequireNoError(t, err, "Unable to get cachgroup ID: %v - alerts: %+v", err, resp.Alerts) + assert.RequireEqual(t, 1, len(resp.Response), "Expected exactly one Cache Group with Name '%s', got: %d", *cg.Name, len(resp.Response)) + assert.RequireNotNil(t, resp.Response[0].ID, "Cache Group '%s' had no ID in Traffic Ops response", *cg.Name) + + id := *resp.Response[0].ID + for _, asn := range testData.ASNs { + asn.CachegroupID = id + resp, _, err := TOSession.CreateASN(asn, client.RequestOptions{}) + assert.NoError(t, err, "Could not create ASN: %v - alerts: %+v", err, resp) + } +} + +func DeleteTestASNs(t *testing.T) { + opts := client.NewRequestOptions() + // Retrieve the ASNs to delete + asns, _, err := TOSession.GetASNs(opts) + assert.NoError(t, err, "Error trying to fetch ASNs for deletion: %v - alerts: %+v", err, asns.Alerts) + for _, asn := range asns.Response { + alerts, _, err := TOSession.DeleteASN(asn.ID, client.RequestOptions{}) + assert.NoError(t, err, "Cannot delete ASN %d: %v - alerts: %+v", asn.ASN, err, alerts) + + // Retrieve the ASN to see if it got deleted + opts.QueryParameters.Set("asn", strconv.Itoa(asn.ASN)) + asns, _, err := TOSession.GetASNs(opts) + assert.NoError(t, err, "Error trying to fetch ASN after deletion: %v - alerts: %+v", err, asns.Alerts) + assert.Equal(t, 0, len(asns.Response), "Expected ASN %d to be deleted, but it was found in Traffic Ops's response", asn.ASN) + } +} diff --git a/traffic_ops/testing/api/v5/cachegroups_test.go b/traffic_ops/testing/api/v5/cachegroups_test.go new file mode 100644 index 0000000000..b0301bb1e6 --- /dev/null +++ b/traffic_ops/testing/api/v5/cachegroups_test.go @@ -0,0 +1,453 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "strconv" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestCacheGroups(t *testing.T) { + WithObjs(t, []TCObj{Types, Parameters, CacheGroups, CDNs, Profiles, Statuses, Divisions, Regions, PhysLocations, Servers, Topologies}, func() { + + tomorrow := time.Now().AddDate(0, 0, 1).Format(time.RFC1123) + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "OK when VALID NAME parameter AND Lat/Long are 0": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"nullLatLongCG"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), ValidateResponseFields()), + }, + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "NOT MODIFIED when VALID NAME parameter when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{ + Header: http.Header{rfc.IfModifiedSince: {tomorrow}}, + QueryParameters: url.Values{"name": {"originCachegroup"}}, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "NOT MODIFIED when VALID SHORTNAME parameter when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{ + Header: http.Header{rfc.IfModifiedSince: {tomorrow}}, + QueryParameters: url.Values{"shortName": {"mog1"}}, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID request": { + ClientSession: TOSession, Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK when VALID NAME parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"parentCachegroup"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + ValidateExpectedField("Name", "parentCachegroup")), + }, + "OK when VALID SHORTNAME parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"shortName": {"pg2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + ValidateExpectedField("ShortName", "pg2")), + }, + "OK when VALID TOPOLOGY parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"topology": {"mso-topology"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK when VALID TYPE parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"type": {"ORG_LOC"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + ValidateExpectedField("TypeName", "ORG_LOC")), + }, + "EMPTY RESPONSE when INVALID ID parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"id": {"10000"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when INVALID TYPE parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"type": {"10000"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "FIRST RESULT when LIMIT=1": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), ValidatePagination("limit")), + }, + "SECOND RESULT when LIMIT=1 OFFSET=1": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "offset": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), ValidatePagination("offset")), + }, + "SECOND RESULT when LIMIT=1 PAGE=2": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "page": {"2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), ValidatePagination("page")), + }, + "BAD REQUEST when INVALID LIMIT parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"-2"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID OFFSET parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "offset": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID PAGE parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "page": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "UNAUTHORIZED when NOT LOGGED IN": { + ClientSession: NoAuthTOSession, Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusUnauthorized)), + }, + }, + "POST": { + "UNAUTHORIZED when NOT LOGGED IN": { + ClientSession: NoAuthTOSession, Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusUnauthorized)), + }, + }, + "PUT": { + "OK when VALID request": { + EndpointId: GetCacheGroupId(t, "cachegroup1"), ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "latitude": 17.5, + "longitude": 17.5, + "name": "cachegroup1", + "shortName": "newShortName", + "localizationMethods": []string{"CZ"}, + "fallbacks": []string{"fallback1"}, + "typeName": "EDGE_LOC", + "typeId": -1, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK when updating CG with null Lat/Long": { + EndpointId: GetCacheGroupId(t, "nullLatLongCG"), ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "nullLatLongCG", + "shortName": "null-ll", + "typeName": "EDGE_LOC", + "fallbacks": []string{"fallback1"}, + "typeId": -1, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "BAD REQUEST when updating TYPE of CG in TOPOLOGY": { + EndpointId: GetCacheGroupId(t, "topology-edge-cg-01"), ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "id": -1, + "latitude": 0, + "longitude": 0, + "name": "topology-edge-cg-01", + "shortName": "te1", + "typeName": "MID_LOC", + "typeId": -1, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "PRECONDITION FAILED when updating with IMS & IUS Headers": { + EndpointId: GetCacheGroupId(t, "cachegroup1"), ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}}, + RequestBody: map[string]interface{}{ + "name": "cachegroup1", + "shortName": "changeName", + "typeName": "EDGE_LOC", + "typeId": -1, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + "PRECONDITION FAILED when updating with IFMATCH ETAG Header": { + EndpointId: GetCacheGroupId(t, "cachegroup1"), ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}}, + RequestBody: map[string]interface{}{ + "name": "cachegroup1", + "shortName": "changeName", + "typeName": "EDGE_LOC", + "typeId": -1, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + "UNAUTHORIZED when NOT LOGGED IN": { + EndpointId: GetCacheGroupId(t, "cachegroup1"), ClientSession: NoAuthTOSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusUnauthorized)), + }, + }, + "DELETE": { + "NOT FOUND when INVALID ID parameter": { + EndpointId: func() int { return 111111 }, ClientSession: TOSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "UNAUTHORIZED when NOT LOGGED IN": { + EndpointId: GetCacheGroupId(t, "cachegroup1"), ClientSession: NoAuthTOSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusUnauthorized)), + }, + }, + "GET AFTER CHANGES": { + "OK when CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {currentTimeRFC}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + cg := tc.CacheGroupNullable{} + + if testCase.RequestOpts.QueryParameters.Has("type") { + val := testCase.RequestOpts.QueryParameters.Get("type") + if _, err := strconv.Atoi(val); err != nil { + testCase.RequestOpts.QueryParameters.Set("type", strconv.Itoa(GetTypeId(t, val))) + } + } + + if testCase.RequestBody != nil { + if _, ok := testCase.RequestBody["id"]; ok { + testCase.RequestBody["id"] = testCase.EndpointId() + } + if typeId, ok := testCase.RequestBody["typeId"]; ok { + if typeId == -1 { + if typeName, ok := testCase.RequestBody["typeName"]; ok { + testCase.RequestBody["typeId"] = GetTypeId(t, typeName.(string)) + } + } + } + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &cg) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET", "GET AFTER CHANGES": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetCacheGroups(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.CreateCacheGroup(cg, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.UpdateCacheGroup(testCase.EndpointId(), cg, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.DeleteCacheGroup(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func ValidateExpectedField(field string, expected string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + cgResp := resp.([]tc.CacheGroupNullable) + cg := cgResp[0] + switch field { + case "Name": + assert.Equal(t, expected, *cg.Name, "Expected name to be %v, but got %v", expected, *cg.Name) + case "ShortName": + assert.Equal(t, expected, *cg.ShortName, "Expected shortName to be %v, but got %v", expected, *cg.ShortName) + case "TypeName": + assert.Equal(t, expected, *cg.Type, "Expected type to be %v, but got %v", expected, *cg.Type) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } +} + +func ValidateResponseFields() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + cgResp := resp.([]tc.CacheGroupNullable) + cg := cgResp[0] + assert.NotNil(t, cg.ID, "Expected response id to not be nil") + assert.NotNil(t, cg.Latitude, "Expected latitude to not be nil") + assert.NotNil(t, cg.Longitude, "Expected longitude to not be nil") + assert.Equal(t, 0.0, *cg.Longitude, "Expected Longitude to be 0, but got %v", cg.Longitude) + assert.Equal(t, 0.0, *cg.Latitude, "Expected Latitude to be 0, but got %v", cg.Latitude) + } +} + +func ValidatePagination(paginationParam string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + paginationResp := resp.([]tc.CacheGroupNullable) + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("orderby", "id") + respBase, _, err := TOSession.GetCacheGroups(opts) + assert.RequireNoError(t, err, "cannot get Cache Groups: %v - alerts: %+v", err, respBase.Alerts) + + cachegroup := respBase.Response + assert.RequireGreaterOrEqual(t, len(cachegroup), 3, "Need at least 3 Cache Groups in Traffic Ops to test pagination support, found: %d", len(cachegroup)) + switch paginationParam { + case "limit:": + assert.Exactly(t, cachegroup[:1], paginationResp, "expected GET Cachegroups with limit = 1 to return first result") + case "offset": + assert.Exactly(t, cachegroup[1:2], paginationResp, "expected GET cachegroup with limit = 1, offset = 1 to return second result") + case "page": + assert.Exactly(t, cachegroup[1:2], paginationResp, "expected GET cachegroup with limit = 1, page = 2 to return second result") + } + } +} + +func GetTypeId(t *testing.T, typeName string) int { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", typeName) + resp, _, err := TOSession.GetTypes(opts) + + assert.RequireNoError(t, err, "Get Types Request failed with error: %v", err) + assert.RequireEqual(t, 1, len(resp.Response), "Expected response object length 1, but got %d", len(resp.Response)) + assert.RequireNotNil(t, &resp.Response[0].ID, "Expected id to not be nil") + + return resp.Response[0].ID +} + +func GetCacheGroupId(t *testing.T, cacheGroupName string) func() int { + return func() int { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", cacheGroupName) + + resp, _, err := TOSession.GetCacheGroups(opts) + assert.RequireNoError(t, err, "Get Cache Groups Request failed with error: %v", err) + assert.RequireEqual(t, len(resp.Response), 1, "Expected response object length 1, but got %d", len(resp.Response)) + assert.RequireNotNil(t, resp.Response[0].ID, "Expected id to not be nil") + + return *resp.Response[0].ID + } +} + +func CreateTestCacheGroups(t *testing.T) { + for _, cg := range testData.CacheGroups { + + resp, _, err := TOSession.CreateCacheGroup(cg, client.RequestOptions{}) + if err != nil { + t.Errorf("could not create Cache Group: %v - alerts: %+v", err, resp.Alerts) + continue + } + + // Testing 'join' fields during create + if cg.ParentName != nil && resp.Response.ParentName == nil { + t.Error("Parent cachegroup is null in response when it should have a value") + } + if cg.SecondaryParentName != nil && resp.Response.SecondaryParentName == nil { + t.Error("Secondary parent cachegroup is null in response when it should have a value") + } + if cg.Type != nil && resp.Response.Type == nil { + t.Error("Type is null in response when it should have a value") + } + assert.NotNil(t, resp.Response.LocalizationMethods, "Localization methods are null") + assert.NotNil(t, resp.Response.Fallbacks, "Fallbacks are null") + } +} + +func DeleteTestCacheGroups(t *testing.T) { + var parentlessCacheGroups []tc.CacheGroupNullable + opts := client.NewRequestOptions() + + // delete the edge caches. + for _, cg := range testData.CacheGroups { + if cg.Name == nil { + t.Error("Found a Cache Group with null or undefined name") + continue + } + + // Retrieve the CacheGroup by name so we can get the id for Deletion + opts.QueryParameters.Set("name", *cg.Name) + resp, _, err := TOSession.GetCacheGroups(opts) + assert.NoError(t, err, "Cannot GET CacheGroup by name '%s': %v - alerts: %+v", *cg.Name, err, resp.Alerts) + + if len(resp.Response) < 1 { + t.Errorf("Could not find test data Cache Group '%s' in Traffic Ops", *cg.Name) + continue + } + cg = resp.Response[0] + + // Cachegroups that are parents (usually mids but sometimes edges) + // need to be deleted only after the children cachegroups are deleted. + if cg.ParentCachegroupID == nil && cg.SecondaryParentCachegroupID == nil { + parentlessCacheGroups = append(parentlessCacheGroups, cg) + continue + } + + if cg.ID == nil { + t.Error("Traffic Ops returned a Cache Group with null or undefined ID") + continue + } + + alerts, _, err := TOSession.DeleteCacheGroup(*cg.ID, client.RequestOptions{}) + assert.NoError(t, err, "Cannot delete Cache Group: %v - alerts: %+v", err, alerts) + + // Retrieve the CacheGroup to see if it got deleted + opts.QueryParameters.Set("name", *cg.Name) + cgs, _, err := TOSession.GetCacheGroups(opts) + assert.NoError(t, err, "Error deleting Cache Group by name: %v - alerts: %+v", err, cgs.Alerts) + assert.Equal(t, 0, len(cgs.Response), "Expected CacheGroup name: %s to be deleted", *cg.Name) + } + + opts = client.NewRequestOptions() + // now delete the parentless cachegroups + for _, cg := range parentlessCacheGroups { + // nil check for cg.Name occurs prior to insertion into parentlessCacheGroups + opts.QueryParameters.Set("name", *cg.Name) + // Retrieve the CacheGroup by name so we can get the id for Deletion + resp, _, err := TOSession.GetCacheGroups(opts) + assert.NoError(t, err, "Cannot get Cache Group by name '%s': %v - alerts: %+v", *cg.Name, err, resp.Alerts) + + if len(resp.Response) < 1 { + t.Errorf("Cache Group '%s' somehow stopped existing since the last time we ask Traffic Ops about it", *cg.Name) + continue + } + + respCG := resp.Response[0] + if respCG.ID == nil { + t.Errorf("Traffic Ops returned Cache Group '%s' with null or undefined ID", *cg.Name) + continue + } + delResp, _, err := TOSession.DeleteCacheGroup(*respCG.ID, client.RequestOptions{}) + assert.NoError(t, err, "Cannot delete Cache Group '%s': %v - alerts: %+v", *respCG.Name, err, delResp.Alerts) + + // Retrieve the CacheGroup to see if it got deleted + opts.QueryParameters.Set("name", *cg.Name) + cgs, _, err := TOSession.GetCacheGroups(opts) + assert.NoError(t, err, "Error attempting to fetch Cache Group '%s' after deletion: %v - alerts: %+v", *cg.Name, err, cgs.Alerts) + assert.Equal(t, 0, len(cgs.Response), "Expected Cache Group '%s' to be deleted", *cg.Name) + } +} diff --git a/traffic_ops/testing/api/v5/cachegroupsdeliveryservices_test.go b/traffic_ops/testing/api/v5/cachegroupsdeliveryservices_test.go new file mode 100644 index 0000000000..96f8b16564 --- /dev/null +++ b/traffic_ops/testing/api/v5/cachegroupsdeliveryservices_test.go @@ -0,0 +1,163 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/http" + "strconv" + "testing" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestCacheGroupsDeliveryServices(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServiceCategories, DeliveryServices, CacheGroupsDeliveryServices}, func() { + methodTests := utils.V5TestCase{ + "POST": { + "BAD REQUEST assigning TOPOLOGY-BASED DS to CACHEGROUP": { + EndpointId: GetCacheGroupId(t, "cachegroup3"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "deliveryServices": []int{GetDeliveryServiceId(t, "top-ds-in-cdn1")()}, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "OK when valid request": { + EndpointId: GetCacheGroupId(t, "cachegroup3"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "deliveryServices": []int{ + GetDeliveryServiceId(t, "ds1")(), + GetDeliveryServiceId(t, "ds2")(), + GetDeliveryServiceId(t, "ds3")(), + GetDeliveryServiceId(t, "ds3")(), + GetDeliveryServiceId(t, "DS5")(), + }, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateCGDSServerAssignments()), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + switch method { + case "POST": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.SetCacheGroupDeliveryServices(testCase.EndpointId(), testCase.RequestBody["deliveryServices"].([]int), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + } + } + }) + } + + }) +} + +func validateCGDSServerAssignments() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + cgDsResp := resp.(tc.CacheGroupPostDSResp) + opts := client.NewRequestOptions() + for _, serverName := range cgDsResp.ServerNames { + opts.QueryParameters.Set("hostName", string(serverName)) + resp, _, err := TOSession.GetServers(opts) + assert.NoError(t, err, "Error: Getting server: %v - alerts: %+v", err, resp.Alerts) + assert.Equal(t, len(resp.Response), 1, "Error: Getting servers: expected 1 got %v", len(resp.Response)) + + serverDSes, _, err := TOSession.GetDeliveryServicesByServer(*resp.Response[0].ID, client.RequestOptions{}) + assert.NoError(t, err, "Error: Getting Delivery Service Servers #%d: %v - alerts: %+v", *resp.Response[0].ID, err, serverDSes.Alerts) + for _, dsID := range cgDsResp.DeliveryServices { + found := false + for _, serverDS := range serverDSes.Response { + if *serverDS.ID == dsID { + found = true + break + } + } + if !found { + t.Errorf("POST succeeded, but didn't assign delivery service %v to server", dsID) + } + } + } + } +} + +func CreateTestCachegroupsDeliveryServices(t *testing.T) { + dses, _, err := TOSession.GetDeliveryServices(client.RequestOptions{}) + assert.RequireNoError(t, err, "Cannot GET DeliveryServices: %v - %v", err, dses) + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", "cachegroup3") + clientCGs, _, err := TOSession.GetCacheGroups(opts) + assert.RequireNoError(t, err, "Cannot GET cachegroup: %v", err) + assert.RequireEqual(t, len(clientCGs.Response), 1, "Getting cachegroup expected 1, got %v", len(clientCGs.Response)) + assert.RequireNotNil(t, clientCGs.Response[0].ID, "Cachegroup has a nil ID") + + dsIDs := []int{} + for _, ds := range dses.Response { + if *ds.CDNName == "cdn1" && ds.Topology == nil { + dsIDs = append(dsIDs, *ds.ID) + } + } + assert.RequireGreaterOrEqual(t, len(dsIDs), 1, "No Delivery Services found in CDN 'cdn1', cannot continue.") + resp, _, err := TOSession.SetCacheGroupDeliveryServices(*clientCGs.Response[0].ID, dsIDs, client.RequestOptions{}) + assert.RequireNoError(t, err, "Setting cachegroup delivery services returned error: %v", err) + assert.RequireGreaterOrEqual(t, len(resp.Response.ServerNames), 1, "Setting cachegroup delivery services returned success, but no servers set") +} + +func setInactive(t *testing.T, dsID int) { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("id", strconv.Itoa(dsID)) + resp, _, err := TOSession.GetDeliveryServices(opts) + assert.RequireNoError(t, err, "Failed to fetch details for Delivery Service #%d: %v - alerts: %+v", dsID, err, resp.Alerts) + assert.RequireEqual(t, len(resp.Response), 1, "Expected exactly one Delivery Service to exist with ID %d, found: %d", dsID, len(resp.Response)) + + ds := resp.Response[0] + if ds.Active == nil { + t.Errorf("Deliver Service #%d had null or undefined 'active'", dsID) + ds.Active = new(bool) + } + if *ds.Active { + *ds.Active = false + _, _, err = TOSession.UpdateDeliveryService(dsID, ds, client.RequestOptions{}) + assert.NoError(t, err, "Failed to set Delivery Service #%d to inactive: %v", dsID, err) + } +} + +func DeleteTestCachegroupsDeliveryServices(t *testing.T) { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("limit", "1000000") + dss, _, err := TOSession.GetDeliveryServiceServers(opts) + assert.NoError(t, err, "Unexpected error retrieving server-to-Delivery-Service assignments: %v - alerts: %+v", err, dss.Alerts) + + for _, ds := range dss.Response { + setInactive(t, *ds.DeliveryService) + alerts, _, err := TOSession.DeleteDeliveryServiceServer(*ds.DeliveryService, *ds.Server, client.RequestOptions{}) + assert.NoError(t, err, "Error deleting delivery service servers: %v - alerts: %+v", err, alerts.Alerts) + } + + dss, _, err = TOSession.GetDeliveryServiceServers(client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error retrieving server-to-Delivery-Service assignments: %v - alerts: %+v", err, dss.Alerts) + assert.Equal(t, len(dss.Response), 0, "Deleting delivery service servers: Expected empty subsequent get, actual %v", len(dss.Response)) +} diff --git a/traffic_ops/testing/api/v5/cdn_dnsseckeys_test.go b/traffic_ops/testing/api/v5/cdn_dnsseckeys_test.go new file mode 100644 index 0000000000..daee11cb24 --- /dev/null +++ b/traffic_ops/testing/api/v5/cdn_dnsseckeys_test.go @@ -0,0 +1,158 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/http" + "reflect" + "strconv" + "strings" + "testing" + + tc "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/lib/go-util" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestCDNsDNSSEC(t *testing.T) { + if !includeSystemTests { + t.Skip() + } + WithObjs(t, []TCObj{CDNs, Types, Tenants, Users, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServerCapabilities, ServiceCategories, DeliveryServices}, func() { + t.Run("GENERATE DNSSEC KEYS", func(t *testing.T) { GenerateDNSSECKeys(t) }) + t.Run("REFRESH DNSSEC KEYS", func(t *testing.T) { RefreshDNSSECKeys(t) }) // NOTE: testing refresh last (while no keys exist) because it's asynchronous and might affect other tests + }) +} + +func RefreshDNSSECKeys(t *testing.T) { + resp, reqInf, err := TOSession.RefreshDNSSECKeys(client.RequestOptions{}) + assert.NoError(t, err, "Unable to refresh DNSSEC keys: %v - alerts: %+v", err, resp.Alerts) + assert.Equal(t, reqInf.StatusCode, http.StatusAccepted, "Refreshing DNSSEC keys - Expected: status code %d, Actual: %d", http.StatusAccepted, reqInf.StatusCode) + + loc := reqInf.RespHeaders.Get("Location") + if loc == "" { + t.Fatalf("Refreshing DNSSEC keys - Expected: non-empty 'Location' response header, Actual: empty") + } + locSplit := strings.Split(loc, "/") + assert.RequireGreaterOrEqual(t, len(locSplit), 5, "Expected 'Location' response header to split into at least 5 parts, Got: %v", len(locSplit)) + asyncID, err := strconv.Atoi(locSplit[4]) + assert.RequireNoError(t, err, "Parsing async_status ID from 'Location' response header - Expected: no error, Actual: %v", err) + + status, _, err := TOSession.GetAsyncStatus(asyncID, client.RequestOptions{}) + assert.NoError(t, err, "Getting async status id %d - Expected: no error, Actual: %v", asyncID, err) + assert.NotNil(t, status.Response.Message, "Getting async status for DNSSEC refresh job - Expected: non-nil message, Actual: nil") +} + +func GenerateDNSSECKeys(t *testing.T) { + assert.RequireGreaterOrEqual(t, len(testData.CDNs), 1, "Need at least one CDN to test updating CDNs") + firstCDN := testData.CDNs[0] + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", firstCDN.Name) + cdns, _, err := TOSession.GetCDNs(opts) + assert.RequireNoError(t, err, "Unexpected error getting CDNs filtered by name '%s': %v - alerts: %+v", firstCDN.Name, err, cdns.Alerts) + assert.RequireEqual(t, len(cdns.Response), 1, "Expected exactly one CDN named '%s' to exist, found: %d", firstCDN.Name, len(cdns.Response)) + + cdn := cdns.Response[0] + + ttl := util.JSONIntStr(60) + req := tc.CDNDNSSECGenerateReq{ + Key: util.StrPtr(firstCDN.Name), + TTL: &ttl, + KSKExpirationDays: &ttl, + ZSKExpirationDays: &ttl, + } + resp, _, err := TOSession.GenerateCDNDNSSECKeys(req, client.RequestOptions{}) + assert.RequireNoError(t, err, "Unexpected error generating CDN DNSSEC keys: %v - alerts: %+v", err, resp.Alerts) + + res, _, err := TOSession.GetCDNDNSSECKeys(firstCDN.Name, client.RequestOptions{}) + assert.RequireNoError(t, err, "Unexpected error getting CDN DNSSEC keys: %v - alerts: %+v", err, res.Alerts) + + if _, ok := res.Response[firstCDN.Name]; !ok { + t.Errorf("getting CDN DNSSEC keys - expected: key %s, actual: missing", firstCDN.Name) + } + originalKeys := res.Response + + resp, _, err = TOSession.GenerateCDNDNSSECKeys(req, client.RequestOptions{}) + assert.RequireNoError(t, err, "Unexpected error generating CDN DNSSEC keys: %v - alerts: %+v", err, resp.Alerts) + + res, _, err = TOSession.GetCDNDNSSECKeys(firstCDN.Name, client.RequestOptions{}) + assert.RequireNoError(t, err, "Unexpected error getting CDN DNSSEC keys: %v - alerts: %+v", err, res.Alerts) + + newKeys := res.Response + + if reflect.DeepEqual(originalKeys, newKeys) { + t.Errorf("Generating CDN DNSSEC keys - expected: original keys to differ from new keys, actual: they are the same") + } + + kskReq := tc.CDNGenerateKSKReq{ + ExpirationDays: util.Uint64Ptr(30), + } + originalKSK := newKeys + resp, _, err = TOSession.GenerateCDNDNSSECKSK(firstCDN.Name, kskReq, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error generating DNSSEC KSK: %v - alerts: %+v", err, resp.Alerts) + + res, _, err = TOSession.GetCDNDNSSECKeys(firstCDN.Name, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error getting CDN DNSSEC keys: %v - alerts: %+v", err, res.Alerts) + + if _, ok := res.Response[firstCDN.Name]; !ok { + t.Fatalf("getting CDN DNSSEC keys - expected: key %s, actual: missing", firstCDN.Name) + } + newKSK := res.Response + if reflect.DeepEqual(originalKSK[firstCDN.Name].KSK, newKSK[firstCDN.Name].KSK) { + t.Error("Generating CDN DNSSEC KSK - Expected: KSK to be different, Actual: KSK is the same") + } + if !reflect.DeepEqual(originalKSK[firstCDN.Name].ZSK, newKSK[firstCDN.Name].ZSK) { + t.Error("Generating CDN DNSSEC KSK - Expected: ZSK to be equal, Actual: ZSK is different") + } + + // ensure that when DNSSEC is enabled on a CDN, creating a new DS will generate DNSSEC keys for that DS: + if !cdn.DNSSECEnabled { + cdn.DNSSECEnabled = true + resp, _, err := TOSession.UpdateCDN(cdn.ID, cdn, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error updating CDN: %v - alerts: %+v", err, resp.Alerts) + + defer func() { + cdn.DNSSECEnabled = false + resp, _, err := TOSession.UpdateCDN(cdn.ID, cdn, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error updating CDN: %v - alerts: %+v", err, resp.Alerts) + }() + } + + opts.QueryParameters.Set("name", "HTTP") + types, _, err := TOSession.GetTypes(opts) + assert.RequireNoError(t, err, "Unexpected error getting Types filtered by name 'HTTP': %v - alerts: %+v", err, types.Alerts) + assert.RequireEqual(t, len(types.Response), 1, "Expected exactly one Type to exist with name 'HTTP', found: %d", len(types.Response)) + + dsXMLID := "testdnssecgen" + customDS := getCustomDS(cdn.ID, types.Response[0].ID, dsXMLID, "cdn", "https://testdnssecgen.example.com", dsXMLID) + ds, _, err := TOSession.CreateDeliveryService(customDS, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error creating Delivery Service: %v - alerts: %+v", err, ds.Alerts) + assert.RequireEqual(t, len(ds.Response), 1, "Expected creating a Delivery Service to create exactly one Delivery Service, Traffic Ops returned: %d", len(ds.Response)) + assert.RequireNotNil(t, ds.Response[0].ID, nil, "Traffic Ops returned a representation for a created Delivery Service with null or undefined ID") + + res, _, err = TOSession.GetCDNDNSSECKeys(firstCDN.Name, client.RequestOptions{}) + assert.RequireNoError(t, err, "Unexpected error getting CDN DNSSEC keys: %v - alerts: %+v", err, res.Alerts) + + if _, ok := res.Response[dsXMLID]; !ok { + t.Error("after creating a new delivery service for a DNSSEC-enabled CDN - expected: DNSSEC keys to be found for the delivery service, actual: no DNSSEC keys found for the delivery service") + } + alerts, _, err := TOSession.DeleteDeliveryService(*ds.Response[0].ID, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error deleting Delivery Service: %v - alerts: %+v", err, alerts.Alerts) + + delResp, _, err := TOSession.DeleteCDNDNSSECKeys(firstCDN.Name, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error deleting CDN DNSSEC keys: %v - alerts: %+v", err, delResp.Alerts) +} diff --git a/traffic_ops/testing/api/v5/cdn_domains_test.go b/traffic_ops/testing/api/v5/cdn_domains_test.go new file mode 100644 index 0000000000..632695f891 --- /dev/null +++ b/traffic_ops/testing/api/v5/cdn_domains_test.go @@ -0,0 +1,62 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/http" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestCDNDomains(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Parameters, Profiles, Statuses}, func() { + + tomorrow := time.Now().AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID request": { + ClientSession: TOSession, Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + utils.ResponseLengthGreaterOrEqual(1)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetDomains(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + } + } + }) + } + }) +} diff --git a/traffic_ops/testing/api/v5/cdn_federations_test.go b/traffic_ops/testing/api/v5/cdn_federations_test.go new file mode 100644 index 0000000000..7973af3c85 --- /dev/null +++ b/traffic_ops/testing/api/v5/cdn_federations_test.go @@ -0,0 +1,301 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "sort" + "strconv" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +var fedIDs = make(map[string]int) + +// All prerequisite Federations are associated to this cdn and this xmlID +var cdnName = "cdn1" +var xmlId = "ds1" + +func TestCDNFederations(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Parameters, Profiles, Tenants, CacheGroups, Statuses, Divisions, Regions, PhysLocations, Servers, Topologies, ServiceCategories, DeliveryServices, CDNFederations}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID ID parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"id": {strconv.Itoa(GetFederationID(t, "the.cname.com.")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1)), + }, + "SORTED by CNAME when ORDERBY=CNAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"cname"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateCDNFederationCNameSort()), + }, + "SORTED when ORDERBY=ID and SORTORDER=DESC": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "sortOrder": {"desc"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateCDNFederationIDDescSort()), + }, + "FIRST RESULT when LIMIT=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateCDNFederationPagination("limit")), + }, + "SECOND RESULT when LIMIT=1 OFFSET=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "offset": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateCDNFederationPagination("offset")), + }, + "SECOND RESULT when LIMIT=1 PAGE=2": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "page": {"2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateCDNFederationPagination("page")), + }, + "BAD REQUEST when INVALID LIMIT parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"-2"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID OFFSET parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "offset": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID PAGE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "page": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "PUT": { + "OK when VALID request": { + EndpointId: GetFederationID(t, "google.com."), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "cname": "new.cname.", + "ttl": 34, + "description": "updated", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateCDNFederationUpdateFields(map[string]interface{}{"CName": "new.cname."})), + }, + "PRECONDITION FAILED when updating with IMS & IUS Headers": { + EndpointId: GetFederationID(t, "booya.com."), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}}, + RequestBody: map[string]interface{}{ + "cname": "booya.com.", + "ttl": 34, + "description": "fooya", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + "PRECONDITION FAILED when updating with IFMATCH ETAG Header": { + EndpointId: GetFederationID(t, "booya.com."), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "cname": "new.cname.", + "ttl": 34, + "description": "updated", + }, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + }, + "GET AFTER CHANGES": { + "OK when CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {currentTimeRFC}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + cdnFederation := tc.CDNFederation{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &cdnFederation) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET", "GET AFTER CHANGES": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetCDNFederationsByName(cdnName, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.CreateCDNFederation(cdnFederation, cdnName, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.UpdateCDNFederation(cdnFederation, cdnName, testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.DeleteCDNFederation(cdnName, testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts.Alerts, err) + } + }) + } + } + }) + } + }) +} + +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) + 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) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } +} + +func validateCDNFederationPagination(paginationParam string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + paginationResp := resp.([]tc.CDNFederation) + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("orderby", "id") + respBase, _, err := TOSession.GetCDNFederationsByName(cdnName, opts) + assert.RequireNoError(t, err, "Cannot get Federation Users: %v - alerts: %+v", err, respBase.Alerts) + + CDNfederations := respBase.Response + assert.RequireGreaterOrEqual(t, len(CDNfederations), 3, "Need at least 3 CDN Federations in Traffic Ops to test pagination support, found: %d", len(CDNfederations)) + switch paginationParam { + case "limit:": + assert.Exactly(t, CDNfederations[:1], paginationResp, "expected GET CDN Federations with limit = 1 to return first result") + case "offset": + assert.Exactly(t, CDNfederations[1:2], paginationResp, "expected GET CDN Federations with limit = 1, offset = 1 to return second result") + case "page": + assert.Exactly(t, CDNfederations[1:2], paginationResp, "expected GET CDN Federations with limit = 1, page = 2 to return second result") + } + } +} + +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) + } + assert.Equal(t, true, sort.StringsAreSorted(federationCNames), "List is not sorted by their names: %v", federationCNames) + } +} + +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) + for _, federation := range CDNFederationResp { + CDNFederationIDs = append([]int{*federation.ID}, CDNFederationIDs...) + } + assert.Equal(t, true, sort.IntsAreSorted(CDNFederationIDs), "List is not sorted by their ids: %v", CDNFederationIDs) + } +} + +func GetFederationID(t *testing.T, cname string) func() int { + return func() int { + ID, ok := fedIDs[cname] + assert.RequireEqual(t, true, ok, "Expected to find Federation CName: %s to have associated ID", cname) + return ID + } +} + +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 CreateTestCDNFederations(t *testing.T) { + for _, federation := range testData.Federations { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("xmlId", *federation.DeliveryServiceIDs.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)) + assert.RequireNotNil(t, dsResp.Response[0].CDNName, "Expected Delivery Service CDN Name to not be nil.") + + resp, _, err := TOSession.CreateCDNFederation(federation, *dsResp.Response[0].CDNName, client.RequestOptions{}) + assert.NoError(t, err, "Could not create CDN Federations: %v - alerts: %+v", err, resp.Alerts) + + // Need to save the ids, otherwise the other tests won't be able to reference the federations + 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()) + assert.NoError(t, err, "Could not create Federation Delivery Service: %v", err) + } +} + +func DeleteTestCDNFederations(t *testing.T) { + opts := client.NewRequestOptions() + for _, id := range fedIDs { + resp, _, err := TOSession.DeleteCDNFederation(cdnName, id, opts) + 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) + 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/cdn_locks_test.go b/traffic_ops/testing/api/v5/cdn_locks_test.go new file mode 100644 index 0000000000..44aa9a5d69 --- /dev/null +++ b/traffic_ops/testing/api/v5/cdn_locks_test.go @@ -0,0 +1,726 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "strconv" + "testing" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestCDNLocks(t *testing.T) { + WithObjs(t, []TCObj{Types, CacheGroups, CDNs, Parameters, Profiles, ProfileParameters, Statuses, Divisions, Regions, PhysLocations, Servers, ServiceCategories, Topologies, Tenants, Roles, Users, DeliveryServices, StaticDNSEntries, CDNLocks}, func() { + + opsUserSession := utils.CreateV5Session(t, Config.TrafficOps.URL, "opsuser", "pa$$word", Config.Default.Session.TimeoutInSecs) + opsUserWithLockSession := utils.CreateV5Session(t, Config.TrafficOps.URL, "opslockuser", "pa$$word", Config.Default.Session.TimeoutInSecs) + + methodTests := utils.V5TestCase{ + "GET": { + "OK when VALID request": { + ClientSession: TOSession, Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + utils.ResponseLengthGreaterOrEqual(1)), + }, + "OK when VALID CDN parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + validateGetResponseFields(map[string]interface{}{"username": "opslockuser", "cdn": "cdn2", "message": "test lock for updates", "soft": false})), + }, + }, + "POST": { + "CREATED when VALID request": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "cdn": "cdn3", + "message": "snapping cdn", + "soft": true, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusCreated), + validateCreateResponseFields(map[string]interface{}{"username": "admin", "cdn": "cdn3", "message": "snapping cdn", "soft": true})), + }, + "NOT CREATED when INVALID shared username": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "cdn": "bar", + "message": "snapping cdn", + "soft": true, + "sharedUserNames": []string{"adminuser2"}, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "CREATED when VALID shared username": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "cdn": "bar", + "message": "snapping cdn", + "soft": true, + "sharedUserNames": []string{"adminuser"}, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusCreated)), + }, + }, + "DELETE": { + "OK when VALID request": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "FORBIDDEN when NON-ADMIN USER DOESNT OWN LOCK": { + ClientSession: opsUserSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn4"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + "OK when ADMIN USER DOESNT OWN LOCK": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn4"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + "SNAPSHOT": { + "OK when USER OWNS LOCK": { + ClientSession: opsUserWithLockSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "FORBIDDEN when ADMIN USER DOESNT OWN LOCK": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn2"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + "SERVERS QUEUE UPDATES": { + "OK when USER OWNS LOCK": { + EndpointId: GetServerID(t, "cdn2-test-edge"), ClientSession: opsUserWithLockSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "FORBIDDEN when ADMIN USER DOESNT OWN LOCK": { + EndpointId: GetServerID(t, "cdn2-test-edge"), ClientSession: TOSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + "TOPOLOGY QUEUE UPDATES": { + "OK when USER OWNS LOCK": { + ClientSession: opsUserWithLockSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"topology": {"top-for-ds-req"}}}, + RequestBody: map[string]interface{}{ + "action": "queue", + "cdnId": GetCDNID(t, "cdn2")(), + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "FORBIDDEN when ADMIN USER DOESNT OWN LOCK": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"topology": {"top-for-ds-req"}}}, + RequestBody: map[string]interface{}{ + "action": "queue", + "cdnId": GetCDNID(t, "cdn2")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + "OK when ADMIN USER DOESNT OWN LOCK FOR DEQUEUE": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"topology": {"top-for-ds-req"}}}, + RequestBody: map[string]interface{}{ + "action": "dequeue", + "cdnId": GetCDNID(t, "cdn2")(), + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + "CDN UPDATE": { + "OK when USER OWNS LOCK": { + EndpointId: GetCDNID(t, "cdn2"), ClientSession: opsUserWithLockSession, + RequestBody: map[string]interface{}{ + "dnssecEnabled": false, + "domainName": "newdomain", + "name": "cdn2", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "FORBIDDEN when ADMIN USER DOESNT OWN LOCK": { + EndpointId: GetCDNID(t, "cdn2"), ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "dnssecEnabled": false, + "domainName": "newdomaintest", + "name": "cdn2", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + "CDN DELETE": { + "OK when USER OWNS LOCK": { + EndpointId: GetCDNID(t, "cdndelete"), ClientSession: opsUserWithLockSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "FORBIDDEN when ADMIN USER DOESNT OWN LOCK": { + EndpointId: GetCDNID(t, "cdn2"), ClientSession: TOSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + "CACHE GROUP UPDATE": { + "OK when USER OWNS LOCK": { + EndpointId: GetCacheGroupId(t, "cachegroup1"), ClientSession: opsUserWithLockSession, + RequestBody: map[string]interface{}{ + "name": "cachegroup1", + "shortName": "newShortName", + "typeName": "EDGE_LOC", + "typeId": GetTypeId(t, "EDGE_LOC"), + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "FORBIDDEN when ADMIN USER DOESNT OWN LOCK": { + EndpointId: GetCacheGroupId(t, "cachegroup1"), ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "cachegroup1", + "shortName": "newShortName", + "typeName": "EDGE_LOC", + "typeId": GetTypeId(t, "EDGE_LOC"), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + "DELIVERY SERVICE POST": { + "OK when USER OWNS LOCK": { + ClientSession: opsUserWithLockSession, RequestBody: generateDeliveryService(t, map[string]interface{}{"xmlId": "testDSLock"}), + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusCreated)), + }, + "FORBIDDEN when ADMIN USER DOESNT OWN LOCK": { + ClientSession: TOSession, RequestBody: generateDeliveryService(t, map[string]interface{}{ + "xmlId": "testDSLock2", "cdnId": GetCDNID(t, "cdn2")()}), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + "DELIVERY SERVICE PUT": { + "OK when USER OWNS LOCK": { + EndpointId: GetDeliveryServiceId(t, "basic-ds-in-cdn2"), ClientSession: opsUserWithLockSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "xmlId": "basic-ds-in-cdn2", "cdnId": GetCDNID(t, "cdn2")(), "cdnName": "cdn2", "routingName": "cdn"}), + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "FORBIDDEN when ADMIN USER DOESNT OWN LOCK": { + EndpointId: GetDeliveryServiceId(t, "basic-ds-in-cdn2"), ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{}), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + "DELIVERY SERVICE DELETE": { + "OK when USER OWNS LOCK": { + EndpointId: GetDeliveryServiceId(t, "ds-forked-topology"), ClientSession: opsUserWithLockSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "FORBIDDEN when ADMIN USER DOESNT OWN LOCK": { + EndpointId: GetDeliveryServiceId(t, "top-ds-in-cdn2"), ClientSession: TOSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + "PROFILE POST": { + "OK when USER OWNS LOCK": { + ClientSession: opsUserWithLockSession, + RequestBody: map[string]interface{}{ + "cdn": GetCDNID(t, "cdn2")(), + "cdnName": "cdn2", + "description": "test cdn locks description", + "name": "TestLocks", + "routing_disabled": false, + "type": "ATS_PROFILE", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "FORBIDDEN when ADMIN USER DOESNT OWN LOCK": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "cdn": GetCDNID(t, "cdn2")(), + "cdnName": "cdn2", + "description": "test cdn locks description", + "name": "TestLocksForbidden", + "routing_disabled": false, + "type": "ATS_PROFILE", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + "PROFILE PUT": { + "OK when USER OWNS LOCK": { + EndpointId: GetProfileID(t, "CDN2_EDGE"), + ClientSession: opsUserWithLockSession, + RequestBody: map[string]interface{}{ + "cdn": GetCDNID(t, "cdn2")(), + "cdnName": "cdn2", + "description": "cdn2 edge description updated when user owns lock", + "name": "CDN2_EDGE", + "routing_disabled": false, + "type": "ATS_PROFILE", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "FORBIDDEN when ADMIN USER DOESNT OWN LOCK": { + EndpointId: GetProfileID(t, "EDGEInCDN2"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "cdn": GetCDNID(t, "cdn2")(), + "cdnName": "cdn2", + "description": "should fail", + "name": "EDGEInCDN2", + "routing_disabled": false, + "type": "ATS_PROFILE", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + "PROFILE DELETE": { + "OK when USER OWNS LOCK": { + EndpointId: GetProfileID(t, "CCR2"), + ClientSession: opsUserWithLockSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "FORBIDDEN when ADMIN USER DOESNT OWN LOCK": { + EndpointId: GetProfileID(t, "MID2"), + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + "PROFILE PARAMETER POST": { + "OK when USER OWNS LOCK": { + ClientSession: opsUserWithLockSession, + RequestBody: map[string]interface{}{ + "profileId": GetProfileID(t, "EDGEInCDN2")(), + "parameterId": GetParameterID(t, "CONFIG proxy.config.admin.user_id", "records.config", "STRING ats")(), + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "FORBIDDEN when ADMIN USER DOESNT OWN LOCK": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "profileId": GetProfileID(t, "EDGEInCDN2")(), + "parameterId": GetParameterID(t, "CONFIG proxy.config.admin.user_id", "records.config", "STRING ats")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + "PROFILE PARAMETER DELETE": { + "OK when USER OWNS LOCK": { + EndpointId: GetProfileID(t, "OKwhenUserOwnLocks"), + ClientSession: opsUserWithLockSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{ + "parameterId": {strconv.Itoa(GetParameterID(t, "test.cdnlock.delete", "rascal.properties", "25.0")())}, + }}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "FORBIDDEN when ADMIN USER DOESNT OWN LOCK": { + EndpointId: GetProfileID(t, "FORBIDDENwhenDoesntOwnLock"), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{ + "parameterId": {strconv.Itoa(GetParameterID(t, "test.cdnlock.forbidden.delete", "rascal.properties", "25.0")())}, + }}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + "SERVER POST": { + "OK when USER OWNS LOCK": { + ClientSession: opsUserWithLockSession, + RequestBody: generateServer(t, map[string]interface{}{ + "cdnId": GetCDNID(t, "cdn2")(), + "profileNames": []string{"EDGEInCDN2"}, + }), + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusCreated)), + }, + "FORBIDDEN when ADMIN USER DOESNT OWN LOCK": { + ClientSession: TOSession, + RequestBody: generateServer(t, map[string]interface{}{ + "cdnId": GetCDNID(t, "cdn2")(), + "profileNames": []string{"EDGEInCDN2"}, + "interfaces": []map[string]interface{}{{ + "ipAddresses": []map[string]interface{}{{ + "address": "127.0.0.2/30", + "serviceAddress": true, + }}, + "name": "eth0", + }}, + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + "SERVER PUT": { + "OK when USER OWNS LOCK": { + EndpointId: GetServerID(t, "edge1-cdn2"), + ClientSession: opsUserWithLockSession, + RequestBody: generateServer(t, map[string]interface{}{ + "id": GetServerID(t, "edge1-cdn2")(), + "cdnId": GetCDNID(t, "cdn2")(), + "profileNames": []string{"EDGEInCDN2"}, + "interfaces": []map[string]interface{}{{ + "ipAddresses": []map[string]interface{}{{ + "address": "0.0.0.1", + "serviceAddress": true, + }}, + "name": "eth0", + }}, + }), + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "FORBIDDEN when ADMIN USER DOESNT OWN LOCK": { + EndpointId: GetServerID(t, "dtrc-edge-07"), + ClientSession: TOSession, + RequestBody: generateServer(t, map[string]interface{}{ + "id": GetServerID(t, "dtrc-edge-07")(), + "cdnId": GetCDNID(t, "cdn2")(), + "cachegroupId": GetCacheGroupId(t, "dtrc2")(), + "profileNames": []string{"CDN2_EDGE"}, + "interfaces": []map[string]interface{}{{ + "ipAddresses": []map[string]interface{}{{ + "address": "192.0.2.11/24", + "serviceAddress": true, + }}, + "name": "eth0", + }}, + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + "SERVER DELETE": { + "OK when USER OWNS LOCK": { + EndpointId: GetServerID(t, "atlanta-mid-17"), + ClientSession: opsUserWithLockSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "FORBIDDEN when ADMIN USER DOESNT OWN LOCK": { + EndpointId: GetServerID(t, "denver-mso-org-02"), + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + "STATIC DNS ENTRIES POST": { + "OK when USER OWNS LOCK": { + ClientSession: opsUserWithLockSession, + RequestBody: map[string]interface{}{ + "address": "192.168.0.1", + "cachegroup": "cachegroup1", + "deliveryservice": "basic-ds-in-cdn2", + "host": "cdn_locks_test_host", + "type": "A_RECORD", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "FORBIDDEN when ADMIN USER DOESNT OWN LOCK": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "address": "192.168.0.1", + "cachegroup": "cachegroup1", + "deliveryservice": "basic-ds-in-cdn2", + "host": "cdn_locks_test_host", + "type": "A_RECORD", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + "STATIC DNS ENTRIES PUT": { + "OK when USER OWNS LOCK": { + EndpointId: GetStaticDNSEntryID(t, "host2"), + ClientSession: opsUserWithLockSession, + RequestBody: map[string]interface{}{ + "address": "192.168.0.2", + "cachegroup": "cachegroup2", + "deliveryservice": "basic-ds-in-cdn2", + "host": "host2", + "type": "A_RECORD", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "FORBIDDEN when ADMIN USER DOESNT OWN LOCK": { + EndpointId: GetStaticDNSEntryID(t, "cdnlock-test-delete-host"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "address": "192.168.0.2", + "cachegroup": "cachegroup2", + "deliveryservice": "basic-ds-in-cdn2", + "host": "host2", + "type": "A_RECORD", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + "STATIC DNS ENTRIES DELETE": { + "OK when USER OWNS LOCK": { + EndpointId: GetStaticDNSEntryID(t, "host3"), + ClientSession: opsUserWithLockSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "FORBIDDEN when ADMIN USER DOESNT OWN LOCK": { + EndpointId: GetStaticDNSEntryID(t, "cdnlock-negtest-delete-host"), + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + var dat []byte + var err error + + if testCase.RequestBody != nil { + dat, err = json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + } + + cases := map[string]func(*testing.T){ + "GET": func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetCDNLocks(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }, + "POST": func(t *testing.T) { + cdnLock := tc.CDNLock{} + err = json.Unmarshal(dat, &cdnLock) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + resp, reqInf, err := testCase.ClientSession.CreateCDNLock(cdnLock, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }, + "DELETE": func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.DeleteCDNLocks(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }, + "SNAPSHOT": func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.SnapshotCRConfig(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }, + "SERVERS QUEUE UPDATES": func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.SetServerQueueUpdate(testCase.EndpointId(), true, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }, + "TOPOLOGY QUEUE UPDATES": func(t *testing.T) { + topology := testCase.RequestOpts.QueryParameters.Get("topology") + topQueueUp := tc.TopologiesQueueUpdateRequest{} + err = json.Unmarshal(dat, &topQueueUp) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + resp, reqInf, err := testCase.ClientSession.TopologiesQueueUpdate(topology, topQueueUp, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, resp.Alerts, err) + } + }, + "CACHE GROUP UPDATE": func(t *testing.T) { + cacheGroup := tc.CacheGroupNullable{} + err = json.Unmarshal(dat, &cacheGroup) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + resp, reqInf, err := testCase.ClientSession.UpdateCacheGroup(testCase.EndpointId(), cacheGroup, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, resp.Alerts, err) + } + }, + "CDN UPDATE": func(t *testing.T) { + cdn := tc.CDN{} + err = json.Unmarshal(dat, &cdn) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + alerts, reqInf, err := testCase.ClientSession.UpdateCDN(testCase.EndpointId(), cdn, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }, + "CDN DELETE": func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.DeleteCDN(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }, + "DELIVERY SERVICE POST": func(t *testing.T) { + ds := tc.DeliveryServiceV4{} + err = json.Unmarshal(dat, &ds) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + resp, reqInf, err := testCase.ClientSession.CreateDeliveryService(ds, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, resp.Alerts, err) + } + }, + "DELIVERY SERVICE PUT": func(t *testing.T) { + ds := tc.DeliveryServiceV4{} + err = json.Unmarshal(dat, &ds) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + resp, reqInf, err := testCase.ClientSession.UpdateDeliveryService(testCase.EndpointId(), ds, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, resp.Alerts, err) + } + }, + "DELIVERY SERVICE DELETE": func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.DeleteDeliveryService(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, resp.Alerts, err) + } + }, + "PROFILE POST": func(t *testing.T) { + profile := tc.Profile{} + err = json.Unmarshal(dat, &profile) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + alerts, reqInf, err := testCase.ClientSession.CreateProfile(profile, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }, + "PROFILE PUT": func(t *testing.T) { + profile := tc.Profile{} + err = json.Unmarshal(dat, &profile) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + alerts, reqInf, err := testCase.ClientSession.UpdateProfile(testCase.EndpointId(), profile, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }, + "PROFILE DELETE": func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.DeleteProfile(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }, + "PROFILE PARAMETER POST": func(t *testing.T) { + profileParameter := tc.ProfileParameterCreationRequest{} + err = json.Unmarshal(dat, &profileParameter) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + alerts, reqInf, err := testCase.ClientSession.CreateProfileParameter(profileParameter, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }, + "PROFILE PARAMETER DELETE": func(t *testing.T) { + parameterId, _ := strconv.Atoi(testCase.RequestOpts.QueryParameters["parameterId"][0]) + alerts, reqInf, err := testCase.ClientSession.DeleteProfileParameter(testCase.EndpointId(), parameterId, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }, + "SERVER POST": func(t *testing.T) { + server := tc.ServerV4{} + err = json.Unmarshal(dat, &server) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + alerts, reqInf, err := testCase.ClientSession.CreateServer(server, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }, + "SERVER PUT": func(t *testing.T) { + server := tc.ServerV4{} + err = json.Unmarshal(dat, &server) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + alerts, reqInf, err := testCase.ClientSession.UpdateServer(testCase.EndpointId(), server, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }, + "SERVER DELETE": func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.DeleteServer(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }, + "STATIC DNS ENTRIES POST": func(t *testing.T) { + staticDNSEntry := tc.StaticDNSEntry{} + err = json.Unmarshal(dat, &staticDNSEntry) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + alerts, reqInf, err := testCase.ClientSession.CreateStaticDNSEntry(staticDNSEntry, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }, + "STATIC DNS ENTRIES PUT": func(t *testing.T) { + staticDNSEntry := tc.StaticDNSEntry{} + err = json.Unmarshal(dat, &staticDNSEntry) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + alerts, reqInf, err := testCase.ClientSession.UpdateStaticDNSEntry(testCase.EndpointId(), staticDNSEntry, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }, + "STATIC DNS ENTRIES DELETE": func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.DeleteStaticDNSEntry(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }, + } + + if _, ok := cases[method]; ok { + t.Run(name, cases[method]) + } else { + t.Errorf("Test Case: %s not found. Test: %s failed to run.", method, name) + } + } + }) + } + }) +} + +func validateGetResponseFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + cdnLockResp := resp.([]tc.CDNLock) + assert.Equal(t, expectedResp["username"], cdnLockResp[0].UserName, "Expected username: %v Got: %v", expectedResp["username"], cdnLockResp[0].UserName) + assert.Equal(t, expectedResp["cdn"], cdnLockResp[0].CDN, "Expected CDN: %v Got: %v", expectedResp["cdn"], cdnLockResp[0].CDN) + assert.Equal(t, expectedResp["message"], *cdnLockResp[0].Message, "Expected Message %v Got: %v", expectedResp["message"], *cdnLockResp[0].Message) + assert.Equal(t, expectedResp["soft"], *cdnLockResp[0].Soft, "Expected 'Soft' to be: %v Got: %v", expectedResp["soft"], *cdnLockResp[0].Soft) + } +} + +func validateCreateResponseFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + cdnLockResp := resp.(tc.CDNLock) + assert.Equal(t, expectedResp["username"], cdnLockResp.UserName, "Expected username: %v Got: %v", expectedResp["username"], cdnLockResp.UserName) + assert.Equal(t, expectedResp["cdn"], cdnLockResp.CDN, "Expected CDN: %v Got: %v", expectedResp["cdn"], cdnLockResp.CDN) + assert.Equal(t, expectedResp["message"], *cdnLockResp.Message, "Expected Message %v Got: %v", expectedResp["message"], *cdnLockResp.Message) + assert.Equal(t, expectedResp["soft"], *cdnLockResp.Soft, "Expected 'Soft' to be: %v Got: %v", expectedResp["soft"], *cdnLockResp.Soft) + } +} + +func CreateTestCDNLocks(t *testing.T) { + for _, cl := range testData.CDNLocks { + ClientSession := TOSession + if cl.UserName != "" { + for _, user := range testData.Users { + if user.Username == cl.UserName { + ClientSession = utils.CreateV5Session(t, Config.TrafficOps.URL, user.Username, *user.LocalPassword, Config.Default.Session.TimeoutInSecs) + } + } + } + resp, _, err := ClientSession.CreateCDNLock(cl, client.RequestOptions{}) + assert.NoError(t, err, "Could not create CDN Lock: %v - alerts: %+v", err, resp.Alerts) + } +} + +func DeleteTestCDNLocks(t *testing.T) { + opts := client.NewRequestOptions() + cdnlocks, _, err := TOSession.GetCDNLocks(opts) + assert.NoError(t, err, "Error retrieving CDN Locks for deletion: %v - alerts: %+v", err, cdnlocks.Alerts) + assert.GreaterOrEqual(t, len(cdnlocks.Response), 1, "Expected at least one CDN Lock for deletion") + for _, cl := range cdnlocks.Response { + opts.QueryParameters.Set("cdn", cl.CDN) + resp, _, err := TOSession.DeleteCDNLocks(opts) + assert.NoError(t, err, "Could not delete CDN Lock: %v - alerts: %+v", err, resp.Alerts) + // Retrieve the CDN Lock to see if it got deleted + cdnlock, _, err := TOSession.GetCDNLocks(opts) + assert.NoError(t, err, "Error deleting CDN Lock for '%s' : %v - alerts: %+v", cl.CDN, err, cdnlock.Alerts) + assert.Equal(t, 0, len(cdnlock.Response), "Expected CDN Lock for '%s' to be deleted", cl.CDN) + } +} diff --git a/traffic_ops/testing/api/v5/cdn_queue_updates_test.go b/traffic_ops/testing/api/v5/cdn_queue_updates_test.go new file mode 100644 index 0000000000..a2722806c8 --- /dev/null +++ b/traffic_ops/testing/api/v5/cdn_queue_updates_test.go @@ -0,0 +1,130 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/http" + "net/url" + "strconv" + "testing" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestCDNQueueUpdates(t *testing.T) { + WithObjs(t, []TCObj{Types, CDNs, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers}, func() { + + methodTests := utils.V5TestCase{ + "POST": { + "OK when VALID TYPE parameter": { + EndpointId: GetCDNID(t, "cdn1"), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"type": {"EDGE"}}}, + RequestBody: map[string]interface{}{ + "action": "queue", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateServersUpdatePending(GetCDNID(t, "cdn1")(), map[string]string{"type": "EDGE"})), + }, + "OK when VALID PROFILE parameter": { + EndpointId: GetCDNID(t, "cdn1"), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"profile": {"EDGE1"}}}, + RequestBody: map[string]interface{}{ + "action": "queue", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateServersUpdatePending(GetCDNID(t, "cdn1")(), map[string]string{"profileName": "EDGE1"})), + }, + }, + } + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + + var queueUpdate bool + if testCase.RequestBody != nil { + if action, ok := testCase.RequestBody["action"]; ok { + if action == "queue" { + queueUpdate = true + } else if action == "dequeue" { + queueUpdate = false + } else { + t.Errorf("Not a valid action: %v", action) + } + } + } + + switch method { + case "POST": + t.Run(name, func(t *testing.T) { + // Clear updates on all associated cdn servers to begin with + _, _, err := TOSession.QueueUpdatesForCDN(testCase.EndpointId(), false, client.RequestOptions{}) + assert.RequireNoError(t, err, "Failed to clear updates for cdn %d", testCase.EndpointId()) + resp, reqInf, err := testCase.ClientSession.QueueUpdatesForCDN(testCase.EndpointId(), queueUpdate, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp, tc.Alerts{}, err) + } + }) + } + } + }) + } + }) +} + +func validateServersUpdatePending(cdnID int, params map[string]string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, _ interface{}, _ tc.Alerts, _ error) { + // Get all the servers for the same CDN and type as that of the first server + serverIDMap := make(map[int]bool, 0) + opts := client.NewRequestOptions() + opts.QueryParameters.Set("cdn", strconv.Itoa(cdnID)) + for k, v := range params { + opts.QueryParameters.Set(k, v) + } + + servers, _, err := TOSession.GetServers(opts) + assert.RequireNoError(t, err, "Couldn't get servers by cdn and parameters: %v", err) + assert.RequireGreaterOrEqual(t, len(servers.Response), 1, "expected atleast one server in response, got %d", len(servers.Response)) + + for _, server := range servers.Response { + assert.RequireNotNil(t, server.HostName, "Expected server hostname to not be nil.") + assert.RequireNotNil(t, server.UpdPending, "Expected Update Pending field for server %s to not be nil.", *server.HostName) + assert.Equal(t, true, *server.UpdPending, "Expected updates to be queued on all the servers filtered by CDN and parameter, but %s didn't queue updates", *server.HostName) + if server.ID != nil { + serverIDMap[*server.ID] = true + } + } + + // Make sure that the servers that are not filtered by the above criteria do not have updates queued + allServersResp, _, err := TOSession.GetServers(client.NewRequestOptions()) + assert.RequireNoError(t, err, "Couldn't get all servers: %v", err) + + for _, server := range allServersResp.Response { + if server.ID != nil { + if _, ok := serverIDMap[*server.ID]; !ok { + assert.RequireNotNil(t, server.HostName, "Expected server hostname to not be nil.") + assert.RequireNotNil(t, server.UpdPending, "Expected Update Pending field for server %s to not be nil.", *server.HostName) + assert.Equal(t, false, *server.UpdPending, "Did not expect server %s to have queued updates", *server.HostName) + } + } + } + } +} diff --git a/traffic_ops/testing/api/v5/cdnnotifications_test.go b/traffic_ops/testing/api/v5/cdnnotifications_test.go new file mode 100644 index 0000000000..b735059bba --- /dev/null +++ b/traffic_ops/testing/api/v5/cdnnotifications_test.go @@ -0,0 +1,100 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/http" + "net/url" + "strconv" + "testing" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestCDNNotifications(t *testing.T) { + WithObjs(t, []TCObj{CDNs, CDNNotifications}, func() { + methodTests := utils.V5TestCase{ + "GET": { + "OK when VALID request": { + ClientSession: TOSession, Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + utils.ResponseLengthGreaterOrEqual(1)), + }, + "OK when VALID CDN parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + validateCDNNotificationFields(map[string]interface{}{"Notification": "test notification: cdn2"})), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetCDNNotifications(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateCDNNotificationFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + notifications := resp.([]tc.CDNNotification) + for field, expected := range expectedResp { + for _, notification := range notifications { + switch field { + case "Notification": + assert.Equal(t, expected, notification.Notification, "Expected Notification to be %v, but got %v", expected, notification.Notification) + } + } + } + } +} + +func CreateTestCDNNotifications(t *testing.T) { + var opts client.RequestOptions + for _, cdn := range testData.CDNs { + resp, _, err := TOSession.CreateCDNNotification(tc.CDNNotificationRequest{CDN: cdn.Name, Notification: "test notification: " + cdn.Name}, opts) + assert.NoError(t, err, "Cannot create CDN Notification for CDN '%s': %v - alerts: %+v", cdn.Name, err, resp.Alerts) + } +} + +func DeleteTestCDNNotifications(t *testing.T) { + resp, _, err := TOSession.GetCDNNotifications(client.RequestOptions{}) + assert.NoError(t, err, "Cannot get notifications for CDNs: %v - alerts: %+v", err, resp.Alerts) + for _, notification := range resp.Response { + delResp, _, err := TOSession.DeleteCDNNotification(notification.ID, client.RequestOptions{}) + assert.NoError(t, err, "Cannot delete CDN notification #%d: %v - alerts: %+v", notification.ID, err, delResp.Alerts) + // Retrieve CDN Notification to see if it got deleted + opts := client.NewRequestOptions() + opts.QueryParameters.Set("id", strconv.Itoa(notification.ID)) + getNotification, _, err := TOSession.GetCDNNotifications(opts) + assert.NoError(t, err, "Error deleting CDN Notification for '%s' : %v - alerts: %+v", notification.ID, err, getNotification.Alerts) + assert.Equal(t, 0, len(getNotification.Response), "Expected CDN Notification '%s' to be deleted", notification.ID) + } +} diff --git a/traffic_ops/testing/api/v5/cdns_test.go b/traffic_ops/testing/api/v5/cdns_test.go new file mode 100644 index 0000000000..2182c7d414 --- /dev/null +++ b/traffic_ops/testing/api/v5/cdns_test.go @@ -0,0 +1,369 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "sort" + "strconv" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + tc "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestCDNs(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Parameters, Tenants, Users}, func() { + + readOnlyUserSession := utils.CreateV5Session(t, Config.TrafficOps.URL, "readonlyuser", "pa$$word", Config.Default.Session.TimeoutInSecs) + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID request": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), validateCDNSort()), + }, + "OK when VALID NAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"cdn1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + validateCDNFields(map[string]interface{}{"Name": "cdn1"})), + }, + "OK when VALID DOMAINNAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"domainName": {"test.cdn2.net"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + validateCDNFields(map[string]interface{}{"DomainName": "test.cdn2.net"})), + }, + "OK when VALID DNSSECENABLED parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"dnssecEnabled": {"false"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateCDNFields(map[string]interface{}{"DNSSECEnabled": false})), + }, + "VALID when SORTORDER param is DESC": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"sortOrder": {"desc"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateCDNDescSort()), + }, + "FIRST RESULT when LIMIT=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateCDNPagination("limit")), + }, + "SECOND RESULT when LIMIT=1 OFFSET=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "offset": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateCDNPagination("offset")), + }, + "SECOND RESULT when LIMIT=1 PAGE=2": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "page": {"2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateCDNPagination("page")), + }, + "BAD REQUEST when INVALID LIMIT parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"-2"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID OFFSET parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "offset": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID PAGE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "page": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "POST": { + "BAD REQUEST when CDN ALREADY EXISTS": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "cdn3", + "dnssecEnabled": false, + "domainName": "test.cdn3.net", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when EMPTY NAME": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "", + "dnssecEnabled": false, + "domainName": "test.noname.net", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when EMPTY DOMAIN NAME": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "nodomain", + "dnssecEnabled": false, + "domainName": "", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "FORBIDDEN when READ ONLY USER": { + ClientSession: readOnlyUserSession, + RequestBody: map[string]interface{}{ + "name": "readOnlyTest", + "dnssecEnabled": false, + "domainName": "test.ro", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + "PUT": { + "OK when VALID request": { + EndpointId: GetCDNID(t, "cdn1"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "dnssecEnabled": false, + "domainName": "domain2", + "name": "cdn1", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateCDNUpdateFields("cdn1", map[string]interface{}{"DomainName": "domain2"})), + }, + "PRECONDITION FAILED when updating with IF-UNMODIFIED-SINCE Headers": { + EndpointId: GetCDNID(t, "cdn1"), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}}, + RequestBody: map[string]interface{}{ + "dnssecEnabled": false, + "domainName": "newDomain", + "name": "cdn1", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + "PRECONDITION FAILED when updating with IFMATCH ETAG Header": { + EndpointId: GetCDNID(t, "cdn1"), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}}, + RequestBody: map[string]interface{}{ + "dnssecEnabled": false, + "domainName": "newDomain", + "name": "cdn1", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + }, + "DELETE": { + "NOT FOUND when INVALID ID parameter": { + EndpointId: func() int { return 111111 }, + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + }, + "GET AFTER CHANGES": { + "OK when CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {currentTimeRFC}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + cdn := tc.CDN{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &cdn) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET", "GET AFTER CHANGES": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetCDNs(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.CreateCDN(cdn, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.UpdateCDN(testCase.EndpointId(), cdn, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.DeleteCDN(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateCDNFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected CDN response to not be nil.") + cdnResp := resp.([]tc.CDN) + for field, expected := range expectedResp { + for _, cdn := range cdnResp { + switch field { + case "Name": + assert.Equal(t, expected, cdn.Name, "Expected Name to be %v, but got %v", expected, cdn.Name) + case "DomainName": + assert.Equal(t, expected, cdn.DomainName, "Expected DomainName to be %v, but got %v", expected, cdn.DomainName) + case "DNSSECEnabled": + assert.Equal(t, expected, cdn.DNSSECEnabled, "Expected DNSSECEnabled to be %v, but got %v", expected, cdn.DNSSECEnabled) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validateCDNUpdateFields(name string, expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", name) + cdn, _, err := TOSession.GetCDNs(opts) + assert.NoError(t, err, "Error getting CDN: %v - alerts: %+v", err, cdn.Alerts) + assert.Equal(t, 1, len(cdn.Response), "Expected one CDN returned Got: %d", len(cdn.Response)) + validateCDNFields(expectedResp)(t, toclientlib.ReqInf{}, cdn.Response, tc.Alerts{}, nil) + } +} + +func validateCDNPagination(paginationParam string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected CDN response to not be nil.") + paginationResp := resp.([]tc.CDN) + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("orderby", "id") + respBase, _, err := TOSession.GetCDNs(opts) + assert.RequireNoError(t, err, "Cannot get CDNs: %v - alerts: %+v", err, respBase.Alerts) + + cachegroup := respBase.Response + assert.RequireGreaterOrEqual(t, len(cachegroup), 3, "Need at least 3 CDNs in Traffic Ops to test pagination support, found: %d", len(cachegroup)) + switch paginationParam { + case "limit:": + assert.Exactly(t, cachegroup[:1], paginationResp, "Expected GET CDNs with limit = 1 to return first result") + case "offset": + assert.Exactly(t, cachegroup[1:2], paginationResp, "Expected GET CDNs with limit = 1, offset = 1 to return second result") + case "page": + assert.Exactly(t, cachegroup[1:2], paginationResp, "Expected GET CDNs with limit = 1, page = 2 to return second result") + } + } +} + +func validateCDNSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected CDN response to not be nil.") + var cdnNames []string + cdnResp := resp.([]tc.CDN) + for _, cdn := range cdnResp { + cdnNames = append(cdnNames, cdn.Name) + } + assert.Equal(t, true, sort.StringsAreSorted(cdnNames), "List is not sorted by their names: %v", cdnNames) + } +} + +func validateCDNDescSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected CDN response to not be nil.") + cdnDescResp := resp.([]tc.CDN) + var descSortedList []string + var ascSortedList []string + assert.RequireGreaterOrEqual(t, len(cdnDescResp), 2, "Need at least 2 CDNs in Traffic Ops to test desc sort, found: %d", len(cdnDescResp)) + // Get CDNs in the default ascending order for comparison. + cdnAscResp, _, err := TOSession.GetCDNs(client.RequestOptions{}) + assert.RequireNoError(t, err, "Unexpected error getting CDNs with default sort order: %v - alerts: %+v", err, cdnAscResp.Alerts) + // Verify the response match in length, i.e. equal amount of CDNs. + assert.RequireEqual(t, len(cdnAscResp.Response), len(cdnDescResp), "Expected descending order response length: %v, to match ascending order response length %v", len(cdnAscResp.Response), len(cdnDescResp)) + // Insert CDN names to the front of a new list, so they are now reversed to be in ascending order. + for _, cdn := range cdnDescResp { + descSortedList = append([]string{cdn.Name}, descSortedList...) + } + // Insert CDN names by appending to a new list, so they stay in ascending order. + for _, cdn := range cdnAscResp.Response { + ascSortedList = append(ascSortedList, cdn.Name) + } + assert.Exactly(t, ascSortedList, descSortedList, "CDN responses are not equal after reversal: %v - %v", ascSortedList, descSortedList) + } +} + +func GetCDNID(t *testing.T, cdnName string) func() int { + return func() int { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", cdnName) + cdnsResp, _, err := TOSession.GetCDNs(opts) + assert.RequireNoError(t, err, "Get CDNs Request failed with error:", err) + assert.RequireEqual(t, 1, len(cdnsResp.Response), "Expected response object length 1, but got %d", len(cdnsResp.Response)) + assert.RequireNotNil(t, cdnsResp.Response[0].ID, "Expected id to not be nil") + return cdnsResp.Response[0].ID + } +} + +func CreateTestCDNs(t *testing.T) { + for _, cdn := range testData.CDNs { + resp, _, err := TOSession.CreateCDN(cdn, client.RequestOptions{}) + assert.NoError(t, err, "Could not create CDN: %v - alerts: %+v", err, resp.Alerts) + } +} + +func DeleteTestCDNs(t *testing.T) { + resp, _, err := TOSession.GetCDNs(client.RequestOptions{}) + assert.NoError(t, err, "Cannot get CDNs: %v - alerts: %+v", err, resp.Alerts) + for _, cdn := range resp.Response { + delResp, _, err := TOSession.DeleteCDN(cdn.ID, client.RequestOptions{}) + assert.NoError(t, err, "Cannot delete CDN '%s' (#%d): %v - alerts: %+v", cdn.Name, cdn.ID, err, delResp.Alerts) + + // Retrieve the CDN to see if it got deleted + opts := client.NewRequestOptions() + opts.QueryParameters.Set("id", strconv.Itoa(cdn.ID)) + cdns, _, err := TOSession.GetCDNs(opts) + assert.NoError(t, err, "Error deleting CDN '%s': %v - alerts: %+v", cdn.Name, err, cdns.Alerts) + assert.Equal(t, 0, len(cdns.Response), "Expected CDN '%s' to be deleted", cdn.Name) + } +} diff --git a/traffic_ops/testing/api/v5/cookie_test.go b/traffic_ops/testing/api/v5/cookie_test.go new file mode 100644 index 0000000000..00a1d1762c --- /dev/null +++ b/traffic_ops/testing/api/v5/cookie_test.go @@ -0,0 +1,80 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "io/ioutil" + "net/http" + "testing" + + "github.com/apache/trafficcontrol/lib/go-tc" + toclient "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestCookies(t *testing.T) { + WithObjs(t, []TCObj{CDNs}, func() { + CookiesTest(t) + }) +} + +func CookiesTest(t *testing.T) { + s, _, err := toclient.LoginWithAgent(Config.TrafficOps.URL, Config.TrafficOps.Users.Admin, Config.TrafficOps.UserPassword, true, "to-api-v5-client-tests", false, toReqTimeout) + credentials := tc.UserCredentials{ + Username: Config.TrafficOps.Users.Admin, + Password: Config.TrafficOps.UserPassword, + } + + js, err := json.Marshal(credentials) + if err != nil { + t.Fatal("unable to json marshal login credentials") + } + path := TestAPIBase + "/user/login" + loginResp, _, err := s.RawRequest(http.MethodPost, path, js) + if err != nil { + t.Fatal("unable to request POST /user/login") + } + defer loginResp.Body.Close() + _, readErr := ioutil.ReadAll(loginResp.Body) + if readErr != nil { + t.Fatal("unable to read response body from POST /user/login") + } + ensureCookie(loginResp, t) + + cdnResp, _, err := s.RawRequest(http.MethodGet, TestAPIBase+"/cdns", nil) + if err != nil { + t.Fatal("unable to request GET /cdns") + } + defer cdnResp.Body.Close() + _, readErr = ioutil.ReadAll(cdnResp.Body) + if readErr != nil { + t.Fatal("unable to read response body from GET /cdns") + } + ensureCookie(cdnResp, t) +} + +func ensureCookie(r *http.Response, t *testing.T) { + cookies := r.Cookies() + if len(cookies) < 1 { + t.Fatal("expected at least one cookie in response, actual: zero") + } + if cookies[0].MaxAge < 1 { + t.Errorf("expected auth cookie Max-Age > 0, actual: %v", *cookies[0]) + } + if cookies[0].Expires.IsZero() { + t.Errorf("expected auth cookie with non-zero Expires, actual: %v", *cookies[0]) + } +} diff --git a/traffic_ops/testing/api/v5/coordinates_test.go b/traffic_ops/testing/api/v5/coordinates_test.go new file mode 100644 index 0000000000..97f956ff6c --- /dev/null +++ b/traffic_ops/testing/api/v5/coordinates_test.go @@ -0,0 +1,363 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "sort" + "strconv" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestCoordinates(t *testing.T) { + WithObjs(t, []TCObj{Parameters, Coordinates}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID request": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateCoordinateSort()), + }, + "OK when VALID NAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"coordinate1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + validateCoordinateFields(map[string]interface{}{"Name": "coordinate1"})), + }, + "EMPTY RESPONSE when INVALID ID parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"id": {"10000"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when INVALID NAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"abcd"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "VALID when SORTORDER param is DESC": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"sortOrder": {"desc"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateCoordinateDescSort()), + }, + "FIRST RESULT when LIMIT=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateCoordinatePagination("limit")), + }, + "SECOND RESULT when LIMIT=1 OFFSET=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "offset": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateCoordinatePagination("offset")), + }, + "SECOND RESULT when LIMIT=1 PAGE=2": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "page": {"2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateCoordinatePagination("page")), + }, + "BAD REQUEST when INVALID LIMIT parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"-2"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID OFFSET parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "offset": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID PAGE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "page": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "POST": { + "BAD REQUEST when INVALID NAME": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "latitude": 1.1, + "longitude": 2.2, + "name": "", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID LATITUDE": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "latitude": 20000, + "longitude": 2.2, + "name": "testlatitude", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID LONGITUDE": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "latitude": 1.1, + "longitude": 20000, + "name": "testlongitude", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "PUT": { + "OK when VALID request": { + EndpointId: GetCoordinateID(t, "coordinate2"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "latitude": 7.7, + "longitude": 8.8, + "name": "coordinate2", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateCoordinateUpdateCreateFields("coordinate2", map[string]interface{}{"Latitude": 7.7, "Longitude": 8.8})), + }, + "NOT FOUND when INVALID ID parameter": { + EndpointId: func() int { return 111111 }, + RequestBody: map[string]interface{}{ + "latitude": 1.1, + "longitude": 2.2, + "name": "coordinate1", + }, + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "PRECONDITION FAILED when updating with IMS & IUS Headers": { + EndpointId: GetCoordinateID(t, "coordinate1"), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}}, + RequestBody: map[string]interface{}{ + "latitude": 1.1, + "longitude": 2.2, + "name": "coordinate1", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + "PRECONDITION FAILED when updating with IFMATCH ETAG Header": { + EndpointId: GetCoordinateID(t, "coordinate1"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "latitude": 1.1, + "longitude": 2.2, + "name": "coordinate1", + }, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + }, + "DELETE": { + "NOT FOUND when INVALID ID parameter": { + EndpointId: func() int { return 12345 }, + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + }, + "GET AFTER CHANGES": { + "OK when CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {currentTimeRFC}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + coordinate := tc.Coordinate{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &coordinate) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET", "GET AFTER CHANGES": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetCoordinates(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.CreateCoordinate(coordinate, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.UpdateCoordinate(testCase.EndpointId(), coordinate, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.DeleteCoordinate(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateCoordinateFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Coordinate response to not be nil.") + coordinateResp := resp.([]tc.Coordinate) + for field, expected := range expectedResp { + for _, coordinate := range coordinateResp { + switch field { + case "Name": + assert.Equal(t, expected, coordinate.Name, "Expected Name to be %v, but got %s", expected, coordinate.Name) + case "Latitude": + assert.Equal(t, expected, coordinate.Latitude, "Expected Latitude to be %v, but got %f", expected, coordinate.Latitude) + case "Longitude": + assert.Equal(t, expected, coordinate.Longitude, "Expected Longitude to be %v, but got %f", expected, coordinate.Longitude) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validateCoordinateUpdateCreateFields(name string, expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", name) + coordinates, _, err := TOSession.GetCoordinates(opts) + assert.RequireNoError(t, err, "Error getting Coordinate: %v - alerts: %+v", err, coordinates.Alerts) + assert.RequireEqual(t, 1, len(coordinates.Response), "Expected one Coordinate returned Got: %d", len(coordinates.Response)) + validateCoordinateFields(expectedResp)(t, toclientlib.ReqInf{}, coordinates.Response, tc.Alerts{}, nil) + } +} + +func validateCoordinatePagination(paginationParam string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + paginationResp := resp.([]tc.Coordinate) + opts := client.NewRequestOptions() + opts.QueryParameters.Set("orderby", "id") + respBase, _, err := TOSession.GetCoordinates(opts) + assert.RequireNoError(t, err, "Cannot get Coordinates: %v - alerts: %+v", err, respBase.Alerts) + + coordinate := respBase.Response + assert.RequireGreaterOrEqual(t, len(coordinate), 2, "Need at least 2 Coordinates in Traffic Ops to test pagination support, found: %d", len(coordinate)) + switch paginationParam { + case "limit:": + assert.Exactly(t, coordinate[:1], paginationResp, "expected GET Coordinates with limit = 1 to return first result") + case "offset": + assert.Exactly(t, coordinate[1:2], paginationResp, "expected GET Coordinates with limit = 1, offset = 1 to return second result") + case "page": + assert.Exactly(t, coordinate[1:2], paginationResp, "expected GET Coordinates with limit = 1, page = 2 to return second result") + } + } +} + +func validateCoordinateSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Coordinate response to not be nil.") + var coordinateNames []string + coordinateResp := resp.([]tc.Coordinate) + for _, coordinate := range coordinateResp { + coordinateNames = append(coordinateNames, coordinate.Name) + } + assert.Equal(t, true, sort.StringsAreSorted(coordinateNames), "List is not sorted by their names: %v", coordinateNames) + } +} + +func validateCoordinateDescSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Coordinate response to not be nil.") + coordinateDescResp := resp.([]tc.Coordinate) + var descSortedList []string + var ascSortedList []string + assert.RequireGreaterOrEqual(t, len(coordinateDescResp), 2, "Need at least 2 Coordinates in Traffic Ops to test desc sort, found: %d", len(coordinateDescResp)) + // Get Coordinates in the default ascending order for comparison. + coordinateAscResp, _, err := TOSession.GetCoordinates(client.RequestOptions{}) + assert.RequireNoError(t, err, "Unexpected error getting Coordinates with default sort order: %v - alerts: %+v", err, coordinateAscResp.Alerts) + // Verify the response match in length, i.e. equal amount of Coordinates. + assert.RequireEqual(t, len(coordinateAscResp.Response), len(coordinateDescResp), "Expected descending order response length: %d, to match ascending order response length %d", len(coordinateAscResp.Response), len(coordinateDescResp)) + // Insert Coordinate names to the front of a new list, so they are now reversed to be in ascending order. + for _, division := range coordinateDescResp { + descSortedList = append([]string{division.Name}, descSortedList...) + } + // Insert Coordinate names by appending to a new list, so they stay in ascending order. + for _, coordinate := range coordinateAscResp.Response { + ascSortedList = append(ascSortedList, coordinate.Name) + } + assert.Exactly(t, ascSortedList, descSortedList, "Coordinate responses are not equal after reversal: %v - %v", ascSortedList, descSortedList) + } +} + +func GetCoordinateID(t *testing.T, coordinateName string) func() int { + return func() int { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", coordinateName) + coordinatesResp, _, err := TOSession.GetCoordinates(opts) + assert.RequireNoError(t, err, "Get Coordinate Request failed with error:", err) + assert.RequireEqual(t, 1, len(coordinatesResp.Response), "Expected response object length 1, but got %d", len(coordinatesResp.Response)) + return coordinatesResp.Response[0].ID + } +} + +func CreateTestCoordinates(t *testing.T) { + for _, coordinate := range testData.Coordinates { + resp, _, err := TOSession.CreateCoordinate(coordinate, client.RequestOptions{}) + assert.RequireNoError(t, err, "Could not create coordinate: %v - alerts: %+v", err, resp.Alerts) + } +} + +func DeleteTestCoordinates(t *testing.T) { + coordinates, _, err := TOSession.GetCoordinates(client.RequestOptions{}) + assert.NoError(t, err, "Cannot get Coordinates: %v - alerts: %+v", err, coordinates.Alerts) + for _, coordinate := range coordinates.Response { + alerts, _, err := TOSession.DeleteCoordinate(coordinate.ID, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error deleting Coordinate '%s' (#%d): %v - alerts: %+v", coordinate.Name, coordinate.ID, err, alerts.Alerts) + // Retrieve the Coordinate to see if it got deleted + opts := client.NewRequestOptions() + opts.QueryParameters.Set("id", strconv.Itoa(coordinate.ID)) + getCoordinate, _, err := TOSession.GetCoordinates(opts) + assert.NoError(t, err, "Error getting Coordinate '%s' after deletion: %v - alerts: %+v", coordinate.Name, err, getCoordinate.Alerts) + assert.Equal(t, 0, len(getCoordinate.Response), "Expected Coordinate '%s' to be deleted, but it was found in Traffic Ops", coordinate.Name) + } +} diff --git a/traffic_ops/testing/api/v5/crconfig_test.go b/traffic_ops/testing/api/v5/crconfig_test.go new file mode 100644 index 0000000000..3e03bf3b16 --- /dev/null +++ b/traffic_ops/testing/api/v5/crconfig_test.go @@ -0,0 +1,356 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/http" + "strconv" + "strings" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/lib/go-util" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" + toclient "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestCRConfig(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, ProfileParameters, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServiceCategories, DeliveryServices}, func() { + UpdateTestCRConfigSnapshot(t) + MonitoringConfig(t) + SnapshotTestCDNbyName(t) + SnapshotTestCDNbyInvalidName(t) + SnapshotTestCDNbyID(t) + SnapshotTestCDNbyInvalidID(t) + SnapshotWithReadOnlyUser(t) + }) +} + +func SnapshotWithReadOnlyUser(t *testing.T) { + if len(testData.CDNs) == 0 { + t.Fatalf("expected one or more valid CDNs, but got none") + } + + tenantOpts := client.NewRequestOptions() + tenantOpts.QueryParameters.Set("name", "root") + resp, _, err := TOSession.GetTenants(tenantOpts) + if err != nil { + t.Fatalf("couldn't get the root tenant ID: %v - alerts: %+v", err, resp.Alerts) + } + if len(resp.Response) != 1 { + t.Fatalf("Expected exactly one Tenant to have the name 'root', found: %d", len(resp.Response)) + } + + toReqTimeout := time.Second * time.Duration(Config.Default.Session.TimeoutInSecs) + user := tc.UserV4{ + Username: "test_user_tm", + RegistrationSent: new(time.Time), + LocalPassword: util.StrPtr("test_pa$$word"), + Role: "read-only", + } + user.Email = util.StrPtr("email_tm@domain.com") + user.TenantID = resp.Response[0].ID + user.FullName = util.StrPtr("firstName LastName") + + u, _, err := TOSession.CreateUser(user, client.RequestOptions{}) + if err != nil { + t.Fatalf("could not create read-only user: %v - alerts: %+v", err, u.Alerts) + } + client, _, err := toclient.LoginWithAgent(TOSession.URL, "test_user_tm", "test_pa$$word", true, "to-api-v5-client-tests/tenant4user", true, toReqTimeout) + if err != nil { + t.Fatalf("failed to log in with test_user: %v", err.Error()) + } + opts := toclient.NewRequestOptions() + opts.QueryParameters.Set("cdn", testData.CDNs[0].Name) + _, reqInf, err := client.SnapshotCRConfig(opts) + if err == nil { + t.Errorf("expected to get an error about a read-only client trying to snap a CDN, but got none") + } + if reqInf.StatusCode != http.StatusForbidden { + t.Errorf("expected a 403 forbidden status code, but got %d", reqInf.StatusCode) + } + ForceDeleteTestUsersByUsernames(t, []string{"test_user_tm"}) +} + +func UpdateTestCRConfigSnapshot(t *testing.T) { + if len(testData.CDNs) < 1 { + t.Error("no cdn test data") + } + cdn := testData.CDNs[0].Name + + tmURLParamName := "tm.url" + tmURLExpected := "crconfig.tm.url.test.invalid" + paramAlerts, _, err := TOSession.CreateParameter(tc.Parameter{ + ConfigFile: "global", + Name: tmURLParamName, + Value: "https://crconfig.tm.url.test.invalid", + }, client.RequestOptions{}) + if err != nil { + t.Fatalf("GetCRConfig CreateParameter error expected: nil, actual: %v - alerts: %+v", err, paramAlerts.Alerts) + } + + // create an ANY_MAP DS assignment to verify that it doesn't show up in the CRConfig + resp, _, err := TOSession.GetServers(client.RequestOptions{}) + if err != nil { + t.Fatalf("GetServers err expected nil, actual: %v - alerts: %+v", err, resp.Alerts) + } + servers := resp.Response + serverID := 0 + for _, server := range servers { + if server.CDNName == nil || server.ID == nil { + t.Error("Traffic Ops returned a representation for a servver with null or undefined ID and/or CDN name") + continue + } + if server.Type == "EDGE" && *server.CDNName == "cdn1" { + serverID = *server.ID + break + } + } + if serverID == 0 { + t.Errorf("GetServers expected EDGE server in cdn1, actual: %+v", servers) + } + opts := client.NewRequestOptions() + opts.QueryParameters.Set("xmlId", "anymap-ds") + res, _, err := TOSession.GetDeliveryServices(opts) + if err != nil { + t.Errorf("Unexpected error getting Delivery Services filtered by XMLID 'anymap-ds': %v - alerts: %+v", err, res.Alerts) + } + if len(res.Response) != 1 { + t.Fatalf("Expected exactly 1 Delivery Service to exist with XMLID 'anymap-ds', actual %d", len(res.Response)) + } + if res.Response[0].ID == nil { + t.Fatal("Traffic Ops returned a representation of Delivery Service 'anymap-ds' that had a null or undefined ID") + } + anymapDSID := *res.Response[0].ID + alerts, _, err := TOSession.CreateDeliveryServiceServers(anymapDSID, []int{serverID}, true, client.RequestOptions{}) + if err != nil { + t.Fatalf("Unexpected error assigning server #%d to Delivery Service #%d: %v - alerts: %+v", serverID, anymapDSID, err, alerts.Alerts) + } + + opts = client.NewRequestOptions() + opts.QueryParameters.Set("cdn", cdn) + snapshotResp, _, err := TOSession.SnapshotCRConfig(opts) + if err != nil { + t.Errorf("Unexpected error taking Snapshot of CDN '%s': %v - alerts: %+v", cdn, err, snapshotResp.Alerts) + } + crcResp, _, err := TOSession.GetCRConfig(cdn, client.RequestOptions{}) + if err != nil { + t.Errorf("Unexpected error retrieving Snapshot of CDN '%s': %v - alerts: %+v", cdn, err, crcResp.Alerts) + } + crc := crcResp.Response + + if len(crc.DeliveryServices) == 0 { + t.Error("GetCRConfig len(crc.DeliveryServices) expected: >0, actual: 0") + } + + // verify no ANY_MAP delivery services are in the CRConfig + for ds := range crc.DeliveryServices { + if ds == "anymap-ds" { + t.Error("found ANY_MAP delivery service in CRConfig deliveryServices") + } + } + for server := range crc.ContentServers { + for ds := range crc.ContentServers[server].DeliveryServices { + if ds == "anymap-ds" { + t.Error("found ANY_MAP delivery service in contentServers deliveryServices mapping") + } + } + } + + if crc.Stats.TMPath != nil { + t.Errorf("Expected no TMPath in APIv4, but it was: %v", *crc.Stats.TMPath) + } + + if crc.Stats.TMHost == nil { + t.Errorf("GetCRConfig crc.Stats.Path expected: '"+tmURLExpected+"', actual: %+v", crc.Stats.TMHost) + } else if *crc.Stats.TMHost != tmURLExpected { + t.Errorf("GetCRConfig crc.Stats.Path expected: '"+tmURLExpected+"', actual: %+v", *crc.Stats.TMHost) + } + + opts.QueryParameters.Del("cdn") + opts.QueryParameters.Set("name", tmURLParamName) + paramResp, _, err := TOSession.GetParameters(opts) + if err != nil { + t.Fatalf("cannot get Parameter by name '%s': %v - alerts: %+v", tmURLParamName, err, paramResp.Alerts) + } + if len(paramResp.Response) == 0 { + t.Fatal("CRConfig create tm.url parameter was successful, but GET returned no parameters") + } + tmURLParam := paramResp.Response[0] + + delResp, _, err := TOSession.DeleteParameter(tmURLParam.ID, client.RequestOptions{}) + if err != nil { + t.Fatalf("cannot DELETE Parameter by name: %v - %v", err, delResp) + } + + crcResp, _, err = TOSession.GetCRConfigNew(cdn, client.RequestOptions{}) + if err != nil { + t.Errorf("Unexpected error getting new Snapshot for CDN '%s': %v - alerts: %+v", cdn, err, crcResp.Alerts) + } + crcNew := crcResp.Response + + if len(crcNew.DeliveryServices) != len(crc.DeliveryServices) { + t.Errorf("/new endpoint returned a different snapshot. DeliveryServices length expected %v, was %v", len(crc.DeliveryServices), len(crcNew.DeliveryServices)) + } + + if *crcNew.Stats.TMHost != "" { + t.Errorf("update to snapshot not captured in /new endpoint") + } +} + +func MonitoringConfig(t *testing.T) { + if len(testData.CDNs) < 1 { + t.Fatalf("no cdn test data") + } + const cdnName = "cdn1" + const profileName = "EDGE1" + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", cdnName) + cdns, _, err := TOSession.GetCDNs(opts) + if err != nil { + t.Fatalf("getting CDNs with name '%s': %v - alerts: %+v", cdnName, err, cdns.Alerts) + } + if len(cdns.Response) != 1 { + t.Fatalf("expected exactly 1 CDN named '%s' but found %d CDNs", cdnName, len(cdns.Response)) + } + opts.QueryParameters.Set("name", profileName) + profiles, _, err := TOSession.GetProfiles(opts) + if err != nil { + t.Fatalf("getting Profiles with name '%s': %v - alerts: %+v", profileName, err, profiles.Alerts) + } + if len(profiles.Response) != 1 { + t.Fatalf("expected exactly 1 Profiles named %s but found %d Profiles", profileName, len(profiles.Response)) + } + parameters, _, err := TOSession.GetParametersByProfileName(profileName, client.RequestOptions{}) + if err != nil { + t.Fatalf("getting Parameters by Profile name '%s': %v - alerts: %+v", profileName, err, parameters.Alerts) + } + parameterMap := map[string]tc.HealthThreshold{} + parameterFound := map[string]bool{} + const thresholdPrefixLength = len(tc.ThresholdPrefix) + for _, parameter := range parameters.Response { + if !strings.HasPrefix(parameter.Name, tc.ThresholdPrefix) { + continue + } + parameterName := parameter.Name[thresholdPrefixLength:] + parameterMap[parameterName], err = tc.StrToThreshold(parameter.Value) + if err != nil { + t.Fatalf("converting string '%s' to HealthThreshold: %s", parameter.Value, err.Error()) + } + parameterFound[parameterName] = false + } + const expectedThresholdParameters = 3 + if len(parameterMap) != expectedThresholdParameters { + t.Fatalf("expected Profile '%s' to contain %d Parameters with names starting with '%s' but %d such Parameters were found", profileName, expectedThresholdParameters, tc.ThresholdPrefix, len(parameterMap)) + } + tmConfig, _, err := TOSession.GetTrafficMonitorConfig(cdnName, client.RequestOptions{}) + if err != nil { + t.Fatalf("getting Traffic Monitor Config: %v - alerts: %+v", err, tmConfig.Alerts) + } + profileFound := false + var profile tc.TMProfile + for _, profile = range tmConfig.Response.Profiles { + if profile.Name == profileName { + profileFound = true + break + } + } + if !profileFound { + t.Fatalf("Traffic Monitor Config contained no Profile named '%s", profileName) + } + for parameterName, value := range profile.Parameters.Thresholds { + if _, ok := parameterFound[parameterName]; !ok { + t.Fatalf("unexpected Threshold Parameter name '%s' found in Profile '%s' in Traffic Monitor Config", parameterName, profileName) + } + parameterFound[parameterName] = true + if parameterMap[parameterName].String() != value.String() { + t.Fatalf("expected '%s' but received '%s' for Threshold Parameter '%s' in Profile '%s' in Traffic Monitor Config", parameterMap[parameterName].String(), value.String(), parameterName, profileName) + } + } + missingParameters := []string{} + for parameterName, found := range parameterFound { + if !found { + missingParameters = append(missingParameters, parameterName) + } + } + if len(missingParameters) != 0 { + t.Fatalf("Threshold parameters defined for Profile '%s' but missing for Profile '%s' in Traffic Monitor Config: %s", profileName, profileName, strings.Join(missingParameters, ", ")) + } +} + +func SnapshotTestCDNbyName(t *testing.T) { + if len(testData.CDNs) < 1 { + t.Fatal("Need at least one CDN to test taking CDN Snapshot using CDN name") + } + firstCDN := testData.CDNs[0].Name + opts := client.NewRequestOptions() + opts.QueryParameters.Set("cdn", firstCDN) + resp, _, err := TOSession.SnapshotCRConfig(opts) + if err != nil { + t.Errorf("failed to snapshot CDN '%s' by name: %v - alerts: %+v", firstCDN, err, resp.Alerts) + } +} + +// Note that this test will break if anyone adds a CDN to the fixture data with +// the name "cdn-invalid". +func SnapshotTestCDNbyInvalidName(t *testing.T) { + invalidCDNName := "cdn-invalid" + opts := client.NewRequestOptions() + opts.QueryParameters.Set("cdn", invalidCDNName) + _, _, err := TOSession.SnapshotCRConfig(opts) + if err == nil { + t.Errorf("snapshot occurred without error on (presumed) invalid CDN '%s'", invalidCDNName) + } +} + +func SnapshotTestCDNbyID(t *testing.T) { + if len(testData.CDNs) < 1 { + t.Fatal("Need at least one CDN to test Snapshotting CDNs") + } + firstCDNName := testData.CDNs[0].Name + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", firstCDNName) + // Retrieve the CDN by name so we can get the id for the snapshot + resp, _, err := TOSession.GetCDNs(opts) + if err != nil { + t.Errorf("cannot get CDN '%s': %v - alerts: %+v", firstCDNName, err, resp.Alerts) + } + if len(resp.Response) != 1 { + t.Fatalf("Expected exactly one CDN to exist with name '%s', found: %d", firstCDNName, len(resp.Response)) + } + remoteCDNID := resp.Response[0].ID + opts.QueryParameters.Del("name") + opts.QueryParameters.Set("cdnID", strconv.Itoa(remoteCDNID)) + alert, _, err := TOSession.SnapshotCRConfig(opts) + if err != nil { + t.Errorf("failed to snapshot CDN '%s' (#%d) by id: %v - alerts: %+v", firstCDNName, remoteCDNID, err, alert.Alerts) + } +} + +// Note that this test will break in the event that 1,000,000 CDNs are created +// in the TO instance at any time (they don't need to exist concurrently, just +// that many successful CDN creations have to happen, even if they are +// all immediately deleted except the 999999th one). +func SnapshotTestCDNbyInvalidID(t *testing.T) { + opts := client.NewRequestOptions() + invalidCDNID := 999999 + opts.QueryParameters.Set("cdnID", strconv.Itoa(invalidCDNID)) + alert, _, err := TOSession.SnapshotCRConfig(opts) + if err == nil { + t.Errorf("snapshot occurred on (presumed) invalid CDN #%d: %v - alerts: %+v", invalidCDNID, err, alert.Alerts) + } +} diff --git a/traffic_ops/testing/api/v5/deliveryservice_request_comments_test.go b/traffic_ops/testing/api/v5/deliveryservice_request_comments_test.go new file mode 100644 index 0000000000..43b48d4f5a --- /dev/null +++ b/traffic_ops/testing/api/v5/deliveryservice_request_comments_test.go @@ -0,0 +1,205 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "sort" + "strconv" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestDeliveryServiceRequestComments(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Parameters, Tenants, DeliveryServiceRequests, DeliveryServiceRequestComments}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID request": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK when VALID ID parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"id": {strconv.Itoa(GetDSRequestCommentId(t, "admin")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1)), + }, + "VALIDATE SORT when DEFAULT is ASC ORDER": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateSortedDSRequestComments()), + }, + }, + "PUT": { + "OK when VALID request": { + EndpointId: GetDSRequestCommentId(t, "admin"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "deliveryServiceRequestId": GetDSRequestId(t, "test-ds1")(), + "value": "updated comment", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "PRECONDITION FAILED when updating with IF-UNMODIFIED-SINCE Header": { + EndpointId: GetDSRequestCommentId(t, "admin"), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}}, + RequestBody: map[string]interface{}{}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + "PRECONDITION FAILED when updating with IFMATCH ETAG Header": { + EndpointId: GetDSRequestCommentId(t, "admin"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{}, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + }, + "GET AFTER CHANGES": { + "OK when CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {currentTimeRFC}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + comment := tc.DeliveryServiceRequestComment{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &comment) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET", "GET AFTER CHANGES": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetDeliveryServiceRequestComments(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.UpdateDeliveryServiceRequestComment(testCase.EndpointId(), comment, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func GetDSRequestCommentId(t *testing.T, author string) func() int { + return func() int { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("author", author) + + resp, _, err := TOSession.GetDeliveryServiceRequestComments(opts) + assert.RequireNoError(t, err, "Get Delivery Service Request Comments failed with error: %v", err) + assert.RequireGreaterOrEqual(t, len(resp.Response), 1, "Expected delivery service request comments response object length of atleast 1, but got %d", len(resp.Response)) + assert.RequireNotNil(t, resp.Response[0].ID, "Expected id to not be nil") + + return resp.Response[0].ID + } +} + +func GetDSRequestId(t *testing.T, xmlId string) func() int { + return func() int { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("xmlId", xmlId) + + resp, _, err := TOSession.GetDeliveryServiceRequests(opts) + assert.RequireNoError(t, err, "Get Delivery Service Requests failed with error: %v", err) + assert.RequireGreaterOrEqual(t, len(resp.Response), 1, "Expected delivery service requests response object length of atleast 1, but got %d", len(resp.Response)) + assert.RequireNotNil(t, resp.Response[0].ID, "Expected id to not be nil") + + return *resp.Response[0].ID + } +} + +func validateSortedDSRequestComments() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, err error) { + var sortedList []string + dsReqComments := resp.([]tc.DeliveryServiceRequestComment) + + for _, comment := range dsReqComments { + sortedList = append(sortedList, comment.XMLID) + } + + res := sort.SliceIsSorted(sortedList, func(p, q int) bool { + return sortedList[p] < sortedList[q] + }) + assert.Equal(t, res, true, "List is not sorted by their names: %v", sortedList) + } +} + +func CreateTestDeliveryServiceRequestComments(t *testing.T) { + for _, comment := range testData.DeliveryServiceRequestComments { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("xmlId", comment.XMLID) + resp, _, err := TOSession.GetDeliveryServiceRequests(opts) + assert.NoError(t, err, "Cannot get Delivery Service Request by XMLID '%s': %v - alerts: %+v", comment.XMLID, err, resp.Alerts) + assert.Equal(t, len(resp.Response), 1, "Found %d Delivery Service request by XMLID '%s, expected exactly one", len(resp.Response), comment.XMLID) + assert.NotNil(t, resp.Response[0].ID, "Got Delivery Service Request with xml_id '%s' that had a null ID", comment.XMLID) + + comment.DeliveryServiceRequestID = *resp.Response[0].ID + alerts, _, err := TOSession.CreateDeliveryServiceRequestComment(comment, client.RequestOptions{}) + assert.NoError(t, err, "Could not create Delivery Service Request Comment: %v - alerts: %+v", err, alerts.Alerts) + } +} + +func DeleteTestDeliveryServiceRequestComments(t *testing.T) { + comments, _, err := TOSession.GetDeliveryServiceRequestComments(client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error getting Delivery Service Request Comments: %v - alerts: %+v", err, comments.Alerts) + + for _, comment := range comments.Response { + resp, _, err := TOSession.DeleteDeliveryServiceRequestComment(comment.ID, client.RequestOptions{}) + assert.NoError(t, err, "Cannot delete Delivery Service Request Comment #%d: %v - alerts: %+v", comment.ID, err, resp.Alerts) + + // Retrieve the delivery service request comment to see if it got deleted + opts := client.NewRequestOptions() + opts.QueryParameters.Set("id", strconv.Itoa(comment.ID)) + comments, _, err := TOSession.GetDeliveryServiceRequestComments(opts) + assert.NoError(t, err, "Unexpected error fetching Delivery Service Request Comment %d after deletion: %v - alerts: %+v", comment.ID, err, comments.Alerts) + assert.Equal(t, len(comments.Response), 0, "Expected Delivery Service Request Comment #%d to be deleted, but it was found in Traffic Ops", comment.ID) + } +} diff --git a/traffic_ops/testing/api/v5/deliveryservice_requests_test.go b/traffic_ops/testing/api/v5/deliveryservice_requests_test.go new file mode 100644 index 0000000000..c6e478a152 --- /dev/null +++ b/traffic_ops/testing/api/v5/deliveryservice_requests_test.go @@ -0,0 +1,394 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "strconv" + "strings" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +// this resets the IDs of things attached to a DS, which needs to be done +// because the WithObjs flow destroys and recreates those object IDs +// non-deterministically with each test - BUT, the client method permanently +// alters the DSR structures by adding these referential IDs. Older clients +// got away with it by not making 'DeliveryService' a pointer, but to add +// original/requested fields you need to sometimes allow each to be nil, so +// this is a problem that needs to be solved at some point. +// A better solution _might_ be to reload all the test fixtures every time +// to wipe any and all referential modifications made to any test data, but +// for now that's overkill. +func resetDS(ds *tc.DeliveryServiceV4) { + if ds == nil { + return + } + ds.CDNID = nil + ds.ID = nil + ds.ProfileID = nil + ds.TenantID = nil + ds.TypeID = nil +} + +func TestDeliveryServiceRequests(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Parameters, Tenants, DeliveryServiceRequests}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID XMLID parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"xmlId": {"test-ds1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateGetDSRequestFields(map[string]interface{}{"XMLID": "test-ds1"})), + }, + }, + "PUT": { + "OK when VALID request": { + EndpointId: GetDeliveryServiceRequestId(t, "test-ds1"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "changeType": "create", + "requested": generateDeliveryService(t, map[string]interface{}{ + "displayName": "NEW DISPLAY NAME", + "tenantId": GetTenantID(t, "tenant1")(), + "xmlId": "test-ds1", + }), + "status": "draft", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK when UPDATING STATUS FROM DRAFT TO SUBMITTED": { + EndpointId: GetDeliveryServiceRequestId(t, "test-ds1"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "changeType": "create", + "requested": generateDeliveryService(t, map[string]interface{}{ + "tenantId": GetTenantID(t, "tenant1")(), + "xmlId": "test-ds1", + }), + "status": "submitted", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validatePutDSRequestFields(map[string]interface{}{"STATUS": tc.RequestStatusSubmitted})), + }, + "BAD REQUEST when using LONG DESCRIPTION 2 and 3 fields": { + EndpointId: GetDeliveryServiceRequestId(t, "test-ds1"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "changeType": "create", + "requested": generateDeliveryService(t, map[string]interface{}{ + "longDesc1": "long desc 1", + "longDesc2": "long desc 2", + "tenantId": GetTenantID(t, "tenant1")(), + "xmlId": "test-ds1", + }), + "status": "draft", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "PRECONDITION FAILED when updating with IF-UNMODIFIED-SINCE Header": { + EndpointId: GetDeliveryServiceRequestId(t, "test-ds1"), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}}, + RequestBody: map[string]interface{}{}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + "PRECONDITION FAILED when updating with IFMATCH ETAG Header": { + EndpointId: GetDeliveryServiceRequestId(t, "test-ds1"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{}, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + }, + "POST": { + "CREATED when VALID request": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "changeType": "create", + "requested": generateDeliveryService(t, map[string]interface{}{ + "ccrDnsTtl": 30, + "deepCachingType": "NEVER", + "initialDispersion": 3, + "ipv6RoutingEnabled": true, + "longDesc": "long desc", + "orgServerFqdn": "http://example.test", + "profileName": nil, + "tenant": "root", + "xmlId": "test-ds2", + }), + "status": "draft", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusCreated)), + }, + "BAD REQUEST when MISSING REQUIRED FIELDS": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "changeType": "create", + "requested": map[string]interface{}{ + "type": "HTTP", + "xmlId": "test-ds-fields", + }, + "status": "draft", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when VALIDATION RULES ARE NOT FOLLOWED": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "changeType": "create", + "requested": map[string]interface{}{ + "ccrDnsTtl": 30, + "deepCachingType": "NEVER", + "displayName": strings.Repeat("X", 49), + "dscp": 0, + "geoLimit": 0, + "geoProvider": 1, + "infoUrl": "xxx", + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": true, + "longDesc": "long desc", + "missLat": 0.0, + "missLong": 0.0, + "multiSiteOrigin": false, + "orgServerFqdn": "http://example.test", + "protocol": 0, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regionalGeoBlocking": true, + "routingName": strings.Repeat("X", 1) + "." + strings.Repeat("X", 48), + "tenant": "tenant1", + "type": "HTTP", + "xmlId": "X " + strings.Repeat("X", 46), + }, + "status": "draft", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when NON-DRAFT": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "changeType": "create", + "requested": map[string]interface{}{ + "active": false, + "cdnName": "cdn1", + "displayName": "Testing transitions", + "dscp": 3, + "geoLimit": 1, + "geoProvider": 1, + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": true, + "missLat": 0.0, + "missLong": 0.0, + "multiSiteOrigin": false, + "orgServerFqdn": "http://example.test", + "protocol": 0, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regionalGeoBlocking": true, + "routingName": "goodroute", + "tenant": "tenant1", + "type": "HTTP", + "xmlId": "test-transitions", + }, + "status": "pending", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when ALREADY EXISTS": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "changeType": "create", + "requested": map[string]interface{}{ + "active": true, + "cdnName": "cdn1", + "displayName": "Good Kabletown CDN", + "dscp": 1, + "geoLimit": 1, + "geoProvider": 1, + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": true, + "missLat": 0.0, + "missLong": 0.0, + "multiSiteOrigin": false, + "orgServerFqdn": "http://example.test", + "protocol": 0, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regionalGeoBlocking": true, + "routingName": "goodroute", + "tenant": "tenant1", + "type": "HTTP", + "xmlId": "test-ds1", + }, + "status": "draft", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "DELETE": { + "OK when VALID request": { + EndpointId: GetDeliveryServiceRequestId(t, "test-deletion"), + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + "GET AFTER CHANGES": { + "OK when CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {currentTimeRFC}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + dsReq := tc.DeliveryServiceRequestV4{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &dsReq) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET", "GET AFTER CHANGES": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetDeliveryServiceRequests(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.CreateDeliveryServiceRequest(dsReq, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.UpdateDeliveryServiceRequest(testCase.EndpointId(), dsReq, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.DeleteDeliveryServiceRequest(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + } + } + }) + } + + }) +} + +func GetDeliveryServiceRequestId(t *testing.T, xmlId string) func() int { + return func() int { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("xmlId", xmlId) + resp, _, err := TOSession.GetDeliveryServiceRequests(opts) + assert.RequireNoError(t, err, "Get Delivery Service Requests failed with error: %v", err) + assert.RequireGreaterOrEqual(t, len(resp.Response), 1, "Expected delivery service requests response object length of atleast 1, but got %d", len(resp.Response)) + assert.RequireNotNil(t, resp.Response[0].ID, "Expected id to not be nil") + return *resp.Response[0].ID + } +} + +func validateGetDSRequestFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + dsReqResp := resp.([]tc.DeliveryServiceRequestV40) + for field, expected := range expectedResp { + for _, ds := range dsReqResp { + switch field { + case "XMLID": + assert.Equal(t, expected, *ds.Requested.XMLID, "Expected XMLID to be %v, but got %v", expected, *ds.Requested.XMLID) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validatePutDSRequestFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + dsReqResp := resp.(tc.DeliveryServiceRequestV40) + for field, expected := range expectedResp { + switch field { + case "STATUS": + assert.Equal(t, expected, dsReqResp.Status, "Expected status to be %v, but got %v", expected, dsReqResp.Status) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } +} + +func CreateTestDeliveryServiceRequests(t *testing.T) { + for _, dsr := range testData.DeliveryServiceRequests { + resetDS(dsr.Original) + resetDS(dsr.Requested) + respDSR, _, err := TOSession.CreateDeliveryServiceRequest(dsr, client.RequestOptions{}) + assert.NoError(t, err, "Could not create Delivery Service Requests: %v - alerts: %+v", err, respDSR.Alerts) + } +} + +func DeleteTestDeliveryServiceRequests(t *testing.T) { + resp, _, err := TOSession.GetDeliveryServiceRequests(client.RequestOptions{}) + assert.NoError(t, err, "Cannot get Delivery Service Requests: %v - alerts: %+v", err, resp.Alerts) + for _, request := range resp.Response { + alert, _, err := TOSession.DeleteDeliveryServiceRequest(*request.ID, client.RequestOptions{}) + assert.NoError(t, err, "Cannot delete Delivery Service Request #%d: %v - alerts: %+v", request.ID, err, alert.Alerts) + + // Retrieve the DeliveryServiceRequest to see if it got deleted + opts := client.NewRequestOptions() + opts.QueryParameters.Set("id", strconv.Itoa(*request.ID)) + dsr, _, err := TOSession.GetDeliveryServiceRequests(opts) + assert.NoError(t, err, "Unexpected error fetching Delivery Service Request #%d after deletion: %v - alerts: %+v", *request.ID, err, dsr.Alerts) + assert.Equal(t, len(dsr.Response), 0, "Expected Delivery Service Request #%d to be deleted, but it was found in Traffic Ops", *request.ID) + } +} diff --git a/traffic_ops/testing/api/v5/deliveryservices_keys_test.go b/traffic_ops/testing/api/v5/deliveryservices_keys_test.go new file mode 100644 index 0000000000..eb9e526dee --- /dev/null +++ b/traffic_ops/testing/api/v5/deliveryservices_keys_test.go @@ -0,0 +1,611 @@ +package v5 + +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "strconv" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/lib/go-util" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/deliveryservice" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestDeliveryServicesKeys(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Users, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServerCapabilities, ServiceCategories, DeliveryServices}, func() { + if !includeSystemTests { + t.Skip() + } + t.Run("Verify SSL key generation on DS creation", VerifySSLKeysOnDsCreationTest) + t.Run("Update CDN for a Delivery Service with SSL keys", SSLDeliveryServiceCDNUpdateTest) + t.Run("Create URL Signature keys for a Delivery Service", CreateTestDeliveryServicesURLSignatureKeys) + t.Run("Retrieve URL Signature keys for a Delivery Service", GetTestDeliveryServicesURLSignatureKeys) + t.Run("Delete URL Signature keys for a Delivery Service", DeleteTestDeliveryServicesURLSignatureKeys) + t.Run("Create URI Signing Keys for a Delivery Service", CreateTestDeliveryServicesURISigningKeys) + t.Run("Retrieve URI Signing keys for a Delivery Service", GetTestDeliveryServicesURISigningKeys) + t.Run("Delete URI Signing keys for a Delivery Service", DeleteTestDeliveryServicesURISigningKeys) + t.Run("Delete old CDN SSL keys", DeleteCDNOldSSLKeys) + t.Run("Create and retrieve SSL keys for a Delivery Service", DeliveryServiceSSLKeys) + }) +} + +func createBlankCDN(cdnName string, t *testing.T) tc.CDN { + _, _, err := TOSession.CreateCDN(tc.CDN{ + DNSSECEnabled: false, + DomainName: cdnName + ".ai", + Name: cdnName, + }, client.RequestOptions{}) + assert.RequireNoError(t, err, "Expected no error when creating cdn: %v", err) + + originalKeys, _, err := TOSession.GetCDNSSLKeys(cdnName, client.RequestOptions{}) + assert.RequireNoError(t, err, "Expected no error when getting cdn ssl keys: %v", err) + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", cdnName) + cdns, _, err := TOSession.GetCDNs(opts) + assert.RequireNoError(t, err, "Unable to get cdn: %v", err) + assert.RequireGreaterOrEqual(t, len(cdns.Response), 1, "Expected more than 0 cdns") + + keys, _, err := TOSession.GetCDNSSLKeys(cdnName, client.RequestOptions{}) + assert.RequireNoError(t, err, "Expected no error when getting cdn ssl keys: %v", err) + assert.RequireEqual(t, len(keys.Response), len(originalKeys.Response), "Expected %v ssl keys on cdn %v, got %v", len(originalKeys.Response), cdnName, len(keys.Response)) + + return cdns.Response[0] +} + +func cleanUp(t *testing.T, ds tc.DeliveryServiceV4, oldCDNID int, newCDNID int, sslKeyVersions []string) { + if ds.ID == nil || ds.XMLID == nil { + t.Error("Cannot clean up Delivery Service with nil ID and/or XMLID") + return + } + xmlid := *ds.XMLID + id := *ds.ID + + opts := client.NewRequestOptions() + for _, version := range sslKeyVersions { + opts.QueryParameters.Set("version", version) + resp, _, err := TOSession.DeleteDeliveryServiceSSLKeys(xmlid, opts) + assert.NoError(t, err, "Unexpected error deleting Delivery Service SSL Keys: %v - alerts: %+v", err, resp.Alerts) + } + resp, _, err := TOSession.DeleteDeliveryService(id, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error deleting Delivery Service '%s' (#%d) during cleanup: %v - alerts: %+v", xmlid, id, err, resp.Alerts) + + if oldCDNID != -1 { + resp2, _, err := TOSession.DeleteCDN(oldCDNID, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error deleting CDN (#%d) during cleanup: %v - alerts: %+v", oldCDNID, err, resp2.Alerts) + } + if newCDNID != -1 { + resp2, _, err := TOSession.DeleteCDN(newCDNID, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error deleting CDN (#%d) during cleanup: %v - alerts: %+v", newCDNID, err, resp2.Alerts) + } +} + +// getCustomDS returns a DS that is guaranteed to have non-nil: +// +// Active +// CDNID +// DSCP +// DisplayName +// RoutingName +// GeoLimit +// GeoProvider +// IPV6RoutingEnabled +// InitialDispersion +// LogsEnabled +// MissLat +// MissLong +// MultiSiteOrigin +// OrgServerFQDN +// Protocol +// QStringIgnore +// RangeRequestHandling +// RegionalGeoBlocking +// TenantID +// TypeID +// XMLID +// +// BUT, will ALWAYS have nil MaxRequestHeaderBytes. +// Note that the Tenant is hard-coded to #1. +func getCustomDS(cdnID, typeID int, displayName, routingName, orgFQDN, dsID string) tc.DeliveryServiceV4 { + customDS := tc.DeliveryServiceV4{} + customDS.Active = util.BoolPtr(true) + customDS.CDNID = util.IntPtr(cdnID) + customDS.DSCP = util.IntPtr(0) + customDS.DisplayName = util.StrPtr(displayName) + customDS.RoutingName = util.StrPtr(routingName) + customDS.GeoLimit = util.IntPtr(0) + customDS.GeoProvider = util.IntPtr(0) + customDS.IPV6RoutingEnabled = util.BoolPtr(false) + customDS.InitialDispersion = util.IntPtr(1) + customDS.LogsEnabled = util.BoolPtr(true) + customDS.MissLat = util.FloatPtr(0) + customDS.MissLong = util.FloatPtr(0) + customDS.MultiSiteOrigin = util.BoolPtr(false) + customDS.OrgServerFQDN = util.StrPtr(orgFQDN) + customDS.Protocol = util.IntPtr(2) + customDS.QStringIgnore = util.IntPtr(0) + customDS.RangeRequestHandling = util.IntPtr(0) + customDS.RegionalGeoBlocking = util.BoolPtr(false) + customDS.TenantID = util.IntPtr(1) + customDS.TypeID = util.IntPtr(typeID) + customDS.XMLID = util.StrPtr(dsID) + customDS.MaxRequestHeaderBytes = nil + return customDS +} + +func DeleteCDNOldSSLKeys(t *testing.T) { + cdn := createBlankCDN("sslkeytransfer", t) + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", "HTTP") + types, _, err := TOSession.GetTypes(opts) + assert.RequireNoError(t, err, "Unable to get Types: %v - alerts: %+v", err, types.Alerts) + assert.RequireGreaterOrEqual(t, len(types.Response), 1, "Expected at least one type") + + // First DS creation + customDS := getCustomDS(cdn.ID, types.Response[0].ID, "displayName", "routingName", "https://test.com", "dsID") + + resp, _, err := TOSession.CreateDeliveryService(customDS, client.RequestOptions{}) + assert.RequireNoError(t, err, "Unexpected error creating a Delivery Service: %v - alerts: %+v", err, resp.Alerts) + assert.RequireEqual(t, len(resp.Response), 1, "Expected Delivery Service creation to return exactly one Delivery Service, got: %d", len(resp.Response)) + + ds := resp.Response[0] + assert.RequireNotNil(t, ds.XMLID, "Traffic Ops returned a representation for a Delivery Service with null or undefined XMLID") + + ds.CDNName = &cdn.Name + sslKeyRequestFields := tc.SSLKeyRequestFields{ + BusinessUnit: util.StrPtr("BU"), + City: util.StrPtr("CI"), + Organization: util.StrPtr("OR"), + HostName: util.StrPtr("*.test.com"), + Country: util.StrPtr("CO"), + State: util.StrPtr("ST"), + } + genResp, _, err := TOSession.GenerateSSLKeysForDS(*ds.XMLID, *ds.CDNName, sslKeyRequestFields, client.RequestOptions{}) + assert.RequireNoError(t, err, "Unexpected error generaing SSL Keys for Delivery Service '%s': %v - alerts: %+v", *ds.XMLID, err, genResp.Alerts) + + defer cleanUp(t, ds, cdn.ID, -1, []string{"1"}) + + // Second DS creation + customDS2 := getCustomDS(cdn.ID, types.Response[0].ID, "displayName2", "routingName2", "https://test2.com", "dsID2") + + resp, _, err = TOSession.CreateDeliveryService(customDS2, client.RequestOptions{}) + assert.RequireNoError(t, err, "Unexpected error creating a Delivery Service: %v - alerts: %+v", err, resp.Alerts) + assert.RequireEqual(t, len(resp.Response), 1, "Expected Delivery Service creation to return exactly one Delivery Service, got: %d", len(resp.Response)) + + ds2 := resp.Response[0] + assert.RequireNotNil(t, ds2.XMLID, "Traffic Ops returned a representation for a Delivery Service with null or undefined XMLID") + + ds2.CDNName = &cdn.Name + sslKeyRequestFields.HostName = util.StrPtr("*.test2.com") + genResp, _, err = TOSession.GenerateSSLKeysForDS(*ds2.XMLID, *ds2.CDNName, sslKeyRequestFields, client.RequestOptions{}) + assert.RequireNoError(t, err, "Unexpected error generaing SSL Keys for Delivery Service '%s': %v - alerts: %+v", *ds2.XMLID, err, genResp.Alerts) + + var cdnKeys []tc.CDNSSLKeys + for tries := 0; tries < 5; tries++ { + time.Sleep(time.Second) + var sslKeysResp tc.CDNSSLKeysResponse + sslKeysResp, _, err = TOSession.GetCDNSSLKeys(cdn.Name, client.RequestOptions{}) + if err != nil { + continue + } + cdnKeys = sslKeysResp.Response + if len(cdnKeys) != 0 { + break + } + } + + assert.RequireNoError(t, err, "Unable to get CDN %v SSL keys: %v", cdn.Name, err) + assert.RequireEqual(t, len(cdnKeys), 2, "Expected two ssl keys for CDN %v, got %d instead", cdn.Name, len(cdnKeys)) + + delResp, _, err := TOSession.DeleteDeliveryService(*ds2.ID, client.RequestOptions{}) + assert.RequireNoError(t, err, "Unexpected error deleting Delivery Service #%d: %v - alerts: %+v", *ds2.ID, err, delResp.Alerts) + + opts = client.NewRequestOptions() + opts.QueryParameters.Set("cdnID", strconv.Itoa(cdn.ID)) + snapResp, _, err := TOSession.SnapshotCRConfig(opts) + assert.RequireNoError(t, err, "Failed to take Snapshot of CDN #%d: %v - alerts: %+v", cdn.ID, err, snapResp.Alerts) + + var newCdnKeys []tc.CDNSSLKeys + for tries := 0; tries < 5; tries++ { + time.Sleep(time.Second) + var sslKeysResp tc.CDNSSLKeysResponse + sslKeysResp, _, err = TOSession.GetCDNSSLKeys(cdn.Name, client.RequestOptions{}) + newCdnKeys = sslKeysResp.Response + if err == nil && len(newCdnKeys) == 1 { + break + } + } + + assert.RequireNoError(t, err, "Unable to get CDN %v SSL keys: %v", cdn.Name, err) + assert.RequireEqual(t, len(newCdnKeys), 1, "Expected 1 ssl keys for CDN %v, got %d instead", cdn.Name, len(newCdnKeys)) +} + +func DeliveryServiceSSLKeys(t *testing.T) { + cdn := createBlankCDN("sslkeytransfer", t) + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", "HTTP") + types, _, err := TOSession.GetTypes(opts) + assert.RequireNoError(t, err, "Unable to get Types: %v - alerts: %+v", err, types.Alerts) + assert.RequireGreaterOrEqual(t, len(types.Response), 1, "Expected at least one type") + + customDS := getCustomDS(cdn.ID, types.Response[0].ID, "displayName", "routingName", "https://test.com", "dsID") + + resp, _, err := TOSession.CreateDeliveryService(customDS, client.RequestOptions{}) + assert.RequireNoError(t, err, "Unexpected error creating a Delivery Service: %v - alerts: %+v", err, resp.Alerts) + assert.RequireEqual(t, len(resp.Response), 1, "Expected Delivery Service creation to return exactly one Delivery Service, got: %d", len(resp.Response)) + + ds := resp.Response[0] + assert.RequireNotNil(t, ds.XMLID, "Traffic Ops returned a representation for a Delivery Service with null or undefined XMLID") + + ds.CDNName = &cdn.Name + genResp, _, err := TOSession.GenerateSSLKeysForDS(*ds.XMLID, *ds.CDNName, tc.SSLKeyRequestFields{ + BusinessUnit: util.StrPtr("BU"), + City: util.StrPtr("CI"), + Organization: util.StrPtr("OR"), + HostName: util.StrPtr("*.test2.com"), + Country: util.StrPtr("CO"), + State: util.StrPtr("ST"), + }, client.RequestOptions{}) + assert.RequireNoError(t, err, "Unexpected error generating SSL Keys for Delivery Service '%s': %v - alerts: %+v", *ds.XMLID, err, genResp.Alerts) + + defer cleanUp(t, ds, cdn.ID, -1, []string{"1"}) + + dsSSLKey := new(tc.DeliveryServiceSSLKeys) + for tries := 0; tries < 5; tries++ { + time.Sleep(time.Second) + var sslKeysResp tc.DeliveryServiceSSLKeysResponse + sslKeysResp, _, err = TOSession.GetDeliveryServiceSSLKeys(*ds.XMLID, client.RequestOptions{}) + *dsSSLKey = sslKeysResp.Response + if err == nil && dsSSLKey != nil { + break + } + } + + if err != nil || dsSSLKey == nil { + t.Fatalf("unable to get DS %s SSL key: %v", *ds.XMLID, err) + } + if dsSSLKey.Certificate.Key == "" { + t.Errorf("expected a valid key but got nothing") + } + if dsSSLKey.Certificate.Crt == "" { + t.Errorf("expected a valid certificate, but got nothing") + } + if dsSSLKey.Certificate.CSR == "" { + t.Errorf("expected a valid CSR, but got nothing") + } + + err = deliveryservice.Base64DecodeCertificate(&dsSSLKey.Certificate) + assert.RequireNoError(t, err, "Couldn't decode certificate: %v", err) + + dsSSLKeyReq := tc.DeliveryServiceSSLKeysReq{ + AuthType: &dsSSLKey.AuthType, + CDN: &dsSSLKey.CDN, + DeliveryService: &dsSSLKey.DeliveryService, + BusinessUnit: &dsSSLKey.BusinessUnit, + City: &dsSSLKey.City, + Organization: &dsSSLKey.Organization, + HostName: &dsSSLKey.Hostname, + Country: &dsSSLKey.Country, + State: &dsSSLKey.State, + Key: &dsSSLKey.Key, + Version: &dsSSLKey.Version, + Certificate: &dsSSLKey.Certificate, + } + addSSLKeysResp, _, err := TOSession.AddSSLKeysForDS(tc.DeliveryServiceAddSSLKeysReq{DeliveryServiceSSLKeysReq: dsSSLKeyReq}, client.RequestOptions{}) + assert.RequireNoError(t, err, "Unexpected error adding SSL keys for Delivery Service '%s': %v - alerts: %+v", dsSSLKey.DeliveryService, err, addSSLKeysResp.Alerts) + + dsSSLKey = new(tc.DeliveryServiceSSLKeys) + for tries := 0; tries < 5; tries++ { + time.Sleep(time.Second) + var sslKeysResp tc.DeliveryServiceSSLKeysResponse + sslKeysResp, _, err = TOSession.GetDeliveryServiceSSLKeys(*ds.XMLID, client.RequestOptions{}) + *dsSSLKey = sslKeysResp.Response + if err == nil && dsSSLKey != nil { + break + } + } + + if err != nil || dsSSLKey == nil { + t.Fatalf("unable to get DS %s SSL key: %v", *ds.XMLID, err) + } + if dsSSLKey.Certificate.Key == "" { + t.Errorf("expected a valid key but got nothing") + } + if dsSSLKey.Certificate.Crt == "" { + t.Errorf("expected a valid certificate, but got nothing") + } + if dsSSLKey.Certificate.CSR == "" { + t.Errorf("expected a valid CSR, but got nothing") + } +} + +func VerifySSLKeysOnDsCreationTest(t *testing.T) { + for _, ds := range testData.DeliveryServices { + if !(*ds.Protocol == tc.DSProtocolHTTPS || *ds.Protocol == tc.DSProtocolHTTPAndHTTPS || *ds.Protocol == tc.DSProtocolHTTPToHTTPS) { + continue + } + var err error + dsSSLKey := new(tc.DeliveryServiceSSLKeys) + for tries := 0; tries < 5; tries++ { + time.Sleep(time.Second) + var sslKeysResp tc.DeliveryServiceSSLKeysResponse + sslKeysResp, _, err = TOSession.GetDeliveryServiceSSLKeys(*ds.XMLID, client.RequestOptions{}) + *dsSSLKey = sslKeysResp.Response + if err == nil && dsSSLKey != nil { + break + } + } + + if err != nil || dsSSLKey == nil { + t.Fatalf("unable to get DS %s SSL key: %v", *ds.XMLID, err) + } + if dsSSLKey.Certificate.Key == "" { + t.Errorf("expected a valid key but got nothing") + } + if dsSSLKey.Certificate.Crt == "" { + t.Errorf("expected a valid certificate, but got nothing") + } + if dsSSLKey.Certificate.CSR == "" { + t.Errorf("expected a valid CSR, but got nothing") + } + + err = deliveryservice.Base64DecodeCertificate(&dsSSLKey.Certificate) + if err != nil { + t.Fatalf("couldn't decode certificate: %v", err) + } + } +} + +func SSLDeliveryServiceCDNUpdateTest(t *testing.T) { + cdnNameOld := "sslkeytransfer" + oldCdn := createBlankCDN(cdnNameOld, t) + cdnNameNew := "sslkeytransfer1" + newCdn := createBlankCDN(cdnNameNew, t) + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", "HTTP") + types, _, err := TOSession.GetTypes(opts) + assert.RequireNoError(t, err, "Unable to get Types: %v - alerts: %+v", err, types.Alerts) + assert.RequireGreaterOrEqual(t, len(types.Response), 1, "expected at least one type") + + customDS := getCustomDS(oldCdn.ID, types.Response[0].ID, "displayName", "routingName", "https://test.com", "dsID") + + resp, _, err := TOSession.CreateDeliveryService(customDS, client.RequestOptions{}) + assert.RequireNoError(t, err, "Unexpected error creating a custom Delivery Service: %v - alerts: %+v", err, resp.Alerts) + assert.RequireEqual(t, len(resp.Response), 1, "Expected Delivery Service creation to create exactly one Delivery Service, Traffic Ops indicates %d were created", len(resp.Response)) + + ds := resp.Response[0] + assert.NotNil(t, ds.XMLID, "Traffic Ops created a Delivery Service with null or undefined XMLID") + + ds.CDNName = &oldCdn.Name + + defer cleanUp(t, ds, oldCdn.ID, newCdn.ID, []string{"1"}) + + _, _, err = TOSession.GenerateSSLKeysForDS(*ds.XMLID, *ds.CDNName, tc.SSLKeyRequestFields{ + BusinessUnit: util.StrPtr("BU"), + City: util.StrPtr("CI"), + Organization: util.StrPtr("OR"), + HostName: util.StrPtr("*.test.com"), + Country: util.StrPtr("CO"), + State: util.StrPtr("ST"), + }, client.RequestOptions{}) + assert.RequireNoError(t, err, "Unable to generate sslkeys for cdn %v: %v", oldCdn.Name, err) + + var oldCDNKeys []tc.CDNSSLKeys + for tries := 0; tries < 5; tries++ { + time.Sleep(time.Second) + resp, _, err := TOSession.GetCDNSSLKeys(oldCdn.Name, client.RequestOptions{}) + oldCDNKeys = resp.Response + if err == nil && len(oldCDNKeys) > 0 { + break + } + } + assert.RequireNoError(t, err, "Unable to get cdn %v keys: %v", oldCdn.Name, err) + assert.RequireEqual(t, len(oldCDNKeys), 1, "Expected at least 1 key") + + newCDNKeys, _, err := TOSession.GetCDNSSLKeys(newCdn.Name, client.RequestOptions{}) + assert.RequireNoError(t, err, "Unable to get cdn %v keys: %v", newCdn.Name, err) + + ds.RoutingName = util.StrPtr("anothername") + _, _, err = TOSession.UpdateDeliveryService(*ds.ID, ds, client.RequestOptions{}) + assert.RequireNotNil(t, err, "Should not be able to update delivery service (routing name) as it has ssl keys") + + ds.RoutingName = util.StrPtr("routingName") + + ds.CDNID = &newCdn.ID + ds.CDNName = &newCdn.Name + _, _, err = TOSession.UpdateDeliveryService(*ds.ID, ds, client.RequestOptions{}) + assert.RequireNotNil(t, err, "Should not be able to update delivery service (cdn) as it has ssl keys") + + // Check new CDN still has an ssl key + keys, _, err := TOSession.GetCDNSSLKeys(newCdn.Name, client.RequestOptions{}) + assert.RequireNoError(t, err, "Unable to get cdn %v keys: %v - alerts: %+v", newCdn.Name, err, keys.Alerts) + assert.RequireEqual(t, len(keys.Response), len(newCDNKeys.Response), "Expected %v keys, got %v", len(newCDNKeys.Response), len(keys.Response)) + + // Check old CDN does not have ssl key + keys, _, err = TOSession.GetCDNSSLKeys(oldCdn.Name, client.RequestOptions{}) + assert.RequireNoError(t, err, "Unable to get cdn %v keys: %v - %+v", oldCdn.Name, err, keys.Alerts) + assert.RequireEqual(t, len(keys.Response), len(oldCDNKeys), "Expected %v key, got %v", len(oldCDNKeys), len(keys.Response)) +} + +func GetTestDeliveryServicesURLSignatureKeys(t *testing.T) { + assert.RequireGreaterOrEqual(t, len(testData.DeliveryServices), 1, "Couldn't get the xml ID of test DS") + firstDS := testData.DeliveryServices[0] + assert.RequireNotNil(t, firstDS.XMLID, "Found a Delivery Service in testing data with a null or undefined XMLID") + + _, _, err := TOSession.GetDeliveryServiceURLSignatureKeys(*firstDS.XMLID, client.RequestOptions{}) + assert.RequireNoError(t, err, "Failed to get url sig keys: %v", err) +} + +func CreateTestDeliveryServicesURLSignatureKeys(t *testing.T) { + assert.RequireGreaterOrEqual(t, len(testData.DeliveryServices), 1, "Couldn't get the xml ID of test DS") + firstDS := testData.DeliveryServices[0] + assert.RequireNotNil(t, firstDS.XMLID, "Found a Delivery Service in testing data with a null or undefined XMLID") + + resp, _, err := TOSession.CreateDeliveryServiceURLSignatureKeys(*firstDS.XMLID, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error creating URL signing keys: %v - alerts: %+v", err, resp.Alerts) + + firstKeys, _, err := TOSession.GetDeliveryServiceURLSignatureKeys(*firstDS.XMLID, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error getting URL signing keys: %v - alerts: %+v", err, firstKeys.Alerts) + assert.GreaterOrEqual(t, len(firstKeys.Response), 1, "failed to create URL signing keys") + + firstKey, ok := firstKeys.Response["key0"] + assert.RequireEqual(t, ok, true, "Expected to find 'key0' in URL signing keys, but didn't") + + // Create new keys again and check that they are different + resp, _, err = TOSession.CreateDeliveryServiceURLSignatureKeys(*firstDS.XMLID, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error creating URL signing keys: %v - alerts: %+v", err, resp.Alerts) + + secondKeys, _, err := TOSession.GetDeliveryServiceURLSignatureKeys(*firstDS.XMLID, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error getting URL signing keys: %v - alerts: %+v", err, secondKeys.Alerts) + assert.GreaterOrEqual(t, len(secondKeys.Response), 0, "Failed to create url sig keys") + + secondKey, ok := secondKeys.Response["key0"] + assert.RequireEqual(t, ok, true, "Expected to find 'key0' in URL signing keys, but didn't") + + if secondKey == firstKey { + t.Errorf("second create did not generate new url sig keys") + } +} + +func DeleteTestDeliveryServicesURLSignatureKeys(t *testing.T) { + assert.RequireGreaterOrEqual(t, len(testData.DeliveryServices), 1, "Couldn't get the xml ID of test DS") + firstDS := testData.DeliveryServices[0] + assert.RequireNotNil(t, firstDS.XMLID, "Found a Delivery Service in testing data with a null or undefined XMLID") + + resp, _, err := TOSession.DeleteDeliveryServiceURLSignatureKeys(*firstDS.XMLID, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error deleting URL signing keys: %v - alerts: %+v", err, resp.Alerts) +} + +func GetTestDeliveryServicesURISigningKeys(t *testing.T) { + assert.RequireGreaterOrEqual(t, len(testData.DeliveryServices), 1, "Couldn't get the xml ID of test DS") + firstDS := testData.DeliveryServices[0] + assert.RequireNotNil(t, firstDS.XMLID, "Found a Delivery Service in testing data with a null or undefined XMLID") + + _, _, err := TOSession.GetDeliveryServiceURISigningKeys(*firstDS.XMLID, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error getting URI signing keys for Delivery Service '%s': %v", *firstDS.XMLID, err) +} + +const ( + keySet1 = ` +{ + "Kabletown URI Authority 1": { + "renewal_kid": "First Key", + "keys": [ + { + "alg": "HS256", + "kid": "First Key", + "kty": "oct", + "k": "Kh_RkUMj-fzbD37qBnDf_3e_RvQ3RP9PaSmVEpE24AM" + } + ] + } +}` + keySet2 = ` +{ +"Kabletown URI Authority 1": { + "renewal_kid": "New First Key", + "keys": [ + { + "alg": "HS256", + "kid": "New First Key", + "kty": "oct", + "k": "Kh_RkUMj-fzbD37qBnDf_3e_RvQ3RP9PaSmVEpE24AM" + } + ] + } +}` +) + +func CreateTestDeliveryServicesURISigningKeys(t *testing.T) { + assert.RequireGreaterOrEqual(t, len(testData.DeliveryServices), 1, "Couldn't get the xml ID of test DS") + firstDS := testData.DeliveryServices[0] + assert.RequireNotNil(t, firstDS.XMLID, "Found a Delivery Service in testing data with a null or undefined XMLID") + + var keyset tc.JWKSMap + + err := json.Unmarshal([]byte(keySet1), &keyset) + assert.NoError(t, err, "json.UnMarshal(): expected nil error, actual: %v", err) + + _, _, err = TOSession.CreateDeliveryServiceURISigningKeys(*firstDS.XMLID, keyset, client.RequestOptions{}) + assert.NoError(t, err, "failed to create uri sig keys: %v", err) + + firstKeysBytes, _, err := TOSession.GetDeliveryServiceURISigningKeys(*firstDS.XMLID, client.RequestOptions{}) + assert.NoError(t, err, "Failed to get uri sig keys: %v", err) + + firstKeys := tc.JWKSMap{} + err = json.Unmarshal(firstKeysBytes, &firstKeys) + assert.NoError(t, err, "Failed to unmarshal uri sig keys") + + kabletownFirstKeys, ok := firstKeys["Kabletown URI Authority 1"] + assert.Equal(t, ok, true, "Failed to create uri sig keys: 'Kabletown URI Authority 1' not found in response after creation") + assert.GreaterOrEqual(t, kabletownFirstKeys.Len(), 1, "Failed to create URI signing keys: 'Kabletown URI Authority 1' had zero keys after creation") + + // Create new keys again and check that they are different + var keyset2 tc.JWKSMap + + err = json.Unmarshal([]byte(keySet2), &keyset2) + assert.NoError(t, err, "json.UnMarshal(): expected nil error, actual: %v", err) + + alerts, _, err := TOSession.CreateDeliveryServiceURISigningKeys(*firstDS.XMLID, keyset2, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error creating URI Signature Keys for Delivery Service '%s': %v - alerts: %+v", *firstDS.XMLID, err, alerts.Alerts) + + secondKeysBytes, _, err := TOSession.GetDeliveryServiceURISigningKeys(*firstDS.XMLID, client.RequestOptions{}) + assert.NoError(t, err, "Failed to get uri sig keys: %v", err) + secondKeys := tc.JWKSMap{} + err = json.Unmarshal(secondKeysBytes, &secondKeys) + assert.NoError(t, err, "Failed to unmarshal uri sig keys") + + kabletownSecondKeys, ok := secondKeys["Kabletown URI Authority 1"] + assert.Equal(t, ok, true, "failed to create uri sig keys: 'Kabletown URI Authority 1' not found in response after creation") + assert.GreaterOrEqual(t, kabletownSecondKeys.Len(), 1, "Failed to create URI signing keys: 'Kabletown URI Authority 1' had zero keys after creation") + + k1, ok := kabletownFirstKeys.Get(0) + assert.Equal(t, ok, true, "Failed to get key 0 from kabletownFirstKeys") + + k2, ok := kabletownSecondKeys.Get(0) + assert.Equal(t, ok, true, "Failed to get key 0 from kabletownSecondKeys") + + if k2.KeyID() == k1.KeyID() { + t.Errorf("Second create did not generate new uri sig keys - key mismatch") + } +} + +func DeleteTestDeliveryServicesURISigningKeys(t *testing.T) { + assert.RequireGreaterOrEqual(t, len(testData.DeliveryServices), 1, "Couldn't get the xml ID of test DS") + firstDS := testData.DeliveryServices[0] + assert.RequireNotNil(t, firstDS.XMLID, "Found a Delivery Service in testing data with a null or undefined XMLID") + + resp, _, err := TOSession.DeleteDeliveryServiceURISigningKeys(*firstDS.XMLID, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error deleting URI Signing keys for Delivery Service '%s': %v - alerts: %+v", *firstDS.XMLID, err, resp.Alerts) + + emptyBytes, _, err := TOSession.GetDeliveryServiceURISigningKeys(*firstDS.XMLID, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error getting URI signing keys for Delivery Service '%s': %v", *firstDS.XMLID, err) + + emptyMap := make(map[string]interface{}) + err = json.Unmarshal(emptyBytes, &emptyMap) + assert.NoError(t, err, "Unexpected error unmarshalling empty URI signing keys response: %v", err) + + renewalKid, hasRenewalKid := emptyMap["renewal_kid"] + keys, hasKeys := emptyMap["keys"] + assert.Equal(t, hasRenewalKid, true, "Getting empty URI signing keys - expected: 'renewal_kid' key, actual: not present") + assert.Equal(t, hasKeys, true, "Getting empty URI signing keys - expected: 'keys' key, actual: not present") + assert.Equal(t, renewalKid, nil, "Getting empty URI signing keys - expected: 'renewal_kid' value to be nil, actual: %+v", renewalKid) + assert.Equal(t, keys, nil, "Getting empty URI signing keys - expected: 'keys' value to be nil, actual: %+v", keys) +} diff --git a/traffic_ops/testing/api/v5/deliveryservices_required_capabilities_test.go b/traffic_ops/testing/api/v5/deliveryservices_required_capabilities_test.go new file mode 100644 index 0000000000..d815a29f9a --- /dev/null +++ b/traffic_ops/testing/api/v5/deliveryservices_required_capabilities_test.go @@ -0,0 +1,285 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "strconv" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestDeliveryServicesRequiredCapabilities(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Users, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, ServerCapabilities, Topologies, ServiceCategories, DeliveryServices, DeliveryServiceServerAssignments, ServerServerCapabilities, DeliveryServicesRequiredCapabilities}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID request": { + ClientSession: TOSession, Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK when VALID DELIVERYSERVICEID parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"deliveryServiceId": {"ds1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateDSRCExpectedFields(map[string]interface{}{"DeliveryServiceId": "ds1"})), + }, + "OK when VALID XMLID parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"xmlID": {"ds2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateDSRCExpectedFields(map[string]interface{}{"XMLID": "ds2"})), + }, + "OK when VALID REQUIREDCAPABILITY parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"requiredCapability": {"bar"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateDSRCExpectedFields(map[string]interface{}{"RequiredCapability": "bar"})), + }, + "FIRST RESULT when LIMIT=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"requiredCapability"}, "limit": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateDSRCPagination("limit")), + }, + "SECOND RESULT when LIMIT=1 OFFSET=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"requiredCapability"}, "limit": {"1"}, "offset": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateDSRCPagination("offset")), + }, + "SECOND RESULT when LIMIT=1 PAGE=2": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"requiredCapability"}, "limit": {"1"}, "page": {"2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateDSRCPagination("page")), + }, + "BAD REQUEST when INVALID LIMIT parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"-2"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID OFFSET parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "offset": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID PAGE parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "page": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "POST": { + "BAD REQUEST when REASSIGNING REQUIRED CAPABILITY to DELIVERY SERVICE": { + ClientSession: TOSession, RequestBody: map[string]interface{}{ + "deliveryServiceID": GetDeliveryServiceId(t, "ds1")(), + "requiredCapability": "foo", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when SERVERS DONT have CAPABILITY": { + ClientSession: TOSession, RequestBody: map[string]interface{}{ + "deliveryServiceID": GetDeliveryServiceId(t, "test-ds-server-assignments")(), + "requiredCapability": "disk", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when DELIVERY SERVICE HAS TOPOLOGY where SERVERS DONT have CAPABILITY": { + ClientSession: TOSession, RequestBody: map[string]interface{}{ + "deliveryServiceID": GetDeliveryServiceId(t, "ds-top-req-cap")(), + "requiredCapability": "bar", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when DELIVERY SERVICE ID EMPTY": { + ClientSession: TOSession, RequestBody: map[string]interface{}{ + "requiredCapability": "bar", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when REQUIRED CAPABILITY EMPTY": { + ClientSession: TOSession, RequestBody: map[string]interface{}{ + "deliveryServiceID": GetDeliveryServiceId(t, "ds1")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "NOT FOUND when NON-EXISTENT REQUIRED CAPABILITY": { + ClientSession: TOSession, RequestBody: map[string]interface{}{ + "deliveryServiceID": GetDeliveryServiceId(t, "ds1")(), + "requiredCapability": "bogus", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "NOT FOUND when NON-EXISTENT DELIVERY SERVICE ID": { + ClientSession: TOSession, RequestBody: map[string]interface{}{ + "deliveryServiceID": -1, + "requiredCapability": "foo", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "BAD REQUEST when INVALID DELIVERY SERVICE TYPE": { + ClientSession: TOSession, RequestBody: map[string]interface{}{ + "deliveryServiceID": GetDeliveryServiceId(t, "anymap-ds")(), + "requiredCapability": "foo", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "DELETE": { + "OK when VALID request": { + EndpointId: GetDeliveryServiceId(t, "ds-top-req-cap"), ClientSession: TOSession, + RequestBody: map[string]interface{}{"requiredCapability": "ram"}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "NOT FOUND when NON-EXISTENT DELIVERYSERVICEID parameter": { + EndpointId: func() int { return -1 }, ClientSession: TOSession, + RequestBody: map[string]interface{}{"requiredCapability": "foo"}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "NOT FOUND when NON-EXISTENT REQUIREDCAPABILITY parameter": { + EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession, + RequestBody: map[string]interface{}{"requiredCapability": "bogus"}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + }, + "GET AFTER CHANGES": { + "OK when CHANGES made": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {currentTimeRFC}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + dsrc := tc.DeliveryServicesRequiredCapability{} + + if val, ok := testCase.RequestOpts.QueryParameters["deliveryServiceId"]; ok { + if _, err := strconv.Atoi(val[0]); err != nil { + testCase.RequestOpts.QueryParameters.Set("deliveryServiceId", strconv.Itoa(GetDeliveryServiceId(t, val[0])())) + } + } + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &dsrc) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET", "GET AFTER CHANGES": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetDeliveryServicesRequiredCapabilities(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.CreateDeliveryServicesRequiredCapability(dsrc, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.DeleteDeliveryServicesRequiredCapability(testCase.EndpointId(), testCase.RequestBody["requiredCapability"].(string), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) + +} + +func validateDSRCExpectedFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + dsrcResp := resp.([]tc.DeliveryServicesRequiredCapability) + for field, expected := range expectedResp { + for _, dsrc := range dsrcResp { + switch field { + case "DeliveryServiceID": + assert.Equal(t, expected, *dsrc.DeliveryServiceID, "Expected deliveryServiceId to be %v, but got %v", expected, dsrc.DeliveryServiceID) + case "XMLID": + assert.Equal(t, expected, *dsrc.XMLID, "Expected xmlID to be %v, but got %v", expected, dsrc.XMLID) + case "RequiredCapability": + assert.Equal(t, expected, *dsrc.RequiredCapability, "Expected requiredCapability to be %v, but got %v", expected, dsrc.RequiredCapability) + } + } + } + } +} + +func validateDSRCPagination(paginationParam string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + paginationResp := resp.([]tc.DeliveryServicesRequiredCapability) + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("orderby", "requiredCapability") + respBase, _, err := TOSession.GetDeliveryServicesRequiredCapabilities(opts) + assert.RequireNoError(t, err, "Cannot get Delivery Services Required Capabilities: %v - alerts: %+v", err, respBase.Alerts) + + dsrc := respBase.Response + assert.RequireGreaterOrEqual(t, len(dsrc), 3, "Need at least 3 Delivery Services Required Capabilities in Traffic Ops to test pagination support, found: %d", len(dsrc)) + switch paginationParam { + case "limit:": + assert.Exactly(t, dsrc[:1], paginationResp, "Expected GET deliveryservices_required_capabilities with limit = 1 to return first result") + case "offset": + assert.Exactly(t, dsrc[1:2], paginationResp, "Expected GET deliveryservices_required_capabilities with limit = 1, offset = 1 to return second result") + case "page": + assert.Exactly(t, dsrc[1:2], paginationResp, "Expected GET deliveryservices_required_capabilities with limit = 1, page = 2 to return second result") + } + } +} + +func CreateTestDeliveryServicesRequiredCapabilities(t *testing.T) { + // Assign all required capability to delivery services listed in `tc-fixtures.json`. + for _, dsrc := range testData.DeliveryServicesRequiredCapabilities { + dsId := GetDeliveryServiceId(t, *dsrc.XMLID)() + dsrc = tc.DeliveryServicesRequiredCapability{ + DeliveryServiceID: &dsId, + RequiredCapability: dsrc.RequiredCapability, + } + resp, _, err := TOSession.CreateDeliveryServicesRequiredCapability(dsrc, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error creating a Delivery Service/Required Capability relationship: %v - alerts: %+v", err, resp.Alerts) + } +} + +func DeleteTestDeliveryServicesRequiredCapabilities(t *testing.T) { + // Get Required Capabilities to delete them + dsrcs, _, err := TOSession.GetDeliveryServicesRequiredCapabilities(client.RequestOptions{}) + assert.NoError(t, err, "Error getting Delivery Service/Required Capability relationships: %v - alerts: %+v", err, dsrcs.Alerts) + + for _, dsrc := range dsrcs.Response { + alerts, _, err := TOSession.DeleteDeliveryServicesRequiredCapability(*dsrc.DeliveryServiceID, *dsrc.RequiredCapability, client.RequestOptions{}) + assert.NoError(t, err, "Error deleting a relationship between a Delivery Service and a Capability: %v - alerts: %+v", err, alerts.Alerts) + } +} diff --git a/traffic_ops/testing/api/v5/deliveryservices_test.go b/traffic_ops/testing/api/v5/deliveryservices_test.go new file mode 100644 index 0000000000..830df157a8 --- /dev/null +++ b/traffic_ops/testing/api/v5/deliveryservices_test.go @@ -0,0 +1,778 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "strconv" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestDeliveryServices(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Users, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServerCapabilities, ServiceCategories, DeliveryServices, ServerServerCapabilities, DeliveryServicesRequiredCapabilities, DeliveryServiceServerAssignments}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + tenant1UserSession := utils.CreateV5Session(t, Config.TrafficOps.URL, "tenant1user", "pa$$word", Config.Default.Session.TimeoutInSecs) + tenant2UserSession := utils.CreateV5Session(t, Config.TrafficOps.URL, "tenant2user", "pa$$word", Config.Default.Session.TimeoutInSecs) + tenant3UserSession := utils.CreateV5Session(t, Config.TrafficOps.URL, "tenant3user", "pa$$word", Config.Default.Session.TimeoutInSecs) + tenant4UserSession := utils.CreateV5Session(t, Config.TrafficOps.URL, "tenant4user", "pa$$word", Config.Default.Session.TimeoutInSecs) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID request": { + ClientSession: TOSession, Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + utils.ResponseLengthGreaterOrEqual(1)), + }, + "OK when VALID ACCESSIBLETO parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"accessibleTo": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1)), + }, + "OK when ACTIVE=TRUE": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"active": {"true"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateDSExpectedFields(map[string]interface{}{"Active": true})), + }, + "OK when ACTIVE=FALSE": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"active": {"false"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateDSExpectedFields(map[string]interface{}{"Active": false})), + }, + "OK when VALID CDN parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateDSExpectedFields(map[string]interface{}{"CDNName": "cdn1"})), + }, + "OK when VALID LOGSENABLED parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"logsEnabled": {"false"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateDSExpectedFields(map[string]interface{}{"LogsEnabled": false})), + }, + "OK when VALID PROFILE parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"profile": {"ATS_EDGE_TIER_CACHE"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateDSExpectedFields(map[string]interface{}{"ProfileName": "ATS_EDGE_TIER_CACHE"})), + }, + "OK when VALID SERVICECATEGORY parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"serviceCategory": {"serviceCategory1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateDSExpectedFields(map[string]interface{}{"ServiceCategory": "serviceCategory1"})), + }, + "OK when VALID TENANT parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"tenant": {"tenant1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateDSExpectedFields(map[string]interface{}{"Tenant": "tenant1"})), + }, + "OK when VALID TOPOLOGY parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"topology": {"mso-topology"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateDSExpectedFields(map[string]interface{}{"Topology": "mso-topology"})), + }, + "OK when VALID TYPE parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"type": {"HTTP"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateDSExpectedFields(map[string]interface{}{"Type": tc.DSTypeHTTP})), + }, + "OK when VALID XMLID parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"xmlId": {"ds1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + validateDSExpectedFields(map[string]interface{}{"XMLID": "ds1"})), + }, + "EMPTY RESPONSE when INVALID ACCESSIBLETO parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"accessibleTo": {"10000"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when INVALID CDN parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"cdn": {"10000"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when INVALID PROFILE parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"profile": {"10000"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when INVALID TENANT parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"tenant": {"10000"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when INVALID TYPE parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"type": {"10000"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when INVALID XMLID parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"xmlId": {"invalid_xml_id"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "FIRST RESULT when LIMIT=1": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validatePagination("limit")), + }, + "SECOND RESULT when LIMIT=1 OFFSET=1": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "offset": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validatePagination("offset")), + }, + "SECOND RESULT when LIMIT=1 PAGE=2": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "page": {"2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validatePagination("page")), + }, + "BAD REQUEST when INVALID LIMIT parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"-2"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID OFFSET parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "offset": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID PAGE parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "page": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "VALID when SORTORDER param is DESC": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"sortOrder": {"desc"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateDescSort()), + }, + "OK when PARENT TENANT reads DS of INACTIVE CHILD TENANT": { + ClientSession: tenant1UserSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"xmlId": {"ds2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1)), + }, + "EMPTY RESPONSE when DS BELONGS to TENANT but PARENT TENANT is INACTIVE": { + ClientSession: tenant3UserSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"xmlId": {"ds3"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when INACTIVE TENANT reads DS of SAME TENANCY": { + ClientSession: tenant2UserSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"xmlId": {"ds2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when TENANT reads DS OUTSIDE TENANCY": { + ClientSession: tenant4UserSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"xmlId": {"ds3"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when CHILD TENANT reads DS of PARENT TENANT": { + ClientSession: tenant3UserSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"xmlId": {"ds2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + }, + "POST": { + "CREATED when VALID request WITH GEO LIMIT COUNTRIES": { + ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "geoLimit": 2, + "geoLimitCountries": []string{"US", "CA"}, + "xmlId": "geolimit-test", + }), + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusCreated), utils.ResponseHasLength(1), + validateDSExpectedFields(map[string]interface{}{"GeoLimitCountries": tc.GeoLimitCountriesType{"US", "CA"}})), + }, + "BAD REQUEST when using LONG DESCRIPTION 2 and 3 fields": { + ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "longDesc1": "long desc 1", + "longDesc2": "long desc 2", + "xmlId": "ld1-ld2-test", + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when XMLID left EMPTY": { + ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "xmlId": "", + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when XMLID is NIL": { + ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "xmlId": nil, + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when TOPOLOGY DOESNT EXIST": { + ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "topology": "topology-doesnt-exist", + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when creating STEERING DS with TLS VERSIONS": { + ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "tlsVersions": []string{"1.1"}, + "typeId": GetTypeId(t, "STEERING"), + "xmlId": "test-TLS-creation-steering", + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "OK when creating HTTP DS with TLS VERSIONS": { + ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "tlsVersions": []string{"1.1"}, + "xmlId": "test-TLS-creation-http", + }), + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusCreated), utils.ResponseHasLength(1)), + }, + "BAD REQUEST when creating DS with TENANCY NOT THE SAME AS CURRENT TENANT": { + ClientSession: tenant4UserSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "tenantId": GetTenantID(t, "tenant3")(), + "xmlId": "test-tenancy", + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden), utils.ResponseHasLength(0)), + }, + }, + "PUT": { + "BAD REQUEST when using LONG DESCRIPTION 2 and 3 fields": { + EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "longDesc1": "long desc 1", + "longDesc2": "long desc 2", + "xmlId": "ds1", + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "OK when VALID request": { + EndpointId: GetDeliveryServiceId(t, "ds2"), ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "maxRequestHeaderBytes": 131080, + "longDesc": "something different", + "maxDNSAnswers": 164598, + "maxOriginConnections": 100, + "active": false, + "displayName": "newds2displayname", + "dscp": 41, + "geoLimit": 1, + "initialDispersion": 2, + "ipv6RoutingEnabled": false, + "logsEnabled": false, + "missLat": 42.881944, + "missLong": -88.627778, + "multiSiteOrigin": true, + "orgServerFqdn": "http://origin.example.net", + "protocol": 2, + "routingName": "ccr-ds2", + "qStringIgnore": 0, + "regionalGeoBlocking": true, + "xmlId": "ds2", + }), + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateDSExpectedFields(map[string]interface{}{"MaxRequestHeaderSize": 131080, + "LongDesc": "something different", "MaxDNSAnswers": 164598, "MaxOriginConnections": 100, + "Active": false, "DisplayName": "newds2displayname", "DSCP": 41, "GeoLimit": 1, + "InitialDispersion": 2, "IPV6RoutingEnabled": false, "LogsEnabled": false, "MissLat": 42.881944, + "MissLong": -88.627778, "MultiSiteOrigin": true, "OrgServerFQDN": "http://origin.example.net", + "Protocol": 2, "QStringIgnore": 0, "RegionalGeoBlocking": true, + })), + }, + "BAD REQUEST when INVALID REMAP TEXT": { + EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "remapText": "@plugin=tslua.so @pparam=/opt/trafficserver/etc/trafficserver/remapPlugin1.lua\nline2", + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when MISSING SLICE PLUGIN SIZE": { + EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "rangeRequestHandling": 3, + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when SLICE PLUGIN SIZE SET with INVALID RANGE REQUEST SETTING": { + EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "rangeRequestHandling": 1, + "rangeSliceBlockSize": 262144, + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when SLICE PLUGIN SIZE TOO SMALL": { + EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "rangeRequestHandling": 3, + "rangeSliceBlockSize": 0, + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when SLICE PLUGIN SIZE TOO LARGE": { + EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "rangeRequestHandling": 3, + "rangeSliceBlockSize": 40000000, + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when ADDING TOPOLOGY to CLIENT STEERING DS": { + EndpointId: GetDeliveryServiceId(t, "ds-client-steering"), ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "topology": "mso-topology", + "xmlId": "ds-client-steering", + "typeId": GetTypeId(t, "CLIENT_STEERING"), + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when TOPOLOGY DOESNT EXIST": { + EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "topology": "", + "xmlId": "ds1", + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when ADDING TOPOLOGY to DS with DS REQUIRED CAPABILITY": { + EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "topology": "top-for-ds-req", + "xmlId": "ds1", + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when ADDING TOPOLOGY to DS when NO CACHES in SAME CDN as DS": { + EndpointId: GetDeliveryServiceId(t, "top-ds-in-cdn2"), ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "cdnId": GetCDNID(t, "cdn2")(), + "topology": "top-with-caches-in-cdn1", + "xmlId": "top-ds-in-cdn2", + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "OK when REMOVING TOPOLOGY": { + EndpointId: GetDeliveryServiceId(t, "ds-based-top-with-no-mids"), ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "topology": nil, + "xmlId": "ds-based-top-with-no-mids", + }), + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK when DS with TOPOLOGY updates HEADER REWRITE FIELDS": { + EndpointId: GetDeliveryServiceId(t, "ds-top"), ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "firstHeaderRewrite": "foo", + "innerHeaderRewrite": "bar", + "lastHeaderRewrite": "baz", + "topology": "mso-topology", + "xmlId": "ds-top", + }), + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "BAD REQUEST when DS with NO TOPOLOGY updates HEADER REWRITE FIELDS": { + EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "firstHeaderRewrite": "foo", + "innerHeaderRewrite": "bar", + "lastHeaderRewrite": "baz", + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when DS with TOPOLOGY updates LEGACY HEADER REWRITE FIELDS": { + EndpointId: GetDeliveryServiceId(t, "ds-top"), ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "edgeHeaderRewrite": "foo", + "midHeaderRewrite": "bar", + "topology": "mso-topology", + "xmlId": "ds-top", + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "OK when DS with NO TOPOLOGY updates LEGACY HEADER REWRITE FIELDS": { + EndpointId: GetDeliveryServiceId(t, "ds2"), ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "profileId": GetProfileID(t, "ATS_EDGE_TIER_CACHE")(), + "edgeHeaderRewrite": "foo", + "midHeaderRewrite": "bar", + "routingName": "ccr-ds2", + "xmlId": "ds2", + }), + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK when UPDATING MINOR VERSION FIELDS": { + EndpointId: GetDeliveryServiceId(t, "ds-test-minor-versions"), ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "consistentHashQueryParams": []string{"d", "e", "f"}, + "consistentHashRegex": "foo", + "deepCachingType": "NEVER", + "fqPacingRate": 41, + "maxOriginConnections": 500, + "routingName": "cdn", + "signingAlgorithm": "uri_signing", + "tenantId": GetTenantID(t, "tenant1")(), + "trRequestHeaders": "X-ooF\nX-raB", + "trResponseHeaders": "Access-Control-Max-Age: 600\nContent-Type: text/html; charset=utf-8", + "xmlId": "ds-test-minor-versions", + }), + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateDSExpectedFields(map[string]interface{}{"ConsistentHashQueryParams": []string{"d", "e", "f"}, + "ConsistentHashRegex": "foo", "DeepCachingType": tc.DeepCachingTypeNever, "FQPacingRate": 41, "MaxOriginConnections": 500, + "SigningAlgorithm": "uri_signing", "Tenant": "tenant1", "TRRequestHeaders": "X-ooF\nX-raB", + "TRResponseHeaders": "Access-Control-Max-Age: 600\nContent-Type: text/html; charset=utf-8", + })), + }, + "BAD REQUEST when INVALID COUNTRY CODE": { + EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "geoLimit": 2, + "geoLimitCountries": []string{"US", "CA", "12"}, + "xmlId": "invalid-geolimit-test", + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when CHANGING TOPOLOGY of DS with ORG SERVERS ASSIGNED": { + EndpointId: GetDeliveryServiceId(t, "ds-top"), ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{ + "topology": "another-topology", + "xmlId": "ds-top", + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when UPDATING DS OUTSIDE TENANCY": { + EndpointId: GetDeliveryServiceId(t, "ds3"), ClientSession: tenant4UserSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{"xmlId": "ds3"}), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + "PRECONDITION FAILED when updating with IMS & IUS Headers": { + EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}}, + RequestBody: generateDeliveryService(t, map[string]interface{}{"xmlId": "ds1"}), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + "PRECONDITION FAILED when updating with IFMATCH ETAG Header": { + EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession, + RequestBody: generateDeliveryService(t, map[string]interface{}{"xmlId": "ds1"}), + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + }, + "DELETE": { + "BAD REQUEST when DELETING DS OUTSIDE TENANCY": { + EndpointId: GetDeliveryServiceId(t, "ds3"), ClientSession: tenant4UserSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + "GET AFTER CHANGES": { + "OK when CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {currentTimeRFC}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + "DELIVERY SERVICES CAPACITY": { + "OK when VALID request": { + EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + ds := tc.DeliveryServiceV4{} + + if val, ok := testCase.RequestOpts.QueryParameters["accessibleTo"]; ok { + if _, err := strconv.Atoi(val[0]); err != nil { + testCase.RequestOpts.QueryParameters.Set("accessibleTo", strconv.Itoa(GetTenantID(t, val[0])())) + } + } + if val, ok := testCase.RequestOpts.QueryParameters["cdn"]; ok { + if _, err := strconv.Atoi(val[0]); err != nil { + testCase.RequestOpts.QueryParameters.Set("cdn", strconv.Itoa(GetCDNID(t, val[0])())) + } + } + if val, ok := testCase.RequestOpts.QueryParameters["profile"]; ok { + if _, err := strconv.Atoi(val[0]); err != nil { + testCase.RequestOpts.QueryParameters.Set("profile", strconv.Itoa(GetProfileID(t, val[0])())) + } + } + if val, ok := testCase.RequestOpts.QueryParameters["type"]; ok { + if _, err := strconv.Atoi(val[0]); err != nil { + testCase.RequestOpts.QueryParameters.Set("type", strconv.Itoa(GetTypeId(t, val[0]))) + } + } + if val, ok := testCase.RequestOpts.QueryParameters["tenant"]; ok { + if _, err := strconv.Atoi(val[0]); err != nil { + testCase.RequestOpts.QueryParameters.Set("tenant", strconv.Itoa(GetTenantID(t, val[0])())) + } + } + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &ds) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET", "GET AFTER CHANGES": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetDeliveryServices(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.CreateDeliveryService(ds, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.UpdateDeliveryService(testCase.EndpointId(), ds, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.DeleteDeliveryService(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, resp.Alerts, err) + } + }) + case "DELIVERY SERVICES CAPACITY": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetDeliveryServiceCapacity(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, resp.Alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateDSExpectedFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + dsResp := resp.([]tc.DeliveryServiceV40) + for field, expected := range expectedResp { + for _, ds := range dsResp { + switch field { + case "Active": + assert.Equal(t, expected, *ds.Active, "Expected Active to be %v, but got %v", expected, *ds.Active) + case "DeepCachingType": + assert.Equal(t, expected, *ds.DeepCachingType, "Expected DeepCachingType to be %v, but got %v", expected, *ds.DeepCachingType) + case "CDNName": + assert.Equal(t, expected, *ds.CDNName, "Expected CDNName to be %v, but got %v", expected, *ds.CDNName) + case "ConsistentHashRegex": + assert.Equal(t, expected, *ds.ConsistentHashRegex, "Expected ConsistentHashRegex to be %v, but got %v", expected, *ds.ConsistentHashRegex) + case "ConsistentHashQueryParams": + assert.Exactly(t, expected, ds.ConsistentHashQueryParams, "Expected ConsistentHashQueryParams to be %v, but got %v", expected, ds.ConsistentHashQueryParams) + case "DisplayName": + assert.Equal(t, expected, *ds.DisplayName, "Expected DisplayName to be %v, but got %v", expected, *ds.DisplayName) + case "DSCP": + assert.Equal(t, expected, *ds.DSCP, "Expected DSCP to be %v, but got %v", expected, *ds.DSCP) + case "FQPacingRate": + assert.Equal(t, expected, *ds.FQPacingRate, "Expected FQPacingRate to be %v, but got %v", expected, *ds.FQPacingRate) + case "GeoLimit": + assert.Equal(t, expected, *ds.GeoLimit, "Expected GeoLimit to be %v, but got &v", expected, ds.GeoLimit) + case "GeoLimitCountries": + assert.Exactly(t, expected, ds.GeoLimitCountries, "Expected GeoLimitCountries to be %v, but got &v", expected, ds.GeoLimitCountries) + case "InitialDispersion": + assert.Equal(t, expected, *ds.InitialDispersion, "Expected InitialDispersion to be %v, but got &v", expected, ds.InitialDispersion) + case "IPV6RoutingEnabled": + assert.Equal(t, expected, *ds.IPV6RoutingEnabled, "Expected IPV6RoutingEnabled to be %v, but got &v", expected, ds.IPV6RoutingEnabled) + case "LogsEnabled": + assert.Equal(t, expected, *ds.LogsEnabled, "Expected LogsEnabled to be %v, but got %v", expected, *ds.LogsEnabled) + case "LongDesc": + assert.Equal(t, expected, *ds.LongDesc, "Expected LongDesc to be %v, but got %v", expected, *ds.LongDesc) + case "MaxDNSAnswers": + assert.Equal(t, expected, *ds.MaxDNSAnswers, "Expected MaxDNSAnswers to be %v, but got %v", expected, *ds.MaxDNSAnswers) + case "MaxOriginConnections": + assert.Equal(t, expected, *ds.MaxOriginConnections, "Expected MaxOriginConnections to be %v, but got %v", expected, *ds.MaxOriginConnections) + case "MaxRequestHeaderSize": + assert.Equal(t, expected, *ds.MaxRequestHeaderBytes, "Expected MaxRequestHeaderBytes to be %v, but got %v", expected, *ds.MaxRequestHeaderBytes) + case "MissLat": + assert.Equal(t, expected, *ds.MissLat, "Expected MissLat to be %v, but got %v", expected, *ds.MissLat) + case "MissLong": + assert.Equal(t, expected, *ds.MissLong, "Expected MissLong to be %v, but got %v", expected, *ds.MissLong) + case "MultiSiteOrigin": + assert.Equal(t, expected, *ds.MultiSiteOrigin, "Expected MultiSiteOrigin to be %v, but got %v", expected, *ds.MultiSiteOrigin) + case "OrgServerFQDN": + assert.Equal(t, expected, *ds.OrgServerFQDN, "Expected OrgServerFQDN to be %v, but got %v", expected, *ds.OrgServerFQDN) + case "ProfileName": + assert.Equal(t, expected, *ds.ProfileName, "Expected ProfileName to be %v, but got %v", expected, *ds.ProfileName) + case "Protocol": + assert.Equal(t, expected, *ds.Protocol, "Expected Protocol to be %v, but got %v", expected, *ds.Protocol) + case "QStringIgnore": + assert.Equal(t, expected, *ds.QStringIgnore, "Expected QStringIgnore to be %v, but got %v", expected, *ds.QStringIgnore) + case "RegionalGeoBlocking": + assert.Equal(t, expected, *ds.RegionalGeoBlocking, "Expected QStringIgnore to be %v, but got %v", expected, *ds.RegionalGeoBlocking) + case "ServiceCategory": + assert.Equal(t, expected, *ds.ServiceCategory, "Expected ServiceCategory to be %v, but got %v", expected, *ds.ServiceCategory) + case "SigningAlgorithm": + assert.Equal(t, expected, *ds.SigningAlgorithm, "Expected SigningAlgorithm to be %v, but got %v", expected, *ds.SigningAlgorithm) + case "Tenant": + assert.Equal(t, expected, *ds.Tenant, "Expected Tenant to be %v, but got %v", expected, *ds.Tenant) + case "Topology": + assert.Equal(t, expected, *ds.Topology, "Expected Topology to be %v, but got %v", expected, *ds.Topology) + case "TRRequestHeaders": + assert.Equal(t, expected, *ds.TRRequestHeaders, "Expected TRRequestHeaders to be %v, but got %v", expected, *ds.TRRequestHeaders) + case "TRResponseHeaders": + assert.Equal(t, expected, *ds.TRResponseHeaders, "Expected TRResponseHeaders to be %v, but got %v", expected, *ds.TRResponseHeaders) + case "Type": + assert.Equal(t, expected, *ds.Type, "Expected Type to be %v, but got %v", expected, *ds.Type) + case "XMLID": + assert.Equal(t, expected, *ds.XMLID, "Expected XMLID to be %v, but got %v", expected, *ds.XMLID) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validatePagination(paginationParam string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + paginationResp := resp.([]tc.DeliveryServiceV40) + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("orderby", "id") + respBase, _, err := TOSession.GetDeliveryServices(opts) + assert.RequireNoError(t, err, "Cannot get Delivery Services: %v - alerts: %+v", err, respBase.Alerts) + + ds := respBase.Response + assert.RequireGreaterOrEqual(t, len(ds), 3, "Need at least 3 Delivery Services in Traffic Ops to test pagination support, found: %d", len(ds)) + switch paginationParam { + case "limit:": + assert.Exactly(t, ds[:1], paginationResp, "expected GET Delivery Services with limit = 1 to return first result") + case "offset": + assert.Exactly(t, ds[1:2], paginationResp, "expected GET Delivery Services with limit = 1, offset = 1 to return second result") + case "page": + assert.Exactly(t, ds[1:2], paginationResp, "expected GET Delivery Services with limit = 1, page = 2 to return second result") + } + } +} + +func validateDescSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + dsDescResp := resp.([]tc.DeliveryServiceV40) + var descSortedList []string + var ascSortedList []string + assert.GreaterOrEqual(t, len(dsDescResp), 2, "Need at least 2 XMLIDs in Traffic Ops to test desc sort, found: %d", len(dsDescResp)) + // Get delivery services in the default ascending order for comparison. + dsAscResp, _, err := TOSession.GetDeliveryServices(client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error getting Delivery Services with default sort order: %v - alerts: %+v", err, dsAscResp.Alerts) + assert.GreaterOrEqual(t, len(dsAscResp.Response), 2, "Need at least 2 XMLIDs in Traffic Ops to test sort, found %d", len(dsAscResp.Response)) + // Verify the response match in length, i.e. equal amount of delivery services. + assert.Equal(t, len(dsAscResp.Response), len(dsDescResp), "Expected descending order response length: %v, to match ascending order response length %v", len(dsAscResp.Response), len(dsDescResp)) + // Insert xmlIDs to the front of a new list, so they are now reversed to be in ascending order. + for _, ds := range dsDescResp { + descSortedList = append([]string{*ds.XMLID}, descSortedList...) + } + // Insert xmlIDs by appending to a new list, so they stay in ascending order. + for _, ds := range dsAscResp.Response { + ascSortedList = append(ascSortedList, *ds.XMLID) + } + assert.Exactly(t, ascSortedList, descSortedList, "Delivery Service responses are not equal after reversal: %v - %v", ascSortedList, descSortedList) + } +} + +func GetDeliveryServiceId(t *testing.T, xmlId string) func() int { + return func() int { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("xmlId", xmlId) + + resp, _, err := TOSession.GetDeliveryServices(opts) + assert.RequireNoError(t, err, "Get Delivery Service Request failed with error: %v", err) + assert.RequireEqual(t, 1, len(resp.Response), "Expected delivery service response object length 1, but got %d", len(resp.Response)) + assert.RequireNotNil(t, resp.Response[0].ID, "Expected id to not be nil") + + return *resp.Response[0].ID + } +} + +func generateDeliveryService(t *testing.T, requestDS map[string]interface{}) map[string]interface{} { + // map for the most basic HTTP Delivery Service a user can create + genericHTTPDS := map[string]interface{}{ + "active": true, + "cdnName": "cdn1", + "cdnId": GetCDNID(t, "cdn1")(), + "displayName": "test ds", + "dscp": 0, + "geoLimit": 0, + "geoProvider": 0, + "initialDispersion": 1, + "ipv6RoutingEnabled": false, + "logsEnabled": false, + "missLat": 0.0, + "missLong": 0.0, + "multiSiteOrigin": false, + "orgServerFqdn": "http://ds.test", + "protocol": 0, + "profileName": "ATS_EDGE_TIER_CACHE", + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regionalGeoBlocking": false, + "routingName": "ccr-ds1", + "tenant": "tenant1", + "type": tc.DSTypeHTTP, + "typeId": GetTypeId(t, "HTTP"), + "xmlId": "ds1", + } + for k, v := range requestDS { + genericHTTPDS[k] = v + } + return genericHTTPDS +} + +func CreateTestDeliveryServices(t *testing.T) { + for _, ds := range testData.DeliveryServices { + ds = ds.RemoveLD1AndLD2() + if ds.XMLID == nil { + t.Error("Found a Delivery Service in testing data with null or undefined XMLID") + continue + } + resp, _, err := TOSession.CreateDeliveryService(ds, client.RequestOptions{}) + assert.NoError(t, err, "Could not create Delivery Service '%s': %v - alerts: %+v", *ds.XMLID, err, resp.Alerts) + } +} + +func DeleteTestDeliveryServices(t *testing.T) { + dses, _, err := TOSession.GetDeliveryServices(client.RequestOptions{}) + assert.NoError(t, err, "Cannot get Delivery Services: %v - alerts: %+v", err, dses.Alerts) + + for _, ds := range dses.Response { + delResp, _, err := TOSession.DeleteDeliveryService(*ds.ID, client.RequestOptions{}) + assert.NoError(t, err, "Could not delete Delivery Service: %v - alerts: %+v", err, delResp.Alerts) + // Retrieve Delivery Service to see if it got deleted + opts := client.NewRequestOptions() + opts.QueryParameters.Set("id", strconv.Itoa(*ds.ID)) + getDS, _, err := TOSession.GetDeliveryServices(opts) + assert.NoError(t, err, "Error deleting Delivery Service for '%s' : %v - alerts: %+v", *ds.XMLID, err, getDS.Alerts) + assert.Equal(t, 0, len(getDS.Response), "Expected Delivery Service '%s' to be deleted", *ds.XMLID) + } +} diff --git a/traffic_ops/testing/api/v5/deliveryserviceservers_test.go b/traffic_ops/testing/api/v5/deliveryserviceservers_test.go new file mode 100644 index 0000000000..f82e903582 --- /dev/null +++ b/traffic_ops/testing/api/v5/deliveryserviceservers_test.go @@ -0,0 +1,373 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "strconv" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestDeliveryServiceServers(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServiceCategories, ServerCapabilities, DeliveryServices, DeliveryServiceServerAssignments, ServerServerCapabilities, DeliveryServicesRequiredCapabilities}, func() { + + tomorrow := time.Now().UTC().AddDate(0, 0, 1).Format(time.RFC1123) + + dssTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID request": { + ClientSession: TOSession, Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK when VALID CDN parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1)), + }, + }, + "POST": { + "OK when VALID request": { + ClientSession: TOSession, RequestBody: map[string]interface{}{ + "dsId": GetDeliveryServiceId(t, "ds3")(), + "replace": true, + "servers": []int{GetServerID(t, "atlanta-edge-01")(), GetServerID(t, "atlanta-edge-03")()}, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK when ASSIGNING ORG SERVER IN CACHEGROUP of TOPOLOGY DS": { + ClientSession: TOSession, RequestBody: map[string]interface{}{ + "dsId": GetDeliveryServiceId(t, "ds-top")(), + "replace": true, + "servers": []int{GetServerID(t, "denver-mso-org-01")()}, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "BAD REQUEST when ASSIGNING ORG SERVER NOT IN CACHEGROUP of TOPOLOGY DS": { + ClientSession: TOSession, RequestBody: map[string]interface{}{ + "dsId": GetDeliveryServiceId(t, "ds-top-req-cap")(), + "replace": true, + "servers": []int{GetServerID(t, "denver-mso-org-01")()}, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when ASSIGNING SERVERS to a TOPOLOGY DS": { + ClientSession: TOSession, RequestBody: map[string]interface{}{ + "dsId": GetDeliveryServiceId(t, "ds-top")(), + "servers": []int{GetServerID(t, "atlanta-edge-01")(), GetServerID(t, "atlanta-edge-03")()}, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "CONFLICT when REMOVING ONLY EDGE SERVER ASSIGNMENT": { + ClientSession: TOSession, RequestBody: map[string]interface{}{ + "dsId": GetDeliveryServiceId(t, "test-ds-server-assignments")(), + "replace": true, + "servers": []int{GetServerID(t, "test-mso-org-01")()}, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusConflict)), + }, + "CONFLICT when REMOVING ONLY ORIGIN SERVER ASSIGNMENT": { + ClientSession: TOSession, RequestBody: map[string]interface{}{ + "dsId": GetDeliveryServiceId(t, "test-ds-server-assignments")(), + "replace": true, + "servers": []int{GetServerID(t, "test-ds-server-assignments")()}, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusConflict)), + }, + "CONFLICT when REPLACING EDGE SERVER ASSIGNMENT with EDGE SERVER in BAD STATE": { + ClientSession: TOSession, RequestBody: map[string]interface{}{ + "dsId": GetDeliveryServiceId(t, "test-ds-server-assignments")(), + "replace": true, + "servers": []int{GetServerID(t, "admin-down-server")()}, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusConflict)), + }, + "OK when MAKING ASSIGNMENTS when DELIVERY SERVICE AND SERVER HAVE MATCHING CAPABILITIES": { + ClientSession: TOSession, RequestBody: map[string]interface{}{ + "dsId": GetDeliveryServiceId(t, "ds2")(), + "replace": true, + "servers": []int{GetServerID(t, "atlanta-org-2")()}, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK when ASSIGNING a ORIGIN server to a DS with REQUIRED CAPABILITY": { + ClientSession: TOSession, RequestBody: map[string]interface{}{ + "dsId": GetDeliveryServiceId(t, "msods1")(), + "replace": true, + "servers": []int{GetServerID(t, "denver-mso-org-01")()}, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + "SERVER STATUS PUT": { + "BAD REQUEST when UPDATING SERVER STATUS when ONLY EDGE SERVER ASSIGNED": { + EndpointId: GetServerID(t, "test-ds-server-assignments"), ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "status": "ADMIN_DOWN", + "offlineReason": "admin down", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusConflict)), + }, + "BAD REQUEST when UPDATING SERVER STATUS when ONLY ORIGIN SERVER ASSIGNED": { + EndpointId: GetServerID(t, "test-mso-org-01"), ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "status": "ADMIN_DOWN", + "offlineReason": "admin down", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusConflict)), + }, + }, + } + for method, testCases := range dssTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + var dsID int + var replace bool + var serverIDs []int + status := tc.ServerPutStatus{} + + if testCase.RequestBody != nil { + if val, ok := testCase.RequestBody["dsId"]; ok { + dsID = val.(int) + } + if val, ok := testCase.RequestBody["replace"]; ok { + replace = val.(bool) + } + if val, ok := testCase.RequestBody["servers"]; ok { + serverIDs = val.([]int) + } + if _, ok := testCase.RequestBody["offlineReason"]; ok { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &status) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + } + + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetDeliveryServiceServers(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.CreateDeliveryServiceServers(dsID, serverIDs, replace, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "SERVER STATUS PUT": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.UpdateServerStatus(testCase.EndpointId(), status, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func TestDeliveryServiceXMLIDServers(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServiceCategories, DeliveryServices, DeliveryServiceServerAssignments}, func() { + dsXMLIDServersTests := utils.V5TestCase{ + "POST": { + "BAD REQUEST when ASSIGNING SERVERS to a TOPOLOGY DS": { + ClientSession: TOSession, RequestBody: map[string]interface{}{ + "xmlID": "ds-top", + "serverNames": []string{"atlanta-edge-01", "atlanta-edge-03"}, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when ASSIGNING ORG SERVER NOT IN CACHEGROUP of TOPOLOGY DS": { + ClientSession: TOSession, RequestBody: map[string]interface{}{ + "xmlID": "ds-top-req-cap", + "serverNames": []string{"denver-mso-org-01"}, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "OK when ASSIGNING ORG SERVER IN CACHEGROUP of TOPOLOGY DS": { + ClientSession: TOSession, RequestBody: map[string]interface{}{ + "xmlID": "ds-top", + "serverNames": []string{"test-mso-org-01"}, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + for method, testCases := range dsXMLIDServersTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + var xmlID string + var servers []string + + if testCase.RequestBody != nil { + if val, ok := testCase.RequestBody["xmlID"]; ok { + xmlID = val.(string) + } + if val, ok := testCase.RequestBody["serverNames"]; ok { + servers = val.([]string) + } + } + + switch method { + case "POST": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.AssignServersToDeliveryService(servers, xmlID, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, resp, err) + } + }) + } + } + }) + } + }) +} + +func TestDeliveryServicesIDServers(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServiceCategories, DeliveryServices, DeliveryServiceServerAssignments}, func() { + dsIDServersTests := utils.V5TestCase{ + "GET": { + "OK when VALID request": { + EndpointId: GetDeliveryServiceId(t, "test-ds-server-assignments"), ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + utils.ResponseHasLength(2)), + }, + }, + } + for method, testCases := range dsIDServersTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetServersByDeliveryService(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + } + } + }) + } + }) +} + +func TestDeliveryServicesDSIDServerID(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServiceCategories, DeliveryServices, DeliveryServiceServerAssignments}, func() { + dssDSIDServerIDTests := utils.V5TestCase{ + "DELETE": { + "OK when VALID REQUEST": { + ClientSession: TOSession, RequestBody: map[string]interface{}{ + "server": GetServerID(t, "denver-mso-org-01")(), + "dsId": GetDeliveryServiceId(t, "ds-top")(), + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "BAD REQUEST when REMOVING ONLY EDGE SERVER ASSIGNMENT": { + ClientSession: TOSession, RequestBody: map[string]interface{}{ + "server": GetServerID(t, "test-ds-server-assignments")(), + "dsId": GetDeliveryServiceId(t, "test-ds-server-assignments")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusConflict)), + }, + "BAD REQUEST when REMOVING ONLY ORIGIN SERVER ASSIGNMENT": { + ClientSession: TOSession, RequestBody: map[string]interface{}{ + "server": GetServerID(t, "test-mso-org-01")(), + "dsId": GetDeliveryServiceId(t, "test-ds-server-assignments")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusConflict)), + }, + }, + } + for method, testCases := range dssDSIDServerIDTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + var dsID int + var serverId int + + if testCase.RequestBody != nil { + if val, ok := testCase.RequestBody["server"]; ok { + serverId = val.(int) + } + if val, ok := testCase.RequestBody["dsId"]; ok { + dsID = val.(int) + } + } + + switch method { + case "DELETE": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.DeleteDeliveryServiceServer(dsID, serverId, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, resp, err) + } + }) + } + } + }) + } + }) +} + +func DeleteTestDeliveryServiceServers(t *testing.T) { + dsServers, _, err := TOSession.GetDeliveryServiceServers(client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error retrieving server-to-Delivery-Service assignments: %v - alerts: %+v", err, dsServers.Alerts) + + for _, dss := range dsServers.Response { + // Retrieve Delivery Service in order to update its active field to false + opts := client.NewRequestOptions() + opts.QueryParameters.Set("id", strconv.Itoa(*dss.DeliveryService)) + getDS, _, err := TOSession.GetDeliveryServices(opts) + assert.NoError(t, err, "Error retrieving Delivery Service: %v - alerts: %+v", err, getDS.Alerts) + assert.Equal(t, 1, len(getDS.Response), "Expected 1 Delivery Service.") + // Update active to false in order to remove the server assignment + active := false + getDS.Response[0].Active = &active + updResp, _, err := TOSession.UpdateDeliveryService(*dss.DeliveryService, getDS.Response[0], client.RequestOptions{}) + assert.NoError(t, err, "Error updating Delivery Service: %v - alerts: %+v", err, updResp.Alerts) + assert.Equal(t, false, *updResp.Response[0].Active, "Expected Delivery Service to be Inactive.") + + alerts, _, err := TOSession.DeleteDeliveryServiceServer(*dss.DeliveryService, *dss.Server, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error removing server-to-Delivery-Service assignments: %v - alerts: %+v", err, alerts.Alerts) + } + dsServers, _, err = TOSession.GetDeliveryServiceServers(client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error retrieving server-to-Delivery-Service assignments: %v - alerts: %+v", err, dsServers.Alerts) + assert.Equal(t, dsServers.Size, 0, "Expected all Delivery Service Server assignments to be deleted.") +} + +func CreateTestDeliveryServiceServerAssignments(t *testing.T) { + for _, dss := range testData.DeliveryServiceServerAssignments { + resp, _, err := TOSession.AssignServersToDeliveryService(dss.ServerNames, dss.XmlId, client.RequestOptions{}) + assert.NoError(t, err, "Could not create Delivery Service Server Assignments: %v - alerts: %+v", err, resp.Alerts) + } +} diff --git a/traffic_ops/testing/api/v5/deliveryservicesideligible_test.go b/traffic_ops/testing/api/v5/deliveryservicesideligible_test.go new file mode 100644 index 0000000000..991099c05f --- /dev/null +++ b/traffic_ops/testing/api/v5/deliveryservicesideligible_test.go @@ -0,0 +1,52 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/http" + "testing" + + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" +) + +func TestDeliveryServicesEligible(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServiceCategories, DeliveryServices}, func() { + + methodTests := utils.V5TestCase{ + "GET": { + "OK when VALID request": { + EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1)), + }, + }, + } + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetDeliveryServicesEligible(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + } + } + }) + } + }) +} diff --git a/traffic_ops/testing/api/v5/deliveryservicesregexes_test.go b/traffic_ops/testing/api/v5/deliveryservicesregexes_test.go new file mode 100644 index 0000000000..4533305a24 --- /dev/null +++ b/traffic_ops/testing/api/v5/deliveryservicesregexes_test.go @@ -0,0 +1,133 @@ +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v5 + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "testing" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestDeliveryServicesRegexes(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Users, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServiceCategories, DeliveryServices, DeliveryServicesRegexes}, func() { + + methodTests := utils.V5TestCase{ + "GET": { + "OK when VALID request": { + EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(3)), + }, + "OK when VALID ID parameter": { + EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"id": {strconv.Itoa(getDSRegexID(t, "ds1"))}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1)), + }, + }, + "POST": { + "BAD REQUEST when MISSING REGEX PATTERN": { + EndpointId: GetDeliveryServiceId(t, "ds1"), ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "type": GetTypeId(t, "HOST_REGEXP"), + "setNumber": 3, + "pattern": "", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + + dsRegex := tc.DeliveryServiceRegexPost{} + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &dsRegex) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetDeliveryServiceRegexesByDSID(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.PostDeliveryServiceRegexesByDSID(testCase.EndpointId(), dsRegex, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + + } + } + }) + } + }) +} + +func getDSRegexID(t *testing.T, dsName string) int { + resp, _, err := TOSession.GetDeliveryServiceRegexesByDSID(GetDeliveryServiceId(t, dsName)(), client.RequestOptions{}) + assert.RequireNoError(t, err, "Get Delivery Service Regex failed with error: %v", err) + assert.RequireGreaterOrEqual(t, len(resp.Response), 1, "Expected delivery service regex response object length 1, but got %d", len(resp.Response)) + assert.RequireNotNil(t, resp.Response[0].ID, "Expected id to not be nil") + + return resp.Response[0].ID +} + +func CreateTestDeliveryServicesRegexes(t *testing.T) { + for _, dsRegex := range testData.DeliveryServicesRegexes { + dsID := GetDeliveryServiceId(t, dsRegex.DSName)() + typeId := GetTypeId(t, dsRegex.TypeName) + dsRegexPost := tc.DeliveryServiceRegexPost{ + Type: typeId, + SetNumber: dsRegex.SetNumber, + Pattern: dsRegex.Pattern, + } + alerts, _, err := TOSession.PostDeliveryServiceRegexesByDSID(dsID, dsRegexPost, client.RequestOptions{}) + assert.NoError(t, err, "Could not create Delivery Service Regex: %v - alerts: %+v", err, alerts) + } +} + +func DeleteTestDeliveryServicesRegexes(t *testing.T) { + db, err := OpenConnection() + assert.RequireNoError(t, err, "Cannot open db: %v", err) + defer func() { + err := db.Close() + assert.NoError(t, err, "Unable to close connection to db: %v", err) + }() + + for _, regex := range testData.DeliveryServicesRegexes { + err = execSQL(db, fmt.Sprintf("DELETE FROM deliveryservice_regex WHERE deliveryservice = '%v' and regex ='%v';", regex.DSID, regex.ID)) + assert.RequireNoError(t, err, "Unable to delete deliveryservice_regex by regex %v and ds %v: %v", regex.ID, regex.DSID, err) + + err := execSQL(db, fmt.Sprintf("DELETE FROM regex WHERE Id = '%v';", regex.ID)) + assert.RequireNoError(t, err, "Unable to delete regex %v: %v", regex.ID, err) + } +} diff --git a/traffic_ops/testing/api/v5/divisions_test.go b/traffic_ops/testing/api/v5/divisions_test.go new file mode 100644 index 0000000000..d63ca694eb --- /dev/null +++ b/traffic_ops/testing/api/v5/divisions_test.go @@ -0,0 +1,319 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "sort" + "strconv" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestDivisions(t *testing.T) { + WithObjs(t, []TCObj{Parameters, Divisions, Regions}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID request": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateDivisionSort()), + }, + "OK when VALID NAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"division1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + validateDivisionFields(map[string]interface{}{"Name": "division1"})), + }, + "EMPTY RESPONSE when INVALID ID parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"id": {"10000"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when INVALID NAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"abcd"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "VALID when SORTORDER param is DESC": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"sortOrder": {"desc"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateDivisionDescSort()), + }, + "FIRST RESULT when LIMIT=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateDivisionPagination("limit")), + }, + "SECOND RESULT when LIMIT=1 OFFSET=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "offset": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateDivisionPagination("offset")), + }, + "SECOND RESULT when LIMIT=1 PAGE=2": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "page": {"2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateDivisionPagination("page")), + }, + "BAD REQUEST when INVALID LIMIT parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"-2"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID OFFSET parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "offset": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID PAGE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "page": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "PUT": { + "OK when VALID request": { + EndpointId: GetDivisionID(t, "cdn-div2"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "testdivision", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateDivisionUpdateCreateFields("testdivision", map[string]interface{}{"Name": "testdivision"})), + }, + "PRECONDITION FAILED when updating with IMS & IUS Headers": { + EndpointId: GetDivisionID(t, "division1"), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}}, + RequestBody: map[string]interface{}{ + "name": "division1", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + "PRECONDITION FAILED when updating with IFMATCH ETAG Header": { + EndpointId: GetDivisionID(t, "division1"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "division1", + }, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + }, + "DELETE": { + "BAD REQUEST when DIVISION in use by REGION": { + EndpointId: GetDivisionID(t, "division1"), + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "NOT FOUND when INVALID ID parameter": { + EndpointId: func() int { return 111111 }, + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + }, + "GET AFTER CHANGES": { + "OK when CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {currentTimeRFC}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + division := tc.Division{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &division) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET", "GET AFTER CHANGES": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetDivisions(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.CreateDivision(division, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.UpdateDivision(testCase.EndpointId(), division, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.DeleteDivision(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateDivisionFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Division response to not be nil.") + divisionResp := resp.([]tc.Division) + for field, expected := range expectedResp { + for _, division := range divisionResp { + switch field { + case "Name": + assert.Equal(t, expected, division.Name, "Expected Name to be %v, but got %s", expected, division.Name) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validateDivisionUpdateCreateFields(name string, expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", name) + divisions, _, err := TOSession.GetDivisions(opts) + assert.RequireNoError(t, err, "Error getting Division: %v - alerts: %+v", err, divisions.Alerts) + assert.RequireEqual(t, 1, len(divisions.Response), "Expected one Division returned Got: %d", len(divisions.Response)) + validateDivisionFields(expectedResp)(t, toclientlib.ReqInf{}, divisions.Response, tc.Alerts{}, nil) + } +} + +func validateDivisionPagination(paginationParam string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + paginationResp := resp.([]tc.Division) + opts := client.NewRequestOptions() + opts.QueryParameters.Set("orderby", "id") + respBase, _, err := TOSession.GetDivisions(opts) + assert.RequireNoError(t, err, "Cannot get Divisions: %v - alerts: %+v", err, respBase.Alerts) + + division := respBase.Response + assert.RequireGreaterOrEqual(t, len(division), 2, "Need at least 2 Divisions in Traffic Ops to test pagination support, found: %d", len(division)) + switch paginationParam { + case "limit:": + assert.Exactly(t, division[:1], paginationResp, "expected GET Divisions with limit = 1 to return first result") + case "offset": + assert.Exactly(t, division[1:2], paginationResp, "expected GET Divisions with limit = 1, offset = 1 to return second result") + case "page": + assert.Exactly(t, division[1:2], paginationResp, "expected GET Divisions with limit = 1, page = 2 to return second result") + } + } +} + +func validateDivisionSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Division response to not be nil.") + var divisionNames []string + divisionResp := resp.([]tc.Division) + for _, division := range divisionResp { + divisionNames = append(divisionNames, division.Name) + } + assert.Equal(t, true, sort.StringsAreSorted(divisionNames), "List is not sorted by their names: %v", divisionNames) + } +} + +func validateDivisionDescSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Division response to not be nil.") + divisionDescResp := resp.([]tc.Division) + var descSortedList []string + var ascSortedList []string + assert.RequireGreaterOrEqual(t, len(divisionDescResp), 2, "Need at least 2 Divisions in Traffic Ops to test desc sort, found: %d", len(divisionDescResp)) + // Get Divisions in the default ascending order for comparison. + divisionAscResp, _, err := TOSession.GetDivisions(client.RequestOptions{}) + assert.RequireNoError(t, err, "Unexpected error getting Divisions with default sort order: %v - alerts: %+v", err, divisionAscResp.Alerts) + // Verify the response match in length, i.e. equal amount of Divisions. + assert.RequireEqual(t, len(divisionAscResp.Response), len(divisionDescResp), "Expected descending order response length: %v, to match ascending order response length %v", len(divisionAscResp.Response), len(divisionDescResp)) + // Insert Division names to the front of a new list, so they are now reversed to be in ascending order. + for _, division := range divisionDescResp { + descSortedList = append([]string{division.Name}, descSortedList...) + } + // Insert Division names by appending to a new list, so they stay in ascending order. + for _, division := range divisionAscResp.Response { + ascSortedList = append(ascSortedList, division.Name) + } + assert.Exactly(t, ascSortedList, descSortedList, "Division responses are not equal after reversal: %v - %v", ascSortedList, descSortedList) + } +} + +func GetDivisionID(t *testing.T, divisionName string) func() int { + return func() int { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", divisionName) + divisionsResp, _, err := TOSession.GetDivisions(opts) + assert.RequireNoError(t, err, "Get Divisions Request failed with error:", err) + assert.RequireEqual(t, 1, len(divisionsResp.Response), "Expected response object length 1, but got %d", len(divisionsResp.Response)) + return divisionsResp.Response[0].ID + } +} + +func CreateTestDivisions(t *testing.T) { + for _, division := range testData.Divisions { + resp, _, err := TOSession.CreateDivision(division, client.RequestOptions{}) + assert.RequireNoError(t, err, "Could not create Division '%s': %v - alerts: %+v", division.Name, err, resp.Alerts) + } +} + +func DeleteTestDivisions(t *testing.T) { + divisions, _, err := TOSession.GetDivisions(client.RequestOptions{}) + assert.NoError(t, err, "Cannot get Divisions: %v - alerts: %+v", err, divisions.Alerts) + for _, division := range divisions.Response { + alerts, _, err := TOSession.DeleteDivision(division.ID, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error deleting Division '%s' (#%d): %v - alerts: %+v", division.Name, division.ID, err, alerts.Alerts) + // Retrieve the Division to see if it got deleted + opts := client.NewRequestOptions() + opts.QueryParameters.Set("id", strconv.Itoa(division.ID)) + getDivision, _, err := TOSession.GetDivisions(opts) + assert.NoError(t, err, "Error getting Division '%s' after deletion: %v - alerts: %+v", division.Name, err, getDivision.Alerts) + assert.Equal(t, 0, len(getDivision.Response), "Expected Division '%s' to be deleted, but it was found in Traffic Ops", division.Name) + } +} diff --git a/traffic_ops/testing/api/v5/federation_deliveryservices_test.go b/traffic_ops/testing/api/v5/federation_deliveryservices_test.go new file mode 100644 index 0000000000..60649e6228 --- /dev/null +++ b/traffic_ops/testing/api/v5/federation_deliveryservices_test.go @@ -0,0 +1,215 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "sort" + "strconv" + "testing" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/lib/go-util" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestFederationsDeliveryServices(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServiceCategories, DeliveryServices, CDNFederations, FederationDeliveryServices}, func() { + + methodTests := utils.V5TestCase{ + "GET": { + "OK when VALID request": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1)), + }, + "SORTED when ORDERBY=DSID parameter": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"dsID"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateFederationDeliveryServicesSort(false)), + }, + "SORTED when ORDERBY=DSID and SORTORDER=DESC parameter": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"dsID"}, "sortOrder": {"desc"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateFederationDeliveryServicesSort(true)), + }, + "FIRST RESULT when LIMIT=1": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"dsID"}, "limit": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateFederationDeliveryServicesPagination(GetFederationID(t, "the.cname.com.")(), "limit")), + }, + "SECOND RESULT when LIMIT=1 OFFSET=1": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"dsID"}, "limit": {"1"}, "offset": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateFederationDeliveryServicesPagination(GetFederationID(t, "the.cname.com.")(), "offset")), + }, + "SECOND RESULT when LIMIT=1 PAGE=2": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"dsID"}, "limit": {"1"}, "page": {"2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateFederationDeliveryServicesPagination(GetFederationID(t, "the.cname.com.")(), "page")), + }, + "BAD REQUEST when INVALID LIMIT parameter": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"-2"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID OFFSET parameter": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "offset": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID PAGE parameter": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "page": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "DELETE": { + "OK when VALID request": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"dsID": {strconv.Itoa(GetDeliveryServiceId(t, "ds1")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "BAD REQUEST when LAST DELIVERY SERVICE": { + EndpointId: GetFederationID(t, "google.com."), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"dsID": {strconv.Itoa(GetDeliveryServiceId(t, "ds2")())}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + var dsID int + fedDS := tc.FederationDSPost{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &fedDS) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetFederationDeliveryServices(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.CreateFederationDeliveryServices(testCase.EndpointId(), fedDS.DSIDs, *fedDS.Replace, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + if val, ok := testCase.RequestOpts.QueryParameters["dsID"]; ok { + id, err := strconv.Atoi(val[0]) + assert.RequireNoError(t, err, "Failed to convert dsID to an integer.") + dsID = id + } + alerts, reqInf, err := testCase.ClientSession.DeleteFederationDeliveryService(testCase.EndpointId(), dsID, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateFederationDeliveryServicesSort(desc bool) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Federation DeliveryServices response to not be nil.") + var federationDSIDs []int + federationDSResp := resp.([]tc.FederationDeliveryServiceNullable) + for _, federationDS := range federationDSResp { + if desc { + federationDSIDs = append([]int{*federationDS.ID}, federationDSIDs...) + } else { + federationDSIDs = append(federationDSIDs, *federationDS.ID) + } + } + assert.Equal(t, true, sort.IntsAreSorted(federationDSIDs), "List is not sorted by their ids: %v", federationDSIDs) + } +} + +func validateFederationDeliveryServicesPagination(fedID int, paginationParam string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Federation DeliveryServices response to not be nil.") + paginationResp := resp.([]tc.FederationDeliveryServiceNullable) + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("orderby", "dsID") + respBase, _, err := TOSession.GetFederationDeliveryServices(fedID, opts) + assert.RequireNoError(t, err, "Cannot get Federation DeliveryServices: %v - alerts: %+v", err, respBase.Alerts) + + federationDS := respBase.Response + assert.RequireGreaterOrEqual(t, len(federationDS), 3, "Need at least 3 Federation DeliveryServices in Traffic Ops to test pagination support, found: %d", len(federationDS)) + switch paginationParam { + case "limit:": + assert.Exactly(t, federationDS[:1], paginationResp, "expected GET Federation DeliveryServices with limit = 1 to return first result") + case "offset": + assert.Exactly(t, federationDS[1:2], paginationResp, "expected GET Federation DeliveryServices with limit = 1, offset = 1 to return second result") + case "page": + assert.Exactly(t, federationDS[1:2], paginationResp, "expected GET Federation DeliveryServices with limit = 1, page = 2 to return second result") + } + } +} + +func CreateTestFederationDeliveryServices(t *testing.T) { + // Prerequisite Federation Delivery Services + federationDS := map[string]tc.FederationDSPost{ + "the.cname.com.": { + DSIDs: []int{GetDeliveryServiceId(t, "ds1")(), GetDeliveryServiceId(t, "ds2")(), GetDeliveryServiceId(t, "ds3")(), GetDeliveryServiceId(t, "ds4")()}, + Replace: util.BoolPtr(true), + }, + "google.com.": { + DSIDs: []int{GetDeliveryServiceId(t, "ds1")()}, + Replace: util.BoolPtr(true), + }, + } + + for federation, fedDS := range federationDS { + alerts, _, err := TOSession.CreateFederationDeliveryServices(GetFederationID(t, federation)(), fedDS.DSIDs, *fedDS.Replace, client.RequestOptions{}) + assert.RequireNoError(t, err, "Creating federations delivery services: %v - alerts: %+v", err, alerts.Alerts) + } +} diff --git a/traffic_ops/testing/api/v5/federation_federation_resolvers_test.go b/traffic_ops/testing/api/v5/federation_federation_resolvers_test.go new file mode 100644 index 0000000000..c4d9b70833 --- /dev/null +++ b/traffic_ops/testing/api/v5/federation_federation_resolvers_test.go @@ -0,0 +1,158 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestFederationFederationResolvers(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Parameters, Profiles, Tenants, CacheGroups, Statuses, Divisions, Regions, PhysLocations, Servers, Topologies, ServiceCategories, DeliveryServices, CDNFederations, FederationResolvers, FederationFederationResolvers}, func() { + + methodTests := utils.V5TestCase{ + "GET": { + "OK when VALID request AND RESOLVERS ASSIGNED": { + EndpointId: GetFederationID(t, "booya.com."), + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1)), + }, + "OK when VALID request AND NO RESOLVERS ASSIGNED": { + EndpointId: GetFederationID(t, "google.com."), + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + }, + "POST": { + "OK when ASSIGNING ONE FEDERATION RESOLVER": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "resolverIDs": []int{GetFederationResolverID(t, "1.2.3.4")()}, + "replace": false, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK when ASSIGNING MULTIPLE FEDERATION RESOLVERS": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "resolverIDs": []int{ + GetFederationResolverID(t, "1.2.3.4")(), + GetFederationResolverID(t, "0.0.0.0/12")(), + GetFederationResolverID(t, "::f1d0:f00d/123")(), + }, + "replace": false, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK when REPLACING ALL FEDERATION RESOLVERS": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "resolverIDs": []int{GetFederationResolverID(t, "dead::babe")()}, + "replace": true, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "BAD REQUEST when FEDERATION DOESNT EXIST": { + EndpointId: func() int { return -1 }, + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "resolverIDs": []int{GetFederationResolverID(t, "1.2.3.4")()}, + "replace": false, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + frAssignment := tc.AssignFederationResolversRequest{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &frAssignment) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetFederationFederationResolvers(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.AssignFederationFederationResolver(testCase.EndpointId(), frAssignment.FedResolverIDs, frAssignment.Replace, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + } + } + }) + } + }) +} + +func CreateTestFederationFederationResolvers(t *testing.T) { + // Prerequisite Federation Federation Resolvers + federationFederationResolvers := map[string]tc.AssignFederationResolversRequest{ + "booya.com.": { + FedResolverIDs: []int{ + GetFederationResolverID(t, "1.2.3.4")(), + GetFederationResolverID(t, "0.0.0.0/12")(), + GetFederationResolverID(t, "::f1d0:f00d/123")(), + GetFederationResolverID(t, "dead::babe")(), + }, + Replace: false, + }, + } + + for cname, federationFederationResolver := range federationFederationResolvers { + fedID := GetFederationID(t, cname)() + resp, _, err := TOSession.AssignFederationFederationResolver(fedID, federationFederationResolver.FedResolverIDs, federationFederationResolver.Replace, client.RequestOptions{}) + assert.RequireNoError(t, err, "Assigning resolvers %v to federation %d: %v - alerts: %+v", federationFederationResolver.FedResolverIDs, fedID, err, resp.Alerts) + } +} + +func DeleteTestFederationFederationResolvers(t *testing.T) { + // Prerequisite Federation Federation Resolvers + federationFederationResolvers := map[string]tc.AssignFederationResolversRequest{ + "booya.com.": { + FedResolverIDs: []int{}, + Replace: true, + }, + } + + for cname, federationFederationResolver := range federationFederationResolvers { + fedID := GetFederationID(t, cname)() + resp, _, err := TOSession.AssignFederationFederationResolver(fedID, federationFederationResolver.FedResolverIDs, federationFederationResolver.Replace, client.RequestOptions{}) + assert.RequireNoError(t, err, "Assigning resolvers %v to federation %d: %v - alerts: %+v", federationFederationResolver.FedResolverIDs, fedID, err, resp.Alerts) + } +} diff --git a/traffic_ops/testing/api/v5/federation_resolvers_test.go b/traffic_ops/testing/api/v5/federation_resolvers_test.go new file mode 100644 index 0000000000..c825580cea --- /dev/null +++ b/traffic_ops/testing/api/v5/federation_resolvers_test.go @@ -0,0 +1,294 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "sort" + "strconv" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/lib/go-util" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestFederationResolvers(t *testing.T) { + WithObjs(t, []TCObj{Types, FederationResolvers}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID request": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1)), + }, + "OK when VALID ID parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"id": {strconv.Itoa(GetFederationResolverID(t, "0.0.0.0/12")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + validateFederationResolverFields(map[string]interface{}{"ID": uint(GetFederationResolverID(t, "0.0.0.0/12")())})), + }, + "OK when VALID IPADDRESS parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"ipAddress": {"1.2.3.4"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + validateFederationResolverFields(map[string]interface{}{"IPAddress": "1.2.3.4"})), + }, + "OK when VALID TYPE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"type": {"RESOLVE4"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateFederationResolverFields(map[string]interface{}{"Type": "RESOLVE4"})), + }, + "SORTED by ID when ORDERBY=ID parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateFederationResolverIDSort()), + }, + "VALID when SORTORDER param is DESC": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "sortOrder": {"desc"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateFederationResolverDescSort()), + }, + "FIRST RESULT when LIMIT=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateFederationResolverPagination("limit")), + }, + "SECOND RESULT when LIMIT=1 OFFSET=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "offset": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateFederationResolverPagination("offset")), + }, + "SECOND RESULT when LIMIT=1 PAGE=2": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "page": {"2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateFederationResolverPagination("page")), + }, + "BAD REQUEST when INVALID LIMIT parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"-2"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID OFFSET parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "offset": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID PAGE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "page": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "POST": { + "BAD REQUEST when MISSING IPADDRESS and TYPE FIELDS": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID IP ADDRESS": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "ipAddress": "not a valid IP address", + "typeId": GetTypeId(t, "RESOLVE4"), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "DELETE": { + "NOT FOUND when INVALID ID": { + EndpointId: func() int { return 0 }, + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + fr := tc.FederationResolver{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &fr) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET", "GET AFTER CHANGES": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetFederationResolvers(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.CreateFederationResolver(fr, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.DeleteFederationResolver(uint(testCase.EndpointId()), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateFederationResolverFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Federation Resolver response to not be nil.") + frResp := resp.([]tc.FederationResolver) + for field, expected := range expectedResp { + for _, fr := range frResp { + switch field { + case "ID": + assert.RequireNotNil(t, fr.ID, "Expected ID to not be nil") + assert.Equal(t, expected, *fr.ID, "Expected ID to be %v, but got %d", expected, *fr.ID) + case "IPAddress": + assert.RequireNotNil(t, fr.IPAddress, "Expected IPAddress to not be nil") + assert.Equal(t, expected, *fr.IPAddress, "Expected IPAddress to be %v, but got %s", expected, *fr.IPAddress) + case "Type": + assert.RequireNotNil(t, fr.Type, "Expected Type to not be nil") + assert.Equal(t, expected, *fr.Type, "Expected Type to be %v, but got %s", expected, *fr.Type) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validateFederationResolverPagination(paginationParam string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + paginationResp := resp.([]tc.FederationResolver) + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("orderby", "id") + respBase, _, err := TOSession.GetFederationResolvers(opts) + assert.RequireNoError(t, err, "Cannot get Federation Resolvers: %v - alerts: %+v", err, respBase.Alerts) + + fr := respBase.Response + assert.RequireGreaterOrEqual(t, len(fr), 3, "Need at least 3 Federation Resolvers in Traffic Ops to test pagination support, found: %d", len(fr)) + switch paginationParam { + case "limit:": + assert.Exactly(t, fr[:1], paginationResp, "expected GET Federation Resolvers with limit = 1 to return first result") + case "offset": + assert.Exactly(t, fr[1:2], paginationResp, "expected GET Federation Resolvers with limit = 1, offset = 1 to return second result") + case "page": + assert.Exactly(t, fr[1:2], paginationResp, "expected GET Federation Resolvers with limit = 1, page = 2 to return second result") + } + } +} + +func validateFederationResolverIDSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Federation Resolver response to not be nil.") + var frIDs []int + frResp := resp.([]tc.FederationResolver) + for _, fr := range frResp { + frIDs = append(frIDs, int(*fr.ID)) + } + assert.Equal(t, true, sort.IntsAreSorted(frIDs), "List is not sorted by their ids: %v", frIDs) + } +} + +func validateFederationResolverDescSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected FederationResolver response to not be nil.") + frDescResp := resp.([]tc.FederationResolver) + var descSortedList []uint + var ascSortedList []uint + assert.RequireGreaterOrEqual(t, len(frDescResp), 2, "Need at least 2 Federation Resolvers in Traffic Ops to test desc sort, found: %d", len(frDescResp)) + // Get Federation Resolvers in the default ascending order for comparison. + opts := client.NewRequestOptions() + opts.QueryParameters.Set("orderby", "id") + frAscResp, _, err := TOSession.GetFederationResolvers(opts) + assert.RequireNoError(t, err, "Unexpected error getting Federation Resolvers with default sort order: %v - alerts: %+v", err, frAscResp.Alerts) + // Verify the response match in length, i.e. equal amount of Federation Resolvers. + assert.RequireEqual(t, len(frAscResp.Response), len(frDescResp), "Expected descending order response length: %v, to match ascending order response length %v", len(frAscResp.Response), len(frDescResp)) + // Insert Federation Resolvers names to the front of a new list, so they are now reversed to be in ascending order. + for _, fr := range frDescResp { + descSortedList = append([]uint{*fr.ID}, descSortedList...) + } + // Insert Federation Resolvers IDs by appending to a new list, so they stay in ascending order. + for _, fr := range frAscResp.Response { + ascSortedList = append(ascSortedList, *fr.ID) + } + assert.Exactly(t, ascSortedList, descSortedList, "Federation Resolver responses are not equal after reversal: %v - %v", ascSortedList, descSortedList) + } +} + +func GetFederationResolverID(t *testing.T, ipAddress string) func() int { + return func() int { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("ipAddress", ipAddress) + federationResolvers, _, err := TOSession.GetFederationResolvers(opts) + assert.RequireNoError(t, err, "Get FederationResolvers Request failed with error:", err) + assert.RequireEqual(t, 1, len(federationResolvers.Response), "Expected response object length 1, but got %d", len(federationResolvers.Response)) + assert.RequireNotNil(t, federationResolvers.Response[0].ID, "Expected Federation Resolver ID to not be nil") + return int(*federationResolvers.Response[0].ID) + } +} + +func CreateTestFederationResolvers(t *testing.T) { + for _, fr := range testData.FederationResolvers { + fr.TypeID = util.UIntPtr(uint(GetTypeId(t, *fr.Type))) + resp, _, err := TOSession.CreateFederationResolver(fr, client.RequestOptions{}) + assert.RequireNoError(t, err, "Failed to create Federation Resolver %+v: %v - alerts: %+v", fr, err, resp.Alerts) + } +} + +func DeleteTestFederationResolvers(t *testing.T) { + frs, _, err := TOSession.GetFederationResolvers(client.RequestOptions{}) + assert.RequireNoError(t, err, "Unexpected error getting Federation Resolvers: %v - alerts: %+v", err, frs.Alerts) + for _, fr := range frs.Response { + alerts, _, err := TOSession.DeleteFederationResolver(*fr.ID, client.RequestOptions{}) + assert.NoError(t, err, "Failed to delete Federation Resolver %+v: %v - alerts: %+v", fr, err, alerts.Alerts) + // Retrieve the Federation Resolver to see if it got deleted + opts := client.NewRequestOptions() + opts.QueryParameters.Set("id", strconv.Itoa(int(*fr.ID))) + getFR, _, err := TOSession.GetFederationResolvers(opts) + assert.NoError(t, err, "Error getting Federation Resolver '%d' after deletion: %v - alerts: %+v", *fr.ID, err, getFR.Alerts) + assert.Equal(t, 0, len(getFR.Response), "Expected Federation Resolver '%d' to be deleted, but it was found in Traffic Ops", *fr.ID) + } +} diff --git a/traffic_ops/testing/api/v5/federation_users_test.go b/traffic_ops/testing/api/v5/federation_users_test.go new file mode 100644 index 0000000000..cabc2f0187 --- /dev/null +++ b/traffic_ops/testing/api/v5/federation_users_test.go @@ -0,0 +1,257 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "sort" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/lib/go-util" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestFederationUsers(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Users, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServiceCategories, DeliveryServices, CDNFederations, FederationUsers}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "BAD REQUEST when INVALID FEDERATION ID": { + EndpointId: func() int { return -1 }, + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "SORTED by ID when ORDERBY=USERID parameter": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"userID"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateFederationUserIDSort(false)), + }, + "VALID when SORTORDER param is DESC": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"userID"}, "sortOrder": {"desc"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateFederationUserIDSort(true)), + }, + "FIRST RESULT when LIMIT=1": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"userID"}, "limit": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateFederationUsersPagination(GetFederationID(t, "the.cname.com.")(), "limit")), + }, + "SECOND RESULT when LIMIT=1 OFFSET=1": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"userID"}, "limit": {"1"}, "offset": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateFederationUsersPagination(GetFederationID(t, "the.cname.com.")(), "offset")), + }, + "SECOND RESULT when LIMIT=1 PAGE=2": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"userID"}, "limit": {"1"}, "page": {"2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateFederationUsersPagination(GetFederationID(t, "the.cname.com.")(), "page")), + }, + "BAD REQUEST when INVALID LIMIT parameter": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"-2"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID OFFSET parameter": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "offset": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID PAGE parameter": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "page": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "POST": { + "OK when VALID request": { + EndpointId: GetFederationID(t, "google.com."), + ClientSession: TOSession, + RequestBody: map[string]interface{}{"userIds": []int{GetUserID(t, "readonlyuser")(), GetUserID(t, "disalloweduser")()}, "replace": false}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK when REPLACING USERS": { + EndpointId: GetFederationID(t, "booya.com."), + ClientSession: TOSession, + RequestBody: map[string]interface{}{"userIds": []int{GetUserID(t, "readonlyuser")()}, "replace": true}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK when ADDING USER": { + EndpointId: GetFederationID(t, "booya.com."), + ClientSession: TOSession, + RequestBody: map[string]interface{}{"userIds": []int{GetUserID(t, "disalloweduser")()}, "replace": false}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "BAD REQUEST when INVALID FEDERATION ID": { + EndpointId: func() int { return -1 }, + ClientSession: TOSession, + RequestBody: map[string]interface{}{"userIds": []int{}, "replace": false}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "BAD REQUEST when INVALID USER ID": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + RequestBody: map[string]interface{}{"userIds": []int{-1}, "replace": false}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + }, + "GET AFTER CHANGES": { + "OK when CHANGES made": { + EndpointId: GetFederationID(t, "the.cname.com."), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {currentTimeRFC}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + federationUser := tc.FederationUserPost{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &federationUser) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET", "GET AFTER CHANGES": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetFederationUsers(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.CreateFederationUsers(testCase.EndpointId(), federationUser.IDs, *federationUser.Replace, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateFederationUsersPagination(federationID int, paginationParam string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + paginationResp := resp.([]tc.FederationUser) + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("orderby", "userID") + respBase, _, err := TOSession.GetFederationUsers(federationID, opts) + assert.RequireNoError(t, err, "Cannot get Federation Users: %v - alerts: %+v", err, respBase.Alerts) + + federationUsers := respBase.Response + assert.RequireGreaterOrEqual(t, len(federationUsers), 3, "Need at least 3 Federation Users in Traffic Ops to test pagination support, found: %d", len(federationUsers)) + switch paginationParam { + case "limit:": + assert.Exactly(t, federationUsers[:1], paginationResp, "expected GET Federation Users with limit = 1 to return first result") + case "offset": + assert.Exactly(t, federationUsers[1:2], paginationResp, "expected GET Federation Users with limit = 1, offset = 1 to return second result") + case "page": + assert.Exactly(t, federationUsers[1:2], paginationResp, "expected GET Federation Users with limit = 1, page = 2 to return second result") + } + } +} + +func validateFederationUserIDSort(desc bool) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Federation User response to not be nil.") + var federationUserIDs []int + federationUserResp := resp.([]tc.FederationUser) + for _, fedUser := range federationUserResp { + if desc { + federationUserIDs = append([]int{*fedUser.ID}, federationUserIDs...) + } else { + federationUserIDs = append(federationUserIDs, *fedUser.ID) + } + } + assert.Equal(t, true, sort.IntsAreSorted(federationUserIDs), "List is not sorted by their ids: %v", federationUserIDs) + } +} + +func CreateTestFederationUsers(t *testing.T) { + // Prerequisite Federation Users + federationUsers := map[string]tc.FederationUserPost{ + "the.cname.com.": { + IDs: []int{GetUserID(t, "admin")(), GetUserID(t, "adminuser")(), GetUserID(t, "disalloweduser")(), GetUserID(t, "readonlyuser")()}, + Replace: util.BoolPtr(false), + }, + "booya.com.": { + IDs: []int{GetUserID(t, "adminuser")()}, + Replace: util.BoolPtr(false), + }, + } + + for cname, federationUser := range federationUsers { + fedID := GetFederationID(t, cname)() + resp, _, err := TOSession.CreateFederationUsers(fedID, federationUser.IDs, *federationUser.Replace, client.RequestOptions{}) + assert.RequireNoError(t, err, "Assigning users %v to federation %d: %v - alerts: %+v", federationUser.IDs, fedID, err, resp.Alerts) + } +} + +func DeleteTestFederationUsers(t *testing.T) { + for _, fedID := range fedIDs { + fedUsers, _, err := TOSession.GetFederationUsers(fedID, client.RequestOptions{}) + assert.RequireNoError(t, err, "Error getting users for federation %d: %v - alerts: %+v", fedID, err, fedUsers.Alerts) + for _, fedUser := range fedUsers.Response { + if fedUser.ID == nil { + t.Error("Traffic Ops returned a representation of a relationship between a user and a Federation that had null or undefined ID") + continue + } + alerts, _, err := TOSession.DeleteFederationUser(fedID, *fedUser.ID, client.RequestOptions{}) + assert.NoError(t, err, "Error deleting user #%d from federation #%d: %v - alerts: %+v", *fedUser.ID, fedID, err, alerts.Alerts) + } + fedUsers, _, err = TOSession.GetFederationUsers(fedID, client.RequestOptions{}) + assert.NoError(t, err, "Error getting users for federation %d: %v - alerts: %+v", fedID, err, fedUsers.Alerts) + assert.Equal(t, 0, len(fedUsers.Response), "Federation users expected 0, actual: %+v", len(fedUsers.Response)) + } +} diff --git a/traffic_ops/testing/api/v5/federations_test.go b/traffic_ops/testing/api/v5/federations_test.go new file mode 100644 index 0000000000..ccb10fbe42 --- /dev/null +++ b/traffic_ops/testing/api/v5/federations_test.go @@ -0,0 +1,277 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "sort" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestFederations(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Users, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServiceCategories, DeliveryServices, CDNFederations, FederationDeliveryServices, FederationUsers}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID request": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateAllFederationsFields([]map[string]interface{}{ + { + "DeliveryService": "ds1", + "Mappings": []map[string]interface{}{ + { + "CName": "the.cname.com.", + "TTL": 48, + }, + { + "CName": "booya.com.", + "TTL": 34, + }, + { + "CName": "google.com.", + "TTL": 30, + }, + }, + }, + })), + }, + }, + "POST": { + "OK when VALID request": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "federations": []map[string]interface{}{ + { + "deliveryService": "ds1", + "mappings": map[string]interface{}{ + "resolve4": []string{"0.0.0.0"}, + "resolve6": []string{"::1"}, + }, + }, + { + "deliveryService": "ds2", + "mappings": map[string]interface{}{ + "resolve4": []string{"1.2.3.4/28"}, + "resolve6": []string{"1234::/110"}, + }, + }, + }, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "CONFLICT when INVALID DELIVERY SERVICE": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "federations": []map[string]interface{}{ + { + "deliveryService": "aoeuhtns", + "mappings": map[string]interface{}{ + "resolve4": []string{"1.2.3.4/28"}, + "resolve6": []string{"dead::beef", "f1d0::f00d/82"}, + }, + }, + }, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusConflict)), + }, + }, + "PUT": { + "OK when VALID request": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "federations": []map[string]interface{}{ + { + "deliveryService": "ds1", + "mappings": map[string]interface{}{ + "resolve4": []string{"192.0.2.0/25", "192.0.2.128/25"}, + "resolve6": []string{"2001:db8::/33", "2001:db8:8000::/33"}, + }, + }, + { + "deliveryService": "ds2", + "mappings": map[string]interface{}{ + "resolve4": []string{"192.0.2.0/25", "192.0.2.128/25"}, + "resolve6": []string{"2001:db8::/33", "2001:db8:8000::/33"}, + }, + }, + }, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateFederationsUpdateFields([]map[string]interface{}{ + { + "DeliveryService": "ds1", + "Resolve4": []string{"192.0.2.0/25", "192.0.2.128/25"}, + "Resolve6": []string{"2001:db8::/33", "2001:db8:8000::/33"}, + }, + { + "DeliveryService": "ds2", + "Resolve4": []string{"192.0.2.0/25", "192.0.2.128/25"}, + "Resolve6": []string{"2001:db8::/33", "2001:db8:8000::/33"}, + }, + })), + }, + "CONFLICT when INVALID DELIVERY SERVICE": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "federations": []map[string]interface{}{ + { + "deliveryService": "aoeuhtns", + "mappings": map[string]interface{}{ + "resolve4": []string{"1.2.3.4/28"}, + "resolve6": []string{"dead::beef", "f1d0::f00d/82"}, + }, + }, + }, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusConflict)), + }, + }, + "GET AFTER CHANGES": { + "OK when CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {currentTimeRFC}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + federation := tc.DeliveryServiceFederationResolverMappingRequest{} + + if testCase.RequestBody != nil { + for _, federationRequest := range testCase.RequestBody { + dat, err := json.Marshal(federationRequest) + assert.RequireNoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &federation) + assert.RequireNoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + } + + switch method { + case "GET", "GET AFTER CHANGES": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.AllFederations(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.AddFederationResolverMappingsForCurrentUser(federation, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.ReplaceFederationResolverMappingsForCurrentUser(federation, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + + t.Run("DELETE/OK when VALID request", func(t *testing.T) { + alerts, reqInf, err := TOSession.DeleteFederationResolverMappingsForCurrentUser(client.RequestOptions{}) + for _, check := range utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)) { + check(t, reqInf, nil, alerts, err) + } + }) + + t.Run("DELETE/CONFLICT when NO FEDERATION RESOLVERS", func(t *testing.T) { + alerts, reqInf, err := TOSession.DeleteFederationResolverMappingsForCurrentUser(client.RequestOptions{}) + for _, check := range utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusConflict)) { + check(t, reqInf, nil, alerts, err) + } + }) + }) +} + +func validateFederationFields(expectedResp []map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Federation response to not be nil.") + federationResp := resp.([]tc.AllDeliveryServiceFederationsMapping) + for _, expectedFed := range expectedResp { + for _, federation := range federationResp { + if federation.DeliveryService.String() == expectedFed["DeliveryService"] { + assert.RequireEqual(t, 1, len(federation.Mappings), "expected 1 mapping, got %d", len(federation.Mappings)) + sort.Strings(federation.Mappings[0].Resolve4) + sort.Strings(federation.Mappings[0].Resolve6) + sort.Strings(expectedFed["Resolve4"].([]string)) + sort.Strings(expectedFed["Resolve6"].([]string)) + assert.Exactly(t, expectedFed["Resolve4"], federation.Mappings[0].Resolve4, "checking federation resolver mappings, expected: %+v, actual: %+v", expectedFed["Resolve4"], federation.Mappings[0].Resolve4) + assert.Exactly(t, expectedFed["Resolve6"], federation.Mappings[0].Resolve6, "checking federation resolver mappings, expected: %+v, actual: %+v", expectedFed["Resolve6"], federation.Mappings[0].Resolve6) + } + } + } + } +} + +func validateFederationsUpdateFields(expectedResp []map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + opts := client.NewRequestOptions() + federation, _, err := TOSession.Federations(opts) + assert.RequireNoError(t, err, "Error getting Federations: %v - alerts: %+v", err, federation.Alerts) + assert.RequireGreaterOrEqual(t, len(federation.Response), 1, "Expected one Federation returned Got: %d", len(federation.Response)) + validateFederationFields(expectedResp)(t, toclientlib.ReqInf{}, federation.Response, tc.Alerts{}, nil) + } +} + +func validateAllFederationsFields(expectedResp []map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected All Federations response to not be nil.") + allFederationResp := resp.([]tc.AllDeliveryServiceFederationsMapping) + for _, expected := range expectedResp { + for _, allFed := range allFederationResp { + if allFed.DeliveryService.String() == expected["DeliveryService"] { + for _, mapping := range allFed.Mappings { + for _, expectedMapping := range expected["Mappings"].([]map[string]interface{}) { + assert.RequireNotNil(t, mapping.CName, "Expected CName to not be nil.") + if expectedMapping["CName"] == *mapping.CName { + assert.RequireNotNil(t, mapping.TTL, "Expected TTL to not be nil.") + assert.Equal(t, expectedMapping["TTL"], *mapping.TTL, "Expected TTL to be %v, but got %s", expected, allFed.Mappings[0].TTL) + } + } + } + } + } + } + } +} diff --git a/traffic_ops/testing/api/v5/fixtures_test.go b/traffic_ops/testing/api/v5/fixtures_test.go new file mode 100644 index 0000000000..cdb38317e6 --- /dev/null +++ b/traffic_ops/testing/api/v5/fixtures_test.go @@ -0,0 +1,43 @@ +/* + * 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. + */ + +package v5 + +import ( + "encoding/json" + "io/ioutil" + "os" + + "github.com/apache/trafficcontrol/lib/go-log" +) + +// LoadFixtures ... +func LoadFixtures(fixturesPath string) { + + f, err := ioutil.ReadFile(fixturesPath) + if err != nil { + log.Errorf("Cannot unmarshal fixtures json %s", err) + os.Exit(1) + } + err = json.Unmarshal(f, &testData) + if err != nil { + log.Errorf("Cannot unmarshal fixtures json %v", err) + os.Exit(1) + } +} diff --git a/traffic_ops/testing/api/v5/iso_test.go b/traffic_ops/testing/api/v5/iso_test.go new file mode 100644 index 0000000000..b915f0de53 --- /dev/null +++ b/traffic_ops/testing/api/v5/iso_test.go @@ -0,0 +1,91 @@ +package v5 + +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import ( + "testing" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestGetOSVersions(t *testing.T) { + if Config.NoISO { + t.Skip("No ISO generation available") + } + // Default value per ./traffic_ops/install/data/json/osversions.json file. + // This should be the data returned in the CiaB environment. + expected := map[string]string{ + "CentOS 7.2": "centos72", + } + + // Ensure request with an authenticated client returns expected data. + t.Run("OK when AUTHENTICATED", func(t *testing.T) { + got, _, err := TOSession.GetOSVersions(client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error from authenticated GetOSVersions(): %v - alerts: %+v", err, got.Alerts) + assert.RequireEqual(t, len(expected), len(got.Response), "Incorrect map length: got %d map entries, expected %d", len(expected), len(got.Response)) + + for k, expectedVal := range expected { + assert.RequireEqual(t, expectedVal, got.Response[k], "Incorrect map entry for key %q: got %q, expected %q", k, got.Response[k], expectedVal) + } + }) + + // Ensure request with an un-authenticated client returns an error. + t.Run("ERROR when UNAUTHENTICATED", func(t *testing.T) { + _, _, err := NoAuthTOSession.GetOSVersions(client.RequestOptions{}) + assert.Error(t, err, "Expected error from unauthenticated GetOSVersions(), got: ") + }) + + // Update database with a Parameter entry. This should cause the endpoint + // to use the Parameter's value as the configuration file's directory. In this + // case, an intentionally missing/invalid directory is provided. + // Ensure authenticated request client returns an error. + // NOTE: This does not assume this test and TO are using the same filesystem, but + // does make the reasonable assumption that `/DOES/NOT/EXIST/osversions.json` will not exist + // on the TO host. + t.Run("ERROR when INVALID PARAMETER", func(t *testing.T) { + p := tc.Parameter{ + ConfigFile: "mkisofs", + Name: "kickstart.files.location", + Value: "/DOES/NOT/EXIST", + } + alerts, _, err := TOSession.CreateParameter(p, client.RequestOptions{}) + assert.RequireNoError(t, err, "Could not create Parameter: %v - alerts: %+v", err, alerts.Alerts) + + // Cleanup DB entry + defer func() { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", p.Name) + opts.QueryParameters.Set("configFile", p.ConfigFile) + opts.QueryParameters.Set("value", p.Value) + resp, _, err := TOSession.GetParameters(opts) + assert.RequireNoError(t, err, "Cannot GET Parameter by name '%s', configFile '%s' and value '%s': %v - alerts: %+v", p.Name, p.ConfigFile, p.Value, err, resp.Alerts) + assert.RequireEqual(t, 1, len(resp.Response), "Unexpected response length %d", len(resp.Response)) + + delResp, _, err := TOSession.DeleteParameter(resp.Response[0].ID, client.RequestOptions{}) + assert.RequireNoError(t, err, "Cannot delete Parameter #%d: %v - alerts: %+v", resp.Response[0].ID, err, delResp.Alerts) + + }() + + _, _, err = TOSession.GetOSVersions(client.RequestOptions{}) + assert.Error(t, err, "Expected error from GetOSVersions() after adding invalid Parameter DB entry, got: ") + }) +} diff --git a/traffic_ops/testing/api/v5/jobs_test.go b/traffic_ops/testing/api/v5/jobs_test.go new file mode 100644 index 0000000000..3036012a59 --- /dev/null +++ b/traffic_ops/testing/api/v5/jobs_test.go @@ -0,0 +1,665 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "strconv" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestJobs(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServiceCategories, DeliveryServices, Jobs}, func() { + + currentTime := time.Now() + pastTime := currentTime.AddDate(0, 0, -1) + futureTime := currentTime.AddDate(0, 0, 1) + startTime := currentTime.UTC().Add(time.Minute) + pastTimeRFC := pastTime.Format(time.RFC3339) + futureTimeRFC := futureTime.Format(time.RFC3339) + + methodTests := utils.V5TestCase{ + "GET": { + "OK when VALID request": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1)), + }, + "OK when VALID ASSETURL parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"assetUrl": {"http://origin.example.net/older"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + validateInvalidationJobsFields(map[string]interface{}{"AssetURL": "http://origin.example.net/older"})), + }, + "OK when VALID CREATEDBY parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"createdBy": {"admin"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateInvalidationJobsFields(map[string]interface{}{"CreatedBy": "admin"})), + }, + "OK when VALID DELIVERYSERVICE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"deliveryService": {"ds2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + validateInvalidationJobsFields(map[string]interface{}{"DeliveryService": "ds2"})), + }, + "OK when VALID DSID parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"dsId": {strconv.Itoa(GetDeliveryServiceId(t, "ds2")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + validateInvalidationJobsFields(map[string]interface{}{"DeliveryService": "ds2"})), + }, + "OK when VALID ID parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"id": {strconv.Itoa(GetJobID(t, "http://origin.example.net/oldest")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + validateInvalidationJobsFields(map[string]interface{}{"ID": GetJobID(t, "http://origin.example.net/oldest")()})), + }, + "OK when VALID INVALIDATIONTYPE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"invalidationType": {"REFRESH"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateInvalidationJobsFields(map[string]interface{}{"InvalidationType": "REFRESH"})), + }, + "OK when VALID MAXREVALDURATIONDAYS parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"maxRevalDurationDays": {""}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateMaxRevalDurationDays()), + }, + "OK when VALID USERID parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"userId": {strconv.Itoa(GetUserID(t, "admin")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateInvalidationJobsFields(map[string]interface{}{"CreatedBy": "admin"})), + }, + "OK when VALID CDN parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"cdn": {"cdn2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateInvalidationJobsFields(map[string]interface{}{"DeliveryService": "ds-forked-topology"})), + }, + "EMPTY RESPONSE when INVALID ASSETURL parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"assetUrl": {"doesntexist"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when INVALID CREATEDBY parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"createdBy": {"doesntexist"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when INVALID DELIVERYSERVICE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"deliveryService": {"doesntexist"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when INVALID DSID parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"dsId": {"1111111111"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when INVALID ID parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"id": {"11111111111"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when INVALID INVALIDATIONTYPE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"invalidationType": {"DOESNT EXIST"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when INVALID USERID parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"userId": {"1111111111"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + }, + "POST": { + "OK when STARTTIME is a FUTURE DATE": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "deliveryService": "ds1", + "regex": "/.*", + "startTime": startTime.AddDate(0, 0, 1), + "ttlHours": 36, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK when STARTTIME is RFC FORMAT AND a FUTURE DATE": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "deliveryService": "ds1", + "regex": "/.*", + "startTime": futureTimeRFC, + "ttlHours": 36, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "BAD REQUEST when TTLHours value GREATER than MAXREVALDURATIONDAYS": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "deliveryService": "ds1", + "regex": "/.*", + "startTime": startTime, + "ttlHours": 9999, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "OK when ALREADY EXISTS": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "deliveryService": "ds1", + "regex": "/.*", + "startTime": startTime, + "ttlHours": 72, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.HasAlertLevel(tc.WarnLevel.String())), + }, + "NOT FOUND when DELIVERYSERVICE DOESNT EXIST": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "deliveryService": "doesntExist", + "regex": "/.*", + "startTime": startTime, + "ttlHours": 36, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "BAD REQUEST when MISSING DELIVERYSERVICE": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "regex": "/.*", + "startTime": startTime, + "ttlHours": 36, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when BLANK DELIVERYSERVICE": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "deliveryService": "", + "regex": "/.*", + "startTime": startTime, + "ttlHours": 36, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when STARTTIME is a PAST DATE": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "deliveryService": "ds1", + "regex": "/.*", + "startTime": startTime.AddDate(0, 0, -1), + "ttlHours": 36, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when STARTTIME is RFC FORMAT AND is a PAST DATE": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "deliveryService": "ds1", + "regex": "/.*", + "startTime": pastTimeRFC, + "ttlHours": 36, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when MISSING STARTTIME": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "deliveryService": "ds1", + "regex": "/.*", + "ttlHours": 36, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when MISSING REGEX": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "deliveryService": "ds1", + "startTime": startTime, + "ttlHours": 36, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when EMPTY REGEX": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "deliveryService": "ds1", + "regex": "", + "startTime": startTime, + "ttlHours": 36, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when MISSING TTL": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "deliveryService": "ds1", + "regex": "/.*", + "startTime": startTime, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when TTL is ZERO": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "deliveryService": "ds1", + "regex": "/.*", + "startTime": startTime, + "ttlHours": 0, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "WARNING ALERT when JOB COLLISION": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "deliveryService": "ds1", + "regex": "/foo", + "startTime": startTime.Add(time.Hour), + "ttlHours": 36, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.HasAlertLevel(tc.WarnLevel.String())), + }, + }, + "PUT": { + "OK when STARTTIME is a FUTURE DATE": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "id": GetJobID(t, "http://origin.example.net/.*")(), + "assetUrl": "http://origin.example.net/.*", + "createdBy": "admin", + "deliveryService": "ds1", + "regex": "/.*", + "startTime": startTime.AddDate(0, 0, 1), + "ttlHours": 36, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "NOT FOUND when INVALID ID": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "assetUrl": "http://origin.example.net/.*", + "createdBy": "admin", + "deliveryService": "ds1", + "id": 111111111, + "regex": "/old", + "startTime": startTime.AddDate(0, 0, 3), + "ttlHours": 2160, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "BAD REQUEST when STARTTIME NOT within 2 DAYS FROM NOW": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "id": GetJobID(t, "http://origin.example.net/.*")(), + "assetUrl": "http://origin.example.net/.*", + "createdBy": "admin", + "deliveryService": "ds1", + "regex": "/old", + "startTime": startTime.AddDate(0, 0, 3), + "ttlHours": 2160, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "CONFLICT when DIFFERENT DELIVERY SERVICE": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "id": GetJobID(t, "http://origin.example.net/.*")(), + "assetUrl": "http://origin.example.net/.*", + "createdBy": "admin", + "deliveryService": "ds3", + "regex": "/old", + "startTime": startTime, + "ttlHours": 2160, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusConflict)), + }, + "CONFLICT when INVALID DELIVERY SERVICE": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "id": GetJobID(t, "http://origin.example.net/.*")(), + "assetUrl": "http://origin.example.net/.*", + "createdBy": "admin", + "deliveryService": "doesntexist", + "regex": "/old", + "startTime": startTime, + "ttlHours": 2160, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusConflict)), + }, + "BAD REQUEST when BLANK DELIVERY SERVICE": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "id": GetJobID(t, "http://origin.example.net/.*")(), + "assetUrl": "http://origin.example.net/.*", + "createdBy": "admin", + "deliveryService": "", + "regex": "/old", + "startTime": startTime, + "ttlHours": 2160, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID ASSETURL": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "id": GetJobID(t, "http://origin.example.net/.*")(), + "assetUrl": "http://google.com", + "createdBy": "admin", + "deliveryService": "ds1", + "regex": "/old", + "startTime": startTime, + "ttlHours": 2160, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when BLANK ASSETURL": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "id": GetJobID(t, "http://origin.example.net/.*")(), + "assetUrl": "", + "createdBy": "admin", + "deliveryService": "ds1", + "regex": "/old", + "startTime": startTime, + "ttlHours": 2160, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when BLANK CREATEDBY": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "id": GetJobID(t, "http://origin.example.net/.*")(), + "assetUrl": "http://origin.example.net/.*", + "deliveryService": "ds1", + "regex": "/old", + "startTime": startTime, + "ttlHours": 2160, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "CONFLICT when DIFFERENT CREATEDBY": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "id": GetJobID(t, "http://origin.example.net/.*")(), + "assetUrl": "http://origin.example.net/.*", + "createdBy": "operator", + "deliveryService": "ds1", + "regex": "/old", + "startTime": startTime, + "ttlHours": 2160, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusConflict)), + }, + "BAD REQUEST when BLANK INVALIDATION TYPE": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "id": GetJobID(t, "http://origin.example.net/.*")(), + "assetUrl": "http://origin.example.net/.*", + "createdBy": "operator", + "deliveryService": "ds1", + "regex": "/old", + "startTime": startTime, + "ttlHours": 2160, + "invalidationType": "", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when STARTTIME is a PAST DATE": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "id": GetJobID(t, "http://origin.example.net/.*")(), + "assetUrl": "http://origin.example.net/.*", + "createdBy": "admin", + "deliveryService": "ds1", + "regex": "/.*", + "startTime": startTime.AddDate(0, 0, -1), + "ttlHours": 36, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when STARTTIME is RFC FORMAT AND is a PAST DATE": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "id": GetJobID(t, "http://origin.example.net/.*")(), + "assetUrl": "http://origin.example.net/.*", + "createdBy": "admin", + "deliveryService": "ds1", + "regex": "/.*", + "startTime": pastTimeRFC, + "ttlHours": 36, + "invalidationType": "REFRESH", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "WARNING ALERT when JOB COLLISION": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "id": GetJobID(t, "http://origin.example.net/foo")(), + "assetUrl": "http://origin.example.net/foo", + "createdBy": "admin", + "deliveryService": "ds1", + "regex": "/foo", + "startTime": startTime.Add(time.Hour), + "ttlHours": 36, + "invalidationType": "REFETCH", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.HasAlertLevel(tc.WarnLevel.String())), + }, + }, + "DELETE": { + "NOT FOUND when JOB DOESNT EXIST": { + EndpointId: func() int { return 1111111111 }, + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + job := tc.InvalidationJobCreateV4{} + jobUpdate := tc.InvalidationJobV4{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + if method == "POST" { + err = json.Unmarshal(dat, &job) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } else if method == "PUT" { + err = json.Unmarshal(dat, &jobUpdate) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + } + + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetInvalidationJobs(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.CreateInvalidationJob(job, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.UpdateInvalidationJob(jobUpdate, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.DeleteInvalidationJob(uint64(testCase.EndpointId()), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + t.Run("POST/BAD REQUEST when REFETCH PARAMETER NOT ENABLED", func(t *testing.T) { CreateRefetchJobParameterFail(t) }) + }) +} + +func validateInvalidationJobsFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Invalidation Jobs response to not be nil.") + jobResp := resp.([]tc.InvalidationJobV4) + for field, expected := range expectedResp { + for _, job := range jobResp { + switch field { + case "AssetURL": + assert.Equal(t, expected, job.AssetURL, "Expected AssetURL to be %v, but got %s", expected, job.AssetURL) + case "CreatedBy": + assert.Equal(t, expected, job.CreatedBy, "Expected CreatedBy to be %v, but got %s", expected, job.CreatedBy) + case "DeliveryService": + assert.Equal(t, expected, job.DeliveryService, "Expected DeliveryService to be %v, but got %s", expected, job.DeliveryService) + case "ID": + assert.Equal(t, uint64(expected.(int)), job.ID, "Expected ID to be %v, but got %s", expected, job.ID) + case "InvalidationType": + assert.Equal(t, expected, job.InvalidationType, "Expected InvalidationType to be %v, but got %s", expected, job.InvalidationType) + case "StartTime": + assert.Equal(t, true, job.StartTime.Round(time.Minute).Equal(expected.(time.Time).Round(time.Minute)), "Expected StartTime to be %v, but got %s", expected, job.StartTime) + case "TTLHours": + assert.Equal(t, expected, job.TTLHours, "Expected TTLHours to be %v, but got %s", expected, job.TTLHours) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validateMaxRevalDurationDays() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Invalidation Jobs response to not be nil.") + maxRevalDurationDays := 90 + jobResp := resp.([]tc.InvalidationJobV4) + for _, job := range jobResp { + if time.Since(job.StartTime) > time.Duration(maxRevalDurationDays)*24*time.Hour { + t.Errorf("GET /jobs by maxRevalDurationDays returned job that is older than %d days: %v}", maxRevalDurationDays, time.Since(job.StartTime)) + } + } + } +} + +func GetJobID(t *testing.T, assetUrl string) func() int { + return func() int { + t.Helper() + opts := client.NewRequestOptions() + opts.QueryParameters.Set("assetUrl", assetUrl) + jobs, _, err := TOSession.GetInvalidationJobs(opts) + assert.RequireNoError(t, err, "Get Jobs Request failed with error:", err) + assert.RequireGreaterOrEqual(t, len(jobs.Response), 1, "Expected at least 1 response object, but got %d", len(jobs.Response)) + return int(jobs.Response[0].ID) + } +} + +func CreateTestJobs(t *testing.T) { + for _, job := range testData.InvalidationJobs { + job.StartTime = time.Now().Add(time.Minute).UTC() + resp, _, err := TOSession.CreateInvalidationJob(job, client.RequestOptions{}) + assert.RequireNoError(t, err, "Could not create job: %v - alerts: %+v", err, resp.Alerts) + } +} + +func DeleteTestJobs(t *testing.T) { + jobs, _, err := TOSession.GetInvalidationJobs(client.RequestOptions{}) + assert.NoError(t, err, "Cannot get Jobs: %v - alerts: %+v", err, jobs.Alerts) + + for _, job := range jobs.Response { + alerts, _, err := TOSession.DeleteInvalidationJob(job.ID, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error deleting Job with ID: (#%d): %v - alerts: %+v", job.ID, err, alerts.Alerts) + // Retrieve the Job to see if it got deleted + opts := client.NewRequestOptions() + opts.QueryParameters.Set("id", strconv.Itoa(int(job.ID))) + getJobs, _, err := TOSession.GetInvalidationJobs(opts) + assert.NoError(t, err, "Error getting Job with ID: '%d' after deletion: %v - alerts: %+v", job.ID, err, getJobs.Alerts) + assert.Equal(t, 0, len(getJobs.Response), "Expected Job to be deleted, but it was found in Traffic Ops") + } +} + +func CreateRefetchJobParameterFail(t *testing.T) { + // Delete the refetch parameter as a prerequisite + clearRefetchEnabledParameter(t) + createJob := tc.InvalidationJobCreateV4{ + DeliveryService: "ds1", + Regex: "/.*", + TTLHours: 72, + StartTime: time.Now().Add(time.Hour).UTC(), + InvalidationType: "REFETCH", + } + _, reqInf, err := TOSession.CreateInvalidationJob(createJob, client.RequestOptions{}) + assert.Error(t, err, "Expected error preventing the creation of the Refetch Invalidation Job.") + assert.Equal(t, http.StatusBadRequest, reqInf.StatusCode, "Expected Status Code: 400, Got: %d", reqInf.StatusCode) +} + +func clearRefetchEnabledParameter(t *testing.T) { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", string(tc.RefetchEnabled)) + paramsResp, _, err := TOSession.GetParameters(opts) + assert.RequireNoError(t, err, "Error retrieving parameters. err: %v \n alerts: %v", err, paramsResp.Alerts) + assert.RequireEqual(t, 1, len(paramsResp.Response), "Expected one parameter returned from response Got: %d", len(paramsResp.Response)) + assert.RequireEqual(t, string(tc.RefetchEnabled), paramsResp.Response[0].Name, "Expected the RefetchEnabled parameter Got: %s", paramsResp.Response[0].Name) + alerts, _, err := TOSession.DeleteParameter(paramsResp.Response[0].ID, client.RequestOptions{}) + assert.RequireNoError(t, err, "Expected no error when deleting RefetchEnabled parameter: %v Alerts: %v", err, alerts.Alerts) +} diff --git a/traffic_ops/testing/api/v5/loginfail_test.go b/traffic_ops/testing/api/v5/loginfail_test.go new file mode 100644 index 0000000000..84e182ebce --- /dev/null +++ b/traffic_ops/testing/api/v5/loginfail_test.go @@ -0,0 +1,111 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "crypto/tls" + "net/http" + "net/http/cookiejar" + "testing" + "time" + + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "golang.org/x/net/publicsuffix" + + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" + toclient "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestLoginFail(t *testing.T) { + WithObjs(t, []TCObj{CDNs}, func() { + PostTestLoginFail(t) + LoginWithEmptyCredentialsTest(t) + }) + WithObjs(t, []TCObj{Roles, Tenants, Users}, func() { + LoginWithTokenTest(t) + }) +} + +func PostTestLoginFail(t *testing.T) { + // This specifically tests a previous bug: auth failure returning a 200, causing the client to think the request succeeded, and deserialize no matching fields successfully, and return an empty object. + + userAgent := "to-api-v5-client-tests-loginfailtest" + uninitializedTOClient, err := getUninitializedTOClient(Config.TrafficOps.Users.Admin, Config.TrafficOps.UserPassword, Config.TrafficOps.URL, userAgent, time.Second*time.Duration(Config.Default.Session.TimeoutInSecs)) + assert.RequireNoError(t, err, "Error getting uninitialized client: %+v", err) + + assert.RequireGreaterOrEqual(t, len(testData.CDNs), 1, "cannot test login: must have at least 1 test data cdn") + + expectedCDN := testData.CDNs[0] + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", expectedCDN.Name) + actualCDNs, _, err := uninitializedTOClient.GetCDNs(opts) + assert.RequireNoError(t, err, "Failed to request CDN '%s': %v - alerts: %+v", expectedCDN.Name, err, actualCDNs.Alerts) + assert.RequireGreaterOrEqual(t, len(actualCDNs.Response), 1, "Uninitialized client should have retried login (possibly login failed with a 200, so it didn't try again, and the CDN request returned an auth failure with a 200, which the client reasonably thought was success, and deserialized with no matching keys, resulting in an empty object); len(actualCDNs) expected >1, actual 0") + + actualCDN := actualCDNs.Response[0] + assert.Equal(t, expectedCDN.Name, actualCDN.Name, "cdn.Name expected '%s' actual '%s'", expectedCDN.Name, actualCDN.Name) +} + +func LoginWithEmptyCredentialsTest(t *testing.T) { + userAgent := "to-api-v5-client-tests-loginfailtest" + _, _, err := toclient.LoginWithAgent(Config.TrafficOps.URL, Config.TrafficOps.Users.Admin, "", true, userAgent, false, time.Second*time.Duration(Config.Default.Session.TimeoutInSecs)) + assert.Error(t, err, "Expected error when logging in with empty credentials, actual nil") +} + +func LoginWithTokenTest(t *testing.T) { + db, err := OpenConnection() + assert.RequireNoError(t, err, "Failed to get database connection: %v", err) + + allowedToken := "test" + disallowedToken := "quest" + + _, err = db.Exec(`UPDATE tm_user SET token=$1 WHERE id = (SELECT id FROM tm_user WHERE role != (SELECT id FROM role WHERE name='disallowed') LIMIT 1)`, allowedToken) + assert.RequireNoError(t, err, "Failed to set allowed token: %v", err) + + _, err = db.Exec(`UPDATE tm_user SET token=$1 WHERE id = (SELECT id FROM tm_user WHERE role = (SELECT id FROM role WHERE name='disallowed') LIMIT 1)`, disallowedToken) + assert.RequireNoError(t, err, "Failed to set disallowed token: %v", err) + + userAgent := "to-api-v5-client-tests-loginfailtest" + s, _, err := toclient.LoginWithToken(Config.TrafficOps.URL, allowedToken, true, userAgent, false, time.Second*time.Duration(Config.Default.Session.TimeoutInSecs)) + assert.NoError(t, err, "Unexpected error when logging in with a token: %v", err) + assert.NotNil(t, s, "returned client was nil") + + // disallowed token + _, _, err = toclient.LoginWithToken(Config.TrafficOps.URL, disallowedToken, true, userAgent, false, time.Second*time.Duration(Config.Default.Session.TimeoutInSecs)) + assert.Error(t, err, "Expected an error when logging in with a disallowed token, actual nil") + + // nonexistent token + _, _, err = toclient.LoginWithToken(Config.TrafficOps.URL, "notarealtoken", true, userAgent, false, time.Second*time.Duration(Config.Default.Session.TimeoutInSecs)) + assert.Error(t, err, "expected an error when logging in with a nonexistent token, actual nil") +} + +func getUninitializedTOClient(user, pass, uri, agent string, reqTimeout time.Duration) (*toclient.Session, error) { + insecure := true + useCache := false + jar, err := cookiejar.New(&cookiejar.Options{ + PublicSuffixList: publicsuffix.List, + }) + if err != nil { + return nil, err + } + return toclient.NewSession(user, pass, uri, agent, &http.Client{ + Timeout: reqTimeout, + Transport: &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure}, + }, + Jar: jar, + }, useCache), nil +} diff --git a/traffic_ops/testing/api/v5/logs_test.go b/traffic_ops/testing/api/v5/logs_test.go new file mode 100644 index 0000000000..277f9efacb --- /dev/null +++ b/traffic_ops/testing/api/v5/logs_test.go @@ -0,0 +1,82 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/http" + "net/url" + "testing" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestLogs(t *testing.T) { + WithObjs(t, []TCObj{Roles, Tenants, Users}, func() { // Objs added to create logs when this test is run alone + + methodTests := utils.V5TestCase{ + "GET": { + "OK when VALID request": { + ClientSession: TOSession, Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + utils.ResponseLengthGreaterOrEqual(1)), + }, + "OK when VALID USERNAME parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"username": {"admin"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateLogsFields(map[string]interface{}{"User": "admin"})), + }, + "RESPONSE LENGTH matches LIMIT parameter": { + ClientSession: TOSession, RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"10"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + utils.ResponseHasLength(10)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetLogs(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateLogsFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + logs := resp.([]tc.Log) + for field, expected := range expectedResp { + for _, log := range logs { + switch field { + case "User": + assert.Equal(t, expected, *log.User, "Expected User to be %v, but got %v", expected, *log.User) + } + } + } + } +} diff --git a/traffic_ops/testing/api/v5/monitoring_test.go b/traffic_ops/testing/api/v5/monitoring_test.go new file mode 100644 index 0000000000..62e0c0c3b8 --- /dev/null +++ b/traffic_ops/testing/api/v5/monitoring_test.go @@ -0,0 +1,93 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "testing" + + "github.com/apache/trafficcontrol/lib/go-tc" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestMonitoring(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, ProfileParameters, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServiceCategories, DeliveryServices}, func() { + GetTestMonitoringConfigNoSnapshotOnTheFly(t) // MUST run first + AllCDNsCanSnapshot(t) + }) +} + +// GetTestMonitoringConfigNoSnapshotOnTheFly verifies that Traffic Ops generates a monitoring.json on-the-fly rather than returning "" or "{}" if no snapshot exists. +// This MUST NOT be run after a different function in the same Test creates a Snapshot, or the test will be invalid. +// This prevents a critical bug of upgrading to 4.x bringing a CDN down until a Snapshot is performed. +func GetTestMonitoringConfigNoSnapshotOnTheFly(t *testing.T) { + var server tc.ServerV4 + for _, sv := range testData.Servers { + if sv.Type != "EDGE" { + continue + } + server = sv + break + } + if server.CDNName == nil || *server.CDNName == "" { + t.Fatal("No edge server found in test data, cannot test") + } + + resp, _, err := TOSession.GetTrafficMonitorConfig(*server.CDNName, client.RequestOptions{}) + if err != nil { + t.Errorf("getting monitoring: %v - alerts: %+v", err, resp.Alerts) + } else if len(resp.Response.TrafficServers) == 0 { + t.Errorf("Expected Monitoring without a snapshot to generate on-the-fly, actual: empty monitoring object for CDN '%s'", *server.CDNName) + } +} + +func AllCDNsCanSnapshot(t *testing.T) { + + serversByHost := make(map[string]tc.ServerV4, len(testData.Servers)) + + for _, server := range testData.Servers { + if server.HostName == nil { + t.Error("Found server in test data with null or undefined hostName") + continue + } + serversByHost[*server.HostName] = server + } + + opts := client.NewRequestOptions() + for _, cdn := range testData.CDNs { + opts.QueryParameters.Set("cdn", cdn.Name) + resp, _, err := TOSession.SnapshotCRConfig(opts) + if err != nil { + t.Errorf("Unexpected error making Snapshot for CDN '%s': %v - alerts: %+v", cdn.Name, err, resp.Alerts) + continue + } + + tmConfig, _, err := TOSession.GetTrafficMonitorConfig(cdn.Name, client.RequestOptions{}) + if err != nil { + t.Errorf("Unexpected error fetching Traffic Monitor Config for CDN '%s': %v - alerts: %+v", cdn.Name, err, tmConfig.Alerts) + continue + } + + for _, server := range tmConfig.Response.TrafficServers { + if _, ok := serversByHost[server.HostName]; !ok { + t.Errorf("Server '%s' not found in test data", server.HostName) + continue + } + if len(server.Interfaces) < 1 { + t.Errorf("Server '%s' expected to get more than 1 interface(s), got %d", server.HostName, len(server.Interfaces)) + } + } + } +} diff --git a/traffic_ops/testing/api/v5/origins_test.go b/traffic_ops/testing/api/v5/origins_test.go new file mode 100644 index 0000000000..3ae782d379 --- /dev/null +++ b/traffic_ops/testing/api/v5/origins_test.go @@ -0,0 +1,681 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "strconv" + "strings" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestOrigins(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Coordinates, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Users, Topologies, ServiceCategories, DeliveryServices, Origins}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + + tenant4UserSession := utils.CreateV5Session(t, Config.TrafficOps.URL, "tenant4user", "pa$$word", Config.Default.Session.TimeoutInSecs) + + methodTests := utils.V5TestCase{ + "GET": { + "OK when VALID request": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1)), + }, + "OK when VALID NAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"origin1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + validateOriginsFields(map[string]interface{}{"Name": "origin1"})), + }, + "OK when VALID DELIVERYSERVICE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"deliveryservice": {strconv.Itoa(GetDeliveryServiceId(t, "ds1")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateOriginsFields(map[string]interface{}{"DeliveryServiceID": GetDeliveryServiceId(t, "ds1")()})), + }, + "OK when VALID CACHEGROUP parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"cachegroup": {strconv.Itoa(GetCacheGroupId(t, "originCachegroup")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateOriginsFields(map[string]interface{}{"CachegroupID": GetCacheGroupId(t, "originCachegroup")()})), + }, + "OK when VALID COORDINATE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"coordinate": {strconv.Itoa(GetCoordinateID(t, "coordinate1")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateOriginsFields(map[string]interface{}{"Coordinate": "coordinate1"})), + }, + "OK when VALID PROFILEID parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"profileId": {strconv.Itoa(GetProfileID(t, "ATS_EDGE_TIER_CACHE")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateOriginsFields(map[string]interface{}{"ProfileID": GetProfileID(t, "ATS_EDGE_TIER_CACHE")()})), + }, + "OK when VALID PRIMARY parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"primary": {"true"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateOriginsFields(map[string]interface{}{"IsPrimary": true})), + }, + "OK when VALID TENANT parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"tenant": {strconv.Itoa(GetTenantID(t, "tenant1")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateOriginsFields(map[string]interface{}{"TenantID": GetTenantID(t, "tenant1")()})), + }, + "EMPTY RESPONSE when CHILD TENANT reads PARENT TENANT ORIGIN": { + ClientSession: tenant4UserSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"tenant": {strconv.Itoa(GetTenantID(t, "tenant3")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when NAME parameter that DOESNT EXIST": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"doesntexist"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when DELIVERYSERVICE parameter that DOESNT EXIST": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"deliveryservice": {"1000000"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when CACHEGROUP parameter that DOESNT EXIST": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"cachegroup": {"1000000"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when COORDINATE parameter that DOESNT EXIST": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"coordinate": {"1000000"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when PROFILEID parameter that DOESNT EXIST": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"profileId": {"1000000"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "BAD REQUEST when INVALID PRIMARY parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"primary": {"1000000"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "EMPTY RESPONSE when TENANT parameter that DOESNT EXIST": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"tenant": {"1000000"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "FIRST RESULT when LIMIT=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateOriginsPagination("limit")), + }, + "SECOND RESULT when LIMIT=1 OFFSET=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "offset": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateOriginsPagination("offset")), + }, + "SECOND RESULT when LIMIT=1 PAGE=2": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "page": {"2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateOriginsPagination("page")), + }, + "BAD REQUEST when INVALID LIMIT parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"-2"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID OFFSET parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "offset": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID PAGE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "page": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "POST": { + "BAD REQUEST when ALREADY EXISTS": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "origin1", + "cachegroup": "originCachegroup", + "Coordinate": "coordinate1", + "deliveryService": "ds1", + "fqdn": "origin1.example.com", + "ipAddress": "1.2.3.4", + "ip6Address": "dead:beef:cafe::42", + "port": 1234, + "Profile": "ATS_EDGE_TIER_CACHE", + "protocol": "http", + "tenantId": GetTenantID(t, "tenant1")(), + "isPrimary": true, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "FORBIDDEN when CHILD TENANT CREATES ORIGIN OUTSIDE TENANCY": { + ClientSession: tenant4UserSession, + RequestBody: map[string]interface{}{ + "name": "originTenancyTest", + "cachegroup": "originCachegroup", + "deliveryServiceId": GetDeliveryServiceId(t, "ds1")(), + "fqdn": "origintenancy.example.com", + "protocol": "http", + "tenantId": GetTenantID(t, "tenant3")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + "NOT FOUND when CACHEGROUP DOESNT EXIST": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "testcg", + "cachegroupId": 10000000, + "deliveryServiceId": GetDeliveryServiceId(t, "ds1")(), + "fqdn": "test.cachegroupId.com", + "protocol": "http", + "tenantId": GetTenantID(t, "tenant1")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "NOT FOUND when PROFILEID DOESNT EXIST": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "testprofile", + "deliveryServiceId": GetDeliveryServiceId(t, "ds1")(), + "fqdn": "test.profileId.com", + "profileId": 1000000, + "protocol": "http", + "tenantId": GetTenantID(t, "tenant1")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "NOT FOUND when COORDINATE DOESNT EXIST": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "testcoordinate", + "coordinateId": 10000000, + "deliveryServiceId": GetDeliveryServiceId(t, "ds1")(), + "fqdn": "test.coordinate.com", + "protocol": "http", + "tenantId": GetTenantID(t, "tenant1")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "FORBIDDEN when INVALID TENANT": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "testtenant", + "deliveryServiceId": GetDeliveryServiceId(t, "ds1")(), + "fqdn": "test.tenant.com", + "protocol": "http", + "tenantId": 11111111, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + "BAD REQUEST when INVALID PROTOCOL": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "testprotocol", + "deliveryServiceId": GetDeliveryServiceId(t, "ds1")(), + "fqdn": "test.protocol.com", + "protocol": "httttpppss", + "tenantId": GetTenantID(t, "tenant1")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID IPV4 ADDRESS": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "testip", + "deliveryServiceId": GetDeliveryServiceId(t, "ds1")(), + "fqdn": "test.ip.com", + "ipAddress": "311.255.323.412", + "protocol": "http", + "tenantId": GetTenantID(t, "tenant1")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID IPV6 ADDRESS": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "testipv6", + "deliveryServiceId": GetDeliveryServiceId(t, "ds1")(), + "fqdn": "origin1.example.com", + "ip6Address": "badipv6::addresss", + "protocol": "http", + "tenantId": GetTenantID(t, "tenant1")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "PUT": { + "OK when VALID request": { + EndpointId: GetOriginID(t, "origin2"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "origin2", + "cachegroup": "multiOriginCachegroup", + "Coordinate": "coordinate2", + "deliveryService": "ds3", + "fqdn": "originupdated.example.com", + "ipAddress": "1.2.3.4", + "ip6Address": "0000::1111", + "port": 1234, + "protocol": "http", + "tenantId": GetTenantID(t, "tenant2")(), + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateOriginsUpdateCreateFields("origin2", map[string]interface{}{"Cachegroup": "multiOriginCachegroup", "Coordinate": "coordinate2", "DeliveryService": "ds3", + "FQDN": "originupdated.example.com", "IPAddress": "1.2.3.4", "IP6Address": "0000::1111", "Port": 1234, "Protocol": "http", "Tenant": "tenant2"})), + }, + "FORBIDDEN when CHILD TENANT updates PARENT TENANT ORIGIN": { + EndpointId: GetOriginID(t, "origin2"), + ClientSession: tenant4UserSession, + RequestBody: map[string]interface{}{ + "name": "testtenancy", + "deliveryServiceId": GetDeliveryServiceId(t, "ds1")(), + "fqdn": "testtenancy.example.com", + "protocol": "http", + "tenantId": GetTenantID(t, "tenant1")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + "NOT FOUND when ORIGIN DOESNT EXIST": { + EndpointId: func() int { return 1111111 }, + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "testid", + "deliveryServiceId": GetDeliveryServiceId(t, "ds1")(), + "fqdn": "testid.example.com", + "protocol": "http", + "tenantId": GetTenantID(t, "tenant1")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "BAD REQUEST when DELIVERY SERVICE DOESNT EXIST": { + EndpointId: GetOriginID(t, "origin2"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "origin2", + "deliveryServiceId": 11111111, + "fqdn": "origin2.example.com", + "protocol": "http", + "tenantId": GetTenantID(t, "tenant1")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "NOT FOUND when CACHEGROUP DOESNT EXIST": { + EndpointId: GetOriginID(t, "origin2"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "origin2", + "cachegroupId": 1111111, + "deliveryServiceId": GetDeliveryServiceId(t, "ds1")(), + "fqdn": "origin2.example.com", + "protocol": "http", + "tenantId": GetTenantID(t, "tenant1")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "NOT FOUND when PROFILEID DOESNT EXIST": { + EndpointId: GetOriginID(t, "origin2"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "origin2", + "cachegroup": "originCachegroup", + "deliveryServiceId": GetDeliveryServiceId(t, "ds1")(), + "fqdn": "origin2.example.com", + "profileId": 11111111, + "protocol": "http", + "tenantId": GetTenantID(t, "tenant1")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "NOT FOUND when COORDINATE DOESNT EXIST": { + EndpointId: GetOriginID(t, "origin2"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "origin2", + "cachegroup": "originCachegroup", + "coordinateId": 1111111, + "deliveryServiceId": GetDeliveryServiceId(t, "ds1")(), + "fqdn": "origin2.example.com", + "protocol": "http", + "tenantId": GetTenantID(t, "tenant1")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "FORBIDDEN when INVALID TENANT": { + EndpointId: GetOriginID(t, "origin2"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "origin1", + "cachegroup": "originCachegroup", + "deliveryServiceId": GetDeliveryServiceId(t, "ds1")(), + "fqdn": "origin1.example.com", + "protocol": "http", + "tenantId": 1111111, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + "BAD REQUEST when INVALID PROTOCOL": { + EndpointId: GetOriginID(t, "origin2"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "origin2", + "cachegroup": "originCachegroup", + "deliveryServiceId": GetDeliveryServiceId(t, "ds1")(), + "fqdn": "origin2.example.com", + "protocol": "htttttpssss", + "tenantId": GetTenantID(t, "tenant1")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID IPV4 ADDRESS": { + EndpointId: GetOriginID(t, "origin2"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "origin2", + "cachegroup": "originCachegroup", + "deliveryServiceId": GetDeliveryServiceId(t, "ds2")(), + "fqdn": "origin2.example.com", + "ipAddress": "300.254.123.1", + "protocol": "http", + "tenantId": GetTenantID(t, "tenant1")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID IPV6 ADDRESS": { + EndpointId: GetOriginID(t, "origin2"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "origin2", + "cachegroup": "originCachegroup", + "deliveryServiceId": GetDeliveryServiceId(t, "ds2")(), + "fqdn": "origin2.example.com", + "ip6Address": "test::42", + "protocol": "http", + "tenantId": GetTenantID(t, "tenant1")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID PORT": { + EndpointId: GetOriginID(t, "origin2"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "origin2", + "cachegroup": "originCachegroup", + "deliveryServiceId": GetDeliveryServiceId(t, "ds2")(), + "fqdn": "origin2.example.com", + "port": 80000, + "protocol": "http", + "tenantId": GetTenantID(t, "tenant1")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "PRECONDITION FAILED when updating with IMS & IUS Headers": { + EndpointId: GetOriginID(t, "origin2"), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}}, + RequestBody: map[string]interface{}{ + "name": "origin2", + "cachegroup": "originCachegroup", + "deliveryService": "ds2", + "fqdn": "origin2.example.com", + "protocol": "http", + "tenantId": GetTenantID(t, "tenant1")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + "PRECONDITION FAILED when updating with IFMATCH ETAG Header": { + EndpointId: GetOriginID(t, "origin2"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "origin2", + "cachegroup": "originCachegroup", + "deliveryService": "ds2", + "fqdn": "origin2.example.com", + "protocol": "http", + "tenantId": GetTenantID(t, "tenant1")(), + }, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + }, + "DELETE": { + "NOT FOUND when DOESNT EXIST": { + EndpointId: func() int { return 11111111 }, + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "FORBIDDEN when CHILD TENANT deletes PARENT TENANT ORIGIN": { + EndpointId: GetOriginID(t, "origin2"), + ClientSession: tenant4UserSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + origin := tc.Origin{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &origin) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetOrigins(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.CreateOrigin(origin, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.UpdateOrigin(testCase.EndpointId(), origin, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.DeleteOrigin(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateOriginsFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Origin response to not be nil.") + originResp := resp.([]tc.Origin) + for field, expected := range expectedResp { + for _, origin := range originResp { + switch field { + case "Cachegroup": + assert.RequireNotNil(t, origin.Cachegroup, "Expected Cachegroup to not be nil.") + assert.Equal(t, expected, *origin.Cachegroup, "Expected Cachegroup to be %v, but got %s", expected, *origin.Cachegroup) + case "CachegroupID": + assert.RequireNotNil(t, origin.CachegroupID, "Expected CachegroupID to not be nil.") + assert.Equal(t, expected, *origin.CachegroupID, "Expected CachegroupID to be %v, but got %d", expected, *origin.Cachegroup) + case "Coordinate": + assert.RequireNotNil(t, origin.Coordinate, "Expected Coordinate to not be nil.") + assert.Equal(t, expected, *origin.Coordinate, "Expected Coordinate to be %v, but got %s", expected, *origin.Coordinate) + case "CoordinateID": + assert.RequireNotNil(t, origin.CoordinateID, "Expected CoordinateID to not be nil.") + assert.Equal(t, expected, *origin.CoordinateID, "Expected CoordinateID to be %v, but got %d", expected, *origin.CoordinateID) + case "DeliveryService": + assert.RequireNotNil(t, origin.DeliveryService, "Expected DeliveryService to not be nil.") + assert.Equal(t, expected, *origin.DeliveryService, "Expected DeliveryService to be %v, but got %s", expected, *origin.DeliveryService) + case "DeliveryServiceID": + assert.RequireNotNil(t, origin.DeliveryServiceID, "Expected DeliveryServiceID to not be nil.") + assert.Equal(t, expected, *origin.DeliveryServiceID, "Expected DeliveryServiceID to be %v, but got %d", expected, *origin.DeliveryServiceID) + case "FQDN": + assert.RequireNotNil(t, origin.FQDN, "Expected FQDN to not be nil.") + assert.Equal(t, expected, *origin.FQDN, "Expected FQDN to be %v, but got %s", expected, *origin.FQDN) + case "ID": + assert.RequireNotNil(t, origin.ID, "Expected ID to not be nil.") + assert.Equal(t, expected, *origin.ID, "Expected ID to be %v, but got %d", expected, *origin.ID) + case "IPAddress": + assert.RequireNotNil(t, origin.IPAddress, "Expected IPAddress to not be nil.") + assert.Equal(t, expected, *origin.IPAddress, "Expected IPAddress to be %v, but got %s", expected, *origin.IPAddress) + case "IP6Address": + assert.RequireNotNil(t, origin.IP6Address, "Expected IP6Address to not be nil.") + assert.Equal(t, expected, *origin.IP6Address, "Expected IP6Address to be %v, but got %s", expected, *origin.IP6Address) + case "IsPrimary": + assert.RequireNotNil(t, origin.IsPrimary, "Expected IsPrimary to not be nil.") + assert.Equal(t, expected, *origin.IsPrimary, "Expected IsPrimary to be %v, but got %v", expected, *origin.IsPrimary) + case "Name": + assert.RequireNotNil(t, origin.Name, "Expected Name to not be nil.") + assert.Equal(t, expected, *origin.Name, "Expected Name to be %v, but got %s", expected, *origin.Name) + case "Port": + assert.RequireNotNil(t, origin.Port, "Expected Port to not be nil.") + assert.Equal(t, expected, *origin.Port, "Expected Port to be %v, but got %d", expected, *origin.Port) + case "Profile": + assert.RequireNotNil(t, origin.Profile, "Expected Profile to not be nil.") + assert.Equal(t, expected, *origin.Profile, "Expected Profile to be %v, but got %s", expected, *origin.Profile) + case "ProfileID": + assert.RequireNotNil(t, origin.ProfileID, "Expected ProfileID to not be nil.") + assert.Equal(t, expected, *origin.ProfileID, "Expected ProfileID to be %v, but got %d", expected, *origin.ProfileID) + case "Protocol": + assert.RequireNotNil(t, origin.Protocol, "Expected Protocol to not be nil.") + assert.Equal(t, expected, *origin.Protocol, "Expected Tenant to be %v, but got %s", expected, *origin.Protocol) + case "Tenant": + assert.RequireNotNil(t, origin.Tenant, "Expected Tenant to not be nil.") + assert.Equal(t, expected, *origin.Tenant, "Expected Tenant to be %v, but got %s", expected, *origin.Tenant) + case "TenantID": + assert.RequireNotNil(t, origin.TenantID, "Expected TenantID to not be nil.") + assert.Equal(t, expected, *origin.TenantID, "Expected TenantID to be %v, but got %d", expected, *origin.TenantID) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validateOriginsUpdateCreateFields(name string, expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", name) + origin, _, err := TOSession.GetOrigins(opts) + assert.RequireNoError(t, err, "Error getting Origin: %v - alerts: %+v", err, origin.Alerts) + assert.RequireEqual(t, 1, len(origin.Response), "Expected one Origin returned Got: %d", len(origin.Response)) + validateOriginsFields(expectedResp)(t, toclientlib.ReqInf{}, origin.Response, tc.Alerts{}, nil) + } +} + +func validateOriginsPagination(paginationParam string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + paginationResp := resp.([]tc.Origin) + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("orderby", "id") + respBase, _, err := TOSession.GetOrigins(opts) + assert.RequireNoError(t, err, "Cannot get Origins: %v - alerts: %+v", err, respBase.Alerts) + + origin := respBase.Response + assert.RequireGreaterOrEqual(t, len(origin), 3, "Need at least 3 Origins in Traffic Ops to test pagination support, found: %d", len(origin)) + switch paginationParam { + case "limit:": + assert.Exactly(t, origin[:1], paginationResp, "expected GET Origins with limit = 1 to return first result") + case "offset": + assert.Exactly(t, origin[1:2], paginationResp, "expected GET Origins with limit = 1, offset = 1 to return second result") + case "page": + assert.Exactly(t, origin[1:2], paginationResp, "expected GET Origins with limit = 1, page = 2 to return second result") + } + } +} + +func GetOriginID(t *testing.T, name string) func() int { + return func() int { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", name) + origins, _, err := TOSession.GetOrigins(opts) + assert.RequireNoError(t, err, "Get Origins Request failed with error:", err) + assert.RequireEqual(t, 1, len(origins.Response), "Expected response object length 1, but got %d", len(origins.Response)) + assert.RequireNotNil(t, origins.Response[0].ID, "Expected ID to not be nil.") + return *origins.Response[0].ID + } +} + +func CreateTestOrigins(t *testing.T) { + for _, origin := range testData.Origins { + resp, _, err := TOSession.CreateOrigin(origin, client.RequestOptions{}) + assert.RequireNoError(t, err, "Could not create Origins: %v - alerts: %+v", err, resp.Alerts) + } +} + +func DeleteTestOrigins(t *testing.T) { + origins, _, err := TOSession.GetOrigins(client.RequestOptions{}) + assert.NoError(t, err, "Cannot get Origins : %v - alerts: %+v", err, origins.Alerts) + + for _, origin := range origins.Response { + assert.RequireNotNil(t, origin.ID, "Expected origin ID to not be nil.") + assert.RequireNotNil(t, origin.Name, "Expected origin ID to not be nil.") + assert.RequireNotNil(t, origin.IsPrimary, "Expected origin ID to not be nil.") + if !*origin.IsPrimary { + alerts, _, err := TOSession.DeleteOrigin(*origin.ID, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error deleting Origin '%s' (#%d): %v - alerts: %+v", *origin.Name, *origin.ID, err, alerts.Alerts) + // Retrieve the Origin to see if it got deleted + opts := client.NewRequestOptions() + opts.QueryParameters.Set("id", strconv.Itoa(*origin.ID)) + getOrigin, _, err := TOSession.GetOrigins(opts) + assert.NoError(t, err, "Error getting Origin '%s' after deletion: %v - alerts: %+v", *origin.Name, err, getOrigin.Alerts) + assert.Equal(t, 0, len(getOrigin.Response), "Expected Origin '%s' to be deleted, but it was found in Traffic Ops", *origin.Name) + } + } +} + +func alertsHaveError(alerts []tc.Alert, err string) bool { + for _, alert := range alerts { + if alert.Level == tc.ErrorLevel.String() && strings.Contains(alert.Text, err) { + return true + } + } + return false +} diff --git a/traffic_ops/testing/api/v5/parameters_test.go b/traffic_ops/testing/api/v5/parameters_test.go new file mode 100644 index 0000000000..621663074e --- /dev/null +++ b/traffic_ops/testing/api/v5/parameters_test.go @@ -0,0 +1,428 @@ +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v5 + +import ( + "encoding/json" + "net/http" + "net/url" + "strconv" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestParameters(t *testing.T) { + WithObjs(t, []TCObj{Parameters}, func() { + + opsUserSession := utils.CreateV5Session(t, Config.TrafficOps.URL, "operations", "twelve", Config.Default.Session.TimeoutInSecs) + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {currentTimeRFC}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK when VALID request": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1)), + }, + "OK when VALID CONFIGFILE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"configFile": {"plugin.config"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateParametersFields(map[string]interface{}{"ConfigFile": "plugin.config"})), + }, + "OK when VALID VALUE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"value": {"90"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateParametersFields(map[string]interface{}{"Value": "90"})), + }, + "OK when VALID NAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"tm.instance_name"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateParametersFields(map[string]interface{}{"Name": "tm.instance_name"})), + }, + "VALUE HIDDEN when OPERATIONS USER views SECURE PARAMETER": { + ClientSession: opsUserSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"testSecure"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateParametersFields(map[string]interface{}{"Secure": true, "Value": "********"})), + }, + "EMPTY RESPONSE when NON-EXISTENT ID parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"id": {"10000"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when NON-EXISTENT NAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"doesntexist"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when NON-EXISTENT CONFIGFILE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"configFile": {"doesntexist"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when NON-EXISTENT VALUE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"value": {"doesntexist"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "FIRST RESULT when LIMIT=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateParametersPagination("limit")), + }, + "SECOND RESULT when LIMIT=1 OFFSET=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "offset": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateParametersPagination("offset")), + }, + "SECOND RESULT when LIMIT=1 PAGE=2": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "page": {"2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateParametersPagination("page")), + }, + "BAD REQUEST when INVALID LIMIT parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"-2"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID OFFSET parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "offset": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID PAGE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "page": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "POST": { + "OK when MULTIPLE PARAMETERS": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "parameters": []map[string]interface{}{ + { + "configFile": "multiple.config1", + "name": "CONFIG1 multiple config", + "secure": false, + "value": "INT 1", + }, + { + "configFile": "multiple.config2", + "name": "CONFIG2 multiple config", + "secure": false, + "value": "INT 2", + }, + { + "configFile": "multiple.config3", + "name": "CONFIG3 multiple config", + "secure": false, + "value": "INT 3", + }, + }, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "BAD REQUEST when ALREADY EXISTS": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "configFile": "records.config", + "name": "CONFIG proxy.config.allocator.enable_reclaim", + "secure": false, + "value": "INT 0", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when MISSING NAME FIELD": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "configFile": "missingname.config", + "name": "", + "secure": false, + "value": "test missing name", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when MISSING CONFIGFILE FIELD": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "configFile": "", + "name": "missing config file", + "secure": false, + "value": "test missing config file", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "PUT": { + "OK when VALID REQUEST": { + EndpointId: GetParameterID(t, "LogObject.Format", "logs_xml.config", "custom_ats_2"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "configFile": "updated.config", + "name": "updated name", + "secure": true, + "value": "updated value", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateParametersUpdateCreateFields("updated name", + map[string]interface{}{"ConfigFile": "updated.config", "Name": "updated name", "Secure": true, "Value": "updated value"})), + }, + "OK when MISSING VALUE FIELD": { + EndpointId: GetParameterID(t, "LogObject.Filename", "logs_xml.config", "custom_ats_2"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "configFile": "logs_new.config", + "name": "LogObject.Filename", + "secure": true, + "value": "", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateParametersUpdateCreateFields("LogObject.Filename", + map[string]interface{}{"ConfigFile": "logs_new.config", "Secure": true, "Value": ""})), + }, + "BAD REQUEST when MISSING NAME FIELD": { + EndpointId: GetParameterID(t, "astats_over_http.so", "plugin.config", ""), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "configFile": "missingname.config", + "name": "", + "secure": false, + "value": "test missing name", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when MISSING CONFIGFILE FIELD": { + EndpointId: GetParameterID(t, "astats_over_http.so", "plugin.config", ""), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "configFile": "", + "name": "missing config file", + "secure": false, + "value": "test missing config file", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "PRECONDITION FAILED when updating with IMS & IUS Headers": { + EndpointId: GetParameterID(t, "LogFormat.Name", "logs_xml.config", "custom_ats_2"), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}}, + RequestBody: map[string]interface{}{ + "configFile": "logs_xml.config", + "name": "LogFormat.Name", + "secure": false, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + "PRECONDITION FAILED when updating with IFMATCH ETAG Header": { + EndpointId: GetParameterID(t, "LogFormat.Name", "logs_xml.config", "custom_ats_2"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "configFile": "logs_xml.config", + "name": "LogFormat.Name", + "secure": false, + }, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + }, + "DELETE": { + "BAD REQUEST when DOESNT EXIST": { + EndpointId: func() int { return 100000 }, + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + parameter := tc.Parameter{} + parameters := []tc.Parameter{} + + if testCase.RequestBody != nil { + if params, ok := testCase.RequestBody["parameters"]; ok { + dat, err := json.Marshal(params) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, ¶meters) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, ¶meter) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET", "GET AFTER CHANGES": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetParameters(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + if len(parameters) == 0 { + alerts, reqInf, err := testCase.ClientSession.CreateParameter(parameter, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + } else { + alerts, reqInf, err := testCase.ClientSession.CreateMultipleParameters(parameters, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.UpdateParameter(testCase.EndpointId(), parameter, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.DeleteParameter(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateParametersFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Parameters response to not be nil.") + parameterResp := resp.([]tc.Parameter) + for field, expected := range expectedResp { + for _, parameter := range parameterResp { + switch field { + case "ConfigFile": + assert.Equal(t, expected, parameter.ConfigFile, "Expected ConfigFile to be %v, but got %s", expected, parameter.ConfigFile) + case "ID": + assert.Equal(t, expected, parameter.ID, "Expected ID to be %v, but got %d", expected, parameter.ID) + case "Name": + assert.Equal(t, expected, parameter.Name, "Expected Name to be %v, but got %s", expected, parameter.Name) + case "Secure": + assert.Equal(t, expected, parameter.Secure, "Expected Secure to be %v, but got %v", expected, parameter.Secure) + case "Value": + assert.Equal(t, expected, parameter.Value, "Expected Value to be %v, but got %s", expected, parameter.Value) + default: + t.Fatalf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validateParametersUpdateCreateFields(name string, expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", name) + parameters, _, err := TOSession.GetParameters(opts) + assert.RequireNoError(t, err, "Error getting Parameter: %v - alerts: %+v", err, parameters.Alerts) + assert.RequireEqual(t, 1, len(parameters.Response), "Expected one Parameter returned Got: %d", len(parameters.Response)) + validateParametersFields(expectedResp)(t, toclientlib.ReqInf{}, parameters.Response, tc.Alerts{}, nil) + } +} + +func validateParametersPagination(paginationParam string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + paginationResp := resp.([]tc.Parameter) + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("orderby", "id") + respBase, _, err := TOSession.GetParameters(opts) + assert.RequireNoError(t, err, "Cannot get Parameters: %v - alerts: %+v", err, respBase.Alerts) + + parameters := respBase.Response + assert.RequireGreaterOrEqual(t, len(parameters), 3, "Need at least 3 Parameters in Traffic Ops to test pagination support, found: %d", len(parameters)) + switch paginationParam { + case "limit:": + assert.Exactly(t, parameters[:1], paginationResp, "expected GET Parameters with limit = 1 to return first result") + case "offset": + assert.Exactly(t, parameters[1:2], paginationResp, "expected GET Parameters with limit = 1, offset = 1 to return second result") + case "page": + assert.Exactly(t, parameters[1:2], paginationResp, "expected GET Parameters with limit = 1, page = 2 to return second result") + } + } +} + +func GetParameterID(t *testing.T, name string, configFile string, value string) func() int { + return func() int { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", name) + opts.QueryParameters.Set("configFile", configFile) + if value != "" { + opts.QueryParameters.Set("value", value) + } + resp, _, err := TOSession.GetParameters(opts) + assert.RequireNoError(t, err, "Get Parameters Request failed with error: %v", err) + assert.RequireEqual(t, 1, len(resp.Response), "Expected response object length 1, but got %d", len(resp.Response)) + return resp.Response[0].ID + } +} + +func CreateTestParameters(t *testing.T) { + alerts, _, err := TOSession.CreateMultipleParameters(testData.Parameters, client.RequestOptions{}) + assert.RequireNoError(t, err, "Could not create Parameters: %v - alerts: %+v", err, alerts) +} + +func DeleteTestParameters(t *testing.T) { + parameters, _, err := TOSession.GetParameters(client.RequestOptions{}) + assert.RequireNoError(t, err, "Cannot get Parameters: %v - alerts: %+v", err, parameters.Alerts) + + for _, parameter := range parameters.Response { + alerts, _, err := TOSession.DeleteParameter(parameter.ID, client.RequestOptions{}) + assert.NoError(t, err, "Cannot delete Parameter #%d: %v - alerts: %+v", parameter.ID, err, alerts.Alerts) + + // Retrieve the Parameter to see if it got deleted + opts := client.NewRequestOptions() + opts.QueryParameters.Set("id", strconv.Itoa(parameter.ID)) + getParameters, _, err := TOSession.GetParameters(opts) + assert.NoError(t, err, "Unexpected error fetching Parameter #%d after deletion: %v - alerts: %+v", parameter.ID, err, getParameters.Alerts) + assert.Equal(t, 0, len(getParameters.Response), "Expected Parameter '%s' to be deleted, but it was found in Traffic Ops", parameter.Name) + } +} diff --git a/traffic_ops/testing/api/v5/phys_locations_test.go b/traffic_ops/testing/api/v5/phys_locations_test.go new file mode 100644 index 0000000000..6fd1afedc6 --- /dev/null +++ b/traffic_ops/testing/api/v5/phys_locations_test.go @@ -0,0 +1,361 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "sort" + "strconv" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestPhysLocations(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Parameters, Divisions, Regions, PhysLocations}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID request": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validatePhysicalLocationSort()), + }, + "OK when VALID NAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"Denver"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validatePhysicalLocationFields(map[string]interface{}{"Name": "Denver"})), + }, + "SORTED by ID when ORDERBY=ID parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validatePhysicalLocationIDSort()), + }, + "FIRST RESULT when LIMIT=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validatePhysicalLocationPagination("limit")), + }, + "SECOND RESULT when LIMIT=1 OFFSET=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "offset": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validatePhysicalLocationPagination("offset")), + }, + "SECOND RESULT when LIMIT=1 PAGE=2": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "page": {"2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validatePhysicalLocationPagination("page")), + }, + "BAD REQUEST when INVALID LIMIT parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"-2"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID OFFSET parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "offset": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID PAGE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "page": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "POST": { + "OK when VALID request": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "address": "100 blah lane", + "city": "foo", + "comments": "comment", + "email": "bar@foobar.com", + "name": "testPhysicalLocation", + "phone": "111-222-3333", + "region": "region1", + "regionId": GetRegionID(t, "region1")(), + "shortName": "testLocation1", + "state": "CO", + "zip": "80602", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validatePhysicalLocationUpdateCreateFields("testPhysicalLocation", map[string]interface{}{"Name": "testPhysicalLocation"})), + }, + "BAD REQUEST when REGION ID does NOT MATCH REGION NAME": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "address": "1234 southern way", + "city": "Atlanta", + "name": "HotAtlanta", + "phone": "404-222-2222", + "region": "region1", + "regionId": GetRegionID(t, "cdn-region2")(), + "shortName": "atlanta", + "state": "GA", + "zip": "30301", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "PUT": { + "OK when VALID request": { + EndpointId: GetPhysicalLocationID(t, "HotAtlanta"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "address": "1234 southern way", + "city": "NewCity", + "name": "HotAtlanta", + "phone": "404-222-2222", + "regionId": GetRegionID(t, "region1")(), + "shortName": "atlanta", + "state": "GA", + "zip": "30301", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validatePhysicalLocationUpdateCreateFields("HotAtlanta", map[string]interface{}{"City": "NewCity"})), + }, + "PRECONDITION FAILED when updating with IMS & IUS Headers": { + EndpointId: GetPhysicalLocationID(t, "HotAtlanta"), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}}, + RequestBody: map[string]interface{}{ + "address": "1234 southern way", + "city": "Atlanta", + "regionId": GetRegionID(t, "region1")(), + "name": "HotAtlanta", + "shortName": "atlanta", + "state": "GA", + "zip": "30301", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + "PRECONDITION FAILED when updating with IFMATCH ETAG Header": { + EndpointId: GetPhysicalLocationID(t, "HotAtlanta"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "address": "1234 southern way", + "city": "Atlanta", + "regionId": GetRegionID(t, "region1")(), + "name": "HotAtlanta", + "shortName": "atlanta", + "state": "GA", + "zip": "30301", + }, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + }, + "DELETE": { + "OK when VALID request": { + EndpointId: GetPhysicalLocationID(t, "testDelete"), + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + "GET AFTER CHANGES": { + "OK when CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {currentTimeRFC}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + pl := tc.PhysLocation{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &pl) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET", "GET AFTER CHANGES": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetPhysLocations(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.CreatePhysLocation(pl, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.UpdatePhysLocation(testCase.EndpointId(), pl, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.DeletePhysLocation(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func validatePhysicalLocationFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Physical Location response to not be nil.") + plResp := resp.([]tc.PhysLocation) + for field, expected := range expectedResp { + for _, pl := range plResp { + switch field { + case "Name": + assert.Equal(t, expected, pl.Name, "Expected Name to be %v, but got %s", expected, pl.Name) + case "City": + assert.Equal(t, expected, pl.City, "Expected City to be %v, but got %s", expected, pl.City) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validatePhysicalLocationUpdateCreateFields(name string, expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", name) + pl, _, err := TOSession.GetPhysLocations(opts) + assert.RequireNoError(t, err, "Error getting Physical Location: %v - alerts: %+v", err, pl.Alerts) + assert.RequireEqual(t, 1, len(pl.Response), "Expected one Physical Location returned Got: %d", len(pl.Response)) + validatePhysicalLocationFields(expectedResp)(t, toclientlib.ReqInf{}, pl.Response, tc.Alerts{}, nil) + } +} + +func validatePhysicalLocationPagination(paginationParam string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + paginationResp := resp.([]tc.PhysLocation) + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("orderby", "id") + respBase, _, err := TOSession.GetPhysLocations(opts) + assert.RequireNoError(t, err, "Cannot get Physical Locations: %v - alerts: %+v", err, respBase.Alerts) + + pl := respBase.Response + assert.RequireGreaterOrEqual(t, len(pl), 3, "Need at least 3 Physical Locations in Traffic Ops to test pagination support, found: %d", len(pl)) + switch paginationParam { + case "limit:": + assert.Exactly(t, pl[:1], paginationResp, "expected GET Physical Locations with limit = 1 to return first result") + case "offset": + assert.Exactly(t, pl[1:2], paginationResp, "expected GET Physical Locations with limit = 1, offset = 1 to return second result") + case "page": + assert.Exactly(t, pl[1:2], paginationResp, "expected GET Physical Locations with limit = 1, page = 2 to return second result") + } + } +} + +func validatePhysicalLocationSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Physical Location response to not be nil.") + var physLocNames []string + physLocResp := resp.([]tc.PhysLocation) + for _, pl := range physLocResp { + physLocNames = append(physLocNames, pl.Name) + } + assert.Equal(t, true, sort.StringsAreSorted(physLocNames), "List is not sorted by their names: %v", physLocNames) + } +} + +func validatePhysicalLocationIDSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Physical Location response to not be nil.") + var physLocIDs []int + physLocResp := resp.([]tc.PhysLocation) + for _, pl := range physLocResp { + physLocIDs = append(physLocIDs, pl.ID) + } + assert.Equal(t, true, sort.IntsAreSorted(physLocIDs), "List is not sorted by their ids: %v", physLocIDs) + } +} + +func GetRegionID(t *testing.T, name string) func() int { + return func() int { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", name) + regions, _, err := TOSession.GetRegions(opts) + assert.RequireNoError(t, err, "Get Regions Request failed with error:", err) + assert.RequireEqual(t, 1, len(regions.Response), "Expected response object length 1, but got %d", len(regions.Response)) + return regions.Response[0].ID + } +} + +func GetPhysicalLocationID(t *testing.T, name string) func() int { + return func() int { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", name) + physicalLocations, _, err := TOSession.GetPhysLocations(opts) + assert.RequireNoError(t, err, "Get PhysLocation Request failed with error:", err) + assert.RequireEqual(t, 1, len(physicalLocations.Response), "Expected response object length 1, but got %d", len(physicalLocations.Response)) + return physicalLocations.Response[0].ID + } +} + +func CreateTestPhysLocations(t *testing.T) { + for _, pl := range testData.PhysLocations { + resp, _, err := TOSession.CreatePhysLocation(pl, client.RequestOptions{}) + assert.RequireNoError(t, err, "Could not create Physical Location '%s': %v - alerts: %+v", pl.Name, err, resp.Alerts) + } +} + +func DeleteTestPhysLocations(t *testing.T) { + physicalLocations, _, err := TOSession.GetPhysLocations(client.RequestOptions{}) + assert.NoError(t, err, "Cannot get Physical Locations: %v - alerts: %+v", err, physicalLocations.Alerts) + + for _, pl := range physicalLocations.Response { + alerts, _, err := TOSession.DeletePhysLocation(pl.ID, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error deleting Physical Location '%s' (#%d): %v - alerts: %+v", pl.Name, pl.ID, err, alerts.Alerts) + // Retrieve the PhysLocation to see if it got deleted + opts := client.NewRequestOptions() + opts.QueryParameters.Set("id", strconv.Itoa(pl.ID)) + getPL, _, err := TOSession.GetPhysLocations(opts) + assert.NoError(t, err, "Error getting Physical Location '%s' after deletion: %v - alerts: %+v", pl.Name, err, getPL.Alerts) + assert.Equal(t, 0, len(getPL.Response), "Expected Physical Location '%s' to be deleted, but it was found in Traffic Ops", pl.Name) + } +} diff --git a/traffic_ops/testing/api/v5/ping_test.go b/traffic_ops/testing/api/v5/ping_test.go new file mode 100644 index 0000000000..3baa468bab --- /dev/null +++ b/traffic_ops/testing/api/v5/ping_test.go @@ -0,0 +1,53 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/http" + "testing" + + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" +) + +func TestPing(t *testing.T) { + + methodTests := utils.V5TestCase{ + "GET": { + "OK when VALID request": { + ClientSession: TOSession, Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK when UNAUTHENTICATED": { + ClientSession: NoAuthTOSession, Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.Ping(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Ping, resp.Alerts, err) + } + }) + } + } + }) + } +} diff --git a/traffic_ops/testing/api/v5/profile_parameters_test.go b/traffic_ops/testing/api/v5/profile_parameters_test.go new file mode 100644 index 0000000000..5c25c8f5b5 --- /dev/null +++ b/traffic_ops/testing/api/v5/profile_parameters_test.go @@ -0,0 +1,261 @@ +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v5 + +import ( + "encoding/json" + "net/http" + "net/url" + "strconv" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestProfileParameters(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Parameters, Profiles, ProfileParameters}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID request": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{ + "profileId": {strconv.Itoa(GetProfileID(t, "RASCAL1")())}, + "parameterId": {strconv.Itoa(GetParameterID(t, "peers.polling.interval", "rascal-config.txt", "60")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + "POST": { + "OK when MULTIPLE PARAMETERS": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "profileParameters": []map[string]interface{}{ + { + "profileId": GetProfileID(t, "MID1")(), + "parameterId": GetParameterID(t, "CONFIG proxy.config.admin.user_id", "records.config", "STRING ats")(), + }, + { + "profileId": GetProfileID(t, "MID2")(), + "parameterId": GetParameterID(t, "CONFIG proxy.config.admin.user_id", "records.config", "STRING ats")(), + }, + }, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "BAD REQUEST when INVALID PROFILEID and PARAMETERID": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "profileId": 0, + "parameterId": 0, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when MISSING PROFILEID field": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "parameterId": GetParameterID(t, "health.threshold.queryTime", "rascal.properties", "1000")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when MISSING PARAMETERID field": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "profileId": GetProfileID(t, "EDGE2")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when EMPTY BODY": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when ALREADY EXISTS": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "profileId": GetProfileID(t, "EDGE1")(), + "parameterId": GetParameterID(t, "health.threshold.availableBandwidthInKbps", "rascal.properties", ">1750000")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "DELETE": { + "OK when VALID request": { + EndpointId: GetProfileID(t, "ATS_EDGE_TIER_CACHE"), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{ + "parameterId": {strconv.Itoa(GetParameterID(t, "location", "set_dscp_37.config", "/etc/trafficserver/dscp")())}, + }}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + profileParameter := tc.ProfileParameterCreationRequest{} + profileParameters := []tc.ProfileParameterCreationRequest{} + + if testCase.RequestBody != nil { + if profileParams, ok := testCase.RequestBody["profileParameters"]; ok { + dat, err := json.Marshal(profileParams) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &profileParameters) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &profileParameter) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetProfileParameters(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + if len(profileParameters) == 0 { + alerts, reqInf, err := testCase.ClientSession.CreateProfileParameter(profileParameter, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + } else { + alerts, reqInf, err := testCase.ClientSession.CreateMultipleProfileParameters(profileParameters, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + parameterId, _ := strconv.Atoi(testCase.RequestOpts.QueryParameters["parameterId"][0]) + alerts, reqInf, err := testCase.ClientSession.DeleteProfileParameter(testCase.EndpointId(), parameterId, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func TestProfileParameter(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Parameters, Profiles}, func() { + + methodTests := utils.V5TestCase{ + "POST": { + "OK when VALID request": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "profileId": GetProfileID(t, "ATS_EDGE_TIER_CACHE")(), + "paramIds": []int64{ + int64(GetParameterID(t, "CONFIG proxy.config.allocator.enable_reclaim", "records.config", "INT 0")()), + int64(GetParameterID(t, "CONFIG proxy.config.allocator.max_overage", "records.config", "INT 3")()), + }, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + profileParameter := tc.PostProfileParam{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &profileParameter) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.CreateProfileWithMultipleParameters(profileParameter, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} +func CreateTestProfileParameters(t *testing.T) { + for _, profile := range testData.Profiles { + profileID := GetProfileID(t, profile.Name)() + + for _, parameter := range profile.Parameters { + assert.RequireNotNil(t, parameter.Name, "Expected parameter name to not be nil.") + assert.RequireNotNil(t, parameter.Value, "Expected parameter value to not be nil.") + assert.RequireNotNil(t, parameter.ConfigFile, "Expected parameter configFile to not be nil.") + + parameterOpts := client.NewRequestOptions() + parameterOpts.QueryParameters.Set("name", *parameter.Name) + parameterOpts.QueryParameters.Set("configFile", *parameter.ConfigFile) + parameterOpts.QueryParameters.Set("value", *parameter.Value) + getParameter, _, err := TOSession.GetParameters(parameterOpts) + assert.RequireNoError(t, err, "Could not get Parameter %s: %v - alerts: %+v", *parameter.Name, err, getParameter.Alerts) + if len(getParameter.Response) == 0 { + alerts, _, err := TOSession.CreateParameter(tc.Parameter{Name: *parameter.Name, Value: *parameter.Value, ConfigFile: *parameter.ConfigFile}, client.RequestOptions{}) + assert.RequireNoError(t, err, "Could not create Parameter %s: %v - alerts: %+v", parameter.Name, err, alerts.Alerts) + getParameter, _, err = TOSession.GetParameters(parameterOpts) + assert.RequireNoError(t, err, "Could not get Parameter %s: %v - alerts: %+v", *parameter.Name, err, getParameter.Alerts) + assert.RequireNotEqual(t, 0, len(getParameter.Response), "Could not get parameter %s: not found", *parameter.Name) + } + profileParameter := tc.ProfileParameterCreationRequest{ProfileID: profileID, ParameterID: getParameter.Response[0].ID} + alerts, _, err := TOSession.CreateProfileParameter(profileParameter, client.RequestOptions{}) + assert.NoError(t, err, "Could not associate Parameter %s with Profile %s: %v - alerts: %+v", parameter.Name, profile.Name, err, alerts.Alerts) + } + } +} + +func DeleteTestProfileParameters(t *testing.T) { + profileParameters, _, err := TOSession.GetProfileParameters(client.RequestOptions{}) + assert.NoError(t, err, "Cannot get Profile Parameters: %v - alerts: %+v", err, profileParameters.Alerts) + + for _, profileParameter := range profileParameters.Response { + alerts, _, err := TOSession.DeleteProfileParameter(GetProfileID(t, profileParameter.Profile)(), profileParameter.Parameter, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error deleting Profile Parameter: Profile: '%s' Parameter ID: (#%d): %v - alerts: %+v", profileParameter.Profile, profileParameter.Parameter, err, alerts.Alerts) + } + // Retrieve the Profile Parameters to see if it got deleted + getProfileParameter, _, err := TOSession.GetProfileParameters(client.RequestOptions{}) + assert.NoError(t, err, "Error getting Profile Parameters after deletion: %v - alerts: %+v", err, getProfileParameter.Alerts) + assert.Equal(t, 0, len(getProfileParameter.Response), "Expected Profile Parameters to be deleted, but %d were found in Traffic Ops", len(getProfileParameter.Response)) +} diff --git a/traffic_ops/testing/api/v5/profiles_export_test.go b/traffic_ops/testing/api/v5/profiles_export_test.go new file mode 100644 index 0000000000..271944b8ca --- /dev/null +++ b/traffic_ops/testing/api/v5/profiles_export_test.go @@ -0,0 +1,78 @@ +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v5 + +import ( + "net/http" + "reflect" + "testing" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +func TestProfilesExport(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Parameters, Profiles, ProfileParameters}, func() { + + methodTests := utils.V5TestCase{ + "GET": { + "OK when VALID request": { + EndpointId: GetProfileID(t, "EDGE1"), + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateProfilesExportFields(map[string]interface{}{"CDNName": "cdn1", "Name": "EDGE1", + "Description": "edge1 description", "Type": "ATS_PROFILE"})), + }, + "NOT FOUND when PROFILE DOESNT EXIST": { + EndpointId: func() int { return 1111111111 }, + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.ExportProfile(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp, resp.Alerts, err) + } + }) + } + } + }) + } + + }) +} + +func validateProfilesExportFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Profiles Export response to not be nil.") + profileExport := resp.(tc.ProfileExportResponse) + for field, expected := range expectedResp { + fieldValue := reflect.Indirect(reflect.ValueOf(profileExport.Profile).FieldByName(field)).String() + assert.RequireNotNil(t, fieldValue, "Expected %s to not be nil.", field) + assert.Equal(t, expected, fieldValue, "Expected %s to be %v, but got %s", field, expected, fieldValue) + } + } +} diff --git a/traffic_ops/testing/api/v5/profiles_import_test.go b/traffic_ops/testing/api/v5/profiles_import_test.go new file mode 100644 index 0000000000..e900a7428a --- /dev/null +++ b/traffic_ops/testing/api/v5/profiles_import_test.go @@ -0,0 +1,128 @@ +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v5 + +import ( + "encoding/json" + "net/http" + "reflect" + "testing" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +func TestProfilesImport(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Parameters, Profiles, ProfileParameters}, func() { + + methodTests := utils.V5TestCase{ + "POST": { + "OK when VALID request": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "importProfile": map[string]interface{}{ + "profile": map[string]interface{}{ + "name": "GLOBAL", + "description": "Global Traffic Ops profile", + "cdn": "cdn1", + "type": "UNK_PROFILE", + }, + "parameters": []map[string]interface{}{ + { + "config_file": "global", + "name": "tm.instance_name", + "value": "Traffic Ops CDN", + }, + { + "config_file": "global", + "name": "tm.toolname", + "value": "Traffic Ops", + }, + }, + }, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateProfilesImport(map[string]interface{}{"Name": "GLOBAL", "CDNName": "cdn1", + "Description": "Global Traffic Ops profile", "Type": "UNK_PROFILE"})), + }, + "BAD REQUEST when SPACE in PROFILE NAME": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "importProfile": map[string]interface{}{ + "profile": map[string]interface{}{ + "name": "GLOBAL SPACES", + "description": "Global Traffic Ops profile", + "cdn": "cdn1", + "type": "UNK_PROFILE", + }, + "parameters": []map[string]interface{}{ + { + "config_file": "global", + "name": "tm.instance_name", + "value": "Traffic Ops CDN", + }, + }, + }, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + importProfile := tc.ProfileImportRequest{} + + if testCase.RequestBody != nil { + if importProfileBody, ok := testCase.RequestBody["importProfile"]; ok { + dat, err := json.Marshal(importProfileBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &importProfile) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + } + + switch method { + case "POST": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.ImportProfile(importProfile, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + } + } + }) + } + + }) +} + +func validateProfilesImport(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Profiles Export response to not be nil.") + profileImportResp := resp.(tc.ProfileImportResponseObj) + profileImport := profileImportResp.ProfileExportImportNullable + for field, expected := range expectedResp { + fieldValue := reflect.Indirect(reflect.ValueOf(profileImport).FieldByName(field)).String() + assert.RequireNotNil(t, fieldValue, "Expected %s to not be nil.", field) + assert.Equal(t, expected, fieldValue, "Expected %s to be %v, but got %s", field, expected, fieldValue) + } + } +} diff --git a/traffic_ops/testing/api/v5/profiles_name_copy_test.go b/traffic_ops/testing/api/v5/profiles_name_copy_test.go new file mode 100644 index 0000000000..b6434bd29e --- /dev/null +++ b/traffic_ops/testing/api/v5/profiles_name_copy_test.go @@ -0,0 +1,93 @@ +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v5 + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" +) + +func TestProfilesNameCopy(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Parameters, Profiles, ProfileParameters}, func() { + + methodTests := utils.V5TestCase{ + "POST": { + "OK when VALID request": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "profileCopyFrom": "EDGE1", + "name": "edge1-copy", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "BAD REQUEST when NEW PROFILE NAME has SPACES": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "profileCopyFrom": "EDGE1", + "name": "Profile Has Spaces", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when PROFILE NAME ALREADY EXISTS": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "profileCopyFrom": "EDGE1", + "name": "EDGE2", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "NOT FOUND when PROFILE to COPY FROM DOESNT EXIST": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "profileCopyFrom": "DOESNTEXIST", + "name": "profileCopyFail", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + profileCopy := tc.ProfileCopy{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &profileCopy) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "POST": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.CopyProfile(profileCopy, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + } + } + }) + } + }) +} diff --git a/traffic_ops/testing/api/v5/profiles_test.go b/traffic_ops/testing/api/v5/profiles_test.go new file mode 100644 index 0000000000..a17b273253 --- /dev/null +++ b/traffic_ops/testing/api/v5/profiles_test.go @@ -0,0 +1,373 @@ +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v5 + +import ( + "encoding/json" + "net/http" + "net/url" + "strconv" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + tc "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestProfiles(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Parameters, Profiles, ProfileParameters}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {currentTimeRFC}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK when VALID NAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"RASCAL1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + validateProfilesFields(map[string]interface{}{"Name": "RASCAL1"})), + }, + "OK when VALID PARAM parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{ + "id": {strconv.Itoa(GetProfileID(t, "EDGE1")())}, + "param": {strconv.Itoa(GetParameterID(t, "health.threshold.loadavg", "rascal.properties", "25.0")())}, + }}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateProfilesFields(map[string]interface{}{"Parameter": "health.threshold.loadavg"})), + }, + "OK when VALID CDN parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"cdn": {strconv.Itoa(GetCDNID(t, "cdn1")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateProfilesFields(map[string]interface{}{"CDNName": "cdn1"})), + }, + "OK when VALID ID parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"id": {strconv.Itoa(GetProfileID(t, "EDGEInCDN2")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + validateProfilesFields(map[string]interface{}{"ID": GetProfileID(t, "EDGEInCDN2")()})), + }, + "FIRST RESULT when LIMIT=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateProfilesPagination("limit")), + }, + "SECOND RESULT when LIMIT=1 OFFSET=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "offset": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateProfilesPagination("offset")), + }, + "SECOND RESULT when LIMIT=1 PAGE=2": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "page": {"2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateProfilesPagination("page")), + }, + "BAD REQUEST when INVALID LIMIT parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"-2"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID OFFSET parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "offset": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID PAGE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "page": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "POST": { + "BAD REQUEST when NAME has SPACES": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "cdn": GetCDNID(t, "cdn1")(), + "description": "name has spaces test", + "name": "name has space", + "type": tc.CacheServerProfileType, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when MISSING ALL FIELDS": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "cdn": 0, + "description": "", + "name": "", + "type": "", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID CDN ID": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "cdn": 0, + "description": "description", + "name": "badprofile", + "type": tc.CacheServerProfileType, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when MISSING DESCRIPTION FIELD": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "cdn": GetCDNID(t, "cdn1")(), + "description": "", + "name": "missing_description", + "type": tc.CacheServerProfileType, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when MISSING NAME FIELD": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "cdn": GetCDNID(t, "cdn1")(), + "description": "missing name", + "name": "", + "type": tc.CacheServerProfileType, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when MISSING TYPE FIELD": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "cdn": GetCDNID(t, "cdn1")(), + "description": "missing type", + "name": "missing_type", + "type": "", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "PUT": { + "OK when VALID REQUEST": { + EndpointId: GetProfileID(t, "EDGE2"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "cdn": GetCDNID(t, "cdn2")(), + "description": "edge2 description updated", + "name": "EDGE2UPDATED", + "routing_disabled": false, + "type": "TR_PROFILE", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateProfilesUpdateCreateFields("EDGE2UPDATED", + map[string]interface{}{"CDNName": "cdn2", "Description": "edge2 description updated", + "Name": "EDGE2UPDATED", "RoutingDisabled": false, "Type": "TR_PROFILE"})), + }, + "PRECONDITION FAILED when updating with IMS & IUS Headers": { + EndpointId: GetProfileID(t, "CCR1"), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}}, + RequestBody: map[string]interface{}{ + "cdn": GetCDNID(t, "cdn1")(), + "description": "cdn1 description", + "name": "CCR1", + "routing_disabled": false, + "type": "TR_PROFILE", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + "PRECONDITION FAILED when updating with IFMATCH ETAG Header": { + EndpointId: GetProfileID(t, "CCR1"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "cdn": GetCDNID(t, "cdn1")(), + "description": "cdn1 description", + "name": "CCR1", + "routing_disabled": false, + "type": "TR_PROFILE", + }, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + profile := tc.Profile{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &profile) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetProfiles(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.CreateProfile(profile, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.UpdateProfile(testCase.EndpointId(), profile, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.DeleteProfile(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateProfilesFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Profiles response to not be nil.") + profileResp := resp.([]tc.Profile) + for field, expected := range expectedResp { + for _, profile := range profileResp { + switch field { + case "CDNID": + assert.Equal(t, expected, profile.CDNID, "Expected CDNID to be %v, but got %d", expected, profile.CDNID) + case "CDNName": + assert.Equal(t, expected, profile.CDNName, "Expected CDNName to be %v, but got %s", expected, profile.CDNName) + case "Description": + assert.Equal(t, expected, profile.Description, "Expected Description to be %v, but got %s", expected, profile.Description) + case "ID": + assert.Equal(t, expected, profile.ID, "Expected ID to be %v, but got %d", expected, profile.ID) + case "Name": + assert.Equal(t, expected, profile.Name, "Expected Name to be %v, but got %s", expected, profile.Name) + case "Parameter": + assert.Equal(t, true, validateProfileContainsParameter(t, expected.(string), profile.Parameters), "Expected to find Parameter in Profiles Parameters list.") + case "Parameters": + assert.Exactly(t, expected, profile.Parameters, "Expected Parameters to be %v, but got %s", expected, profile.Parameters) + case "RoutingDisabled": + assert.Equal(t, expected, profile.RoutingDisabled, "Expected RoutingDisabled to be %v, but got %v", expected, profile.RoutingDisabled) + case "Type": + assert.Equal(t, expected, profile.Type, "Expected Type to be %v, but got %s", expected, profile.Type) + default: + t.Fatalf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validateProfilesUpdateCreateFields(name string, expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", name) + profiles, _, err := TOSession.GetProfiles(opts) + assert.RequireNoError(t, err, "Error getting Profile: %v - alerts: %+v", err, profiles.Alerts) + assert.RequireEqual(t, 1, len(profiles.Response), "Expected one Profile returned Got: %d", len(profiles.Response)) + validateProfilesFields(expectedResp)(t, toclientlib.ReqInf{}, profiles.Response, tc.Alerts{}, nil) + } +} + +func validateProfilesPagination(paginationParam string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + paginationResp := resp.([]tc.Profile) + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("orderby", "id") + respBase, _, err := TOSession.GetProfiles(opts) + assert.RequireNoError(t, err, "Cannot get Profiles: %v - alerts: %+v", err, respBase.Alerts) + + profiles := respBase.Response + assert.RequireGreaterOrEqual(t, len(profiles), 3, "Need at least 3 Profiles in Traffic Ops to test pagination support, found: %d", len(profiles)) + switch paginationParam { + case "limit:": + assert.Exactly(t, profiles[:1], paginationResp, "expected GET Profiles with limit = 1 to return first result") + case "offset": + assert.Exactly(t, profiles[1:2], paginationResp, "expected GET Profiles with limit = 1, offset = 1 to return second result") + case "page": + assert.Exactly(t, profiles[1:2], paginationResp, "expected GET Profiles with limit = 1, page = 2 to return second result") + } + } +} + +func validateProfileContainsParameter(t *testing.T, expectedParameter string, actualParameters []tc.ParameterNullable) bool { + for _, parameter := range actualParameters { + assert.RequireNotNil(t, parameter.Name, "Expected Parameter Name to not be nil.") + if expectedParameter == *parameter.Name { + return true + } + } + return false +} + +func GetProfileID(t *testing.T, profileName string) func() int { + return func() int { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", profileName) + resp, _, err := TOSession.GetProfiles(opts) + assert.RequireNoError(t, err, "Get Profiles Request failed with error: %v", err) + assert.RequireEqual(t, 1, len(resp.Response), "Expected response object length 1, but got %d", len(resp.Response)) + return resp.Response[0].ID + } +} + +func CreateTestProfiles(t *testing.T) { + for _, profile := range testData.Profiles { + resp, _, err := TOSession.CreateProfile(profile, client.RequestOptions{}) + assert.RequireNoError(t, err, "Could not create Profile '%s': %v - alerts: %+v", profile.Name, err, resp.Alerts) + } +} + +func DeleteTestProfiles(t *testing.T) { + profiles, _, err := TOSession.GetProfiles(client.RequestOptions{}) + assert.NoError(t, err, "Cannot get Profiles: %v - alerts: %+v", err, profiles.Alerts) + for _, profile := range profiles.Response { + alerts, _, err := TOSession.DeleteProfile(profile.ID, client.RequestOptions{}) + assert.NoError(t, err, "Cannot delete Profile: %v - alerts: %+v", err, alerts.Alerts) + // Retrieve the Profile to see if it got deleted + opts := client.NewRequestOptions() + opts.QueryParameters.Set("id", strconv.Itoa(profile.ID)) + getProfiles, _, err := TOSession.GetProfiles(opts) + assert.NoError(t, err, "Error getting Profile '%s' after deletion: %v - alerts: %+v", profile.Name, err, getProfiles.Alerts) + assert.Equal(t, 0, len(getProfiles.Response), "Expected Profile '%s' to be deleted, but it was found in Traffic Ops", profile.Name) + } +} diff --git a/traffic_ops/testing/api/v5/regions_test.go b/traffic_ops/testing/api/v5/regions_test.go new file mode 100644 index 0000000000..88856f8dbd --- /dev/null +++ b/traffic_ops/testing/api/v5/regions_test.go @@ -0,0 +1,352 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "sort" + "strconv" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestRegions(t *testing.T) { + WithObjs(t, []TCObj{Parameters, Divisions, Regions}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {currentTimeRFC}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK when VALID request": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateRegionsSort()), + }, + "OK when VALID NAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"region1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + validateRegionsFields(map[string]interface{}{"Name": "region1"})), + }, + "OK when VALID DIVISION parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"division": {strconv.Itoa(GetDivisionID(t, "division1")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateRegionsFields(map[string]interface{}{"DivisionName": "division1"})), + }, + "EMPTY RESPONSE when REGION NAME DOESNT EXIST": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"doesntexist"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when REGION ID DOESNT EXIST": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"id": {"9999999"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when DIVISION DOESNT EXIST": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"division": {"9999999"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "VALID when SORTORDER param is DESC": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"sortOrder": {"desc"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateRegionsDescSort()), + }, + "FIRST RESULT when LIMIT=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateRegionsPagination("limit")), + }, + "SECOND RESULT when LIMIT=1 OFFSET=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "offset": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateRegionsPagination("offset")), + }, + "SECOND RESULT when LIMIT=1 PAGE=2": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "page": {"2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateRegionsPagination("page")), + }, + "BAD REQUEST when INVALID LIMIT parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"-2"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID OFFSET parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "offset": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID PAGE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "page": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "POST": { + "NOT FOUND when DIVISION DOESNT EXIST": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "invalidDivision", + "division": 99999999, + "divisionName": "doesntexist", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + }, + "PUT": { + "OK when VALID request": { + EndpointId: GetRegionID(t, "cdn-region2"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "newName", + "division": GetDivisionID(t, "cdn-div2")(), + "divisionName": "cdn-div2", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateRegionsUpdateCreateFields("newName", map[string]interface{}{"Name": "newName"})), + }, + "PRECONDITION FAILED when updating with IMS & IUS Headers": { + EndpointId: GetRegionID(t, "region1"), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}}, + RequestBody: map[string]interface{}{ + "name": "newName", + "division": GetDivisionID(t, "division1")(), + "divisionName": "division1", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + "PRECONDITION FAILED when updating with IFMATCH ETAG Header": { + EndpointId: GetRegionID(t, "region1"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "newName", + "division": GetDivisionID(t, "division1")(), + "divisionName": "division1", + }, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + }, + "DELETE": { + "OK when VALID request": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"test-deletion"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "NOT FOUND when INVALID ID": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"id": {"99999999"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "NOT FOUND when INVALID NAME": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"doesntexist"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + region := tc.Region{} + regionName := "" + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, ®ion) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetRegions(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.CreateRegion(region, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.UpdateRegion(testCase.EndpointId(), region, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + if val, ok := testCase.RequestOpts.QueryParameters["name"]; ok { + regionName = val[0] + } + alerts, reqInf, err := testCase.ClientSession.DeleteRegion(regionName, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateRegionsFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Regions response to not be nil.") + regionResp := resp.([]tc.Region) + for field, expected := range expectedResp { + for _, region := range regionResp { + switch field { + case "Division": + assert.Equal(t, expected, region.Division, "Expected Division to be %v, but got %d", expected, region.Division) + case "DivisionName": + assert.Equal(t, expected, region.DivisionName, "Expected DivisionName to be %v, but got %s", expected, region.DivisionName) + case "ID": + assert.Equal(t, expected, region.ID, "Expected ID to be %v, but got %d", expected, region.ID) + case "Name": + assert.Equal(t, expected, region.Name, "Expected Name to be %v, but got %s", expected, region.Name) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validateRegionsUpdateCreateFields(name string, expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", name) + region, _, err := TOSession.GetRegions(opts) + assert.RequireNoError(t, err, "Error getting Region: %v - alerts: %+v", err, region.Alerts) + assert.RequireEqual(t, 1, len(region.Response), "Expected one Region returned Got: %d", len(region.Response)) + validateRegionsFields(expectedResp)(t, toclientlib.ReqInf{}, region.Response, tc.Alerts{}, nil) + } +} + +func validateRegionsSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Regions response to not be nil.") + var regionNames []string + regionResp := resp.([]tc.Region) + for _, region := range regionResp { + regionNames = append(regionNames, region.Name) + } + assert.Equal(t, true, sort.StringsAreSorted(regionNames), "List is not sorted by their names: %v", regionNames) + } +} + +func validateRegionsDescSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Regions response to not be nil.") + regionDescResp := resp.([]tc.Region) + var descSortedList []string + var ascSortedList []string + assert.RequireGreaterOrEqual(t, len(regionDescResp), 2, "Need at least 2 Regions in Traffic Ops to test desc sort, found: %d", len(regionDescResp)) + // Get Regions in the default ascending order for comparison. + regionAscResp, _, err := TOSession.GetRegions(client.RequestOptions{}) + assert.RequireNoError(t, err, "Unexpected error getting Regions with default sort order: %v - alerts: %+v", err, regionAscResp.Alerts) + // Verify the response match in length, i.e. equal amount of Regions. + assert.RequireEqual(t, len(regionAscResp.Response), len(regionDescResp), "Expected descending order response length: %v, to match ascending order response length %v", len(regionAscResp.Response), len(regionDescResp)) + // Insert Region names to the front of a new list, so they are now reversed to be in ascending order. + for _, region := range regionDescResp { + descSortedList = append([]string{region.Name}, descSortedList...) + } + // Insert Region names by appending to a new list, so they stay in ascending order. + for _, region := range regionAscResp.Response { + ascSortedList = append(ascSortedList, region.Name) + } + assert.Exactly(t, ascSortedList, descSortedList, "Region responses are not equal after reversal: %v - %v", ascSortedList, descSortedList) + } +} + +func validateRegionsPagination(paginationParam string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + paginationResp := resp.([]tc.Region) + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("orderby", "id") + respBase, _, err := TOSession.GetRegions(opts) + assert.RequireNoError(t, err, "Cannot get Regions: %v - alerts: %+v", err, respBase.Alerts) + + region := respBase.Response + assert.RequireGreaterOrEqual(t, len(region), 2, "Need at least 2 Regions in Traffic Ops to test pagination support, found: %d", len(region)) + switch paginationParam { + case "limit:": + assert.Exactly(t, region[:1], paginationResp, "expected GET Regions with limit = 1 to return first result") + case "offset": + assert.Exactly(t, region[1:2], paginationResp, "expected GET Regions with limit = 1, offset = 1 to return second result") + case "page": + assert.Exactly(t, region[1:2], paginationResp, "expected GET Regions with limit = 1, page = 2 to return second result") + } + } +} + +func CreateTestRegions(t *testing.T) { + for _, region := range testData.Regions { + resp, _, err := TOSession.CreateRegion(region, client.RequestOptions{}) + assert.RequireNoError(t, err, "Could not create Region '%s': %v - alerts: %+v", region.Name, err, resp.Alerts) + } +} + +func DeleteTestRegions(t *testing.T) { + regions, _, err := TOSession.GetRegions(client.RequestOptions{}) + assert.NoError(t, err, "Cannot get Regions: %v - alerts: %+v", err, regions.Alerts) + + for _, region := range regions.Response { + alerts, _, err := TOSession.DeleteRegion(region.Name, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error deleting Region '%s' (#%d): %v - alerts: %+v", region.Name, region.ID, err, alerts.Alerts) + // Retrieve the Region to see if it got deleted + opts := client.NewRequestOptions() + opts.QueryParameters.Set("id", strconv.Itoa(region.ID)) + getRegion, _, err := TOSession.GetRegions(opts) + assert.NoError(t, err, "Error getting Region '%s' after deletion: %v - alerts: %+v", region.Name, err, getRegion.Alerts) + assert.Equal(t, 0, len(getRegion.Response), "Expected Region '%s' to be deleted, but it was found in Traffic Ops", region.Name) + } +} diff --git a/traffic_ops/testing/api/v5/roles_test.go b/traffic_ops/testing/api/v5/roles_test.go new file mode 100644 index 0000000000..bd95f1503a --- /dev/null +++ b/traffic_ops/testing/api/v5/roles_test.go @@ -0,0 +1,357 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "sort" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestRoles(t *testing.T) { + WithObjs(t, []TCObj{Roles}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID request": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateRoleSort()), + }, + "OK when VALID NAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"new_admin"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + validateRoleFields(map[string]interface{}{"Name": "new_admin"})), + }, + "VALID when SORTORDER param is DESC": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"name"}, "sortOrder": {"desc"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateRoleDescSort()), + }, + }, + "POST": { + "BAD REQUEST when MISSING NAME": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "description": "missing name", + "permissions": []string{ + "all-read", + "all-write", + }, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when MISSING DESCRIPTION": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "noDescription", + "permissions": []string{ + "all-read", + "all-write", + }, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when ROLE NAME ALREADY EXISTS": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "new_admin", + "description": "description", + "permissions": []string{ + "all-read", + "all-write", + }, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "PUT": { + "OK when VALID request": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"update_role"}}}, + RequestBody: map[string]interface{}{ + "name": "new_name", + "description": "new updated description", + "permissions": []string{ + "all-read", + "all-write", + }, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateRoleUpdateCreateFields("new_name", map[string]interface{}{"Name": "new_name", "Description": "new updated description"})), + }, + "BAD REQUEST when MISSING NAME": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"another_role"}}}, + RequestBody: map[string]interface{}{ + "description": "missing name", + "permissions": []string{ + "all-read", + "all-write", + }, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when MISSING DESCRIPTION": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"another_role"}}}, + RequestBody: map[string]interface{}{ + "name": "noDescription", + "permissions": []string{ + "all-read", + "all-write", + }, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when ADMIN ROLE": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"admin"}}}, + RequestBody: map[string]interface{}{ + "name": "adminUpdated", + "description": "description", + "permissions": []string{ + "all-read", + "all-write", + }, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "NOT FOUND when ROLE DOESNT EXIST": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"doesntexist"}}}, + RequestBody: map[string]interface{}{ + "name": "doesntexist", + "description": "description", + "permissions": []string{ + "all-read", + "all-write", + }, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "BAD REQUEST when ROLE NAME ALREADY EXISTS": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"another_role"}}}, + RequestBody: map[string]interface{}{ + "name": "new_admin", + "description": "description", + "permissions": []string{ + "all-read", + "all-write", + }, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "PRECONDITION FAILED when updating with IMS & IUS Headers": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{ + QueryParameters: url.Values{"name": {"another_role"}}, + Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}, + }, + RequestBody: map[string]interface{}{ + "name": "another_role", + "description": "super-user 3", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + "PRECONDITION FAILED when updating with IFMATCH ETAG Header": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{ + QueryParameters: url.Values{"name": {"another_role"}}, + Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}, + }, + RequestBody: map[string]interface{}{ + "name": "another_role", + "description": "super-user 3", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + }, + "DELETE": { + "BAD REQUEST when SPECIAL ADMIN ROLE": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {tc.AdminRoleName}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "GET AFTER CHANGES": { + "OK when CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {currentTimeRFC}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + role := tc.RoleV4{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &role) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET", "GET AFTER CHANGES": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetRoles(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.CreateRole(role, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.UpdateRole(testCase.RequestOpts.QueryParameters["name"][0], role, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.DeleteRole(testCase.RequestOpts.QueryParameters["name"][0], testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateRoleFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Role response to not be nil.") + roleResp := resp.([]tc.RoleV4) + for field, expected := range expectedResp { + for _, role := range roleResp { + switch field { + case "Name": + assert.Equal(t, expected, role.Name, "Expected Name to be %v, but got %s", expected, role.Name) + case "Description": + assert.Equal(t, expected, role.Description, "Expected Description to be %v, but got %s", expected, role.Description) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validateRoleUpdateCreateFields(name string, expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", name) + role, _, err := TOSession.GetRoles(opts) + assert.RequireNoError(t, err, "Error getting Role: %v - alerts: %+v", err, role.Alerts) + assert.RequireEqual(t, 1, len(role.Response), "Expected one Role returned Got: %d", len(role.Response)) + validateRoleFields(expectedResp)(t, toclientlib.ReqInf{}, role.Response, tc.Alerts{}, nil) + } +} + +func validateRoleSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Role response to not be nil.") + var roleNames []string + roleResp := resp.([]tc.RoleV4) + for _, role := range roleResp { + roleNames = append(roleNames, role.Name) + } + assert.Equal(t, true, sort.StringsAreSorted(roleNames), "List is not sorted by their names: %v", roleNames) + } +} + +func validateRoleDescSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Role response to not be nil.") + roleDescResp := resp.([]tc.RoleV4) + var descSortedList []string + var ascSortedList []string + assert.RequireGreaterOrEqual(t, len(roleDescResp), 2, "Need at least 2 Roles in Traffic Ops to test desc sort, found: %d", len(roleDescResp)) + // Get Roles in the default ascending order for comparison. + roleAscResp, _, err := TOSession.GetRoles(client.RequestOptions{}) + assert.RequireNoError(t, err, "Unexpected error getting Roles with default sort order: %v - alerts: %+v", err, roleAscResp.Alerts) + // Verify the response match in length, i.e. equal amount of Roles. + assert.RequireEqual(t, len(roleAscResp.Response), len(roleDescResp), "Expected descending order response length: %d, to match ascending order response length %d", len(roleAscResp.Response), len(roleDescResp)) + // Insert Role names to the front of a new list, so they are now reversed to be in ascending order. + for _, role := range roleDescResp { + descSortedList = append([]string{role.Name}, descSortedList...) + } + // Insert Role names by appending to a new list, so they stay in ascending order. + for _, role := range roleAscResp.Response { + ascSortedList = append(ascSortedList, role.Name) + } + assert.Exactly(t, ascSortedList, descSortedList, "Role responses are not equal after reversal: %v - %v", ascSortedList, descSortedList) + } +} + +func CreateTestRoles(t *testing.T) { + for _, role := range testData.Roles { + _, _, err := TOSession.CreateRole(role, client.RequestOptions{}) + assert.NoError(t, err, "No error expected, but got %v", err) + } +} + +func DeleteTestRoles(t *testing.T) { + roles, _, err := TOSession.GetRoles(client.RequestOptions{}) + assert.NoError(t, err, "Cannot get Roles: %v - alerts: %+v", err, roles.Alerts) + for _, role := range roles.Response { + // Don't delete active roles created by test setup + if role.Name == "admin" || role.Name == "disallowed" || role.Name == "operations" || role.Name == "portal" || role.Name == "read-only" || role.Name == "steering" || role.Name == "federation" { + continue + } + _, _, err := TOSession.DeleteRole(role.Name, client.NewRequestOptions()) + assert.NoError(t, err, "Expected no error while deleting role %s, but got %v", role.Name, err) + // Retrieve the Role to see if it got deleted + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", role.Name) + getRole, _, err := TOSession.GetRoles(opts) + assert.NoError(t, err, "Error getting Role '%s' after deletion: %v - alerts: %+v", role.Name, err, getRole.Alerts) + assert.Equal(t, 0, len(getRole.Response), "Expected Role '%s' to be deleted, but it was found in Traffic Ops", role.Name) + } +} diff --git a/traffic_ops/testing/api/v5/server_capabilities_test.go b/traffic_ops/testing/api/v5/server_capabilities_test.go new file mode 100644 index 0000000000..46829aa27f --- /dev/null +++ b/traffic_ops/testing/api/v5/server_capabilities_test.go @@ -0,0 +1,214 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "sort" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestServerCapabilities(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, ServerCapabilities, ServerServerCapabilities}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "OK when VALID request": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateServerCapabilitiesSort()), + }, + "OK when VALID NAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"ram"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1)), + }, + "EMPTY RESPONSE when INVALID NAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"abcd"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + }, + "POST": { + "BAD REQUEST when ALREADY EXISTS": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{"name": "foo"}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID NAME": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{"name": "b@dname"}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "PUT": { + "OK when VALID request": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"blah"}}}, + RequestBody: map[string]interface{}{"name": "newname"}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateServerCapabilitiesUpdateFields(map[string]interface{}{"Name": "newname"}), + validateSSCFieldsOnServerCapabilityUpdate("newname", map[string]interface{}{"ServerCapability": "newname"})), + }, + "BAD REQUEST when NAME DOESNT EXIST": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"invalid"}}}, + RequestBody: map[string]interface{}{"name": "newname"}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "PRECONDITION FAILED when updating with IMS & IUS Headers": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{ + QueryParameters: url.Values{"name": {"disk"}}, + Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}, + }, + RequestBody: map[string]interface{}{"name": "newname"}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + "PRECONDITION FAILED when updating with IFMATCH ETAG Header": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{ + QueryParameters: url.Values{"name": {"disk"}}, + Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}, + }, + RequestBody: map[string]interface{}{"name": "newname"}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + }, + "DELETE": { + "NOT FOUND when NAME DOESNT EXIST": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"invalid"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "BAD REQUEST when EMPTY NAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {""}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + serverCapability := tc.ServerCapability{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &serverCapability) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetServerCapabilities(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.CreateServerCapability(serverCapability, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.UpdateServerCapability(testCase.RequestOpts.QueryParameters["name"][0], serverCapability, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.DeleteServerCapability(testCase.RequestOpts.QueryParameters["name"][0], testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateServerCapabilitiesUpdateFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Server Capabilities response to not be nil.") + serverCapabilitiesResp := resp.(tc.ServerCapability) + for field, expected := range expectedResp { + switch field { + case "Name": + assert.Equal(t, expected, serverCapabilitiesResp.Name, "Expected Name to be %v, but got %s", expected, serverCapabilitiesResp.Name) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } +} + +func validateServerCapabilitiesSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Server Capabilities response to not be nil.") + var serverCapabilityNames []string + serverCapabilitiesResp := resp.([]tc.ServerCapability) + for _, serverCapability := range serverCapabilitiesResp { + serverCapabilityNames = append(serverCapabilityNames, serverCapability.Name) + } + assert.Equal(t, true, sort.StringsAreSorted(serverCapabilityNames), "List is not sorted by their names: %v", serverCapabilityNames) + } +} + +func CreateTestServerCapabilities(t *testing.T) { + for _, sc := range testData.ServerCapabilities { + resp, _, err := TOSession.CreateServerCapability(sc, client.RequestOptions{}) + assert.RequireNoError(t, err, "Unexpected error creating Server Capability '%s': %v - alerts: %+v", sc.Name, err, resp.Alerts) + } +} + +func DeleteTestServerCapabilities(t *testing.T) { + serverCapabilities, _, err := TOSession.GetServerCapabilities(client.RequestOptions{}) + assert.NoError(t, err, "Cannot get Server Capabilities: %v - alerts: %+v", err, serverCapabilities.Alerts) + + for _, serverCapability := range serverCapabilities.Response { + alerts, _, err := TOSession.DeleteServerCapability(serverCapability.Name, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error deleting Server Capability '%s': %v - alerts: %+v", serverCapability.Name, err, alerts.Alerts) + // Retrieve the Server Capability to see if it got deleted + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", serverCapability.Name) + getServerCapability, _, err := TOSession.GetServerCapabilities(opts) + assert.NoError(t, err, "Error getting Server Capability '%s' after deletion: %v - alerts: %+v", serverCapability.Name, err, getServerCapability.Alerts) + assert.Equal(t, 0, len(getServerCapability.Response), "Expected Server Capability '%s' to be deleted, but it was found in Traffic Ops", serverCapability.Name) + } +} diff --git a/traffic_ops/testing/api/v5/server_server_capabilities_test.go b/traffic_ops/testing/api/v5/server_server_capabilities_test.go new file mode 100644 index 0000000000..f2897f476e --- /dev/null +++ b/traffic_ops/testing/api/v5/server_server_capabilities_test.go @@ -0,0 +1,341 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "sort" + "strconv" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestServerServerCapabilities(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServiceCategories, DeliveryServices, DeliveryServiceServerAssignments, ServerCapabilities, ServerServerCapabilities, DeliveryServicesRequiredCapabilities}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + var multipleSCs []string + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID request": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateServerServerCapabilitiesSort()), + }, + "OK when VALID SERVERID parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"serverId": {strconv.Itoa(GetServerID(t, "dtrc-edge-01")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateServerServerCapabilitiesFields(map[string]interface{}{"ServerID": GetServerID(t, "dtrc-edge-01")()})), + }, + "OK when VALID SERVERHOSTNAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"serverHostName": {"atlanta-edge-16"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateServerServerCapabilitiesFields(map[string]interface{}{"Server": "atlanta-edge-16"})), + }, + "OK when VALID SERVERCAPABILITY parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"serverCapability": {"asdf"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateServerServerCapabilitiesFields(map[string]interface{}{"ServerCapability": "asdf"})), + }, + "FIRST RESULT when LIMIT=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"serverId"}, "limit": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateServerServerCapabilitiesPagination("limit")), + }, + "SECOND RESULT when LIMIT=1 OFFSET=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"serverId"}, "limit": {"1"}, "offset": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateServerServerCapabilitiesPagination("offset")), + }, + "SECOND RESULT when LIMIT=1 PAGE=2": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"serverId"}, "limit": {"1"}, "page": {"2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateServerServerCapabilitiesPagination("page")), + }, + "BAD REQUEST when INVALID LIMIT parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"-2"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID OFFSET parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "offset": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID PAGE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "page": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "POST": { + "BAD REQUEST when ALREADY EXISTS": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "serverId": GetServerID(t, "dtrc-mid-01")(), + "serverCapability": "disk", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when MISSING SERVER ID": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "serverCapability": "disk", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when MISSING SERVER CAPABILITY": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "serverId": GetServerID(t, "dtrc-mid-01")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "NOT FOUND when SERVER CAPABILITY DOESNT EXIST": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "serverId": GetServerID(t, "dtrc-mid-01")(), + "serverCapability": "bogus", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "NOT FOUND when SERVER DOESNT EXIST": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "serverId": 99999999, + "serverCapability": "bogus", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "BAD REQUEST when SERVER TYPE NOT EDGE or MID": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "serverId": GetServerID(t, "trafficvault")(), + "serverCapability": "bogus", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "PUT": { + "OK When Assigned Multiple Server Capabilities": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "serverId": GetServerID(t, "dtrc-mid-04")(), + "serverCapabilities": append(multipleSCs, "disk", "blah"), + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + "DELETE": { + "OK when NOT the LAST SERVER of CACHE GROUP of TOPOLOGY DS which has REQUIRED CAPABILITIES": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"serverId": {strconv.Itoa(GetServerID(t, "dtrc-edge-01")())}, "serverCapability": {"ram"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "BAD REQUEST when LAST SERVER of CACHE GROUP of TOPOLOGY DS which has REQUIRED CAPABILITIES": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"serverId": {strconv.Itoa(GetServerID(t, "edge-in-cdn1-only")())}, "serverCapability": {"ram"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when SERVER ASSIGNED TO DS with REQUIRED CAPABILITIES": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"serverId": {strconv.Itoa(GetServerID(t, "atlanta-org-2")())}, "serverCapability": {"bar"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "NOT FOUND when SERVER SERVER CAPABILITY DOESNT EXIST": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"serverId": {strconv.Itoa(GetServerID(t, "atlanta-org-1")())}, "serverCapability": {"doesntexist"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "BAD REQUEST when MISSING SERVER CAPABILITY": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"serverId": {strconv.Itoa(GetServerID(t, "atlanta-org-1")())}, "serverCapability": {""}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + ssc := tc.ServerServerCapability{} + msc := tc.MultipleServerCapabilities{} + var serverId int + var serverCapability string + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + if method == "PUT" { + err = json.Unmarshal(dat, &msc) + } else { + err = json.Unmarshal(dat, &ssc) + } + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetServerServerCapabilities(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.CreateServerServerCapability(ssc, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.AssignMultipleServerCapability(msc, testCase.RequestOpts, serverId) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + if val, ok := testCase.RequestOpts.QueryParameters["serverId"]; ok { + serverId, _ = strconv.Atoi(val[0]) + } + if val, ok := testCase.RequestOpts.QueryParameters["serverCapability"]; ok { + serverCapability = val[0] + } + alerts, reqInf, err := testCase.ClientSession.DeleteServerServerCapability(serverId, serverCapability, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateServerServerCapabilitiesFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Server Server Capabilities response to not be nil.") + serverServerCapabilityResponse := resp.([]tc.ServerServerCapability) + for field, expected := range expectedResp { + for _, serverServerCapability := range serverServerCapabilityResponse { + switch field { + case "Server": + assert.RequireNotNil(t, serverServerCapability.Server, "Expected Server to not be nil.") + assert.Equal(t, expected, *serverServerCapability.Server, "Expected Server to be %v, but got %s", expected, *serverServerCapability.Server) + case "ServerCapability": + assert.RequireNotNil(t, serverServerCapability.ServerCapability, "Expected Server Capability to not be nil.") + assert.Equal(t, expected, *serverServerCapability.ServerCapability, "Expected ServerCapability to be %v, but got %s", expected, *serverServerCapability.ServerCapability) + case "ServerID": + assert.RequireNotNil(t, serverServerCapability.ServerID, "Expected Server ID to not be nil.") + assert.Equal(t, expected, *serverServerCapability.ServerID, "Expected ServerID to be %v, but got %d", expected, *serverServerCapability.ServerID) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validateSSCFieldsOnServerCapabilityUpdate(name string, expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("serverCapability", name) + ssc, _, err := TOSession.GetServerServerCapabilities(opts) + assert.RequireNoError(t, err, "Error getting Server Server Capabilities: %v - alerts: %+v", err, ssc.Alerts) + assert.RequireEqual(t, 1, len(ssc.Response), "Expected one Server Server Capability returned Got: %d", len(ssc.Response)) + validateServerServerCapabilitiesFields(expectedResp)(t, toclientlib.ReqInf{}, ssc.Response, tc.Alerts{}, nil) + } +} + +func validateServerServerCapabilitiesSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Server Server Capabilities response to not be nil.") + var serverNames []string + serverServerCapabilityResponse := resp.([]tc.ServerServerCapability) + for _, serverServerCapability := range serverServerCapabilityResponse { + assert.RequireNotNil(t, serverServerCapability.Server, "Expected Server to not be nil.") + serverNames = append(serverNames, *serverServerCapability.Server) + } + assert.Equal(t, true, sort.StringsAreSorted(serverNames), "List is not sorted by server names: %v", serverNames) + } +} + +func validateServerServerCapabilitiesPagination(paginationParam string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + paginationResp := resp.([]tc.ServerServerCapability) + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("orderby", "serverId") + respBase, _, err := TOSession.GetServerServerCapabilities(opts) + assert.RequireNoError(t, err, "Cannot get Server Server Capabilities: %v - alerts: %+v", err, respBase.Alerts) + + ssc := respBase.Response + assert.RequireGreaterOrEqual(t, len(ssc), 3, "Need at least 3 Server Server Capabilities in Traffic Ops to test pagination support, found: %d", len(ssc)) + switch paginationParam { + case "limit:": + assert.Exactly(t, ssc[:1], paginationResp, "expected GET Server Server Capabilities with limit = 1 to return first result") + case "offset": + assert.Exactly(t, ssc[1:2], paginationResp, "expected GET Server Server Capabilities with limit = 1, offset = 1 to return second result") + case "page": + assert.Exactly(t, ssc[1:2], paginationResp, "expected GET Server Server Capabilities with limit = 1, page = 2 to return second result") + } + } +} + +func CreateTestServerServerCapabilities(t *testing.T) { + for _, ssc := range testData.ServerServerCapabilities { + assert.RequireNotNil(t, ssc.Server, "Expected Server to not be nil.") + assert.RequireNotNil(t, ssc.ServerCapability, "Expected Server Capability to not be nil.") + serverID := GetServerID(t, *ssc.Server)() + ssc.ServerID = &serverID + resp, _, err := TOSession.CreateServerServerCapability(ssc, client.RequestOptions{}) + assert.RequireNoError(t, err, "Could not associate Capability '%s' with server '%s': %v - alerts: %+v", *ssc.ServerCapability, *ssc.Server, err, resp.Alerts) + } +} + +func DeleteTestServerServerCapabilities(t *testing.T) { + sscs, _, err := TOSession.GetServerServerCapabilities(client.RequestOptions{}) + assert.RequireNoError(t, err, "Cannot get server server capabilities: %v - alerts: %+v", err, sscs.Alerts) + for _, ssc := range sscs.Response { + assert.RequireNotNil(t, ssc.Server, "Expected Server to not be nil.") + assert.RequireNotNil(t, ssc.ServerCapability, "Expected Server Capability to not be nil.") + alerts, _, err := TOSession.DeleteServerServerCapability(*ssc.ServerID, *ssc.ServerCapability, client.RequestOptions{}) + assert.NoError(t, err, "Could not remove Capability '%s' from server '%s' (#%d): %v - alerts: %+v", *ssc.ServerCapability, *ssc.Server, *ssc.ServerID, err, alerts.Alerts) + } +} diff --git a/traffic_ops/testing/api/v5/servercheckextension_test.go b/traffic_ops/testing/api/v5/servercheckextension_test.go new file mode 100644 index 0000000000..4f4a316324 --- /dev/null +++ b/traffic_ops/testing/api/v5/servercheckextension_test.go @@ -0,0 +1,133 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "strconv" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +var ( + toReqTimeout = time.Second * time.Duration(Config.Default.Session.TimeoutInSecs) +) + +func TestServerCheckExtensions(t *testing.T) { + WithObjs(t, []TCObj{ServerCheckExtensions}, func() { + + extensionUser := utils.CreateV5Session(t, Config.TrafficOps.URL, Config.TrafficOps.Users.Extension, Config.TrafficOps.UserPassword, Config.Default.Session.TimeoutInSecs) + + methodTests := utils.V5TestCase{ + "POST": { + "FORBIDDEN when NOT EXTENSION USER": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "MEM_CHECKER", + "version": "3.0.3", + "info_url": "-", + "script_file": "mem.py", + "isactive": 1, + "servercheck_short_name": "MC", + "type": "CHECK_EXTENSION_MEM", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + "BAD REQUEST when NO OPEN SLOTS": { + ClientSession: extensionUser, + RequestBody: map[string]interface{}{ + "name": "MEM_CHECKER", + "version": "3.0.3", + "info_url": "-", + "script_file": "mem.py", + "isactive": 1, + "servercheck_short_name": "MC", + "type": "CHECK_EXTENSION_NUM", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID TYPE": { + ClientSession: extensionUser, + RequestBody: map[string]interface{}{ + "name": "MEM_CHECKER", + "version": "3.0.3", + "info_url": "-", + "script_file": "mem.py", + "isactive": 1, + "servercheck_short_name": "MC", + "type": "INVALID_TYPE", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + } + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + serverCheckExtension := tc.ServerCheckExtensionNullable{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &serverCheckExtension) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.CreateServerCheckExtension(serverCheckExtension, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func CreateTestServerCheckExtensions(t *testing.T) { + extensionUser := utils.CreateV5Session(t, Config.TrafficOps.URL, Config.TrafficOps.Users.Extension, Config.TrafficOps.UserPassword, Config.Default.Session.TimeoutInSecs) + for _, ext := range testData.ServerCheckExtensions { + resp, _, err := extensionUser.CreateServerCheckExtension(ext, client.RequestOptions{}) + assert.NoError(t, err, "Could not create Servercheck Extension: %v - alerts: %+v", err, resp.Alerts) + } +} + +func DeleteTestServerCheckExtensions(t *testing.T) { + extensionUser := utils.CreateV5Session(t, Config.TrafficOps.URL, Config.TrafficOps.Users.Extension, Config.TrafficOps.UserPassword, Config.Default.Session.TimeoutInSecs) + extensions, _, err := TOSession.GetServerCheckExtensions(client.RequestOptions{}) + assert.RequireNoError(t, err, "Could not get Servercheck Extensions: %v - alerts: %+v", err, extensions.Alerts) + + for _, extension := range extensions.Response { + alerts, _, err := extensionUser.DeleteServerCheckExtension(*extension.ID, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error deleting Servercheck Extension '%s' (#%d): %v - alerts: %+v", *extension.Name, *extension.ID, err, alerts.Alerts) + // Retrieve the Server Extension to see if it got deleted + opts := client.NewRequestOptions() + opts.QueryParameters.Set("id", strconv.Itoa(*extension.ID)) + getExtension, _, err := TOSession.GetServerCheckExtensions(opts) + assert.NoError(t, err, "Error getting Servercheck Extension '%s' after deletion: %v - alerts: %+v", *extension.Name, err, getExtension.Alerts) + assert.Equal(t, 0, len(getExtension.Response), "Expected Servercheck Extension '%s' to be deleted, but it was found in Traffic Ops", *extension.Name) + } +} diff --git a/traffic_ops/testing/api/v5/serverchecks_test.go b/traffic_ops/testing/api/v5/serverchecks_test.go new file mode 100644 index 0000000000..808bfe440a --- /dev/null +++ b/traffic_ops/testing/api/v5/serverchecks_test.go @@ -0,0 +1,187 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "strconv" + "testing" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestServerChecks(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, ServerCheckExtensions, ServerChecks}, func() { + + extensionSession := utils.CreateV5Session(t, Config.TrafficOps.URL, Config.TrafficOps.Users.Extension, Config.TrafficOps.UserPassword, Config.Default.Session.TimeoutInSecs) + + methodTests := utils.V5TestCase{ + "GET": { + "OK when VALID request": { + ClientSession: extensionSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1)), + }, + "OK when VALID HOSTNAME parameter": { + ClientSession: extensionSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"hostName": {"atlanta-edge-01"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + validateServerCheckFields(map[string]interface{}{"HostName": "atlanta-edge-01", "Checks": map[string]int{"ORT": 13}})), + }, + "OK when VALID ID parameter": { + ClientSession: extensionSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"id": {strconv.Itoa(GetServerID(t, "atlanta-edge-01")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1)), + }, + }, + "POST": { + "OK when UPDATING EXISTING SERVER CHECK": { + ClientSession: extensionSession, + RequestBody: map[string]interface{}{ + "servercheck_short_name": "ILO", + "host_name": "atlanta-edge-01", + "value": 0, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateServerCheckCreateFields("atlanta-edge-01", map[string]interface{}{"HostName": "atlanta-edge-01", "Checks": map[string]int{"ORT": 13, "ILO": 0}})), + }, + "BAD REQUEST when NO SERVER ID": { + ClientSession: extensionSession, + RequestBody: map[string]interface{}{ + "id": nil, + "servercheck_short_name": "ILO", + "value": 1, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID SERVER ID": { + ClientSession: extensionSession, + RequestBody: map[string]interface{}{ + "host_name": "atlanta-edge-01", + "id": -1, + "servercheck_short_name": "ILO", + "value": 1, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID SERVERCHECK SHORT NAME": { + ClientSession: extensionSession, + RequestBody: map[string]interface{}{ + "host_name": "atlanta-edge-01", + "servercheck_short_name": "BOGUS", + "value": 1, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "FORBIDDEN when NON EXTENSION USER": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "host_name": "atlanta-edge-01", + "id": GetServerID(t, "atlanta-edge-01")(), + "servercheck_short_name": "TEST", + "value": 1, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + serverCheck := tc.ServercheckRequestNullable{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &serverCheck) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetServersChecks(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.InsertServerCheckStatus(serverCheck, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, tc.Alerts{Alerts: resp.Alerts}, err) + } + }) + } + } + }) + } + }) +} + +func validateServerCheckFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Server Check response to not be nil.") + serverCheckResp := resp.([]tc.GenericServerCheck) + for field, expected := range expectedResp { + for _, serverCheck := range serverCheckResp { + switch field { + case "HostName": + assert.Equal(t, expected, serverCheck.HostName, "Expected HostName to be %v, but got %s", expected, serverCheck.HostName) + case "Checks": + for name, value := range expected.(map[string]int) { + assert.RequireNotNil(t, serverCheck.Checks[name], "Expected Checks[%s] value to not be nil.", name) + assert.Equal(t, value, *serverCheck.Checks[name], "Expected Checks ILO Value to be %d, but got %s", value, *serverCheck.Checks[name]) + } + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validateServerCheckCreateFields(hostName string, expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("hostName", hostName) + serverChecks, _, err := TOSession.GetServersChecks(opts) + assert.RequireNoError(t, err, "Error getting Server Checks: %v - alerts: %+v", err, serverChecks.Alerts) + assert.RequireEqual(t, 1, len(serverChecks.Response), "Expected one Server Check returned Got: %d", len(serverChecks.Response)) + validateServerCheckFields(expectedResp)(t, toclientlib.ReqInf{}, serverChecks.Response, tc.Alerts{}, nil) + } +} + +func CreateTestServerChecks(t *testing.T) { + extensionSession := utils.CreateV5Session(t, Config.TrafficOps.URL, Config.TrafficOps.Users.Extension, Config.TrafficOps.UserPassword, Config.Default.Session.TimeoutInSecs) + + for _, servercheck := range testData.Serverchecks { + resp, _, err := extensionSession.InsertServerCheckStatus(servercheck, client.RequestOptions{}) + assert.RequireNoError(t, err, "Could not insert Servercheck: %v - alerts: %+v", err, resp.Alerts) + } +} + +// Need to define no-op function as TCObj interface expects a delete function +// There is no delete path for serverchecks +func DeleteTestServerChecks(*testing.T) { + return +} diff --git a/traffic_ops/testing/api/v5/servers_id_deliveryservices_test.go b/traffic_ops/testing/api/v5/servers_id_deliveryservices_test.go new file mode 100644 index 0000000000..3b3826d780 --- /dev/null +++ b/traffic_ops/testing/api/v5/servers_id_deliveryservices_test.go @@ -0,0 +1,180 @@ +package v5 + +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/http" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestServersIDDeliveryServices(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Tenants, Topologies, ServiceCategories, DeliveryServices, DeliveryServiceServerAssignments}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + EndpointId: GetServerID(t, "atlanta-edge-14"), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID request": { + EndpointId: GetServerID(t, "atlanta-edge-14"), + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + "POST": { + "OK when VALID request": { + EndpointId: GetServerID(t, "atlanta-edge-01"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "dsIds": []int{GetDeliveryServiceId(t, "ds1")()}, + "replace": true, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateServersDeliveryServicesPost(GetServerID(t, "atlanta-edge-01")(), GetDeliveryServiceId(t, "ds1")())), + }, + "OK when ASSIGNING EDGE to TOPOLOGY BASED DELIVERY SERVICE": { + EndpointId: GetServerID(t, "atlanta-edge-03"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "dsIds": []int{GetDeliveryServiceId(t, "top-ds-in-cdn1")()}, + "replace": true, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateServersDeliveryServicesPost(GetServerID(t, "atlanta-edge-03")(), GetDeliveryServiceId(t, "top-ds-in-cdn1")())), + }, + "OK when ASSIGNING ORIGIN to TOPOLOGY BASED DELIVERY SERVICE": { + EndpointId: GetServerID(t, "denver-mso-org-01"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "dsIds": []int{GetDeliveryServiceId(t, "ds-top")()}, + "replace": true, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateServersDeliveryServicesPost(GetServerID(t, "denver-mso-org-01")(), GetDeliveryServiceId(t, "ds-top")())), + }, + "CONFLICT when SERVER NOT IN SAME CDN as DELIVERY SERVICE": { + EndpointId: GetServerID(t, "cdn2-test-edge"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "dsIds": []int{GetDeliveryServiceId(t, "ds1")()}, + "replace": true, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusConflict)), + }, + "BAD REQUEST when ORIGIN'S CACHEGROUP IS NOT A PART OF TOPOLOGY BASED DELIVERY SERVICE": { + EndpointId: GetServerID(t, "denver-mso-org-01"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "dsIds": []int{GetDeliveryServiceId(t, "ds-top-req-cap")()}, + "replace": true, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "CONFLICT when REMOVING ONLY EDGE SERVER ASSIGNMENT": { + EndpointId: GetServerID(t, "test-ds-server-assignments"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "dsIds": []int{}, + "replace": true, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusConflict)), + }, + "CONFLICT when REMOVING ONLY ORIGIN SERVER ASSIGNMENT": { + EndpointId: GetServerID(t, "test-mso-org-01"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "dsIds": []int{}, + "replace": true, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusConflict)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + + var dsIds []int + var replace bool + + if testCase.RequestBody != nil { + if val, ok := testCase.RequestBody["dsIds"]; ok { + dsIds = val.([]int) + } + if val, ok := testCase.RequestBody["replace"]; ok { + replace = val.(bool) + } + } + + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetServerIDDeliveryServices(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.AssignDeliveryServiceIDsToServerID(testCase.EndpointId(), dsIds, replace, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateServersDeliveryServices(expectedDSID int) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Server Delivery Service response to not be nil.") + var found bool + deliveryServices := resp.([]tc.DeliveryServiceV40) + for _, ds := range deliveryServices { + if ds.ID != nil && *ds.ID == expectedDSID { + found = true + break + } + } + assert.Equal(t, true, found, "Expected to find Delivery Service ID: %d in response.") + } +} + +func validateServersDeliveryServicesPost(serverID int, expectedDSID int) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + serverDeliveryServices, _, err := TOSession.GetServerIDDeliveryServices(serverID, client.RequestOptions{}) + assert.RequireNoError(t, err, "Error getting Server Delivery Services: %v - alerts: %+v", err, serverDeliveryServices.Alerts) + assert.RequireEqual(t, 1, len(serverDeliveryServices.Response), "Expected one Delivery Service returned Got: %d", len(serverDeliveryServices.Response)) + validateServersDeliveryServices(expectedDSID)(t, toclientlib.ReqInf{}, serverDeliveryServices.Response, tc.Alerts{}, nil) + } +} diff --git a/traffic_ops/testing/api/v5/servers_test.go b/traffic_ops/testing/api/v5/servers_test.go new file mode 100644 index 0000000000..082e3142af --- /dev/null +++ b/traffic_ops/testing/api/v5/servers_test.go @@ -0,0 +1,651 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "strconv" + "strings" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestServers(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Users, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServiceCategories, DeliveryServices, DeliveryServiceServerAssignments}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID HOSTNAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"hostName": {"atlanta-edge-01"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + validateServerFields(map[string]interface{}{"HostName": "atlanta-edge-01"})), + }, + "OK when VALID CACHEGROUP parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"cachegroup": {strconv.Itoa(GetCacheGroupId(t, "cachegroup1")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateServerFields(map[string]interface{}{"CachegroupID": GetCacheGroupId(t, "cachegroup1")()})), + }, + "OK when VALID CACHEGROUPNAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"cachegroupName": {"topology-mid-cg-01"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateServerFields(map[string]interface{}{"Cachegroup": "topology-mid-cg-01"})), + }, + "OK when VALID CDN parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"cdn": {strconv.Itoa(GetCDNID(t, "cdn2")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateServerFields(map[string]interface{}{"CDNID": GetCDNID(t, "cdn2")()})), + }, + "OK when VALID DSID parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"dsId": {strconv.Itoa(GetDeliveryServiceId(t, "test-ds-server-assignments")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateExpectedServers([]string{"test-ds-server-assignments", "test-mso-org-01"})), + }, + "OK when VALID PARENTCACHEGROUP parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"parentCacheGroup": {strconv.Itoa(GetCacheGroupId(t, "parentCachegroup")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1)), + }, + "OK when VALID PROFILENAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"profileName": {"EDGE1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1)), + }, + "OK when VALID STATUS parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"status": {"REPORTED"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateServerFields(map[string]interface{}{"Status": "REPORTED"})), + }, + "OK when VALID TOPOLOGY parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"topology": {"mso-topology"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateExpectedServers([]string{"denver-mso-org-01", "denver-mso-org-02", "edge1-cdn1-cg3", "edge2-cdn1-cg3", + "atlanta-mid-01", "atlanta-mid-16", "atlanta-mid-17", "edgeInCachegroup3", "midInParentCachegroup", + "midInSecondaryCachegroup", "midInSecondaryCachegroupInCDN1", "test-mso-org-01"})), + }, + "OK when VALID TYPE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"type": {"EDGE"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateServerFields(map[string]interface{}{"Type": "EDGE"})), + }, + "VALID SERVER LIST when using TOPOLOGY BASED DSID parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"dsId": {strconv.Itoa(GetDeliveryServiceId(t, "ds-top")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateExpectedServers([]string{"denver-mso-org-01"})), + }, + "VALID SERVER TYPE when DS TOPOLOGY CONTAINS NO MIDS": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"dsId": {strconv.Itoa(GetDeliveryServiceId(t, "ds-based-top-with-no-mids")())}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), validateServerTypeIsNotMid()), + }, + "EMPTY RESPONSE when INVALID DSID parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"dsId": {"999999"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "FIRST RESULT when LIMIT=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateServerPagination("limit")), + }, + "SECOND RESULT when LIMIT=1 OFFSET=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "offset": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateServerPagination("offset")), + }, + "SECOND RESULT when LIMIT=1 PAGE=2": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "page": {"2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateServerPagination("page")), + }, + "BAD REQUEST when INVALID LIMIT parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"-2"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID OFFSET parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "offset": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID PAGE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "page": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "POST": { + "BAD REQUEST when BLANK PROFILENAMES": { + ClientSession: TOSession, + RequestBody: generateServer(t, map[string]interface{}{"profileNames": []string{""}}), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "PUT": { + "OK when VALID request": { + EndpointId: GetServerID(t, "atlanta-edge-03"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "id": GetServerID(t, "atlanta-edge-03")(), + "cdnId": GetCDNID(t, "cdn1")(), + "cachegroupId": GetCacheGroupId(t, "cachegroup1")(), + "domainName": "updateddomainname", + "hostName": "atl-edge-01", + "httpsPort": 8080, + "interfaces": []map[string]interface{}{{ + "ipAddresses": []map[string]interface{}{ + { + "address": "2345:1234:12:2::4/64", + "gateway": "2345:1234:12:2::4", + "serviceAddress": false, + }, + { + "address": "127.0.0.13/30", + "gateway": "127.0.0.1", + "serviceAddress": true, + }, + }, + "monitor": true, + "mtu": uint64(1280), + "name": "bond1", + "routerHostName": "router5", + "routerPort": "9004", + }}, + "physLocationId": GetPhysicalLocationID(t, "Denver")(), + "profileNames": []string{"EDGE1"}, + "rack": "RR 119.03", + "statusId": GetStatusID(t, "REPORTED")(), + "tcpPort": 8080, + "typeId": GetTypeId(t, "EDGE"), + "updPending": true, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateServerFieldsForUpdate("atl-edge-01", map[string]interface{}{ + "CDNName": "cdn1", "Cachegroup": "cachegroup1", "DomainName": "updateddomainname", "HostName": "atl-edge-01", + "HTTPSPort": 8080, "InterfaceName": "bond1", "MTU": uint64(1280), "PhysLocation": "Denver", "Rack": "RR 119.03", + "TCPPort": 8080, "TypeID": GetTypeId(t, "EDGE"), + })), + }, + "BAD REQUEST when CHANGING XMPPID": { + EndpointId: GetServerID(t, "atlanta-edge-16"), + ClientSession: TOSession, + RequestBody: generateServer(t, map[string]interface{}{ + "id": GetServerID(t, "atlanta-edge-16")(), + "xmppId": "CHANGINGTHIS", + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "CONFLICT when UPDATING SERVER TYPE when ASSIGNED to DS": { + EndpointId: GetServerID(t, "test-ds-server-assignments"), + ClientSession: TOSession, + RequestBody: generateServer(t, map[string]interface{}{ + "id": GetServerID(t, "test-ds-server-assignments")(), + "cachegroupId": GetCacheGroupId(t, "cachegroup1")(), + "typeId": GetTypeId(t, "MID"), + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusConflict)), + }, + "CONFLICT when UPDATING SERVER STATUS when its the ONLY EDGE SERVER ASSIGNED": { + EndpointId: GetServerID(t, "test-ds-server-assignments"), + ClientSession: TOSession, + RequestBody: generateServer(t, map[string]interface{}{ + "id": GetServerID(t, "test-ds-server-assignments")(), + "statusId": GetStatusID(t, "ADMIN_DOWN")(), + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusConflict)), + }, + "CONFLICT when UPDATING SERVER STATUS when its the ONLY ORG SERVER ASSIGNED": { + EndpointId: GetServerID(t, "test-mso-org-01"), + ClientSession: TOSession, + RequestBody: generateServer(t, map[string]interface{}{ + "id": GetServerID(t, "test-mso-org-01")(), + "statusId": GetStatusID(t, "ADMIN_DOWN")(), + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusConflict)), + }, + "BAD REQUEST when UPDATING CDN when LAST SERVER IN CACHEGROUP IN TOPOLOGY": { + EndpointId: GetServerID(t, "midInTopologyMidCg01"), + ClientSession: TOSession, + RequestBody: generateServer(t, map[string]interface{}{ + "id": GetServerID(t, "midInTopologyMidCg01")(), + "cdnId": GetCDNID(t, "cdn1")(), + "profileNames": []string{"MID1"}, + "cachegroupId": GetCacheGroupId(t, "topology-mid-cg-01")(), + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when UPDATING CACHEGROUP when LAST SERVER IN CACHEGROUP IN TOPOLOGY": { + EndpointId: GetServerID(t, "midInTopologyMidCg01"), + ClientSession: TOSession, + RequestBody: generateServer(t, map[string]interface{}{ + "id": GetServerID(t, "midInTopologyMidCg01")(), + "hostName": "midInTopologyMidCg01", + "cdnId": GetCDNID(t, "cdn2")(), + "profileNames": []string{"CDN2_MID"}, + "cachegroupId": GetCacheGroupId(t, "topology-mid-cg-02")(), + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when IPADDRESS EXISTS with SAME PROFILE": { + EndpointId: GetServerID(t, "atlanta-edge-16"), + ClientSession: TOSession, + RequestBody: generateServer(t, map[string]interface{}{ + "profileNames": []string{"EDGE1"}, + "interfaces": []map[string]interface{}{{ + "ipAddresses": []map[string]interface{}{{ + "address": "127.0.0.11/22", + "gateway": "127.0.0.11", + "serviceAddress": true, + }}, + "name": "eth1", + }}, + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when BLANK HOSTNAME": { + EndpointId: GetServerID(t, "atlanta-edge-16"), + ClientSession: TOSession, + RequestBody: generateServer(t, map[string]interface{}{"hostName": ""}), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when BLANK DOMAINNAME": { + EndpointId: GetServerID(t, "atlanta-edge-16"), + ClientSession: TOSession, + RequestBody: generateServer(t, map[string]interface{}{"domainName": ""}), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "PRECONDITION FAILED when updating with IMS & IUS Headers": { + EndpointId: GetServerID(t, "atlanta-edge-01"), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}}, + RequestBody: generateServer(t, map[string]interface{}{ + "id": GetServerID(t, "atlanta-edge-01")(), + }), + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + "PRECONDITION FAILED when updating with IFMATCH ETAG Header": { + EndpointId: GetServerID(t, "atlanta-edge-01"), + ClientSession: TOSession, + RequestBody: generateServer(t, map[string]interface{}{ + "id": GetServerID(t, "atlanta-edge-01")(), + }), + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + }, + "DELETE": { + "BAD REQUEST when LAST SERVER in CACHE GROUP": { + EndpointId: GetServerID(t, "midInTopologyMidCg01"), + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "CONFLICT when DELETING SERVER when its the ONLY EDGE SERVER ASSIGNED": { + EndpointId: GetServerID(t, "test-ds-server-assignments"), + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusConflict)), + }, + }, + "GET AFTER CHANGES": { + "OK when CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {currentTimeRFC}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + server := tc.ServerV4{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &server) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET", "GET AFTER CHANGES": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetServers(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.CreateServer(server, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.UpdateServer(testCase.EndpointId(), server, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.DeleteServer(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + t.Run("DS SERVER ASSIGNMENT REMOVED when DS UPDATED TO USE TOPOLOGY", func(t *testing.T) { UpdateDSGetServerDSID(t) }) + t.Run("STATUSLASTUPDATED ONLY CHANGES when STATUS CHANGES", func(t *testing.T) { UpdateTestServerStatusLastUpdated(t) }) + + }) +} + +func validateServerFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected response to not be nil.") + serverResp := resp.([]tc.ServerV4) + for field, expected := range expectedResp { + for _, server := range serverResp { + switch field { + case "CachegroupID": + assert.RequireNotNil(t, server.CachegroupID, "Expected CachegroupID to not be nil") + assert.Equal(t, expected, *server.CachegroupID, "Expected CachegroupID to be %d, but got %d", expected, *server.CachegroupID) + case "Cachegroup": + assert.RequireNotNil(t, server.Cachegroup, "Expected Cachegroup to not be nil") + assert.Equal(t, expected, *server.Cachegroup, "Expected Cachegroup to be %s, but got %s", expected, *server.Cachegroup) + case "CDNName": + assert.RequireNotNil(t, server.CDNName, "Expected CDNName to not be nil") + assert.Equal(t, expected, *server.CDNName, "Expected CDNName to be %s, but got %s", expected, *server.CDNName) + case "CDNID": + assert.RequireNotNil(t, server.CDNID, "Expected CDNID to not be nil") + assert.Equal(t, expected, *server.CDNID, "Expected CDNID to be %d, but got %d", expected, *server.CDNID) + case "DomainName": + assert.RequireNotNil(t, server.DomainName, "Expected DomainName to not be nil") + assert.Equal(t, expected, *server.DomainName, "Expected DomainName to be %s, but got %s", expected, *server.DomainName) + case "HostName": + assert.RequireNotNil(t, server.HostName, "Expected HostName to not be nil") + assert.Equal(t, expected, *server.HostName, "Expected HostName to be %s, but got %s", expected, *server.HostName) + case "HTTPSPort": + assert.RequireNotNil(t, server.HTTPSPort, "Expected HTTPSPort to not be nil") + assert.Equal(t, expected, *server.HTTPSPort, "Expected HTTPSPort to be %d, but got %d", expected, *server.HTTPSPort) + case "InterfaceName": + assert.RequireGreaterOrEqual(t, len(server.Interfaces), 1, "Expected Interfaces to have at least 1 interface") + assert.Equal(t, expected, server.Interfaces[0].Name, "Expected InterfaceName to be %s, but got %s", expected, server.Interfaces[0].Name) + case "MTU": + assert.RequireGreaterOrEqual(t, len(server.Interfaces), 1, "Expected Interfaces to have at least 1 interface") + assert.RequireNotNil(t, server.Interfaces[0].MTU, "Expected MTU to not be nil") + assert.Equal(t, expected, *server.Interfaces[0].MTU, "Expected MTU to be %d, but got %d", expected, *server.Interfaces[0].MTU) + case "PhysLocation": + assert.RequireNotNil(t, server.PhysLocation, "Expected PhysLocation to not be nil") + assert.Equal(t, expected, *server.PhysLocation, "Expected PhysLocation to be %s, but got %s", expected, *server.PhysLocation) + case "ProfileNames": + assert.Exactly(t, expected, server.ProfileNames, "Expected ProfileNames to be %v, but got %v", expected, server.ProfileNames) + case "Rack": + assert.RequireNotNil(t, server.Rack, "Expected Rack to not be nil") + assert.Equal(t, expected, *server.Rack, "Expected Rack to be %s, but got %s", expected, *server.Rack) + case "Status": + assert.RequireNotNil(t, server.Status, "Expected Status to not be nil") + assert.Equal(t, expected, *server.Status, "Expected Status to be %s, but got %s", expected, *server.Status) + case "TCPPort": + assert.RequireNotNil(t, server.TCPPort, "Expected TCPPort to not be nil") + assert.Equal(t, expected, *server.TCPPort, "Expected TCPPort to be %d, but got %d", expected, *server.TCPPort) + case "Type": + assert.Equal(t, expected, server.Type, "Expected Type to be %s, but got %s", expected, server.Type) + case "TypeID": + assert.RequireNotNil(t, server.TypeID, "Expected TypeID to not be nil") + assert.Equal(t, expected, *server.TypeID, "Expected Type to be %d, but got %d", expected, *server.TypeID) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validateServerFieldsForUpdate(hostname string, expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, _ interface{}, _ tc.Alerts, _ error) { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("hostName", hostname) + servers, _, err := TOSession.GetServers(opts) + assert.NoError(t, err, "Error getting Server: %v - alerts: %+v", err, servers.Alerts) + assert.Equal(t, 1, len(servers.Response), "Expected Server one server returned Got: %d", len(servers.Response)) + validateServerFields(expectedResp)(t, toclientlib.ReqInf{}, servers.Response, tc.Alerts{}, nil) + } +} + +func validateExpectedServers(expectedHostnames []string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected response to not be nil.") + serverResp := resp.([]tc.ServerV4) + var notInResponse []string + serverMap := make(map[string]struct{}) + for _, server := range serverResp { + assert.RequireNotNil(t, server.HostName, "Expected server host name to not be nil.") + serverMap[*server.HostName] = struct{}{} + } + for _, expected := range expectedHostnames { + if _, exists := serverMap[expected]; !exists { + notInResponse = append(notInResponse, expected) + } + } + assert.Equal(t, len(notInResponse), 0, "%d servers missing from the response: %s", len(notInResponse), strings.Join(notInResponse, ", ")) + } +} + +func validateServerTypeIsNotMid() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected response to not be nil.") + serverResp := resp.([]tc.ServerV4) + for _, server := range serverResp { + assert.RequireNotNil(t, server.HostName, "Expected server host name to not be nil.") + assert.NotEqual(t, server.Type, tc.CacheTypeMid.String(), "Expected to find no %s-typed servers but found server %s", tc.CacheTypeMid, *server.HostName) + } + } +} + +func validateServerPagination(paginationParam string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected response to not be nil.") + paginationResp := resp.([]tc.ServerV4) + opts := client.NewRequestOptions() + opts.QueryParameters.Set("orderby", "id") + respBase, _, err := TOSession.GetServers(opts) + assert.RequireNoError(t, err, "Cannot get Servers: %v - alerts: %+v", err, respBase.Alerts) + + ds := respBase.Response + assert.RequireGreaterOrEqual(t, len(ds), 3, "Need at least 3 Servers in Traffic Ops to test pagination support, found: %d", len(ds)) + switch paginationParam { + case "limit:": + assert.Exactly(t, ds[:1], paginationResp, "expected GET Servers with limit = 1 to return first result") + case "offset": + assert.Exactly(t, ds[1:2], paginationResp, "expected GET Servers with limit = 1, offset = 1 to return second result") + case "page": + assert.Exactly(t, ds[1:2], paginationResp, "expected GET Servers with limit = 1, page = 2 to return second result") + } + } +} + +func generateServer(t *testing.T, requestServer map[string]interface{}) map[string]interface{} { + // map for the most basic Server a user can create + genericServer := map[string]interface{}{ + "cdnId": GetCDNID(t, "cdn1")(), + "cachegroupId": GetCacheGroupId(t, "cachegroup1")(), + "domainName": "localhost", + "hostName": "testserver", + "interfaces": []map[string]interface{}{{ + "ipAddresses": []map[string]interface{}{{ + "address": "127.0.0.1", + "serviceAddress": true, + }}, + "name": "eth0", + }}, + "physLocationId": GetPhysicalLocationID(t, "Denver")(), + "profileNames": []string{"EDGE1"}, + "statusId": GetStatusID(t, "REPORTED")(), + "typeId": GetTypeId(t, "EDGE"), + } + + for k, v := range requestServer { + genericServer[k] = v + } + return genericServer +} + +func GetServerID(t *testing.T, hostName string) func() int { + return func() int { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("hostName", hostName) + serversResp, _, err := TOSession.GetServers(opts) + assert.RequireNoError(t, err, "Get Servers Request failed with error:", err) + assert.RequireEqual(t, 1, len(serversResp.Response), "Expected response object length 1, but got %d", len(serversResp.Response)) + assert.RequireNotNil(t, serversResp.Response[0].ID, "Expected id to not be nil") + return *serversResp.Response[0].ID + } +} + +func UpdateTestServerStatusLastUpdated(t *testing.T) { + const hostName = "atl-edge-01" + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("hostName", hostName) + resp, _, err := TOSession.GetServers(opts) + assert.RequireNoError(t, err, "Cannot get Server by hostname '%s': %v - alerts %+v", hostName, err, resp.Alerts) + assert.RequireGreaterOrEqual(t, len(resp.Response), 1, "Expected at least one server to exist by hostname '%s'", hostName) + assert.RequireNotNil(t, resp.Response[0].StatusLastUpdated, "Traffic Ops returned a representation for a server with null or undefined Status Last Updated time") + originalServer := resp.Response[0] + + // Perform an update with no changes to status + alerts, _, err := TOSession.UpdateServer(*originalServer.ID, originalServer, client.RequestOptions{}) + assert.RequireNoError(t, err, "Cannot UPDATE Server by ID %d (hostname '%s'): %v - alerts: %+v", *originalServer.ID, hostName, err, alerts) + + resp, _, err = TOSession.GetServers(opts) + assert.RequireNoError(t, err, "Cannot get Server by hostname '%s': %v - alerts %+v", hostName, err, resp.Alerts) + assert.RequireGreaterOrEqual(t, len(resp.Response), 1, "Expected at least one server to exist by hostname '%s'", hostName) + respServer := resp.Response[0] + assert.RequireNotNil(t, respServer.StatusLastUpdated, "Traffic Ops returned a representation for a server with null or undefined Status Last Updated time") + assert.Equal(t, *originalServer.StatusLastUpdated, *respServer.StatusLastUpdated, "Since status didnt change, no change in 'StatusLastUpdated' time was expected. "+ + "old value: %v, new value: %v", *originalServer.StatusLastUpdated, *respServer.StatusLastUpdated) + + // Changing the status, perform an update and make sure that statusLastUpdated changed + newStatusID := GetStatusID(t, "ONLINE")() + originalServer.StatusID = &newStatusID + + alerts, _, err = TOSession.UpdateServer(*originalServer.ID, originalServer, client.RequestOptions{}) + assert.RequireNoError(t, err, "Cannot UPDATE Server by ID %d (hostname '%s'): %v - alerts: %+v", *originalServer.ID, hostName, err, alerts) + + resp, _, err = TOSession.GetServers(opts) + assert.RequireNoError(t, err, "Cannot get Server by hostname '%s': %v - alerts %+v", hostName, err, resp.Alerts) + assert.RequireGreaterOrEqual(t, len(resp.Response), 1, "Expected at least one server to exist by hostname '%s'", hostName) + respServer = resp.Response[0] + assert.RequireNotNil(t, respServer.StatusLastUpdated, "Traffic Ops returned a representation for a server with null or undefined Status Last Updated time") + assert.NotEqual(t, *originalServer.StatusLastUpdated, *respServer.StatusLastUpdated, "Since status changed, expected 'StatusLastUpdated' to change. "+ + "old value: %v, new value: %v", *originalServer.StatusLastUpdated, *respServer.StatusLastUpdated) +} + +func UpdateDSGetServerDSID(t *testing.T) { + const hostName = "atlanta-edge-14" + const xmlId = "ds3" + var topology = "mso-topology" + var firstHeaderRewrite = "first header rewrite" + var innerHeaderRewrite = "inner header rewrite" + var lastHeaderRewrite = "last header rewrite" + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("dsId", strconv.Itoa(GetDeliveryServiceId(t, xmlId)())) + servers, _, err := TOSession.GetServers(opts) + assert.RequireNoError(t, err, "Failed to get Servers: %v - alerts: %+v", err, servers.Alerts) + assert.RequireGreaterOrEqual(t, len(servers.Response), 1, "Failed to get at least one Server") + assert.RequireEqual(t, hostName, *servers.Response[0].HostName, "Expected delivery service assignment between xmlId: %v and server: %v. Got server: %v", xmlId, hostName, servers.Response[0].HostName) + + opts.QueryParameters.Set("xmlId", xmlId) + dses, _, err := TOSession.GetDeliveryServices(opts) + assert.RequireNoError(t, err, "Failed to get Delivery Services: %v - alerts: %+v", err, dses.Alerts) + assert.RequireEqual(t, len(dses.Response), 1, "Failed to get at least one Delivery Service") + ds := dses.Response[0] + + ds.Topology = &topology + ds.FirstHeaderRewrite = &firstHeaderRewrite + ds.InnerHeaderRewrite = &innerHeaderRewrite + ds.LastHeaderRewrite = &lastHeaderRewrite + ds.EdgeHeaderRewrite = nil + ds.MidHeaderRewrite = nil + + updResp, _, err := TOSession.UpdateDeliveryService(*ds.ID, ds, client.RequestOptions{}) + assert.RequireNoError(t, err, "Unable to add topology-related fields to deliveryservice %s: %v - alerts: %+v", xmlId, err, updResp.Alerts) + + opts.QueryParameters.Set("dsId", strconv.Itoa(*ds.ID)) + servers, _, err = TOSession.GetServers(opts) + assert.RequireNoError(t, err, "Failed to get servers by Topology-based Delivery Service ID with xmlId %s: %v - alerts: %+v", xmlId, err, servers.Alerts) + assert.RequireGreaterOrEqual(t, len(servers.Response), 1, "Expected at least one server") + for _, server := range servers.Response { + assert.NotEqual(t, hostName, *server.HostName, "Server: %v was not expected to be returned.") + } +} + +func CreateTestServers(t *testing.T) { + for _, server := range testData.Servers { + resp, _, err := TOSession.CreateServer(server, client.RequestOptions{}) + assert.RequireNoError(t, err, "Could not create server '%s': %v - alerts: %+v", *server.HostName, err, resp.Alerts) + } +} + +func DeleteTestServers(t *testing.T) { + servers, _, err := TOSession.GetServers(client.RequestOptions{}) + assert.NoError(t, err, "Cannot get Servers: %v - alerts: %+v", err, servers.Alerts) + + for _, server := range servers.Response { + delResp, _, err := TOSession.DeleteServer(*server.ID, client.RequestOptions{}) + assert.NoError(t, err, "Could not delete Server: %v - alerts: %+v", err, delResp.Alerts) + // Retrieve Server to see if it got deleted + opts := client.NewRequestOptions() + opts.QueryParameters.Set("id", strconv.Itoa(*server.ID)) + getServer, _, err := TOSession.GetServers(opts) + assert.RequireNotNil(t, server.HostName, "Expected server host name to not be nil.") + assert.NoError(t, err, "Error deleting Server for '%s' : %v - alerts: %+v", *server.HostName, err, getServer.Alerts) + assert.Equal(t, 0, len(getServer.Response), "Expected Server '%s' to be deleted", *server.HostName) + } +} diff --git a/traffic_ops/testing/api/v5/serverupdatestatus_test.go b/traffic_ops/testing/api/v5/serverupdatestatus_test.go new file mode 100644 index 0000000000..64dfd367cc --- /dev/null +++ b/traffic_ops/testing/api/v5/serverupdatestatus_test.go @@ -0,0 +1,639 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "strconv" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/lib/go-util" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestServerUpdateStatusLastAssigned(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, ServiceCategories, Topologies, DeliveryServices}, func() { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("hostName", "atlanta-edge-01") + resp, _, err := TOSession.GetServers(opts) + if err != nil { + t.Fatalf("cannot get server by hostname: %v", err) + } + if len(resp.Response) != 1 { + t.Fatalf("Expected a server named 'atlanta-edge-01' to exist") + } + edge := resp.Response[0] + opts = client.NewRequestOptions() + opts.QueryParameters.Set("xmlId", "ds-top") + dsResp, _, err := TOSession.GetDeliveryServices(opts) + if err != nil { + t.Fatalf("cannot get delivery service by xmlId: %v", err) + } + if len(resp.Response) != 1 { + t.Fatalf("Expected one delivery service with xmlId 'ds-top' to exist") + } + // temporarily unassign the topology in order to assign an EDGE + ds := dsResp.Response[0] + tmpTop := *ds.Topology + ds.Topology = nil + ds.FirstHeaderRewrite = nil + ds.LastHeaderRewrite = nil + ds.InnerHeaderRewrite = nil + _, _, err = TOSession.UpdateDeliveryService(*ds.ID, ds, client.RequestOptions{}) + if err != nil { + t.Fatalf("cannot update delivery service 'ds-top': %v", err) + } + _, _, err = TOSession.CreateDeliveryServiceServers(*ds.ID, []int{*edge.ID}, true, client.RequestOptions{}) + if err != nil { + t.Fatalf("cannot create delivery service server: %v", err) + } + // reassign the topology + ds.Topology = &tmpTop + _, _, err = TOSession.UpdateDeliveryService(*ds.ID, ds, client.RequestOptions{}) + if err != nil { + t.Fatalf("cannot update delivery service 'ds-top': %v", err) + } + // attempt to set the edge to OFFLINE + _, _, err = TOSession.UpdateServerStatus(*edge.ID, tc.ServerPutStatus{ + Status: util.JSONNameOrIDStr{Name: util.StrPtr("OFFLINE")}, + OfflineReason: util.StrPtr("testing")}, client.RequestOptions{}) + if err != nil { + t.Errorf("setting edge to OFFLINE when it's the only edge assigned to a topology-based delivery service - expected: no error, actual: %v", err) + } + // remove EDGE assignment + _, _, err = TOSession.CreateDeliveryServiceServers(*ds.ID, []int{}, true, client.RequestOptions{}) + if err != nil { + t.Errorf("removing delivery service servers from topology-based delivery service - expected: no error, actual: %v", err) + } + }) +} + +func TestServerUpdateStatus(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers}, func() { + //TODO: DON'T hard-code server hostnames! + var edge1cdn1 tc.ServerV4 + var edge2cdn1 tc.ServerV4 + var mid1cdn1 tc.ServerV4 + var edge1cdn2 tc.ServerV4 + + opts := client.NewRequestOptions() + + getServers := func() { + for _, s := range []struct { + name string + server *tc.ServerV4 + }{ + { + "atlanta-edge-01", + &edge1cdn1, + }, + { + "atlanta-edge-03", + &edge2cdn1, + }, + { + "atlanta-mid-16", + &mid1cdn1, + }, + { + "edge1-cdn2", + &edge1cdn2, + }, + } { + opts.QueryParameters.Set("hostName", s.name) + resp, _, err := TOSession.GetServers(opts) + if err != nil { + t.Errorf("cannot get Server by hostname '%s': %v - alerts: %+v", s.name, err, resp.Alerts) + } + if len(resp.Response) < 1 { + t.Fatalf("Expected a server named '%s' to exist", s.name) + } + if len(resp.Response) > 1 { + t.Errorf("Expected exactly one server named '%s' to exist - actual: %d", s.name, len(resp.Response)) + t.Logf("Testing will proceed with server: %+v", resp.Response[0]) + } + *s.server = resp.Response[0] + if s.server.ID == nil { + t.Fatalf("server '%s' was returned with nil ID", s.name) + } + if s.server.HostName == nil { + t.Fatalf("server '%s' was returned with nil HostName", s.name) + } + } + } + getServers() + + // assert that servers don't have updates pending + for _, s := range []tc.ServerV4{ + edge1cdn1, + edge2cdn1, + mid1cdn1, + edge1cdn2, + } { + if s.UpdPending == nil { + t.Error("expected UpdPending: false, actual: null") + } else if *s.UpdPending { + t.Error("expected UpdPending: false, actual: true") + } + } + + // update status of MID server to OFFLINE + alerts, _, err := TOSession.UpdateServerStatus(*mid1cdn1.ID, tc.ServerPutStatus{ + Status: util.JSONNameOrIDStr{Name: util.StrPtr("OFFLINE")}, + OfflineReason: util.StrPtr("testing")}, client.RequestOptions{}) + if err != nil { + t.Errorf("cannot update server status: %v - alerts: %+v", err, alerts) + } + + // assert that updates were queued for the proper EDGE servers + getServers() + if edge1cdn1.UpdPending == nil { + t.Errorf("expected: child %s (%d) to have updates pending, actual: property was null (or missing)", *edge1cdn1.HostName, *edge1cdn1.ID) + } else if !*edge1cdn1.UpdPending { + t.Errorf("expected: child %s (%d) to have updates pending, actual: no updates pending", *edge1cdn1.HostName, *edge1cdn1.ID) + } + + if edge2cdn1.UpdPending == nil { + t.Errorf("expected: child %s (%d) to have updates pending, actual: property was null (or missing)", *edge2cdn1.HostName, *edge2cdn1.ID) + } else if !*edge2cdn1.UpdPending { + t.Errorf("expected: child %s (%d) to have updates pending, actual: no updates pending", *edge2cdn1.HostName, *edge2cdn1.ID) + } + if mid1cdn1.UpdPending == nil { + t.Errorf("expected: server %s (%d) with updated status to have no updates pending, actual: property was null (or missing)", *mid1cdn1.HostName, *mid1cdn1.ID) + } else if *mid1cdn1.UpdPending { + t.Errorf("expected: server %s (%d) with updated status to have no updates pending, actual: updates pending", *mid1cdn1.HostName, *mid1cdn1.ID) + } + + if edge1cdn2.UpdPending == nil { + t.Errorf("expected: server %s (%d) in different CDN than server with updated status to have no updates pending, actual: updates pending", *edge1cdn2.HostName, *edge1cdn2.ID) + } else if *edge1cdn2.UpdPending { + t.Errorf("expected: server %s (%d) in different CDN than server with updated status to have no updates pending, actual: updates pending", *edge1cdn2.HostName, *edge1cdn2.ID) + } + + // update status of MID server to OFFLINE via status ID + opts = client.NewRequestOptions() + opts.QueryParameters.Set("name", "OFFLINE") + status, _, err := TOSession.GetStatuses(opts) + if err != nil { + t.Fatalf("cannot get Status 'OFFLINE': %v - alerts: %+v", err, status.Alerts) + } + if len(status.Response) != 1 { + t.Fatalf("Expected exactly one Status to exist with name 'OFFLINE', found: %d", len(status.Response)) + } + alerts, _, err = TOSession.UpdateServerStatus( + *mid1cdn1.ID, + tc.ServerPutStatus{ + Status: util.JSONNameOrIDStr{ID: util.IntPtr(status.Response[0].ID)}, + OfflineReason: util.StrPtr("testing"), + }, + client.RequestOptions{}, + ) + if err != nil { + t.Errorf("cannot update server status: %v - alerts: %+v", err, alerts.Alerts) + } + + // negative cases: + // server doesn't exist + _, _, err = TOSession.UpdateServerStatus( + -1, + tc.ServerPutStatus{ + Status: util.JSONNameOrIDStr{Name: util.StrPtr("OFFLINE")}, + OfflineReason: util.StrPtr("testing"), + }, + client.RequestOptions{}, + ) + if err == nil { + t.Error("update server status exected: err, actual: nil") + } + + // status does not exist + _, _, err = TOSession.UpdateServerStatus( + *mid1cdn1.ID, + tc.ServerPutStatus{ + Status: util.JSONNameOrIDStr{Name: util.StrPtr("NOT_A_REAL_STATUS")}, + OfflineReason: util.StrPtr("testing"), + }, + client.RequestOptions{}, + ) + if err == nil { + t.Error("update server status exected: err, actual: nil") + } + + // offlineReason required for OFFLINE status + _, _, err = TOSession.UpdateServerStatus( + *mid1cdn1.ID, + tc.ServerPutStatus{ + Status: util.JSONNameOrIDStr{Name: util.StrPtr("OFFLINE")}, + OfflineReason: nil, + }, + client.RequestOptions{}, + ) + if err == nil { + t.Error("update server status exected: err, actual: nil") + } + + // offlineReason required for ADMIN_DOWN status + _, _, err = TOSession.UpdateServerStatus( + *mid1cdn1.ID, + tc.ServerPutStatus{ + Status: util.JSONNameOrIDStr{Name: util.StrPtr("ADMIN_DOWN")}, + OfflineReason: nil, + }, + client.RequestOptions{}, + ) + if err == nil { + t.Error("update server status exected: err, actual: nil") + } + }) +} + +func TestServerQueueUpdate(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers}, func() { + // TODO: DON'T hard-code server hostnames! + const serverName = "atlanta-edge-01" + + queueUpdateActions := map[bool]string{ + false: "dequeue", + true: "queue", + } + + var s tc.ServerV4 + opts := client.NewRequestOptions() + opts.QueryParameters.Add("hostName", serverName) + resp, _, err := TOSession.GetServers(opts) + if err != nil { + t.Fatalf("failed to get Server by hostname '%s': %v - alerts: %+v", serverName, err, resp.Alerts) + } + if len(resp.Response) < 1 { + t.Fatalf("Expected a server named '%s' to exist", serverName) + } + if len(resp.Response) > 1 { + t.Errorf("Expected exactly one server named '%s' to exist", serverName) + t.Logf("Testing will proceed with server: %+v", resp.Response[0]) + } + s = resp.Response[0] + + // assert that servers don't have updates pending + if s.UpdPending == nil { + t.Fatalf("Server '%s' had null (or missing) updPending property", serverName) + } + if got, want := *s.UpdPending, false; got != want { + t.Fatalf("unexpected UpdPending, got: %v, want: %v", got, want) + } + + if s.ID == nil { + t.Fatalf("Server '%s' had nil ID", serverName) + } + + for _, setVal := range [...]bool{true, false} { + t.Run(fmt.Sprint(setVal), func(t *testing.T) { + // queue update and check response + quResp, _, err := TOSession.SetServerQueueUpdate(*s.ID, setVal, client.RequestOptions{}) + if err != nil { + t.Fatalf("failed to set queue update for server with ID %d to %t: %v - alerts: %+v", s.ID, setVal, err, quResp.Alerts) + } + if got, want := int(quResp.Response.ServerID), *s.ID; got != want { + t.Errorf("wrong serverId in response, got: %v, want: %v", got, want) + } + if got, want := quResp.Response.Action, queueUpdateActions[setVal]; got != want { + t.Errorf("wrong action in response, got: %v, want: %v", got, want) + } + + // assert that the server has updates queued + resp, _, err = TOSession.GetServers(opts) + if err != nil { + t.Fatalf("failed to GET Server by hostname '%s': %v - %v", serverName, err, resp.Alerts) + } + if len(resp.Response) < 1 { + t.Fatalf("Expected a server named '%s' to exist", serverName) + } + if len(resp.Response) > 1 { + t.Errorf("Expected exactly one server named '%s' to exist", serverName) + t.Logf("Testing will proceed with server: %+v", resp.Response[0]) + } + s = resp.Response[0] + if s.UpdPending == nil { + t.Fatalf("Server '%s' had null (or missing) updPending property", serverName) + } + if got, want := *s.UpdPending, setVal; got != want { + t.Errorf("unexpected UpdPending, got: %v, want: %v", got, want) + } + }) + } + + t.Run("validations", func(t *testing.T) { + // server doesn't exist + _, _, err = TOSession.SetServerQueueUpdate(-1, true, client.RequestOptions{}) + if err == nil { + t.Error("update server status expected: error, actual: nil") + } + + // invalid action + req, err := json.Marshal(tc.ServerQueueUpdateRequest{Action: "foobar"}) + if err != nil { + t.Fatalf("failed to encode request body: %v", err) + } + + // TODO: don't construct URLs like this, nor use "RawRequest" + path := fmt.Sprintf(TestAPIBase+"/servers/%d/queue_update", *s.ID) + httpResp, _, err := TOSession.RawRequest(http.MethodPost, path, req) + if err != nil { + t.Fatalf("POST request failed: %v", err) + } + if httpResp.StatusCode >= 200 && httpResp.StatusCode <= 299 { + t.Errorf("unexpected status code: got %v, want something outside the range [200, 299]", httpResp.StatusCode) + } + }) + }) +} + +func TestSetServerUpdateStatuses(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers}, func() { + if len(testData.Servers) < 1 { + t.Fatal("cannot GET Server: no test data") + } + testServer := testData.Servers[0] + if testServer.HostName == nil { + t.Fatalf("First test server had nil hostname: %+v", testServer) + } + + opts := client.NewRequestOptions() + opts.QueryParameters.Add("hostName", *testServer.HostName) + testVals := func(configApply, revalApply *time.Time) { + resp, _, err := TOSession.GetServers(opts) + if err != nil { + t.Errorf("cannot get Server by name '%s': %v - alerts: %+v", *testServer.HostName, err, resp.Alerts) + } else if len(resp.Response) != 1 { + t.Fatalf("GET Server expected 1, actual %v", len(resp.Response)) + } + + beforeServer := resp.Response[0] + + // Ensure baseline + if beforeServer.UpdPending == nil { + t.Fatalf("Server '%s' had nil UpdPending before update status change", *testServer.HostName) + } + if beforeServer.RevalPending == nil { + t.Fatalf("Server '%s' had nil RevalPending before update status change", *testServer.HostName) + } + if beforeServer.ConfigUpdateTime == nil { + t.Fatalf("Server '%s' had nil ConfigUpdateTime before update status change", *testServer.HostName) + } + if beforeServer.ConfigApplyTime == nil { + t.Fatalf("Server '%s' had nil ConfigApplyTime before update status change", *testServer.HostName) + } + if beforeServer.RevalUpdateTime == nil { + t.Fatalf("Server '%s' had nil RevalUpdateTime before update status change", *testServer.HostName) + } + if beforeServer.RevalApplyTime == nil { + t.Fatalf("Server '%s' had nil RevalApplyTime before update status change", *testServer.HostName) + } + + // Make change + if alerts, _, err := TOSession.SetUpdateServerStatusTimes(*testServer.HostName, configApply, revalApply, client.RequestOptions{}); err != nil { + t.Fatalf("SetUpdateServerStatusTimes error. expected: nil, actual: %v - alerts: %+v", err, alerts.Alerts) + } + + resp, _, err = TOSession.GetServers(opts) + if err != nil { + t.Errorf("cannot GET Server by name '%s': %v - alerts: %+v", *testServer.HostName, err, resp.Alerts) + } else if len(resp.Response) != 1 { + t.Fatalf("GET Server expected 1, actual %v", len(resp.Response)) + } + + afterServer := resp.Response[0] + + if afterServer.UpdPending == nil { + t.Fatalf("Server '%s' had nil UpdPending after update status change", *testServer.HostName) + } + if afterServer.RevalPending == nil { + t.Fatalf("Server '%s' had nil RevalPending after update status change", *testServer.HostName) + } + + // Ensure values were actually set + if configApply != nil { + if afterServer.ConfigApplyTime == nil || !afterServer.ConfigApplyTime.Equal(*configApply) { + t.Errorf("Failed to set server's ConfigApplyTime. expected: %v actual: %v", *configApply, afterServer.ConfigApplyTime) + } + } + if revalApply != nil { + if afterServer.RevalApplyTime == nil || !afterServer.RevalApplyTime.Equal(*revalApply) { + t.Errorf("Failed to set server's RevalApplyTime. expected: %v actual: %v", *revalApply, afterServer.RevalApplyTime) + } + } + + } + + // Postgres stores microsecond precision. There is also some discussion around MacOS losing + // precision as well. The nanosecond precision is accurate within go one linux however, + // but round trips to and from the database may result in an inaccurate Equals comparison + // with the loss of precision. Also, it appears to Round and not Truncate. + now := time.Now().Round(time.Microsecond) + + // Test setting the values works as expected + testVals(util.TimePtr(now), nil) // configApply + testVals(nil, util.TimePtr(now)) // revalApply + + // Test sending all nils. Should fail + if _, _, err := TOSession.SetUpdateServerStatusTimes(*testServer.HostName, nil, nil, client.RequestOptions{}); err == nil { + t.Errorf("UpdateServerStatuses with (nil,nil) expected error, actual nil") + } + }) +} + +func TestSetTopologiesServerUpdateStatuses(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies}, func() { + const ( + topologyName = "forked-topology" + edgeCacheGroup = "topology-edge-cg-01" + otherEdgeCacheGroup = "topology-edge-cg-02" + midCacheGroup = "topology-mid-cg-04" + ) + cacheGroupNames := []string{edgeCacheGroup, otherEdgeCacheGroup, midCacheGroup} + cachesByCDNCacheGroup := make(map[string]map[string][]tc.ServerV4) + updateStatusByCacheGroup := map[string]tc.ServerUpdateStatusV40{} + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", topologyName) + forkedTopology, _, err := TOSession.GetTopologies(opts) + if err != nil { + t.Fatalf("Topology '%s' was not found: %v - alerts: %+v", topologyName, err, forkedTopology.Alerts) + } + if len(forkedTopology.Response) != 1 { + t.Fatalf("Expected exactly one Topology to exist with name '%s', found: %d", topologyName, len(forkedTopology.Response)) + } + for _, cacheGroupName := range cacheGroupNames { + foundNode := false + for _, node := range forkedTopology.Response[0].Nodes { + if node.Cachegroup == cacheGroupName { + foundNode = true + break + } + } + if !foundNode { + t.Fatalf("unable to find topology node with cachegroup %s", cacheGroupName) + } + + opts = client.NewRequestOptions() + opts.QueryParameters.Set("name", cacheGroupName) + cacheGroups, _, err := TOSession.GetCacheGroups(opts) + if err != nil { + t.Fatalf("unable to get cachegroup %s: %s", cacheGroupName, err.Error()) + } + if len(cacheGroups.Response) != 1 { + t.Fatalf("incorrect number of cachegroups. expected: 1 actual: %d", len(cacheGroups.Response)) + } + cacheGroup := cacheGroups.Response[0] + + opts.QueryParameters = url.Values{"cachegroup": []string{strconv.Itoa(*cacheGroup.ID)}} + srvs, _, err := TOSession.GetServers(opts) + if err != nil { + t.Fatalf("unable to get a server from cachegroup %s: %v - alerts: %+v", cacheGroupName, err, srvs.Alerts) + } + if len(srvs.Response) < 1 { + t.Fatalf("Expected at least one server in Cache Group #%d - found none", *cacheGroup.ID) + } + for _, s := range srvs.Response { + if _, ok := cachesByCDNCacheGroup[*s.CDNName]; !ok { + cachesByCDNCacheGroup[*s.CDNName] = make(map[string][]tc.ServerV4) + } + cachesByCDNCacheGroup[*s.CDNName][cacheGroupName] = append(cachesByCDNCacheGroup[*s.CDNName][cacheGroupName], s) + } + } + cdnNames := make([]string, 0, len(cachesByCDNCacheGroup)) + for cdn := range cachesByCDNCacheGroup { + cdnNames = append(cdnNames, cdn) + } + if len(cdnNames) < 2 { + t.Fatalf("expected servers in at least two CDNs, actual number of CDNs: %d", len(cdnNames)) + } + cdn1 := cdnNames[0] + cdn2 := cdnNames[1] + + // update status of MID server to OFFLINE + resp, _, err := TOSession.UpdateServerStatus(*cachesByCDNCacheGroup[cdn1][midCacheGroup][0].ID, tc.ServerPutStatus{ + Status: util.JSONNameOrIDStr{Name: util.StrPtr("OFFLINE")}, + OfflineReason: util.StrPtr("testing")}, client.RequestOptions{}) + if err != nil { + t.Fatalf("cannot update server status: %v - alerts: %+v", err, resp.Alerts) + } + + opts = client.NewRequestOptions() + for _, cacheGroupName := range cacheGroupNames { + cgID := *cachesByCDNCacheGroup[cdn1][cacheGroupName][0].CachegroupID + opts.QueryParameters.Set("cachegroup", strconv.Itoa(cgID)) + srvs, _, err := TOSession.GetServers(opts) + if err != nil { + t.Fatalf("unable to get a server from cachegroup %s: %v - alerts: %+v", cacheGroupName, err, srvs.Alerts) + } + if len(srvs.Response) < 1 { + t.Fatalf("Expected at least one Server in Cache Group #%d, found none", cgID) + } + for _, s := range srvs.Response { + if s.HostName == nil || s.UpdPending == nil || s.ID == nil { + t.Fatal("Traffic Ops returned a representation of a server with null or undefined Host Name and/or ID and/or Update Pending flag") + } + if len(cachesByCDNCacheGroup[*s.CDNName][cacheGroupName]) > 0 { + cachesByCDNCacheGroup[*s.CDNName][cacheGroupName] = []tc.ServerV4{} + } + cachesByCDNCacheGroup[*s.CDNName][cacheGroupName] = append(cachesByCDNCacheGroup[*s.CDNName][cacheGroupName], s) + } + } + for _, cacheGroupName := range cacheGroupNames { + updResp, _, err := TOSession.GetServerUpdateStatus(*cachesByCDNCacheGroup[cdn1][cacheGroupName][0].HostName, client.RequestOptions{}) + if err != nil { + t.Fatalf("unable to get update status for a server from Cache Group '%s': %v - alerts: %+v", cacheGroupName, err, updResp.Alerts) + } + if len(updResp.Response) < 1 { + t.Fatalf("Expected at least one server with Host Name '%s' to have an update status", *cachesByCDNCacheGroup[cdn1][cacheGroupName][0].HostName) + } + updateStatusByCacheGroup[cacheGroupName] = updResp.Response[0] + } + // updating the server status does not queue updates within the same cachegroup in same CDN + if *cachesByCDNCacheGroup[cdn1][midCacheGroup][0].UpdPending { + t.Fatalf("expected UpdPending: %t, actual: %t", false, *cachesByCDNCacheGroup[cdn1][midCacheGroup][0].UpdPending) + } + // updating the server status does not queue updates within the same cachegroup in different CDN + if *cachesByCDNCacheGroup[cdn2][midCacheGroup][0].UpdPending { + t.Fatalf("expected UpdPending: %t, actual: %t", false, *cachesByCDNCacheGroup[cdn2][midCacheGroup][0].UpdPending) + } + // edgeCacheGroup is a descendant of midCacheGroup + if !*cachesByCDNCacheGroup[cdn1][edgeCacheGroup][0].UpdPending { + t.Fatalf("expected UpdPending: %t, actual: %t", true, *cachesByCDNCacheGroup[cdn1][edgeCacheGroup][0].UpdPending) + } + // descendant of midCacheGroup in different CDN should not be queued + if *cachesByCDNCacheGroup[cdn2][edgeCacheGroup][0].UpdPending { + t.Fatalf("expected UpdPending: %t, actual: %t", false, *cachesByCDNCacheGroup[cdn2][edgeCacheGroup][0].UpdPending) + } + if !updateStatusByCacheGroup[edgeCacheGroup].UpdatePending { + t.Fatalf("expected UpdPending: %t, actual: %t", true, updateStatusByCacheGroup[edgeCacheGroup].UpdatePending) + } + // otherEdgeCacheGroup is not a descendant of midCacheGroup but is still in the same topology + if *cachesByCDNCacheGroup[cdn1][otherEdgeCacheGroup][0].UpdPending { + t.Fatalf("expected UpdPending: %t, actual: %t", false, *cachesByCDNCacheGroup[cdn1][otherEdgeCacheGroup][0].UpdPending) + } + if updateStatusByCacheGroup[otherEdgeCacheGroup].UpdatePending { + t.Fatalf("expected UpdPending: %t, actual: %t", false, updateStatusByCacheGroup[otherEdgeCacheGroup].UpdatePending) + } + + squResp, _, err := TOSession.SetServerQueueUpdate(*cachesByCDNCacheGroup[cdn1][midCacheGroup][0].ID, true, client.RequestOptions{}) + if err != nil { + t.Fatalf("cannot update server status on %s: %v - alerts: %+v", *cachesByCDNCacheGroup[cdn1][midCacheGroup][0].HostName, err, squResp.Alerts) + } + for _, cacheGroupName := range cacheGroupNames { + updResp, _, err := TOSession.GetServerUpdateStatus(*cachesByCDNCacheGroup[cdn1][cacheGroupName][0].HostName, client.RequestOptions{}) + if err != nil { + t.Fatalf("unable to get an update status for a server from Cache Group '%s': %v - alerts: %+v", cacheGroupName, err, updResp.Alerts) + } + if len(updResp.Response) < 1 { + t.Fatalf("Expected at least one server with Host Name '%s' to have an update status", *cachesByCDNCacheGroup[cdn1][cacheGroupName][0].HostName) + } + updateStatusByCacheGroup[cacheGroupName] = updResp.Response[0] + } + + // edgeCacheGroup is a descendant of midCacheGroup + if !updateStatusByCacheGroup[edgeCacheGroup].ParentPending { + t.Fatalf("expected UpdPending: %t, actual: %t", true, updateStatusByCacheGroup[edgeCacheGroup].ParentPending) + } + // otherEdgeCacheGroup is not a descendant of midCacheGroup but is still in the same topology + if updateStatusByCacheGroup[otherEdgeCacheGroup].ParentPending { + t.Fatalf("expected UpdPending: %t, actual: %t", false, updateStatusByCacheGroup[otherEdgeCacheGroup].ParentPending) + } + + edgeHostName := *cachesByCDNCacheGroup[cdn1][edgeCacheGroup][0].HostName + *cachesByCDNCacheGroup[cdn1][edgeCacheGroup][0].HostName = *cachesByCDNCacheGroup[cdn1][midCacheGroup][0].HostName + _, _, err = TOSession.UpdateServer(*cachesByCDNCacheGroup[cdn1][edgeCacheGroup][0].ID, cachesByCDNCacheGroup[cdn1][edgeCacheGroup][0], client.RequestOptions{}) + if err != nil { + t.Fatalf("unable to update %s's hostname to %s: %s", edgeHostName, *cachesByCDNCacheGroup[cdn1][midCacheGroup][0].HostName, err) + } + + updResp, _, err := TOSession.GetServerUpdateStatus(*cachesByCDNCacheGroup[cdn1][midCacheGroup][0].HostName, client.RequestOptions{}) + if err != nil { + t.Fatalf("expected no error getting server updates for a non-unique hostname %s, got: %v - alerts: %+v", *cachesByCDNCacheGroup[cdn1][midCacheGroup][0].HostName, err, updResp.Alerts) + } + + *cachesByCDNCacheGroup[cdn1][edgeCacheGroup][0].HostName = edgeHostName + _, _, err = TOSession.UpdateServer(*cachesByCDNCacheGroup[cdn1][edgeCacheGroup][0].ID, cachesByCDNCacheGroup[cdn1][edgeCacheGroup][0], client.RequestOptions{}) + if err != nil { + t.Fatalf("unable to revert %s's hostname back to %s: %s", edgeHostName, edgeHostName, err) + } + }) +} diff --git a/traffic_ops/testing/api/v5/servicecategories_test.go b/traffic_ops/testing/api/v5/servicecategories_test.go new file mode 100644 index 0000000000..bd528a3f10 --- /dev/null +++ b/traffic_ops/testing/api/v5/servicecategories_test.go @@ -0,0 +1,313 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "sort" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestServiceCategories(t *testing.T) { + WithObjs(t, []TCObj{ServiceCategories}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID request": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateServiceCategoriesSort()), + }, + "OK when VALID NAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"serviceCategory1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateServiceCategoriesFields(map[string]interface{}{"Name": "serviceCategory1"})), + }, + "VALID when SORTORDER param is DESC": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"sortOrder": {"desc"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateServiceCategoriesDescSort()), + }, + "EMPTY RESPONSE when SERVICE CATEGORY DOESNT EXIST": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"invalid"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "FIRST RESULT when LIMIT=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateServiceCategoriesPagination("limit")), + }, + "SECOND RESULT when LIMIT=1 OFFSET=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "offset": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateServiceCategoriesPagination("offset")), + }, + "SECOND RESULT when LIMIT=1 PAGE=2": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "page": {"2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateServiceCategoriesPagination("page")), + }, + "BAD REQUEST when INVALID LIMIT parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"-2"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID OFFSET parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "offset": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID PAGE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "page": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "POST": { + "BAD REQUEST when ALREADY EXISTS": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "serviceCategory1", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when NAME FIELD is BLANK": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "name": "", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "PUT": { + "OK when VALID request": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"barServiceCategory2"}}}, + RequestBody: map[string]interface{}{"name": "newName"}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateServiceCategoriesUpdateCreateFields("newName", map[string]interface{}{"Name": "newName"})), + }, + "PRECONDITION FAILED when updating with IMS & IUS Headers": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{ + QueryParameters: url.Values{"name": {"serviceCategory1"}}, + Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}, + }, + RequestBody: map[string]interface{}{"name": "newName"}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + "PRECONDITION FAILED when updating with IFMATCH ETAG Header": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{"name": "newName"}, + RequestOpts: client.RequestOptions{ + QueryParameters: url.Values{"name": {"serviceCategory1"}}, + Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + }, + "DELETE": { + "NOT FOUND when DOESNT EXIST": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"invalid"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + }, + "GET AFTER CHANGES": { + "OK when CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {currentTimeRFC}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + serviceCategory := tc.ServiceCategory{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &serviceCategory) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET", "GET AFTER CHANGES": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetServiceCategories(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.CreateServiceCategory(serviceCategory, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.UpdateServiceCategory(testCase.RequestOpts.QueryParameters["name"][0], serviceCategory, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.DeleteServiceCategory(testCase.RequestOpts.QueryParameters["name"][0], testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateServiceCategoriesFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Service Categories response to not be nil.") + serviceCategoryResp := resp.([]tc.ServiceCategory) + for field, expected := range expectedResp { + for _, serviceCategory := range serviceCategoryResp { + switch field { + case "Name": + assert.Equal(t, expected, serviceCategory.Name, "Expected Name to be %v, but got %s", expected, serviceCategory.Name) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validateServiceCategoriesUpdateCreateFields(name string, expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", name) + serviceCategories, _, err := TOSession.GetServiceCategories(opts) + assert.RequireNoError(t, err, "Error getting Service Categories: %v - alerts: %+v", err, serviceCategories.Alerts) + assert.RequireEqual(t, 1, len(serviceCategories.Response), "Expected one Service Category returned Got: %d", len(serviceCategories.Response)) + validateServiceCategoriesFields(expectedResp)(t, toclientlib.ReqInf{}, serviceCategories.Response, tc.Alerts{}, nil) + } +} + +func validateServiceCategoriesPagination(paginationParam string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + paginationResp := resp.([]tc.ServiceCategory) + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("orderby", "id") + respBase, _, err := TOSession.GetServiceCategories(opts) + assert.RequireNoError(t, err, "Cannot get Service Categories: %v - alerts: %+v", err, respBase.Alerts) + + serviceCategories := respBase.Response + assert.RequireGreaterOrEqual(t, len(serviceCategories), 2, "Need at least 2 Service Categories in Traffic Ops to test pagination support, found: %d", len(serviceCategories)) + switch paginationParam { + case "limit:": + assert.Exactly(t, serviceCategories[:1], paginationResp, "expected GET Service Categories with limit = 1 to return first result") + case "offset": + assert.Exactly(t, serviceCategories[1:2], paginationResp, "expected GET Service Categories with limit = 1, offset = 1 to return second result") + case "page": + assert.Exactly(t, serviceCategories[1:2], paginationResp, "expected GET Service Categories with limit = 1, page = 2 to return second result") + } + } +} + +func validateServiceCategoriesSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Service Categories response to not be nil.") + var serviceCategoryNames []string + serviceCategoryResp := resp.([]tc.ServiceCategory) + for _, serviceCategory := range serviceCategoryResp { + serviceCategoryNames = append(serviceCategoryNames, serviceCategory.Name) + } + assert.Equal(t, true, sort.StringsAreSorted(serviceCategoryNames), "List is not sorted by their names: %v", serviceCategoryNames) + } +} + +func validateServiceCategoriesDescSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Service Categories response to not be nil.") + serviceCategoriesDescResp := resp.([]tc.ServiceCategory) + var descSortedList []string + var ascSortedList []string + assert.RequireGreaterOrEqual(t, len(serviceCategoriesDescResp), 2, "Need at least 2 Service Categories in Traffic Ops to test desc sort, found: %d", len(serviceCategoriesDescResp)) + // Get Service Categories in the default ascending order for comparison. + serviceCategoriesAscResp, _, err := TOSession.GetServiceCategories(client.RequestOptions{}) + assert.RequireNoError(t, err, "Unexpected error getting Service Categories with default sort order: %v - alerts: %+v", err, serviceCategoriesAscResp.Alerts) + // Verify the response match in length, i.e. equal amount of Service Categories. + assert.RequireEqual(t, len(serviceCategoriesAscResp.Response), len(serviceCategoriesDescResp), "Expected descending order response length: %v, to match ascending order response length %v", len(serviceCategoriesAscResp.Response), len(serviceCategoriesDescResp)) + // Insert Service Category names to the front of a new list, so they are now reversed to be in ascending order. + for _, serviceCategory := range serviceCategoriesDescResp { + descSortedList = append([]string{serviceCategory.Name}, descSortedList...) + } + // Insert Service Category names by appending to a new list, so they stay in ascending order. + for _, serviceCategory := range serviceCategoriesAscResp.Response { + ascSortedList = append(ascSortedList, serviceCategory.Name) + } + assert.Exactly(t, ascSortedList, descSortedList, "Service Categories responses are not equal after reversal: %v - %v", ascSortedList, descSortedList) + } +} + +func CreateTestServiceCategories(t *testing.T) { + for _, serviceCategory := range testData.ServiceCategories { + resp, _, err := TOSession.CreateServiceCategory(serviceCategory, client.RequestOptions{}) + assert.RequireNoError(t, err, "Could not create Service Category: %v - alerts: %+v", err, resp.Alerts) + } +} + +func DeleteTestServiceCategories(t *testing.T) { + serviceCategories, _, err := TOSession.GetServiceCategories(client.RequestOptions{}) + assert.NoError(t, err, "Cannot get Service Categories: %v - alerts: %+v", err, serviceCategories.Alerts) + + for _, serviceCategory := range serviceCategories.Response { + alerts, _, err := TOSession.DeleteServiceCategory(serviceCategory.Name, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error deleting Service Category '%s': %v - alerts: %+v", serviceCategory.Name, err, alerts.Alerts) + // Retrieve the Service Category to see if it got deleted + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", serviceCategory.Name) + getServiceCategory, _, err := TOSession.GetServiceCategories(opts) + assert.NoError(t, err, "Error getting Service Category '%s' after deletion: %v - alerts: %+v", serviceCategory.Name, err, getServiceCategory.Alerts) + assert.Equal(t, 0, len(getServiceCategory.Response), "Expected Service Category '%s' to be deleted, but it was found in Traffic Ops", serviceCategory.Name) + } +} diff --git a/traffic_ops/testing/api/v5/session_test.go b/traffic_ops/testing/api/v5/session_test.go new file mode 100644 index 0000000000..76e3626a45 --- /dev/null +++ b/traffic_ops/testing/api/v5/session_test.go @@ -0,0 +1,55 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "time" + + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" + + _ "github.com/lib/pq" +) + +var ( + TOSession *client.Session + NoAuthTOSession *client.Session +) + +func SetupSession(toReqTimeout time.Duration, toURL string, toUser string, toPass string) error { + var err error + + toReqTimeout = time.Second * time.Duration(Config.Default.Session.TimeoutInSecs) + NoAuthTOSession = client.NewNoAuthSession(toURL, true, "to-api-v5-client-tests", true, toReqTimeout) + TOSession, _, err = client.LoginWithAgent(toURL, toUser, toPass, true, "to-api-v5-client-tests", true, toReqTimeout) + return err +} + +func TeardownSession(toReqTimeout time.Duration, toURL string, toUser string, toPass string) error { + var err error + toReqTimeout = time.Second * time.Duration(Config.Default.Session.TimeoutInSecs) + TOSession, _, err = client.LogoutWithAgent(toURL, toUser, toPass, true, "to-api-v5-client-tests", true, toReqTimeout) + + return err +} + +func SwitchSession(toReqTimeout time.Duration, toURL string, toOldUser string, toOldPass string, toNewUser string, toNewPass string) error { + err := TeardownSession(toReqTimeout, toURL, toOldUser, toOldPass) + + // intentially skip errors so that we can continue with setup in the event of a 403 + + err = SetupSession(toReqTimeout, toURL, toNewUser, toNewPass) + return err +} diff --git a/traffic_ops/testing/api/v5/staticdnsentries_test.go b/traffic_ops/testing/api/v5/staticdnsentries_test.go new file mode 100644 index 0000000000..d5a06fa39f --- /dev/null +++ b/traffic_ops/testing/api/v5/staticdnsentries_test.go @@ -0,0 +1,280 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "sort" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestStaticDNSEntries(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServiceCategories, DeliveryServices, StaticDNSEntries}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID request": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateStaticDNSEntriesSort()), + }, + "OK when VALID HOST parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"host": {"host1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + validateStaticDNSEntriesFields(map[string]interface{}{"Host": "host1"})), + }, + }, + "PUT": { + "OK when VALID request": { + EndpointId: GetStaticDNSEntryID(t, "host2"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "address": "192.168.0.2", + "cachegroup": "cachegroup2", + "deliveryservice": "ds2", + "host": "host2", + "type": "A_RECORD", + "ttl": 10, + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateStaticDNSEntriesUpdateCreateFields("host2", map[string]interface{}{"Address": "192.168.0.2"})), + }, + "BAD REQUEST when INVALID IPV4 ADDRESS for A_RECORD": { + EndpointId: GetStaticDNSEntryID(t, "host2"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "address": "test.testdomain.net.", + "cachegroup": "cachegroup2", + "deliveryservice": "ds2", + "host": "host2", + "type": "A_RECORD", + "ttl": 10, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID DNS for CNAME_RECORD": { + EndpointId: GetStaticDNSEntryID(t, "host1"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "address": "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "cachegroup": "cachegroup1", + "deliveryservice": "ds1", + "host": "host1", + "type": "CNAME_RECORD", + "ttl": 0, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when MISSING TRAILING PERIOD for CNAME_RECORD": { + EndpointId: GetStaticDNSEntryID(t, "host1"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "address": "cdn.test.com", + "cachegroup": "cachegroup1", + "deliveryservice": "ds1", + "host": "host1", + "type": "CNAME_RECORD", + "ttl": 0, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID IPV6 ADDRESS for AAAA_RECORD": { + EndpointId: GetStaticDNSEntryID(t, "host3"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "address": "192.168.0.1", + "cachegroup": "cachegroup2", + "deliveryservice": "ds1", + "host": "host3", + "ttl": 10, + "type": "AAAA_RECORD", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "PRECONDITION FAILED when updating with IMS & IUS Headers": { + EndpointId: GetStaticDNSEntryID(t, "host3"), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}}, + RequestBody: map[string]interface{}{ + "address": "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "cachegroup": "cachegroup2", + "deliveryservice": "ds1", + "host": "host3", + "ttl": 10, + "type": "AAAA_RECORD", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + "PRECONDITION FAILED when updating with IFMATCH ETAG Header": { + EndpointId: GetStaticDNSEntryID(t, "host3"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "address": "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "cachegroup": "cachegroup2", + "deliveryservice": "ds1", + "host": "host3", + "ttl": 10, + "type": "AAAA_RECORD", + }, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + staticDNSEntry := tc.StaticDNSEntry{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &staticDNSEntry) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET", "GET AFTER CHANGES": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetStaticDNSEntries(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.CreateStaticDNSEntry(staticDNSEntry, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.UpdateStaticDNSEntry(testCase.EndpointId(), staticDNSEntry, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.DeleteStaticDNSEntry(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateStaticDNSEntriesFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Static DNS Entries response to not be nil.") + staticDNSEntriesResp := resp.([]tc.StaticDNSEntry) + for field, expected := range expectedResp { + for _, staticDNSEntry := range staticDNSEntriesResp { + switch field { + case "Address": + assert.Equal(t, expected, staticDNSEntry.Address, "Expected Address to be %v, but got %s", expected, staticDNSEntry.Address) + case "Host": + assert.Equal(t, expected, staticDNSEntry.Host, "Expected Host to be %v, but got %s", expected, staticDNSEntry.Host) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validateStaticDNSEntriesUpdateCreateFields(host string, expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("host", host) + staticDNSEntries, _, err := TOSession.GetStaticDNSEntries(opts) + assert.RequireNoError(t, err, "Error getting Static DNS Entries: %v - alerts: %+v", err, staticDNSEntries.Alerts) + assert.RequireEqual(t, 1, len(staticDNSEntries.Response), "Expected one Static DNS Entry returned Got: %d", len(staticDNSEntries.Response)) + validateStaticDNSEntriesFields(expectedResp)(t, toclientlib.ReqInf{}, staticDNSEntries.Response, tc.Alerts{}, nil) + } +} + +func validateStaticDNSEntriesSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Static DNS Entries response to not be nil.") + var staticDNSEntryHosts []string + staticDNSEntryResp := resp.([]tc.StaticDNSEntry) + for _, staticDNSEntry := range staticDNSEntryResp { + staticDNSEntryHosts = append(staticDNSEntryHosts, staticDNSEntry.Host) + } + assert.Equal(t, true, sort.StringsAreSorted(staticDNSEntryHosts), "List is not sorted by their hosts: %v", staticDNSEntryHosts) + } +} + +func GetStaticDNSEntryID(t *testing.T, host string) func() int { + return func() int { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("host", host) + staticDNSEntries, _, err := TOSession.GetStaticDNSEntries(opts) + assert.RequireNoError(t, err, "Get Static DNS Entries Request failed with error:", err) + assert.RequireEqual(t, 1, len(staticDNSEntries.Response), "Expected response object length 1, but got %d", len(staticDNSEntries.Response)) + return staticDNSEntries.Response[0].ID + } +} + +func CreateTestStaticDNSEntries(t *testing.T) { + for _, staticDNSEntry := range testData.StaticDNSEntries { + resp, _, err := TOSession.CreateStaticDNSEntry(staticDNSEntry, client.RequestOptions{}) + assert.RequireNoError(t, err, "Could not create Static DNS Entry: %v - alerts: %+v", err, resp.Alerts) + } +} + +func DeleteTestStaticDNSEntries(t *testing.T) { + staticDNSEntries, _, err := TOSession.GetStaticDNSEntries(client.RequestOptions{}) + assert.NoError(t, err, "Cannot get Static DNS Entries: %v - alerts: %+v", err, staticDNSEntries.Alerts) + + for _, staticDNSEntry := range staticDNSEntries.Response { + alerts, _, err := TOSession.DeleteStaticDNSEntry(staticDNSEntry.ID, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error deleting Static DNS Entry '%s' (#%d): %v - alerts: %+v", staticDNSEntry.Host, staticDNSEntry.ID, err, alerts.Alerts) + // Retrieve the Static DNS Entry to see if it got deleted + opts := client.NewRequestOptions() + opts.QueryParameters.Set("host", staticDNSEntry.Host) + getStaticDNSEntry, _, err := TOSession.GetStaticDNSEntries(opts) + assert.NoError(t, err, "Error getting Static DNS Entry '%s' after deletion: %v - alerts: %+v", staticDNSEntry.Host, err, getStaticDNSEntry.Alerts) + assert.Equal(t, 0, len(getStaticDNSEntry.Response), "Expected Static DNS Entry '%s' to be deleted, but it was found in Traffic Ops", staticDNSEntry.Host) + } +} diff --git a/traffic_ops/testing/api/v5/stats_summary_test.go b/traffic_ops/testing/api/v5/stats_summary_test.go new file mode 100644 index 0000000000..3e6964ca72 --- /dev/null +++ b/traffic_ops/testing/api/v5/stats_summary_test.go @@ -0,0 +1,169 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +var latestTime time.Time + +func TestStatsSummary(t *testing.T) { + + CreateTestStatsSummaries(t) + + methodTests := utils.V5TestCase{ + "GET": { + "OK when VALID request": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK when VALID STATNAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"statName": {"daily_bytesserved"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateStatsSummaryFields(map[string]interface{}{"StatName": "daily_bytesserved"})), + }, + "OK when VALID CDNNAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"cdnName": {"cdn1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(2), + validateStatsSummaryFields(map[string]interface{}{"CDNName": "cdn1"})), + }, + "OK when VALID DELIVERYSERVICENAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"deliveryServiceName": {"all"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(2), + validateStatsSummaryFields(map[string]interface{}{"DeliveryService": "all"})), + }, + "OK when VALID LASTSUMMARYDATE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"statName": {"daily_bytesserved"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateStatsSummaryLastUpdatedField(latestTime)), + }, + "EMPTY RESPONSE when NON-EXISTENT STATNAME": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"statName": {"bogus"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when NON-EXISTENT DELIVERYSERVICENAME": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"deliveryServiceName": {"bogus"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + "EMPTY RESPONSE when NON-EXISTENT CDNNAME": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"cdnName": {"bogus"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + statsSummary := tc.StatsSummary{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &statsSummary) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + if name == "OK when VALID LASTSUMMARYDATE parameter" { + resp, reqInf, err := testCase.ClientSession.GetSummaryStatsLastUpdated(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + } else { + resp, reqInf, err := testCase.ClientSession.GetSummaryStats(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.CreateSummaryStats(statsSummary, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } +} + +func validateStatsSummaryFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Stats Summary response to not be nil.") + statsSummaryResp := resp.([]tc.StatsSummary) + for field, expected := range expectedResp { + for _, statsSummary := range statsSummaryResp { + switch field { + case "CDNName": + assert.RequireNotNil(t, statsSummary.CDNName, "Expected CDNName to not be nil.") + assert.Equal(t, expected, *statsSummary.CDNName, "Expected CDNName to be %v, but got %s", expected, *statsSummary.CDNName) + case "DeliveryService": + assert.RequireNotNil(t, statsSummary.DeliveryService, "Expected DeliveryService to not be nil.") + assert.Equal(t, expected, *statsSummary.DeliveryService, "Expected DeliveryService to be %v, but got %s", expected, *statsSummary.DeliveryService) + case "StatName": + assert.RequireNotNil(t, statsSummary.StatName, "Expected StatName to not be nil.") + assert.Equal(t, expected, *statsSummary.StatName, "Expected StatName to be %v, but got %s", expected, *statsSummary.StatName) + case "SummaryTime": + assert.Equal(t, true, expected.(time.Time).Equal(statsSummary.SummaryTime), "Expected SummaryTime to be %v, but got %v", expected, statsSummary.SummaryTime) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validateStatsSummaryLastUpdatedField(expectedTime time.Time) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected StatsSummaryLastUpdated response to not be nil.") + statsSummaryLastUpdated := resp.(tc.StatsSummaryLastUpdated) + assert.RequireNotNil(t, statsSummaryLastUpdated.SummaryTime, "Expected SummaryTime to not be nil.") + assert.Equal(t, expectedTime, *statsSummaryLastUpdated.SummaryTime, "Expected SummaryTime to be %v, but got %v", expectedTime, *statsSummaryLastUpdated.SummaryTime) + } +} + +// Note that these stats summaries are never cleaned up, and will be left in +// the TODB after the tests complete +func CreateTestStatsSummaries(t *testing.T) { + for _, ss := range testData.StatsSummaries { + latestTime = time.Now().Truncate(time.Second) + ss.SummaryTime = latestTime + alerts, _, err := TOSession.CreateSummaryStats(ss, client.RequestOptions{}) + assert.RequireNoError(t, err, "Creating Stats Summary for stat '%s': %v - alerts: %+v", *ss.StatName, err, alerts.Alerts) + } +} diff --git a/traffic_ops/testing/api/v5/statuses_test.go b/traffic_ops/testing/api/v5/statuses_test.go new file mode 100644 index 0000000000..bf4d1afb47 --- /dev/null +++ b/traffic_ops/testing/api/v5/statuses_test.go @@ -0,0 +1,220 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "sort" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestStatuses(t *testing.T) { + WithObjs(t, []TCObj{Parameters, Statuses}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID request": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateStatusesSort()), + }, + "OK when VALID NAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"CCR_IGNORE"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateStatusesFields(map[string]interface{}{"Name": "CCR_IGNORE"})), + }, + }, + "PUT": { + "OK when VALID request": { + EndpointId: GetStatusID(t, "TEST_NULL_DESCRIPTION"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "description": "new description", + "name": "TEST_NULL_DESCRIPTION", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateStatusesUpdateCreateFields("TEST_NULL_DESCRIPTION", map[string]interface{}{"Description": "new description"})), + }, + "PRECONDITION FAILED when updating with IMS & IUS Headers": { + EndpointId: GetStatusID(t, "TEST_NULL_DESCRIPTION"), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}}, + RequestBody: map[string]interface{}{ + "description": "new description", + "name": "TEST_NULL_DESCRIPTION", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + "PRECONDITION FAILED when updating with IFMATCH ETAG Header": { + EndpointId: GetStatusID(t, "TEST_NULL_DESCRIPTION"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "description": "new description", + "name": "TEST_NULL_DESCRIPTION", + }, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + }, + "GET AFTER CHANGES": { + "OK when CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {currentTimeRFC}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + status := tc.Status{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &status) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET", "GET AFTER CHANGES": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetStatuses(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.UpdateStatus(testCase.EndpointId(), status, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.DeleteStatus(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateStatusesFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Status response to not be nil.") + statusResp := resp.([]tc.Status) + for field, expected := range expectedResp { + for _, status := range statusResp { + switch field { + case "Description": + assert.Equal(t, expected, status.Description, "Expected Description to be %v, but got %s", expected, status.Description) + case "Name": + assert.Equal(t, expected, status.Name, "Expected Name to be %v, but got %s", expected, status.Name) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validateStatusesUpdateCreateFields(name string, expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", name) + statuses, _, err := TOSession.GetStatuses(opts) + assert.RequireNoError(t, err, "Error getting Statuses: %v - alerts: %+v", err, statuses.Alerts) + assert.RequireEqual(t, 1, len(statuses.Response), "Expected one Status returned Got: %d", len(statuses.Response)) + validateStatusesFields(expectedResp)(t, toclientlib.ReqInf{}, statuses.Response, tc.Alerts{}, nil) + } +} + +func validateStatusesSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Status response to not be nil.") + var statusNames []string + statusResp := resp.([]tc.Status) + for _, status := range statusResp { + statusNames = append(statusNames, status.Name) + } + assert.Equal(t, true, sort.StringsAreSorted(statusNames), "List is not sorted by their names: %v", statusNames) + } +} + +func GetStatusID(t *testing.T, name string) func() int { + return func() int { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", name) + statusResp, _, err := TOSession.GetStatuses(opts) + assert.RequireNoError(t, err, "Get Statuses Request failed with error:", err) + assert.RequireEqual(t, 1, len(statusResp.Response), "Expected response object length 1, but got %d", len(statusResp.Response)) + return statusResp.Response[0].ID + } +} + +func CreateTestStatuses(t *testing.T) { + for _, status := range testData.Statuses { + resp, _, err := TOSession.CreateStatus(status, client.RequestOptions{}) + assert.RequireNoError(t, err, "Could not create Status: %v - alerts: %+v", err, resp.Alerts) + } +} + +func DeleteTestStatuses(t *testing.T) { + opts := client.NewRequestOptions() + for _, status := range testData.Statuses { + assert.RequireNotNil(t, status.Name, "Cannot get test statuses: test data statuses must have names") + // Retrieve the Status by name, so we can get the id for the Update + opts.QueryParameters.Set("name", *status.Name) + resp, _, err := TOSession.GetStatuses(opts) + assert.RequireNoError(t, err, "Cannot get Statuses filtered by name '%s': %v - alerts: %+v", *status.Name, err, resp.Alerts) + assert.RequireEqual(t, 1, len(resp.Response), "Expected 1 status returned. Got: %d", len(resp.Response)) + respStatus := resp.Response[0] + + delResp, _, err := TOSession.DeleteStatus(respStatus.ID, client.RequestOptions{}) + assert.NoError(t, err, "Cannot delete Status: %v - alerts: %+v", err, delResp.Alerts) + + // Retrieve the Status to see if it got deleted + resp, _, err = TOSession.GetStatuses(opts) + assert.NoError(t, err, "Unexpected error getting Statuses filtered by name after deletion: %v - alerts: %+v", err, resp.Alerts) + assert.Equal(t, 0, len(resp.Response), "Expected Status '%s' to be deleted, but it was found in Traffic Ops", *status.Name) + } +} diff --git a/traffic_ops/testing/api/v5/steering_test.go b/traffic_ops/testing/api/v5/steering_test.go new file mode 100644 index 0000000000..51516f84fc --- /dev/null +++ b/traffic_ops/testing/api/v5/steering_test.go @@ -0,0 +1,88 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/http" + "testing" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +func TestSteering(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServiceCategories, DeliveryServices, Users, SteeringTargets}, func() { + methodTests := utils.V5TestCase{ + "GET": { + "OK when VALID request": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(2), + validateSteeringFields(map[string]interface{}{"TargetsLength": 1, "TargetsOrder": int32(0), + "TargetsGeoOrderPtr": (*int)(nil), "TargetsLongitudePtr": (*float64)(nil), "TargetsLatitudePtr": (*float64)(nil), "TargetsWeight": int32(42)})), + }, + }, + } + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.Steering(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateSteeringFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Steering response to not be nil.") + steeringResp := resp.([]tc.Steering) + for field, expected := range expectedResp { + for _, steering := range steeringResp { + switch field { + case "TargetsLength": + assert.Equal(t, expected, len(steering.Targets), "Expected Targets Length to be %v, but got %d", expected, len(steering.Targets)) + case "TargetsOrder": + assert.RequireEqual(t, 1, len(steering.Targets), "Expected Targets Length to be %d, but got %d", 1, len(steering.Targets)) + assert.Equal(t, expected, steering.Targets[0].Order, "Expected Targets Order to be %v, but got %d", expected, steering.Targets[0].Order) + case "TargetsGeoOrderPtr": + assert.RequireEqual(t, 1, len(steering.Targets), "Expected Targets Length to be %d, but got %d", 1, len(steering.Targets)) + assert.Equal(t, expected, steering.Targets[0].GeoOrder, "Expected Targets GeoOrder to be %v, but got %v", nil, steering.Targets[0].GeoOrder) + case "TargetsLongitudePtr": + assert.RequireEqual(t, 1, len(steering.Targets), "Expected Targets Length to be %d, but got %d", 1, len(steering.Targets)) + assert.Equal(t, expected, steering.Targets[0].Longitude, "Expected Targets Longitude to be %v, but got %v", nil, steering.Targets[0].Longitude) + case "TargetsLatitudePtr": + assert.RequireEqual(t, 1, len(steering.Targets), "Expected Targets Length to be %d, but got %d", 1, len(steering.Targets)) + assert.Equal(t, expected, steering.Targets[0].Latitude, "Expected Targets Latitude to be %v, but got %v", nil, steering.Targets[0].Latitude) + case "TargetsWeight": + assert.RequireEqual(t, 1, len(steering.Targets), "Expected Targets Length to be %d, but got %d", 1, len(steering.Targets)) + assert.Equal(t, expected, steering.Targets[0].Weight, "Expected Targets Weight to be %v, but got %v", expected, steering.Targets[0].Weight) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } + } +} diff --git a/traffic_ops/testing/api/v5/steeringtargets_test.go b/traffic_ops/testing/api/v5/steeringtargets_test.go new file mode 100644 index 0000000000..3bcb0781c3 --- /dev/null +++ b/traffic_ops/testing/api/v5/steeringtargets_test.go @@ -0,0 +1,252 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/lib/go-util" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestSteeringTargets(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServiceCategories, DeliveryServices, Users, SteeringTargets}, func() { + + steeringUserSession := utils.CreateV5Session(t, Config.TrafficOps.URL, "steering", "pa$$word", Config.Default.Session.TimeoutInSecs) + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + EndpointId: GetDeliveryServiceId(t, "ds1"), + ClientSession: steeringUserSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID request": { + EndpointId: GetDeliveryServiceId(t, "ds1"), + ClientSession: steeringUserSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + validateSteeringTargetFields(map[string]interface{}{"DeliveryService": "ds1", "DeliveryServiceID": uint64(GetDeliveryServiceId(t, "ds1")()), + "Target": "ds2", "TargetID": uint64(GetDeliveryServiceId(t, "ds2")()), "Type": "STEERING_WEIGHT", "TypeID": GetTypeID(t, "STEERING_WEIGHT")(), "Value": util.JSONIntStr(42)})), + }, + }, + "PUT": { + "OK when VALID request": { + ClientSession: steeringUserSession, + RequestBody: map[string]interface{}{ + "deliveryServiceId": GetDeliveryServiceId(t, "ds3")(), + "targetId": GetDeliveryServiceId(t, "ds4")(), + "value": -12345, + "typeId": GetTypeID(t, "STEERING_WEIGHT")(), + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateSteeringTargetUpdateCreateFields(GetDeliveryServiceId(t, "ds3")(), + map[string]interface{}{"DeliveryService": "ds3", "DeliveryServiceID": uint64(GetDeliveryServiceId(t, "ds3")()), + "Target": "ds4", "TargetID": uint64(GetDeliveryServiceId(t, "ds4")()), "Type": "STEERING_WEIGHT", + "TypeID": GetTypeID(t, "STEERING_WEIGHT")(), "Value": util.JSONIntStr(-12345)})), + }, + "PRECONDITION FAILED when updating with IMS & IUS Headers": { + ClientSession: steeringUserSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}}, + RequestBody: map[string]interface{}{ + "deliveryServiceId": GetDeliveryServiceId(t, "ds3")(), + "targetId": GetDeliveryServiceId(t, "ds4")(), + "value": -12345, + "type": "STEERING_WEIGHT", + "typeId": GetTypeID(t, "STEERING_WEIGHT")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + "PRECONDITION FAILED when updating with IFMATCH ETAG Header": { + ClientSession: steeringUserSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}}, + RequestBody: map[string]interface{}{ + "deliveryServiceId": GetDeliveryServiceId(t, "ds3")(), + "targetId": GetDeliveryServiceId(t, "ds4")(), + "value": -12345, + "type": "STEERING_WEIGHT", + "typeId": GetTypeID(t, "STEERING_WEIGHT")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + }, + "GET AFTER CHANGES": { + "OK when CHANGES made": { + EndpointId: GetDeliveryServiceId(t, "ds1"), + ClientSession: steeringUserSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {currentTimeRFC}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + steeringTarget := tc.SteeringTargetNullable{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &steeringTarget) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET", "GET AFTER CHANGES": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetSteeringTargets(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.CreateSteeringTarget(steeringTarget, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.UpdateSteeringTarget(steeringTarget, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + var targetID int + if testCase.RequestBody != nil { + if val, ok := testCase.RequestBody["targetID"]; ok { + targetID = val.(int) + } + } + alerts, reqInf, err := testCase.ClientSession.DeleteSteeringTarget(testCase.EndpointId(), targetID, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) + +} + +func validateSteeringTargetFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Steering Targets response to not be nil.") + steeringTargetsResp := resp.([]tc.SteeringTargetNullable) + for field, expected := range expectedResp { + for _, steeringTarget := range steeringTargetsResp { + switch field { + case "DeliveryService": + assert.RequireNotNil(t, steeringTarget.DeliveryService, "Expected DeliveryService to not be nil.") + assert.Equal(t, expected, string(*steeringTarget.DeliveryService), "Expected DeliveryService to be %v, but got %s", expected, *steeringTarget.DeliveryService) + case "DeliveryServiceID": + assert.RequireNotNil(t, steeringTarget.DeliveryServiceID, "Expected DeliveryServiceID to not be nil.") + assert.Equal(t, expected, *steeringTarget.DeliveryServiceID, "Expected DeliveryServiceID to be %v, but got %s", expected, *steeringTarget.DeliveryServiceID) + case "Target": + assert.RequireNotNil(t, steeringTarget.Target, "Expected Target to not be nil.") + assert.Equal(t, expected, string(*steeringTarget.Target), "Expected Target to be %v, but got %s", expected, *steeringTarget.Target) + case "TargetID": + assert.RequireNotNil(t, steeringTarget.TargetID, "Expected TargetID to not be nil.") + assert.Equal(t, expected, *steeringTarget.TargetID, "Expected TargetID to be %v, but got %s", expected, *steeringTarget.TargetID) + case "Type": + assert.RequireNotNil(t, steeringTarget.Type, "Expected Type to not be nil.") + assert.Equal(t, expected, *steeringTarget.Type, "Expected Type to be %v, but got %s", expected, *steeringTarget.Type) + case "TypeID": + assert.RequireNotNil(t, steeringTarget.Type, "Expected TypeID to not be nil.") + assert.Equal(t, expected, *steeringTarget.TypeID, "Expected TypeID to be %v, but got %s", expected, *steeringTarget.TypeID) + case "Value": + assert.RequireNotNil(t, steeringTarget.Value, "Expected Value to not be nil.") + assert.Equal(t, expected, *steeringTarget.Value, "Expected Value to be %v, but got %s", expected, *steeringTarget.Value) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validateSteeringTargetUpdateCreateFields(dsId int, expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + steeringTargets, _, err := TOSession.GetSteeringTargets(dsId, client.RequestOptions{}) + assert.RequireNoError(t, err, "Error getting Steering Targets: %v - alerts: %+v", err, steeringTargets.Alerts) + assert.RequireEqual(t, 1, len(steeringTargets.Response), "Expected one Steering Target returned Got: %d", len(steeringTargets.Response)) + validateSteeringTargetFields(expectedResp)(t, toclientlib.ReqInf{}, steeringTargets.Response, tc.Alerts{}, nil) + } +} + +func CreateTestSteeringTargets(t *testing.T) { + steeringUserSession := utils.CreateV5Session(t, Config.TrafficOps.URL, "steering", "pa$$word", Config.Default.Session.TimeoutInSecs) + for _, st := range testData.SteeringTargets { + st.TypeID = util.IntPtr(GetTypeID(t, *st.Type)()) + st.DeliveryServiceID = util.UInt64Ptr(uint64(GetDeliveryServiceId(t, string(*st.DeliveryService))())) + st.TargetID = util.UInt64Ptr(uint64(GetDeliveryServiceId(t, string(*st.Target))())) + resp, _, err := steeringUserSession.CreateSteeringTarget(st, client.RequestOptions{}) + assert.RequireNoError(t, err, "Creating steering target: %v - alerts: %+v", err, resp.Alerts) + } +} + +func DeleteTestSteeringTargets(t *testing.T) { + steeringUserSession := utils.CreateV5Session(t, Config.TrafficOps.URL, "steering", "pa$$word", Config.Default.Session.TimeoutInSecs) + dsIDs := []uint64{} + for _, st := range testData.SteeringTargets { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("xmlId", string(*st.DeliveryService)) + respDS, _, err := steeringUserSession.GetDeliveryServices(opts) + assert.RequireNoError(t, err, "Deleting steering target: getting ds: %v - alerts: %+v", err, respDS.Alerts) + assert.RequireEqual(t, 1, len(respDS.Response), "Deleting steering target: getting ds: expected 1 delivery service") + assert.RequireNotNil(t, respDS.Response[0].ID, "Deleting steering target: getting ds: nil ID returned") + + dsID := uint64(*respDS.Response[0].ID) + st.DeliveryServiceID = &dsID + dsIDs = append(dsIDs, dsID) + + opts.QueryParameters.Set("xmlId", string(*st.Target)) + respTarget, _, err := steeringUserSession.GetDeliveryServices(opts) + assert.RequireNoError(t, err, "Deleting steering target: getting target ds: %v - alerts: %+v", err, respTarget.Alerts) + assert.RequireEqual(t, 1, len(respTarget.Response), "Deleting steering target: getting target ds: expected 1 delivery service") + assert.RequireNotNil(t, respTarget.Response[0].ID, "Deleting steering target: getting target ds: not found") + + targetID := uint64(*respTarget.Response[0].ID) + st.TargetID = &targetID + + resp, _, err := steeringUserSession.DeleteSteeringTarget(int(*st.DeliveryServiceID), int(*st.TargetID), client.RequestOptions{}) + assert.NoError(t, err, "Deleting steering target: deleting: %v - alerts: %+v", err, resp.Alerts) + } + + for _, dsID := range dsIDs { + sts, _, err := steeringUserSession.GetSteeringTargets(int(dsID), client.RequestOptions{}) + assert.NoError(t, err, "deleting steering targets: getting steering target: %v - alerts: %+v", err, sts.Alerts) + assert.Equal(t, 0, len(sts.Response), "Deleting steering targets: after delete, getting steering target: expected 0 actual %d", len(sts.Response)) + } +} diff --git a/traffic_ops/testing/api/v5/tc-fixtures.json b/traffic_ops/testing/api/v5/tc-fixtures.json new file mode 100644 index 0000000000..8f34bb26fc --- /dev/null +++ b/traffic_ops/testing/api/v5/tc-fixtures.json @@ -0,0 +1,6406 @@ +{ + "asns": [ + { + "asn": 8888, + "cachegroupName": "originCachegroup" + }, + { + "asn": 9999, + "cachegroupName": "multiOriginCachegroup" + } + ], + "cachegroups": [ + { + "latitude": 0, + "longitude": 0, + "name": "originCachegroup", + "shortName": "og1", + "typeName": "ORG_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "multiOriginCachegroup", + "shortName": "mog1", + "typeName": "ORG_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "parentCachegroup", + "shortName": "pg1", + "typeName": "MID_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "parentCachegroup2", + "shortName": "pg2", + "typeName": "MID_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "parentCachegroup3", + "shortName": "pg3", + "typeName": "MID_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "secondaryCachegroup", + "shortName": "sg1", + "typeName": "MID_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "cachegroup1", + "parentCachegroupName": "parentCachegroup", + "secondaryParentCachegroupName": "secondaryCachegroup", + "shortName": "cg1", + "localizationMethods": [ + "CZ", + "DEEP_CZ", + "GEO" + ], + "typeName": "EDGE_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "fallback1", + "parentCachegroupName": "parentCachegroup", + "secondaryParentCachegroupName": "secondaryCachegroup", + "shortName": "fb1", + "localizationMethods": [ + "CZ", + "DEEP_CZ", + "GEO" + ], + "typeName": "EDGE_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "fallback2", + "parentCachegroupName": "parentCachegroup", + "secondaryParentCachegroupName": "secondaryCachegroup", + "shortName": "fb2", + "localizationMethods": [ + "CZ", + "DEEP_CZ", + "GEO" + ], + "typeName": "EDGE_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "fallback3", + "parentCachegroupName": "parentCachegroup", + "secondaryParentCachegroupName": "secondaryCachegroup", + "shortName": "fb3", + "localizationMethods": [ + "CZ", + "DEEP_CZ", + "GEO" + ], + "typeName": "EDGE_LOC" + }, + { + "latitude": 24.1234, + "longitude": -121.1234, + "name": "cachegroup2", + "parentCachegroupName": "secondaryCachegroup", + "secondaryParentCachegroupName": "parentCachegroup", + "shortName": "cg2", + "typeName": "EDGE_LOC", + "fallbacks": [ + "fallback1", + "fallback2", + "fallback3" + ] + }, + { + "latitude": 0, + "longitude": 0, + "name": "cachegroup4", + "parentCachegroupName": "parentCachegroup3", + "shortName": "cg4", + "localizationMethods": [ + "CZ", + "DEEP_CZ", + "GEO" + ], + "typeName": "EDGE_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "cachegroup3", + "parentCachegroupName": "parentCachegroup", + "secondaryParentCachegroupName": "secondaryCachegroup", + "shortName": "cg3", + "typeName": "EDGE_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "edge-parent1", + "shortName": "ep1", + "typeName": "EDGE_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "has-edge-parent1", + "parentCachegroupName": "edge-parent1", + "shortName": "hep1", + "typeName": "EDGE_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "topology-edge-cg-01", + "shortName": "te1", + "typeName": "EDGE_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "topology-edge-cg-02", + "shortName": "te2", + "typeName": "EDGE_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "topology-mid-cg-01", + "shortName": "tm1", + "typeName": "MID_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "topology-mid-cg-02", + "shortName": "tm2", + "typeName": "MID_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "topology-mid-cg-03", + "shortName": "tm3", + "typeName": "MID_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "topology-mid-cg-04", + "shortName": "tm4", + "typeName": "MID_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "topology-mid-cg-05", + "shortName": "tm5", + "typeName": "MID_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "topology-mid-cg-06", + "shortName": "tm6", + "typeName": "MID_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "topology-mid-cg-07", + "shortName": "tm7", + "typeName": "MID_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "dtrc1", + "shortName": "dtrc1", + "typeName": "MID_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "dtrc2", + "shortName": "dtrc2", + "typeName": "EDGE_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "dtrc3", + "shortName": "dtrc3", + "typeName": "EDGE_LOC" + }, + { + "latitude": 0, + "longitude": 0, + "name": "cdn1-only", + "shortName": "cdn1-only", + "typeName": "EDGE_LOC" + }, + { + "name": "nullLatLongCG", + "shortName": "null-ll", + "typeName": "EDGE_LOC" + }, + { + "name": "noServers", + "shortName": "noServers", + "typeName": "EDGE_LOC" + } + ], + "cdns": [ + { + "dnssecEnabled": false, + "domainName": "test.cdn1.net", + "name": "cdn1" + }, + { + "dnssecEnabled": false, + "domainName": "test.cdn2.net", + "name": "cdn2" + }, + { + "dnssecEnabled": false, + "domainName": "test.cdn3.net", + "name": "cdn3" + }, + { + "dnssecEnabled": false, + "domainName": "test.cdn4.net", + "name": "cdn4" + }, + { + "dnssecEnabled": false, + "domainName": "test.bar.net", + "name": "bar" + }, + { + "dnssecEnabled": false, + "domainName": "test.delete.net", + "name": "cdndelete" + } + ], + "cdnlocks": [ + { + "userName": "opslockuser", + "cdn": "cdn1", + "message": "test lock", + "soft": true + }, + { + "userName": "opslockuser", + "cdn": "cdn2", + "message": "test lock for updates", + "soft": false + }, + { + "userName": "opslockuser", + "cdn": "cdn4", + "message": "test lock", + "soft": true + }, + { + "userName": "opslockuser", + "cdn": "cdndelete", + "message": "test lock for deleting cdn action", + "soft": false + } + ], + "deliveryServiceRequestComments": [ + { + "value": "this is comment one", + "xmlId": "test-ds1" + }, + { + "value": "this is comment two", + "xmlId": "test-ds1" + }, + { + "value": "this is comment three", + "xmlId": "test-ds1" + }, + { + "value": "this is comment four", + "xmlId": "test-ds1" + } + ], + "deliveryServiceRequests": [ + { + "changeType": "create", + "requested": { + "active": true, + "cdnName": "cdn1", + "ccrDnsTtl": 30, + "deepCachingType": "NEVER", + "displayName": "Good Kabletown CDN", + "dscp": 1, + "geoLimit": 1, + "geoProvider": 1, + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": true, + "longDesc": "long desc", + "missLat": 0.0, + "missLong": 0.0, + "multiSiteOrigin": false, + "orgServerFqdn": "http://example.test", + "protocol": 0, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regionalGeoBlocking": true, + "routingName": "goodroute", + "tenant": "tenant1", + "type": "HTTP", + "xmlId": "test-ds1" + }, + "status": "draft" + }, + { + "changeType": "create", + "requested": { + "active": true, + "cdnName": "cdn1", + "ccrDnsTtl": 30, + "deepCachingType": "NEVER", + "displayName": "Good Kabletown CDN", + "dscp": 1, + "geoLimit": 1, + "geoProvider": 1, + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": true, + "longDesc": "long desc", + "missLat": 0.0, + "missLong": 0.0, + "multiSiteOrigin": false, + "orgServerFqdn": "http://example.test", + "protocol": 0, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regionalGeoBlocking": true, + "routingName": "goodroute", + "tenant": "tenant1", + "type": "HTTP", + "xmlId": "test-deletion" + }, + "status": "draft" + } + ], + "deliveryServices": [ + { + "active": true, + "cdnName": "cdn1", + "ccrDnsTtl": 3600, + "checkPath": "", + "consistentHashQueryParams": [], + "deepCachingType": "NEVER", + "displayName": "ds1DisplayName", + "dnsBypassCname": null, + "dnsBypassIp": "", + "dnsBypassIp6": "", + "dnsBypassTtl": 30, + "dscp": 40, + "edgeHeaderRewrite": "edgeHeader1\nedgeHeader2", + "exampleURLs": [ + "http://ccr.ds1.example.net", + "https://ccr.ds1.example.net" + ], + "fqPacingRate": 0, + "geoLimit": 0, + "geoLimitCountries": "", + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": 0, + "globalMaxTps": 0, + "httpBypassFqdn": "", + "infoUrl": "TBD", + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": false, + "longDesc": "d s 1", + "longDesc1": "ds1", + "longDesc2": "ds1", + "matchList": [ + { + "pattern": ".*\\.ds1\\..*", + "setNumber": 0, + "type": "HOST_REGEXP" + } + ], + "maxDnsAnswers": 0, + "midHeaderRewrite": "midHeader1\nmidHeader2", + "missLat": 41.881944, + "missLong": -87.627778, + "multiSiteOrigin": false, + "orgServerFqdn": "http://origin.example.net", + "originShield": null, + "profileDescription": null, + "profileName": "ATS_EDGE_TIER_CACHE", + "protocol": 2, + "qstringIgnore": 1, + "rangeRequestHandling": 0, + "regexRemap": "rr1\nrr2", + "regionalGeoBlocking": false, + "remapText": "@plugin=tslua.so @pparam=/opt/trafficserver/etc/trafficserver/remapPlugin1.lua", + "routingName": "ccr-ds1", + "signed": false, + "signingAlgorithm": "url_sig", + "sslKeyVersion": 2, + "tenant": "tenant1", + "tenantName": "tenant1", + "type": "HTTP", + "xmlId": "ds1", + "anonymousBlockingEnabled": true, + "maxRequestHeaderBytes": 131072 + }, + { + "active": true, + "cdnName": "cdn1", + "ccrDnsTtl": 3600, + "checkPath": "", + "consistentHashQueryParams": [ + "fmt", + "limit", + "somethingelse" + ], + "deepCachingType": "NEVER", + "displayName": "d s 1", + "dnsBypassCname": null, + "dnsBypassIp": "", + "dnsBypassIp6": "", + "dnsBypassTtl": 30, + "dscp": 40, + "edgeHeaderRewrite": "edgeRewrite1\nedgeHeader2", + "exampleURLs": [ + "http://ccr.ds2.example.net", + "https://ccr.ds2x.example.net" + ], + "fqPacingRate": 0, + "geoLimit": 0, + "geoLimitCountries": "", + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": 0, + "globalMaxTps": 0, + "httpBypassFqdn": "", + "infoUrl": "TBD", + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": false, + "longDesc": "d s 1", + "longDesc1": "ds2", + "longDesc2": "ds2", + "matchList": [ + { + "pattern": ".*\\.ds2\\..*", + "setNumber": 0, + "type": "HOST_REGEXP" + } + ], + "maxDnsAnswers": 0, + "maxOriginConnections": -1, + "midHeaderRewrite": "midHeader1\nmidHeader2", + "missLat": 41.881944, + "missLong": -87.627778, + "multiSiteOrigin": false, + "orgServerFqdn": "http://origin.ds2.example.net", + "originShield": null, + "profileDescription": null, + "profileName": null, + "protocol": 2, + "qstringIgnore": 1, + "rangeRequestHandling": 0, + "regexRemap": "rr1\nrr2", + "regionalGeoBlocking": false, + "remapText": "@plugin=tslua.so @pparam=/opt/trafficserver/etc/trafficserver/ds2plugin.lua", + "routingName": "ccr-ds2", + "signed": false, + "signingAlgorithm": "url_sig", + "sslKeyVersion": 2, + "tenant": "tenant2", + "tenantName": "tenant2", + "type": "HTTP_LIVE", + "xmlId": "ds2", + "anonymousBlockingEnabled": true, + "maxRequestHeaderBytes": 131072 + }, + { + "active": true, + "cdnName": "cdn1", + "ccrDnsTtl": 3600, + "checkPath": "", + "consistentHashQueryParams": null, + "deepCachingType": "NEVER", + "displayName": "d s 1", + "dnsBypassCname": null, + "dnsBypassIp": "", + "dnsBypassIp6": "", + "dnsBypassTtl": 30, + "dscp": 40, + "edgeHeaderRewrite": "edgeRewrite1\nedgeHeader2", + "exampleURLs": [ + "http://ccr.ds3.example.net", + "https://ccr.ds3x.example.net" + ], + "fqPacingRate": 0, + "geoLimit": 0, + "geoLimitCountries": "", + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": 0, + "globalMaxTps": 0, + "httpBypassFqdn": "", + "infoUrl": "TBD", + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": false, + "longDesc": "d s 3", + "longDesc1": "ds3", + "longDesc2": "ds3", + "matchList": [ + { + "pattern": ".*\\.ds3\\..*", + "setNumber": 0, + "type": "HOST_REGEXP" + } + ], + "maxDnsAnswers": 0, + "maxOriginConnections": 0, + "midHeaderRewrite": "midHeader1\nmidHeader2", + "missLat": 41.881944, + "missLong": -87.627778, + "multiSiteOrigin": false, + "orgServerFqdn": "http://origin.ds3.example.net", + "originShield": null, + "profileDescription": null, + "profileName": null, + "protocol": 2, + "qstringIgnore": 1, + "rangeRequestHandling": 0, + "regexRemap": "rr1\nrr2", + "regionalGeoBlocking": false, + "remapText": "@plugin=tslua.so @pparam=/opt/trafficserver/etc/trafficserver/ds3plugin.lua", + "routingName": "ccr-ds3", + "signed": false, + "signingAlgorithm": "url_sig", + "sslKeyVersion": 2, + "tenant": "tenant3", + "tenantName": "tenant3", + "type": "HTTP_LIVE", + "xmlId": "ds3", + "anonymousBlockingEnabled": true, + "maxRequestHeaderBytes": 131072 + }, + { + "active": true, + "cdnName": "cdn1", + "ccrDnsTtl": 3600, + "checkPath": "", + "deepCachingType": "NEVER", + "displayName": "anymap-ds", + "dnsBypassCname": null, + "dnsBypassIp": "", + "dnsBypassIp6": "", + "dnsBypassTtl": 30, + "dscp": 40, + "edgeHeaderRewrite": "", + "exampleURLs": [], + "fqPacingRate": 0, + "geoLimit": 0, + "geoLimitCountries": "", + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": 0, + "globalMaxTps": 0, + "httpBypassFqdn": "", + "infoUrl": "", + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": false, + "longDesc": "", + "longDesc1": "", + "longDesc2": "", + "matchList": [], + "maxDnsAnswers": 0, + "maxOriginConnections": 1, + "midHeaderRewrite": "", + "missLat": 41.881944, + "missLong": -87.627778, + "multiSiteOrigin": false, + "orgServerFqdn": "http://example.com", + "originShield": null, + "profileDescription": null, + "profileName": null, + "protocol": 2, + "qstringIgnore": 1, + "rangeRequestHandling": 0, + "regexRemap": "", + "regionalGeoBlocking": false, + "remapText": "map some raw remap text", + "routingName": "", + "signed": false, + "signingAlgorithm": "url_sig", + "sslKeyVersion": 2, + "tenant": "tenant3", + "tenantName": "tenant3", + "type": "ANY_MAP", + "xmlId": "anymap-ds", + "anonymousBlockingEnabled": true, + "maxRequestHeaderBytes": 131072 + }, + { + "active": true, + "cdnName": "cdn1", + "ccrDnsTtl": 3600, + "checkPath": "", + "consistentHashQueryParams": [ + "a", + "b", + "c" + ], + "consistentHashRegex": "foo", + "deepCachingType": "ALWAYS", + "displayName": "ds-test-minor-versions", + "dnsBypassCname": null, + "dnsBypassIp": "", + "dnsBypassIp6": "", + "dnsBypassTtl": 30, + "dscp": 40, + "edgeHeaderRewrite": "edgeRewrite1\nedgeHeader2", + "fqPacingRate": 42, + "geoLimit": 0, + "geoLimitCountries": "", + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": 0, + "globalMaxTps": 0, + "httpBypassFqdn": "", + "infoUrl": "TBD", + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": false, + "longDesc": "d s 1", + "longDesc1": "ds1", + "longDesc2": "ds1", + "maxDnsAnswers": 0, + "maxOriginConnections": 1000, + "midHeaderRewrite": "midHeader1\nmidHeader2", + "missLat": 41.881944, + "missLong": -87.627778, + "multiSiteOrigin": false, + "orgServerFqdn": "http://origin-test-minor-version.example.net", + "originShield": null, + "profileDescription": null, + "profileName": null, + "protocol": 2, + "qstringIgnore": 1, + "rangeRequestHandling": 0, + "regexRemap": "rr1\nrr2", + "regionalGeoBlocking": false, + "remapText": "@plugin=tslua.so @pparam=/opt/trafficserver/etc/trafficserver/remapPlugin1.lua", + "routingName": "cdn", + "signed": true, + "signingAlgorithm": "url_sig", + "sslKeyVersion": 2, + "tenantId": 1, + "tenant": "root", + "trRequestHeaders": "X-Foo\nX-Bar", + "trResponseHeaders": "Access-Control-Allow-Origin: *\nContent-Type: text/html; charset=utf-8", + "type": "HTTP_LIVE", + "xmlId": "ds-test-minor-versions", + "anonymousBlockingEnabled": true, + "maxRequestHeaderBytes": 131072 + }, + { + "active": true, + "cdnName": "cdn1", + "ccrDnsTtl": 3600, + "checkPath": "", + "consistentHashQueryParams": [], + "deepCachingType": "NEVER", + "displayName": "ds1DisplayName", + "dnsBypassCname": null, + "dnsBypassIp": "", + "dnsBypassIp6": "", + "dnsBypassTtl": 30, + "dscp": 40, + "edgeHeaderRewrite": "edgeRewrite1\nedgeHeader2", + "exampleURLs": [ + "http://ccr.msods1.example.net", + "https://ccr.msods1.example.net" + ], + "fqPacingRate": 0, + "geoLimit": 0, + "geoLimitCountries": "", + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": 0, + "globalMaxTps": 0, + "httpBypassFqdn": "", + "infoUrl": "TBD", + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": false, + "longDesc": "mso DS 1", + "longDesc1": "msods1", + "longDesc2": "msods1", + "matchList": [ + { + "pattern": ".*\\.msods1\\..*", + "setNumber": 0, + "type": "HOST_REGEXP" + } + ], + "maxDnsAnswers": 0, + "midHeaderRewrite": "midHeader1\nmidHeader2", + "missLat": 41.881944, + "missLong": -87.627778, + "multiSiteOrigin": true, + "orgServerFqdn": "http://origin.example.net", + "originShield": null, + "profileDescription": null, + "profileName": null, + "protocol": 2, + "qstringIgnore": 1, + "rangeRequestHandling": 0, + "regexRemap": "rr1\nrr2", + "regionalGeoBlocking": false, + "remapText": "@plugin=tslua.so @pparam=/opt/trafficserver/etc/trafficserver/remapPlugin1.lua", + "routingName": "ccr-msods1", + "signed": false, + "signingAlgorithm": "url_sig", + "sslKeyVersion": 2, + "tenant": "tenant1", + "tenantName": "tenant1", + "type": "HTTP_LIVE", + "xmlId": "msods1", + "anonymousBlockingEnabled": true, + "maxRequestHeaderBytes": 131072 + }, + { + "active": true, + "cdnName": "cdn1", + "ccrDnsTtl": 3600, + "checkPath": "", + "consistentHashQueryParams": [], + "deepCachingType": "NEVER", + "displayName": "ds1natDisplayName", + "dnsBypassCname": null, + "dnsBypassIp": "", + "dnsBypassIp6": "", + "dnsBypassTtl": 30, + "dscp": 40, + "edgeHeaderRewrite": "edgeRewrite1\nedgeHeader2", + "exampleURLs": [ + "http://ccr.ds1nat.example.net", + "https://ccr.ds1nat.example.net" + ], + "fqPacingRate": 0, + "geoLimit": 0, + "geoLimitCountries": "", + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": 0, + "globalMaxTps": 0, + "httpBypassFqdn": "", + "infoUrl": "TBD", + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": false, + "longDesc": "d s 1", + "longDesc1": "ds1nat", + "longDesc2": "ds1nat", + "matchList": [ + { + "pattern": ".*\\.ds1nat\\..*", + "setNumber": 0, + "type": "HOST_REGEXP" + } + ], + "maxDnsAnswers": 0, + "midHeaderRewrite": "midHeader1\nmidHeader2", + "missLat": 41.881944, + "missLong": -87.627778, + "multiSiteOrigin": false, + "orgServerFqdn": "http://origin.example.net", + "originShield": null, + "profileDescription": null, + "profileName": null, + "protocol": 2, + "qstringIgnore": 1, + "rangeRequestHandling": 0, + "regexRemap": "rr1\nrr2", + "regionalGeoBlocking": false, + "remapText": "@plugin=tslua.so @pparam=/opt/trafficserver/etc/trafficserver/remapPlugin1.lua", + "routingName": "ccr-ds1nat", + "signed": false, + "signingAlgorithm": "url_sig", + "sslKeyVersion": 2, + "tenant": "tenant1", + "tenantName": "tenant1", + "type": "HTTP_LIVE_NATNL", + "xmlId": "ds1nat", + "anonymousBlockingEnabled": true, + "maxRequestHeaderBytes": 131072 + }, + { + "active": true, + "cdnName": "cdn1", + "ccrDnsTtl": 3600, + "checkPath": "", + "consistentHashQueryParams": [], + "deepCachingType": "NEVER", + "displayName": "ds-with-topology", + "dnsBypassCname": null, + "dnsBypassIp": "", + "dnsBypassIp6": "", + "dnsBypassTtl": 30, + "dscp": 40, + "edgeHeaderRewrite": null, + "fqPacingRate": 0, + "geoLimit": 0, + "geoLimitCountries": "", + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": 0, + "globalMaxTps": 0, + "httpBypassFqdn": "", + "infoUrl": "TBD", + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": false, + "longDesc": "d s top", + "longDesc1": "ds top", + "longDesc2": "ds-top", + "matchList": [ + { + "pattern": ".*\\.ds-top\\..*", + "setNumber": 0, + "type": "HOST_REGEXP" + } + ], + "maxDnsAnswers": 0, + "midHeaderRewrite": null, + "missLat": 41.881944, + "missLong": -87.627778, + "multiSiteOrigin": false, + "orgServerFqdn": "http://origin.topology.example.net", + "originShield": null, + "profileDescription": null, + "profileName": null, + "protocol": 2, + "qstringIgnore": 1, + "rangeRequestHandling": 0, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "cdn", + "signed": false, + "signingAlgorithm": null, + "sslKeyVersion": 0, + "tenant": "tenant1", + "tenantName": "tenant1", + "type": "HTTP_LIVE_NATNL", + "xmlId": "ds-top", + "anonymousBlockingEnabled": false, + "topology": "mso-topology", + "firstHeaderRewrite": "first header rewrite", + "innerHeaderRewrite": "inner header rewrite", + "lastHeaderRewrite": "last header rewrite", + "maxRequestHeaderBytes": 131072 + }, + { + "active": true, + "cdnName": "cdn1", + "ccrDnsTtl": 3600, + "checkPath": "", + "consistentHashQueryParams": [], + "deepCachingType": "NEVER", + "displayName": "ds-top-req-cap", + "dnsBypassCname": null, + "dnsBypassIp": "", + "dnsBypassIp6": "", + "dnsBypassTtl": 30, + "dscp": 40, + "edgeHeaderRewrite": null, + "fqPacingRate": 0, + "geoLimit": 0, + "geoLimitCountries": "", + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": 0, + "globalMaxTps": 0, + "httpBypassFqdn": "", + "infoUrl": "TBD", + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": false, + "longDesc": "", + "longDesc1": "", + "longDesc2": "", + "matchList": [ + { + "pattern": ".*\\.ds-top-req-cap\\..*", + "setNumber": 0, + "type": "HOST_REGEXP" + } + ], + "maxDnsAnswers": 0, + "midHeaderRewrite": null, + "missLat": 41.881944, + "missLong": -87.627778, + "multiSiteOrigin": false, + "orgServerFqdn": "http://example.org", + "originShield": null, + "profileDescription": null, + "profileName": null, + "protocol": 0, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "cdn", + "signed": false, + "signingAlgorithm": null, + "sslKeyVersion": 0, + "tenant": "tenant1", + "tenantName": "tenant1", + "topology": "top-for-ds-req", + "type": "HTTP", + "xmlId": "ds-top-req-cap", + "anonymousBlockingEnabled": false, + "maxRequestHeaderBytes": 131072 + }, + { + "active": true, + "cdnName": "cdn1", + "ccrDnsTtl": 3600, + "checkPath": "", + "consistentHashQueryParams": [], + "deepCachingType": "NEVER", + "displayName": "ds-top-req-cap2", + "dnsBypassCname": null, + "dnsBypassIp": "", + "dnsBypassIp6": "", + "dnsBypassTtl": 30, + "dscp": 40, + "edgeHeaderRewrite": null, + "fqPacingRate": 0, + "geoLimit": 0, + "geoLimitCountries": "", + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": 0, + "globalMaxTps": 0, + "httpBypassFqdn": "", + "infoUrl": "TBD", + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": false, + "longDesc": "", + "longDesc1": "", + "longDesc2": "", + "matchList": [ + { + "pattern": ".*\\.ds-top-req-cap2\\..*", + "setNumber": 0, + "type": "HOST_REGEXP" + } + ], + "maxDnsAnswers": 0, + "midHeaderRewrite": null, + "missLat": 41.881944, + "missLong": -87.627778, + "multiSiteOrigin": false, + "orgServerFqdn": "http://example.org", + "originShield": null, + "profileDescription": null, + "profileName": null, + "protocol": 0, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "cdn", + "signed": false, + "signingAlgorithm": null, + "sslKeyVersion": 0, + "tenant": "tenant1", + "tenantName": "tenant1", + "topology": "top-for-ds-req2", + "type": "HTTP", + "xmlId": "ds-top-req-cap2", + "anonymousBlockingEnabled": false, + "maxRequestHeaderBytes": 131072 + }, + { + "active": true, + "cdnName": "cdn1", + "ccrDnsTtl": 3600, + "checkPath": "", + "consistentHashQueryParams": [], + "deepCachingType": "NEVER", + "displayName": "ds-client-steering", + "dnsBypassCname": null, + "dnsBypassIp": "", + "dnsBypassIp6": "", + "dnsBypassTtl": 30, + "dscp": 40, + "edgeHeaderRewrite": null, + "fqPacingRate": 0, + "geoLimit": 0, + "geoLimitCountries": "", + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": 0, + "globalMaxTps": 0, + "httpBypassFqdn": "", + "infoUrl": "TBD", + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": false, + "longDesc": "d s client-steering", + "longDesc1": "ds client-steering", + "longDesc2": "ds-client-steering", + "matchList": [ + { + "pattern": ".*\\.ds-client-steering\\..*", + "setNumber": 0, + "type": "HOST_REGEXP" + } + ], + "maxDnsAnswers": 0, + "midHeaderRewrite": null, + "missLat": 41.881944, + "missLong": -87.627778, + "multiSiteOrigin": false, + "orgServerFqdn": null, + "originShield": null, + "profileDescription": null, + "profileName": null, + "protocol": 2, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "cdn", + "signed": false, + "signingAlgorithm": null, + "sslKeyVersion": 0, + "tenant": "tenant1", + "tenantName": "tenant1", + "type": "CLIENT_STEERING", + "xmlId": "ds-client-steering", + "anonymousBlockingEnabled": false, + "maxRequestHeaderBytes": 131072 + }, + { + "active": true, + "cdnName": "cdn2", + "ccrDnsTtl": 3600, + "checkPath": "", + "consistentHashQueryParams": [], + "deepCachingType": "NEVER", + "displayName": "ds-forked-topology", + "dnsBypassCname": null, + "dnsBypassIp": "", + "dnsBypassIp6": "", + "dnsBypassTtl": 30, + "dscp": 40, + "edgeHeaderRewrite": null, + "fqPacingRate": 0, + "geoLimit": 0, + "geoLimitCountries": "", + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": 0, + "globalMaxTps": 0, + "httpBypassFqdn": "", + "infoUrl": "TBD", + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": false, + "longDesc": "", + "longDesc1": "", + "longDesc2": "", + "matchList": [ + { + "pattern": ".*\\.ds-forked-topology\\..*", + "setNumber": 0, + "type": "HOST_REGEXP" + } + ], + "maxDnsAnswers": 0, + "midHeaderRewrite": null, + "missLat": 41.881944, + "missLong": -87.627778, + "multiSiteOrigin": false, + "orgServerFqdn": "http://example.org", + "originShield": null, + "profileDescription": null, + "profileName": null, + "protocol": 0, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "cdn", + "signed": false, + "signingAlgorithm": null, + "sslKeyVersion": 0, + "tenant": "tenant1", + "tenantName": "tenant1", + "topology": "forked-topology", + "type": "HTTP", + "xmlId": "ds-forked-topology", + "anonymousBlockingEnabled": false, + "maxRequestHeaderBytes": 131072 + }, + { + "active": true, + "cdnName": "cdn1", + "ccrDnsTtl": 3600, + "checkPath": "", + "consistentHashQueryParams": [], + "deepCachingType": "NEVER", + "displayName": "top-ds-in-cdn1", + "dnsBypassCname": null, + "dnsBypassIp": "", + "dnsBypassIp6": "", + "dnsBypassTtl": 30, + "dscp": 40, + "edgeHeaderRewrite": null, + "fqPacingRate": 0, + "geoLimit": 0, + "geoLimitCountries": "", + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": 0, + "globalMaxTps": 0, + "httpBypassFqdn": "", + "infoUrl": "TBD", + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": false, + "longDesc": "", + "longDesc1": "", + "longDesc2": "", + "matchList": [ + { + "pattern": ".*\\.top-ds-in-cdn1\\..*", + "setNumber": 0, + "type": "HOST_REGEXP" + } + ], + "maxDnsAnswers": 0, + "midHeaderRewrite": null, + "missLat": 41.881944, + "missLong": -87.627778, + "multiSiteOrigin": false, + "orgServerFqdn": "http://example.org", + "originShield": null, + "profileDescription": null, + "profileName": null, + "protocol": 0, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "cdn", + "signed": false, + "signingAlgorithm": null, + "sslKeyVersion": 0, + "tenant": "tenant1", + "tenantName": "tenant1", + "topology": "top-used-by-cdn1-and-cdn2", + "type": "HTTP", + "xmlId": "top-ds-in-cdn1", + "anonymousBlockingEnabled": false, + "maxRequestHeaderBytes": 131072 + }, + { + "active": true, + "cdnName": "cdn1", + "ccrDnsTtl": 3600, + "checkPath": "", + "consistentHashQueryParams": [], + "deepCachingType": "NEVER", + "displayName": "top-ds-in-cdn1", + "dnsBypassCname": null, + "dnsBypassIp": "", + "dnsBypassIp6": "", + "dnsBypassTtl": 30, + "dscp": 40, + "edgeHeaderRewrite": null, + "fqPacingRate": 0, + "geoLimit": 0, + "geoLimitCountries": "", + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": 0, + "globalMaxTps": 0, + "httpBypassFqdn": "", + "infoUrl": "TBD", + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": false, + "longDesc": "", + "longDesc1": "", + "longDesc2": "", + "matchList": [ + { + "pattern": ".*\\.ds-based-top-with-no-mids\\..*", + "setNumber": 0, + "type": "HOST_REGEXP" + } + ], + "maxDnsAnswers": 0, + "midHeaderRewrite": null, + "missLat": 41.881944, + "missLong": -87.627778, + "multiSiteOrigin": false, + "orgServerFqdn": "http://example.org", + "originShield": null, + "profileDescription": null, + "profileName": null, + "protocol": 0, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "cdn", + "signed": false, + "signingAlgorithm": null, + "sslKeyVersion": 0, + "tenant": "tenant1", + "tenantName": "tenant1", + "topology": "top-with-no-mids", + "type": "HTTP", + "xmlId": "ds-based-top-with-no-mids", + "anonymousBlockingEnabled": false, + "maxRequestHeaderBytes": 131072 + }, + { + "active": true, + "cdnName": "cdn2", + "ccrDnsTtl": 3600, + "checkPath": "", + "consistentHashQueryParams": [], + "deepCachingType": "NEVER", + "displayName": "basic-ds-in-cdn2", + "dnsBypassCname": null, + "dnsBypassIp": "", + "dnsBypassIp6": "", + "dnsBypassTtl": 30, + "dscp": 40, + "edgeHeaderRewrite": null, + "fqPacingRate": 0, + "geoLimit": 0, + "geoLimitCountries": "", + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": 0, + "globalMaxTps": 0, + "httpBypassFqdn": "", + "infoUrl": "TBD", + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": false, + "longDesc": "", + "longDesc1": "", + "longDesc2": "", + "matchList": [ + { + "pattern": ".*\\.basic-ds-in-cdn2\\..*", + "setNumber": 0, + "type": "HOST_REGEXP" + } + ], + "maxDnsAnswers": 0, + "midHeaderRewrite": null, + "missLat": 41.881944, + "missLong": -87.627778, + "multiSiteOrigin": false, + "orgServerFqdn": "http://example.org", + "originShield": null, + "profileDescription": null, + "profileName": null, + "protocol": 0, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "cdn", + "signed": false, + "signingAlgorithm": null, + "sslKeyVersion": 0, + "tenant": "tenant1", + "tenantName": "tenant1", + "type": "HTTP", + "xmlId": "basic-ds-in-cdn2", + "anonymousBlockingEnabled": false, + "maxRequestHeaderBytes": 131072 + }, + { + "active": true, + "cdnName": "cdn2", + "ccrDnsTtl": 3600, + "checkPath": "", + "consistentHashQueryParams": [], + "deepCachingType": "NEVER", + "displayName": "top-ds-in-cdn2", + "dnsBypassCname": null, + "dnsBypassIp": "", + "dnsBypassIp6": "", + "dnsBypassTtl": 30, + "dscp": 40, + "edgeHeaderRewrite": null, + "fqPacingRate": 0, + "geoLimit": 0, + "geoLimitCountries": "", + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": 0, + "globalMaxTps": 0, + "httpBypassFqdn": "", + "infoUrl": "TBD", + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": false, + "longDesc": "", + "longDesc1": "", + "longDesc2": "", + "matchList": [ + { + "pattern": ".*\\.top-ds-in-cdn2\\..*", + "setNumber": 0, + "type": "HOST_REGEXP" + } + ], + "maxDnsAnswers": 0, + "midHeaderRewrite": null, + "missLat": 41.881944, + "missLong": -87.627778, + "multiSiteOrigin": false, + "orgServerFqdn": "http://example.org", + "originShield": null, + "profileDescription": null, + "profileName": null, + "protocol": 0, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regexRemap": null, + "regionalGeoBlocking": false, + "remapText": null, + "routingName": "cdn", + "signed": false, + "signingAlgorithm": null, + "sslKeyVersion": 0, + "tenant": "tenant1", + "tenantName": "tenant1", + "topology": "top-used-by-cdn1-and-cdn2", + "type": "HTTP", + "xmlId": "top-ds-in-cdn2", + "anonymousBlockingEnabled": false, + "maxRequestHeaderBytes": 131072 + }, + { + "active": false, + "cdnName": "cdn1", + "ccrDnsTtl": 3600, + "checkPath": "", + "consistentHashQueryParams": [], + "deepCachingType": "NEVER", + "displayName": "inactiveDSDisplayName", + "dnsBypassCname": null, + "dnsBypassIp": "", + "dnsBypassIp6": "", + "dnsBypassTtl": 30, + "dscp": 40, + "edgeHeaderRewrite": "edgeHeader1\nedgeHeader2", + "exampleURLs": [ + "http://ccr.inactiveds.example.net", + "https://ccr.inactiveds.example.net" + ], + "fqPacingRate": 0, + "geoLimit": 0, + "geoLimitCountries": "", + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": 0, + "globalMaxTps": 0, + "httpBypassFqdn": "", + "infoUrl": "TBD", + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": false, + "longDesc": "d s inactive", + "longDesc1": "dsinactive", + "longDesc2": "dsinactive", + "matchList": [ + { + "pattern": ".*\\.dsinactive\\..*", + "setNumber": 0, + "type": "HOST_REGEXP" + } + ], + "maxDnsAnswers": 0, + "midHeaderRewrite": "midHeader1\nmidHeader2", + "missLat": 41.881944, + "missLong": -87.627778, + "multiSiteOrigin": false, + "orgServerFqdn": "http://origin.example.net", + "originShield": null, + "profileDescription": null, + "profileName": null, + "protocol": 2, + "qstringIgnore": 1, + "rangeRequestHandling": 0, + "regexRemap": "rr1\nrr2", + "regionalGeoBlocking": false, + "remapText": "@plugin=tslua.so @pparam=/opt/trafficserver/etc/trafficserver/remapPlugin1.lua", + "routingName": "ccr-inactiveds", + "signed": false, + "signingAlgorithm": "url_sig", + "sslKeyVersion": 2, + "tenant": "tenant1", + "tenantName": "tenant1", + "type": "HTTP", + "xmlId": "inactiveds", + "anonymousBlockingEnabled": true, + "maxRequestHeaderBytes": 131072 + }, + { + "active": true, + "anonymousBlockingEnabled": false, + "cdnName": "cdn1", + "displayName": "test", + "dscp": 0, + "geoLimit": 0, + "geoProvider": 0, + "initialDispersion": 1, + "ipv6RoutingEnabled": false, + "logsEnabled": false, + "missLat": 0, + "missLong": 0, + "multiSiteOrigin": true, + "orgServerFqdn": "https://example.com", + "protocol": 0, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regionalGeoBlocking": false, + "tenant": "tenant1", + "type": "HTTP", + "xmlId": "test-ds-server-assignments", + "maxRequestHeaderBytes": 131072 + }, + { + "active": false, + "cdnName": "cdn1", + "cacheurl": "cacheUrl3", + "ccrDnsTtl": 3600, + "checkPath": "", + "consistentHashQueryParams": null, + "deepCachingType": "NEVER", + "displayName": "d s 4", + "dnsBypassCname": null, + "dnsBypassIp": "", + "dnsBypassIp6": "", + "dnsBypassTtl": 30, + "dscp": 40, + "edgeHeaderRewrite": "edgeRewrite1\nedgeHeader2", + "exampleURLs": [ + "http://ccr.ds4.example.net", + "https://ccr.ds4x.example.net" + ], + "fqPacingRate": 0, + "geoLimit": 0, + "geoLimitCountries": "", + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": 0, + "globalMaxTps": 0, + "httpBypassFqdn": "", + "infoUrl": "TBD", + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": false, + "longDesc": "d s 4", + "longDesc1": "ds4", + "longDesc2": "ds4", + "matchList": [ + { + "pattern": ".*\\.ds4\\..*", + "setNumber": 0, + "type": "HOST_REGEXP" + } + ], + "maxDnsAnswers": 0, + "maxOriginConnections": 0, + "midHeaderRewrite": "midHeader1\nmidHeader2", + "missLat": 41.881944, + "missLong": -87.627778, + "multiSiteOrigin": false, + "orgServerFqdn": "http://origin.ds4.example.net", + "originShield": null, + "profileDescription": null, + "profileName": null, + "protocol": 2, + "qstringIgnore": 1, + "rangeRequestHandling": 0, + "regexRemap": "rr1\nrr2", + "regionalGeoBlocking": false, + "remapText": "@plugin=tslua.so @pparam=/opt/trafficserver/etc/trafficserver/ds4plugin.lua", + "routingName": "ccr-ds4", + "signed": false, + "signingAlgorithm": "url_sig", + "sslKeyVersion": 2, + "tenant": "tenant3", + "tenantName": "tenant3", + "type": "HTTP_LIVE", + "xmlId": "ds4", + "anonymousBlockingEnabled": true, + "maxRequestHeaderBytes": 131072 + }, + { + "active": false, + "cdnName": "cdn1", + "cacheurl": "cacheUrl3", + "ccrDnsTtl": 3600, + "checkPath": "", + "consistentHashQueryParams": null, + "deepCachingType": "NEVER", + "displayName": "d s 5", + "dnsBypassCname": null, + "dnsBypassIp": "", + "dnsBypassIp6": "", + "dnsBypassTtl": 30, + "dscp": 40, + "edgeHeaderRewrite": "edgeRewrite1\nedgeHeader2", + "exampleURLs": [ + "http://ccr.ds4.example.net", + "https://ccr.ds4x.example.net" + ], + "fqPacingRate": 0, + "geoLimit": 0, + "geoLimitCountries": "", + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": 0, + "globalMaxTps": 0, + "httpBypassFqdn": "", + "infoUrl": "TBD", + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": false, + "longDesc": "d s 5", + "longDesc1": "ds5", + "longDesc2": "ds5", + "matchList": [ + { + "pattern": ".*\\.ds5\\..*", + "setNumber": 0, + "type": "HOST_REGEXP" + } + ], + "maxDnsAnswers": 0, + "maxOriginConnections": 0, + "midHeaderRewrite": "midHeader1\nmidHeader2", + "missLat": 41.881944, + "missLong": -87.627778, + "multiSiteOrigin": false, + "orgServerFqdn": "http://origin.ds4.example.net", + "originShield": null, + "profileDescription": null, + "profileName": null, + "protocol": 2, + "qstringIgnore": 1, + "rangeRequestHandling": 3, + "rangeSliceBlockSize": 1000000, + "regexRemap": "rr1\nrr2", + "regionalGeoBlocking": false, + "remapText": "@plugin=tslua.so @pparam=/opt/trafficserver/etc/trafficserver/ds4plugin.lua", + "routingName": "ccr-ds5", + "serviceCategory": "serviceCategory1", + "signed": false, + "signingAlgorithm": "url_sig", + "sslKeyVersion": 2, + "tenant": "tenant3", + "tenantName": "tenant3", + "type": "HTTP_LIVE", + "xmlId": "DS5", + "anonymousBlockingEnabled": true, + "maxRequestHeaderBytes": 131072 + }, + { + "active": true, + "cdnName": "cdn1", + "ccrDnsTtl": 3600, + "checkPath": "", + "deepCachingType": "NEVER", + "displayName": "steering-ds", + "dnsBypassCname": null, + "dnsBypassIp": "", + "dnsBypassIp6": "", + "dnsBypassTtl": 30, + "dscp": 40, + "edgeHeaderRewrite": "", + "exampleURLs": [], + "fqPacingRate": 0, + "geoLimit": 0, + "geoLimitCountries": "", + "geoLimitRedirectURL": null, + "geoProvider": 0, + "globalMaxMbps": 0, + "globalMaxTps": 0, + "httpBypassFqdn": "", + "infoUrl": "", + "initialDispersion": 1, + "ipv6RoutingEnabled": true, + "logsEnabled": false, + "longDesc": "", + "longDesc1": "", + "longDesc2": "", + "matchList": [], + "maxDnsAnswers": 0, + "maxOriginConnections": 1, + "midHeaderRewrite": "", + "missLat": 41.881944, + "missLong": -87.627778, + "multiSiteOrigin": false, + "orgServerFqdn": "http://example.com", + "originShield": null, + "profileDescription": null, + "profileName": null, + "protocol": 2, + "qstringIgnore": 1, + "rangeRequestHandling": 0, + "regexRemap": "", + "regionalGeoBlocking": false, + "remapText": "map some raw remap text", + "routingName": "", + "signed": false, + "signingAlgorithm": "url_sig", + "sslKeyVersion": 2, + "tenant": "tenant3", + "tenantName": "tenant3", + "type": "STEERING", + "xmlId": "steering-ds", + "anonymousBlockingEnabled": true, + "maxRequestHeaderBytes": 131072 + }, + { + "active": true, + "anonymousBlockingEnabled": false, + "cdnName": "cdn1", + "displayName": "test-rm-ssc", + "dscp": 0, + "geoLimit": 0, + "geoProvider": 0, + "initialDispersion": 1, + "ipv6RoutingEnabled": false, + "logsEnabled": false, + "missLat": 0, + "missLong": 0, + "multiSiteOrigin": true, + "orgServerFqdn": "https://example.com", + "protocol": 0, + "qstringIgnore": 0, + "rangeRequestHandling": 0, + "regionalGeoBlocking": false, + "tenant": "tenant1", + "topology": "top-with-caches-in-cdn1", + "type": "HTTP", + "xmlId": "test-rm-ssc", + "maxRequestHeaderBytes": 131072 + } + ], + "deliveryServicesRegexes": [ + { + "dsName": "ds1", + "typeName": "HOST_REGEXP", + "setNumber": 1, + "pattern": ".*" + }, + { + "dsName": "ds1", + "typeName": "HOST_REGEXP", + "setNumber": 2, + "pattern": "\\d+" + }, + { + "dsName": "ds2", + "typeName": "HOST_REGEXP", + "setNumber": 1, + "pattern": ".*" + } + ], + "deliveryservicesRequiredCapabilities": [ + { + "xmlID": "ds1", + "RequiredCapability": "foo" + }, + { + "xmlID": "ds2", + "RequiredCapability": "bar" + }, + { + "xmlID": "msods1", + "RequiredCapability": "bar" + }, + { + "xmlID": "ds-top-req-cap", + "RequiredCapability": "ram" + }, + { + "xmlID": "ds-top-req-cap", + "RequiredCapability": "disk" + }, + { + "xmlID": "ds-top-req-cap2", + "RequiredCapability": "ram" + }, + { + "xmlID": "test-rm-ssc", + "RequiredCapability": "ram" + } + ], + "deliveryServiceServerAssignments": [ + { + "xmlId": "ds-top", + "serverNames": ["denver-mso-org-01"] + }, + { + "xmlId": "test-ds-server-assignments", + "serverNames": ["test-ds-server-assignments", "test-mso-org-01"] + }, + { + "xmlId": "ds3", + "serverNames": ["atlanta-edge-14"] + }, + { + "xmlId": "ds2", + "serverNames": ["atlanta-org-2"] + } + ], + "divisions": [ + { + "name": "division1" + }, + { + "name": "cdn-div2" + } + ], + "federations": [ + { + "cname": "the.cname.com.", + "ttl": 48, + "description": "the description", + "deliveryService": { + "xmlId": "ds1" + } + }, + { + "cname": "booya.com.", + "ttl": 34, + "description": "fooya", + "deliveryService": { + "xmlId": "ds1" + } + }, + { + "cname": "google.com.", + "ttl": 30, + "description": "google", + "deliveryService": { + "xmlId": "ds1" + } + } + ], + "federation_resolvers": [ + { + "ipAddress": "1.2.3.4", + "type": "RESOLVE4", + "id": 1 + }, + { + "ipAddress": "0.0.0.0/12", + "type": "RESOLVE4", + "id": 2 + }, + { + "ipAddress": "dead::babe", + "type": "RESOLVE6", + "id": 3 + }, + { + "ipAddress": "::f1d0:f00d/123", + "type": "RESOLVE6", + "id": 4 + } + ], + "coordinates": [ + { + "latitude": 1.1, + "longitude": 2.2, + "name": "coordinate1" + }, + { + "latitude": 3.3, + "longitude": 4.4, + "name": "coordinate2" + }, + { + "latitude": 5.5, + "longitude": 6.6, + "name": "abc-coordinate" + } + ], + "origins": [ + { + "name": "origin1", + "cachegroup": "originCachegroup", + "Coordinate": "coordinate1", + "deliveryService": "ds1", + "fqdn": "origin1.example.com", + "ipAddress": "1.2.3.4", + "ip6Address": "dead:beef:cafe::42", + "port": 1234, + "Profile": "ATS_EDGE_TIER_CACHE", + "protocol": "http", + "tenant": "tenant1", + "isPrimary": true + }, + { + "name": "origin2", + "cachegroup": "originCachegroup", + "Coordinate": "coordinate1", + "deliveryService": "ds2", + "fqdn": "origin2.example.com", + "ipAddress": "5.6.7.8", + "ip6Address": "cafe::42", + "port": 5678, + "protocol": "https", + "tenant": "tenant1" + } + ], + "parameters": [ + { + "configFile": "rascal.properties", + "name": "history.count", + "secure": false, + "value": "30" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.allocator.enable_reclaim", + "secure": false, + "value": "INT 0" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.allocator.max_overage", + "secure": false, + "value": "INT 3" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.diags.show_location", + "secure": false, + "value": "INT 0" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.http.cache.allow_empty_doc", + "secure": false, + "value": "INT 0" + }, + { + "configFile": "records.config", + "name": "LOCAL proxy.config.cache.interim.storage", + "secure": false, + "value": "STRING NULL" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.http.parent_proxy.file", + "secure": false, + "value": "STRING parent.config" + }, + { + "configFile": "plugin.config", + "name": "astats_over_http.so", + "secure": false, + "value": "_astats 33.101.99.100,172.39.19.39,172.39.19.49,172.39.19.49,172.39.29.49" + }, + { + "configFile": "logs_xml.config", + "name": "LogFormat.Name", + "secure": false, + "value": "custom_ats_2" + }, + { + "configFile": "logs_xml.config", + "name": "LogObject.Format", + "secure": false, + "value": "custom_ats_2" + }, + { + "configFile": "logs_xml.config", + "name": "LogObject.Filename", + "secure": false, + "value": "custom_ats_2" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.cache.control.filename", + "secure": false, + "value": "STRING cache.config" + }, + { + "configFile": "plugin.config", + "name": "regex_revalidate.so", + "secure": false, + "value": "--config regex_revalidate.config" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.hostdb.storage_size", + "secure": false, + "value": "INT 33554432" + }, + { + "configFile": "regex_revalidate.config", + "name": "maxRevalDurationDays", + "secure": false, + "value": "90" + }, + { + "configFile": "package", + "name": "trafficserver", + "secure": false, + "value": "5.3.2-765.f4354b9.el7.centos.x86_64" + }, + { + "configFile": "global", + "name": "tm.instance_name", + "secure": false, + "value": "Traffic Ops API Tests" + }, + { + "configFile": "remap.config", + "name": "location", + "secure": false, + "value": "/remap/config/location/parameter/" + }, + { + "configFile": "global", + "name": "refetch_enabled", + "secure": false, + "value": "true" + }, + { + "configFile": "secure.config", + "name": "testSecure", + "secure": true, + "value": "hidden value" + } + + ], + "physLocations": [ + { + "address": "1234 mile high circle", + "city": "Denver", + "comments": null, + "email": null, + "name": "Denver", + "phone": "303-111-1111", + "poc": null, + "region": "region1", + "shortName": "denver", + "state": "CO", + "zip": "80202" + }, + { + "address": "1234 green way", + "city": "Boulder", + "comments": null, + "email": null, + "name": "Boulder", + "phone": "303-222-2222", + "poc": null, + "region": "region1", + "shortName": "boulder", + "state": "CO", + "zip": "80301" + }, + { + "address": "1234 southern way", + "city": "Atlanta", + "comments": null, + "email": null, + "name": "HotAtlanta", + "phone": "404-222-2222", + "poc": null, + "region": "region1", + "shortName": "atlanta", + "state": "GA", + "zip": "30301" + }, + { + "address": "1234 delete way", + "city": "Denver", + "comments": null, + "email": null, + "name": "testDelete", + "phone": "303-222-2222", + "poc": null, + "region": "region1", + "shortName": "testdelete", + "state": "C0", + "zip": "80202" + } + ], + "profiles": [ + { + "cdnName": "cdn1", + "description": "Edge Cache - Apache Traffic Server", + "name": "ATS_EDGE_TIER_CACHE", + "routingDisabled": false, + "type": "ATS_PROFILE", + "params": [ + { + "configFile": "records.config", + "name": "CONFIG proxy.config.proxy_name", + "secure": false, + "value": "STRING __HOSTNAME__" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.config_dir", + "secure": false, + "value": "STRING /etc/trafficserver" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.admin.user_id", + "secure": false, + "value": "STRING ats" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.http.server_ports", + "secure": false, + "value": "STRING 80 80:ipv6" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.http.insert_response_via_str", + "secure": false, + "value": "INT 3" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.http.parent_proxy_routing_enable", + "secure": false, + "value": "INT 1" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.http.parent_proxy.retry_time", + "secure": false, + "value": "INT 60" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.http.connect_attempts_timeout", + "secure": false, + "value": "INT 10" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.http.cache.required_headers", + "secure": false, + "value": "INT 0" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.http.enable_http_stats", + "secure": false, + "value": "INT 1" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.dns.round_robin_nameservers", + "secure": false, + "value": "INT 0" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.log.max_space_mb_for_logs", + "secure": false, + "value": "INT 512" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.log.max_space_mb_headroom", + "secure": false, + "value": "INT 50" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.log.logfile_dir", + "secure": false, + "value": "STRING /var/log/trafficserver" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.reverse_proxy.enabled", + "secure": false, + "value": "INT 0" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.diags.debug.enabled", + "secure": false, + "value": "INT 1" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.http.slow.log.threshold", + "secure": false, + "value": "INT 10000" + }, + { + "configFile": "cache.config", + "name": "location", + "secure": false, + "value": "/etc/trafficserver/" + }, + { + "configFile": "hosting.config", + "name": "location", + "secure": false, + "value": "/etc/trafficserver/" + }, + { + "configFile": "parent.config", + "name": "location", + "secure": false, + "value": "/etc/trafficserver/" + }, + { + "configFile": "plugin.config", + "name": "location", + "secure": false, + "value": "/etc/trafficserver/" + }, + { + "configFile": "records.config", + "name": "location", + "secure": false, + "value": "/etc/trafficserver/" + }, + { + "configFile": "storage.config", + "name": "location", + "secure": false, + "value": "/etc/trafficserver/" + }, + { + "configFile": "volume.config", + "name": "location", + "secure": false, + "value": "/etc/trafficserver/" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.url_remap.remap_required", + "secure": false, + "value": "INT 0" + }, + { + "configFile": "rascal.properties", + "name": "health.threshold.queryTime", + "secure": false, + "value": "1000" + }, + { + "configFile": "rascal.properties", + "name": "health.polling.url", + "secure": false, + "value": "http://${hostname}/_astats?application=&inf.name=${interface_name}" + }, + { + "configFile": "storage.config", + "name": "Disk_Volume", + "secure": false, + "value": "1" + }, + { + "configFile": "rascal.properties", + "name": "health.connection.timeout", + "secure": false, + "value": "2000" + }, + { + "configFile": "chkconfig", + "name": "trafficserver", + "secure": false, + "value": "0:off\t1:off\t2:on\t3:on\t4:on\t5:on\t6:off" + }, + { + "configFile": "regex_revalidate.config", + "name": "location", + "secure": false, + "value": "/etc/trafficserver" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.exec_thread.autoconfig", + "secure": false, + "value": "INT 0" + }, + { + "configFile": "plugin.config", + "name": "astats_over_http.so", + "secure": false, + "value": "" + }, + { + "configFile": "astats.config", + "name": "allow_ip", + "secure": false, + "value": "127.0.0.1,10.0.0.0/8,172.16.0.0/12,192.168.0.0/16" + }, + { + "configFile": "astats.config", + "name": "allow_ip6", + "secure": false, + "value": "::1/128,fc01:9400:1000:8::/64" + }, + { + "configFile": "astats.config", + "name": "location", + "secure": false, + "value": "/etc/trafficserver" + }, + { + "configFile": "astats.config", + "name": "path", + "secure": false, + "value": "_astats" + }, + { + "configFile": "astats.config", + "name": "record_types", + "secure": false, + "value": "122" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.http.transaction_active_timeout_in", + "secure": false, + "value": "INT 0" + }, + { + "configFile": "records.config", + "name": "CONFIG proxy.config.body_factory.template_sets_dir", + "secure": false, + "value": "STRING /etc/trafficserver/body_factory" + }, + { + "configFile": "storage.config", + "name": "Drive_Letters", + "secure": false, + "value": "cache" + }, + { + "configFile": "ip_allow.config", + "name": "location", + "secure": false, + "value": "/etc/trafficserver" + }, + { + "configFile": "storage.config", + "name": "Drive_Prefix", + "secure": false, + "value": "/var/trafficserver/" + }, + { + "configFile": "set_dscp_0.config", + "name": "location", + "value": "/etc/trafficserver/dscp" + }, + { + "configFile": "set_dscp_10.config", + "name": "location", + "value": "/etc/trafficserver/dscp" + }, + { + "configFile": "set_dscp_12.config", + "name": "location", + "value": "/etc/trafficserver/dscp" + }, + { + "configFile": "set_dscp_14.config", + "name": "location", + "value": "/etc/trafficserver/dscp" + }, + { + "configFile": "set_dscp_18.config", + "name": "location", + "value": "/etc/trafficserver/dscp" + }, + { + "configFile": "set_dscp_20.config", + "name": "location", + "value": "/etc/trafficserver/dscp" + }, + { + "configFile": "set_dscp_22.config", + "name": "location", + "value": "/etc/trafficserver/dscp" + }, + { + "configFile": "set_dscp_26.config", + "name": "location", + "value": "/etc/trafficserver/dscp" + }, + { + "configFile": "set_dscp_28.config", + "name": "location", + "value": "/etc/trafficserver/dscp" + }, + { + "configFile": "set_dscp_30.config", + "name": "location", + "value": "/etc/trafficserver/dscp" + }, + { + "configFile": "set_dscp_34.config", + "name": "location", + "value": "/etc/trafficserver/dscp" + }, + { + "configFile": "set_dscp_36.config", + "name": "location", + "value": "/etc/trafficserver/dscp" + }, + { + "configFile": "set_dscp_38.config", + "name": "location", + "value": "/etc/trafficserver/dscp" + }, + { + "configFile": "set_dscp_8.config", + "name": "location", + "value": "/etc/trafficserver/dscp" + }, + { + "configFile": "set_dscp_16.config", + "name": "location", + "value": "/etc/trafficserver/dscp" + }, + { + "configFile": "set_dscp_24.config", + "name": "location", + "value": "/etc/trafficserver/dscp" + }, + { + "configFile": "set_dscp_32.config", + "name": "location", + "value": "/etc/trafficserver/dscp" + }, + { + "configFile": "set_dscp_40.config", + "name": "location", + "value": "/etc/trafficserver/dscp" + }, + { + "configFile": "set_dscp_48.config", + "name": "location", + "value": "/etc/trafficserver/dscp" + }, + { + "configFile": "set_dscp_56.config", + "name": "location", + "value": "/etc/trafficserver/dscp" + }, + { + "configFile": "set_dscp_37.config", + "name": "location", + "value": "/etc/trafficserver/dscp" + } + ] + }, + { + "cdnName": "cdn1", + "description": "edge1 description", + "name": "EDGE1", + "routing_disabled": false, + "type": "ATS_PROFILE", + "params": [ + { + "configFile": "rascal.properties", + "name": "health.threshold.loadavg", + "secure": false, + "value": "25.0" + }, + { + "configFile": "rascal.properties", + "name": "health.threshold.availableBandwidthInKbps", + "secure": false, + "value": ">1750000" + }, + { + "configFile": "rascal.properties", + "name": "health.threshold.queryTime", + "secure": false, + "value": "1000" + } + ] + }, + { + "cdnName": "cdn2", + "description": "edge2 description", + "name": "EDGEInCDN2", + "routing_disabled": false, + "type": "ATS_PROFILE" + }, + { + "cdnName": "cdn4", + "description": "edge2 description", + "name": "EDGE2", + "routing_disabled": false, + "type": "ATS_PROFILE" + }, + { + "cdnName": "cdn2", + "description": "cdn2 edge description", + "name": "CDN2_EDGE", + "routing_disabled": false, + "type": "ATS_PROFILE" + }, + { + "cdnName": "cdn1", + "description": "mid description", + "name": "MID1", + "routing_disabled": false, + "type": "ATS_PROFILE" + }, + { + "cdnName": "cdn2", + "description": "mid description", + "name": "MID2", + "routing_disabled": false, + "type": "ATS_PROFILE" + }, + { + "cdnName": "cdn1", + "description": "origin description", + "name": "ORIGIN1", + "routing_disabled": false, + "type": "ORG_PROFILE" + }, + { + "cdnName": "cdn1", + "description": "cdn1 description", + "name": "CCR1", + "routing_disabled": false, + "type": "TR_PROFILE" + }, + { + "cdnName": "cdn2", + "description": "cdn2 description", + "name": "CCR2", + "routing_disabled": false, + "type": "TR_PROFILE" + }, + { + "cdnName": "cdn1", + "description": "rascal description", + "name": "RASCAL1", + "routing_disabled": false, + "type": "TM_PROFILE", + "params": [ + { + "configFile": "rascal.properties", + "name": "health.threshold.queryTime", + "secure": false, + "value": "1000" + }, + { + "configFile": "rascal.properties", + "name": "health.polling.url", + "secure": false, + "value": "http://${hostname}/_astats?application=&inf.name=${interface_name}" + }, + { + "configFile": "rascal-config.txt", + "name": "peers.polling.interval", + "secure": false, + "value": "60" + }, + { + "configFile": "rascal-config.txt", + "name": "health.polling.interval", + "secure": false, + "value": "30" + } + ] + }, + { + "cdnName": "cdn1", + "description": "mso origin description", + "name": "MSO", + "routing_disabled": false, + "type": "ORG_PROFILE" + }, + { + "cdnName": "cdn2", + "description": "mso origin description", + "name": "MSO-CDN2", + "routing_disabled": false, + "type": "ORG_PROFILE" + }, + { + "cdnName": "cdn2", + "description": "cdn2 mid description", + "name": "CDN2_MID", + "routing_disabled": false, + "type": "ATS_PROFILE" + }, + { + "cdnName": "cdn2", + "description": "cdn lock test", + "name": "OKwhenUserOwnLocks", + "routing_disabled": false, + "type": "ATS_PROFILE", + "params": [ + { + "configFile": "rascal.properties", + "name": "test.cdnlock.delete", + "secure": false, + "value": "25.0" + } + ] + }, + { + "cdnName": "cdn2", + "description": "cdn lock forbidden test", + "name": "FORBIDDENwhenDoesntOwnLock", + "routing_disabled": false, + "type": "ATS_PROFILE", + "params": [ + { + "configFile": "rascal.properties", + "name": "test.cdnlock.forbidden.delete", + "secure": false, + "value": "25.0" + } + ] + } + ], + "regions": [ + { + "divisionName": "division1", + "name": "region1" + }, + { + "divisionName": "cdn-div2", + "name": "cdn-region2" + }, + { + "divisionName": "division1", + "name": "test-deletion" + } + ], + "roles": [ + { + "name": "new_admin", + "description": "super-user 2", + "permissions": [ + "all-read", + "all-write" + ] + }, + { + "name": "another_role", + "description": "super-user 3", + "permissions": [ + "all-read", + "all-write" + ] + }, + { + "name": "update_role", + "description": "role for testing update", + "permissions": [ + "all-read", + "all-write" + ] + } + ], + "servers": [ + { + "cachegroup": "cachegroup1", + "cdnName": "cdn1", + "domainName": "ga.atlanta.kabletown.net", + "guid": null, + "hostName": "atlanta-edge-01", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.0.21/30", + "gateway": "127.0.0.21", + "serviceAddress": true + }, + { + "address": "2345:1234:12:8::1/64", + "gateway": "2345:1234:12:8::1", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router1", + "routerPort": "9000" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false, + "xmppId": "atlanta-edge-01\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + }, + { + "cachegroup": "cachegroup1", + "cdnName": "cdn2", + "domainName": "ga.atlanta.kabletown.net", + "guid": null, + "hostName": "cdn2-test-edge", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "0.0.0.0/0", + "gateway": "0.0.0.0", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 1500, + "name": "eth0", + "routerHostName": "router2", + "routerPort": "9001" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["EDGEInCDN2"], + "rack": "", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false, + "xmppId": "", + "xmppPasswd": "" + }, + { + "cachegroup": "cachegroup1", + "cdnName": "cdn1", + "domainName": "kabletown.net", + "guid": null, + "hostName": "influxdb02", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.0.11/22", + "gateway": "127.0.0.11", + "serviceAddress": true + }, + { + "address": "2345:1234:12:8::2/64", + "gateway": "2345:1234:12:8::2", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 1500, + "name": "eth1", + "routerHostName": "router3", + "routerPort": "9002" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 8086, + "type": "EDGE", + "updPending": false, + "xmppId": "", + "xmppPasswd": "" + }, + { + "cachegroup": "cachegroup1", + "cdnName": "cdn1", + "domainName": "ga.atlanta.kabletown.net", + "guid": null, + "hostName": "atlanta-router-01", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.0.12/30", + "gateway": "127.0.0.1", + "serviceAddress": true + }, + { + "address": "2345:1234:12:8::3/64", + "gateway": "2345:1234:12:8::3", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router4", + "routerPort": "9003" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false, + "xmppId": "atlanta-router-01\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + }, + { + "cachegroup": "cachegroup2", + "cdnName": "cdn1", + "domainName": "ga.atlanta.kabletown.net", + "guid": null, + "hostName": "atlanta-edge-03", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2345:1234:12:2::4/64", + "gateway": "2345:1234:12:2::4", + "serviceAddress": false + }, + { + "address": "127.0.0.13/30", + "gateway": "127.0.0.1", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router5", + "routerPort": "9004" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false, + "xmppId": "atlanta-edge-03\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + }, + { + "cachegroup": "cachegroup1", + "cdnName": "cdn1", + "domainName": "ga.atlanta.kabletown.net", + "guid": null, + "hostName": "atlanta-edge-14", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2345:1234:12:8::5/64", + "gateway": "2345:1234:12:8::5", + "serviceAddress": false + }, + { + "address": "127.0.0.14/30", + "gateway": "127.0.0.1", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router6", + "routerPort": "9005" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false, + "xmppId": "atlanta-edge-14\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + }, + { + "cachegroup": "cachegroup1", + "cdnName": "cdn1", + "domainName": "ga.atlanta.kabletown.net", + "guid": null, + "hostName": "atlanta-edge-15", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2345:1234:12:d::6/64", + "gateway": "2345:1234:12:d::6", + "serviceAddress": false + }, + { + "address": "127.0.0.15/30", + "gateway": "127.0.0.7", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router7", + "routerPort": "9006" + } + ], + "ipNetmask": "255.255.255.252", + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false, + "xmppId": "atlanta-edge-15\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + }, + { + "cachegroup": "parentCachegroup", + "cdnName": "cdn1", + "domainName": "ga.atlanta.kabletown.net", + "guid": null, + "hostName": "atlanta-mid-16", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2345:1234:12:d::7/64", + "gateway": "2345:1234:12:d::7", + "serviceAddress": false + }, + { + "address": "127.0.0.16/30", + "gateway": "127.0.0.7", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router8", + "routerPort": "9007" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["MID1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "MID", + "updPending": false, + "xmppId": "atlanta-mid-16\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + }, + { + "cachegroup": "parentCachegroup", + "cdnName": "cdn2", + "domainName": "ga.atlanta.kabletown.net", + "guid": null, + "hostName": "atlanta-mid-17", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2345:1234:17:d::7/64", + "gateway": "2345:1234:17:d::7", + "serviceAddress": false + }, + { + "address": "127.0.0.17/30", + "gateway": "127.0.0.17", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router9", + "routerPort": "9008" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["MID2"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "MID", + "updPending": false, + "xmppId": "atlanta-mid-17\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + }, + { + "cachegroup": "cachegroup1", + "cdnName": "cdn1", + "domainName": "ga.atlanta.kabletown.net", + "guid": null, + "hostName": "atlanta-org-1", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2345:1234:12:d::8/64", + "gateway": "2345:1234:12:d::8", + "serviceAddress": false + }, + { + "address": "127.0.0.17/30", + "gateway": "127.0.0.17", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router10", + "routerPort": "9009" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false, + "xmppId": "atlanta-org-1\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + }, + { + "cachegroup": "cachegroup1", + "cdnName": "cdn1", + "domainName": "ga.atlanta.kabletown.net", + "guid": null, + "hostName": "atlanta-org-2", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.0.18/30", + "gateway": "127.0.0.18", + "serviceAddress": true + }, + { + "address": "2345:1234:12:d::9/64", + "gateway": "2345:1234:12:d::9", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router11", + "routerPort": "9010" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false, + "xmppId": "atlanta-org-1\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + }, + { + "cachegroup": "parentCachegroup", + "cdnName": "cdn1", + "domainName": "ga.atlanta.kabletown.net", + "guid": null, + "hostName": "atlanta-mid-01", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.0.2/30", + "gateway": "127.0.0.2", + "serviceAddress": true + }, + { + "address": "2345:1234:12:9::10/64", + "gateway": "2345:1234:12:9::10", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router12", + "routerPort": "9011" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["MID1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "MID", + "updPending": false, + "xmppId": "atlanta-mid-01\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + }, + { + "cachegroup": "cachegroup1", + "cdnName": "cdn1", + "domainName": "kabletown.net", + "guid": null, + "hostName": "rascal01", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.0.4/30", + "gateway": "127.0.0.4", + "serviceAddress": true + }, + { + "address": "2345:1234:12:b::11/64", + "gateway": "2345:1234:12:b::11", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router13", + "routerPort": "9012" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["RASCAL1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 81, + "type": "RASCAL", + "updPending": false, + "xmppId": "", + "xmppPasswd": "X" + }, + { + "cachegroup": "cachegroup2", + "cdnName": "cdn2", + "domainName": "kabletown2.net", + "guid": null, + "hostName": "edge1-cdn2", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.0.31/24", + "gateway": "127.0.0.4", + "serviceAddress": true + }, + { + "address": "2345:1234:12:b::13/64", + "gateway": "2345:1234:12:b::13", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router14", + "routerPort": "9013" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["CDN2_EDGE"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 81, + "type": "EDGE", + "updPending": false, + "xmppId": "", + "xmppPasswd": "X" + }, + { + "cachegroup": "cachegroup1", + "cdnName": "cdn1", + "domainName": "infra.ciab.test", + "guid": null, + "hostName": "trafficvault", + "httpsPort": 8088, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.0.1/22", + "gateway": "127.0.0.1", + "serviceAddress": true + }, + { + "address": "2345:1234:12:b::12/64", + "gateway": "2345:1234:12:b::12", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 1500, + "name": "eth1", + "routerHostName": "router15", + "routerPort": "9014" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "ONLINE", + "tcpPort": 8088, + "type": "RIAK", + "updPending": false, + "xmppId": "", + "xmppPasswd": "" + }, + { + "cachegroup": "multiOriginCachegroup", + "cdnName": "cdn1", + "domainName": "ga.denver.kabletown.net", + "guid": null, + "hostName": "denver-mso-org-01", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.0.1/30", + "gateway": "127.0.0.1", + "serviceAddress": true + }, + { + "address": "2345:1234:12:8::20/64", + "gateway": "2345:1234:12:8::20", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router16", + "routerPort": "9015" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["MSO"], + "rack": "RR 119.02", + "revalPending": true, + "status": "REPORTED", + "tcpPort": 80, + "type": "ORG", + "updPending": true, + "xmppId": "denver-mso-org-01\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + }, + { + "cachegroup": "multiOriginCachegroup", + "cdnName": "cdn1", + "domainName": "ga.denver.kabletown.net", + "guid": null, + "hostName": "test-mso-org-01", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.4.1/30", + "gateway": "127.0.4.1", + "serviceAddress": true + }, + { + "address": "2345:1244:12:8::20/64", + "gateway": "2345:1244:12:8::20", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router16", + "routerPort": "9015" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["MSO"], + "rack": "RR 119.02", + "revalPending": false, + "status": "ONLINE", + "tcpPort": 80, + "type": "ORG", + "updPending": false, + "xmppId": "test-mso-org-01\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + }, + { + "cachegroup": "multiOriginCachegroup", + "cdnName": "cdn2", + "domainName": "ga.denver.kabletown.net", + "guid": null, + "hostName": "denver-mso-org-02", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.0.1/30", + "gateway": "127.0.0.1", + "serviceAddress": true + }, + { + "address": "2345:1234:12:8::20/64", + "gateway": "2345:1234:12:8::20", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router17", + "routerPort": "9016" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["MSO-CDN2"], + "rack": "RR 119.02", + "revalPending": true, + "status": "REPORTED", + "tcpPort": 80, + "type": "ORG", + "updPending": true, + "xmppId": "denver-mso-org-02\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + }, + { + "cachegroup": "cachegroup3", + "cdnName": "cdn1", + "domainName": "kabletown2.net", + "guid": null, + "hostName": "edge1-cdn1-cg3", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "::13/64", + "gateway": "2345:1234:12:b::13", + "serviceAddress": false + }, + { + "address": "127.0.0.100/24", + "gateway": "127.0.0.4", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router18", + "routerPort": "9017" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 81, + "type": "EDGE", + "updPending": false, + "xmppId": "", + "xmppPasswd": "X" + }, + { + "cachegroup": "cachegroup3", + "cdnName": "cdn1", + "domainName": "kabletown2.net", + "guid": null, + "hostName": "edge2-cdn1-cg3", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "::14/64", + "gateway": "2345:1234:12:b::13", + "serviceAddress": false + }, + { + "address": "127.0.0.101/24", + "gateway": "127.0.0.4", + "serviceAddress": true + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router19", + "routerPort": "9018" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 81, + "type": "EDGE", + "updPending": false, + "xmppId": "", + "xmppPasswd": "X" + }, + { + "cachegroup": "topology-edge-cg-01", + "cdnName": "cdn1", + "domainName": "edge-01.forked-topology.kabletown.net", + "hostName": "topology-edge-01", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2345:1234:12:2::4/64", + "gateway": "2345:1234:12:2::4", + "serviceAddress": false + }, + { + "address": "127.0.0.19/30", + "gateway": "127.0.0.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router20", + "routerPort": "9019" + } + ], + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false + }, + { + "cachegroup": "topology-edge-cg-02", + "cdnName": "cdn1", + "domainName": "edge-02.forked-topology.kabletown.net", + "hostName": "topology-edge-02", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2345:1234:12:2::4/64", + "gateway": "2345:1234:12:2::4", + "serviceAddress": false + }, + { + "address": "127.0.0.20/30", + "gateway": "127.0.0.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router21", + "routerPort": "9020" + } + ], + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false + }, + { + "cachegroup": "topology-mid-cg-04", + "cdnName": "cdn1", + "domainName": "mid-04.forked-topology.kabletown.net", + "hostName": "topology-mid-04", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2345:1234:12:2::4/64", + "gateway": "2345:1234:12:2::4", + "serviceAddress": false + }, + { + "address": "127.0.0.13/30", + "gateway": "127.0.0.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router22", + "routerPort": "9021" + } + ], + "physLocation": "Denver", + "profileNames": ["MID1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "MID", + "updPending": false + }, + { + "cachegroup": "dtrc1", + "cdnName": "cdn1", + "domainName": "kabletown.net", + "hostName": "dtrc-mid-01", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::2/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.2/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router23", + "routerPort": "9022" + } + ], + "physLocation": "Denver", + "profileNames": ["MID1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "MID", + "updPending": false + }, + { + "cachegroup": "dtrc1", + "cdnName": "cdn1", + "domainName": "kabletown.net", + "hostName": "dtrc-mid-02", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::3/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.3/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router24", + "routerPort": "9023" + } + ], + "physLocation": "Denver", + "profileNames": ["MID1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "MID", + "updPending": false + }, + { + "cachegroup": "dtrc1", + "cdnName": "cdn1", + "domainName": "kabletown.net", + "hostName": "dtrc-mid-03", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::4/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.4/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router25", + "routerPort": "9024" + } + ], + "physLocation": "Denver", + "profileNames": ["MID1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "MID", + "updPending": false + }, + { + "cachegroup": "dtrc2", + "cdnName": "cdn1", + "domainName": "kabletown.net", + "hostName": "dtrc-edge-01", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::5/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.5/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router26", + "routerPort": "9025" + } + ], + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false + }, + { + "cachegroup": "dtrc2", + "cdnName": "cdn1", + "domainName": "kabletown.net", + "hostName": "dtrc-edge-02", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::6/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.6/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router27", + "routerPort": "9026" + } + ], + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false + }, + { + "cachegroup": "dtrc2", + "cdnName": "cdn1", + "domainName": "kabletown.net", + "hostName": "dtrc-edge-03", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::7/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.7/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router28", + "routerPort": "9027" + } + ], + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false + }, + { + "cachegroup": "dtrc3", + "cdnName": "cdn1", + "domainName": "kabletown.net", + "hostName": "dtrc-edge-04", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::8/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.8/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router29", + "routerPort": "9028" + } + ], + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false + }, + { + "cachegroup": "dtrc3", + "cdnName": "cdn1", + "domainName": "kabletown.net", + "hostName": "dtrc-edge-05", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::9/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.9/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router30", + "routerPort": "9029" + } + ], + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false + }, + { + "cachegroup": "dtrc3", + "cdnName": "cdn1", + "domainName": "kabletown.net", + "hostName": "dtrc-edge-06", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::10/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.10/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router31", + "routerPort": "9030" + } + ], + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false + }, + { + "cachegroup": "dtrc2", + "cdnName": "cdn2", + "domainName": "kabletown.net", + "hostName": "dtrc-edge-07", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::11/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.11/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router32", + "routerPort": "9031" + } + ], + "physLocation": "Denver", + "profileNames": ["CDN2_EDGE"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false + }, + { + "cachegroup": "dtrc3", + "cdnName": "cdn2", + "domainName": "kabletown.net", + "hostName": "dtrc-edge-08", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::12/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.12/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router33", + "routerPort": "9032" + } + ], + "physLocation": "Denver", + "profileNames": ["CDN2_EDGE"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false + }, + { + "cachegroup": "dtrc1", + "cdnName": "cdn2", + "domainName": "kabletown.net", + "hostName": "dtrc-mid-04", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::13/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.13/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router34", + "routerPort": "9033" + } + ], + "physLocation": "Denver", + "profileNames": ["CDN2_MID"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "MID", + "updPending": false + }, + { + "cachegroup": "cachegroup3", + "cdnName": "cdn1", + "domainName": "kabletown.net", + "hostName": "edgeInCachegroup3", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::12/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.13/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router35", + "routerPort": "9034" + } + ], + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false + }, + { + "cachegroup": "parentCachegroup", + "cdnName": "cdn2", + "domainName": "kabletown.net", + "hostName": "midInParentCachegroup", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::12/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.14/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router36", + "routerPort": "9035" + } + ], + "physLocation": "Denver", + "profileNames": ["CDN2_MID"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "MID", + "updPending": false + }, + { + "cachegroup": "secondaryCachegroup", + "cdnName": "cdn2", + "domainName": "kabletown.net", + "hostName": "midInSecondaryCachegroup", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::12/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.15/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router37", + "routerPort": "9036" + } + ], + "physLocation": "Denver", + "profileNames": ["CDN2_MID"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "MID", + "updPending": false + }, + { + "cachegroup": "secondaryCachegroup", + "cdnName": "cdn1", + "domainName": "kabletown.net", + "hostName": "midInSecondaryCachegroupInCDN1", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:deed:beef::12/64", + "gateway": "2001:db8:deed:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.7.15/24", + "gateway": "192.0.7.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router38", + "routerPort": "9037" + } + ], + "physLocation": "Denver", + "profileNames": ["MID1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "MID", + "updPending": false + }, + { + "cachegroup": "cdn1-only", + "cdnName": "cdn1", + "domainName": "foo.kabletown.net", + "guid": null, + "hostName": "edge-in-cdn1-only", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "192.0.2.88/24", + "gateway": "192.0.2.1", + "serviceAddress": true + }, + { + "address": "2001:db8:f33d:beef::2/64", + "gateway": "2001:db8:f33d:beef::1", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router39", + "routerPort": "9038" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false, + "xmppId": "", + "xmppPasswd": "" + }, + { + "cachegroup": "fallback1", + "cdnName": "cdn2", + "domainName": "kabletown.net", + "hostName": "edgeInFallback1", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::12/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.16/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router40", + "routerPort": "9039" + } + ], + "physLocation": "Denver", + "profileNames": ["CDN2_EDGE"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false + }, + { + "cachegroup": "fallback2", + "cdnName": "cdn2", + "domainName": "kabletown.net", + "hostName": "edgeInFallback2", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::12/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.17/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router41", + "routerPort": "9040" + } + ], + "physLocation": "Denver", + "profileNames": ["CDN2_EDGE"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false + }, + { + "cachegroup": "parentCachegroup2", + "cdnName": "cdn2", + "domainName": "kabletown.net", + "hostName": "midInParentCachegroup2", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::12/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.18/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router42", + "routerPort": "9041" + } + ], + "physLocation": "Denver", + "profileNames": ["CDN2_MID"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "MID", + "updPending": false + }, + { + "cachegroup": "topology-edge-cg-01", + "cdnName": "cdn2", + "domainName": "kabletown.net", + "hostName": "edgeInTopologyEdgeCg01", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::12/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.19/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router43", + "routerPort": "9042" + } + ], + "physLocation": "Denver", + "profileNames": ["CDN2_EDGE"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false + }, + { + "cachegroup": "topology-edge-cg-02", + "cdnName": "cdn2", + "domainName": "kabletown.net", + "hostName": "edgeInTopologyEdgeCg02", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::12/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.20/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router44", + "routerPort": "9043" + } + ], + "physLocation": "Denver", + "profileNames": ["CDN2_EDGE"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false + }, + { + "cachegroup": "topology-mid-cg-01", + "cdnName": "cdn2", + "domainName": "kabletown.net", + "hostName": "midInTopologyMidCg01", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::12/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.21/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router45", + "routerPort": "9044" + } + ], + "physLocation": "Denver", + "profileNames": ["CDN2_MID"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "MID", + "updPending": false + }, + { + "cachegroup": "topology-mid-cg-01", + "cdnName": "cdn1", + "domainName": "kabletown.net", + "hostName": "midInTopologyMidCg01InCDN1", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:de4d:beef::12/64", + "gateway": "2001:db8:de4d:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.12.21/24", + "gateway": "192.0.12.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router46", + "routerPort": "9045" + } + ], + "physLocation": "Denver", + "profileNames": ["MID1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "MID", + "updPending": false + }, + { + "cachegroup": "topology-mid-cg-02", + "cdnName": "cdn2", + "domainName": "kabletown.net", + "hostName": "midInTopologyMidCg02", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::12/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.22/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router47", + "routerPort": "9046" + } + ], + "physLocation": "Denver", + "profileNames": ["CDN2_MID"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "MID", + "updPending": false + }, + { + "cachegroup": "topology-mid-cg-03", + "cdnName": "cdn2", + "domainName": "kabletown.net", + "hostName": "midInTopologyMidCg03", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::12/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.23/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router48", + "routerPort": "9047" + } + ], + "physLocation": "Denver", + "profileNames": ["CDN2_MID"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "MID", + "updPending": false + }, + { + "cachegroup": "topology-mid-cg-04", + "cdnName": "cdn2", + "domainName": "kabletown.net", + "hostName": "midInTopologyMidCg04", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::12/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.24/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router49", + "routerPort": "9048" + } + ], + "physLocation": "Denver", + "profileNames": ["CDN2_MID"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "MID", + "updPending": false + }, + { + "cachegroup": "topology-mid-cg-05", + "cdnName": "cdn2", + "domainName": "kabletown.net", + "hostName": "midInTopologyMidCg05", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::12/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.25/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router50", + "routerPort": "9049" + } + ], + "physLocation": "Denver", + "profileNames": ["CDN2_MID"], + "rack": "RR 119.02", + "revalPending": true, + "status": "OFFLINE", + "tcpPort": 80, + "type": "MID", + "updPending": true + }, + { + "cachegroup": "topology-mid-cg-06", + "cdnName": "cdn2", + "domainName": "kabletown.net", + "hostName": "midInTopologyMidCg06", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::12/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.26/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router51", + "routerPort": "9050" + } + ], + "physLocation": "Denver", + "profileNames": ["CDN2_MID"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "MID", + "updPending": false + }, + { + "cachegroup": "topology-mid-cg-07", + "cdnName": "cdn2", + "domainName": "kabletown.net", + "hostName": "midInTopologyMidCg07", + "httpsPort": 443, + "interfaces": [ + { + "ipAddresses": [ + { + "address": "2001:db8:dead:beef::12/64", + "gateway": "2001:db8:dead:beef::1", + "serviceAddress": false + }, + { + "address": "192.0.2.27/24", + "gateway": "192.0.2.1", + "serviceAddress": true + } + ], + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router52", + "routerPort": "9051" + } + ], + "physLocation": "Denver", + "profileNames": ["CDN2_MID"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "MID", + "updPending": false + }, + { + "cachegroup": "cachegroup1", + "cdnName": "cdn1", + "domainName": "test-ds-server-assignments", + "hostName": "test-ds-server-assignments", + "httpsPort": 443, + "interfaces": [ + { + "name": "eth0", + "ipAddresses": [ + { + "address": "198.51.100.1", + "serviceAddress": true + } + ], + "mtu": 1280, + "routerHostName": "router53", + "routerPort": "9052" + } + ], + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "revalPending": false, + "status": "ONLINE", + "tcpPort": 80, + "type": "EDGE", + "updPending": false + }, + { + "cachegroup": "cachegroup1", + "cdnName": "cdn1", + "domainName": "ga.atlanta.kabletown.net", + "guid": null, + "hostName": "atlanta-edge-16", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.1.0.21/30", + "gateway": "127.1.0.21", + "serviceAddress": true + }, + { + "address": "2346:1234:12:8::1/64", + "gateway": "2346:1234:12:8::1", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router54", + "routerPort": "9000" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["EDGE1", "ATS_EDGE_TIER_CACHE"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false, + "xmppId": "atlanta-edge-16\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + }, + { + "cachegroup": "parentCachegroup3", + "cdnName": "cdn1", + "domainName": "ga.atlanta.kabletown.net", + "guid": null, + "hostName": "atlanta-mid-02", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.1.0.2/30", + "gateway": "127.1.0.2", + "serviceAddress": true + }, + { + "address": "2346:1234:12:9::10/64", + "gateway": "2346:1234:12:9::10", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router55", + "routerPort": "9011" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["MID1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "MID", + "updPending": false, + "xmppId": "atlanta-mid-02\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + }, + { + "cachegroup": "cachegroup1", + "cdnName": "cdn1", + "domainName": "ga.atlanta.kabletown.net", + "guid": null, + "hostName": "revalpnd-legacy-true", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.43.1/30", + "gateway": "127.0.43.1", + "serviceAddress": true + }, + { + "address": "2345:1234:43:8::1/64", + "gateway": "2345:1234:43:8::1", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router1", + "routerPort": "9000" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": true, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false, + "xmppId": "revalpnd-legacy-true\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + }, + { + "cachegroup": "cachegroup1", + "cdnName": "cdn1", + "domainName": "ga.atlanta.kabletown.net", + "guid": null, + "hostName": "updatepnd-legacy-true", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.44.1/30", + "gateway": "127.0.44.1", + "serviceAddress": true + }, + { + "address": "2345:1234:44:8::1/64", + "gateway": "2345:1234:44:8::1", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router1", + "routerPort": "9000" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": true, + "xmppId": "revalpnd-legaacy-true\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + }, + { + "cachegroup": "cachegroup1", + "cdnName": "cdn1", + "configUpdateTime": "2022-01-01T17:00:00-07:00", + "configApplyTime": "1969-12-31T17:00:00-07:00", + "domainName": "ga.atlanta.kabletown.net", + "guid": null, + "hostName": "config-update-time", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.45.1/30", + "gateway": "127.0.45.1", + "serviceAddress": true + }, + { + "address": "2345:1234:45:8::1/64", + "gateway": "2345:1234:45:8::1", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router1", + "routerPort": "9000" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false, + "xmppId": "config-update-time\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + }, + { + "cachegroup": "cachegroup1", + "cdnName": "cdn1", + "configUpdateTime": "2022-01-01T17:00:00-07:00", + "configApplyTime": "1969-12-31T17:00:00-07:00", + "domainName": "ga.atlanta.kabletown.net", + "guid": null, + "hostName": "config-update-time-no-updpend", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.45.2/30", + "gateway": "127.0.45.2", + "serviceAddress": true + }, + { + "address": "2345:1234:45:8::2/64", + "gateway": "2345:1234:45:8::2", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router1", + "routerPort": "9000" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "xmppId": "config-update-time-no-updpend\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + }, + { + "cachegroup": "cachegroup1", + "cdnName": "cdn1", + "domainName": "ga.atlanta.kabletown.net", + "guid": null, + "hostName": "reval-update-time", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.46.1/30", + "gateway": "127.0.46.1", + "serviceAddress": true + }, + { + "address": "2345:1234:46:8::1/64", + "gateway": "2345:1234:46:8::1", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router1", + "routerPort": "9000" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "revalUpdateTime": "2022-01-01T17:00:00-07:00", + "revalApplyTime": "1969-12-31T17:00:00-07:00", + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false, + "xmppId": "config-update-time\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + }, + { + "cachegroup": "cachegroup1", + "cdnName": "cdn1", + "domainName": "ga.atlanta.kabletown.net", + "guid": null, + "hostName": "reval-update-time-no-revalpend", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.46.2/30", + "gateway": "127.0.46.2", + "serviceAddress": true + }, + { + "address": "2345:1234:46:8::2/64", + "gateway": "2345:1234:46:8::2", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router1", + "routerPort": "9000" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalUpdateTime": "2022-01-01T17:00:00-07:00", + "revalApplyTime": "1969-12-31T17:00:00-07:00", + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false, + "xmppId": "config-update-time\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + }, + { + "cachegroup": "cachegroup1", + "cdnName": "cdn1", + "configUpdateTime": "2022-01-01T17:00:00-07:00", + "configApplyTime": "1969-12-31T17:00:00-07:00", + "domainName": "ga.atlanta.kabletown.net", + "guid": null, + "hostName": "config-reval-update-time", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.47.1/30", + "gateway": "127.0.47.1", + "serviceAddress": true + }, + { + "address": "2345:1234:47:8::1/64", + "gateway": "2345:1234:47:8::1", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router1", + "routerPort": "9000" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "revalUpdateTime": "2022-01-01T17:00:00-07:00", + "revalApplyTime": "1969-12-31T17:00:00-07:00", + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "updPending": false, + "xmppId": "config-update-time\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + }, + { + "cachegroup": "cachegroup1", + "cdnName": "cdn1", + "configUpdateTime": "2022-01-01T17:00:00-07:00", + "configApplyTime": "1969-12-31T17:00:00-07:00", + "domainName": "ga.atlanta.kabletown.net", + "guid": null, + "hostName": "config-reval-update-time-only", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.47.2/30", + "gateway": "127.0.47.2", + "serviceAddress": true + }, + { + "address": "2345:1234:47:8::2/64", + "gateway": "2345:1234:47:8::2", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router1", + "routerPort": "9000" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalUpdateTime": "2022-01-01T17:00:00-07:00", + "revalApplyTime": "1969-12-31T17:00:00-07:00", + "status": "REPORTED", + "tcpPort": 80, + "type": "EDGE", + "xmppId": "config-update-time\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + }, + { + "cachegroup": "cachegroup1", + "cdnName": "cdn1", + "domainName": "ga.atlanta.kabletown.net", + "guid": null, + "hostName": "admin-down-server", + "httpsPort": 443, + "iloIpAddress": "", + "iloIpGateway": "", + "iloIpNetmask": "", + "iloPassword": "", + "iloUsername": "", + "interfaces": [ + { + "ipAddresses": [ + { + "address": "127.0.0.22/30", + "gateway": "127.0.0.22", + "serviceAddress": true + }, + { + "address": "2345:1234:13:8::1/64", + "gateway": "2345:1234:13:8::1", + "serviceAddress": false + } + ], + "maxBandwidth": null, + "monitor": true, + "mtu": 9000, + "name": "bond0", + "routerHostName": "router1", + "routerPort": "9000" + } + ], + "mgmtIpAddress": "", + "mgmtIpGateway": "", + "mgmtIpNetmask": "", + "offlineReason": null, + "physLocation": "Denver", + "profileNames": ["EDGE1"], + "rack": "RR 119.02", + "revalPending": false, + "status": "ADMIN_DOWN", + "tcpPort": 80, + "type": "EDGE", + "updPending": false, + "xmppId": "atlanta-edge-01\\\\@ocdn.kabletown.net", + "xmppPasswd": "X" + } + ], + "serverCapabilities": [ + { + "name": "foo" + }, + { + "name": "bar" + }, + { + "name": "ram" + }, + { + "name": "disk" + }, + { + "name": "asdf" + }, + { + "name": "blah" + } + ], + "serverServerCapabilities": [ + { + "serverHostName": "atlanta-org-1", + "serverCapability": "foo" + }, + { + "serverHostName": "atlanta-org-2", + "serverCapability": "bar" + }, + { + "serverHostName": "dtrc-mid-01", + "serverCapability": "ram" + }, + { + "serverHostName": "dtrc-mid-01", + "serverCapability": "disk" + }, + { + "serverHostName": "dtrc-mid-02", + "serverCapability": "ram" + }, + { + "serverHostName": "dtrc-mid-02", + "serverCapability": "disk" + }, + { + "serverHostName": "dtrc-edge-01", + "serverCapability": "ram" + }, + { + "serverHostName": "dtrc-edge-01", + "serverCapability": "disk" + }, + { + "serverHostName": "dtrc-edge-01", + "serverCapability": "asdf" + }, + { + "serverHostName": "dtrc-edge-02", + "serverCapability": "ram" + }, + { + "serverHostName": "dtrc-edge-02", + "serverCapability": "disk" + }, + { + "serverHostName": "dtrc-edge-04", + "serverCapability": "ram" + }, + { + "serverHostName": "dtrc-edge-04", + "serverCapability": "disk" + }, + { + "serverHostName": "dtrc-edge-05", + "serverCapability": "ram" + }, + { + "serverHostName": "dtrc-edge-05", + "serverCapability": "disk" + }, + { + "serverHostName": "dtrc-edge-07", + "serverCapability": "asdf" + }, + { + "serverHostName": "dtrc-edge-08", + "serverCapability": "asdf" + }, + { + "serverHostName": "dtrc-mid-04", + "serverCapability": "asdf" + }, + { + "serverHostName": "atlanta-edge-16", + "serverCapability": "blah" + }, + { + "serverHostName": "edge-in-cdn1-only", + "serverCapability": "ram" + } + ], + "serviceCategories": [ + { + "name": "serviceCategory1" + }, + { + "name": "barServiceCategory2" + } + ], + "staticdnsentries": [ + { + "address": "192.168.0.1", + "cachegroup": "cachegroup2", + "deliveryservice": "ds1", + "host": "host2", + "type": "A_RECORD", + "ttl": 10 + }, + { + "address": "this.is.a.hostname.", + "cachegroup": "cachegroup1", + "deliveryservice": "ds1", + "host": "host1", + "type": "CNAME_RECORD", + "ttl": 0 + }, + { + "address": "2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "cachegroup": "cachegroup2", + "deliveryservice": "ds1", + "host": "host3", + "ttl": 10, + "type": "AAAA_RECORD" + }, + { + "address": "192.168.0.3", + "cachegroup": "cachegroup2", + "deliveryservice": "basic-ds-in-cdn2", + "host": "cdnlock-test-delete-host", + "type": "A_RECORD", + "ttl": 10 + }, + { + "address": "192.168.0.3", + "cachegroup": "cachegroup2", + "deliveryservice": "basic-ds-in-cdn2", + "host": "cdnlock-negtest-delete-host", + "type": "A_RECORD", + "ttl": 10 + } + ], + "statuses": [ + { + "description": "Edge: 12M will not include caches in this state in CCR config files. Mid: N/A for now", + "name": "CCR_IGNORE" + }, + { + "name": "TEST_NULL_DESCRIPTION" + } + ], + "tenants": [ + { + "active": true, + "name": "tenant1", + "parentName": "root" + }, + { + "active": false, + "name": "tenant2", + "parentName": "tenant1" + }, + { + "active": true, + "name": "tenant3", + "parentName": "tenant2" + }, + { + "active": true, + "name": "tenant4", + "parentName": "root" + } + ], + "topologies": [ + { + "name": "mso-topology", + "description": "a multi-site origin topology", + "nodes": [ + { + "cachegroup": "multiOriginCachegroup", + "parents": [] + }, + { + "cachegroup": "parentCachegroup", + "parents": [ + 0 + ] + }, + { + "cachegroup": "secondaryCachegroup", + "parents": [ + 0 + ] + }, + { + "cachegroup": "cachegroup3", + "parents": [ + 1, + 2 + ] + } + ] + }, + { + "name": "another-topology", + "description": "another topology", + "nodes": [ + { + "cachegroup": "parentCachegroup", + "parents": [] + }, + { + "cachegroup": "cachegroup1", + "parents": [ + 0 + ] + }, + { + "cachegroup": "secondaryCachegroup", + "parents": [] + }, + { + "cachegroup": "cachegroup2", + "parents": [ + 2 + ] + } + ] + }, + { + "name": "secondary-parents", + "description": "A topology with secondary parents", + "nodes": [ + { + "cachegroup": "parentCachegroup", + "parent": "", + "secParent": "", + "parents": [] + }, + { + "cachegroup": "cachegroup1", + "parent": "parentCachegroup", + "secParent": "secondaryCachegroup", + "parents": [ + 0, + 2 + ] + }, + { + "cachegroup": "secondaryCachegroup", + "parent": "", + "secParent": "", + "parents": [] + }, + { + "cachegroup": "fallback1", + "parent": "secondaryCachegroup", + "secParent": "parentCachegroup", + "parents": [ + 2, + 0 + ] + }, + { + "cachegroup": "fallback2", + "parent": "secondaryCachegroup", + "secParent": "parentCachegroup", + "parents": [ + 2, + 0 + ] + } + ] + }, + { + "name": "4-tiers", + "description": "A 4-tier topology", + "nodes": [ + { + "cachegroup": "parentCachegroup", + "parents": [] + }, + { + "cachegroup": "parentCachegroup2", + "parents": [ + 0 + ] + }, + { + "cachegroup": "cachegroup1", + "parents": [ + 1 + ] + }, + { + "cachegroup": "secondaryCachegroup", + "parents": [ + 1, + 0 + ] + }, + { + "cachegroup": "fallback1", + "parents": [ + 3 + ] + } + ] + }, + { + "name": "forked-topology", + "description": "This topology stems from 2 ancestors", + "nodes": [ + { + "cachegroup": "topology-edge-cg-01", + "parents": [ + 2 + ] + }, + { + "cachegroup": "topology-edge-cg-02", + "parents": [ + 6 + ] + }, + { + "cachegroup": "topology-mid-cg-01", + "parents": [ + 3 + ] + }, + { + "cachegroup": "topology-mid-cg-02", + "parents": [ + 4 + ] + }, + { + "cachegroup": "topology-mid-cg-03", + "parents": [ + 5 + ] + }, + { + "cachegroup": "topology-mid-cg-04", + "parents": [] + }, + { + "cachegroup": "topology-mid-cg-05", + "parents": [ + 7 + ] + }, + { + "cachegroup": "topology-mid-cg-06", + "parents": [ + 8 + ] + }, + { + "cachegroup": "topology-mid-cg-07", + "parents": [ + 9 + ] + }, + { + "cachegroup": "multiOriginCachegroup", + "parents": [] + } + ] + }, + { + "name": "top-for-ds-req", + "description": "a topology", + "nodes": [ + { + "cachegroup": "dtrc1", + "parents": [] + }, + { + "cachegroup": "dtrc2", + "parents": [ + 0 + ] + }, + { + "cachegroup": "dtrc3", + "parents": [ + 0 + ] + } + ] + }, + { + "name": "top-for-ds-req2", + "description": "a topology", + "nodes": [ + { + "cachegroup": "multiOriginCachegroup", + "parents": [] + }, + { + "cachegroup": "dtrc1", + "parents": [ + 0 + ] + }, + { + "cachegroup": "dtrc2", + "parents": [ + 1 + ] + } + ] + }, + { + "name": "top-used-by-cdn1-and-cdn2", + "description": "a topology", + "nodes": [ + { + "cachegroup": "dtrc1", + "parents": [] + }, + { + "cachegroup": "dtrc2", + "parents": [ + 0 + ] + }, + { + "cachegroup": "dtrc3", + "parents": [ + 0 + ] + } + ] + }, + { + "name": "top-with-caches-in-cdn1", + "description": "a topology", + "nodes": [ + { + "cachegroup": "cdn1-only", + "parents": [] + } + ] + }, + { + "name": "top-with-no-mids", + "description": "A topology that has no MID_LOC cachegroups", + "nodes": [ + { + "cachegroup": "cachegroup1", + "parents": [] + } + ] + } + ], + "types": [ + { + "description": "Host header regular expression", + "name": "HOST_REGEXP", + "useInTable": "regex" + }, + { + "description": "DNS Content routing, RAM cache, National", + "name": "DNS_LIVE_NATNL", + "useInTable": "deliveryservice" + }, + { + "description": "Other CDN (CDS-IS, Akamai, etc)", + "name": "OTHER_CDN", + "useInTable": "server" + }, + { + "description": "Client-Controlled Steering Delivery Service", + "name": "CLIENT_STEERING", + "useInTable": "deliveryservice" + }, + { + "description": "influxdb type", + "name": "INFLUXDB", + "useInTable": "server" + }, + { + "description": "riak type", + "name": "RIAK", + "useInTable": "server" + }, + { + "description": "Origin", + "name": "ORG", + "useInTable": "server" + }, + { + "description": "HTTP Content routing cache in RAM ", + "name": "HTTP_LIVE", + "useInTable": "deliveryservice" + }, + { + "description": "Active Directory User", + "name": "ACTIVE_DIRECTORY", + "useInTable": "tm_user" + }, + { + "description": "federation type resolve4", + "name": "RESOLVE4", + "useInTable": "federation" + }, + { + "description": "Static DNS A entry", + "name": "A_RECORD", + "useInTable": "staticdnsentry" + }, + { + "description": "Local User", + "name": "LOCAL", + "useInTable": "tm_user" + }, + { + "description": "Weighted steering target", + "name": "STEERING_WEIGHT", + "useInTable": "steering_target" + }, + { + "description": "HTTP Content routing, RAM cache, National", + "name": "HTTP_LIVE_NATNL", + "useInTable": "deliveryservice" + }, + { + "description": "Ops hosts for management", + "name": "TOOLS_SERVER", + "useInTable": "server" + }, + { + "description": "Path regular expression", + "name": "PATH_REGEXP", + "useInTable": "regex" + }, + { + "description": "Static DNS CNAME entry", + "name": "CNAME_RECORD", + "useInTable": "staticdnsentry" + }, + { + "description": "Kabletown Content Router", + "name": "CCR", + "useInTable": "server" + }, + { + "description": "Origin Cachegroup", + "name": "ORG_LOC", + "useInTable": "cachegroup" + }, + { + "description": "Mid Cachegroup", + "name": "MID_LOC", + "useInTable": "cachegroup" + }, + { + "description": "Edge Cache", + "name": "EDGE", + "useInTable": "server" + }, + { + "description": "Ordered steering target", + "name": "STEERING_ORDER", + "useInTable": "steering_target" + }, + { + "description": "DNS Content Routing", + "name": "DNS", + "useInTable": "deliveryservice" + }, + { + "description": "federation type resolve6", + "name": "RESOLVE6", + "useInTable": "federation" + }, + { + "description": "Static DNS AAAA entry", + "name": "AAAA_RECORD", + "useInTable": "staticdnsentry" + }, + { + "description": "HTTP Content Routing, no caching", + "name": "HTTP_NO_CACHE", + "useInTable": "deliveryservice" + }, + { + "description": "any_map type", + "name": "ANY_MAP", + "useInTable": "deliveryservice" + }, + { + "description": "Steering Delivery Service", + "name": "STEERING", + "useInTable": "deliveryservice" + }, + { + "description": "Edge Cachegroup", + "name": "EDGE_LOC", + "useInTable": "cachegroup" + }, + { + "description": "HTTP Content routing cache ", + "name": "HTTP", + "useInTable": "deliveryservice" + }, + { + "description": "Mid Tier Cache", + "name": "MID", + "useInTable": "server" + }, + { + "description": "Traffic Monitor (Rascal)", + "name": "RASCAL", + "useInTable": "server" + } + ], + "users": [ + { + "addressLine1": "address of admin", + "addressLine2": "", + "city": "Anywhere", + "company": "Comcast", + "country": "USA", + "email": "admin@example.com", + "fullName": "Fred the admin", + "gid": 0, + "localPasswd": "pa$$word", + "confirmLocalPasswd": "pa$$word", + "newUser": false, + "phoneNumber": "810-555-9876", + "postalCode": "55443", + "publicSshKey": "", + "role": "admin", + "stateOrProvince": "LA", + "tenant": "root", + "token": "test", + "uid": 0, + "username": "adminuser" + }, + { + "addressLine1": "address of disallowed", + "addressLine2": "place", + "city": "somewhere", + "company": "else", + "country": "UK", + "email": "disallowed@example.com", + "fullName": "Me me", + "gid": 0, + "localPasswd": "pa$$word", + "confirmLocalPasswd": "pa$$word", + "newUser": false, + "phoneNumber": "", + "postalCode": "", + "publicSshKey": "", + "role": "disallowed", + "stateOrProvince": "", + "tenant": "tenant1", + "token": "quest", + "uid": 0, + "username": "disalloweduser" + }, + { + "addressLine1": "address of readonly", + "addressLine2": "place", + "city": "somewhere", + "company": "else", + "country": "UK", + "email": "readonly@example.com", + "fullName": "Readonly User", + "gid": 0, + "localPasswd": "pa$$word", + "confirmLocalPasswd": "pa$$word", + "newUser": false, + "phoneNumber": "", + "postalCode": "", + "publicSshKey": "", + "role": "read-only", + "stateOrProvince": "", + "tenant": "tenant1", + "uid": 0, + "username": "readonlyuser" + }, + { + "addressLine1": "address of tenant1user", + "addressLine2": "", + "city": "Anywhere", + "company": "Comcast", + "country": "USA", + "email": "tenant1user@example.com", + "fullName": "Fred the admin", + "gid": 0, + "localPasswd": "pa$$word", + "confirmLocalPasswd": "pa$$word", + "newUser": false, + "phoneNumber": "810-555-9876", + "postalCode": "55443", + "publicSshKey": "", + "role": "admin", + "stateOrProvince": "LA", + "tenant": "tenant1", + "uid": 0, + "username": "tenant1user" + }, + { + "addressLine1": "address of tenant2user", + "addressLine2": "", + "city": "Anywhere", + "company": "Comcast", + "country": "USA", + "email": "tenant2user@example.com", + "fullName": "Fred the admin", + "gid": 0, + "localPasswd": "pa$$word", + "confirmLocalPasswd": "pa$$word", + "newUser": false, + "phoneNumber": "810-555-9876", + "postalCode": "55443", + "publicSshKey": "", + "role": "admin", + "stateOrProvince": "LA", + "tenant": "tenant2", + "uid": 0, + "username": "tenant2user" + }, + { + "addressLine1": "address of admin", + "addressLine2": "", + "city": "Anywhere", + "company": "Comcast", + "country": "USA", + "email": "tenant3user@example.com", + "fullName": "Fred the admin", + "gid": 0, + "localPasswd": "pa$$word", + "confirmLocalPasswd": "pa$$word", + "newUser": false, + "phoneNumber": "810-555-9876", + "postalCode": "55443", + "publicSshKey": "", + "role": "admin", + "stateOrProvince": "LA", + "tenant": "tenant3", + "uid": 0, + "username": "tenant3user" + }, + { + "addressLine1": "address of admin", + "addressLine2": "", + "city": "Anywhere", + "company": "Comcast", + "country": "USA", + "email": "tenant4user@example.com", + "fullName": "Fred the admin", + "gid": 0, + "localPasswd": "pa$$word", + "confirmLocalPasswd": "pa$$word", + "newUser": false, + "phoneNumber": "810-555-9876", + "postalCode": "55443", + "publicSshKey": "", + "role": "admin", + "stateOrProvince": "LA", + "tenant": "tenant4", + "uid": 0, + "username": "tenant4user" + }, + { + "addressLine1": "address of ops", + "addressLine2": "place", + "city": "somewhere", + "company": "else", + "country": "UK", + "email": "ops@example.com", + "fullName": "Operations User", + "gid": 0, + "localPasswd": "pa$$word", + "confirmLocalPasswd": "pa$$word", + "newUser": false, + "phoneNumber": "", + "postalCode": "", + "publicSshKey": "", + "role": "operations", + "stateOrProvince": "", + "tenant": "root", + "uid": 0, + "username": "opsuser" + }, + { + "addressLine1": "address of steering", + "addressLine2": "place", + "city": "somewhere", + "company": "else", + "country": "UK", + "email": "steering@example.com", + "fullName": "Steering User", + "gid": 0, + "localPasswd": "pa$$word", + "confirmLocalPasswd": "pa$$word", + "newUser": false, + "phoneNumber": "", + "postalCode": "", + "publicSshKey": "", + "role": "steering", + "stateOrProvince": "", + "tenant": "root", + "uid": 0, + "username": "steering" + }, + { + "addressLine1": "address of ops user with lock", + "addressLine2": "place", + "city": "somewhere", + "company": "else", + "country": "US", + "email": "opswithlock@example.com", + "fullName": "Ops with Lock User", + "gid": 0, + "localPasswd": "pa$$word", + "confirmLocalPasswd": "pa$$word", + "newUser": false, + "phoneNumber": "", + "postalCode": "", + "publicSshKey": "", + "role": "operations", + "stateOrProvince": "", + "tenant": "root", + "uid": 0, + "username": "opslockuser" + } + ], + "steeringTargets": [ + { + "deliveryService": "ds1", + "target": "ds2", + "value": 42, + "type": "STEERING_WEIGHT" + }, + { + "deliveryService": "ds3", + "target": "ds4", + "value": 42, + "type": "STEERING_WEIGHT" + } + ], + "servercheck_extensions": [ + { + "name": "ILO_PING", + "version": "1.0.0", + "info_url": "-", + "script_file": "ToPingCheck.pl", + "isactive": 1, + "description": "", + "servercheck_short_name": "ILO", + "type": "CHECK_EXTENSION_BOOL" + }, + { + "name": "ORT_ERROR_COUNT", + "version": "1.0.0", + "info_url": "-", + "script_file": "ToORTCheck.pl", + "isactive": 1, + "description": "", + "servercheck_short_name": "ORT", + "type": "CHECK_EXTENSION_NUM" + } + ], + "serverchecks": [ + { + "servercheck_short_name": "ILO", + "host_name": "atlanta-edge-01", + "value": 1 + }, + { + "servercheck_short_name": "ORT", + "host_name": "atlanta-edge-01", + "value": 13 + } + ], + "invalidationJobs": [ + { + "deliveryService": "ds1", + "regex": "/.*", + "startTime": "2100-06-19T13:57:51Z", + "ttlHours": 72, + "invalidationType" : "REFRESH" + }, + { + "deliveryService": "ds1", + "regex": "/foo", + "startTime": "2100-06-19T13:57:51Z", + "ttlHours": 36, + "invalidationType" : "REFRESH" + }, + { + "deliveryService": "ds2", + "regex": "\\/some-path?.+\\.jpg", + "startTime": "2100-06-19T13:57:51Z", + "ttlHours": 144, + "invalidationType" : "REFRESH" + }, + { + "deliveryService": "ds1", + "regex": "/oldest", + "startTime": "1970-01-01T12:00:00Z", + "ttlHours": 2160, + "invalidationType" : "REFRESH" + }, + { + "deliveryService": "ds1", + "regex": "/older", + "startTime": "1970-01-01T12:00:00Z", + "ttlHours": 2160, + "invalidationType" : "REFRESH" + }, + { + "deliveryService": "ds1", + "regex": "/old", + "startTime": "1970-01-01T12:00:00Z", + "ttlHours": 2160, + "invalidationType" : "REFRESH" + }, + { + "deliveryService": "ds-forked-topology", + "regex": "/this-ds-is-in-cdn2", + "startTime": "2100-06-19T13:57:51Z", + "ttlHours": 2160, + "invalidationType" : "REFRESH" + }, + { + "deliveryService": "ds1", + "regex": "/.*", + "startTime": "2100-06-19T13:57:51Z", + "ttlHours": 72, + "invalidationType" : "REFETCH" + }, + { + "deliveryService": "ds1", + "regex": "/foo", + "startTime": "2100-06-19T13:57:51Z", + "ttlHours": 36, + "invalidationType" : "REFETCH" + } + ], + "statsSummaries": [ + { + "cdnName": "cdn1", + "deliveryServiceName": "all", + "statName": "daily_maxgbps", + "statValue": 5, + "summaryTime": "2019-01-01T00:00:00-06:00" + }, + { + "cdnName": "cdn1", + "deliveryServiceName": "all", + "statName": "daily_bytesserved", + "statValue": 1000, + "summaryTime": "2019-01-01T00:00:00-06:00" + } + ], + "capabilities": [ + { + "name": "test", + "description": "quest" + }, + { + "name": "foo", + "description": "bar" + } + ] +} diff --git a/traffic_ops/testing/api/v5/tenants_test.go b/traffic_ops/testing/api/v5/tenants_test.go new file mode 100644 index 0000000000..f619cb7fd0 --- /dev/null +++ b/traffic_ops/testing/api/v5/tenants_test.go @@ -0,0 +1,338 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "sort" + "strconv" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestTenants(t *testing.T) { + WithObjs(t, []TCObj{Tenants}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID request": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + utils.ResponseLengthGreaterOrEqual(1), validateTenantSort()), + }, + "OK when VALID ACTIVE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"active": {"true"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateTenantFields(map[string]interface{}{"Active": true})), + }, + "VALID when SORTORDER param is DESC": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"sortOrder": {"desc"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateTenantDescSort()), + }, + "FIRST RESULT when LIMIT=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateTenantPagination("limit")), + }, + "SECOND RESULT when LIMIT=1 OFFSET=1": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "offset": {"1"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateTenantPagination("offset")), + }, + "SECOND RESULT when LIMIT=1 PAGE=2": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"orderby": {"id"}, "limit": {"1"}, "page": {"2"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateTenantPagination("page")), + }, + "BAD REQUEST when INVALID LIMIT parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"-2"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID OFFSET parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "offset": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID PAGE parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"limit": {"1"}, "page": {"0"}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "POST": { + "OK when VALID request": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "active": true, + "name": "tenant5", + "parentName": "root", + "parentId": GetTenantID(t, "root")(), + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateTenantCreateUpdateFields(map[string]interface{}{"Name": "tenant5"})), + }, + }, + "PUT": { + "OK when VALID request": { + EndpointId: GetTenantID(t, "tenant4"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "active": false, + "name": "newname", + "parentName": "root", + "parentId": GetTenantID(t, "root")(), + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateTenantCreateUpdateFields(map[string]interface{}{"Name": "newname", "Active": false})), + }, + "BAD REQUEST when ROOT TENANT": { + EndpointId: GetTenantID(t, "root"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "active": false, + "name": "tenant1", + "parentName": "root", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "PRECONDITION FAILED when updating with IMS & IUS Headers": { + EndpointId: GetTenantID(t, "tenant2"), + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}}}, + RequestBody: map[string]interface{}{ + "active": false, + "name": "tenant2", + "parentName": "root", + "parentId": GetTenantID(t, "root")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + "PRECONDITION FAILED when updating with IFMATCH ETAG Header": { + EndpointId: GetTenantID(t, "tenant2"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "active": false, + "name": "tenant2", + "parentName": "root", + "parentId": GetTenantID(t, "root")(), + }, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}}}, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)), + }, + }, + "DELETE": { + "BAD REQUEST when TENANT HAS CHILDREN": { + EndpointId: GetTenantID(t, "tenant1"), + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + tenant := tc.Tenant{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &tenant) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET", "GET AFTER CHANGES": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetTenants(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.CreateTenant(tenant, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.UpdateTenant(testCase.EndpointId(), tenant, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.DeleteTenant(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateTenantFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Tenant response to not be nil.") + tenantResp := resp.([]tc.Tenant) + for field, expected := range expectedResp { + for _, tenant := range tenantResp { + switch field { + case "Active": + assert.Equal(t, expected, tenant.Active, "Expected Active to be %v, but got %b", expected, tenant.Active) + case "Name": + assert.Equal(t, expected, tenant.Name, "Expected Name to be %v, but got %s", expected, tenant.Name) + case "ParentName": + assert.Equal(t, expected, tenant.ParentName, "Expected ParentName to be %v, but got %s", expected, tenant.ParentName) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validateTenantCreateUpdateFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Tenant response to not be nil.") + tenantResp := resp.(tc.Tenant) + tenants := []tc.Tenant{tenantResp} + validateTenantFields(expectedResp)(t, toclientlib.ReqInf{}, tenants, tc.Alerts{}, nil) + } +} + +func validateTenantSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Tenant response to not be nil.") + var tenants []string + tenantResp := resp.([]tc.Tenant) + for _, tenant := range tenantResp { + tenants = append(tenants, tenant.Name) + } + assert.Equal(t, true, sort.StringsAreSorted(tenants), "List is not sorted by their names: %v", tenants) + } +} + +func validateTenantDescSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Tenant response to not be nil.") + tenantDescResp := resp.([]tc.Tenant) + var descSortedList []string + var ascSortedList []string + assert.RequireGreaterOrEqual(t, len(tenantDescResp), 2, "Need at least 2 Tenants in Traffic Ops to test desc sort, found: %d", len(tenantDescResp)) + // Get Tenants in the default ascending order for comparison. + tenantsAscResp, _, err := TOSession.GetTenants(client.RequestOptions{}) + assert.RequireNoError(t, err, "Unexpected error getting Tenants with default sort order: %v - alerts: %+v", err, tenantsAscResp.Alerts) + // Verify the response match in length, i.e. equal amount of Tenants. + assert.RequireEqual(t, len(tenantsAscResp.Response), len(tenantDescResp), "Expected descending order response length: %v, to match ascending order response length %v", len(tenantsAscResp.Response), len(tenantDescResp)) + // Insert Tenant names to the front of a new list, so they are now reversed to be in ascending order. + for _, tenant := range tenantDescResp { + descSortedList = append([]string{tenant.Name}, descSortedList...) + } + // Insert Tenant names by appending to a new list, so they stay in ascending order. + for _, tenant := range tenantsAscResp.Response { + ascSortedList = append(ascSortedList, tenant.Name) + } + assert.Exactly(t, ascSortedList, descSortedList, "Tenant responses are not equal after reversal: %v - %v", ascSortedList, descSortedList) + } +} + +func validateTenantPagination(paginationParam string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + paginationResp := resp.([]tc.Tenant) + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("orderby", "id") + respBase, _, err := TOSession.GetTenants(opts) + assert.RequireNoError(t, err, "Cannot get Tenants: %v - alerts: %+v", err, respBase.Alerts) + + tenants := respBase.Response + assert.RequireGreaterOrEqual(t, len(tenants), 3, "Need at least 3 Tenants in Traffic Ops to test pagination support, found: %d", len(tenants)) + switch paginationParam { + case "limit:": + assert.Exactly(t, tenants[:1], paginationResp, "expected GET Tenants with limit = 1 to return first result") + case "offset": + assert.Exactly(t, tenants[1:2], paginationResp, "expected GET Tenants with limit = 1, offset = 1 to return second result") + case "page": + assert.Exactly(t, tenants[1:2], paginationResp, "expected GET Tenants with limit = 1, page = 2 to return second result") + } + } +} + +func GetTenantID(t *testing.T, name string) func() int { + return func() int { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", name) + tenants, _, err := TOSession.GetTenants(opts) + assert.RequireNoError(t, err, "Get Tenants Request failed with error:", err) + assert.RequireEqual(t, 1, len(tenants.Response), "Expected response object length 1, but got %d", len(tenants.Response)) + return tenants.Response[0].ID + } +} + +func CreateTestTenants(t *testing.T) { + for _, tenant := range testData.Tenants { + resp, _, err := TOSession.CreateTenant(tenant, client.RequestOptions{}) + assert.RequireNoError(t, err, "Could not create Tenant '%s': %v - alerts: %+v", tenant.Name, err, resp.Alerts) + } +} + +func DeleteTestTenants(t *testing.T) { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("sortOrder", "desc") + tenants, _, err := TOSession.GetTenants(opts) + assert.NoError(t, err, "Cannot get Tenants: %v - alerts: %+v", err, tenants.Alerts) + + for _, tenant := range tenants.Response { + if tenant.Name == "root" { + continue + } + alerts, _, err := TOSession.DeleteTenant(tenant.ID, client.RequestOptions{}) + assert.NoError(t, err, "Unexpected error deleting Tenant '%s' (#%d): %v - alerts: %+v", tenant.Name, tenant.ID, err, alerts.Alerts) + // Retrieve the Tenant to see if it got deleted + opts.QueryParameters.Set("id", strconv.Itoa(tenant.ID)) + getTenants, _, err := TOSession.GetTenants(opts) + assert.NoError(t, err, "Error getting Tenant '%s' after deletion: %v - alerts: %+v", tenant.Name, err, getTenants.Alerts) + assert.Equal(t, 0, len(getTenants.Response), "Expected Tenant '%s' to be deleted, but it was found in Traffic Ops", tenant.Name) + } +} diff --git a/traffic_ops/testing/api/v5/todb_test.go b/traffic_ops/testing/api/v5/todb_test.go new file mode 100644 index 0000000000..2b09fa2903 --- /dev/null +++ b/traffic_ops/testing/api/v5/todb_test.go @@ -0,0 +1,453 @@ +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v5 + +import ( + "database/sql" + "fmt" + "os" + + "github.com/apache/trafficcontrol/lib/go-log" + "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/auth" +) + +var ( + db *sql.DB +) + +// OpenConnection ... +func OpenConnection() (*sql.DB, error) { + var err error + sslStr := "require" + if !Config.TrafficOpsDB.SSL { + sslStr = "disable" + } + + db, err = sql.Open("postgres", fmt.Sprintf("postgres://%s:%s@%s/%s?sslmode=%s", Config.TrafficOpsDB.User, Config.TrafficOpsDB.Password, Config.TrafficOpsDB.Hostname, Config.TrafficOpsDB.Name, sslStr)) + + if err != nil { + log.Errorf("opening database: %v\n", err) + return nil, fmt.Errorf("transaction failed: %s", err) + } + return db, err +} + +// SetupTestData ... +// TODO error does not need returned as this function can never return a non-nil error +func SetupTestData(*sql.DB) error { + var err error + + err = SetupRoles(db) + if err != nil { + fmt.Printf("\nError setting up roles %s - %s, %v\n", Config.TrafficOps.URL, Config.TrafficOps.Users.Admin, err) + os.Exit(1) + } + + err = SetupCapabilities(db) + if err != nil { + fmt.Printf("\nError setting up capabilities %s - %s, %v\n", Config.TrafficOps.URL, Config.TrafficOps.Users.Admin, err) + os.Exit(1) + } + + err = SetupRoleCapabilities(db) + if err != nil { + fmt.Printf("\nError setting up roleCapabilities %s - %s, %v\n", Config.TrafficOps.URL, Config.TrafficOps.Users.Admin, err) + os.Exit(1) + } + + err = SetupTenants(db) + if err != nil { + fmt.Printf("\nError setting up tenant %s - %s, %v\n", Config.TrafficOps.URL, Config.TrafficOps.Users.Admin, err) + os.Exit(1) + } + + err = SetupTmusers(db) + if err != nil { + fmt.Printf("\nError setting up tm_user %s - %s, %v\n", Config.TrafficOps.URL, Config.TrafficOps.Users.Admin, err) + os.Exit(1) + } + + err = SetupTypes(db) + if err != nil { + fmt.Printf("\nError setting up types %s - %s, %v\n", Config.TrafficOps.URL, Config.TrafficOps.Users.Admin, err) + os.Exit(1) + } + + err = SetupToExtensions(db) + if err != nil { + fmt.Printf("\nError setting up to_extensions %s - %s, %v\n", Config.TrafficOps.URL, Config.TrafficOps.Users.Admin, err) + os.Exit(1) + } + + return err +} + +// SetupRoles ... +func SetupRoles(db *sql.DB) error { + + sqlStmt := ` +INSERT INTO role (name, description, priv_level) VALUES ('disallowed','Block all access',0) ON CONFLICT DO NOTHING; +INSERT INTO role (name, description, priv_level) VALUES ('read-only','Block all access', 10) ON CONFLICT DO NOTHING; +INSERT INTO role (name, description, priv_level) VALUES ('operations','Block all access', 20) ON CONFLICT DO NOTHING; +INSERT INTO role (name, description, priv_level) VALUES ('admin','super-user', 30) ON CONFLICT DO NOTHING; +INSERT INTO role (name, description, priv_level) VALUES ('portal','Portal User', 2) ON CONFLICT DO NOTHING; +INSERT INTO role (name, description, priv_level) VALUES ('steering','Steering User', 15) ON CONFLICT DO NOTHING; +INSERT INTO role (name, description, priv_level) VALUES ('federation','Role for Secondary CZF', 15) ON CONFLICT DO NOTHING; +` + err := execSQL(db, sqlStmt) + if err != nil { + return fmt.Errorf("exec failed %v", err) + } + return nil +} + +func SetupCapabilities(db *sql.DB) error { + sqlStmt := ` +INSERT INTO capability (name, description) VALUES ('all-read','Full read access') ON CONFLICT DO NOTHING; +INSERT INTO capability (name, description) VALUES ('all-write','Full write access') ON CONFLICT DO NOTHING; +INSERT INTO capability (name, description) VALUES ('cdn-read','View CDN configuration') ON CONFLICT DO NOTHING; +INSERT INTO capability (name, description) VALUES ('asns-read', 'Read ASNs') ON CONFLICT DO NOTHING; +INSERT INTO capability (name, description) VALUES ('asns-write', 'Write ASNs') ON CONFLICT DO NOTHING; +INSERT INTO capability (name, description) VALUES ('cache-groups-read', 'Read CGs') ON CONFLICT DO NOTHING; +` + err := execSQL(db, sqlStmt) + if err != nil { + return fmt.Errorf("exec failed %v", err) + } + return nil +} + +func SetupAPICapabilities(db *sql.DB) error { + sqlStmt := ` +INSERT INTO api_capability (http_method, route, capability) VALUES ('GET', '/asns', 'asns-read') ON CONFLICT DO NOTHING; +INSERT INTO api_capability (http_method, route, capability) VALUES ('POST', '/asns', 'asns-write') ON CONFLICT DO NOTHING; +INSERT INTO api_capability (http_method, route, capability) VALUES ('GET', '/cachegroups', 'cache-groups-read') ON CONFLICT DO NOTHING; +` + + err := execSQL(db, sqlStmt) + if err != nil { + return fmt.Errorf("exec failed %v", err) + } + return nil +} + +func SetupRoleCapabilities(db *sql.DB) error { + sqlStmt := ` +INSERT INTO role_capability SELECT id, perm FROM public.role CROSS JOIN (VALUES +('ASN:CREATE'), +('ASN:DELETE'), +('ASN:UPDATE'), +('CACHE-GROUP:CREATE'), +('CACHE-GROUP:DELETE'), +('CACHE-GROUP:UPDATE'), +('CDN-LOCK:CREATE'), +('CDN-LOCK:DELETE'), +('CDN-SNAPSHOT:CREATE'), +('CDN:CREATE'), +('CDN:DELETE'), +('CDN:UPDATE'), +('COORDINATE:CREATE'), +('COORDINATE:UPDATE'), +('COORDINATE:DELETE'), +('DELIVERY-SERVICE-SAFE:UPDATE'), +('DELIVERY-SERVICE:CREATE'), +('DELIVERY-SERVICE:DELETE'), +('DIVISION:CREATE'), +('DIVISION:DELETE'), +('DIVISION:UPDATE'), +('DNS-SEC:UPDATE'), +('ISO:GENERATE'), +('ORIGIN:CREATE'), +('ORIGIN:DELETE'), +('ORIGIN:UPDATE'), +('PARAMETER:CREATE'), +('PARAMETER:DELETE'), +('PARAMETER:UPDATE'), +('PHYSICAL-LOCATION:CREATE'), +('PHYSICAL-LOCATION:DELETE'), +('PHYSICAL-LOCATION:UPDATE'), +('PROFILE:CREATE'), +('PROFILE:DELETE'), +('PROFILE:UPDATE'), +('REGION:CREATE'), +('REGION:DELETE'), +('REGION:UPDATE'), +('SERVER-CAPABILITY:CREATE'), +('SERVER-CAPABILITY:DELETE'), +('SERVER-CAPABILITY:UPDATE'), +('SERVER:CREATE'), +('SERVER:DELETE'), +('SERVER:QUEUE'), +('SERVER:UPDATE'), +('SERVICE-CATEGORY:CREATE'), +('SERVICE-CATEGORY:DELETE'), +('SERVICE-CATEGORY:UPDATE'), +('STATIC-DN:CREATE'), +('STATIC-DN:DELETE'), +('STATIC-DN:UPDATE'), +('STATUS:CREATE'), +('STATUS:DELETE'), +('STATUS:UPDATE'), +('TENANT:CREATE'), +('TENANT:DELETE'), +('TENANT:UPDATE'), +('TOPOLOGY:CREATE'), +('TOPOLOGY:DELETE'), +('TOPOLOGY:UPDATE'), +('TYPE:CREATE'), +('TYPE:DELETE'), +('TYPE:UPDATE'), +('USER:CREATE'), +('USER:UPDATE'), +('SERVER-CHECK:CREATE'), +('SERVER-CHECK:DELETE')) AS perms(perm) +WHERE priv_level >= 20 ON CONFLICT DO NOTHING; + +INSERT INTO role_capability SELECT id, perm FROM public.role CROSS JOIN (VALUES +('FEDERATION:CREATE'), +('FEDERATION:UPDATE'), +('FEDERATION:DELETE'), +('FEDERATION-RESOLVER:CREATE'), +('FEDERATION-RESOLVER:DELETE'), +('DELIVERY-SERVICE:UPDATE'), +('JOB:CREATE'), +('JOB:UPDATE'), +('JOB:DELETE'), +('DS-REQUEST:UPDATE'), +('DS-REQUEST:CREATE'), +('DS-REQUEST:DELETE'), +('STEERING:CREATE'), +('STEERING:UPDATE'), +('STEERING:DELETE')) AS perms(perm) +WHERE priv_level >= 15 ON CONFLICT DO NOTHING; + +INSERT INTO role_capability SELECT id, perm FROM public.role CROSS JOIN (VALUES +('ASN:READ'), +('ASYNC-STATUS:READ'), +('CACHE-GROUP:READ'), +('CAPABILITY:READ'), +('CDN-SNAPSHOT:READ'), +('CDN:READ'), +('COORDINATE:READ'), +('DELIVERY-SERVICE:READ'), +('DIVISION:READ'), +('DS-REQUEST:READ'), +('DS-SECURITY-KEY:READ'), +('FEDERATION:READ'), +('FEDERATION-RESOLVER:READ'), +('ISO:READ'), +('JOB:READ'), +('LOG:READ'), +('MONITOR-CONFIG:READ'), +('ORIGIN:READ'), +('PARAMETER:READ'), +('PHYSICAL-LOCATION:READ'), +('PLUGIN-READ'), +('PROFILE:READ'), +('REGION:READ'), +('ROLE:READ'), +('SERVER-CAPABILITY:READ'), +('SERVER:READ'), +('SERVICE-CATEGORY:READ'), +('STATIC-DN:READ'), +('STATUS:READ'), +('SERVER-CHECK:READ'), +('STEERING:READ'), +('STAT:READ'), +('TENANT:READ'), +('TOPOLOGY:READ'), +('TRAFFIC-VAULT:READ'), +('TYPE:READ'), +('USER:READ'), +('STAT:CREATE')) AS perms(perm) +WHERE priv_level >= 10 ON CONFLICT DO NOTHING; + +INSERT INTO public.role_capability +SELECT role, perm +FROM public.tm_user +CROSS JOIN (VALUES +('SERVER-CHECK:CREATE'), +('SERVER-CHECK:DELETE'), +('SERVER-CHECK:READ'), +('SERVER:READ')) AS perms(perm) +WHERE username = 'extension' ON CONFLICT DO NOTHING; +` + + err := execSQL(db, sqlStmt) + if err != nil { + return fmt.Errorf("exec failed %v", err) + } + return nil +} + +// SetupTmusers ... +func SetupTmusers(db *sql.DB) error { + + var err error + encryptedPassword, err := auth.DerivePassword(Config.TrafficOps.UserPassword) + if err != nil { + return fmt.Errorf("password encryption failed %v", err) + } + + // Creates users in different tenants + sqlStmt := ` +INSERT INTO tm_user (username, local_passwd, role, tenant_id) VALUES ('` + Config.TrafficOps.Users.Disallowed + `','` + encryptedPassword + `', 1, 1); +INSERT INTO tm_user (username, local_passwd, role, tenant_id) VALUES ('` + Config.TrafficOps.Users.ReadOnly + `','` + encryptedPassword + `', 2, 1); +INSERT INTO tm_user (username, local_passwd, role, tenant_id) VALUES ('` + Config.TrafficOps.Users.Operations + `','` + encryptedPassword + `', 3, 1); +INSERT INTO tm_user (username, local_passwd, role, tenant_id) VALUES ('` + Config.TrafficOps.Users.Admin + `','` + encryptedPassword + `', 4, 1); +INSERT INTO tm_user (username, local_passwd, role, tenant_id) VALUES ('` + Config.TrafficOps.Users.Portal + `','` + encryptedPassword + `', 5, 1); +INSERT INTO tm_user (username, local_passwd, role, tenant_id) VALUES ('` + Config.TrafficOps.Users.Federation + `','` + encryptedPassword + `', 6, 1); +INSERT INTO tm_user (username, local_passwd, role, tenant_id) VALUES ('` + Config.TrafficOps.Users.Extension + `','` + encryptedPassword + `', 3, 1); +` + + err = execSQL(db, sqlStmt) + if err != nil { + return fmt.Errorf("exec failed %v", err) + } + return nil +} + +// SetupTenants ... +func SetupTenants(db *sql.DB) error { + + // TODO: root tenant must be present in initial database. "badtenant" is needed for now so tests can be done + // with a tenant outside the user's tenant. That should be removed once User API tests are in place rather than the SetupUsers defined above. + sqlStmt := ` +INSERT INTO tenant (name, active, parent_id, last_updated) VALUES ('root', true, null, '2018-01-19 19:01:21.327262'); +INSERT INTO tenant (name, active, parent_id, last_updated) VALUES ('badtenant', true, 1, '2018-01-19 19:01:21.327262'); +` + err := execSQL(db, sqlStmt) + if err != nil { + return fmt.Errorf("exec failed %v", err) + } + return nil +} + +// SetupJobs ... +func SetupJobs(db *sql.DB) error { + + sqlStmt := ` +INSERT INTO job (id, ttl_hr, asset_url, start_time, entered_time, job_user, last_updated, job_deliveryservice, invalidation_type) VALUES (100, 24, 'http://cdn2.edge/job1/.*', '2018-01-19 21:01:14.000000', '2018-01-19 21:01:14.000000', (SELECT id FROM tm_user where username = 'admin'), '2018-01-19 21:19:32.468643', 100, 'REFRESH'); +INSERT INTO job (id, ttl_hr, asset_url, start_time, entered_time, job_user, last_updated, job_deliveryservice, invalidation_type) VALUES (200, 36, 'http://cdn2.edge/job2/.*', '2018-01-19 21:09:34.000000', '2018-01-19 21:09:34.000000', (SELECT id FROM tm_user where username = 'admin'), '2018-01-19 21:19:32.450915', 200, 'REFETCH'); +INSERT INTO job (id, ttl_hr, asset_url, start_time, entered_time, job_user, last_updated, job_deliveryservice, invalidation_type) VALUES (300, 72, 'http://cdn2.edge/job3/.*', '2018-01-19 21:14:34.000000', '2018-01-19 21:14:34.000000', (SELECT id FROM tm_user where username = 'admin'), '2018-01-19 21:19:32.460870', 100, 'REFRESH'); +` + err := execSQL(db, sqlStmt) + if err != nil { + return fmt.Errorf("exec failed %v", err) + } + return nil +} + +// SetupTypes Set up to_extension types +func SetupTypes(db *sql.DB) error { + + sqlStmt := ` +INSERT INTO type (name, description, use_in_table) VALUES ('CHECK_EXTENSION_BOOL', 'Extension for checkmark in Server Check', 'to_extension'); +INSERT INTO type (name, description, use_in_table) VALUES ('CHECK_EXTENSION_NUM', 'Extension for int value in Server Check', 'to_extension'); +INSERT INTO type (name, description, use_in_table) VALUES ('CHECK_EXTENSION_OPEN_SLOT', 'Open slot for check in Server Status', 'to_extension'); +` + err := execSQL(db, sqlStmt) + if err != nil { + return fmt.Errorf("exec failed %v", err) + } + return nil +} + +// SetupToExtensions setup open slot in to_extension table +func SetupToExtensions(db *sql.DB) error { + + sqlStmt := ` +INSERT INTO to_extension (name, version, info_url, isactive, script_file, servercheck_column_name, type) VALUES ('OPEN', '1.0.0', '-', false, '', 'aa', (SELECT id FROM type WHERE name = 'CHECK_EXTENSION_OPEN_SLOT')); +INSERT INTO to_extension (name, version, info_url, isactive, script_file, servercheck_column_name, type) VALUES ('OPEN', '1.0.0', '-', false, '', 'ab', (SELECT id FROM type WHERE name = 'CHECK_EXTENSION_OPEN_SLOT')); + ` + err := execSQL(db, sqlStmt) + if err != nil { + return fmt.Errorf("exec failed %v", err) + } + return nil +} + +// Teardown - ensures that the data is cleaned up for a fresh run +func Teardown(db *sql.DB) error { + + sqlStmt := ` + DELETE FROM api_capability; + DELETE FROM deliveryservices_required_capability; + DELETE FROM server_server_capability; + DELETE FROM server_capability; + DELETE FROM to_extension; + DELETE FROM staticdnsentry; + DELETE FROM job; + DELETE FROM log; + DELETE FROM asn; + DELETE FROM tm_user; + DELETE FROM role; + DELETE FROM capability; + ALTER SEQUENCE role_id_seq RESTART WITH 1; + DELETE FROM deliveryservice_regex; + DELETE FROM regex; + DELETE FROM deliveryservice_server; + DELETE FROM deliveryservice; + DELETE FROM origin; + DELETE FROM ip_address; + DELETE FROM interface; + DELETE FROM server; + DELETE FROM phys_location; + DELETE FROM region; + DELETE FROM division; + DELETE FROM profile; + DELETE FROM parameter; + DELETE FROM profile_parameter; + DELETE FROM topology_cachegroup_parents; + DELETE FROM topology_cachegroup; + DELETE FROM topology; + DELETE FROM cachegroup; + DELETE FROM coordinate; + DELETE FROM type; + DELETE FROM status s WHERE s.name NOT IN ('OFFLINE', 'ONLINE', 'PRE_PROD', 'ADMIN_DOWN', 'REPORTED'); + DELETE FROM snapshot; + DELETE FROM cdn; + DELETE FROM service_category; + DELETE FROM tenant; + ALTER SEQUENCE tenant_id_seq RESTART WITH 1; +` + err := execSQL(db, sqlStmt) + if err != nil { + return fmt.Errorf("exec failed %v", err) + } + return err +} + +// execSQL ... +func execSQL(db *sql.DB, sqlStmt string) error { + var err error + + tx, err := db.Begin() + if err != nil { + return fmt.Errorf("transaction begin failed %v %v ", err, tx) + } + + res, err := tx.Exec(sqlStmt) + if err != nil { + return fmt.Errorf("exec failed %v %v", err, res) + } + + err = tx.Commit() + if err != nil { + return fmt.Errorf("commit failed %v %v", err, res) + } + return nil +} diff --git a/traffic_ops/testing/api/v5/topologies_queue_update_test.go b/traffic_ops/testing/api/v5/topologies_queue_update_test.go new file mode 100644 index 0000000000..e9fc296273 --- /dev/null +++ b/traffic_ops/testing/api/v5/topologies_queue_update_test.go @@ -0,0 +1,149 @@ +package v5 + +/* + * 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 ( + "encoding/json" + "net/http" + "net/url" + "strconv" + "testing" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestTopologiesQueueUpdate(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Users, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, Topologies, ServiceCategories, DeliveryServices}, func() { + + methodTests := utils.V5TestCase{ + "POST": { + "OK when VALID REQUEST": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"mso-topology"}}}, + RequestBody: map[string]interface{}{ + "action": "queue", + "cdnId": GetCDNID(t, "cdn1")(), + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateTopologiesQueueUpdateFields(map[string]interface{}{"Action": "queue", "CDNID": int64(GetCDNID(t, "cdn1")()), "Topology": tc.TopologyName("mso-topology")}), + validateServerUpdatesAreQueued("ds-top")), + }, + "BAD REQUEST when INVALID CDNID": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"mso-topology"}}}, + RequestBody: map[string]interface{}{ + "action": "queue", + "cdnId": -1, + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when INVALID ACTION": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"mso-topology"}}}, + RequestBody: map[string]interface{}{ + "action": "requeue", + "cdnId": GetCDNID(t, "cdn1")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST when TOPOLOGY DOESNT EXIST": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"nonexistent"}}}, + RequestBody: map[string]interface{}{ + "action": "queue", + "cdnId": GetCDNID(t, "cdn1")(), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + topQueueUpdate := tc.TopologiesQueueUpdateRequest{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &topQueueUpdate) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "POST": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.TopologiesQueueUpdate(testCase.RequestOpts.QueryParameters["name"][0], topQueueUpdate, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.TopologiesQueueUpdate, resp.Alerts, err) + } + }) + } + } + }) + } + + }) +} + +func validateTopologiesQueueUpdateFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Topologies Queue Update response to not be nil.") + topQueueUpdateResp := resp.(tc.TopologiesQueueUpdate) + for field, expected := range expectedResp { + switch field { + case "Action": + assert.Equal(t, expected, topQueueUpdateResp.Action, "Expected Action to be %v, but got %s", expected, topQueueUpdateResp.Action) + case "CDNID": + assert.Equal(t, expected, topQueueUpdateResp.CDNID, "Expected CDNID to be %v, but got %d", expected, topQueueUpdateResp.CDNID) + case "Topology": + assert.Equal(t, expected, topQueueUpdateResp.Topology, "Expected Topology to be %v, but got %s", expected, topQueueUpdateResp.Topology) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } +} + +func validateServerUpdatesAreQueued(topologyDS string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Topologies Queue Update response to not be nil.") + topQueueUpdateResp := resp.(tc.TopologiesQueueUpdate) + + opts := client.NewRequestOptions() + opts.QueryParameters.Set("dsId", strconv.Itoa(GetDeliveryServiceId(t, topologyDS)())) + serversResponse, _, err := TOSession.GetServers(opts) + assert.RequireNoError(t, err, "Expected no error when getting servers: %v", err) + + for _, server := range serversResponse.Response { + assert.RequireNotNil(t, server.CDNID, "Expected Server CDNID to not be nil.") + assert.RequireNotNil(t, server.HostName, "Expected Server HostName to not be nil.") + assert.RequireNotNil(t, server.UpdPending, "Expected Server UpdPending to not be nil.") + if *server.CDNID != int(topQueueUpdateResp.CDNID) { + continue + } + assert.Equal(t, true, *server.UpdPending, "Expected Server %s Update Pending flag to be set to true.", *server.HostName) + } + } +} diff --git a/traffic_ops/testing/api/v5/topologies_test.go b/traffic_ops/testing/api/v5/topologies_test.go new file mode 100644 index 0000000000..2b57ea8958 --- /dev/null +++ b/traffic_ops/testing/api/v5/topologies_test.go @@ -0,0 +1,1412 @@ +package v5 + +/* + * 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 ( + "fmt" + "net/http" + "net/url" + "reflect" + "strconv" + "strings" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/lib/go-util" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" + toclient "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +type topologyTestCase struct { + testCaseDescription string + tc.Topology +} + +func TestTopologies(t *testing.T) { + WithObjs(t, []TCObj{Types, CacheGroups, CDNs, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, Servers, ServerCapabilities, ServerServerCapabilities, Topologies, Tenants, ServiceCategories, DeliveryServices, DeliveryServicesRequiredCapabilities}, func() { + GetTestTopologies(t) + currentTime := time.Now().UTC().Add(-5 * time.Second) + rfcTime := currentTime.Format(time.RFC1123) + var header http.Header + header = make(map[string][]string) + header.Set(rfc.IfModifiedSince, rfcTime) + header.Set(rfc.IfUnmodifiedSince, rfcTime) + UpdateTestTopologies(t) + UpdateTestTopologiesWithHeaders(t, header) + header = make(map[string][]string) + etag := rfc.ETag(currentTime) + header.Set(rfc.IfMatch, etag) + UpdateTestTopologiesWithHeaders(t, header) + ValidationTestTopologies(t) + UpdateValidateTopologyORGServerCacheGroup(t) + EdgeParentOfEdgeSucceedsWithWarning(t) + UpdateTopologyName(t) + GetTopologyWithNonExistentName(t) + CreateTopologyWithInvalidCacheGroup(t) + CreateTopologyWithInvalidParentNumber(t) + CreateTopologyWithoutDescription(t) + CreateTopologyWithoutName(t) + CreateTopologyWithoutServers(t) + CreateTopologyWithDuplicateParents(t) + CreateTopologyWithNodeAsParentOfItself(t) + CreateTopologyWithOrgLocAsChildNode(t) + CreateTopologyWithExistingName(t) + CreateTopologyWithMidLocTypeWithoutChild(t) + CRUDTopologyReadOnlyUser(t) + UpdateTopologyWithCachegroupAssignedToBecomeParentOfItself(t) + UpdateTopologyWithNoServers(t) + UpdateTopologyWithInvalidParentNumber(t) + UpdateTopologyWithMidLocTypeWithoutChild(t) + UpdateTopologyWithOrgLocAsChildNode(t) + UpdateTopologyWithSameParentAndSecondaryParent(t) + DeleteTopologyWithNonExistentName(t) + DeleteTopologyBeingUsedByDeliveryService(t) + }) +} + +func CreateTestTopologies(t *testing.T) { + for _, topology := range testData.Topologies { + postResponse, _, err := TOSession.CreateTopology(topology, toclient.RequestOptions{}) + if err != nil { + t.Fatalf("could not create Topology: %v - alerts: %+v", err, postResponse.Alerts) + } + postResponse.Response.LastUpdated = nil + if !reflect.DeepEqual(topology, postResponse.Response) { + t.Fatalf("Topology in response should be the same as the one POSTed. expected: %v\nactual: %v", topology, postResponse.Response) + } + } +} + +func GetTestTopologies(t *testing.T) { + if len(testData.Topologies) < 1 { + t.Fatalf("test data has no topologies, can't test") + } + topos, _, err := TOSession.GetTopologies(toclient.RequestOptions{}) + if err != nil { + t.Fatalf("expected error to be nil, actual: %v - alerts: %+v", err, topos.Alerts) + } + if len(topos.Response) != len(testData.Topologies) { + t.Errorf("expected %d Topologies to exist in Traffic Ops, actual: %d", len(testData.Topologies), len(topos.Response)) + } +} + +func UpdateTestTopologiesWithHeaders(t *testing.T, header http.Header) { + originalName := "top-used-by-cdn1-and-cdn2" + newName := "blah" + + // Retrieve the Topology by name so we can get the id for Update() + opts := toclient.NewRequestOptions() + opts.QueryParameters.Set("name", originalName) + resp, _, err := TOSession.GetTopologies(opts) + if err != nil { + t.Errorf("cannot get Topology by name '%s': %v - alerts: %+v", originalName, err, resp.Alerts) + } + if len(resp.Response) != 1 { + t.Fatalf("Expected exactly one Topology to exist with name '%s', found: %d", originalName, len(resp.Response)) + } + resp.Response[0].Name = newName + _, reqInf, err := TOSession.UpdateTopology(originalName, resp.Response[0], toclient.RequestOptions{Header: header}) + if err == nil { + t.Errorf("Expected error about Precondition Failed, got none") + } + if reqInf.StatusCode != http.StatusPreconditionFailed { + t.Errorf("Expected status code 412, got %v", reqInf.StatusCode) + } +} + +func EdgeParentOfEdgeSucceedsWithWarning(t *testing.T) { + testCase := topologyTestCase{testCaseDescription: "an edge parenting a mid", Topology: tc.Topology{ + Name: "edge-parent-of-edge", + Description: "An edge is a parent, which is technically valid, but we will warn the user in case it was a mistake", + Nodes: []tc.TopologyNode{ + {Cachegroup: "cachegroup1", Parents: []int{1}}, + {Cachegroup: "cachegroup2", Parents: []int{}}, + }}} + response, _, err := TOSession.CreateTopology(testCase.Topology, toclient.RequestOptions{}) + if err != nil { + t.Fatalf("Unexpected error creating Topology for '%s': %v - alerts: %+v", testCase.testCaseDescription, err, response.Alerts) + } + containsWarning := false + for _, alert := range response.Alerts.Alerts { + if alert.Level == tc.WarnLevel.String() { + containsWarning = true + } + } + if !containsWarning { + t.Fatalf("expected a warning-level alert message in the response, actual: %v", response.Alerts) + } + delResp, _, err := TOSession.DeleteTopology(testCase.Topology.Name, toclient.RequestOptions{}) + if err != nil { + t.Fatalf("cannot delete Topology: %v - alerts: %+v", err, delResp.Alerts) + } +} + +func ValidationTestTopologies(t *testing.T) { + invalidTopologyTestCases := []topologyTestCase{ + {testCaseDescription: "no nodes", Topology: tc.Topology{Name: "empty-top", Description: "Invalid because there are no nodes", Nodes: []tc.TopologyNode{}}}, + {testCaseDescription: "a node listing itself as a parent", Topology: tc.Topology{Name: "self-parent", Description: "Invalid because a node lists itself as a parent", Nodes: []tc.TopologyNode{ + {Cachegroup: "cachegroup1", Parents: []int{1}}, + {Cachegroup: "parentCachegroup", Parents: []int{1}}, + }}}, + {testCaseDescription: "duplicate parents", Topology: tc.Topology{}}, + {testCaseDescription: "too many parents", Topology: tc.Topology{Name: "duplicate-parents", Description: "Invalid because a node lists the same parent twice", Nodes: []tc.TopologyNode{ + {Cachegroup: "cachegroup1", Parents: []int{1, 1}}, + {Cachegroup: "parentCachegroup", Parents: []int{}}, + }}}, + {testCaseDescription: "too many parents", Topology: tc.Topology{Name: "too-many-parents", Description: "Invalid because a node has more than 2 parents", Nodes: []tc.TopologyNode{ + {Cachegroup: "parentCachegroup", Parents: []int{}}, + {Cachegroup: "secondaryCachegroup", Parents: []int{}}, + {Cachegroup: "parentCachegroup2", Parents: []int{}}, + {Cachegroup: "cachegroup1", Parents: []int{0, 1, 2}}, + }}}, + {testCaseDescription: "an edge parenting a mid", Topology: tc.Topology{Name: "edge-parent-of-mid", Description: "Invalid because an edge is a parent of a mid", Nodes: []tc.TopologyNode{ + {Cachegroup: "cachegroup1", Parents: []int{1}}, + {Cachegroup: "parentCachegroup", Parents: []int{2}}, + {Cachegroup: "cachegroup2", Parents: []int{}}, + }}}, + {testCaseDescription: "a leaf mid", Topology: tc.Topology{Name: "leaf-mid", Description: "Invalid because a mid is a leaf node", Nodes: []tc.TopologyNode{ + {Cachegroup: "parentCachegroup", Parents: []int{1}}, + {Cachegroup: "secondaryCachegroup", Parents: []int{}}, + }}}, + {testCaseDescription: "cyclical nodes", Topology: tc.Topology{Name: "cyclical-nodes", Description: "Invalid because it contains cycles", Nodes: []tc.TopologyNode{ + {Cachegroup: "cachegroup1", Parents: []int{1, 2}}, + {Cachegroup: "parentCachegroup", Parents: []int{2}}, + {Cachegroup: "secondaryCachegroup", Parents: []int{1}}, + }}}, + {testCaseDescription: "a cycle across topologies", Topology: tc.Topology{Name: "cycle-with-4-tier-topology", Description: `Invalid because it contains a cycle when combined with the "4-tiers" topology`, Nodes: []tc.TopologyNode{ + {Cachegroup: "parentCachegroup", Parents: []int{1}}, + {Cachegroup: "parentCachegroup2", Parents: []int{}}, + {Cachegroup: "cachegroup1", Parents: []int{0}}, + }}}, + {testCaseDescription: "a cycle across cache groups", Topology: tc.Topology{Name: "cycle-with-non-topology-cachegroups", Description: "Invalid because it contains a cycle when combined with a topology constructed from cache group parentage", Nodes: []tc.TopologyNode{ + {Cachegroup: "edge-parent1", Parents: []int{1}}, + {Cachegroup: "has-edge-parent1", Parents: []int{}}, + }}}, + {testCaseDescription: "a nonexistent cache group", Topology: tc.Topology{Name: "nonexistent-cg", Description: "Invalid because it references a cache group that does not exist", Nodes: []tc.TopologyNode{ + {Cachegroup: "legitcachegroup", Parents: []int{}}, + }}}, + {testCaseDescription: "an out-of-bounds parent index", Topology: tc.Topology{Name: "oob-parent", Description: "Invalid because it contains an out-of-bounds parent", Nodes: []tc.TopologyNode{ + {Cachegroup: "cachegroup1", Parents: []int{7}}, + }}}, + {testCaseDescription: "a cachegroup containing no servers", Topology: tc.Topology{Name: "empty-cg", Description: `Invalid because it contains a cachegroup, fallback3, that contains no servers`, Nodes: []tc.TopologyNode{ + {Cachegroup: "parentCachegroup", Parents: []int{}}, + {Cachegroup: "parentCachegroup2", Parents: []int{}}, + {Cachegroup: "fallback3", Parents: []int{0, 1}}, + }}}, + } + + for _, testCase := range invalidTopologyTestCases { + _, reqInf, err := TOSession.CreateTopology(testCase.Topology, toclient.RequestOptions{}) + if err == nil { + t.Fatalf("expected POST with %v to return an error, actual: nil", testCase.testCaseDescription) + } + statusCode := reqInf.StatusCode + if statusCode < 400 || statusCode >= 500 { + t.Fatalf("Expected a 400-level status code for topology %s but got %d", testCase.Topology.Name, statusCode) + } + } +} + +func updateSingleTopology(topology tc.Topology) error { + updateResponse, _, err := TOSession.UpdateTopology(topology.Name, topology, toclient.RequestOptions{}) + if err != nil { + return fmt.Errorf("cannot put Topology: %v - alerts: %+v", err, updateResponse.Alerts) + } + updateResponse.Response.LastUpdated = nil + if !reflect.DeepEqual(topology, updateResponse.Response) { + return fmt.Errorf("Topologies should be equal after updating. expected: %v\nactual: %v", topology, updateResponse.Response) + } + return nil +} + +func UpdateTestTopologies(t *testing.T) { + for _, topology := range testData.Topologies { + if err := updateSingleTopology(topology); err != nil { + t.Fatalf(err.Error()) + } + } + + opts := toclient.NewRequestOptions() + + // attempt to add cachegroup that doesn't meet DS required capabilities + opts.QueryParameters.Set("name", "top-for-ds-req") + resp, _, err := TOSession.GetTopologies(opts) + if err != nil { + t.Fatalf("cannot get Topology: %v - alerts: %+v", err, resp.Alerts) + } + if len(resp.Response) != 1 { + t.Fatalf("Expected exactly one Topology to exist with name 'top-for-ds-req', found: %d", len(resp.Response)) + } + top := resp.Response[0] + + top.Nodes = append(top.Nodes, tc.TopologyNode{Cachegroup: "cachegroup1", Parents: []int{0}}) + _, _, err = TOSession.UpdateTopology(top.Name, top, toclient.RequestOptions{}) + if err == nil { + t.Errorf("making invalid update to topology - expected: error, actual: nil") + } + + // attempt to add a cachegroup that only has caches in one CDN while the topology is assigned to DSes from multiple CDNs + opts.QueryParameters.Set("name", "top-used-by-cdn1-and-cdn2") + resp, _, err = TOSession.GetTopologies(opts) + if err != nil { + t.Fatalf("cannot get Topology: %v - alerts: %+v", err, resp.Alerts) + } + if len(resp.Response) != 1 { + t.Fatalf("Expected exactly one Topology to exist with name 'top-for-ds-req', found: %d", len(resp.Response)) + } + top = resp.Response[0] + + opts = toclient.NewRequestOptions() + opts.QueryParameters.Set("topology", "top-used-by-cdn1-and-cdn2") + dses, _, err := TOSession.GetDeliveryServices(opts) + if err != nil { + t.Fatalf("cannot get Delivery Services: %v - alerts: %+v", err, dses.Alerts) + } + if len(dses.Response) < 2 { + t.Fatalf("expected at least 2 delivery services assigned to topology top-used-by-cdn1-and-cdn2, actual: %d", len(dses.Response)) + } + foundCDN1 := false + foundCDN2 := false + for _, ds := range dses.Response { + if ds.CDNName == nil { + t.Error("Traffic Ops returned a representation of a Delivery Service that had null or undefined CDN Name") + continue + } + if *ds.CDNName == "cdn1" { + foundCDN1 = true + } else if *ds.CDNName == "cdn2" { + foundCDN2 = true + } + } + if !foundCDN1 || !foundCDN2 { + t.Fatalf("expected delivery services assigned to topology top-used-by-cdn1-and-cdn2 to be assigned to cdn1 and cdn2") + } + opts = toclient.NewRequestOptions() + opts.QueryParameters.Set("name", "cdn1-only") + cgs, _, err := TOSession.GetCacheGroups(opts) + if err != nil { + t.Fatalf("unable to GET cachegroup by name: %v", err) + } + if len(cgs.Response) != 1 { + t.Fatalf("expected: to get 1 cachegroup named 'cdn1-only', actual: got %d", len(cgs.Response)) + } + if cgs.Response[0].ID == nil { + t.Fatal("Traffic Ops returned a representation for Cache Group 'cdn1-only' that had a null or undefined ID") + } + opts.QueryParameters = url.Values{} + opts.QueryParameters.Add("cachegroup", strconv.Itoa(*cgs.Response[0].ID)) + servers, _, err := TOSession.GetServers(opts) + if err != nil { + t.Fatalf("unable to get servers by Cache Group ID: %v - alerts: %+v", err, servers.Alerts) + } + for _, s := range servers.Response { + if s.Cachegroup == nil || s.CDNName == nil { + t.Error("Traffic Ops returned a representation of a server with null or undefined Cache Group and/or CDN name") + continue + } + if *s.Cachegroup != "cdn1-only" { + t.Fatalf("GET servers by cachegroup 'cdn1-only' - expected: only servers in cachegroup 'cdn1-only', actual: got server in %s", *s.Cachegroup) + } + if *s.CDNName != "cdn1" { + t.Fatalf("expected: servers in cachegroup 'cdn1-only' to only be in cdn1, actual: servers in cdn %s", *s.CDNName) + } + } + top.Nodes = append(top.Nodes, tc.TopologyNode{ + Cachegroup: "cdn1-only", + Parents: []int{0}, + }) + _, _, err = TOSession.UpdateTopology(top.Name, top, toclient.RequestOptions{}) + if err == nil { + t.Errorf("making invalid update to topology (cachegroup contains only servers from cdn1 while the topology is assigned to delivery services in cdn1 and cdn2) - expected: error, actual: nil") + } +} + +func UpdateValidateTopologyORGServerCacheGroup(t *testing.T) { + opts := toclient.NewRequestOptions() + opts.QueryParameters.Set("xmlId", "ds-top") + + //Get the correct DS + resp, _, err := TOSession.GetDeliveryServices(opts) + if err != nil { + t.Errorf("cannot get Delivery Services: %v - alerts: %+v", err, resp.Alerts) + } + if len(resp.Response) < 1 { + t.Fatalf("Expected exactly one Delivery Service to exist with XMLID 'ds-top', found: %d", len(resp.Response)) + } + remoteDS := resp.Response[0] + if remoteDS.XMLID == nil || remoteDS.Topology == nil || remoteDS.ID == nil { + t.Fatal("Traffic Ops returned a representation of a Delivery Service that had null or undefined Topology and/or XMLID and/or ID") + } + + //Assign ORG server to DS + assignServer := []string{"denver-mso-org-01"} + assignResponse, _, err := TOSession.AssignServersToDeliveryService(assignServer, *remoteDS.XMLID, toclient.RequestOptions{}) + if err != nil { + t.Errorf("Unexpected error assigning server 'denver-mso-org-01' to Delivery Service '%s': %v - alerts: %+v", *remoteDS.XMLID, err, assignResponse.Alerts) + } + + //Get Topology node to update and remove ORG server nodes + origTopo := *remoteDS.Topology + opts = toclient.NewRequestOptions() + opts.QueryParameters.Set("name", origTopo) + topResp, _, err := TOSession.GetTopologies(opts) + if err != nil { + t.Fatalf("couldn't find any Topologies: %v - alerts: %+v", err, topResp.Alerts) + } + if len(resp.Response) != 1 { + t.Fatalf("Expected exactly one Topology to exist with name '%s', found: %d", origTopo, len(resp.Response)) + } + topo := topResp.Response[0] + + // remove org server cachegroup + var p []int + newNodes := []tc.TopologyNode{{Id: 0, Cachegroup: "topology-edge-cg-01", Parents: p, LastUpdated: nil}} + if *remoteDS.Topology == topo.Name { + topo.Nodes = newNodes + } + updTopResp, _, err := TOSession.UpdateTopology(*remoteDS.Topology, topo, toclient.RequestOptions{}) + if err == nil { + t.Fatalf("should not update Topology: %s to %s, but update was a success", *remoteDS.Topology, newNodes[0].Cachegroup) + } else if !alertsHaveError(updTopResp.Alerts.Alerts, "ORG servers are assigned to delivery services that use this topology, and their cachegroups cannot be removed:") { + t.Errorf("expected error messsage containing: \"ORG servers are assigned to delivery services that use this topology, and their cachegroups cannot be removed\", got: %v - alets: %+v", err, updTopResp.Alerts) + } + + //Remove org server assignment and reset DS back to as it was for further testing + opts = toclient.NewRequestOptions() + opts.QueryParameters.Set("hostName", "denver-mso-org-01") + serverResp, _, err := TOSession.GetServers(opts) + if err != nil { + t.Errorf("Unexpected error getting servers filtered by Host Name 'denver-mso-org-01': %v - alerts: %+v", err, serverResp.Alerts) + } + if len(serverResp.Response) == 0 { + t.Fatal("no servers in response, quitting") + } + if serverResp.Response[0].ID == nil { + t.Fatal("ID of the response server is nil, quitting") + } + alerts, _, err := TOSession.DeleteDeliveryServiceServer(*remoteDS.ID, *serverResp.Response[0].ID, toclient.RequestOptions{}) + if err != nil { + t.Errorf("cannot remove server #%d from Delivery Service #%d: %v - alerts: %+v", *serverResp.Response[0].ID, *remoteDS.ID, err, alerts.Alerts) + } +} + +func UpdateTopologyName(t *testing.T) { + currentTopologyName := "top-used-by-cdn1-and-cdn2" + + // Get details on existing topology + opts := toclient.NewRequestOptions() + opts.QueryParameters.Set("name", currentTopologyName) + resp, _, err := TOSession.GetTopologies(opts) + if err != nil { + t.Errorf("unable to get Topology filtered by name '%s': %v - alerts: %+v", currentTopologyName, err, resp.Alerts) + } + if len(resp.Response) != 1 { + t.Fatalf("Expected exactly one Topology to exist with name '%s', found: %d", currentTopologyName, len(resp.Response)) + } + newTopologyName := "test-topology" + resp.Response[0].Name = newTopologyName + + // Update topology with new name + updateResponse, _, err := TOSession.UpdateTopology(currentTopologyName, resp.Response[0], toclient.RequestOptions{}) + if err != nil { + t.Errorf("cannot updated Topology: %v - alerts: %+v", err, updateResponse.Alerts) + } + if updateResponse.Response.Name != newTopologyName { + t.Errorf("update topology name failed, expected: %v but got:%v", newTopologyName, updateResponse.Response.Name) + } + + //To check whether the primary key change trickled down to DS table + opts = toclient.NewRequestOptions() + opts.QueryParameters.Set("xmlId", "top-ds-in-cdn2") + resp1, _, err := TOSession.GetDeliveryServices(opts) + if err != nil { + t.Errorf("failed to get details on DS: %v - alerts: %+v", err, resp1.Alerts) + } + if len(resp1.Response) != 1 { + t.Fatalf("Expected exactly one Delivery Service to exist with XMLID 'top-ds-in-cdn2', found: %d", len(resp1.Response)) + } + if resp1.Response[0].Topology == nil { + t.Fatal("Expected Delivery Service 'top-ds-in-cdn2' to have a Topology, but it was null or undefined in response from Traffic Ops") + } + if *resp1.Response[0].Topology != newTopologyName { + t.Errorf("topology name change failed to trickle to delivery service table, expected: %s but got: %s", newTopologyName, *resp1.Response[0].Topology) + } + + // Set everything back as it was for further testing. + resp.Response[0].Name = currentTopologyName + r, _, err := TOSession.UpdateTopology(newTopologyName, resp.Response[0], toclient.RequestOptions{}) + if err != nil { + t.Errorf("cannot update Topology: %v - alerts: %+v", err, r.Alerts) + } +} + +func DeleteTestTopologies(t *testing.T) { + for _, top := range testData.Topologies { + delResp, _, err := TOSession.DeleteTopology(top.Name, toclient.RequestOptions{}) + if err != nil { + t.Fatalf("cannot delete Topology: %v - alerts: %+v", err, delResp.Alerts) + } + opts := toclient.NewRequestOptions() + opts.QueryParameters.Set("limit", "1") + deleteLog, _, err := TOSession.GetLogs(opts) + if err != nil { + t.Fatalf("unable to get latest audit log entry: %v - alerts: %+v", err, deleteLog.Alerts) + } + if len(deleteLog.Response) != 1 { + t.Fatalf("log entry length - expected: 1, actual: %d", len(deleteLog.Response)) + } + if deleteLog.Response[0].Message == nil { + t.Fatal("Traffic Ops responded with a representation of a log entry with null or undefined message") + } + if !strings.Contains(*deleteLog.Response[0].Message, top.Name) { + t.Errorf("topology deletion audit log entry - expected: message containing topology name '%s', actual: %s", top.Name, *deleteLog.Response[0].Message) + } + + opts.QueryParameters.Del("limit") + opts.QueryParameters.Set("name", top.Name) + resp, _, err := TOSession.GetTopologies(opts) + if err != nil { + t.Errorf("Unexpected error trying to fetch Topologies after deletion: %v - alerts: %+v", err, resp.Alerts) + } + if len(resp.Response) != 0 { + t.Fatalf("expected not to find deleted Topology '%s' in Traffic Ops, but %d Topologies were found by that name", top.Name, len(resp.Response)) + } + } +} + +func GetTopologyWithNonExistentName(t *testing.T) { + opts := toclient.NewRequestOptions() + opts.QueryParameters.Set("name", "non-existent-topology") + resp, reqInf, _ := TOSession.GetTopologies(opts) + if len(resp.Response) != 0 { + t.Errorf("expected nothing in the response, but got %d Topologies", len(resp.Response)) + } + if reqInf.StatusCode != http.StatusOK { + t.Errorf("expected a 200 response code, but got %d", reqInf.StatusCode) + } +} + +func CreateTopologyWithInvalidCacheGroup(t *testing.T) { + nodes := make([]tc.TopologyNode, 0) + node := tc.TopologyNode{ + Cachegroup: "non-existent-cachegroup", + Parents: nil, + } + nodes = append(nodes, node) + top := tc.Topology{ + Description: "blah", + Name: "invalid-cachegroup-topology", + Nodes: nodes, + } + _, reqInf, err := TOSession.CreateTopology(top, toclient.RequestOptions{}) + if reqInf.StatusCode != http.StatusBadRequest { + t.Errorf("expected a '400 Bad Request' response code, but got %d", reqInf.StatusCode) + } + if err == nil { + t.Errorf("expected error about the cachegroup name not being valid, but got none") + } +} + +func CreateTopologyWithInvalidParentNumber(t *testing.T) { + cacheGroups, _, err := TOSession.GetCacheGroups(toclient.RequestOptions{}) + if err != nil { + t.Fatalf("error while getting cachegroups: %v", err) + } + if len(cacheGroups.Response) == 0 { + t.Fatalf("no cachegroups in response") + } + + cachegroupName := "" + opts := toclient.NewRequestOptions() + for _, cg := range cacheGroups.Response { + if cg.ID == nil { + t.Error("Traffic Ops returned a representation for a Cache Group with null or undefined ID") + continue + } + opts.QueryParameters.Set("cachegroup", strconv.Itoa(*cg.ID)) + resp, _, _ := TOSession.GetServers(opts) + if len(resp.Response) != 0 { + if cg.Name != nil && cg.Type != nil && *cg.Type == tc.CacheGroupEdgeTypeName { + cachegroupName = *cg.Name + break + } + } + } + if cachegroupName == "" { + t.Fatal("No servers could be found in any Cache Groups - need at least one valid server to test creating a topology with an invalid parent number") + } + node := tc.TopologyNode{ + Cachegroup: cachegroupName, + Parents: []int{100}, + } + nodes := make([]tc.TopologyNode, 1) + nodes = append(nodes, node) + top := tc.Topology{ + Description: "blah", + Name: "invalid-parent-topology", + Nodes: nodes, + } + _, reqInf, err := TOSession.CreateTopology(top, toclient.RequestOptions{}) + if reqInf.StatusCode != http.StatusBadRequest { + t.Errorf("expected a '400 Bad Request' response code, but got %d", reqInf.StatusCode) + } + if err == nil { + t.Errorf("expected error about the parent not being valid, but got none") + } +} + +func CreateTopologyWithoutDescription(t *testing.T) { + cacheGroups, _, err := TOSession.GetCacheGroups(toclient.RequestOptions{}) + if err != nil { + t.Fatalf("error while getting cachegroups: %v", err) + } + if len(cacheGroups.Response) == 0 { + t.Fatalf("no cachegroups in response") + } + + cachegroupName := "" + opts := toclient.NewRequestOptions() + for _, cg := range cacheGroups.Response { + if cg.ID == nil { + t.Error("Traffic Ops returned a representation for a Cache Group with null or undefined ID") + continue + } + opts.QueryParameters.Set("cachegroup", strconv.Itoa(*cg.ID)) + resp, _, _ := TOSession.GetServers(opts) + if len(resp.Response) != 0 { + if cg.Name != nil && cg.Type != nil && *cg.Type == tc.CacheGroupEdgeTypeName { + cachegroupName = *cg.Name + break + } + } + } + if cachegroupName == "" { + t.Fatal("Failed to find a single Cache Group with any Servers in it") + } + + node := tc.TopologyNode{ + Cachegroup: cachegroupName, + Parents: nil, + } + nodes := make([]tc.TopologyNode, 0, 1) + nodes = append(nodes, node) + top := tc.Topology{ + Name: "topology-without-description", + Nodes: nodes, + } + resp, reqInf, err := TOSession.CreateTopology(top, toclient.RequestOptions{}) + if reqInf.StatusCode != http.StatusOK { + t.Errorf("expected a 200 response code, but got %d", reqInf.StatusCode) + } + if err != nil { + t.Errorf("no error expected about description being empty, but got: %v - alerts: %+v", err, resp.Alerts) + } + alerts, _, err := TOSession.DeleteTopology(top.Name, toclient.RequestOptions{}) + if err != nil { + t.Errorf("couldn't delete Topology with name '%s': %v - alerts: %+v", top.Name, err, alerts.Alerts) + } +} + +func CreateTopologyWithoutName(t *testing.T) { + cacheGroups, _, err := TOSession.GetCacheGroups(toclient.RequestOptions{}) + if err != nil { + t.Fatalf("error while getting cachegroups: %v", err) + } + if len(cacheGroups.Response) == 0 { + t.Fatalf("no cachegroups in response") + } + + cachegroupName := "" + opts := toclient.NewRequestOptions() + for _, cg := range cacheGroups.Response { + if cg.ID == nil { + t.Error("Traffic Ops returned a representation for a Cache Group with null or undefined ID") + continue + } + opts.QueryParameters.Set("cachegroup", strconv.Itoa(*cg.ID)) + resp, _, _ := TOSession.GetServers(opts) + if len(resp.Response) != 0 { + if cg.Name != nil && cg.Type != nil && *cg.Type == tc.CacheGroupEdgeTypeName { + cachegroupName = *cg.Name + break + } + } + } + + node := tc.TopologyNode{ + Cachegroup: cachegroupName, + Parents: nil, + } + nodes := make([]tc.TopologyNode, 1) + nodes = append(nodes, node) + top := tc.Topology{ + Description: "description", + Nodes: nodes, + } + + _, reqInf, err := TOSession.CreateTopology(top, toclient.RequestOptions{}) + if reqInf.StatusCode != http.StatusBadRequest { + t.Errorf("expected a 400 response code, but got %d", reqInf.StatusCode) + } + if err == nil { + t.Errorf("error about topology name being empty expected, but got none") + } +} + +func CreateTopologyWithoutServers(t *testing.T) { + cacheGroups, _, err := TOSession.GetCacheGroups(toclient.RequestOptions{}) + if err != nil { + t.Fatalf("error while getting cachegroups: %v", err) + } + if len(cacheGroups.Response) == 0 { + t.Fatalf("no cachegroups in response") + } + + cachegroupName := "" + opts := toclient.NewRequestOptions() + for _, cg := range cacheGroups.Response { + if cg.ID == nil { + t.Error("Traffic Ops returned a representation for a Cache Group with null or undefined ID") + continue + } + opts.QueryParameters.Set("cachegroup", strconv.Itoa(*cg.ID)) + resp, _, _ := TOSession.GetServers(opts) + if len(resp.Response) == 0 { + if cg.Name != nil && cg.Type != nil && *cg.Type == tc.CacheGroupEdgeTypeName { + cachegroupName = *cg.Name + break + } + } + } + + node := tc.TopologyNode{ + Cachegroup: cachegroupName, + Parents: nil, + } + nodes := make([]tc.TopologyNode, 1) + nodes = append(nodes, node) + top := tc.Topology{ + Name: "topology_without_servers", + Description: "description", + Nodes: nodes, + } + _, reqInf, err := TOSession.CreateTopology(top, toclient.RequestOptions{}) + if reqInf.StatusCode != http.StatusBadRequest { + t.Errorf("expected a 400 response code, but got %d", reqInf.StatusCode) + } + if err == nil { + t.Errorf("error about topology containing no servers expected, but got none") + } +} + +func CreateTopologyWithDuplicateParents(t *testing.T) { + cacheGroups, _, err := TOSession.GetCacheGroups(toclient.RequestOptions{}) + if err != nil { + t.Fatalf("error while getting cachegroups: %v", err) + } + if len(cacheGroups.Response) == 0 { + t.Fatalf("no cachegroups in response") + } + + cachegroupName := "" + parentName := "" + opts := toclient.NewRequestOptions() + for _, cg := range cacheGroups.Response { + if cg.ID == nil { + t.Error("Traffic Ops returned a representation for a Cache Group with null or undefined ID") + continue + } + opts.QueryParameters.Set("cachegroup", strconv.Itoa(*cg.ID)) + resp, _, _ := TOSession.GetServers(opts) + if len(resp.Response) != 0 { + if cg.Name != nil && parentName != *cg.Name && cg.Type != nil && *cg.Type == tc.CacheGroupEdgeTypeName { + cachegroupName = *cg.Name + break + } + } + } + + for _, cg := range cacheGroups.Response { + if cg.ID == nil { + t.Error("Traffic Ops returned a representation for a Cache Group with null or undefined ID") + continue + } + opts.QueryParameters.Set("cachegroup", strconv.Itoa(*cg.ID)) + resp, _, _ := TOSession.GetServers(opts) + if len(resp.Response) != 0 { + if cg.Name != nil { + parentName = *cg.Name + break + } + } + } + + nodes := []tc.TopologyNode{ + { + Cachegroup: parentName, + Parents: []int{}, + }, + { + Cachegroup: cachegroupName, + Parents: []int{0, 0}, + }, + } + top := tc.Topology{ + Name: "topology_with_duplicate_parents", + Description: "description", + Nodes: nodes, + } + _, reqInf, err := TOSession.CreateTopology(top, toclient.RequestOptions{}) + if reqInf.StatusCode != http.StatusBadRequest { + t.Errorf("expected a 400 response code, but got %d", reqInf.StatusCode) + } + if err == nil { + t.Errorf("error about topology having duplicate parent expected, but got none") + } +} + +func CreateTopologyWithNodeAsParentOfItself(t *testing.T) { + cacheGroups, _, err := TOSession.GetCacheGroups(toclient.RequestOptions{}) + if err != nil { + t.Fatalf("error while getting cachegroups: %v", err) + } + if len(cacheGroups.Response) == 0 { + t.Fatalf("no cachegroups in response") + } + + cachegroupName := "" + parentName := "" + opts := toclient.NewRequestOptions() + for _, cg := range cacheGroups.Response { + if cg.ID == nil { + t.Error("Traffic Ops returned a representation for a Cache Group with null or undefined ID") + continue + } + opts.QueryParameters.Set("cachegroup", strconv.Itoa(*cg.ID)) + resp, _, _ := TOSession.GetServers(opts) + if len(resp.Response) != 0 { + if cg.Name != nil && cg.Type != nil && *cg.Type == tc.CacheGroupEdgeTypeName { + cachegroupName = *cg.Name + break + } + } + } + + for _, cg := range cacheGroups.Response { + if cg.ID == nil { + t.Error("Traffic Ops returned a representation for a Cache Group with null or undefined ID") + continue + } + opts.QueryParameters.Set("cachegroup", strconv.Itoa(*cg.ID)) + resp, _, _ := TOSession.GetServers(opts) + if len(resp.Response) != 0 { + if cg.Name != nil && parentName != *cg.Name { + parentName = *cg.Name + break + } + } + } + + nodes := []tc.TopologyNode{ + { + Cachegroup: parentName, + Parents: []int{}, + }, + { + Cachegroup: cachegroupName, + Parents: []int{0, 1}, + }, + } + top := tc.Topology{ + Name: "topology_with_node_as_parent_of_itself", + Description: "description", + Nodes: nodes, + } + _, reqInf, err := TOSession.CreateTopology(top, toclient.RequestOptions{}) + if reqInf.StatusCode != http.StatusBadRequest { + t.Errorf("expected a 400 response code, but got %d", reqInf.StatusCode) + } + if err == nil { + t.Errorf("error about topology having node as parent of itself expected, but got none") + } +} + +func CreateTopologyWithOrgLocAsChildNode(t *testing.T) { + cacheGroups, _, err := TOSession.GetCacheGroups(toclient.RequestOptions{}) + if err != nil { + t.Fatalf("error while getting cachegroups: %v", err) + } + if len(cacheGroups.Response) == 0 { + t.Fatalf("no cachegroups in response") + } + + cachegroupName := "" + parentName := "" + opts := toclient.NewRequestOptions() + for _, cg := range cacheGroups.Response { + if cg.ID == nil { + t.Error("Traffic Ops returned a representation for a Cache Group with null or undefined ID") + continue + } + opts.QueryParameters.Set("cachegroup", strconv.Itoa(*cg.ID)) + resp, _, _ := TOSession.GetServers(opts) + if len(resp.Response) != 0 { + if cg.Name != nil && cg.Type != nil && *cg.Type == tc.CacheGroupEdgeTypeName { + parentName = *cg.Name + break + } + } + } + + for _, cg := range cacheGroups.Response { + if cg.ID == nil { + t.Error("Traffic Ops returned a representation for a Cache Group with null or undefined ID") + continue + } + opts.QueryParameters.Set("cachegroup", strconv.Itoa(*cg.ID)) + resp, _, _ := TOSession.GetServers(opts) + if len(resp.Response) != 0 { + if cg.Name != nil && parentName != *cg.Name && cg.Type != nil && *cg.Type == tc.CacheGroupOriginTypeName { + cachegroupName = *cg.Name + break + } + } + } + + nodes := []tc.TopologyNode{ + { + Cachegroup: parentName, + Parents: []int{}, + }, + { + Cachegroup: cachegroupName, + Parents: []int{0}, + }, + } + top := tc.Topology{ + Name: "topology_with_orgloc_as_child_node", + Description: "description", + Nodes: nodes, + } + _, reqInf, err := TOSession.CreateTopology(top, toclient.RequestOptions{}) + if reqInf.StatusCode != http.StatusBadRequest { + t.Errorf("expected a 400 response code, but got %d", reqInf.StatusCode) + } + if err == nil { + t.Errorf("error about topology having ord_loc node as child expected, but got none") + } +} + +func CreateTopologyWithExistingName(t *testing.T) { + resp, _, err := TOSession.GetTopologies(toclient.RequestOptions{}) + if err != nil { + t.Fatalf("could not get Topologies: %v - alerts: %+v", err, resp.Alerts) + } + if len(resp.Response) == 0 { + t.Fatalf("expected 1 or more topologies in response, but got 0") + } + _, reqInf, err := TOSession.CreateTopology(resp.Response[0], toclient.RequestOptions{}) + if err == nil { + t.Errorf("expected error about creating topology with same name, but got none") + } + if reqInf.StatusCode != http.StatusBadRequest { + t.Errorf("expected a 400 response code, but got %d", reqInf.StatusCode) + } +} + +func CreateTopologyWithMidLocTypeWithoutChild(t *testing.T) { + cacheGroups, _, err := TOSession.GetCacheGroups(toclient.RequestOptions{}) + if err != nil { + t.Fatalf("error while getting Cache Groups: %v - alerts: %+v", err, cacheGroups.Alerts) + } + if len(cacheGroups.Response) == 0 { + t.Fatal("no cachegroups in response") + } + + parentName := "" + opts := toclient.NewRequestOptions() + for _, cg := range cacheGroups.Response { + if cg.ID == nil { + t.Error("Traffic Ops returned a representation for a Cache Group with null or undefined ID") + continue + } + opts.QueryParameters.Set("cachegroup", strconv.Itoa(*cg.ID)) + resp, _, _ := TOSession.GetServers(opts) + if len(resp.Response) != 0 { + if cg.Name != nil && cg.Type != nil && *cg.Type == tc.CacheGroupMidTypeName { + parentName = *cg.Name + break + } + } + } + + nodes := []tc.TopologyNode{ + { + Cachegroup: parentName, + Parents: []int{}, + }, + } + top := tc.Topology{ + Name: "topology_with_midloc_and_no_child_nodes", + Description: "description", + Nodes: nodes, + } + _, reqInf, err := TOSession.CreateTopology(top, toclient.RequestOptions{}) + if reqInf.StatusCode != http.StatusBadRequest { + t.Errorf("expected a 400 response code, but got %d", reqInf.StatusCode) + } + if err == nil { + t.Errorf("error about topology having mid_loc node and no children expected, but got none") + } +} + +func CRUDTopologyReadOnlyUser(t *testing.T) { + opts := toclient.NewRequestOptions() + opts.QueryParameters.Set("name", "root") + resp, _, err := TOSession.GetTenants(opts) + if err != nil { + t.Fatalf("couldn't get the root tenant ID: %v - alerts: %+v", err, resp.Alerts) + } + if len(resp.Response) != 1 { + t.Fatalf("Expected exactly one Tenant to have the name 'root', found: %d", len(resp.Response)) + } + + toReqTimeout := time.Second * time.Duration(Config.Default.Session.TimeoutInSecs) + user := tc.UserV4{ + Username: "test_user", + RegistrationSent: new(time.Time), + LocalPassword: util.StrPtr("test_pa$$word"), + Role: "read-only", + } + user.Email = util.StrPtr("email@domain.com") + user.TenantID = resp.Response[0].ID + user.FullName = util.StrPtr("firstName LastName") + + u, _, err := TOSession.CreateUser(user, client.RequestOptions{}) + if err != nil { + t.Fatalf("could not create read-only user: %v - alerts: %+v", err, u.Alerts) + } + client, _, err := toclient.LoginWithAgent(TOSession.URL, "test_user", "test_pa$$word", true, "to-api-v5-client-tests/tenant4user", true, toReqTimeout) + if err != nil { + t.Fatalf("failed to log in with test_user: %v", err.Error()) + } + nodes := []tc.TopologyNode{ + { + Cachegroup: "parentName", + Parents: []int{}, + }, + } + top := tc.Topology{ + Name: "topology", + Description: "description", + Nodes: nodes, + } + // Create + _, reqInf, err := client.CreateTopology(top, toclient.RequestOptions{}) + if reqInf.StatusCode != http.StatusForbidden { + t.Errorf("expected a 403 Forbidden error, but got %d", reqInf.StatusCode) + } + if err == nil { + t.Errorf("expected error about Read-Only users not being able to create topologies, but got nothing") + alerts, _, err := client.DeleteTopology(top.Name, toclient.RequestOptions{}) + if err != nil { + t.Errorf("could not delete Topology '%s': %v - alerts: %+v", top.Name, err, alerts.Alerts) + } + } + + // Read + tops, _, err := client.GetTopologies(toclient.RequestOptions{}) + if err != nil { + t.Fatalf("couldn't get Topologies: %v - alerts: %+v", err, tops.Alerts) + } + if len(tops.Response) == 0 { + t.Fatal("expected to get one or more topologies in the response, but got none") + } + + // Update + updatedTop := tops.Response[0] + updatedTop.Description = "updated description" + _, reqInf, err = client.UpdateTopology(updatedTop.Name, updatedTop, toclient.RequestOptions{}) + if reqInf.StatusCode != http.StatusForbidden { + t.Errorf("expected a 403 Forbidden error, but got %d", reqInf.StatusCode) + } + if err == nil { + t.Errorf("expected error about Read-Only users not being able to update topologies, but got nothing") + } + + // Delete + _, reqInf, err = client.DeleteTopology(tops.Response[0].Name, toclient.RequestOptions{}) + if reqInf.StatusCode != http.StatusForbidden { + t.Errorf("expected a 403 Forbidden error, but got %d", reqInf.StatusCode) + } + if err == nil { + t.Errorf("expected error about Read-Only users not being able to delete topologies, but got nothing") + } + + ForceDeleteTestUsersByUsernames(t, []string{"test_user"}) +} + +func UpdateTopologyWithCachegroupAssignedToBecomeParentOfItself(t *testing.T) { + tops, _, err := TOSession.GetTopologies(toclient.RequestOptions{}) + if err != nil { + t.Fatalf("couldn't get Topologies: %v - alerts: %+v", err, tops.Alerts) + } + if len(tops.Response) == 0 { + t.Fatal("expected to get one or more topologies in the response, but got none") + } + tp := tops.Response[0] + + // create a list of indices consisting of all the node indices, + // so that when we assign this parent list wile updating, + // TO complains about the parent of a node being the same as itself + parents := make([]int, 0, len(tp.Nodes)) + for i := range tp.Nodes { + parents = append(parents, i) + } + nodes := tp.Nodes + for i := range nodes { + nodes[i].Parents = parents + } + tp.Nodes = nodes + + tops.Response[0] = tp + _, reqInf, err := TOSession.UpdateTopology(tops.Response[0].Name, tops.Response[0], toclient.RequestOptions{}) + if reqInf.StatusCode != http.StatusBadRequest { + t.Errorf("expected a 400 response code, but got %d", reqInf.StatusCode) + } + if err == nil { + t.Errorf("error about topology having parents the same as children expected, but got none") + } +} + +func UpdateTopologyWithSameParentAndSecondaryParent(t *testing.T) { + cacheGroups, _, err := TOSession.GetCacheGroups(toclient.RequestOptions{}) + if err != nil { + t.Fatalf("error while getting cachegroups: %v", err) + } + if len(cacheGroups.Response) == 0 { + t.Fatalf("no cachegroups in response") + } + + cachegroupName := "" + parentName := "" + opts := toclient.NewRequestOptions() + for _, cg := range cacheGroups.Response { + if cg.ID == nil { + t.Error("Traffic Ops returned a representation for a Cache Group with null or undefined ID") + continue + } + opts.QueryParameters.Set("cachegroup", strconv.Itoa(*cg.ID)) + resp, _, _ := TOSession.GetServers(opts) + if len(resp.Response) != 0 { + if cg.Name != nil && cg.Type != nil && *cg.Type == tc.CacheGroupEdgeTypeName { + parentName = *cg.Name + break + } + } + } + + for _, cg := range cacheGroups.Response { + if cg.ID == nil { + t.Error("Traffic Ops returned a representation for a Cache Group with null or undefined ID") + continue + } + opts.QueryParameters.Set("cachegroup", strconv.Itoa(*cg.ID)) + resp, _, _ := TOSession.GetServers(opts) + if len(resp.Response) != 0 { + if cg.Name != nil && parentName != *cg.Name && cg.Type != nil && *cg.Type == tc.CacheGroupEdgeTypeName { + cachegroupName = *cg.Name + break + } + } + } + + nodes := []tc.TopologyNode{ + { + Cachegroup: parentName, + Parents: []int{}, + }, + { + Cachegroup: cachegroupName, + Parents: []int{0, 0}, + }, + } + + tops, _, err := TOSession.GetTopologies(toclient.RequestOptions{}) + if err != nil { + t.Fatalf("couldn't get Topologies: %v - alerts: %+v", err, tops.Alerts) + } + if len(tops.Response) == 0 { + t.Fatal("expected to get one or more topologies in the response, but got none") + } + + top := tc.Topology{ + Description: tops.Response[0].Description, + Name: tops.Response[0].Name, + Nodes: nodes, + } + _, reqInf, err := TOSession.UpdateTopology(top.Name, top, toclient.RequestOptions{}) + if reqInf.StatusCode != http.StatusBadRequest { + t.Errorf("expected a 400 response code, but got %d", reqInf.StatusCode) + } + if err == nil { + t.Errorf("error about topology's cachegroup having same primary and secondary parents, but got none") + } +} + +func UpdateTopologyWithOrgLocAsChildNode(t *testing.T) { + cacheGroups, _, err := TOSession.GetCacheGroups(toclient.RequestOptions{}) + if err != nil { + t.Fatalf("error while getting cachegroups: %v", err) + } + if len(cacheGroups.Response) == 0 { + t.Fatalf("no cachegroups in response") + } + + cachegroupName := "" + parentName := "" + opts := toclient.NewRequestOptions() + for _, cg := range cacheGroups.Response { + if cg.ID == nil { + t.Error("Traffic Ops returned a representation for a Cache Group with null or undefined ID") + continue + } + opts.QueryParameters.Set("cachegroup", strconv.Itoa(*cg.ID)) + resp, _, _ := TOSession.GetServers(opts) + if len(resp.Response) != 0 { + if cg.Name != nil && cg.Type != nil && *cg.Type == tc.CacheGroupEdgeTypeName { + parentName = *cg.Name + break + } + } + } + + for _, cg := range cacheGroups.Response { + if cg.ID == nil { + t.Error("Traffic Ops returned a representation for a Cache Group with null or undefined ID") + continue + } + opts.QueryParameters.Set("cachegroup", strconv.Itoa(*cg.ID)) + resp, _, _ := TOSession.GetServers(opts) + if len(resp.Response) != 0 { + if cg.Name != nil && parentName != *cg.Name && cg.Type != nil && *cg.Type == tc.CacheGroupOriginTypeName { + cachegroupName = *cg.Name + break + } + } + } + + nodes := []tc.TopologyNode{ + { + Cachegroup: parentName, + Parents: []int{}, + }, + { + Cachegroup: cachegroupName, + Parents: []int{0}, + }, + } + + tops, _, err := TOSession.GetTopologies(toclient.RequestOptions{}) + if err != nil { + t.Fatalf("couldn't get topologies: %v", err) + } + if len(tops.Response) == 0 { + t.Fatal("expected to get one or more topologies in the response, but got none") + } + top := tc.Topology{ + Name: "topology_with_orgloc_as_child_node", + Description: "description", + Nodes: nodes, + } + _, reqInf, err := TOSession.UpdateTopology(tops.Response[0].Name, top, toclient.RequestOptions{}) + if reqInf.StatusCode != http.StatusBadRequest { + t.Errorf("expected a 400 response code, but got %d", reqInf.StatusCode) + } + if err == nil { + t.Errorf("error about topology having ord_loc node as child expected, but got none") + } +} + +func UpdateTopologyWithMidLocTypeWithoutChild(t *testing.T) { + cacheGroups, _, err := TOSession.GetCacheGroups(toclient.RequestOptions{}) + if err != nil { + t.Fatalf("error while getting cachegroups: %v", err) + } + if len(cacheGroups.Response) == 0 { + t.Fatalf("no cachegroups in response") + } + + parentName := "" + opts := toclient.NewRequestOptions() + for _, cg := range cacheGroups.Response { + if cg.ID == nil { + t.Error("Traffic Ops returned a representation for a Cache Group with null or undefined ID") + continue + } + opts.QueryParameters.Set("cachegroup", strconv.Itoa(*cg.ID)) + resp, _, _ := TOSession.GetServers(opts) + if len(resp.Response) != 0 { + if cg.Name != nil && cg.Type != nil && *cg.Type == tc.CacheGroupMidTypeName { + parentName = *cg.Name + break + } + } + } + + nodes := []tc.TopologyNode{ + { + Cachegroup: parentName, + Parents: []int{}, + }, + } + tops, _, err := TOSession.GetTopologies(toclient.RequestOptions{}) + if err != nil { + t.Fatalf("couldn't get topologies: %v", err) + } + if len(tops.Response) == 0 { + t.Fatal("expected to get one or more topologies in the response, but got none") + } + + top := tc.Topology{ + Name: tops.Response[0].Name, + Description: tops.Response[0].Description, + Nodes: nodes, + } + + _, reqInf, err := TOSession.UpdateTopology(tops.Response[0].Name, top, toclient.RequestOptions{}) + if reqInf.StatusCode != http.StatusBadRequest { + t.Errorf("expected a 400 response code, but got %d", reqInf.StatusCode) + } + if err == nil { + t.Errorf("error about topology having mid_loc node and no children expected, but got none") + } +} + +func UpdateTopologyWithInvalidParentNumber(t *testing.T) { + tops, _, err := TOSession.GetTopologies(toclient.RequestOptions{}) + if err != nil { + t.Fatalf("couldn't get Topologies: %v - alerts: %+v", err, tops.Alerts) + } + if len(tops.Response) == 0 { + t.Fatal("expected to get one or more topologies in the response, but got none") + } + tp := tops.Response[0] + parents := make([]int, 0) + parents = append(parents, len(tp.Nodes)+1) + for i := range tp.Nodes { + tp.Nodes[i].Parents = parents + } + _, reqInf, err := TOSession.UpdateTopology(tp.Name, tp, toclient.RequestOptions{}) + if reqInf.StatusCode != http.StatusBadRequest { + t.Errorf("expected a '400 Bad Request' response code, but got %d", reqInf.StatusCode) + } + if err == nil { + t.Errorf("expected error about the parent not being valid, but got none") + } +} + +func UpdateTopologyWithNoServers(t *testing.T) { + cacheGroups, _, err := TOSession.GetCacheGroups(toclient.RequestOptions{}) + if err != nil { + t.Fatalf("error while getting cachegroups: %v", err) + } + if len(cacheGroups.Response) == 0 { + t.Fatalf("no cachegroups in response") + } + nodes := make([]tc.TopologyNode, 0) + + cachegroupName := "" + opts := toclient.NewRequestOptions() + for _, cg := range cacheGroups.Response { + if cg.ID == nil { + t.Error("Traffic Ops returned a representation for a Cache Group with null or undefined ID") + continue + } + opts.QueryParameters.Set("cachegroup", strconv.Itoa(*cg.ID)) + resp, _, _ := TOSession.GetServers(opts) + if len(resp.Response) == 0 { + if cg.Name != nil && cg.Type != nil && *cg.Type == tc.CacheGroupEdgeTypeName { + cachegroupName = *cg.Name + break + } + } + } + node := tc.TopologyNode{ + Cachegroup: cachegroupName, + Parents: nil, + } + nodes = append(nodes, node) + + tops, _, err := TOSession.GetTopologies(toclient.RequestOptions{}) + if err != nil { + t.Fatalf("error getting Topologies: %v - alerts: %+v", err, tops.Alerts) + } + if len(tops.Response) == 0 { + t.Fatalf("expected 1 or more topologies in response, but got none") + } + tops.Response[0].Nodes = nodes + _, reqInf, err := TOSession.UpdateTopology(tops.Response[0].Name, tops.Response[0], toclient.RequestOptions{}) + if reqInf.StatusCode != http.StatusBadRequest { + t.Errorf("expected a 400 response code, but got %d", reqInf.StatusCode) + } + if err == nil { + t.Errorf("error about topology having no servers expected, but got none") + } +} + +func DeleteTopologyBeingUsedByDeliveryService(t *testing.T) { + ds, _, err := TOSession.GetDeliveryServices(toclient.RequestOptions{}) + if err != nil { + t.Fatalf("couldn't get Delivery Services: %v - alerts: %+v", err, ds.Alerts) + } + if len(ds.Response) == 0 { + t.Fatalf("expected one or more ds's in the response, got none") + } + topologyName := "" + for _, d := range ds.Response { + if d.Topology != nil && *d.Topology != "" { + topologyName = *d.Topology + break + } + } + if topologyName == "" { + t.Error("Expected at least one Delivery Service to have a Topology, but none did") + } + _, reqInf, err := TOSession.DeleteTopology(topologyName, toclient.RequestOptions{}) + if reqInf.StatusCode != http.StatusBadRequest { + t.Errorf("expected a 400 response code, but got %d", reqInf.StatusCode) + } + if err == nil { + t.Errorf("error about topology being used by a ds expected, but got none") + } +} + +func DeleteTopologyWithNonExistentName(t *testing.T) { + _, reqInf, err := TOSession.DeleteTopology("non existent name", toclient.RequestOptions{}) + if reqInf.StatusCode != http.StatusBadRequest { + t.Errorf("expected a 400 response code, but got %d", reqInf.StatusCode) + } + if err == nil { + t.Errorf("error about topology not being present expected, but got none") + } +} diff --git a/traffic_ops/testing/api/v5/traffic_control_test.go b/traffic_ops/testing/api/v5/traffic_control_test.go new file mode 100644 index 0000000000..a86ffcfb05 --- /dev/null +++ b/traffic_ops/testing/api/v5/traffic_control_test.go @@ -0,0 +1,64 @@ +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v5 + +import ( + "github.com/apache/trafficcontrol/lib/go-tc" +) + +// TrafficControl - maps to the tc-fixtures.json file +type TrafficControl struct { + ASNs []tc.ASN `json:"asns"` + CDNs []tc.CDN `json:"cdns"` + CDNLocks []tc.CDNLock `json:"cdnlocks"` + CacheGroups []tc.CacheGroupNullable `json:"cachegroups"` + Capabilities []tc.Capability `json:"capability"` + Coordinates []tc.Coordinate `json:"coordinates"` + DeliveryServicesRegexes []tc.DeliveryServiceRegexesTest `json:"deliveryServicesRegexes"` + DeliveryServiceRequests []tc.DeliveryServiceRequestV40 `json:"deliveryServiceRequests"` + DeliveryServiceRequestComments []tc.DeliveryServiceRequestComment `json:"deliveryServiceRequestComments"` + DeliveryServices []tc.DeliveryServiceV4 `json:"deliveryservices"` + DeliveryServicesRequiredCapabilities []tc.DeliveryServicesRequiredCapability `json:"deliveryservicesRequiredCapabilities"` + DeliveryServiceServerAssignments []tc.DeliveryServiceServers `json:"deliveryServiceServerAssignments"` + TopologyBasedDeliveryServicesRequiredCapabilities []tc.DeliveryServicesRequiredCapability `json:"topologyBasedDeliveryServicesRequiredCapabilities"` + Divisions []tc.Division `json:"divisions"` + Federations []tc.CDNFederation `json:"federations"` + FederationResolvers []tc.FederationResolver `json:"federation_resolvers"` + Jobs []tc.InvalidationJobCreateV4 `json:"jobs"` + Origins []tc.Origin `json:"origins"` + Profiles []tc.Profile `json:"profiles"` + Parameters []tc.Parameter `json:"parameters"` + ProfileParameters []tc.ProfileParameter `json:"profileParameters"` + PhysLocations []tc.PhysLocation `json:"physLocations"` + Regions []tc.Region `json:"regions"` + Roles []tc.RoleV4 `json:"roles"` + Servers []tc.ServerV4 `json:"servers"` + ServerServerCapabilities []tc.ServerServerCapability `json:"serverServerCapabilities"` + ServerCapabilities []tc.ServerCapability `json:"serverCapabilities"` + ServiceCategories []tc.ServiceCategory `json:"serviceCategories"` + Statuses []tc.StatusNullable `json:"statuses"` + StaticDNSEntries []tc.StaticDNSEntry `json:"staticdnsentries"` + StatsSummaries []tc.StatsSummary `json:"statsSummaries"` + Tenants []tc.Tenant `json:"tenants"` + ServerCheckExtensions []tc.ServerCheckExtensionNullable `json:"servercheck_extensions"` + Topologies []tc.Topology `json:"topologies"` + Types []tc.Type `json:"types"` + SteeringTargets []tc.SteeringTargetNullable `json:"steeringTargets"` + Serverchecks []tc.ServercheckRequestNullable `json:"serverchecks"` + Users []tc.UserV4 `json:"users"` + InvalidationJobs []tc.InvalidationJobCreateV4 `json:"invalidationJobs"` + InvalidationJobsRefetch []tc.InvalidationJobCreateV4 `json:"invalidationJobsRefetch"` +} diff --git a/traffic_ops/testing/api/v5/traffic_ops_test.go b/traffic_ops/testing/api/v5/traffic_ops_test.go new file mode 100644 index 0000000000..7339b350dd --- /dev/null +++ b/traffic_ops/testing/api/v5/traffic_ops_test.go @@ -0,0 +1,115 @@ +// Package v5 provides tests for the Traffic Ops API version 5. +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "database/sql" + "flag" + "fmt" + "os" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-log" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/config" + _ "github.com/lib/pq" +) + +var ( + Config config.Config + testData TrafficControl + includeSystemTests bool +) + +const TestAPIBase = "/api/4.0" + +func TestMain(m *testing.M) { + configFileName := flag.String("cfg", "traffic-ops-test.conf", "The config file path") + tcFixturesFileName := flag.String("fixtures", "tc-fixtures.json", "The test fixtures for the API test tool") + cliIncludeSystemTests := *flag.Bool("includeSystemTests", false, "Whether to enable tests that have environment dependencies beyond a database") + flag.Parse() + + // Skip loading configuration when run with `go test -list=`. The -list + // flag does not actually run tests, so configuration data is not needed in + // that mode. If the user is just trying to list the available tests we + // don't want to abort with an error about a bad configuration the user + // doesn't care about yet. + if f := flag.Lookup("test.list"); f != nil { + if f.Value.String() != "" { + os.Exit(m.Run()) + } + } + + var err error + if Config, err = config.LoadConfig(*configFileName); err != nil { + fmt.Printf("Error Loading Config: %v\n", err) + os.Exit(1) + } + + // CLI option overrides config + includeSystemTests = Config.Default.IncludeSystemTests || cliIncludeSystemTests + + if err = log.InitCfg(Config); err != nil { + fmt.Printf("Error initializing loggers: %v\n", err) + os.Exit(1) + } + + log.Infof(`Using Config values: + TO Config File: %s + TO Fixtures: %s + TO URL: %s + TO Session Timeout In Secs: %d + DB Server: %s + DB User: %s + DB Name: %s + DB Ssl: %t + UseIMS: %v`, *configFileName, *tcFixturesFileName, Config.TrafficOps.URL, Config.Default.Session.TimeoutInSecs, Config.TrafficOpsDB.Hostname, Config.TrafficOpsDB.User, Config.TrafficOpsDB.Name, Config.TrafficOpsDB.SSL, Config.UseIMS) + + //Load the test data + LoadFixtures(*tcFixturesFileName) + + var db *sql.DB + db, err = OpenConnection() + if err != nil { + fmt.Printf("\nError opening connection to %s - %s, %v\n", Config.TrafficOps.URL, Config.TrafficOpsDB.User, err) + os.Exit(1) + } + defer db.Close() + + err = Teardown(db) + if err != nil { + fmt.Printf("\nError tearingdown data %s - %s, %v\n", Config.TrafficOps.URL, Config.TrafficOpsDB.User, err) + os.Exit(1) + } + + err = SetupTestData(db) + if err != nil { + fmt.Printf("\nError setting up data %s - %s, %v\n", Config.TrafficOps.URL, Config.TrafficOpsDB.User, err) + os.Exit(1) + } + + toReqTimeout := time.Second * time.Duration(Config.Default.Session.TimeoutInSecs) + err = SetupSession(toReqTimeout, Config.TrafficOps.URL, Config.TrafficOps.Users.Admin, Config.TrafficOps.UserPassword) + if err != nil { + fmt.Printf("\nError creating session to %s - %s, %v\n", Config.TrafficOps.URL, Config.TrafficOpsDB.User, err) + os.Exit(1) + } + + // Now run the test case + rc := m.Run() + os.Exit(rc) +} diff --git a/traffic_ops/testing/api/v5/traffic_vault_ping_test.go b/traffic_ops/testing/api/v5/traffic_vault_ping_test.go new file mode 100644 index 0000000000..4ee643d088 --- /dev/null +++ b/traffic_ops/testing/api/v5/traffic_vault_ping_test.go @@ -0,0 +1,56 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/http" + "testing" + + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" +) + +func TestTrafficVaultPing(t *testing.T) { + + if !includeSystemTests { + t.Skip() + } + + WithObjs(t, []TCObj{CDNs, Types, Tenants, Users, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers}, func() { + methodTests := utils.V5TestCase{ + "GET": { + "OK when VALID request": { + ClientSession: TOSession, Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.TrafficVaultPing(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp, resp.Alerts, err) + } + }) + } + } + }) + } + }) +} diff --git a/traffic_ops/testing/api/v5/types_test.go b/traffic_ops/testing/api/v5/types_test.go new file mode 100644 index 0000000000..b3f2a97434 --- /dev/null +++ b/traffic_ops/testing/api/v5/types_test.go @@ -0,0 +1,279 @@ +package v5 + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "fmt" + "net/http" + "net/url" + "sort" + "testing" + "time" + + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + + "github.com/apache/trafficcontrol/lib/go-rfc" + tc "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestTypes(t *testing.T) { + WithObjs(t, []TCObj{Parameters, Types}, func() { + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when VALID request": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateTypeSort()), + }, + "OK when VALID NAME parameter": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"name": {"ORG"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(1), + validateTypeFields(map[string]interface{}{"Name": "ORG"})), + }, + }, + "POST": { + "BAD REQUEST when useInTable NOT server": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "description": "Host header regular expression-Test", + "name": "TEST_1", + "useInTable": "regex", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "OK when VALID request when useInTable=server": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "description": "Host header regular expression-Test", + "name": "TEST_4", + "useInTable": "server", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateTypeUpdateCreateFields("TEST_4", map[string]interface{}{"Name": "TEST_4"})), + }, + }, + "PUT": { + "BAD REQUEST when useInTable NOT server": { + EndpointId: GetTypeID(t, "ACTIVE_DIRECTORY"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "description": "Active Directory User", + "name": "TEST_3", + "useInTable": "cachegroup", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "OK when VALID request when useInTable=server": { + EndpointId: GetTypeID(t, "RIAK"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "description": "riak type", + "name": "TEST_5", + "useInTable": "server", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateTypeUpdateCreateFields("TEST_5", map[string]interface{}{"Name": "TEST_5"})), + }, + }, + "DELETE": { + "OK when VALID request": { + EndpointId: GetTypeID(t, "INFLUXDB"), + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + "GET AFTER CHANGES": { + "OK when CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {currentTimeRFC}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + typ := tc.Type{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &typ) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET", "GET AFTER CHANGES": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetTypes(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.CreateType(typ, client.RequestOptions{}) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.UpdateType(testCase.EndpointId(), typ, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + case "DELETE": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.DeleteType(testCase.EndpointId(), testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateTypeSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Type response to not be nil.") + var typeNames []string + typeResp := resp.([]tc.Type) + for _, typ := range typeResp { + typeNames = append(typeNames, typ.Name) + } + assert.Equal(t, true, sort.StringsAreSorted(typeNames), "List is not sorted by their names: %v", typeNames) + } +} + +func validateTypeFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Type response to not be nil.") + typeResp := resp.([]tc.Type) + for field, expected := range expectedResp { + for _, typ := range typeResp { + switch field { + case "Name": + assert.Equal(t, expected, typ.Name, "Expected Name to be %v, but got %s", expected, typ.Name) + case "UseInTable": + assert.Equal(t, expected, typ.UseInTable, "Expected UseInTable to be %v, but got %s", expected, typ.UseInTable) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validateTypeUpdateCreateFields(name string, expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", name) + typ, _, err := TOSession.GetTypes(opts) + assert.RequireNoError(t, err, "Error getting Types: %v - alerts: %+v", err, typ.Alerts) + assert.RequireEqual(t, 1, len(typ.Response), "Expected one Type returned, Got: %d", len(typ.Response)) + validateTypeFields(expectedResp)(t, toclientlib.ReqInf{}, typ.Response, tc.Alerts{}, nil) + } +} + +func GetTypeID(t *testing.T, typeName string) func() int { + return func() int { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", typeName) + resp, _, err := TOSession.GetTypes(opts) + + assert.RequireNoError(t, err, "Get Types Request failed with error: %v", err) + assert.RequireEqual(t, 1, len(resp.Response), "Expected response object length 1, but got %d", len(resp.Response)) + + return resp.Response[0].ID + } +} + +func CreateTestTypes(t *testing.T) { + db, err := OpenConnection() + assert.RequireNoError(t, err, "cannot open db") + + defer func() { + err := db.Close() + assert.NoError(t, err, "unable to close connection to db, error: %v", err) + }() + dbQueryTemplate := "INSERT INTO type (name, description, use_in_table) VALUES ('%s', '%s', '%s');" + + for _, typ := range testData.Types { + if typ.UseInTable != "server" { + err = execSQL(db, fmt.Sprintf(dbQueryTemplate, typ.Name, typ.Description, typ.UseInTable)) + assert.RequireNoError(t, err, "could not create Type using database operations: %v", err) + } else { + alerts, _, err := TOSession.CreateType(typ, client.RequestOptions{}) + assert.RequireNoError(t, err, "could not create Type: %v - alerts: %+v", err, alerts.Alerts) + } + } +} + +func DeleteTestTypes(t *testing.T) { + db, err := OpenConnection() + assert.RequireNoError(t, err, "cannot open db") + + defer func() { + err := db.Close() + assert.NoError(t, err, "unable to close connection to db, error: %v", err) + }() + dbDeleteTemplate := "DELETE FROM type WHERE name='%s';" + + types, _, err := TOSession.GetTypes(client.RequestOptions{}) + assert.NoError(t, err, "Cannot get Types: %v - alerts: %+v", err, types.Alerts) + + for _, typ := range types.Response { + if typ.Name == "CHECK_EXTENSION_BOOL" || typ.Name == "CHECK_EXTENSION_NUM" || typ.Name == "CHECK_EXTENSION_OPEN_SLOT" { + continue + } + + if typ.UseInTable != "server" { + err := execSQL(db, fmt.Sprintf(dbDeleteTemplate, typ.Name)) + assert.RequireNoError(t, err, "cannot delete Type using database operations: %v", err) + } else { + delResp, _, err := TOSession.DeleteType(typ.ID, client.RequestOptions{}) + assert.RequireNoError(t, err, "cannot delete Type using the API: %v - alerts: %+v", err, delResp.Alerts) + } + + // Retrieve the Type by name to see if it was deleted. + opts := client.NewRequestOptions() + opts.QueryParameters.Set("name", typ.Name) + types, _, err := TOSession.GetTypes(opts) + assert.NoError(t, err, "error fetching Types filtered by presumably deleted name: %v - alerts: %+v", err, types.Alerts) + assert.Equal(t, 0, len(types.Response), "expected Type '%s' to be deleted", typ.Name) + } +} diff --git a/traffic_ops/testing/api/v5/user_current_test.go b/traffic_ops/testing/api/v5/user_current_test.go new file mode 100644 index 0000000000..157aa8e5e3 --- /dev/null +++ b/traffic_ops/testing/api/v5/user_current_test.go @@ -0,0 +1,115 @@ +package v5 + +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" +) + +func TestUserCurrent(t *testing.T) { + WithObjs(t, []TCObj{Tenants, Parameters, Users}, func() { + + opsUserSession := utils.CreateV5Session(t, Config.TrafficOps.URL, "opsuser", "pa$$word", Config.Default.Session.TimeoutInSecs) + + methodTests := utils.V5TestCase{ + "GET": { + "OK when VALID request": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateUsersUpdateCreateFields(map[string]interface{}{"Username": "admin"})), + }, + }, + "PUT": { + "OK when VALID request": { + ClientSession: opsUserSession, + RequestBody: map[string]interface{}{ + "addressLine1": "address of ops", + "addressLine2": "place", + "city": "somewhere", + "company": "else", + "country": "UK", + "email": "ops-updated@example.com", + "fullName": "Operations User Updated", + "localPasswd": "pa$$word", + "confirmLocalPasswd": "pa$$word", + "role": "operations", + "tenant": "root", + "tenantId": GetTenantID(t, "root")(), + "username": "opsuser", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateUsersUpdateCreateFields(map[string]interface{}{"Email": "ops-updated@example.com", "FullName": "Operations User Updated"})), + }, + "BAD REQUEST when EMPTY EMAIL field": { + ClientSession: opsUserSession, + RequestBody: map[string]interface{}{ + "addressLine1": "address of ops", + "addressLine2": "place", + "city": "somewhere", + "company": "else", + "country": "UK", + "email": "", + "fullName": "Operations User Updated", + "localPasswd": "pa$$word", + "confirmLocalPasswd": "pa$$word", + "role": "operations", + "tenant": "root", + "tenantId": GetTenantID(t, "root")(), + "username": "opsuser", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + user := tc.UserV4{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &user) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetUserCurrent(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.UpdateCurrentUser(user, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + } + } + }) + } + }) +} diff --git a/traffic_ops/testing/api/v5/users_register_test.go b/traffic_ops/testing/api/v5/users_register_test.go new file mode 100644 index 0000000000..7207e4a191 --- /dev/null +++ b/traffic_ops/testing/api/v5/users_register_test.go @@ -0,0 +1,93 @@ +package v5 + +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "testing" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +func TestUsersRegister(t *testing.T) { + if includeSystemTests { + WithObjs(t, []TCObj{Tenants, Parameters}, func() { + + methodTests := utils.V5TestCase{ + "POST": { + "OK when VALID request": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "addressLine1": "address of ops", + "addressLine2": "place", + "city": "somewhere", + "company": "else", + "country": "UK", + "email": "opsupdated@example.com", + "fullName": "Operations User Updated", + "localPasswd": "pa$$word", + "confirmLocalPasswd": "pa$$word", + "role": "operations", + "tenant": "root", + "tenantId": GetTenantID(t, "root")(), + "username": "opsuser", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateDeletion("opsupdated@example.com")), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + userRegistration := tc.UserRegistrationRequestV4{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &userRegistration) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "POST": + t.Run(name, func(t *testing.T) { + alerts, reqInf, err := testCase.ClientSession.RegisterNewUser(userRegistration.TenantID, userRegistration.Role, userRegistration.Email, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } + }) + } + } + }) + } + }) + } +} + +func validateDeletion(email string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + db, err := OpenConnection() + assert.RequireNoError(t, err, "Cannot open db") + defer db.Close() + q := `DELETE FROM tm_user WHERE email = '` + email + `'` + err = execSQL(db, q) + assert.NoError(t, err, "Cannot execute SQL to delete registered users: %s; SQL is %s", err, q) + } +} diff --git a/traffic_ops/testing/api/v5/users_test.go b/traffic_ops/testing/api/v5/users_test.go new file mode 100644 index 0000000000..b9250a3c23 --- /dev/null +++ b/traffic_ops/testing/api/v5/users_test.go @@ -0,0 +1,375 @@ +package v5 + +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "net/http" + "net/url" + "sort" + "strings" + "testing" + "time" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/assert" + "github.com/apache/trafficcontrol/traffic_ops/testing/api/utils" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + client "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +func TestUsers(t *testing.T) { + WithObjs(t, []TCObj{Tenants, Parameters, Users}, func() { + + opsUserSession := utils.CreateV5Session(t, Config.TrafficOps.URL, "opsuser", "pa$$word", Config.Default.Session.TimeoutInSecs) + tenant4UserSession := utils.CreateV5Session(t, Config.TrafficOps.URL, "tenant4user", "pa$$word", Config.Default.Session.TimeoutInSecs) + + currentTime := time.Now().UTC().Add(-15 * time.Second) + currentTimeRFC := currentTime.Format(time.RFC1123) + tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123) + + methodTests := utils.V5TestCase{ + "GET": { + "NOT MODIFIED when NO CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {tomorrow}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusNotModified)), + }, + "OK when CHANGES made": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{Header: http.Header{rfc.IfModifiedSince: {currentTimeRFC}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK when VALID request": { + ClientSession: TOSession, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateUsersSort()), + }, + "ADMIN can view CHILD TENANT": { + ClientSession: TOSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"tenant": {"tenant4"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseLengthGreaterOrEqual(1), + validateUsersFields(map[string]interface{}{"Tenant": "tenant4"})), + }, + "EMPTY RESPONSE when CHILD TENANT reads PARENT TENANT": { + ClientSession: tenant4UserSession, + RequestOpts: client.RequestOptions{QueryParameters: url.Values{"tenant": {"tenant3"}}}, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), utils.ResponseHasLength(0)), + }, + }, + "POST": { + "FORBIDDEN when CHILD TENANT creates USER with PARENT TENANCY": { + ClientSession: tenant4UserSession, + RequestBody: map[string]interface{}{ + "email": "outsidetenancy@example.com", + "fullName": "Outside Tenancy", + "localPasswd": "pa$$word", + "confirmLocalPasswd": "pa$$word", + "role": "operations", + "tenantId": GetTenantID(t, "tenant3")(), + "username": "outsideTenantUser", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + "PUT": { + "OK when VALID request": { + EndpointId: GetUserID(t, "steering"), + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "addressLine1": "updated line 1", + "addressLine2": "updated line 2", + "city": "updated city name", + "company": "new company", + "country": "US", + "email": "steeringupdated@example.com", + "fullName": "Steering User Updated", + "localPasswd": "pa$$word", + "confirmLocalPasswd": "pa$$word", + "newUser": false, + "role": "steering", + "tenant": "root", + "tenantId": GetTenantID(t, "root")(), + "username": "steering", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateUsersUpdateCreateFields(map[string]interface{}{"AddressLine1": "updated line 1", + "AddressLine2": "updated line 2", "City": "updated city name", "Company": "new company", + "Country": "US", "Email": "steeringupdated@example.com", "FullName": "Steering User Updated"})), + }, + "OK when UPDATING SELF": { + EndpointId: GetUserID(t, "opsuser"), + ClientSession: opsUserSession, + RequestBody: map[string]interface{}{ + "addressLine1": "address of ops", + "addressLine2": "place", + "city": "somewhere", + "company": "else", + "country": "UK", + "email": "ops-updated@example.com", + "fullName": "Operations User Updated", + "localPasswd": "pa$$word", + "confirmLocalPasswd": "pa$$word", + "role": "operations", + "tenant": "root", + "tenantId": GetTenantID(t, "root")(), + "username": "opsuser", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), + validateUsersUpdateCreateFields(map[string]interface{}{"Email": "ops-updated@example.com", "FullName": "Operations User Updated"})), + }, + "NOT FOUND when UPDATING SELF with ROLE that DOESNT EXIST": { + EndpointId: GetUserID(t, "opsuser"), + ClientSession: opsUserSession, + RequestBody: map[string]interface{}{ + "addressLine1": "address of ops", + "addressLine2": "place", + "city": "somewhere", + "company": "else", + "country": "UK", + "email": "ops-updated@example.com", + "fullName": "Operations User Updated", + "localPasswd": "pa$$word", + "confirmLocalPasswd": "pa$$word", + "role": "operations_updated", + "tenant": "root", + "tenantId": GetTenantID(t, "root")(), + "username": "opsuser", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusNotFound)), + }, + "FORBIDDEN when OPERATIONS USER updates ADMIN USER": { + EndpointId: GetUserID(t, "admin"), + ClientSession: opsUserSession, + RequestBody: map[string]interface{}{ + "email": "oops@ops.net", + "fullName": "oops", + "localPasswd": "pa$$word", + "confirmLocalPasswd": "pa$$word", + "role": "admin", + "tenant": "root", + "tenantId": GetTenantID(t, "root")(), + "username": "admin", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + "FORBIDDEN when CHILD TENANT USER updates PARENT TENANT USER": { + EndpointId: GetUserID(t, "tenant3user"), + ClientSession: tenant4UserSession, + RequestBody: map[string]interface{}{ + "email": "tenant3user@example.com", + "fullName": "Parent tenant test", + "localPasswd": "pa$$word", + "confirmLocalPasswd": "pa$$word", + "role": "admin", + "tenant": "tenant2", + "tenantId": GetTenantID(t, "tenant2")(), + "username": "tenant3user", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusForbidden)), + }, + }, + } + + for method, testCases := range methodTests { + t.Run(method, func(t *testing.T) { + for name, testCase := range testCases { + user := tc.UserV4{} + + if testCase.RequestBody != nil { + dat, err := json.Marshal(testCase.RequestBody) + assert.NoError(t, err, "Error occurred when marshalling request body: %v", err) + err = json.Unmarshal(dat, &user) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + + switch method { + case "GET": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.GetUsers(testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "POST": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.CreateUser(user, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + case "PUT": + t.Run(name, func(t *testing.T) { + resp, reqInf, err := testCase.ClientSession.UpdateUser(testCase.EndpointId(), user, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, resp.Response, resp.Alerts, err) + } + }) + } + } + }) + } + }) +} + +func validateUsersFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Users response to not be nil.") + userResp := resp.([]tc.UserV4) + for field, expected := range expectedResp { + for _, user := range userResp { + switch field { + case "AddressLine1": + assert.RequireNotNil(t, user.AddressLine1, "Expected AddressLine1 to not be nil.") + assert.Equal(t, expected, *user.AddressLine1, "Expected AddressLine1 to be %v, but got %s", expected, *user.AddressLine1) + case "AddressLine2": + assert.RequireNotNil(t, user.AddressLine2, "Expected AddressLine2 to not be nil.") + assert.Equal(t, expected, *user.AddressLine2, "Expected AddressLine2 to be %v, but got %s", expected, *user.AddressLine2) + case "City": + assert.RequireNotNil(t, user.City, "Expected City to not be nil.") + assert.Equal(t, expected, *user.City, "Expected City to be %v, but got %s", expected, *user.City) + case "Company": + assert.RequireNotNil(t, user.Company, "Expected Company to not be nil.") + assert.Equal(t, expected, *user.Company, "Expected Company to be %v, but got %s", expected, *user.Company) + case "Country": + assert.RequireNotNil(t, user.Country, "Expected Country to not be nil.") + assert.Equal(t, expected, *user.Country, "Expected Country to be %v, but got %s", expected, *user.Country) + case "Email": + assert.RequireNotNil(t, user.Email, "Expected Email to not be nil.") + assert.Equal(t, expected, *user.Email, "Expected Email to be %v, but got %s", expected, *user.Email) + case "FullName": + assert.RequireNotNil(t, user.FullName, "Expected FullName to not be nil.") + assert.Equal(t, expected, *user.FullName, "Expected FullName to be %v, but got %s", expected, *user.FullName) + case "ID": + assert.RequireNotNil(t, user.ID, "Expected ID to not be nil.") + assert.Equal(t, expected, *user.ID, "Expected ID to be %v, but got %d", expected, user.ID) + case "PhoneNumber": + assert.RequireNotNil(t, user.PhoneNumber, "Expected PhoneNumber to not be nil.") + assert.Equal(t, expected, *user.PhoneNumber, "Expected PhoneNumber to be %v, but got %s", expected, *user.PhoneNumber) + case "PostalCode": + assert.RequireNotNil(t, user.PostalCode, "Expected PostalCode to not be nil.") + assert.Equal(t, expected, *user.PostalCode, "Expected PostalCode to be %v, but got %s", expected, *user.PostalCode) + case "RegistrationSent": + assert.RequireNotNil(t, user.RegistrationSent, "Expected RegistrationSent to not be nil.") + assert.Equal(t, expected, *user.RegistrationSent, "Expected RegistrationSent to be %v, but got %v", expected, *user.RegistrationSent) + case "Role": + assert.Equal(t, expected, user.Role, "Expected Role to be %v, but got %s", expected, user.Role) + case "StateOrProvince": + assert.RequireNotNil(t, user.StateOrProvince, "Expected StateOrProvince to not be nil.") + assert.Equal(t, expected, *user.StateOrProvince, "Expected StateOrProvince to be %v, but got %s", expected, *user.StateOrProvince) + case "Tenant": + assert.RequireNotNil(t, user.Tenant, "Expected Tenant to not be nil.") + assert.Equal(t, expected, *user.Tenant, "Expected Tenant to be %v, but got %s", expected, *user.Tenant) + case "TenantID": + assert.Equal(t, expected, user.TenantID, "Expected TenantID to be %v, but got %d", expected, user.TenantID) + case "Username": + assert.Equal(t, expected, user.Username, "Expected Username to be %v, but got %s", expected, user.Username) + default: + t.Errorf("Expected field: %v, does not exist in response", field) + } + } + } + } +} + +func validateUsersUpdateCreateFields(expectedResp map[string]interface{}) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Users response to not be nil.") + assert.RequireNotEqual(t, resp.(tc.UserV4), tc.UserV4{}, "Expected a non empty response.") + userResp := resp.(tc.UserV4) + users := []tc.UserV4{userResp} + validateUsersFields(expectedResp)(t, toclientlib.ReqInf{}, users, tc.Alerts{}, nil) + } +} + +func validateUsersSort() utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + assert.RequireNotNil(t, resp, "Expected Users response to not be nil.") + var usernames []string + usersResp := resp.([]tc.UserV4) + for _, user := range usersResp { + usernames = append(usernames, user.Username) + } + assert.Equal(t, true, sort.StringsAreSorted(usernames), "List is not sorted by their usernames: %v", usernames) + } +} + +func GetUserID(t *testing.T, username string) func() int { + return func() int { + opts := client.NewRequestOptions() + opts.QueryParameters.Set("username", username) + users, _, err := TOSession.GetUsers(opts) + assert.RequireNoError(t, err, "Get Users Request failed with error:", err) + assert.RequireEqual(t, 1, len(users.Response), "Expected response object length 1, but got %d", len(users.Response)) + assert.RequireNotNil(t, users.Response[0].ID, "Expected ID to not be nil.") + return *users.Response[0].ID + } +} + +func CreateTestUsers(t *testing.T) { + for _, user := range testData.Users { + resp, _, err := TOSession.CreateUser(user, client.RequestOptions{}) + assert.RequireNoError(t, err, "Could not create user: %v - alerts: %+v", err, resp.Alerts) + } +} + +// ForceDeleteTestUsers forcibly deletes the users from the db. +// NOTE: Special circumstances! This should *NOT* be done without a really good reason! +// Connects directly to the DB to remove users rather than going through the client. +// This is required here because the DeleteUser action does not really delete users, but disables them. +func ForceDeleteTestUsers(t *testing.T) { + + db, err := OpenConnection() + assert.RequireNoError(t, err, "Cannot open db") + defer db.Close() + + var usernames []string + for _, user := range testData.Users { + usernames = append(usernames, `'`+user.Username+`'`) + } + + // there is a constraint that prevents users from being deleted when they have a log + q := `DELETE FROM log WHERE NOT tm_user = (SELECT id FROM tm_user WHERE username = 'admin')` + err = execSQL(db, q) + assert.RequireNoError(t, err, "Cannot execute SQL: %v; SQL is %s", err, q) + + q = `DELETE FROM tm_user WHERE username IN (` + strings.Join(usernames, ",") + `)` + err = execSQL(db, q) + assert.NoError(t, err, "Cannot execute SQL: %v; SQL is %s", err, q) +} + +// ForceDeleteTestUsersByUsernames forcibly deletes the users passed in from a slice of usernames from the db. +// NOTE: Special circumstances! This should *NOT* be done without a really good reason! +// Connects directly to the DB to remove users rather than going through the client. +// This is required here because the DeleteUser action does not really delete users, but disables them. +func ForceDeleteTestUsersByUsernames(t *testing.T, usernames []string) { + + db, err := OpenConnection() + assert.RequireNoError(t, err, "Cannot open db") + defer db.Close() + + for i, u := range usernames { + usernames[i] = `'` + u + `'` + } + // there is a constraint that prevents users from being deleted when they have a log + q := `DELETE FROM log WHERE NOT tm_user = (SELECT id FROM tm_user WHERE username = 'admin')` + err = execSQL(db, q) + assert.RequireNoError(t, err, "Cannot execute SQL: %s; SQL is %s", err, q) + + q = `DELETE FROM tm_user WHERE username IN (` + strings.Join(usernames, ",") + `)` + err = execSQL(db, q) + assert.NoError(t, err, "Cannot execute SQL: %s; SQL is %s", err, q) +} diff --git a/traffic_ops/testing/api/v5/withobjs_test.go b/traffic_ops/testing/api/v5/withobjs_test.go new file mode 100644 index 0000000000..94fdbf7bb5 --- /dev/null +++ b/traffic_ops/testing/api/v5/withobjs_test.go @@ -0,0 +1,132 @@ +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package v5 + +import ( + "testing" +) + +// WithObjs creates the objs in order, runs f, and defers deleting the objs in the same order. +// +// Because deletion is deferred, using this ensures objects will be cleaned up if f panics or calls t.Fatal, as much as possible. +// +// Note that f itself may still create things which are not cleaned up properly, and likewise, the object creation and deletion tests themselves may fail. +// All tests in the Traffic Ops API Testing framework use the same Traffic Ops instance, with persistent data. Because of this, when any test fails, all subsequent tests should be considered invalid, irrespective whether they pass or fail. Users are encouraged to use `go test -failfast`. +func WithObjs(t *testing.T, objs []TCObj, f func()) { + defer func() { + for index := len(objs) - 1; index >= 0; index-- { + obj := objs[index] + withFuncs[obj].Delete(t) + } + }() + for _, obj := range objs { + withFuncs[obj].Create(t) + } + f() +} + +type TCObj int + +const ( + ASN TCObj = iota + CacheGroups + CacheGroupsDeliveryServices + CDNs + CDNLocks + CDNFederations + CDNNotifications + Coordinates + DeliveryServices + DeliveryServicesRegexes + DeliveryServiceRequests + DeliveryServiceRequestComments + DeliveryServicesRequiredCapabilities + DeliveryServiceServerAssignments + Divisions + FederationDeliveryServices + FederationResolvers + FederationFederationResolvers + FederationUsers + Jobs + Origins + Parameters + PhysLocations + Profiles + ProfileParameters + Regions + Roles + ServerCapabilities + ServerChecks + ServerServerCapabilities + Servers + ServiceCategories + Statuses + StaticDNSEntries + SteeringTargets + Tenants + ServerCheckExtensions + Topologies + Types + Users +) + +type TCObjFuncs struct { + Create func(t *testing.T) + Delete func(t *testing.T) +} + +var withFuncs = map[TCObj]TCObjFuncs{ + ASN: {CreateTestASNs, DeleteTestASNs}, + CacheGroups: {CreateTestCacheGroups, DeleteTestCacheGroups}, + CacheGroupsDeliveryServices: {CreateTestCachegroupsDeliveryServices, DeleteTestCachegroupsDeliveryServices}, + CDNs: {CreateTestCDNs, DeleteTestCDNs}, + CDNLocks: {CreateTestCDNLocks, DeleteTestCDNLocks}, + CDNNotifications: {CreateTestCDNNotifications, DeleteTestCDNNotifications}, + CDNFederations: {CreateTestCDNFederations, DeleteTestCDNFederations}, + Coordinates: {CreateTestCoordinates, DeleteTestCoordinates}, + DeliveryServices: {CreateTestDeliveryServices, DeleteTestDeliveryServices}, + DeliveryServicesRegexes: {CreateTestDeliveryServicesRegexes, DeleteTestDeliveryServicesRegexes}, + DeliveryServiceRequests: {CreateTestDeliveryServiceRequests, DeleteTestDeliveryServiceRequests}, + DeliveryServiceRequestComments: {CreateTestDeliveryServiceRequestComments, DeleteTestDeliveryServiceRequestComments}, + DeliveryServicesRequiredCapabilities: {CreateTestDeliveryServicesRequiredCapabilities, DeleteTestDeliveryServicesRequiredCapabilities}, + DeliveryServiceServerAssignments: {CreateTestDeliveryServiceServerAssignments, DeleteTestDeliveryServiceServers}, + Divisions: {CreateTestDivisions, DeleteTestDivisions}, + FederationDeliveryServices: {CreateTestFederationDeliveryServices, DeleteTestCDNFederations}, + FederationUsers: {CreateTestFederationUsers, DeleteTestFederationUsers}, + FederationResolvers: {CreateTestFederationResolvers, DeleteTestFederationResolvers}, + FederationFederationResolvers: {CreateTestFederationFederationResolvers, DeleteTestFederationFederationResolvers}, + Jobs: {CreateTestJobs, DeleteTestJobs}, + Origins: {CreateTestOrigins, DeleteTestOrigins}, + Parameters: {CreateTestParameters, DeleteTestParameters}, + PhysLocations: {CreateTestPhysLocations, DeleteTestPhysLocations}, + Profiles: {CreateTestProfiles, DeleteTestProfiles}, + ProfileParameters: {CreateTestProfileParameters, DeleteTestProfileParameters}, + Regions: {CreateTestRegions, DeleteTestRegions}, + Roles: {CreateTestRoles, DeleteTestRoles}, + ServerCapabilities: {CreateTestServerCapabilities, DeleteTestServerCapabilities}, + ServerChecks: {CreateTestServerChecks, DeleteTestServerChecks}, + ServerServerCapabilities: {CreateTestServerServerCapabilities, DeleteTestServerServerCapabilities}, + Servers: {CreateTestServers, DeleteTestServers}, + ServiceCategories: {CreateTestServiceCategories, DeleteTestServiceCategories}, + Statuses: {CreateTestStatuses, DeleteTestStatuses}, + StaticDNSEntries: {CreateTestStaticDNSEntries, DeleteTestStaticDNSEntries}, + SteeringTargets: {CreateTestSteeringTargets, DeleteTestSteeringTargets}, + Tenants: {CreateTestTenants, DeleteTestTenants}, + ServerCheckExtensions: {CreateTestServerCheckExtensions, DeleteTestServerCheckExtensions}, + Topologies: {CreateTestTopologies, DeleteTestTopologies}, + Types: {CreateTestTypes, DeleteTestTypes}, + Users: {CreateTestUsers, ForceDeleteTestUsers}, +} diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go b/traffic_ops/traffic_ops_golang/routing/routes.go index 98e833408d..d70de9e2a9 100644 --- a/traffic_ops/traffic_ops_golang/routing/routes.go +++ b/traffic_ops/traffic_ops_golang/routing/routes.go @@ -127,6 +127,406 @@ func Routes(d ServerData) ([]Route, http.Handler, error) { // NOTE: Route IDs are immutable and unique. DO NOT change the ID of an existing Route; otherwise, existing // configurations may break. New Route IDs can be any integer between 0 and 2147483647 (inclusive), as long as // it's unique. + /** + * 5.x API + */ + + // CDNI integration + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `OC/FCI/advertisement/?$`, Handler: cdni.GetCapabilities, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"CDNI-CAPACITY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 5413577290771}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `OC/CI/configuration/?$`, Handler: cdni.PutConfiguration, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"CDNI-CAPACITY:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 5413577290781}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `OC/CI/configuration/{host}$`, Handler: cdni.PutHostConfiguration, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"CDNI-CAPACITY:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 5413577290791}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `OC/CI/configuration/request/{id}/{approved}$`, Handler: cdni.PutConfigurationResponse, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"CDNI-ADMIN:READ", "CDNI-ADMIN:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 5413577290801}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `OC/CI/configuration/requests/?$`, Handler: cdni.GetRequests, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"CDNI-ADMIN:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 5413577290811}, + + // SSL Keys + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `sslkey_expirations/?$`, Handler: deliveryservice.GetSSlKeyExpirationInformation, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"SSL-KEY-EXPIRATION:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 413577290751}, + + // CDN lock + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `cdn_locks/?$`, Handler: cdn_lock.Read, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"CDN:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 41343905611}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `cdn_locks/?$`, Handler: cdn_lock.Create, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"CDN-LOCK:CREATE", "CDN:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 41343905621}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `cdn_locks/?$`, Handler: cdn_lock.Delete, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"CDN-LOCK:DELETE", "CDN:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 41343905641}, + + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `acme_accounts/providers?$`, Handler: acme.ReadProviders, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"ACME:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 40343905651}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `deliveryservices/sslkeys/generate/acme/?$`, Handler: deliveryservice.GenerateAcmeCertificates, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DS-SECURITY-KEY:UPDATE", "ACME:READ", "DELIVERY-SERVICE:READ", "DELIVERY-SERVICE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 25343905761}, + + // ACME account information + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `acme_accounts/?$`, Handler: acme.Read, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"ACME:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 40343905611}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `acme_accounts/?$`, Handler: acme.Create, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"ACME:CREATE", "ACME:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 40343905621}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `acme_accounts/?$`, Handler: acme.Update, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"ACME:UPDATE", "ACME:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 40343905631}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `acme_accounts/{provider}/{email}?$`, Handler: acme.Delete, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"ACME:DELETE", "ACME:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 40343905641}, + + //Delivery service ACME + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `deliveryservices/xmlId/{xmlid}/sslkeys/renew$`, Handler: deliveryservice.RenewAcmeCertificate, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: nil, Authenticated: Authenticated, Middlewares: nil, ID: 25343905731}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `acme_autorenew/?$`, Handler: deliveryservice.RenewCertificates, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"ACME:READ", "DS-SECURITY-KEY:UPDATE", "DELIVERY-SERVICE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 25343905741}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `async_status/{id}$`, Handler: api.GetAsyncStatus, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"ASYNC-STATUS:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 25343905751}, + + //ASNs + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `asns/?$`, Handler: api.UpdateHandler(&asn.TOASNV11{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"ASN:UPDATE", "ASN:READ", "CACHE-GROUP:UPDATE", "CACHE-GROUP:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 426417231731}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `asns/?$`, Handler: api.DeleteHandler(&asn.TOASNV11{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"ASN:DELETE", "ASN:READ", "CACHE-GROUP:READ", "CACHE-GROUP:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 4020489831}, + + //ASN: CRUD + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `asns/?$`, Handler: api.ReadHandler(&asn.TOASNV11{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"ASN:READ", "CACHE-GROUP:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 47387772231}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `asns/{id}$`, Handler: api.UpdateHandler(&asn.TOASNV11{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"ASN:UPDATE", "ASN:READ", "CACHE-GROUP:READ", "CACHE-GROUP:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 495119862931}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `asns/?$`, Handler: api.CreateHandler(&asn.TOASNV11{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"ASN:CREATE", "ASN:READ", "CACHE-GROUP:READ", "CACHE-GROUP:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 499949218831}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `asns/{id}$`, Handler: api.DeleteHandler(&asn.TOASNV11{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"ASN:DELETE", "ASN:READ", "CACHE-GROUP:READ", "CACHE-GROUP:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 467252476931}, + + // Traffic Stats access + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `deliveryservice_stats`, Handler: trafficstats.GetDSStats, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"STAT:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 431956902831}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `cache_stats`, Handler: trafficstats.GetCacheStats, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"CDN:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 449799790631}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `current_stats/?$`, Handler: trafficstats.GetCurrentStats, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"CDN:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 478544289331}, + + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `caches/stats/?$`, Handler: cachesstats.Get, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"CACHE-GROUP:READ", "PROFILE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 481320658831}, + + //CacheGroup: CRUD + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `cachegroups/?$`, Handler: api.ReadHandler(&cachegroup.TOCacheGroup{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"CACHE-GROUP:READ", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 42307911031}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `cachegroups/{id}$`, Handler: api.UpdateHandler(&cachegroup.TOCacheGroup{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"CACHE-GROUP:UPDATE", "CACHE-GROUP:READ", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 41295454631}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `cachegroups/?$`, Handler: api.CreateHandler(&cachegroup.TOCacheGroup{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"CACHE-GROUP:CREATE", "CACHE-GROUP:READ", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 4298266531}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `cachegroups/{id}$`, Handler: api.DeleteHandler(&cachegroup.TOCacheGroup{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"CACHE-GROUP:DELETE", "CACHE-GROUP:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 42786936531}, + + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `cachegroups/{id}/queue_update$`, Handler: cachegroup.QueueUpdates, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"CACHE-GROUP:READ", "CDN:READ", "SERVER:READ", "SERVER:QUEUE"}, Authenticated: Authenticated, Middlewares: nil, ID: 407164411031}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `cachegroups/{id}/deliveryservices/?$`, Handler: cachegroup.DSPostHandlerV40, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"CACHE-GROUP:UPDATE", "DELIVERY-SERVICE:UPDATE", "CACHE-GROUP:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 452024043131}, + + //CDN + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `cdns/name/{name}/sslkeys/?$`, Handler: cdn.GetSSLKeys, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"DS-SECURITY-KEY:READ", "CDN:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 427858177231}, + + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `cdns/capacity$`, Handler: cdn.GetCapacity, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"CDN:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 49718528131}, + + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `cdns/{name}/health/?$`, Handler: cdn.GetNameHealth, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"CDN:READ", "CACHE-GROUP:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 413534819431}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `cdns/health/?$`, Handler: cdn.GetHealth, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"CACHE-GROUP:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 408538113431}, + + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `cdns/domains/?$`, Handler: cdn.DomainsHandler, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"CDN:READ", "PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 42690256031}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `cdns/routing$`, Handler: crstats.GetCDNRouting, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"CDN:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 4672298231}, + + //CDN: CRUD + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `cdns/name/{name}$`, Handler: cdn.DeleteName, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"CDN:DELETE"}, Authenticated: Authenticated, Middlewares: nil, ID: 40880495931}, + + //CDN: queue updates + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `cdns/{id}/queue_update$`, Handler: cdn.Queue, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER:QUEUE", "CDN:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 42151598031}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `cdns/dnsseckeys/generate?$`, Handler: cdn.CreateDNSSECKeys, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"DNS-SEC:CREATE", "CDN:UPDATE", "CDN:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 47533631}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `cdns/name/{name}/dnsseckeys?$`, Handler: cdn.DeleteDNSSECKeys, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"DNS-SEC:DELETE", "CDN:UPDATE", "DELIVERY-SERVICE:UPDATE", "CDN:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 47110420731}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `cdns/name/{name}/dnsseckeys/?$`, Handler: cdn.GetDNSSECKeys, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"DNS-SEC:READ", "CDN:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 47901060931}, + + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `cdns/dnsseckeys/refresh/?$`, Handler: cdn.RefreshDNSSECKeysV4, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DNS-SEC:UPDATE", "CDN:UPDATE", "CDN:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 477199711631}, + + //CDN: Monitoring: Traffic Monitor + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `cdns/{cdn}/configs/monitoring?$`, Handler: crconfig.SnapshotGetMonitoringHandler, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"MONITOR-CONFIG:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 424084789231}, + + //Database dumps + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `dbdump/?`, Handler: dbdump.DBDump, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"DBDUMP:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 42401664731}, + + //Division: CRUD + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `divisions/?$`, Handler: api.ReadHandler(&division.TODivision{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"DIVISION:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 408518153431}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `divisions/{id}$`, Handler: api.UpdateHandler(&division.TODivision{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DIVISION:UPDATE", "DIVISION:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 40636914031}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `divisions/?$`, Handler: api.CreateHandler(&division.TODivision{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DIVISION:CREATE", "DIVISION:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 45371380031}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `divisions/{id}$`, Handler: api.DeleteHandler(&division.TODivision{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DIVISION:DELETE", "DIVISION:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 432538223731}, + + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `logs/?$`, Handler: logs.Getv40, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"LOG:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 44834055031}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `logs/newcount/?$`, Handler: logs.GetNewCount, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"LOG:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 440583301231}, + + //Content invalidation jobs + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `jobs/?$`, Handler: api.ReadHandler(&invalidationjobs.InvalidationJobV4{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: nil, Authenticated: Authenticated, Middlewares: nil, ID: 496678204131}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `jobs/?$`, Handler: invalidationjobs.DeleteV40, RequiredPrivLevel: auth.PrivLevelPortal, RequiredPermissions: nil, Authenticated: Authenticated, Middlewares: nil, ID: 41678077631}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `jobs/?$`, Handler: invalidationjobs.UpdateV40, RequiredPrivLevel: auth.PrivLevelPortal, RequiredPermissions: nil, Authenticated: Authenticated, Middlewares: nil, ID: 48613422631}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `jobs/?`, Handler: invalidationjobs.CreateV40, RequiredPrivLevel: auth.PrivLevelPortal, RequiredPermissions: nil, Authenticated: Authenticated, Middlewares: nil, ID: 4045095531}, + + //Login + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `user/login/?$`, Handler: login.LoginHandler(d.DB, d.Config), RequiredPrivLevel: auth.PrivLevelUnauthenticated, RequiredPermissions: nil, Authenticated: NoAuth, Middlewares: nil, ID: 439267082131}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `user/logout/?$`, Handler: login.LogoutHandler(d.Config.Secrets[0]), RequiredPrivLevel: auth.PrivLevelUnauthenticated, RequiredPermissions: nil, Authenticated: Authenticated, Middlewares: nil, ID: 44343482531}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `user/login/oauth/?$`, Handler: login.OauthLoginHandler(d.DB, d.Config), RequiredPrivLevel: auth.PrivLevelUnauthenticated, RequiredPermissions: nil, Authenticated: NoAuth, Middlewares: nil, ID: 441588600931}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `user/login/token/?$`, Handler: login.TokenLoginHandler(d.DB, d.Config), RequiredPrivLevel: auth.PrivLevelUnauthenticated, RequiredPermissions: nil, Authenticated: NoAuth, Middlewares: nil, ID: 40240884131}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `user/reset_password/?$`, Handler: login.ResetPassword(d.DB, d.Config), RequiredPrivLevel: auth.PrivLevelUnauthenticated, RequiredPermissions: nil, Authenticated: NoAuth, Middlewares: nil, ID: 429291463031}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `users/register/?$`, Handler: login.RegisterUser, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"USER:CREATE", "USER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 433731}, + + //ISO + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `osversions/?$`, Handler: iso.GetOSVersions, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"ISO:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 47608865731}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `isos/?$`, Handler: iso.ISOs, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"ISO:GENERATE", "ISO:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 47603365731}, + + //User: CRUD + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `users/?$`, Handler: user.Get, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"USER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 449192990031}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `users/{id}$`, Handler: user.Get, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"USER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 41380998031}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `users/{id}$`, Handler: user.Update, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"USER:UPDATE", "USER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 43543340431}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `users/?$`, Handler: user.Create, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"USER:CREATE", "USER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 47624481631}, + + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `user/current/?$`, Handler: user.Current, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: nil, Authenticated: Authenticated, Middlewares: nil, ID: 461070161431}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `user/current/?$`, Handler: user.ReplaceCurrentV4, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: nil, Authenticated: Authenticated, Middlewares: nil, ID: 42031}, + + //Parameter: CRUD + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `parameters/?$`, Handler: api.ReadHandler(¶meter.TOParameter{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 421255429231}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `parameters/{id}$`, Handler: api.UpdateHandler(¶meter.TOParameter{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PARAMETER:UPDATE", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 487393611531}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `parameters/?$`, Handler: api.CreateHandler(¶meter.TOParameter{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PARAMETER:CREATE", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 466951085931}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `parameters/{id}$`, Handler: api.DeleteHandler(¶meter.TOParameter{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PARAMETER:DELETE", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 42627711831}, + + //Phys_Location: CRUD + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `phys_locations/?$`, Handler: api.ReadHandler(&physlocation.TOPhysLocation{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"PHYSICAL-LOCATION:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 42040518231}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `phys_locations/{id}$`, Handler: api.UpdateHandler(&physlocation.TOPhysLocation{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PHYSICAL-LOCATION:UPDATE", "PHYSICAL-LOCATION:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 42279502131}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `phys_locations/?$`, Handler: api.CreateHandler(&physlocation.TOPhysLocation{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PHYSICAL-LOCATION:CREATE", "PHYSICAL-LOCATION:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 424645664831}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `phys_locations/{id}$`, Handler: api.DeleteHandler(&physlocation.TOPhysLocation{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PHYSICAL-LOCATION:DELETE", "PHYSICAL-LOCATION:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 4561422131}, + + //Ping + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `ping$`, Handler: ping.Handler, RequiredPrivLevel: auth.PrivLevelUnauthenticated, RequiredPermissions: nil, Authenticated: NoAuth, Middlewares: nil, ID: 455566159731}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `vault/ping/?$`, Handler: ping.Vault, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"TRAFFIC-VAULT:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 488401211431}, + + //Profile: CRUD + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `profiles/?$`, Handler: api.ReadHandler(&profile.TOProfile{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"PROFILE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 46875858931}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `profiles/{id}$`, Handler: api.UpdateHandler(&profile.TOProfile{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:UPDATE", "PROFILE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 4843917231}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `profiles/?$`, Handler: api.CreateHandler(&profile.TOProfile{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:CREATE", "PROFILE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 454021155631}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `profiles/{id}$`, Handler: api.DeleteHandler(&profile.TOProfile{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:DELETE", "PROFILE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 420559446531}, + + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `profiles/{id}/export/?$`, Handler: profile.ExportProfileHandler, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 4013351731}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `profiles/import/?$`, Handler: profile.ImportProfileHandler, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:CREATE", "PARAMETER:CREATE", "PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 40614320831}, + + // Copy Profile + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `profiles/name/{new_profile}/copy/{existing_profile}`, Handler: profile.CopyProfileHandler, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:CREATE", "PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 40614320931}, + + //Region: CRUDs + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `regions/?$`, Handler: api.ReadHandler(®ion.TORegion{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"REGION:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 41003708531}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `regions/{id}$`, Handler: api.UpdateHandler(®ion.TORegion{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"REGION:UPDATE", "REGION:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 42230822431}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `regions/?$`, Handler: api.CreateHandler(®ion.TORegion{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"REGION:CREATE", "REGION:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 428833448831}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `regions/?$`, Handler: api.DeleteHandler(®ion.TORegion{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"REGION:DELETE"}, Authenticated: Authenticated, Middlewares: nil, ID: 423262675831}, + + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `topologies/?$`, Handler: api.CreateHandler(&topology.TOTopology{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"TOPOLOGY:CREATE", "TOPOLOGY:READ", "CACHE-GROUP:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 48714522211}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `topologies/?$`, Handler: api.ReadHandler(&topology.TOTopology{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"TOPOLOGY:READ", "CACHE-GROUP:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 48714522221}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `topologies/?$`, Handler: api.UpdateHandler(&topology.TOTopology{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"TOPOLOGY:UPDATE", "TOPOLOGY:READ", "CACHE-GROUP:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 48714522231}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `topologies/?$`, Handler: api.DeleteHandler(&topology.TOTopology{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"TOPOLOGY:DELETE", "TOPOLOGY:READ", "CACHE-GROUP:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 48714522241}, + + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `topologies/{name}/queue_update$`, Handler: topology.QueueUpdateHandler, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER:QUEUE", "TOPOLOGY:READ", "SERVER:READ", "CACHE-GROUP:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 42053517481}, + + // get all edge servers associated with a delivery service (from deliveryservice_server table) + + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `deliveryserviceserver/?$`, Handler: dsserver.ReadDSSHandler, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"SERVER:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 494614503331}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `deliveryserviceserver$`, Handler: dsserver.GetReplaceHandler, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DELIVERY-SERVICE:READ", "SERVER:READ", "SERVER:UPDATE", "DELIVERY-SERVICE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 42979978831}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `deliveryserviceserver/{dsid}/{serverid}`, Handler: dsserver.Delete, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DELIVERY-SERVICE:READ", "DELIVERY-SERVICE:UPDATE", "SERVER:READ", "SERVER:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 453218452331}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `deliveryservices/{xml_id}/servers$`, Handler: dsserver.GetCreateHandler, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DELIVERY-SERVICE:UPDATE", "SERVER:UPDATE", "DELIVERY-SERVICE:READ", "SERVER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 442818120631}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `servers/{id}/deliveryservices$`, Handler: api.ReadHandler(&dsserver.TODSSDeliveryService{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"DELIVERY-SERVICE:READ", "SERVER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 43311541131}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `servers/{id}/deliveryservices$`, Handler: server.AssignDeliveryServicesToServerHandler, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DELIVERY-SERVICE:READ", "SERVER:READ", "DELIVERY-SERVICE:UPDATE", "SERVER:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 48012825331}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `deliveryservices/{id}/servers$`, Handler: dsserver.GetReadAssigned, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"CACHE-GROUP:READ", "CDN:READ", "TYPE:READ", "PROFILE:READ", "DELIVERY-SERVICE:READ", "SERVER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 434512122331}, + + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `deliveryservices/{id}/capacity/?$`, Handler: deliveryservice.GetCapacity, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 423140911031}, + //Serverchecks + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `servercheck/?$`, Handler: servercheck.ReadServerCheck, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"SERVER-CHECK:READ", "SERVER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 479611292231}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `servercheck/?$`, Handler: servercheck.CreateUpdateServercheck, RequiredPrivLevel: auth.PrivLevelInvalid, RequiredPermissions: []string{"SERVER-CHECK:CREATE", "SERVER-CHECK:READ", "SERVER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 476428156831}, + + // Servercheck Extensions + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `servercheck/extensions$`, Handler: extensions.Create, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"SERVER-CHECK:CREATE", "SERVER-CHECK:READ", "SERVER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 48049859931}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `servercheck/extensions$`, Handler: extensions.Get, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"SERVER-CHECK:READ", "SERVER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 48349859931}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `servercheck/extensions/{id}$`, Handler: extensions.Delete, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"SERVER-CHECK:DELETE", "SERVER-CHECK:READ", "SERVER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 48049829931}, + + //Server status + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `servers/{id}/status$`, Handler: server.UpdateStatusHandler, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER:UPDATE", "SERVER:READ", "STATUS:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 47666385131}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `servers/{id}/queue_update$`, Handler: server.QueueUpdateHandler, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER:QUEUE", "SERVER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 418947131}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `servers/{host_name}/update_status$`, Handler: server.GetServerUpdateStatusHandler, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"SERVER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 43845159931}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `servers/{id-or-name}/update$`, Handler: server.UpdateHandlerV4, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER:UPDATE", "SERVER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 4438132331}, + + //Server: CRUD + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `servers/?$`, Handler: server.Read, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"SERVER:READ", "DELIVERY-SERVICE:READ", "CDN:READ", "PHYSICAL-LOCATION:READ", "CACHE-GROUP:READ", "TYPE:READ", "PROFILE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 472095928531}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `servers/{id}$`, Handler: server.Update, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER:UPDATE", "SERVER:READ", "DELIVERY-SERVICE:READ", "CDN:READ", "PHYSICAL-LOCATION:READ", "CACHE-GROUP:READ", "TYPE:READ", "PROFILE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 45863410331}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `servers/?$`, Handler: server.Create, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER:CREATE", "SERVER:READ", "DELIVERY-SERVICE:READ", "CDN:READ", "PHYSICAL-LOCATION:READ", "CACHE-GROUP:READ", "TYPE:READ", "PROFILE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 422555806131}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `servers/{id}$`, Handler: server.Delete, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER:DELETE", "SERVER:READ", "DELIVERY-SERVICE:READ", "CDN:READ", "PHYSICAL-LOCATION:READ", "CACHE-GROUP:READ", "TYPE:READ", "PROFILE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 49232223331}, + + //Server Capability + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `server_capabilities$`, Handler: api.ReadHandler(&servercapability.TOServerCapability{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"SERVER-CAPABILITY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 41040739131}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `server_capabilities$`, Handler: api.CreateHandler(&servercapability.TOServerCapability{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER-CAPABILITY:CREATE", "SERVER-CAPABILITY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 407447070831}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `server_capabilities$`, Handler: api.UpdateHandler(&servercapability.TOServerCapability{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER-CAPABILITY:UPDATE", "SERVER-CAPABILITY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 425437701091}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `server_capabilities$`, Handler: api.DeleteHandler(&servercapability.TOServerCapability{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER-CAPABILITY:DELETE", "SERVER-CAPABILITY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 43641503831}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `multiple_server_capabilities/?$`, Handler: server.AssignMultipleServerCapabilities, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER:UPDATE", "SERVER:READ", "SERVER-CAPABILITY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 407924192581}, + + //Server Server Capabilities: CRUD + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `server_server_capabilities/?$`, Handler: api.ReadHandler(&server.TOServerServerCapability{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"SERVER:READ", "SERVER-CAPABILITY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 480023188931}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `server_server_capabilities/?$`, Handler: api.CreateHandler(&server.TOServerServerCapability{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER:UPDATE", "SERVER:READ", "SERVER-CAPABILITY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 429316683431}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `server_server_capabilities/?$`, Handler: api.DeleteHandler(&server.TOServerServerCapability{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER:UPDATE", "SERVER:READ", "SERVER-CAPABILITY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 405871405831}, + + //Status: CRUD + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `statuses/?$`, Handler: api.ReadHandler(&status.TOStatus{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"STATUS:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 424490565631}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `statuses/{id}$`, Handler: api.UpdateHandler(&status.TOStatus{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"STATUS:UPDATE", "STATUS:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 420796650431}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `statuses/?$`, Handler: api.CreateHandler(&status.TOStatus{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"STATUS:CREATE", "STATUS:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 436912361231}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `statuses/{id}$`, Handler: api.DeleteHandler(&status.TOStatus{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"STATUS:DELETE", "STATUS:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 45511136031}, + + //System + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `system/info/?$`, Handler: systeminfo.Get, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: nil, Authenticated: Authenticated, Middlewares: nil, ID: 42104747531}, + + //Type: CRUD + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `types/?$`, Handler: api.ReadHandler(&types.TOType{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 422670182331}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `types/{id}$`, Handler: api.UpdateHandler(&types.TOType{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"TYPE:UPDATE", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 4886011531}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `types/?$`, Handler: api.CreateHandler(&types.TOType{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"TYPE:CREATE", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 451330819531}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `types/{id}$`, Handler: api.DeleteHandler(&types.TOType{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"TYPE:DELETE", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 4317577331}, + + //About + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `about/?$`, Handler: about.Handler(), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: nil, Authenticated: Authenticated, Middlewares: nil, ID: 431750116631}, + + //Coordinates + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `coordinates/?$`, Handler: api.ReadHandler(&coordinate.TOCoordinate{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"COORDINATE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 49670074531}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `coordinates/?$`, Handler: api.UpdateHandler(&coordinate.TOCoordinate{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"COORDINATE:UPDATE", "COORDINATE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 46892617431}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `coordinates/?$`, Handler: api.CreateHandler(&coordinate.TOCoordinate{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"COORDINATE:CREATE", "COORDINATE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 442811215731}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `coordinates/?$`, Handler: api.DeleteHandler(&coordinate.TOCoordinate{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"COORDINATE:DELETE", "COORDINATE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 430384988931}, + + //CDN notification + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `cdn_notifications/?$`, Handler: cdnnotification.Read, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"CDN:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 22212245141}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `cdn_notifications/?$`, Handler: cdnnotification.Create, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"CDN:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 27652235131}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `cdn_notifications/?$`, Handler: cdnnotification.Delete, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"CDN:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 27224118511}, + + //CDN generic handlers: + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `cdns/?$`, Handler: api.ReadHandler(&cdn.TOCDN{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"CDN:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 423031862131}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `cdns/{id}$`, Handler: api.UpdateHandler(&cdn.TOCDN{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"CDN:UPDATE", "CDN:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 431117893431}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `cdns/?$`, Handler: api.CreateHandler(&cdn.TOCDN{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"CDN:READ", "CDN:CREATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 416050528931}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `cdns/{id}$`, Handler: api.DeleteHandler(&cdn.TOCDN{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"CDN:DELETE", "CDN:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 42769465731}, + + //Delivery service requests + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `deliveryservice_requests/?$`, Handler: dsrequest.Get, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"DS-REQUEST:READ", "DELIVERY-SERVICE:READ", "USER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 468116393531}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `deliveryservice_requests/?$`, Handler: dsrequest.Put, RequiredPrivLevel: auth.PrivLevelPortal, RequiredPermissions: []string{"DS-REQUEST:UPDATE", "DELIVERY-SERVICE:READ", "USER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 424990791831}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `deliveryservice_requests/?$`, Handler: dsrequest.Post, RequiredPrivLevel: auth.PrivLevelPortal, RequiredPermissions: []string{"DS-REQUEST:CREATE", "DELIVERY-SERVICE:READ", "USER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 4938503931}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `deliveryservice_requests/?$`, Handler: dsrequest.Delete, RequiredPrivLevel: auth.PrivLevelPortal, RequiredPermissions: []string{"DS-REQUEST:DELETE", "DELIVERY-SERVICE:READ", "USER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 429698502531}, + + //Delivery service request: Actions + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `deliveryservice_requests/{id}/assign$`, Handler: dsrequest.GetAssignment, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DS-REQUEST:READ", "USER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 470316029041}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `deliveryservice_requests/{id}/assign$`, Handler: dsrequest.PutAssignment, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DS-REQUEST:UPDATE", "DS-REQUEST:READ", "USER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 470316029031}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `deliveryservice_requests/{id}/status$`, Handler: dsrequest.GetStatus, RequiredPrivLevel: auth.PrivLevelPortal, RequiredPermissions: []string{"DS-REQUEST:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 46841509941}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `deliveryservice_requests/{id}/status$`, Handler: dsrequest.PutStatus, RequiredPrivLevel: auth.PrivLevelPortal, RequiredPermissions: []string{"DS-REQUEST:UPDATE", "DS-REQUEST:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 46841509931}, + + //Delivery service request comment: CRUD + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `deliveryservice_request_comments/?$`, Handler: api.ReadHandler(&comment.TODeliveryServiceRequestComment{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"DS-REQUEST:READ", "DELIVERY-SERVICE:READ", "USER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 403265073731}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `deliveryservice_request_comments/?$`, Handler: api.UpdateHandler(&comment.TODeliveryServiceRequestComment{}), RequiredPrivLevel: auth.PrivLevelPortal, RequiredPermissions: []string{"DS-REQUEST:UPDATE", "DELIVERY-SERVICE:READ", "USER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 46048784731}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `deliveryservice_request_comments/?$`, Handler: api.CreateHandler(&comment.TODeliveryServiceRequestComment{}), RequiredPrivLevel: auth.PrivLevelPortal, RequiredPermissions: []string{"DS-REQUEST:UPDATE", "DELIVERY-SERVICE:READ", "USER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 42722767231}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `deliveryservice_request_comments/?$`, Handler: api.DeleteHandler(&comment.TODeliveryServiceRequestComment{}), RequiredPrivLevel: auth.PrivLevelPortal, RequiredPermissions: []string{"DS-REQUEST:UPDATE", "DELIVERY-SERVICE:READ", "USER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 49950466831}, + + //Delivery service uri signing keys: CRUD + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `deliveryservices/{xmlID}/urisignkeys$`, Handler: urisigning.GetURIsignkeysHandler, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"DS-SECURITY-KEY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 429307855831}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `deliveryservices/{xmlID}/urisignkeys$`, Handler: urisigning.SaveDeliveryServiceURIKeysHandler, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"DS-SECURITY-KEY:CREATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 40846633531}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `deliveryservices/{xmlID}/urisignkeys$`, Handler: urisigning.SaveDeliveryServiceURIKeysHandler, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"DS-SECURITY-KEY:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 4764896931}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `deliveryservices/{xmlID}/urisignkeys$`, Handler: urisigning.RemoveDeliveryServiceURIKeysHandler, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"DS-SECURITY-KEY:DELETE", "DS-SECURITY-KEY:READ", "DELIVERY-SERVICE:READ", "DELIVERY-SERVICE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 42992541731}, + + //Delivery Service Required Capabilities: CRUD + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `deliveryservices_required_capabilities/?$`, Handler: api.ReadHandler(&deliveryservice.RequiredCapability{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 415852222731}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `deliveryservices_required_capabilities/?$`, Handler: api.CreateHandler(&deliveryservice.RequiredCapability{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DELIVERY-SERVICE:READ", "DELIVERY-SERVICE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 409687399231}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `deliveryservices_required_capabilities/?$`, Handler: api.DeleteHandler(&deliveryservice.RequiredCapability{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DELIVERY-SERVICE:READ", "DELIVERY-SERVICE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 449628930431}, + + // Federations by CDN (the actual table for federation) + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `cdns/{name}/federations/?$`, Handler: api.ReadHandler(&cdnfederation.TOCDNFederation{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"CDN:READ", "FEDERATION:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 48922503231}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `cdns/{name}/federations/?$`, Handler: api.CreateHandler(&cdnfederation.TOCDNFederation{}), RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"FEDERATION:CREATE", "FEDERATION:READ, CDN:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 495489421931}, + {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.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}, + + //Origins + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `origins/?$`, Handler: api.ReadHandler(&origin.TOOrigin{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"ORIGIN:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 44464925631}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `origins/?$`, Handler: api.UpdateHandler(&origin.TOOrigin{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"ORIGIN:UPDATE", "ORIGIN:READ", "DELIVERY-SERVICE:READ", "DELIVERY-SERVICE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 4156774631}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `origins/?$`, Handler: api.CreateHandler(&origin.TOOrigin{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"ORIGIN:CREATE", "ORIGIN:READ", "DELIVERY-SERVICE:READ", "DELIVERY-SERVICE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 409956164331}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `origins/?$`, Handler: api.DeleteHandler(&origin.TOOrigin{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"ORIGIN:DELETE", "DELIVERY-SERVICE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 46027326331}, + + //Roles + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `roles/?$`, Handler: role.Get, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"ROLE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 48708858331}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `roles/?$`, Handler: role.Update, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"ROLE:UPDATE", "ROLE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 461289748931}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `roles/?$`, Handler: role.Create, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"ROLE:CREATE", "ROLE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 43065240631}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `roles/?$`, Handler: role.Delete, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"ROLE:DELETE", "ROLE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 435670598231}, + + //Delivery Services Regexes + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `deliveryservices_regexes/?$`, Handler: deliveryservicesregexes.Get, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 40550145331}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `deliveryservices/{dsid}/regexes/?$`, Handler: deliveryservicesregexes.DSGet, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"DELIVERY-SERVICE:READ", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 47743276331}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `deliveryservices/{dsid}/regexes/?$`, Handler: deliveryservicesregexes.Post, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DELIVERY-SERVICE:UPDATE", "DELIVERY-SERVICE:READ", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 41273780031}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `deliveryservices/{dsid}/regexes/{regexid}?$`, Handler: deliveryservicesregexes.Put, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DELIVERY-SERVICE:UPDATE", "DELIVERY-SERVICE:READ", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 424833969131}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `deliveryservices/{dsid}/regexes/{regexid}?$`, Handler: deliveryservicesregexes.Delete, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DELIVERY-SERVICE:UPDATE", "DELIVERY-SERVICE:READ", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 424673166331}, + + //ServiceCategories + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `service_categories/?$`, Handler: api.ReadHandler(&servicecategory.TOServiceCategory{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"SERVICE-CATEGORY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 40851815431}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `service_categories/{name}/?$`, Handler: servicecategory.Update, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVICE-CATEGORY:UPDATE", "SERVICE-CATEGORY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 4063691411}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `service_categories/?$`, Handler: api.CreateHandler(&servicecategory.TOServiceCategory{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVICE-CATEGORY:CREATE", "SERVICE-CATEGORY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 4537138011}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `service_categories/{name}$`, Handler: api.DeleteHandler(&servicecategory.TOServiceCategory{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVICE-CATEGORY:DELETE", "SERVICE-CATEGORY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 43253822381}, + + //StaticDNSEntries + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `staticdnsentries/?$`, Handler: api.ReadHandler(&staticdnsentry.TOStaticDNSEntry{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"STATIC-DN:READ", "CACHE-GROUP:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 42893947731}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `staticdnsentries/?$`, Handler: api.UpdateHandler(&staticdnsentry.TOStaticDNSEntry{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"STATIC-DN:UPDATE", "STATIC-DN:READ", "CACHE-GROUP:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 44245711131}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `staticdnsentries/?$`, Handler: api.CreateHandler(&staticdnsentry.TOStaticDNSEntry{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"STATIC-DN:CREATE", "STATIC-DN:READ", "CACHE-GROUP:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 462914823831}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `staticdnsentries/?$`, Handler: api.DeleteHandler(&staticdnsentry.TOStaticDNSEntry{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"STATIC-DN:DELETE", "STATIC-DN:READ", "DELIVERY-SERVICE:READ", "CACHE-GROUP:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 484603113231}, + + //ProfileParameters + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `profiles/{id}/parameters/?$`, Handler: profileparameter.GetProfileID, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 47646497531}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `profiles/name/{name}/parameters/?$`, Handler: profileparameter.GetProfileName, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 426773783231}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `profiles/name/{name}/parameters/?$`, Handler: profileparameter.PostProfileParamsByName, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:UPDATE", "PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 435594558231}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `profiles/{id}/parameters/?$`, Handler: profileparameter.PostProfileParamsByID, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:UPDATE", "PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 41681870831}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `profileparameters/?$`, Handler: api.ReadHandler(&profileparameter.TOProfileParameter{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 45060980531}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `profileparameters/?$`, Handler: api.CreateHandler(&profileparameter.TOProfileParameter{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:READ", "PARAMETER:READ", "PROFILE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 42880969331}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `profileparameter/?$`, Handler: profileparameter.PostProfileParam, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:READ", "PARAMETER:READ", "PROFILE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 42427531}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `parameterprofile/?$`, Handler: profileparameter.PostParamProfile, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:UPDATE", "PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 408061086131}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `profileparameters/{profileId}/{parameterId}$`, Handler: api.DeleteHandler(&profileparameter.TOProfileParameter{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:UPDATE", "PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 42483952931}, + + //Tenants + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `tenants/?$`, Handler: api.ReadHandler(&apitenant.TOTenant{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"TENANT:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 467796781431}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `tenants/{id}$`, Handler: api.UpdateHandler(&apitenant.TOTenant{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"TENANT:UPDATE", "TENANT:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 409413147831}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `tenants/?$`, Handler: api.CreateHandler(&apitenant.TOTenant{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"TENANT:CREATE", "TENANT:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 41724801331}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `tenants/{id}$`, Handler: api.DeleteHandler(&apitenant.TOTenant{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"TENANT:DELETE", "TENANT:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 41636555831}, + + //CRConfig + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `cdns/{cdn}/snapshot/?$`, Handler: crconfig.SnapshotGetHandler, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"CDN-SNAPSHOT:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 495727369531}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `cdns/{cdn}/snapshot/new/?$`, Handler: crconfig.Handler, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"CDN-SNAPSHOT:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 47671688931}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `snapshot/?$`, Handler: crconfig.SnapshotHandler, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"CDN-SNAPSHOT:CREATE", "CDN-SNAPSHOT:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 496991182931}, + + // Federations + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `federations/all/?$`, Handler: federations.GetAll, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"FEDERATION-RESOLVER:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 4105998631}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `federations/?$`, Handler: federations.Get, RequiredPrivLevel: auth.PrivLevelFederation, RequiredPermissions: []string{"FEDERATION-RESOLVER:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 45495499431}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `federations/?$`, Handler: federations.AddFederationResolverMappingsForCurrentUser, RequiredPrivLevel: auth.PrivLevelFederation, RequiredPermissions: []string{"FEDERATION-RESOLVER:CREATE", "FEDERATION-RESOLVER:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 489406474231}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `federations/?$`, Handler: federations.RemoveFederationResolverMappingsForCurrentUser, RequiredPrivLevel: auth.PrivLevelFederation, RequiredPermissions: []string{"FEDERATION-RESOLVER:DELETE"}, Authenticated: Authenticated, Middlewares: nil, ID: 4209832331}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `federations/?$`, Handler: federations.ReplaceFederationResolverMappingsForCurrentUser, RequiredPrivLevel: auth.PrivLevelFederation, RequiredPermissions: []string{"FEDERATION-RESOLVER:DELETE", "FEDERATION-RESOLVER:CREATE", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 428318251631}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `federations/{id}/deliveryservices/?$`, Handler: federations.PostDSes, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"FEDERATION:UPDATE", "DELIVERY-SERVICE:UPDATE", "FEDERATION:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 468286351331}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `federations/{id}/deliveryservices/?$`, Handler: api.ReadHandler(&federations.TOFedDSes{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"FEDERATION:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 45377303431}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `federations/{id}/deliveryservices/{dsID}/?$`, Handler: api.DeleteHandler(&federations.TOFedDSes{}), RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"FEDERATION:UPDATE", "DELIVERY-SERVICE:UPDATE", "FEDERATION:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 441740257031}, + + // Federation Resolvers + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `federation_resolvers/?$`, Handler: federation_resolvers.Create, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"FEDERATION-RESOLVER:CREATE", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 413437366131}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `federation_resolvers/?$`, Handler: federation_resolvers.Read, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"FEDERATION-RESOLVER:READ", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 45660875931}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `federations/{id}/federation_resolvers/?$`, Handler: federations.AssignFederationResolversToFederationHandler, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"FEDERATION:UPDATE", "FEDERATION:READ", "FEDERATION-RESOLVER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 45660876031}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `federations/{id}/federation_resolvers/?$`, Handler: federations.GetFederationFederationResolversHandler, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"FEDERATION:READ", "FEDERATION-RESOLVER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 45660876131}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `federation_resolvers/?$`, Handler: federation_resolvers.Delete, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"FEDERATION-RESOLVER:DELETE", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 400131}, + + // Federations Users + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `federations/{id}/users/?$`, Handler: federations.PostUsers, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"FEDERATION:UPDATE", "USER:READ", "FEDERATION:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 477933493031}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `federations/{id}/users/?$`, Handler: api.ReadHandler(&federations.TOUsers{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"FEDERATION:READ", "USER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 49407501531}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `federations/{id}/users/{userID}/?$`, Handler: api.DeleteHandler(&federations.TOUsers{}), RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"FEDERATION:UPDATE", "FEDERATION:READ", "USER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 494910288231}, + + ////DeliveryServices + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `deliveryservices/?$`, Handler: api.ReadHandler(&deliveryservice.TODeliveryService{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"DELIVERY-SERVICE:READ", "CDN:READ", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 423831729431}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `deliveryservices/?$`, Handler: deliveryservice.CreateV40, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DELIVERY-SERVICE:CREATE", "DELIVERY-SERVICE:READ", "CDN:READ", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 40643153231}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `deliveryservices/{id}/?$`, Handler: deliveryservice.UpdateV40, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DELIVERY-SERVICE:UPDATE", "DELIVERY-SERVICE:READ", "CDN:READ", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 476656756731}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `deliveryservices/{id}/safe/?$`, Handler: deliveryservice.UpdateSafe, RequiredPrivLevel: auth.PrivLevelUnauthenticated, RequiredPermissions: []string{"DELIVERY-SERVICE-SAFE:UPDATE", "DELIVERY-SERVICE:READ", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 44721093131}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `deliveryservices/{id}/?$`, Handler: api.DeleteHandler(&deliveryservice.TODeliveryService{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DELIVERY-SERVICE:DELETE", "DELIVERY-SERVICE:READ", "CDN:READ", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 42264207431}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `deliveryservices/{id}/servers/eligible/?$`, Handler: deliveryservice.GetServersEligible, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"DELIVERY-SERVICE:READ", "SERVER:READ", "CACHE-GROUP:READ", "TYPE:READ", "CDN:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 47476158431}, + + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `deliveryservices/xmlId/{xmlid}/sslkeys$`, Handler: deliveryservice.GetSSLKeysByXMLID, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"DS-SECURITY-KEY:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 413577290731}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `deliveryservices/sslkeys/add$`, Handler: deliveryservice.AddSSLKeys, RequiredPrivLevel: auth.PrivLevelAdmin, RequiredPermissions: []string{"DS-SECURITY-KEY:CREATE", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 487287858331}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `deliveryservices/xmlId/{xmlid}/sslkeys$`, Handler: deliveryservice.DeleteSSLKeys, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DS-SECURITY-KEY:DELETE", "DELIVERY-SERVICE:READ", "DS-SECURITY-KEY:READ", "DELIVERY-SERVICE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 492673431}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `deliveryservices/sslkeys/generate/?$`, Handler: deliveryservice.GenerateSSLKeys, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DS-SECURITY-KEY:CREATE", "DELIVERY-SERVICE:READ", "DELIVERY-SERVICE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 45343905131}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `deliveryservices/xmlId/{name}/urlkeys/copyFromXmlId/{copy-name}/?$`, Handler: deliveryservice.CopyURLKeys, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DS-SECURITY-KEY:READ", "DS-SECURITY-KEY:CREATE", "DELIVERY-SERVICE:READ", "DELIVERY-SERVICE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 426250107631}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `deliveryservices/xmlId/{name}/urlkeys/generate/?$`, Handler: deliveryservice.GenerateURLKeys, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DS-SECURITY-KEY:CREATE", "DELIVERY-SERVICE:READ", "DELIVERY-SERVICE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 453048282431}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `deliveryservices/xmlId/{name}/urlkeys/?$`, Handler: deliveryservice.GetURLKeysByName, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"DS-SECURITY-KEY:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 420271921131}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `deliveryservices/xmlId/{name}/urlkeys/?$`, Handler: deliveryservice.DeleteURLKeysByName, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DS-SECURITY-KEY:DELETE", "DS-SECURITY-KEY:READ", "DELIVERY-SERVICE:READ", "DELIVERY-SERVICE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 420271921141}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `deliveryservices/{id}/urlkeys/?$`, Handler: deliveryservice.GetURLKeysByID, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"DS-SECURITY-KEY:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 49319711431}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `deliveryservices/{id}/urlkeys/?$`, Handler: deliveryservice.DeleteURLKeysByID, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DS-SECURITY-KEY:DELETE", "DELIVERY-SERVICE:READ", "DELIVERY-SERVICE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 49319711441}, + + //Delivery service LetsEncrypt + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `deliveryservices/sslkeys/generate/letsencrypt/?$`, Handler: deliveryservice.GenerateLetsEncryptCertificates, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DS-SECURITY-KEY:CREATE", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 45343905231}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `letsencrypt/dnsrecords/?$`, Handler: deliveryservice.GetDnsChallengeRecords, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DS-SECURITY-KEY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 45343905531}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `letsencrypt/autorenew/?$`, Handler: deliveryservice.RenewCertificatesDeprecated, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"DS-SECURITY-KEY:CREATE", "DELIVERY-SERVICE:READ", "DELIVERY-SERVICE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 45343905631}, + + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `deliveryservices/{id}/health/?$`, Handler: deliveryservice.GetHealth, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"DELIVERY-SERVICE:READ", "CACHE-GROUP:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 423459010131}, + + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `deliveryservices/{id}/routing$`, Handler: crstats.GetDSRouting, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 4673398331}, + + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `steering/{deliveryservice}/targets/?$`, Handler: api.ReadHandler(&steeringtargets.TOSteeringTargetV11{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"STEERING:READ", "DELIVERY-SERVICE:READ", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 456960782431}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `steering/{deliveryservice}/targets/?$`, Handler: api.CreateHandler(&steeringtargets.TOSteeringTargetV11{}), RequiredPrivLevel: auth.PrivLevelSteering, RequiredPermissions: []string{"STEERING:CREATE", "STEERING:READ", "DELIVERY-SERVICE:READ", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 433821639731}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPut, Path: `steering/{deliveryservice}/targets/{target}/?$`, Handler: api.UpdateHandler(&steeringtargets.TOSteeringTargetV11{}), RequiredPrivLevel: auth.PrivLevelSteering, RequiredPermissions: []string{"STEERING:UPDATE", "STEERING:READ", "DELIVERY-SERVICE:READ", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 443860829531}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `steering/{deliveryservice}/targets/{target}/?$`, Handler: api.DeleteHandler(&steeringtargets.TOSteeringTargetV11{}), RequiredPrivLevel: auth.PrivLevelSteering, RequiredPermissions: []string{"STEERING:DELETE", "STEERING:READ", "DELIVERY-SERVICE:READ", "TYPE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 428802151531}, + + // Stats Summary + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `stats_summary/?$`, Handler: trafficstats.GetStatsSummary, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"STAT:READ", "CDN:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 48049859831}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `stats_summary/?$`, Handler: trafficstats.CreateStatsSummary, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"STAT:CREATE", "STAT:READ", "CDN:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 48049159831}, + + //Pattern based consistent hashing endpoint + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `consistenthash/?$`, Handler: consistenthash.Post, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"CDN:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 46075507631}, + + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `steering/?$`, Handler: steering.Get, RequiredPrivLevel: auth.PrivLevelSteering, RequiredPermissions: []string{"STEERING:READ", "DELIVERY-SERVICE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 417485245731}, + + // Plugins + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `plugins/?$`, Handler: plugins.Get(d.Plugins), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"PLUGIN:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 48349853931}, + /** * 4.x API */ diff --git a/traffic_ops/v4-client/README.md b/traffic_ops/v4-client/README.md index d645023ca5..0f6b5989cf 100644 --- a/traffic_ops/v4-client/README.md +++ b/traffic_ops/v4-client/README.md @@ -1,10 +1,5 @@ # Traffic Ops Go Client -## Unstable -The version of the Traffic Ops API for which this client was made is -*unstable*, meaning that breaking changes to it - and to this client - can -occur at any time. Use at your own peril! - ## Getting Started 1. Obtain the latest version of the library diff --git a/traffic_ops/v5-client/README.md b/traffic_ops/v5-client/README.md new file mode 100644 index 0000000000..790ab40bc7 --- /dev/null +++ b/traffic_ops/v5-client/README.md @@ -0,0 +1,53 @@ +# Traffic Ops Go Client + +## Getting Started +1. Obtain the latest version of the library + +`go get github.com/apache/trafficcontrol/traffic_ops/v5-client` + +2. Get a basic TO session started and fetch a list of CDNs +```go +package main + +import ( + "fmt" + "os" + "time" + + "github.com/apache/trafficcontrol/lib/go-tc" + toclient "github.com/apache/trafficcontrol/traffic_ops/v5-client" +) + +const TOURL = "http://localhost" +const TOUser = "user" +const TOPassword = "password" +const AllowInsecureConnections = true +const UserAgent = "MySampleApp" +const UseClientCache = false +const TrafficOpsRequestTimeout = time.Second * time.Duration(10) + +func main() { + session, remoteaddr, err := toclient.LoginWithAgent( + TOURL, + TOUser, + TOPassword, + AllowInsecureConnections, + UserAgent, + UseClientCache, + TrafficOpsRequestTimeout) + if err != nil { + fmt.Printf("An error occurred while logging in:\n\t%v\n", err) + os.Exit(1) + } + fmt.Println("Connected to: " + remoteaddr.String()) + var cdns []tc.CDN + cdns, _, err = session.GetCDNs(nil) + if err != nil { + fmt.Printf("An error occurred while getting cdns:\n\t%v\n", err) + os.Exit(1) + } + for _, cdn := range cdns { + fmt.Println(cdn.Name) + } +} +``` diff --git a/traffic_ops/v5-client/about.go b/traffic_ops/v5-client/about.go new file mode 100644 index 0000000000..e7614e927e --- /dev/null +++ b/traffic_ops/v5-client/about.go @@ -0,0 +1,32 @@ +// Package client implements methods for interacting with the Traffic Ops API. +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiAbout is the API version-relative path for the /about API endpoint. +const apiAbout = "/about" + +// GetAbout gets data about the TO instance. +func (to *Session) GetAbout(opts RequestOptions) (map[string]string, toclientlib.ReqInf, error) { + route := apiAbout + var data map[string]string + reqInf, err := to.get(route, opts, &data) + return data, reqInf, err +} diff --git a/traffic_ops/v5-client/acme.go b/traffic_ops/v5-client/acme.go new file mode 100644 index 0000000000..d2498a40d4 --- /dev/null +++ b/traffic_ops/v5-client/acme.go @@ -0,0 +1,31 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiAcmeAutorenew is the API version-relative path for the /acme_autorenew API endpoint. +const apiAcmeAutorenew = "/acme_autorenew" + +// AcmeAutorenew renews all ACME certificates asynchronously. +func (to *Session) AutoRenew(opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(apiAcmeAutorenew, opts, nil, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/asn.go b/traffic_ops/v5-client/asn.go new file mode 100644 index 0000000000..ba7d221625 --- /dev/null +++ b/traffic_ops/v5-client/asn.go @@ -0,0 +1,64 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/url" + "strconv" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiASNs is the API version-relative path for the /asns API endpoint. +const apiASNs = "/asns" + +// CreateASN creates the passed ASN. +func (to *Session) CreateASN(asn tc.ASN, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(apiASNs, opts, asn, &alerts) + return alerts, reqInf, err +} + +// UpdateASN updates the ASN identified by id by replacing it with the passed +// ASN. +func (to *Session) UpdateASN(id int, entity tc.ASN, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("id", strconv.Itoa(id)) + var alerts tc.Alerts + reqInf, err := to.put(apiASNs, opts, entity, &alerts) + return alerts, reqInf, err +} + +// GetASNs retrieves ASNs from Traffic Ops. +func (to *Session) GetASNs(opts RequestOptions) (tc.ASNsResponse, toclientlib.ReqInf, error) { + var data tc.ASNsResponse + reqInf, err := to.get(apiASNs, opts, &data) + return data, reqInf, err +} + +// DeleteASN deletes the ASN with the given ID. +func (to *Session) DeleteASN(id int, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("id", strconv.Itoa(id)) + var alerts tc.Alerts + reqInf, err := to.del(apiASNs, opts, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/async_status.go b/traffic_ops/v5-client/async_status.go new file mode 100644 index 0000000000..0fb4f624e7 --- /dev/null +++ b/traffic_ops/v5-client/async_status.go @@ -0,0 +1,34 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "fmt" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiAsyncStatus is the API version-relative path for the /async_status/{id} API endpoint. +const apiAsyncStatus = "/async_status/%d" + +// GetAsyncStatus gets an async_status from Traffic Ops. +func (to *Session) GetAsyncStatus(id int, opts RequestOptions) (tc.AsyncStatusResponse, toclientlib.ReqInf, error) { + route := fmt.Sprintf(apiAsyncStatus, id) + var data tc.AsyncStatusResponse + reqInf, err := to.get(route, opts, &data) + return data, reqInf, err +} diff --git a/traffic_ops/v5-client/cachegroup.go b/traffic_ops/v5-client/cachegroup.go new file mode 100644 index 0000000000..0d290fa66b --- /dev/null +++ b/traffic_ops/v5-client/cachegroup.go @@ -0,0 +1,111 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "errors" + "fmt" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiCachegroups is the API version-relative path for the /cachegroups API endpoint. +const apiCachegroups = "/cachegroups" + +// CreateCacheGroup creates the given Cache Group. +func (to *Session) CreateCacheGroup(cachegroup tc.CacheGroupNullable, opts RequestOptions) (tc.CacheGroupDetailResponse, toclientlib.ReqInf, error) { + var resp tc.CacheGroupDetailResponse + if cachegroup.TypeID == nil && cachegroup.Type != nil { + opts := NewRequestOptions() + opts.QueryParameters.Set("name", *cachegroup.Type) + ty, _, err := to.GetTypes(opts) + if err != nil { + return resp, toclientlib.ReqInf{}, fmt.Errorf("resolving Type name '%s' to an ID: %w - alerts: %+v", *cachegroup.Name, err, ty.Alerts) + } + if len(ty.Response) == 0 { + return resp, toclientlib.ReqInf{}, errors.New("no type named " + *cachegroup.Type) + } + cachegroup.TypeID = &ty.Response[0].ID + } + + if cachegroup.ParentCachegroupID == nil && cachegroup.ParentName != nil { + opts := NewRequestOptions() + opts.QueryParameters.Set("name", *cachegroup.ParentName) + p, _, err := to.GetCacheGroups(opts) + if err != nil { + resp.Alerts = p.Alerts + return resp, toclientlib.ReqInf{}, err + } + if len(p.Response) == 0 { + resp.Alerts = p.Alerts + return resp, toclientlib.ReqInf{}, errors.New("no cachegroup named " + *cachegroup.ParentName) + } + cachegroup.ParentCachegroupID = p.Response[0].ID + } + + if cachegroup.SecondaryParentCachegroupID == nil && cachegroup.SecondaryParentName != nil { + opts := NewRequestOptions() + opts.QueryParameters.Set("name", *cachegroup.SecondaryParentName) + p, _, err := to.GetCacheGroups(opts) + if err != nil { + resp.Alerts = p.Alerts + return resp, toclientlib.ReqInf{}, err + } + if len(p.Response) == 0 { + resp.Alerts = p.Alerts + return resp, toclientlib.ReqInf{}, errors.New("no cachegroup named " + *cachegroup.ParentName) + } + cachegroup.SecondaryParentCachegroupID = p.Response[0].ID + } + + reqInf, err := to.post(apiCachegroups, opts, cachegroup, &resp) + return resp, reqInf, err +} + +// UpdateCacheGroup replaces the Cache Group identified by the given ID with +// the given Cache Group. +func (to *Session) UpdateCacheGroup(id int, cachegroup tc.CacheGroupNullable, opts RequestOptions) (tc.CacheGroupDetailResponse, toclientlib.ReqInf, error) { + route := fmt.Sprintf("%s/%d", apiCachegroups, id) + var cachegroupResp tc.CacheGroupDetailResponse + reqInf, err := to.put(route, opts, cachegroup, &cachegroupResp) + return cachegroupResp, reqInf, err +} + +// GetCacheGroups retrieves Cache Groups configured in Traffic Ops. +func (to *Session) GetCacheGroups(opts RequestOptions) (tc.CacheGroupsNullableResponse, toclientlib.ReqInf, error) { + var data tc.CacheGroupsNullableResponse + reqInf, err := to.get(apiCachegroups, opts, &data) + return data, reqInf, err +} + +// DeleteCacheGroup deletes the Cache Group with the given ID. +func (to *Session) DeleteCacheGroup(id int, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + route := fmt.Sprintf("%s/%d", apiCachegroups, id) + var alerts tc.Alerts + reqInf, err := to.del(route, opts, &alerts) + return alerts, reqInf, err +} + +// SetCacheGroupDeliveryServices assigns all of the assignable Cache Servers in +// the identified Cache Group to all of the identified the Delivery Services. +func (to *Session) SetCacheGroupDeliveryServices(cgID int, dsIDs []int, opts RequestOptions) (tc.CacheGroupPostDSRespResponse, toclientlib.ReqInf, error) { + uri := fmt.Sprintf("%s/%d/deliveryservices", apiCachegroups, cgID) + req := tc.CachegroupPostDSReq{DeliveryServices: dsIDs} + var resp tc.CacheGroupPostDSRespResponse + reqInf, err := to.post(uri, opts, req, &resp) + return resp, reqInf, err +} diff --git a/traffic_ops/v5-client/cdn.go b/traffic_ops/v5-client/cdn.go new file mode 100644 index 0000000000..99979c16fa --- /dev/null +++ b/traffic_ops/v5-client/cdn.go @@ -0,0 +1,78 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "fmt" + "net/url" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiCDNs is the API version-relative path for the /cdns API endpoint. +const apiCDNs = "/cdns" + +// CreateCDN creates a CDN. +func (to *Session) CreateCDN(cdn tc.CDN, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(apiCDNs, opts, cdn, &alerts) + return alerts, reqInf, err +} + +// UpdateCDN replaces the identified CDN with the provided CDN. +func (to *Session) UpdateCDN(id int, cdn tc.CDN, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + route := fmt.Sprintf("%s/%d", apiCDNs, id) + var alerts tc.Alerts + reqInf, err := to.put(route, opts, cdn, &alerts) + return alerts, reqInf, err +} + +// GetCDNs retrieves CDNs from Traffic Ops. +func (to *Session) GetCDNs(opts RequestOptions) (tc.CDNsResponse, toclientlib.ReqInf, error) { + var data tc.CDNsResponse + reqInf, err := to.get(apiCDNs, opts, &data) + return data, reqInf, err +} + +// DeleteCDN deletes the CDN with the given ID. +func (to *Session) DeleteCDN(id int, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + route := fmt.Sprintf("%s/%d", apiCDNs, id) + var alerts tc.Alerts + reqInf, err := to.del(route, opts, &alerts) + return alerts, reqInf, err +} + +// GetCDNSSLKeys retrieves the SSL keys for the CDN with the given name. +func (to *Session) GetCDNSSLKeys(name string, opts RequestOptions) (tc.CDNSSLKeysResponse, toclientlib.ReqInf, error) { + route := fmt.Sprintf("%s/name/%s/sslkeys", apiCDNs, url.PathEscape(name)) + var data tc.CDNSSLKeysResponse + reqInf, err := to.get(route, opts, &data) + return data, reqInf, err +} + +// QueueUpdatesForCDN set the "updPending" field of a list of servers identified by +// 'cdnID' and any other query params (type or profile) to the value of 'queueUpdate' +func (to *Session) QueueUpdatesForCDN(cdnID int, queueUpdate bool, opts RequestOptions) (tc.CDNQueueUpdateResponse, toclientlib.ReqInf, error) { + req := tc.CDNQueueUpdateRequest{Action: queueUpdateActions[queueUpdate]} + var resp tc.CDNQueueUpdateResponse + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + path := fmt.Sprintf("/cdns/%d/queue_update", cdnID) + reqInf, err := to.post(path, opts, req, &resp) + return resp, reqInf, err +} diff --git a/traffic_ops/v5-client/cdn_dnssec.go b/traffic_ops/v5-client/cdn_dnssec.go new file mode 100644 index 0000000000..6950b5fdd3 --- /dev/null +++ b/traffic_ops/v5-client/cdn_dnssec.go @@ -0,0 +1,68 @@ +package client + +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "fmt" + "net/url" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +const ( + apiCDNsDNSSECKeysGenerate = "/cdns/dnsseckeys/generate" + apiCDNsNameDNSSECKeys = "/cdns/name/%s/dnsseckeys" + apiCDNsDNSSECRefresh = "/cdns/dnsseckeys/refresh" + apiCDNsDNSSECKeysKSKGenerate = "/cdns/%s/dnsseckeys/ksk/generate" +) + +// GenerateCDNDNSSECKeys generates DNSSEC keys for the given CDN. +func (to *Session) GenerateCDNDNSSECKeys(req tc.CDNDNSSECGenerateReq, opts RequestOptions) (tc.GenerateCDNDNSSECKeysResponse, toclientlib.ReqInf, error) { + var resp tc.GenerateCDNDNSSECKeysResponse + reqInf, err := to.post(apiCDNsDNSSECKeysGenerate, opts, req, &resp) + return resp, reqInf, err +} + +// GetCDNDNSSECKeys gets the DNSSEC keys for the given CDN. +func (to *Session) GetCDNDNSSECKeys(name string, opts RequestOptions) (tc.CDNDNSSECKeysResponse, toclientlib.ReqInf, error) { + route := fmt.Sprintf(apiCDNsNameDNSSECKeys, url.PathEscape(name)) + var resp tc.CDNDNSSECKeysResponse + reqInf, err := to.get(route, opts, &resp) + return resp, reqInf, err +} + +// DeleteCDNDNSSECKeys deletes all the DNSSEC keys for the given CDN. +func (to *Session) DeleteCDNDNSSECKeys(name string, opts RequestOptions) (tc.DeleteCDNDNSSECKeysResponse, toclientlib.ReqInf, error) { + route := fmt.Sprintf(apiCDNsNameDNSSECKeys, url.PathEscape(name)) + var resp tc.DeleteCDNDNSSECKeysResponse + reqInf, err := to.del(route, opts, &resp) + return resp, reqInf, err +} + +// RefreshDNSSECKeys asynchronously regenerates any expired DNSSEC keys in all CDNs. +func (to *Session) RefreshDNSSECKeys(opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var resp tc.Alerts + reqInf, err := to.put(apiCDNsDNSSECRefresh, opts, nil, &resp) + return resp, reqInf, err +} + +// GenerateCDNDNSSECKSK generates the DNSSEC KSKs (key-signing key) for the given CDN. +func (to *Session) GenerateCDNDNSSECKSK(name string, req tc.CDNGenerateKSKReq, opts RequestOptions) (tc.GenerateCDNDNSSECKeysResponse, toclientlib.ReqInf, error) { + route := fmt.Sprintf(apiCDNsDNSSECKeysKSKGenerate, url.PathEscape(name)) + var resp tc.GenerateCDNDNSSECKeysResponse + reqInf, err := to.post(route, opts, req, &resp) + return resp, reqInf, err +} diff --git a/traffic_ops/v5-client/cdn_domains.go b/traffic_ops/v5-client/cdn_domains.go new file mode 100644 index 0000000000..9ff1b3d72c --- /dev/null +++ b/traffic_ops/v5-client/cdn_domains.go @@ -0,0 +1,28 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// GetDomains gets all CDN Domains. +func (to *Session) GetDomains(opts RequestOptions) (tc.DomainsResponse, toclientlib.ReqInf, error) { + var data tc.DomainsResponse + inf, err := to.get("/cdns/domains", opts, &data) + return data, inf, err +} diff --git a/traffic_ops/v5-client/cdn_lock.go b/traffic_ops/v5-client/cdn_lock.go new file mode 100644 index 0000000000..022a550bd5 --- /dev/null +++ b/traffic_ops/v5-client/cdn_lock.go @@ -0,0 +1,45 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiCDNLocks is the API version-relative path for the /cdn_locks API endpoint. +const apiCDNLocks = "/cdn_locks" + +// CreateCDNLock creates a CDN Lock. +func (to *Session) CreateCDNLock(cdnLock tc.CDNLock, opts RequestOptions) (tc.CDNLockCreateResponse, toclientlib.ReqInf, error) { + var response tc.CDNLockCreateResponse + reqInf, err := to.post(apiCDNLocks, opts, cdnLock, &response) + return response, reqInf, err +} + +// GetCDNLocks retrieves the CDN locks based on the passed in parameters. +func (to *Session) GetCDNLocks(opts RequestOptions) (tc.CDNLocksGetResponse, toclientlib.ReqInf, error) { + var data tc.CDNLocksGetResponse + reqInf, err := to.get(apiCDNLocks, opts, &data) + return data, reqInf, err +} + +// DeleteCDNLocks deletes the CDN lock of a particular(requesting) user. +func (to *Session) DeleteCDNLocks(opts RequestOptions) (tc.CDNLockDeleteResponse, toclientlib.ReqInf, error) { + var data tc.CDNLockDeleteResponse + reqInf, err := to.del(apiCDNLocks, opts, &data) + return data, reqInf, err +} diff --git a/traffic_ops/v5-client/cdn_notifications.go b/traffic_ops/v5-client/cdn_notifications.go new file mode 100644 index 0000000000..abf0f678c3 --- /dev/null +++ b/traffic_ops/v5-client/cdn_notifications.go @@ -0,0 +1,53 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/url" + "strconv" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiCDNNotifications is the API version-relative path to the +// /cdn_notifications API endpoint. +const apiCDNNotifications = "/cdn_notifications" + +// GetCDNNotifications returns a list of CDN Notifications. +func (to *Session) GetCDNNotifications(opts RequestOptions) (tc.CDNNotificationsResponse, toclientlib.ReqInf, error) { + var data tc.CDNNotificationsResponse + reqInf, err := to.get(apiCDNNotifications, opts, &data) + return data, reqInf, err +} + +// CreateCDNNotification creates a CDN notification. +func (to *Session) CreateCDNNotification(notification tc.CDNNotificationRequest, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(apiCDNNotifications, opts, notification, &alerts) + return alerts, reqInf, err +} + +// DeleteCDNNotification deletes a CDN Notification by notification ID. +func (to *Session) DeleteCDNNotification(id int, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("id", strconv.Itoa(id)) + reqInf, err := to.del(apiCDNNotifications, opts, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/cdnfederations.go b/traffic_ops/v5-client/cdnfederations.go new file mode 100644 index 0000000000..dcd6bfcb66 --- /dev/null +++ b/traffic_ops/v5-client/cdnfederations.go @@ -0,0 +1,65 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "fmt" + "net/url" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +/* Internally, the CDNName is only used in the GET method. The CDNName + * seems to primarily be used to differentiate between `/federations` and + * `/cdns/:name/federations`. Although the behavior is odd, it is kept to + * keep the same behavior from perl. */ + +// 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 + route := "/cdns/" + url.PathEscape(cdnName) + "/federations" + inf, err := to.post(route, opts, f, &data) + return data, inf, err +} + +// 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 + route := "/cdns/" + url.PathEscape(cdnName) + "/federations" + inf, err := to.get(route, opts, &data) + return data, inf, err +} + +// 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 + route := fmt.Sprintf("/cdns/%s/federations/%d", url.PathEscape(cdnName), id) + inf, err := to.put(route, opts, f, &data) + return data, inf, err +} + +// 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 + route := fmt.Sprintf("/cdns/%s/federations/%d", url.PathEscape(cdnName), id) + inf, err := to.del(route, opts, &data) + return data, inf, err +} diff --git a/traffic_ops/v5-client/coordinate.go b/traffic_ops/v5-client/coordinate.go new file mode 100644 index 0000000000..9397938b69 --- /dev/null +++ b/traffic_ops/v5-client/coordinate.go @@ -0,0 +1,64 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/url" + "strconv" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiCoordinates is the API version-relative path for the /coordinates API endpoint. +const apiCoordinates = "/coordinates" + +// CreateCoordinate creates the given Coordinate. +func (to *Session) CreateCoordinate(coordinate tc.Coordinate, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(apiCoordinates, opts, coordinate, &alerts) + return alerts, reqInf, err +} + +// UpdateCoordinate replaces the Coordinate with the given ID with the one +// provided. +func (to *Session) UpdateCoordinate(id int, coordinate tc.Coordinate, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("id", strconv.Itoa(id)) + var alerts tc.Alerts + reqInf, err := to.put(apiCoordinates, opts, coordinate, &alerts) + return alerts, reqInf, err +} + +// GetCoordinates returns all Coordinates in Traffic Ops. +func (to *Session) GetCoordinates(opts RequestOptions) (tc.CoordinatesResponse, toclientlib.ReqInf, error) { + var data tc.CoordinatesResponse + reqInf, err := to.get(apiCoordinates, opts, &data) + return data, reqInf, err +} + +// DeleteCoordinate deletes the Coordinate with the given ID. +func (to *Session) DeleteCoordinate(id int, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("id", strconv.Itoa(id)) + var alerts tc.Alerts + reqInf, err := to.del(apiCoordinates, opts, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/crconfig.go b/traffic_ops/v5-client/crconfig.go new file mode 100644 index 0000000000..5a055073dc --- /dev/null +++ b/traffic_ops/v5-client/crconfig.go @@ -0,0 +1,61 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "encoding/json" + "errors" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiSnapshot is the API version-relative path for the /snapshot API endpoint. +const apiSnapshot = "/snapshot" + +// OuterResponse is the most basic type of a Traffic Ops API response, +// containing some kind of JSON-encoded 'response' object. +type OuterResponse struct { + Response json.RawMessage `json:"response"` +} + +// GetCRConfig returns the Snapshot for the given CDN from Traffic Ops. +func (to *Session) GetCRConfig(cdn string, opts RequestOptions) (tc.SnapshotResponse, toclientlib.ReqInf, error) { + uri := `/cdns/` + cdn + `/snapshot` + var resp tc.SnapshotResponse + reqInf, err := to.get(uri, opts, &resp) + return resp, reqInf, err +} + +// SnapshotCRConfig creates a new Snapshot for the CDN with the given Name - +// NOT just a new CRConfig! +func (to *Session) SnapshotCRConfig(opts RequestOptions) (tc.PutSnapshotResponse, toclientlib.ReqInf, error) { + var resp tc.PutSnapshotResponse + if opts.QueryParameters == nil || (opts.QueryParameters.Get("cdn") == "" && opts.QueryParameters.Get("cdnID") == "") { + return resp, toclientlib.ReqInf{}, errors.New("cannot take Snapshot of unidentified CDN - set 'cdn' or 'cdnID' query parameter") + } + reqInf, err := to.put(apiSnapshot, opts, nil, &resp) + return resp, reqInf, err +} + +// GetCRConfigNew returns the *new* Snapshot for the given CDN from Traffic +// Ops. +func (to *Session) GetCRConfigNew(cdn string, opts RequestOptions) (tc.SnapshotResponse, toclientlib.ReqInf, error) { + uri := `/cdns/` + cdn + `/snapshot/new` + var resp tc.SnapshotResponse + reqInf, err := to.get(uri, opts, &resp) + return resp, reqInf, err +} diff --git a/traffic_ops/v5-client/deliveryservice.go b/traffic_ops/v5-client/deliveryservice.go new file mode 100644 index 0000000000..e464784ebf --- /dev/null +++ b/traffic_ops/v5-client/deliveryservice.go @@ -0,0 +1,350 @@ +package client + +/* + * 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 ( + "encoding/json" + "errors" + "fmt" + "net/url" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/lib/go-util" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// These are the API endpoints used by the various Delivery Service-related client methods. +const ( + // API_DELIVERY_SERVICES is the API path on which Traffic Ops serves Delivery Service + // information. More specific information is typically found on sub-paths of this. + apiDeliveryServices = "/deliveryservices" + + // APIDeliveryServiceId is the API path on which Traffic Ops serves information about + // a specific Delivery Service identified by an integral, unique identifier. It is + // intended to be used with fmt.Sprintf to insert its required path parameter (namely the ID + // of the Delivery Service of interest). + apiDeliveryServiceID = apiDeliveryServices + "/%d" + + // apiDeliveryServiceHealth is the API path on which Traffic Ops serves information about + // the 'health' of a specific Delivery Service identified by an integral, unique identifier. It is + // intended to be used with fmt.Sprintf to insert its required path parameter (namely the ID + // of the Delivery Service of interest). + apiDeliveryServiceHealth = apiDeliveryServiceID + "/health" + + // apiDeliveryServiceCapacity is the API path on which Traffic Ops serves information about + // the 'capacity' of a specific Delivery Service identified by an integral, unique identifier. It is + // intended to be used with fmt.Sprintf to insert its required path parameter (namely the ID + // of the Delivery Service of interest). + apiDeliveryServiceCapacity = apiDeliveryServiceID + "/capacity" + + // apiDeliveryServiceEligibleServers is the API path on which Traffic Ops serves information about + // the servers which are eligible to be assigned to a specific Delivery Service identified by an integral, + // unique identifier. It is intended to be used with fmt.Sprintf to insert its required path parameter + // (namely the ID of the Delivery Service of interest). + apiDeliveryServiceEligibleServers = apiDeliveryServiceID + "/servers/eligible" + + // apiDeliveryServicesSafeUpdate is the API path on which Traffic Ops provides the functionality to + // update the "safe" subset of properties of a Delivery Service identified by an integral, unique + // identifier. It is intended to be used with fmt.Sprintf to insert its required path parameter + // (namely the ID of the Delivery Service of interest). + apiDeliveryServicesSafeUpdate = apiDeliveryServiceID + "/safe" + + // apiAPIDeliveryServiceXMLIDSSLKeys is the API path on which Traffic Ops serves information about + // and functionality relating to the SSL keys used by a Delivery Service identified by its XMLID. It is + // intended to be used with fmt.Sprintf to insert its required path parameter (namely the XMLID + // of the Delivery Service of interest). + apiAPIDeliveryServiceXMLIDSSLKeys = apiDeliveryServices + "/xmlId/%s/sslkeys" + + // apiDeliveryServiceGenerateSSLKeys is the API path on which Traffic Ops will generate new SSL keys. + apiDeliveryServiceGenerateSSLKeys = apiDeliveryServices + "/sslkeys/generate" + + // apiDeliveryServiceAddSSLKeys is the API path on which Traffic Ops will add SSL keys. + apiDeliveryServiceAddSSLKeys = apiDeliveryServices + "/sslkeys/add" + + // apiDeliveryServiceURISigningKeys is the API path on which Traffic Ops serves information + // about and functionality relating to the URI-signing keys used by a Delivery Service identified + // by its XMLID. It is intended to be used with fmt.Sprintf to insert its required path parameter + // (namely the XMLID of the Delivery Service of interest). + apiDeliveryServicesURISigningKeys = apiDeliveryServices + "/%s/urisignkeys" + + // apiDeliveryServicesURLSignatureKeys is the API path on which Traffic Ops serves information + // about and functionality relating to the URL-signing keys used by a Delivery Service identified + // by its XMLID. It is intended to be used with fmt.Sprintf to insert its required path parameter + // (namely the XMLID of the Delivery Service of interest). + apiDeliveryServicesURLSignatureKeys = apiDeliveryServices + "/xmlId/%s/urlkeys" + + // apiDeliveryServicesURLSignatureKeysGenerate is the API path on which Traffic Ops provides + // functionality to generate new URL-signing keys used by a Delivery Service identified + // by its XMLID. It is intended to be used with fmt.Sprintf to insert its required path parameter + // (namely the XMLID of the Delivery Service of interest). + apiDeliveryServicesURLSignatureKeysGenerate = apiDeliveryServices + "/xmlId/%s/urlkeys/generate" + + // apiDeliveryServicesRegexes is the API path on which Traffic Ops serves Delivery Service + // 'regex' (Regular Expression) information. + apiDeliveryServicesRegexes = "/deliveryservices_regexes" + + // apiServerDeliveryServices is the API path on which Traffic Ops serves functionality + // related to the associations a specific server and its assigned Delivery Services. It is + // intended to be used with fmt.Sprintf to insert its required path parameter (namely the ID + // of the server of interest). + apiServerDeliveryServices = "/servers/%d/deliveryservices" + + // apiDeliveryServiceServer is the API path on which Traffic Ops serves functionality related + // to the associations between Delivery Services and their assigned Server(s). + apiDeliveryServiceServer = "/deliveryserviceserver" + + // apiDeliveryServicesServers is the API path on which Traffic Ops serves functionality related + // to the associations between a Delivery Service and its assigned Server(s). + apiDeliveryServicesServers = "/deliveryservices/%s/servers" +) + +// GetDeliveryServicesByServer retrieves all Delivery Services assigned to the +// server with the given ID. +func (to *Session) GetDeliveryServicesByServer(id int, opts RequestOptions) (tc.DeliveryServicesResponseV4, toclientlib.ReqInf, error) { + var data tc.DeliveryServicesResponseV4 + reqInf, err := to.get(fmt.Sprintf(apiServerDeliveryServices, id), opts, &data) + return data, reqInf, err +} + +// GetDeliveryServices returns (tenant-visible) Delivery Services. +func (to *Session) GetDeliveryServices(opts RequestOptions) (tc.DeliveryServicesResponseV4, toclientlib.ReqInf, error) { + var data tc.DeliveryServicesResponseV4 + reqInf, err := to.get(apiDeliveryServices, opts, &data) + return data, reqInf, err +} + +// CreateDeliveryService creates the Delivery Service it's passed. +func (to *Session) CreateDeliveryService(ds tc.DeliveryServiceV4, opts RequestOptions) (tc.DeliveryServicesResponseV4, toclientlib.ReqInf, error) { + var reqInf toclientlib.ReqInf + var resp tc.DeliveryServicesResponseV4 + if ds.TypeID == nil && ds.Type != nil { + typeOpts := NewRequestOptions() + typeOpts.QueryParameters.Set("name", ds.Type.String()) + ty, _, err := to.GetTypes(typeOpts) + if err != nil { + return resp, reqInf, err + } + if len(ty.Response) == 0 { + return resp, reqInf, fmt.Errorf("no Type named '%s'", ds.Type) + } + ds.TypeID = &ty.Response[0].ID + } + + if ds.CDNID == nil && ds.CDNName != nil { + cdnOpts := NewRequestOptions() + cdnOpts.QueryParameters.Set("name", *ds.CDNName) + cdns, _, err := to.GetCDNs(cdnOpts) + if err != nil { + err = fmt.Errorf("attempting to resolve CDN name '%s' to an ID: %w", *ds.CDNName, err) + return resp, reqInf, err + } + if len(cdns.Response) == 0 { + return resp, reqInf, fmt.Errorf("no CDN named '%s'", *ds.CDNName) + } + ds.CDNID = &cdns.Response[0].ID + } + + if ds.ProfileID == nil && ds.ProfileName != nil { + profileOpts := NewRequestOptions() + profileOpts.QueryParameters.Set("name", *ds.ProfileName) + profiles, _, err := to.GetProfiles(profileOpts) + if err != nil { + return resp, reqInf, fmt.Errorf("attempting to resolve Profile name '%s' to an ID: %w", *ds.ProfileName, err) + } + if len(profiles.Response) == 0 { + return resp, reqInf, errors.New("no Profile named " + *ds.ProfileName) + } + ds.ProfileID = &profiles.Response[0].ID + } + + if ds.TenantID == nil && ds.Tenant != nil { + tenantOpts := NewRequestOptions() + tenantOpts.QueryParameters.Set("name", *ds.Tenant) + ten, _, err := to.GetTenants(tenantOpts) + if err != nil { + return resp, reqInf, fmt.Errorf("attempting to resolve Tenant '%s' to an ID: %w", *ds.Tenant, err) + } + if len(ten.Response) == 0 { + return resp, reqInf, fmt.Errorf("no Tenant named '%s'", *ds.Tenant) + } + ds.TenantID = &ten.Response[0].ID + } + + reqInf, err := to.post(apiDeliveryServices, RequestOptions{Header: opts.Header}, ds, &resp) + if err != nil { + return resp, reqInf, err + } + + return resp, reqInf, nil +} + +// UpdateDeliveryService replaces the Delivery Service identified by the +// integral, unique identifier 'id' with the one it's passed. +func (to *Session) UpdateDeliveryService(id int, ds tc.DeliveryServiceV4, opts RequestOptions) (tc.DeliveryServicesResponseV4, toclientlib.ReqInf, error) { + var data tc.DeliveryServicesResponseV4 + reqInf, err := to.put(fmt.Sprintf(apiDeliveryServiceID, id), opts, ds, &data) + if err != nil { + return data, reqInf, err + } + return data, reqInf, nil +} + +// DeleteDeliveryService deletes the DeliveryService matching the ID it's passed. +func (to *Session) DeleteDeliveryService(id int, opts RequestOptions) (tc.DeleteDeliveryServiceResponse, toclientlib.ReqInf, error) { + var data tc.DeleteDeliveryServiceResponse + reqInf, err := to.del(fmt.Sprintf(apiDeliveryServiceID, id), opts, &data) + return data, reqInf, err +} + +// GetDeliveryServiceHealth gets the 'health' of the Delivery Service identified by the +// integral, unique identifier 'id'. +func (to *Session) GetDeliveryServiceHealth(id int, opts RequestOptions) (tc.DeliveryServiceHealthResponse, toclientlib.ReqInf, error) { + var data tc.DeliveryServiceHealthResponse + reqInf, err := to.get(fmt.Sprintf(apiDeliveryServiceHealth, id), opts, &data) + return data, reqInf, err +} + +// GetDeliveryServiceCapacity gets the 'capacity' of the Delivery Service identified by the +// integral, unique identifier 'id'. +func (to *Session) GetDeliveryServiceCapacity(id int, opts RequestOptions) (tc.DeliveryServiceCapacityResponse, toclientlib.ReqInf, error) { + var data tc.DeliveryServiceCapacityResponse + reqInf, err := to.get(fmt.Sprintf(apiDeliveryServiceCapacity, id), opts, &data) + return data, reqInf, err +} + +// GenerateSSLKeysForDS generates ssl keys for a given cdn. +func (to *Session) GenerateSSLKeysForDS( + xmlid string, + cdnName string, + sslFields tc.SSLKeyRequestFields, + opts RequestOptions, +) (tc.DeliveryServiceSSLKeysGenerationResponse, toclientlib.ReqInf, error) { + if sslFields.Version == nil { + sslFields.Version = util.IntPtr(1) + } + version := util.JSONIntStr(*sslFields.Version) + request := tc.DeliveryServiceSSLKeysReq{ + BusinessUnit: sslFields.BusinessUnit, + CDN: util.StrPtr(cdnName), + City: sslFields.City, + Country: sslFields.Country, + DeliveryService: util.StrPtr(xmlid), + HostName: sslFields.HostName, + Key: util.StrPtr(xmlid), + Organization: sslFields.Organization, + State: sslFields.State, + Version: &version, + } + var response tc.DeliveryServiceSSLKeysGenerationResponse + reqInf, err := to.post(apiDeliveryServiceGenerateSSLKeys, opts, request, &response) + return response, reqInf, err +} + +// AddSSLKeysForDS adds SSL Keys for the given Delivery Service. +func (to *Session) AddSSLKeysForDS(request tc.DeliveryServiceAddSSLKeysReq, opts RequestOptions) (tc.SSLKeysAddResponse, toclientlib.ReqInf, error) { + var response tc.SSLKeysAddResponse + reqInf, err := to.post(apiDeliveryServiceAddSSLKeys, opts, request, &response) + return response, reqInf, err +} + +// DeleteDeliveryServiceSSLKeys deletes the SSL Keys used by the Delivery +// Service identified by the passed XMLID. +func (to *Session) DeleteDeliveryServiceSSLKeys(xmlid string, opts RequestOptions) (tc.DeliveryServiceSSLKeysGenerationResponse, toclientlib.ReqInf, error) { + var resp tc.DeliveryServiceSSLKeysGenerationResponse + reqInf, err := to.del(fmt.Sprintf(apiAPIDeliveryServiceXMLIDSSLKeys, url.QueryEscape(xmlid)), opts, &resp) + return resp, reqInf, err +} + +// GetDeliveryServiceSSLKeys retrieves the SSL keys of the Delivery Service +// with the given XMLID. +func (to *Session) GetDeliveryServiceSSLKeys(xmlid string, opts RequestOptions) (tc.DeliveryServiceSSLKeysResponse, toclientlib.ReqInf, error) { + var data tc.DeliveryServiceSSLKeysResponse + reqInf, err := to.get(fmt.Sprintf(apiAPIDeliveryServiceXMLIDSSLKeys, url.QueryEscape(xmlid)), opts, &data) + return data, reqInf, err +} + +// GetDeliveryServicesEligible returns the servers eligible for assignment to the Delivery +// Service identified by the integral, unique identifier 'dsID'. +func (to *Session) GetDeliveryServicesEligible(dsID int, opts RequestOptions) (tc.DSServerResponseV4, toclientlib.ReqInf, error) { + var resp tc.DSServerResponseV4 + reqInf, err := to.get(fmt.Sprintf(apiDeliveryServiceEligibleServers, dsID), opts, &resp) + return resp, reqInf, err +} + +// GetDeliveryServiceURLSignatureKeys returns the URL-signing keys used by the Delivery Service +// identified by the XMLID 'dsName'. +func (to *Session) GetDeliveryServiceURLSignatureKeys(dsName string, opts RequestOptions) (tc.URLSignatureKeysResponse, toclientlib.ReqInf, error) { + var data tc.URLSignatureKeysResponse + reqInf, err := to.get(fmt.Sprintf(apiDeliveryServicesURLSignatureKeys, dsName), opts, &data) + return data, reqInf, err +} + +// CreateDeliveryServiceURLSignatureKeys creates new URL-signing keys used by +// the Delivery Service identified by the XMLID 'dsName'. +func (to *Session) CreateDeliveryServiceURLSignatureKeys(dsName string, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(fmt.Sprintf(apiDeliveryServicesURLSignatureKeysGenerate, url.PathEscape(dsName)), opts, nil, &alerts) + return alerts, reqInf, err +} + +// DeleteDeliveryServiceURLSignatureKeys deletes the URL-signing keys used by the Delivery Service +// identified by the XMLID 'dsName'. +func (to *Session) DeleteDeliveryServiceURLSignatureKeys(dsName string, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.del(fmt.Sprintf(apiDeliveryServicesURLSignatureKeys, url.PathEscape(dsName)), opts, &alerts) + return alerts, reqInf, err +} + +// GetDeliveryServiceURISigningKeys returns the URI-signing keys used by the Delivery Service +// identified by the XMLID 'dsName'. The result is not parsed. +// Note that unlike most methods, this is incapable of returning alerts. +func (to *Session) GetDeliveryServiceURISigningKeys(dsName string, opts RequestOptions) ([]byte, toclientlib.ReqInf, error) { + data := json.RawMessage{} + reqInf, err := to.get(fmt.Sprintf(apiDeliveryServicesURISigningKeys, url.PathEscape(dsName)), opts, &data) + return []byte(data), reqInf, err +} + +// CreateDeliveryServiceURISigningKeys creates new URI-signing keys used by the Delivery Service +// identified by the XMLID 'dsXMLID'. +func (to *Session) CreateDeliveryServiceURISigningKeys(dsXMLID string, body tc.JWKSMap, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(fmt.Sprintf(apiDeliveryServicesURISigningKeys, url.PathEscape(dsXMLID)), opts, body, &alerts) + return alerts, reqInf, err +} + +// DeleteDeliveryServiceURISigningKeys deletes the URI-signing keys used by the Delivery Service +// identified by the XMLID 'dsXMLID'. +func (to *Session) DeleteDeliveryServiceURISigningKeys(dsXMLID string, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.del(fmt.Sprintf(apiDeliveryServicesURISigningKeys, url.PathEscape(dsXMLID)), opts, &alerts) + return alerts, reqInf, err +} + +// SafeDeliveryServiceUpdate updates the "safe" fields of the Delivery +// Service identified by the integral, unique identifier 'id'. +func (to *Session) SafeDeliveryServiceUpdate( + id int, + r tc.DeliveryServiceSafeUpdateRequest, + opts RequestOptions, +) (tc.DeliveryServiceSafeUpdateResponseV4, toclientlib.ReqInf, error) { + var data tc.DeliveryServiceSafeUpdateResponseV4 + reqInf, err := to.put(fmt.Sprintf(apiDeliveryServicesSafeUpdate, id), opts, r, &data) + return data, reqInf, err +} diff --git a/traffic_ops/v5-client/deliveryservice_regexes.go b/traffic_ops/v5-client/deliveryservice_regexes.go new file mode 100644 index 0000000000..df9488f979 --- /dev/null +++ b/traffic_ops/v5-client/deliveryservice_regexes.go @@ -0,0 +1,53 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "fmt" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiDSRegexes is the full API route to the +// /deliveryservices/{{ID}}/regexes endpoint. +const apiDSRegexes = "/deliveryservices/%d/regexes" + +// GetDeliveryServiceRegexesByDSID gets DeliveryServiceRegexes by a DS id +// also accepts an optional map of query parameters. +func (to *Session) GetDeliveryServiceRegexesByDSID(dsID int, opts RequestOptions) (tc.DeliveryServiceIDRegexResponse, toclientlib.ReqInf, error) { + var response tc.DeliveryServiceIDRegexResponse + route := fmt.Sprintf(apiDSRegexes, dsID) + reqInf, err := to.get(route, opts, &response) + return response, reqInf, err +} + +// GetDeliveryServiceRegexes retrieves all Delivery Service Regexes in Traffic +// Ops. +func (to *Session) GetDeliveryServiceRegexes(opts RequestOptions) (tc.DeliveryServiceRegexResponse, toclientlib.ReqInf, error) { + var data tc.DeliveryServiceRegexResponse + reqInf, err := to.get(apiDeliveryServicesRegexes, opts, &data) + return data, reqInf, err +} + +// PostDeliveryServiceRegexesByDSID adds the given Regex to the identified +// Delivery Service. +func (to *Session) PostDeliveryServiceRegexesByDSID(dsID int, regex tc.DeliveryServiceRegexPost, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + route := fmt.Sprintf(apiDSRegexes, dsID) + reqInf, err := to.post(route, opts, regex, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/deliveryservice_request_comments.go b/traffic_ops/v5-client/deliveryservice_request_comments.go new file mode 100644 index 0000000000..e7d8f945ae --- /dev/null +++ b/traffic_ops/v5-client/deliveryservice_request_comments.go @@ -0,0 +1,68 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/url" + "strconv" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiDeliveryServiceRequestComments is the API version-relative route to +// the /deliveryservice_request_comments endpoint. +const apiDeliveryServiceRequestComments = "/deliveryservice_request_comments" + +// CreateDeliveryServiceRequestComment creates the given Delivery Service +// Request comment. +func (to *Session) CreateDeliveryServiceRequestComment(comment tc.DeliveryServiceRequestComment, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(apiDeliveryServiceRequestComments, opts, comment, &alerts) + return alerts, reqInf, err +} + +// UpdateDeliveryServiceRequestComment replaces the Delivery Service Request +// comment identified by 'id' with the one provided. +func (to *Session) UpdateDeliveryServiceRequestComment(id int, comment tc.DeliveryServiceRequestComment, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("id", strconv.Itoa(id)) + var alerts tc.Alerts + reqInf, err := to.put(apiDeliveryServiceRequestComments, opts, comment, &alerts) + return alerts, reqInf, err +} + +// GetDeliveryServiceRequestComments retrieves all comments on all Delivery +// Service Requests. +func (to *Session) GetDeliveryServiceRequestComments(opts RequestOptions) (tc.DeliveryServiceRequestCommentsResponse, toclientlib.ReqInf, error) { + var data tc.DeliveryServiceRequestCommentsResponse + reqInf, err := to.get(apiDeliveryServiceRequestComments, opts, &data) + return data, reqInf, err +} + +// DeleteDeliveryServiceRequestComment deletes the Delivery Service Request +// comment with the given ID. +func (to *Session) DeleteDeliveryServiceRequestComment(id int, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("id", strconv.Itoa(id)) + var alerts tc.Alerts + reqInf, err := to.del(apiDeliveryServiceRequestComments, opts, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/deliveryservice_requests.go b/traffic_ops/v5-client/deliveryservice_requests.go new file mode 100644 index 0000000000..89e59575d0 --- /dev/null +++ b/traffic_ops/v5-client/deliveryservice_requests.go @@ -0,0 +1,142 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "errors" + "fmt" + "net/url" + "strconv" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiDSRequests is the API version-relative path to the +// /deliveryservice_requests API endpoint. +const apiDSRequests = "/deliveryservice_requests" + +// CreateDeliveryServiceRequest creates the given Delivery Service Request. +func (to *Session) CreateDeliveryServiceRequest(dsr tc.DeliveryServiceRequestV4, opts RequestOptions) (tc.DeliveryServiceRequestResponseV4, toclientlib.ReqInf, error) { + var resp tc.DeliveryServiceRequestResponseV4 + if dsr.AssigneeID == nil && dsr.Assignee != nil { + assigneeOpts := NewRequestOptions() + assigneeOpts.QueryParameters.Set("username", *dsr.Assignee) + res, reqInf, err := to.GetUsers(assigneeOpts) + if err != nil { + return resp, reqInf, err + } + if len(res.Response) == 0 { + return resp, reqInf, fmt.Errorf("no user with username '%s'", *dsr.Assignee) + } + dsr.AssigneeID = res.Response[0].ID + } + + if dsr.AuthorID == nil && dsr.Author != "" { + authorOpts := NewRequestOptions() + authorOpts.QueryParameters.Set("username", dsr.Author) + res, reqInf, err := to.GetUsers(authorOpts) + if err != nil { + return resp, reqInf, err + } + if len(res.Response) == 0 { + return resp, reqInf, fmt.Errorf("no user with name '%s'", dsr.Author) + } + dsr.AuthorID = res.Response[0].ID + } + + var ds *tc.DeliveryServiceV4 + if dsr.ChangeType == tc.DSRChangeTypeDelete { + ds = dsr.Original + } else { + ds = dsr.Requested + } + + if ds.TypeID == nil && ds.Type.String() != "" { + typeOpts := NewRequestOptions() + typeOpts.QueryParameters.Set("name", ds.Type.String()) + ty, reqInf, err := to.GetTypes(typeOpts) + if err != nil || len(ty.Response) == 0 { + return resp, reqInf, errors.New("no type named " + ds.Type.String()) + } + ds.TypeID = &ty.Response[0].ID + } + + if ds.CDNID == nil && ds.CDNName != nil { + cdnOpts := NewRequestOptions() + cdnOpts.QueryParameters.Set("name", *ds.CDNName) + cdns, reqInf, err := to.GetCDNs(cdnOpts) + if err != nil || len(cdns.Response) == 0 { + return resp, reqInf, fmt.Errorf("no CDN named '%s'", *ds.CDNName) + } + ds.CDNID = &cdns.Response[0].ID + } + + if ds.ProfileID == nil && ds.ProfileName != nil { + profileOpts := NewRequestOptions() + profileOpts.QueryParameters.Set("name", *ds.ProfileName) + profiles, reqInf, err := to.GetProfiles(profileOpts) + if err != nil || len(profiles.Response) == 0 { + return resp, reqInf, fmt.Errorf("no Profile named '%s'", *ds.ProfileName) + } + ds.ProfileID = &profiles.Response[0].ID + } + + if ds.TenantID == nil && ds.Tenant != nil { + tenantOpts := NewRequestOptions() + tenantOpts.QueryParameters.Set("name", *ds.Tenant) + ten, reqInf, err := to.GetTenants(tenantOpts) + if err != nil || len(ten.Response) == 0 { + return resp, reqInf, fmt.Errorf("no Tenant named '%s'", *ds.Tenant) + } + ds.TenantID = &ten.Response[0].ID + } + + reqInf, err := to.post(apiDSRequests, opts, dsr, &resp) + return resp, reqInf, err +} + +// GetDeliveryServiceRequests retrieves Delivery Service Requests available to session user. +func (to *Session) GetDeliveryServiceRequests(opts RequestOptions) (tc.DeliveryServiceRequestsResponseV4, toclientlib.ReqInf, error) { + var data tc.DeliveryServiceRequestsResponseV4 + reqInf, err := to.get(apiDSRequests, opts, &data) + return data, reqInf, err +} + +// DeleteDeliveryServiceRequest deletes the Delivery Service Request with the given ID. +func (to *Session) DeleteDeliveryServiceRequest(id int, opts RequestOptions) (tc.DeliveryServiceRequestResponseV4, toclientlib.ReqInf, error) { + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("id", strconv.Itoa(id)) + var resp tc.DeliveryServiceRequestResponseV4 + reqInf, err := to.del(apiDSRequests, opts, &resp) + return resp, reqInf, err +} + +// UpdateDeliveryServiceRequest replaces the existing DSR that has the given +// ID with the DSR passed. +func (to *Session) UpdateDeliveryServiceRequest(id int, dsr tc.DeliveryServiceRequestV4, opts RequestOptions) (tc.DeliveryServiceRequestResponseV4, toclientlib.ReqInf, error) { + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("id", strconv.Itoa(id)) + + var payload tc.DeliveryServiceRequestResponseV4 + reqInf, err := to.put(apiDSRequests, opts, dsr, &payload) + + return payload, reqInf, err +} diff --git a/traffic_ops/v5-client/deliveryservices_required_capabilities.go b/traffic_ops/v5-client/deliveryservices_required_capabilities.go new file mode 100644 index 0000000000..d0f63f7059 --- /dev/null +++ b/traffic_ops/v5-client/deliveryservices_required_capabilities.go @@ -0,0 +1,55 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/url" + "strconv" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiDeliveryServicesRequiredCapabilities is the API version-relative +// route to the /deliveryservices_required_capabilities endpoint. +const apiDeliveryServicesRequiredCapabilities = "/deliveryservices_required_capabilities" + +// CreateDeliveryServicesRequiredCapability assigns a Required Capability to a Delivery Service. +func (to *Session) CreateDeliveryServicesRequiredCapability(capability tc.DeliveryServicesRequiredCapability, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(apiDeliveryServicesRequiredCapabilities, opts, capability, &alerts) + return alerts, reqInf, err +} + +// DeleteDeliveryServicesRequiredCapability unassigns a Required Capability from a Delivery Service. +func (to *Session) DeleteDeliveryServicesRequiredCapability(deliveryserviceID int, capability string, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("deliveryServiceID", strconv.Itoa(deliveryserviceID)) + opts.QueryParameters.Set("requiredCapability", capability) + reqInf, err := to.del(apiDeliveryServicesRequiredCapabilities, opts, &alerts) + return alerts, reqInf, err +} + +// GetDeliveryServicesRequiredCapabilities retrieves a list of relationships +// between Delivery Services and the Capabilities they require. +func (to *Session) GetDeliveryServicesRequiredCapabilities(opts RequestOptions) (tc.DeliveryServicesRequiredCapabilitiesResponse, toclientlib.ReqInf, error) { + var resp tc.DeliveryServicesRequiredCapabilitiesResponse + reqInf, err := to.get(apiDeliveryServicesRequiredCapabilities, opts, &resp) + return resp, reqInf, err +} diff --git a/traffic_ops/v5-client/deliveryserviceserver.go b/traffic_ops/v5-client/deliveryserviceserver.go new file mode 100644 index 0000000000..225870b42a --- /dev/null +++ b/traffic_ops/v5-client/deliveryserviceserver.go @@ -0,0 +1,75 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "fmt" + "net/url" + "strconv" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/lib/go-util" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// CreateDeliveryServiceServers associates the given servers with the given +// Delivery Services. If replace is true, it deletes any existing associations +// for the given Delivery Service. +func (to *Session) CreateDeliveryServiceServers(dsID int, serverIDs []int, replace bool, opts RequestOptions) (tc.DeliveryserviceserverResponse, toclientlib.ReqInf, error) { + req := tc.DSServerIDs{ + DeliveryServiceID: util.IntPtr(dsID), + ServerIDs: serverIDs, + Replace: util.BoolPtr(replace), + } + var resp tc.DeliveryserviceserverResponse + reqInf, err := to.post(apiDeliveryServiceServer, opts, req, &resp) + return resp, reqInf, err +} + +// DeleteDeliveryServiceServer removes the association between the Delivery +// Service identified by dsID and the server identified by serverID. +func (to *Session) DeleteDeliveryServiceServer(dsID int, serverID int, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + route := fmt.Sprintf("%s/%d/%d", apiDeliveryServiceServer, dsID, serverID) + var resp tc.Alerts + reqInf, err := to.del(route, opts, &resp) + return resp, reqInf, err +} + +// AssignServersToDeliveryService assigns the given list of servers to the +// Delivery Service with the given xmlID. +func (to *Session) AssignServersToDeliveryService(servers []string, xmlID string, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + route := fmt.Sprintf(apiDeliveryServicesServers, url.PathEscape(xmlID)) + dss := tc.DeliveryServiceServers{ServerNames: servers, XmlId: xmlID} + var resp tc.Alerts + reqInf, err := to.post(route, opts, dss, &resp) + return resp, reqInf, err +} + +// GetServersByDeliveryService gets the servers that are assigned to the delivery service with the given ID. +func (to *Session) GetServersByDeliveryService(id int, opts RequestOptions) (tc.DSServerResponseV4, toclientlib.ReqInf, error) { + route := fmt.Sprintf(apiDeliveryServicesServers, strconv.Itoa(id)) + resp := tc.DSServerResponseV4{} + reqInf, err := to.get(route, opts, &resp) + return resp, reqInf, err +} + +// GetDeliveryServiceServers returns associations between Delivery Services and +// servers. +func (to *Session) GetDeliveryServiceServers(opts RequestOptions) (tc.DeliveryServiceServerResponse, toclientlib.ReqInf, error) { + var resp tc.DeliveryServiceServerResponse + reqInf, err := to.get(apiDeliveryServiceServer, opts, &resp) + return resp, reqInf, err +} diff --git a/traffic_ops/v5-client/division.go b/traffic_ops/v5-client/division.go new file mode 100644 index 0000000000..880fe3baa1 --- /dev/null +++ b/traffic_ops/v5-client/division.go @@ -0,0 +1,57 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "fmt" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiDivisions is the API version-relative path to the /divisions API route. +const apiDivisions = "/divisions" + +// CreateDivision creates the given Division. +func (to *Session) CreateDivision(division tc.Division, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(apiDivisions, opts, division, &alerts) + return alerts, reqInf, err +} + +// UpdateDivision replaces the Division identified by 'id' with the one +// provided. +func (to *Session) UpdateDivision(id int, division tc.Division, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + route := fmt.Sprintf("%s/%d", apiDivisions, id) + var alerts tc.Alerts + reqInf, err := to.put(route, opts, division, &alerts) + return alerts, reqInf, err +} + +// GetDivisions returns Divisions from Traffic Ops. +func (to *Session) GetDivisions(opts RequestOptions) (tc.DivisionsResponse, toclientlib.ReqInf, error) { + var data tc.DivisionsResponse + reqInf, err := to.get(apiDivisions, opts, &data) + return data, reqInf, err +} + +// DeleteDivision deletes the Division identified by 'id'. +func (to *Session) DeleteDivision(id int, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + route := fmt.Sprintf("%s/%d", apiDivisions, id) + var alerts tc.Alerts + reqInf, err := to.del(route, opts, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/endpoints.go b/traffic_ops/v5-client/endpoints.go new file mode 100644 index 0000000000..9aa4ac18ba --- /dev/null +++ b/traffic_ops/v5-client/endpoints.go @@ -0,0 +1,27 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// apiVersions is the list of minor API versions in this client's major version. +// This should be all minor versions from 0 up to the latest minor in Traffic Control +// as of this client code. +// +// Versions are ordered latest-first. +func apiVersions() []string { + return []string{ + "5.0", + } +} diff --git a/traffic_ops/v5-client/federation.go b/traffic_ops/v5-client/federation.go new file mode 100644 index 0000000000..1b0bfb04b8 --- /dev/null +++ b/traffic_ops/v5-client/federation.go @@ -0,0 +1,141 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "fmt" + "strconv" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiFederations is the API version-relative path to the /federations API route. +const apiFederations = "/federations" + +// apiFederationsAll is the API version-relative path to the /federations/all +// API route. +const apiFederationsAll = "/federations/all" + +// Federations gets all Delivery Service-to-Federation mappings in Traffic Ops +// that are assigned to the current user. +func (to *Session) Federations(opts RequestOptions) (tc.FederationsResponse, toclientlib.ReqInf, error) { + var data tc.FederationsResponse + inf, err := to.get(apiFederations, opts, &data) + return data, inf, err +} + +// AllFederations gets all Delivery Service-to-Federation mappings in Traffic +// Ops. +func (to *Session) AllFederations(opts RequestOptions) (tc.FederationsResponse, toclientlib.ReqInf, error) { + var data tc.FederationsResponse + inf, err := to.get(apiFederationsAll, opts, &data) + return data, inf, err +} + +// CreateFederationDeliveryServices assigns the Delivery Services identified in +// 'deliveryServiceIDs' with the Federation identified by 'federationID'. If +// 'replace' is true, existing assignments for the Federation are overwritten. +func (to *Session) CreateFederationDeliveryServices( + federationID int, + deliveryServiceIDs []int, + replace bool, + opts RequestOptions, +) (tc.Alerts, toclientlib.ReqInf, error) { + req := tc.FederationDSPost{DSIDs: deliveryServiceIDs, Replace: &replace} + var alerts tc.Alerts + inf, err := to.post(`federations/`+strconv.Itoa(federationID)+`/deliveryservices`, opts, req, &alerts) + return alerts, inf, err +} + +// GetFederationDeliveryServices returns the Delivery Services assigned to the +// Federation identified by 'federationID'. +func (to *Session) GetFederationDeliveryServices(federationID int, opts RequestOptions) (tc.FederationDeliveryServicesResponse, toclientlib.ReqInf, error) { + var data tc.FederationDeliveryServicesResponse + inf, err := to.get(fmt.Sprintf("federations/%d/deliveryservices", federationID), opts, &data) + return data, inf, err +} + +// DeleteFederationDeliveryService unassigns the Delivery Service identified by +// 'deliveryServiceID' from the Federation identified by 'federationID'. +func (to *Session) DeleteFederationDeliveryService(federationID, deliveryServiceID int, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + route := fmt.Sprintf("federations/%d/deliveryservices/%d", federationID, deliveryServiceID) + var alerts tc.Alerts + reqInf, err := to.del(route, opts, &alerts) + return alerts, reqInf, err +} + +// CreateFederationUsers assigns the Federation identified by 'federationID' to +// the user(s) identified in 'userIDs'. If 'replace' is true, all existing user +// assignments for the Federation are overwritten. +func (to *Session) CreateFederationUsers(federationID int, userIDs []int, replace bool, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + req := tc.FederationUserPost{IDs: userIDs, Replace: &replace} + var alerts tc.Alerts + inf, err := to.post(fmt.Sprintf("federations/%d/users", federationID), opts, req, &alerts) + return alerts, inf, err +} + +// GetFederationUsers retrieves all users to whom the Federation identified by +// 'federationID' is assigned. +func (to *Session) GetFederationUsers(federationID int, opts RequestOptions) (tc.FederationUsersResponse, toclientlib.ReqInf, error) { + var data tc.FederationUsersResponse + inf, err := to.get(fmt.Sprintf("federations/%d/users", federationID), opts, &data) + return data, inf, err +} + +// DeleteFederationUser unassigns the Federation identified by 'federationID' +// from the user identified by 'userID'. +func (to *Session) DeleteFederationUser(federationID, userID int, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + route := fmt.Sprintf("federations/%d/users/%d", federationID, userID) + var alerts tc.Alerts + reqInf, err := to.del(route, opts, &alerts) + return alerts, reqInf, err +} + +// AddFederationResolverMappingsForCurrentUser adds Federation Resolver mappings to one or more +// Delivery Services for the current user. +func (to *Session) AddFederationResolverMappingsForCurrentUser( + mappings tc.DeliveryServiceFederationResolverMappingRequest, + opts RequestOptions, +) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(apiFederations, opts, mappings, &alerts) + return alerts, reqInf, err +} + +// DeleteFederationResolverMappingsForCurrentUser removes ALL Federation Resolver mappings for ALL +// Federations assigned to the currently authenticated user, as well as deleting ALL of the +// Federation Resolvers themselves. +func (to *Session) DeleteFederationResolverMappingsForCurrentUser(opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.del(apiFederations, opts, &alerts) + return alerts, reqInf, err +} + +// ReplaceFederationResolverMappingsForCurrentUser replaces any and all Federation Resolver mappings +// on all Federations assigned to the currently authenticated user. This will first remove ALL +// Federation Resolver mappings for ALL Federations assigned to the currently authenticated user, as +// well as deleting ALL of the Federation Resolvers themselves. In other words, calling this is +// equivalent to a call to DeleteFederationResolverMappingsForCurrentUser followed by a call to +// AddFederationResolverMappingsForCurrentUser . +func (to *Session) ReplaceFederationResolverMappingsForCurrentUser( + mappings tc.DeliveryServiceFederationResolverMappingRequest, + opts RequestOptions, +) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.put(apiFederations, opts, mappings, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/federation_federation_resolver.go b/traffic_ops/v5-client/federation_federation_resolver.go new file mode 100644 index 0000000000..1a308952a6 --- /dev/null +++ b/traffic_ops/v5-client/federation_federation_resolver.go @@ -0,0 +1,48 @@ +package client + +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ +import ( + "fmt" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// GetFederationFederationResolvers retrieves all Federation Resolvers belonging to Federation of ID. +func (to *Session) GetFederationFederationResolvers(id int, opts RequestOptions) (tc.FederationFederationResolversResponse, toclientlib.ReqInf, error) { + path := fmt.Sprintf("/federations/%d/federation_resolvers", id) + var resp tc.FederationFederationResolversResponse + reqInf, err := to.get(path, opts, &resp) + return resp, reqInf, err +} + +// AssignFederationFederationResolver assigns the resolvers identified in +// resolverIDs to the Federation identified by fedID. If replace is true, this +// will overwrite any and all existing resolvers assigned to the Federation. +func (to *Session) AssignFederationFederationResolver( + fedID int, + resolverIDs []int, + replace bool, + opts RequestOptions, +) (tc.AssignFederationFederationResolversResponse, toclientlib.ReqInf, error) { + path := fmt.Sprintf("/federations/%d/federation_resolvers", fedID) + req := tc.AssignFederationResolversRequest{ + Replace: replace, + FedResolverIDs: resolverIDs, + } + resp := tc.AssignFederationFederationResolversResponse{} + reqInf, err := to.post(path, opts, req, &resp) + return resp, reqInf, err +} diff --git a/traffic_ops/v5-client/federation_resolver.go b/traffic_ops/v5-client/federation_resolver.go new file mode 100644 index 0000000000..2818a79f21 --- /dev/null +++ b/traffic_ops/v5-client/federation_resolver.go @@ -0,0 +1,52 @@ +package client + +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/url" + "strconv" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiFederationResolvers is the API-version-relative path to the +// /federation_resolvers endpoint. +const apiFederationResolvers = "/federation_resolvers" + +// GetFederationResolvers retrieves Federation Resolvers from Traffic Ops. +func (to *Session) GetFederationResolvers(opts RequestOptions) (tc.FederationResolversResponse, toclientlib.ReqInf, error) { + var data tc.FederationResolversResponse + inf, err := to.get(apiFederationResolvers, opts, &data) + return data, inf, err +} + +// CreateFederationResolver creates the Federation Resolver 'fr'. +func (to *Session) CreateFederationResolver(fr tc.FederationResolver, opts RequestOptions) (tc.FederationResolverResponse, toclientlib.ReqInf, error) { + var response tc.FederationResolverResponse + reqInf, err := to.post(apiFederationResolvers, opts, fr, &response) + return response, reqInf, err +} + +// DeleteFederationResolver deletes the Federation Resolver identified by 'id'. +func (to *Session) DeleteFederationResolver(id uint, opts RequestOptions) (tc.FederationResolverResponse, toclientlib.ReqInf, error) { + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("id", strconv.FormatUint(uint64(id), 10)) + var alerts tc.FederationResolverResponse + reqInf, err := to.del(apiFederationResolvers, opts, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/iso.go b/traffic_ops/v5-client/iso.go new file mode 100644 index 0000000000..eb1e6ff6c9 --- /dev/null +++ b/traffic_ops/v5-client/iso.go @@ -0,0 +1,36 @@ +package client + +/* + * 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 ( + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiOSVersions is the full path to the /osversions API endpoint. +const apiOSVersions = "/osversions" + +// GetOSVersions GETs all available Operating System (OS) versions for ISO generation, +// as well as the name of the directory where the "kickstarter" files are found. +func (to *Session) GetOSVersions(opts RequestOptions) (tc.OSVersionsAPIResponse, toclientlib.ReqInf, error) { + var data tc.OSVersionsAPIResponse + reqInf, err := to.get(apiOSVersions, opts, &data) + return data, reqInf, err +} diff --git a/traffic_ops/v5-client/job.go b/traffic_ops/v5-client/job.go new file mode 100644 index 0000000000..5898f8a115 --- /dev/null +++ b/traffic_ops/v5-client/job.go @@ -0,0 +1,66 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/url" + "strconv" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiJobs is the API version-relative path to the /jobs API route. +const apiJobs = "/jobs" + +// CreateInvalidationJob creates the passed Content Invalidation Job. +func (to *Session) CreateInvalidationJob(job tc.InvalidationJobCreateV4, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(apiJobs, opts, job, &alerts) + return alerts, reqInf, err +} + +// DeleteInvalidationJob deletes the Content Invalidation Job identified by +// 'jobID'. +func (to *Session) DeleteInvalidationJob(jobID uint64, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("id", strconv.FormatUint(jobID, 10)) + var alerts tc.Alerts + reqInf, err := to.del(apiJobs, opts, &alerts) + return alerts, reqInf, err +} + +// UpdateInvalidationJob updates the passed Content Invalidation Job (it is +// expected to have an ID). +func (to *Session) UpdateInvalidationJob(job tc.InvalidationJobV4, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("id", strconv.FormatUint(job.ID, 10)) + reqInf, err := to.put(apiJobs, opts, job, &alerts) + return alerts, reqInf, err +} + +// GetInvalidationJobs returns a list of Content Invalidation Jobs visible to +// your Tenant. +func (to *Session) GetInvalidationJobs(opts RequestOptions) (tc.InvalidationJobsResponseV4, toclientlib.ReqInf, error) { + var data tc.InvalidationJobsResponseV4 + reqInf, err := to.get(apiJobs, opts, &data) + return data, reqInf, err +} diff --git a/traffic_ops/v5-client/log.go b/traffic_ops/v5-client/log.go new file mode 100644 index 0000000000..f828f4e3a5 --- /dev/null +++ b/traffic_ops/v5-client/log.go @@ -0,0 +1,31 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiLogs is the API version-relative path to the /logs API endpoint. +const apiLogs = "/logs" + +// GetLogs gets a list of logs. +func (to *Session) GetLogs(opts RequestOptions) (tc.LogsResponse, toclientlib.ReqInf, error) { + var data tc.LogsResponse + reqInf, err := to.get(apiLogs, opts, &data) + return data, reqInf, err +} diff --git a/traffic_ops/v5-client/origin.go b/traffic_ops/v5-client/origin.go new file mode 100644 index 0000000000..b735527780 --- /dev/null +++ b/traffic_ops/v5-client/origin.go @@ -0,0 +1,151 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "errors" + "fmt" + "net" + "net/url" + "strconv" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiOrigins is the full path to the /origins API route. +const apiOrigins = "/origins" + +func (to *Session) originIDs(origin *tc.Origin) error { + if origin == nil { + return errors.New("invalid call to originIDs; nil origin") + } + + opts := NewRequestOptions() + if origin.CachegroupID == nil && origin.Cachegroup != nil { + opts.QueryParameters.Set("name", *origin.Cachegroup) + p, _, err := to.GetCacheGroups(opts) + if err != nil { + return fmt.Errorf("resolving Cache Group name '%s' to an ID: %w - alerts: %+v", *origin.Cachegroup, err, p.Alerts) + } + if len(p.Response) == 0 { + return fmt.Errorf("no Cache Group named '%s'", *origin.Cachegroup) + } + opts.QueryParameters.Del("name") + origin.CachegroupID = p.Response[0].ID + } + + if origin.DeliveryServiceID == nil && origin.DeliveryService != nil { + opts.QueryParameters.Set("xmlId", *origin.DeliveryService) + dses, _, err := to.GetDeliveryServices(opts) + if err != nil { + return fmt.Errorf("resolving Delivery Service XMLID '%s' to an ID: %w - alerts: %+v", *origin.DeliveryService, err, dses.Alerts) + } + if len(dses.Response) == 0 { + return fmt.Errorf("no Delivery Service with XMLID '%s'", *origin.DeliveryService) + } + opts.QueryParameters.Del("xmlId") + origin.DeliveryServiceID = dses.Response[0].ID + } + + if origin.ProfileID == nil && origin.Profile != nil { + opts.QueryParameters.Set("name", *origin.Profile) + profiles, _, err := to.GetProfiles(opts) + if err != nil { + return fmt.Errorf("resolving Profile name '%s' to an ID: %w - alerts: %+v", *origin.Profile, err, profiles.Alerts) + } + if len(profiles.Response) == 0 { + return errors.New("no profile with name " + *origin.Profile) + } + origin.ProfileID = &profiles.Response[0].ID + } + + if origin.CoordinateID == nil && origin.Coordinate != nil { + opts.QueryParameters.Set("name", *origin.Coordinate) + coordinates, _, err := to.GetCoordinates(opts) + if err != nil { + return fmt.Errorf("resolving Coordinates name '%s' to an ID: %w - alerts: %+v", *origin.Coordinate, err, coordinates.Alerts) + } + if len(coordinates.Response) == 0 { + return fmt.Errorf("no coordinate with name '%s'", *origin.Coordinate) + } + origin.CoordinateID = &coordinates.Response[0].ID + } + + if origin.TenantID == nil && origin.Tenant != nil { + opts.QueryParameters.Set("name", *origin.Tenant) + tenant, _, err := to.GetTenants(opts) + if err != nil { + return fmt.Errorf("resolving Tenant name '%s' to an ID: %w - alerts: %+v", *origin.Tenant, err, tenant.Alerts) + } + if len(tenant.Response) == 0 { + return fmt.Errorf("no Tenant with name '%s'", *origin.Tenant) + } + origin.TenantID = &tenant.Response[0].ID + } + + return nil +} + +// CreateOrigin creates the given Origin. +func (to *Session) CreateOrigin(origin tc.Origin, opts RequestOptions) (tc.OriginDetailResponse, toclientlib.ReqInf, error) { + var originResp tc.OriginDetailResponse + var remoteAddr net.Addr + reqInf := toclientlib.ReqInf{CacheHitStatus: toclientlib.CacheHitStatusMiss, RemoteAddr: remoteAddr} + + err := to.originIDs(&origin) + if err != nil { + return originResp, reqInf, err + } + reqInf, err = to.post(apiOrigins, opts, origin, &originResp) + return originResp, reqInf, err +} + +// UpdateOrigin replaces the Origin identified by 'id' with the passed Origin. +func (to *Session) UpdateOrigin(id int, origin tc.Origin, opts RequestOptions) (tc.OriginDetailResponse, toclientlib.ReqInf, error) { + var originResp tc.OriginDetailResponse + var remoteAddr net.Addr + reqInf := toclientlib.ReqInf{CacheHitStatus: toclientlib.CacheHitStatusMiss, RemoteAddr: remoteAddr} + + err := to.originIDs(&origin) + if err != nil { + return originResp, reqInf, err + } + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("id", strconv.Itoa(id)) + reqInf, err = to.put(apiOrigins, opts, origin, &originResp) + return originResp, reqInf, err +} + +// GetOrigins retrieves Origins from Traffic Ops. +func (to *Session) GetOrigins(opts RequestOptions) (tc.OriginsResponse, toclientlib.ReqInf, error) { + var data tc.OriginsResponse + reqInf, err := to.get(apiOrigins, opts, &data) + return data, reqInf, err +} + +// DeleteOrigin deletes the Origin with the given ID. +func (to *Session) DeleteOrigin(id int, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("id", strconv.Itoa(id)) + var alerts tc.Alerts + reqInf, err := to.del(apiOrigins, opts, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/parameter.go b/traffic_ops/v5-client/parameter.go new file mode 100644 index 0000000000..288648a29f --- /dev/null +++ b/traffic_ops/v5-client/parameter.go @@ -0,0 +1,64 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "fmt" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiParameters is the full path to the /parameters API endpoint. +const apiParameters = "/parameters" + +// CreateParameter performs a POST to create a Parameter. +func (to *Session) CreateParameter(pl tc.Parameter, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(apiParameters, opts, pl, &alerts) + return alerts, reqInf, err +} + +// CreateMultipleParameters performs a POST to create multiple Parameters at once. +func (to *Session) CreateMultipleParameters(pls []tc.Parameter, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(apiParameters, opts, pls, &alerts) + return alerts, reqInf, err +} + +// UpdateParameter replaces the Parameter identified by 'id' with the one +// provided. +func (to *Session) UpdateParameter(id int, pl tc.Parameter, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + route := fmt.Sprintf("%s/%d", apiParameters, id) + var alerts tc.Alerts + reqInf, err := to.put(route, opts, pl, &alerts) + return alerts, reqInf, err +} + +// GetParameters returns all Parameters in Traffic Ops. +func (to *Session) GetParameters(opts RequestOptions) (tc.ParametersResponse, toclientlib.ReqInf, error) { + var data tc.ParametersResponse + reqInf, err := to.get(apiParameters, opts, &data) + return data, reqInf, err +} + +// DeleteParameter deletes the Parameter with the given ID. +func (to *Session) DeleteParameter(id int, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + URI := fmt.Sprintf("%s/%d", apiParameters, id) + var alerts tc.Alerts + reqInf, err := to.del(URI, opts, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/phys_location.go b/traffic_ops/v5-client/phys_location.go new file mode 100644 index 0000000000..c9c94f3f76 --- /dev/null +++ b/traffic_ops/v5-client/phys_location.go @@ -0,0 +1,70 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "fmt" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiPhysLocations is the full path to the /phys_locations API route. +const apiPhysLocations = "/phys_locations" + +// CreatePhysLocation creates the passed Physical Location. +func (to *Session) CreatePhysLocation(pl tc.PhysLocation, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + if pl.RegionID == 0 && pl.RegionName != "" { + regionOpts := NewRequestOptions() + regionOpts.QueryParameters.Set("name", pl.RegionName) + regions, reqInf, err := to.GetRegions(regionOpts) + if err != nil { + err = fmt.Errorf("resolving Region name '%s' to an ID", pl.RegionName) + return regions.Alerts, reqInf, err + } + if len(regions.Response) == 0 { + return regions.Alerts, reqInf, fmt.Errorf("no region with name '%s'", pl.RegionName) + } + pl.RegionID = regions.Response[0].ID + } + var alerts tc.Alerts + reqInf, err := to.post(apiPhysLocations, opts, pl, &alerts) + return alerts, reqInf, err +} + +// UpdatePhysLocation replaces the Physical Location identified by 'id' with +// the given Physical Location structure. +func (to *Session) UpdatePhysLocation(id int, pl tc.PhysLocation, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + route := fmt.Sprintf("%s/%d", apiPhysLocations, id) + var alerts tc.Alerts + reqInf, err := to.put(route, opts, pl, &alerts) + return alerts, reqInf, err +} + +// GetPhysLocations retrieves Physical Locations from Traffic Ops. +func (to *Session) GetPhysLocations(opts RequestOptions) (tc.PhysLocationsResponse, toclientlib.ReqInf, error) { + var data tc.PhysLocationsResponse + reqInf, err := to.get(apiPhysLocations, opts, &data) + return data, reqInf, err +} + +// DeletePhysLocation deletes the Physical Location with the given ID. +func (to *Session) DeletePhysLocation(id int, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + route := fmt.Sprintf("%s/%d", apiPhysLocations, id) + var alerts tc.Alerts + reqInf, err := to.del(route, opts, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/ping.go b/traffic_ops/v5-client/ping.go new file mode 100644 index 0000000000..40bc9843cf --- /dev/null +++ b/traffic_ops/v5-client/ping.go @@ -0,0 +1,38 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiPing is the full path to the /ping API endpoint. +const apiPing = "/ping" + +// PingResponse is the type of a response from Traffic Ops to a requestt made +// to its /ping API endpoint. +type PingResponse struct { + Ping string `json:"ping"` + tc.Alerts +} + +// Ping returns a simple response to show that Traffic Ops is responsive. +func (to *Session) Ping(opts RequestOptions) (PingResponse, toclientlib.ReqInf, error) { + var data PingResponse + reqInf, err := to.get(apiPing, opts, &data) + return data, reqInf, err +} diff --git a/traffic_ops/v5-client/profile.go b/traffic_ops/v5-client/profile.go new file mode 100644 index 0000000000..efc80277f0 --- /dev/null +++ b/traffic_ops/v5-client/profile.go @@ -0,0 +1,117 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "fmt" + "net/url" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +const ( + // apiProfiles is the full path to the /profiles API endpoint. + apiProfiles = "/profiles" + // apiProfilesNameParameters is the full path to the + // /profiles/name/{{name}}/parameters API endpoint. + apiProfilesNameParameters = apiProfiles + "/name/%s/parameters" +) + +// CreateProfile creates the passed Profile. +func (to *Session) CreateProfile(pl tc.Profile, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + if pl.CDNID == 0 && pl.CDNName != "" { + cdnOpts := NewRequestOptions() + cdnOpts.QueryParameters.Set("name", pl.CDNName) + cdns, _, err := to.GetCDNs(cdnOpts) + if err != nil { + return tc.Alerts{}, toclientlib.ReqInf{}, fmt.Errorf("resolving Profile's CDN Name '%s' to an ID: %w", pl.CDNName, err) + } + if len(cdns.Response) == 0 { + return tc.Alerts{ + Alerts: []tc.Alert{ + { + Text: fmt.Sprintf("no CDN with name %s", pl.CDNName), + Level: "error", + }, + }, + }, + toclientlib.ReqInf{}, + fmt.Errorf("no CDN with name %s", pl.CDNName) + } + pl.CDNID = cdns.Response[0].ID + } + + var alerts tc.Alerts + reqInf, err := to.post(apiProfiles, opts, pl, &alerts) + return alerts, reqInf, err +} + +// UpdateProfile replaces the Profile identified by ID with the one provided. +func (to *Session) UpdateProfile(id int, pl tc.Profile, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + route := fmt.Sprintf("%s/%d", apiProfiles, id) + var alerts tc.Alerts + reqInf, err := to.put(route, opts, pl, &alerts) + return alerts, reqInf, err +} + +// GetParametersByProfileName returns all of the Parameters that are assigned +// to the Profile with the given Name. +func (to *Session) GetParametersByProfileName(profileName string, opts RequestOptions) (tc.ParametersResponse, toclientlib.ReqInf, error) { + route := fmt.Sprintf(apiProfilesNameParameters, profileName) + var data tc.ParametersResponse + reqInf, err := to.get(route, opts, &data) + return data, reqInf, err +} + +// GetProfiles returns all Profiles stored in Traffic Ops. +func (to *Session) GetProfiles(opts RequestOptions) (tc.ProfilesResponse, toclientlib.ReqInf, error) { + var data tc.ProfilesResponse + reqInf, err := to.get(apiProfiles, opts, &data) + return data, reqInf, err +} + +// DeleteProfile deletes the Profile with the given ID. +func (to *Session) DeleteProfile(id int, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + URI := fmt.Sprintf("%s/%d", apiProfiles, id) + var alerts tc.Alerts + reqInf, err := to.del(URI, opts, &alerts) + return alerts, reqInf, err +} + +// ExportProfile Returns an exported Profile. +func (to *Session) ExportProfile(id int, opts RequestOptions) (tc.ProfileExportResponse, toclientlib.ReqInf, error) { + route := fmt.Sprintf("%s/%d/export", apiProfiles, id) + var data tc.ProfileExportResponse + reqInf, err := to.get(route, opts, &data) + return data, reqInf, err +} + +// ImportProfile imports an exported Profile. +func (to *Session) ImportProfile(importRequest tc.ProfileImportRequest, opts RequestOptions) (tc.ProfileImportResponse, toclientlib.ReqInf, error) { + route := fmt.Sprintf("%s/import", apiProfiles) + var data tc.ProfileImportResponse + reqInf, err := to.post(route, opts, importRequest, &data) + return data, reqInf, err +} + +// CopyProfile creates a new profile from an existing profile. +func (to *Session) CopyProfile(p tc.ProfileCopy, opts RequestOptions) (tc.ProfileCopyResponse, toclientlib.ReqInf, error) { + path := fmt.Sprintf("%s/name/%s/copy/%s", apiProfiles, url.PathEscape(p.Name), url.PathEscape(p.ExistingName)) + var resp tc.ProfileCopyResponse + reqInf, err := to.post(path, opts, p, &resp) + return resp, reqInf, err +} diff --git a/traffic_ops/v5-client/profile_parameter.go b/traffic_ops/v5-client/profile_parameter.go new file mode 100644 index 0000000000..e0adfcbb6e --- /dev/null +++ b/traffic_ops/v5-client/profile_parameter.go @@ -0,0 +1,72 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "fmt" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiProfileParameters is the full path to the /profileparameters API endpoint. +const apiProfileParameters = "/profileparameters" +const apiProfileParameter = "/profileparameter" + +// Supported query string parameter names. +const ( + ProfileIDQueryParam = "profileId" + ParameterIDQueryParam = "parameterId" +) + +// CreateProfileParameter assigns a Parameter to a Profile. +func (to *Session) CreateProfileParameter(pp tc.ProfileParameterCreationRequest, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(apiProfileParameters, opts, pp, &alerts) + return alerts, reqInf, err +} + +// CreateMultipleProfileParameters assigns multip Parameters to one or more +// Profiles at once. +func (to *Session) CreateMultipleProfileParameters(pps []tc.ProfileParameterCreationRequest, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(apiProfileParameters, opts, pps, &alerts) + return alerts, reqInf, err +} + +// CreateProfileWithMultipleParameters assigns multipe Parameters to one or more +// Profiles at once. +func (to *Session) CreateProfileWithMultipleParameters(pps tc.PostProfileParam, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(apiProfileParameter, opts, pps, &alerts) + return alerts, reqInf, err +} + +// GetProfileParameters retrieves associations between Profiles and Parameters. +func (to *Session) GetProfileParameters(opts RequestOptions) (tc.ProfileParametersAPIResponse, toclientlib.ReqInf, error) { + var data tc.ProfileParametersAPIResponse + reqInf, err := to.get(apiProfileParameters, opts, &data) + return data, reqInf, err +} + +// DeleteProfileParameter removes the Parameter with the ID 'parameter' from +// the Profile identified by the ID 'profile'. +func (to *Session) DeleteProfileParameter(profile int, parameter int, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + URI := fmt.Sprintf("%s/%d/%d", apiProfileParameters, profile, parameter) + var alerts tc.Alerts + reqInf, err := to.del(URI, opts, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/region.go b/traffic_ops/v5-client/region.go new file mode 100644 index 0000000000..7ef85216bf --- /dev/null +++ b/traffic_ops/v5-client/region.go @@ -0,0 +1,79 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "errors" + "fmt" + "net/url" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiRegions is the API version-relative path to the /regions API endpoint. +const apiRegions = "/regions" + +// CreateRegion creates the given Region. +func (to *Session) CreateRegion(region tc.Region, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + if region.Division == 0 && region.DivisionName != "" { + divisionOpts := NewRequestOptions() + divisionOpts.QueryParameters.Set("name", region.DivisionName) + divisions, reqInf, err := to.GetDivisions(divisionOpts) + if err != nil { + return divisions.Alerts, reqInf, err + } + if len(divisions.Response) == 0 { + return divisions.Alerts, reqInf, errors.New("no division with name " + region.DivisionName) + } + region.Division = divisions.Response[0].ID + } + var alerts tc.Alerts + reqInf, err := to.post(apiRegions, opts, region, &alerts) + return alerts, reqInf, err +} + +// UpdateRegion replaces the Region identified by ID with the one provided. +func (to *Session) UpdateRegion(id int, region tc.Region, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + route := fmt.Sprintf("%s/%d", apiRegions, id) + var alerts tc.Alerts + reqInf, err := to.put(route, opts, region, &alerts) + return alerts, reqInf, err +} + +// GetRegions returns all Regions in Traffic Ops. +func (to *Session) GetRegions(opts RequestOptions) (tc.RegionsResponse, toclientlib.ReqInf, error) { + var data tc.RegionsResponse + reqInf, err := to.get(apiRegions, opts, &data) + return data, reqInf, err +} + +// DeleteRegion lets you delete a Region. Regions can be deleted by ID instead +// of by name if the ID is provided in the request options and the name is an +// empty string. +func (to *Session) DeleteRegion(name string, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + + if name != "" || opts.QueryParameters.Get("id") == "" { + opts.QueryParameters.Set("name", name) + } + + var alerts tc.Alerts + reqInf, err := to.del(apiRegions, opts, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/role.go b/traffic_ops/v5-client/role.go new file mode 100644 index 0000000000..cf831f7f1b --- /dev/null +++ b/traffic_ops/v5-client/role.go @@ -0,0 +1,62 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/url" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiRoles is the full path to the /roles API endpoint. +const apiRoles = "/roles" + +// CreateRole creates the given Role. +func (to *Session) CreateRole(role tc.RoleV4, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(apiRoles, opts, role, &alerts) + return alerts, reqInf, err +} + +// UpdateRole replaces the Role identified by 'id' with the one provided. +func (to *Session) UpdateRole(name string, role tc.RoleV4, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("name", name) + var alerts tc.Alerts + reqInf, err := to.put(apiRoles, opts, role, &alerts) + return alerts, reqInf, err +} + +// GetRoles retrieves Roles from Traffic Ops. +func (to *Session) GetRoles(opts RequestOptions) (tc.RolesResponseV4, toclientlib.ReqInf, error) { + var data tc.RolesResponseV4 + reqInf, err := to.get(apiRoles, opts, &data) + return data, reqInf, err +} + +// DeleteRole deletes the Role with the given ID. +func (to *Session) DeleteRole(name string, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("name", name) + var alerts tc.Alerts + reqInf, err := to.del(apiRoles, opts, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/server.go b/traffic_ops/v5-client/server.go new file mode 100644 index 0000000000..ab56c42c47 --- /dev/null +++ b/traffic_ops/v5-client/server.go @@ -0,0 +1,170 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "fmt" + "net" + "net/url" + "strconv" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +const ( + // apiServers is the API version-relative path to the /servers API + // endpoint. + apiServers = "/servers" +) + +func needAndCanFetch(id *int, name *string) bool { + return (id == nil || *id == 0) && name != nil && *name != "" +} + +// CreateServer creates the given Server. +func (to *Session) CreateServer(server tc.ServerV4, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + var remoteAddr net.Addr + reqInf := toclientlib.ReqInf{CacheHitStatus: toclientlib.CacheHitStatusMiss, RemoteAddr: remoteAddr} + + if needAndCanFetch(server.CachegroupID, server.Cachegroup) { + innerOpts := NewRequestOptions() + innerOpts.QueryParameters.Set("name", *server.Cachegroup) + cg, reqInf, err := to.GetCacheGroups(innerOpts) + if err != nil { + return cg.Alerts, reqInf, fmt.Errorf("no cachegroup named %s: %w", *server.Cachegroup, err) + } + if len(cg.Response) == 0 { + return cg.Alerts, reqInf, fmt.Errorf("no cachegroup named %s", *server.Cachegroup) + } + if cg.Response[0].ID == nil { + return cg.Alerts, reqInf, fmt.Errorf("Cachegroup named %s has a nil ID", *server.Cachegroup) + } + server.CachegroupID = cg.Response[0].ID + } + if needAndCanFetch(server.CDNID, server.CDNName) { + innerOpts := NewRequestOptions() + innerOpts.QueryParameters.Set("name", *server.CDNName) + c, reqInf, err := to.GetCDNs(innerOpts) + if err != nil { + return c.Alerts, reqInf, fmt.Errorf("no CDN named %s: %w", *server.CDNName, err) + } + if len(c.Response) == 0 { + return c.Alerts, reqInf, fmt.Errorf("no CDN named %s", *server.CDNName) + } + server.CDNID = &c.Response[0].ID + } + if needAndCanFetch(server.PhysLocationID, server.PhysLocation) { + innerOpts := NewRequestOptions() + innerOpts.QueryParameters.Set("name", *server.PhysLocation) + ph, reqInf, err := to.GetPhysLocations(innerOpts) + if err != nil { + return ph.Alerts, reqInf, fmt.Errorf("no physlocation named %s: %w", *server.PhysLocation, err) + } + if len(ph.Response) == 0 { + return ph.Alerts, reqInf, fmt.Errorf("no physlocation named %s", *server.PhysLocation) + } + server.PhysLocationID = &ph.Response[0].ID + } + if needAndCanFetch(server.StatusID, server.Status) { + innerOpts := NewRequestOptions() + innerOpts.QueryParameters.Set("name", *server.Status) + st, reqInf, err := to.GetStatuses(innerOpts) + if err != nil { + return st.Alerts, reqInf, fmt.Errorf("no Status named %s: %w", *server.Status, err) + } + if len(st.Response) == 0 { + return alerts, reqInf, fmt.Errorf("no Status named %s", *server.Status) + } + server.StatusID = &st.Response[0].ID + } + if (server.TypeID == nil || *server.TypeID == 0) && server.Type != "" { + innerOpts := NewRequestOptions() + innerOpts.QueryParameters.Set("name", server.Type) + ty, _, err := to.GetTypes(innerOpts) + if err != nil { + return ty.Alerts, reqInf, fmt.Errorf("no Type named '%s': %w", server.Type, err) + } + if len(ty.Response) == 0 { + return ty.Alerts, reqInf, fmt.Errorf("no type named %s", server.Type) + } + server.TypeID = &ty.Response[0].ID + } + + reqInf, err := to.post(apiServers, opts, server, &alerts) + return alerts, reqInf, err +} + +// UpdateServer replaces the Server identified by ID with the provided one. +func (to *Session) UpdateServer(id int, server tc.ServerV4, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + route := fmt.Sprintf("%s/%d", apiServers, id) + reqInf, err := to.put(route, opts, server, &alerts) + return alerts, reqInf, err +} + +// GetServers retrieves Servers from Traffic Ops. +func (to *Session) GetServers(opts RequestOptions) (tc.ServersV4Response, toclientlib.ReqInf, error) { + var data tc.ServersV4Response + reqInf, err := to.get(apiServers, opts, &data) + return data, reqInf, err +} + +// DeleteServer deletes the Server with the given ID. +func (to *Session) DeleteServer(id int, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + route := fmt.Sprintf("%s/%d", apiServers, id) + var alerts tc.Alerts + reqInf, err := to.del(route, opts, &alerts) + return alerts, reqInf, err +} + +// AssignDeliveryServiceIDsToServerID assigns a set of Delivery Services to a +// single server, optionally replacing any and all existing assignments. +// 'server' should be the requested server's ID, 'dsIDs' should be a slice of +// the requested Delivery Services' IDs. If 'replace' is 'true', existing +// assignments to the server will be replaced. +func (to *Session) AssignDeliveryServiceIDsToServerID(server int, dsIDs []int, replace bool, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + // datatypes here match the library tc.Server's and tc.DeliveryService's ID fields + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("replace", strconv.FormatBool(replace)) + + endpoint := fmt.Sprintf(apiServerDeliveryServices, server) + + var alerts tc.Alerts + reqInf, err := to.post(endpoint, opts, dsIDs, &alerts) + return alerts, reqInf, err +} + +// GetServerIDDeliveryServices returns all of the Delivery Services assigned to the server identified +// by the integral, unique identifier 'server'. +func (to *Session) GetServerIDDeliveryServices(server int, opts RequestOptions) (tc.DeliveryServicesResponseV4, toclientlib.ReqInf, error) { + endpoint := fmt.Sprintf(apiServerDeliveryServices, server) + var data tc.DeliveryServicesResponseV4 + reqInf, err := to.get(endpoint, opts, &data) + return data, reqInf, err +} + +// GetServerUpdateStatus retrieves the Server Update Status of the Server with +// the given (short) hostname. +func (to *Session) GetServerUpdateStatus(hostName string, opts RequestOptions) (tc.ServerUpdateStatusResponseV4, toclientlib.ReqInf, error) { + path := apiServers + `/` + url.PathEscape(hostName) + `/update_status` + var data tc.ServerUpdateStatusResponseV4 + reqInf, err := to.get(path, opts, &data) + return data, reqInf, err +} diff --git a/traffic_ops/v5-client/server_server_capabilities.go b/traffic_ops/v5-client/server_server_capabilities.go new file mode 100644 index 0000000000..dd17238517 --- /dev/null +++ b/traffic_ops/v5-client/server_server_capabilities.go @@ -0,0 +1,65 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/url" + "strconv" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiServerServerCapabilities is the API version-relative path to the +// /server_server_capabilities API endpoint. +const apiServerServerCapabilities = "/server_server_capabilities" + +// apiMultipleServerCapabilities is the API version-relative path to the /multiple_server_capabilities API endpoint. +const apiMultipleServerCapabilities = "/multiple_server_capabilities" + +// CreateServerServerCapability assigns a Server Capability to a Server. +func (to *Session) CreateServerServerCapability(ssc tc.ServerServerCapability, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(apiServerServerCapabilities, opts, ssc, &alerts) + return alerts, reqInf, err +} + +// DeleteServerServerCapability unassigns a Server Capability from a Server. +func (to *Session) DeleteServerServerCapability(serverID int, serverCapability string, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("serverId", strconv.Itoa(serverID)) + opts.QueryParameters.Set("serverCapability", serverCapability) + var alerts tc.Alerts + reqInf, err := to.del(apiServerServerCapabilities, opts, &alerts) + return alerts, reqInf, err +} + +// GetServerServerCapabilities retrieves a list of Server Capabilities that are +// assigned to Servers. +func (to *Session) GetServerServerCapabilities(opts RequestOptions) (tc.ServerServerCapabilitiesResponse, toclientlib.ReqInf, error) { + var resp tc.ServerServerCapabilitiesResponse + reqInf, err := to.get(apiServerServerCapabilities, opts, &resp) + return resp, reqInf, err +} + +// AssignMultipleServerCapability assigns multiple server capabilities to a server. +func (to *Session) AssignMultipleServerCapability(msc tc.MultipleServerCapabilities, opts RequestOptions, id int) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.put(apiMultipleServerCapabilities, opts, msc, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/server_update_status.go b/traffic_ops/v5-client/server_update_status.go new file mode 100644 index 0000000000..b61223dd74 --- /dev/null +++ b/traffic_ops/v5-client/server_update_status.go @@ -0,0 +1,79 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "errors" + "fmt" + "net/url" + "time" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// UpdateServerStatus updates the Status of the server identified by +// 'serverID'. +func (to *Session) UpdateServerStatus(serverID int, req tc.ServerPutStatus, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + path := fmt.Sprintf("servers/%d/status", serverID) + var alerts tc.Alerts + reqInf, err := to.put(path, opts, req, &alerts) + return alerts, reqInf, err +} + +var queueUpdateActions = map[bool]string{ + false: "dequeue", + true: "queue", +} + +// SetServerQueueUpdate set the "updPending" field of th eserver identified by +// 'serverID' to the value of 'queueUpdate - and properly queues updates on +// parents/children as necessary. +func (to *Session) SetServerQueueUpdate(serverID int, queueUpdate bool, opts RequestOptions) (tc.ServerQueueUpdateResponse, toclientlib.ReqInf, error) { + req := tc.ServerQueueUpdateRequest{Action: queueUpdateActions[queueUpdate]} + var resp tc.ServerQueueUpdateResponse + path := fmt.Sprintf("/servers/%d/queue_update", serverID) + reqInf, err := to.post(path, opts, req, &resp) + return resp, reqInf, err +} + +// SetUpdateServerStatusTimes updates a server's config queue status and/or reval status. +// Each argument individually is optional, however at least one argument must not be nil. +func (to *Session) SetUpdateServerStatusTimes(serverName string, configApplyTime, revalApplyTime *time.Time, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + reqInf := toclientlib.ReqInf{CacheHitStatus: toclientlib.CacheHitStatusMiss} + var alerts tc.Alerts + + if configApplyTime == nil && revalApplyTime == nil { + return alerts, reqInf, errors.New("one must be non-nil (configApplyTime, revalApplyTime); nothing to do") + } + + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + + if configApplyTime != nil { + cat := configApplyTime.Format(time.RFC3339Nano) + opts.QueryParameters.Set("config_apply_time", cat) + } + if revalApplyTime != nil { + rat := revalApplyTime.Format(time.RFC3339Nano) + opts.QueryParameters.Set("revalidate_apply_time", rat) + } + + path := `/servers/` + url.PathEscape(serverName) + `/update` + reqInf, err := to.post(path, opts, nil, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/servercapability.go b/traffic_ops/v5-client/servercapability.go new file mode 100644 index 0000000000..511b42e52c --- /dev/null +++ b/traffic_ops/v5-client/servercapability.go @@ -0,0 +1,63 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/url" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiServerCapabilities is the full path to the /server_capabilities API +// endpoint. +const apiServerCapabilities = "/server_capabilities" + +// CreateServerCapability creates the given Server Capability. +func (to *Session) CreateServerCapability(sc tc.ServerCapability, opts RequestOptions) (tc.ServerCapabilityDetailResponse, toclientlib.ReqInf, error) { + var scResp tc.ServerCapabilityDetailResponse + reqInf, err := to.post(apiServerCapabilities, opts, sc, &scResp) + return scResp, reqInf, err +} + +// GetServerCapabilities returns all the Server Capabilities in Traffic Ops. +func (to *Session) GetServerCapabilities(opts RequestOptions) (tc.ServerCapabilitiesResponse, toclientlib.ReqInf, error) { + var data tc.ServerCapabilitiesResponse + reqInf, err := to.get(apiServerCapabilities, opts, &data) + return data, reqInf, err +} + +// UpdateServerCapability updates a Server Capability by name. +func (to *Session) UpdateServerCapability(name string, sc tc.ServerCapability, opts RequestOptions) (tc.ServerCapabilityDetailResponse, toclientlib.ReqInf, error) { + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("name", name) + var data tc.ServerCapabilityDetailResponse + reqInf, err := to.put(apiServerCapabilities, opts, sc, &data) + return data, reqInf, err +} + +// DeleteServerCapability deletes the given server capability by name. +func (to *Session) DeleteServerCapability(name string, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("name", name) + var alerts tc.Alerts + reqInf, err := to.del(apiServerCapabilities, opts, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/servercheck.go b/traffic_ops/v5-client/servercheck.go new file mode 100644 index 0000000000..9a429aff73 --- /dev/null +++ b/traffic_ops/v5-client/servercheck.go @@ -0,0 +1,40 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiServercheck is the API version-relative path to the /servercheck API endpoint. +const apiServercheck = "/servercheck" + +// InsertServerCheckStatus will insert/update the Servercheck value based on if +// it already exists or not. +func (to *Session) InsertServerCheckStatus(status tc.ServercheckRequestNullable, opts RequestOptions) (tc.ServercheckPostResponse, toclientlib.ReqInf, error) { + var resp tc.ServercheckPostResponse + reqInf, err := to.post(apiServercheck, opts, status, &resp) + return resp, reqInf, err +} + +// GetServersChecks fetches check and meta information about servers from +// /servercheck. +func (to *Session) GetServersChecks(opts RequestOptions) (tc.ServercheckAPIResponse, toclientlib.ReqInf, error) { + var response tc.ServercheckAPIResponse + reqInf, err := to.get(apiServercheck, opts, &response) + return response, reqInf, err +} diff --git a/traffic_ops/v5-client/servercheckextensions.go b/traffic_ops/v5-client/servercheckextensions.go new file mode 100644 index 0000000000..205f11e218 --- /dev/null +++ b/traffic_ops/v5-client/servercheckextensions.go @@ -0,0 +1,47 @@ +package client + +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "fmt" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiTOExtension is the API version-relative path to the /servercheck/extensions API +// endpoint. +const apiTOExtension = "/servercheck/extensions" + +// CreateServerCheckExtension creates the given Servercheck Extension. +func (to *Session) CreateServerCheckExtension(serverCheckExtension tc.ServerCheckExtensionNullable, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(apiTOExtension, opts, serverCheckExtension, &alerts) + return alerts, reqInf, err +} + +// DeleteServerCheckExtension deletes the Servercheck Extension identified by +// 'id'. +func (to *Session) DeleteServerCheckExtension(id int, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + URI := fmt.Sprintf("%s/%d", apiTOExtension, id) + var alerts tc.Alerts + reqInf, err := to.del(URI, opts, &alerts) + return alerts, reqInf, err +} + +// GetServerCheckExtensions gets all Servercheck Extensions in Traffic Ops. +func (to *Session) GetServerCheckExtensions(opts RequestOptions) (tc.ServerCheckExtensionResponse, toclientlib.ReqInf, error) { + var toExtResp tc.ServerCheckExtensionResponse + reqInf, err := to.get(apiTOExtension, opts, &toExtResp) + return toExtResp, reqInf, err +} diff --git a/traffic_ops/v5-client/serviceCategory.go b/traffic_ops/v5-client/serviceCategory.go new file mode 100644 index 0000000000..31cb964040 --- /dev/null +++ b/traffic_ops/v5-client/serviceCategory.go @@ -0,0 +1,59 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "fmt" + "net/url" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiServiceCategories is the API version-relative path to the /service_categories API +// endpoints. +const apiServiceCategories = "/service_categories" + +// CreateServiceCategory creates the given Service Category. +func (to *Session) CreateServiceCategory(serviceCategory tc.ServiceCategory, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(apiServiceCategories, opts, serviceCategory, &alerts) + return alerts, reqInf, err +} + +// UpdateServiceCategory replaces the Service Category with the given Name with +// the one provided. +func (to *Session) UpdateServiceCategory(name string, serviceCategory tc.ServiceCategory, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + route := fmt.Sprintf("%s/%s", apiServiceCategories, url.PathEscape(name)) + var alerts tc.Alerts + reqInf, err := to.put(route, opts, serviceCategory, &alerts) + return alerts, reqInf, err +} + +// GetServiceCategories retrieves Service Categories from Traffic Ops. +func (to *Session) GetServiceCategories(opts RequestOptions) (tc.ServiceCategoriesResponse, toclientlib.ReqInf, error) { + var data tc.ServiceCategoriesResponse + reqInf, err := to.get(apiServiceCategories, opts, &data) + return data, reqInf, err +} + +// DeleteServiceCategory deletes the Service Category with the given Name. +func (to *Session) DeleteServiceCategory(name string, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + route := fmt.Sprintf("%s/%s", apiServiceCategories, url.PathEscape(name)) + var alerts tc.Alerts + reqInf, err := to.del(route, opts, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/session.go b/traffic_ops/v5-client/session.go new file mode 100644 index 0000000000..1072281660 --- /dev/null +++ b/traffic_ops/v5-client/session.go @@ -0,0 +1,147 @@ +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +// Package client provides Go bindings to the Traffic Ops RPC API. +package client + +import ( + "net" + "net/http" + "net/url" + "time" + + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// RequestOptions is the set of options commonly available to pass to methods +// of a Session. +type RequestOptions struct { + // Any and all extra HTTP headers to pass in the request. + Header http.Header + // Any and all query parameters to pass in the request. + QueryParameters url.Values +} + +// NewRequestOptions returns a RequestOptions object with initialized, empty Header +// and QueryParameters. +func NewRequestOptions() RequestOptions { + return RequestOptions{ + Header: http.Header{}, + QueryParameters: url.Values{}, + } +} + +// Login authenticates with Traffic Ops and returns the client object. +// +// Returns the logged in client, the remote address of Traffic Ops which was translated and used to log in, and any error. If the error is not nil, the remote address may or may not be nil, depending whether the error occurred before the login request. +// +// See ClientOpts for details about options, which options are required, and how they behave. +func Login(url, user, pass string, opts Options) (*Session, toclientlib.ReqInf, error) { + cl, reqInf, err := toclientlib.Login(url, user, pass, opts.ClientOpts, apiVersions()) + if err != nil { + return nil, reqInf, err + } + return &Session{TOClient: *cl}, reqInf, err +} + +// Options is the options to configure the creation of the Client. +// +// This exists to allow adding new features without a breaking change to the +// Login function. Users should understand this, and understand that upgrading +// their library may result in new options that their application doesn't know +// to use. New fields should always behave as-before if their value is the +// default. +type Options struct { + toclientlib.ClientOpts +} + +// Session is a Traffic Ops client. +type Session struct { + toclientlib.TOClient +} + +// NewSession constructs a new, unauthenticated Session using the provided information. +func NewSession(user, password, url, userAgent string, client *http.Client, useCache bool) *Session { + return &Session{ + TOClient: *toclientlib.NewClient(user, password, url, userAgent, client, apiVersions()), + } +} + +// LoginWithAgent creates a new authenticated session with a Traffic Ops +// server. The session cookie should be set automatically in the returned +// Session, so that subsequent calls are properly authenticated without further +// manual intervention. +// +// Returns the logged in client, the remote address of Traffic Ops which was +// translated and used to log in, and any error that occurred along the way. If +// the error is not nil, the remote address may or may not be nil, depending on +// whether the error occurred before the login request. +func LoginWithAgent(toURL string, toUser string, toPasswd string, insecure bool, userAgent string, useCache bool, requestTimeout time.Duration) (*Session, net.Addr, error) { + cl, ip, err := toclientlib.LoginWithAgent(toURL, toUser, toPasswd, insecure, userAgent, requestTimeout, apiVersions()) + if err != nil { + return nil, nil, err + } + return &Session{TOClient: *cl}, ip, err +} + +// LoginWithToken functions identically to LoginWithAgent, but uses token-based +// authentication rather than a username/password pair. +func LoginWithToken(toURL string, token string, insecure bool, userAgent string, useCache bool, requestTimeout time.Duration) (*Session, net.Addr, error) { + cl, ip, err := toclientlib.LoginWithToken(toURL, token, insecure, userAgent, requestTimeout, apiVersions()) + if err != nil { + return nil, nil, err + } + return &Session{TOClient: *cl}, ip, err +} + +// LogoutWithAgent constructs an authenticated Session - exactly like +// LoginWithAgent - and then immediately calls the '/logout' API endpoint to +// end the session. +func LogoutWithAgent(toURL string, toUser string, toPasswd string, insecure bool, userAgent string, useCache bool, requestTimeout time.Duration) (*Session, net.Addr, error) { + cl, ip, err := toclientlib.LogoutWithAgent(toURL, toUser, toPasswd, insecure, userAgent, requestTimeout, apiVersions()) + if err != nil { + return nil, nil, err + } + return &Session{TOClient: *cl}, ip, err +} + +// NewNoAuthSession returns a new Session without logging in. +// this can be used for querying unauthenticated endpoints without requiring a login. +func NewNoAuthSession(toURL string, insecure bool, userAgent string, useCache bool, requestTimeout time.Duration) *Session { + return &Session{TOClient: *toclientlib.NewNoAuthClient(toURL, insecure, userAgent, requestTimeout, apiVersions())} +} + +func (to *Session) get(path string, opts RequestOptions, response interface{}) (toclientlib.ReqInf, error) { + return to.req(http.MethodGet, path, opts, nil, response) +} + +func (to *Session) post(path string, opts RequestOptions, body, response interface{}) (toclientlib.ReqInf, error) { + return to.req(http.MethodPost, path, opts, body, response) +} + +func (to *Session) put(path string, opts RequestOptions, body, response interface{}) (toclientlib.ReqInf, error) { + return to.req(http.MethodPut, path, opts, body, response) +} + +func (to *Session) del(path string, opts RequestOptions, response interface{}) (toclientlib.ReqInf, error) { + return to.req(http.MethodDelete, path, opts, nil, response) +} + +func (to *Session) req(method, path string, opts RequestOptions, body, response interface{}) (toclientlib.ReqInf, error) { + if len(opts.QueryParameters) > 0 { + path += "?" + opts.QueryParameters.Encode() + } + return to.TOClient.Req(method, path, body, opts.Header, response) +} diff --git a/traffic_ops/v5-client/staticdnsentry.go b/traffic_ops/v5-client/staticdnsentry.go new file mode 100644 index 0000000000..dee4fe520d --- /dev/null +++ b/traffic_ops/v5-client/staticdnsentry.go @@ -0,0 +1,128 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "errors" + "net/url" + "strconv" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiStaticDNSEntries is the full path to the /staticdnsentries API +// endpoint. +const apiStaticDNSEntries = "/staticdnsentries" + +func staticDNSEntryIDs(to *Session, sdns *tc.StaticDNSEntry) error { + if sdns == nil { + return errors.New("cannot resolve names to IDs for nil StaticDNSEntry") + } + if sdns.CacheGroupID == 0 && sdns.CacheGroupName != "" { + opts := NewRequestOptions() + opts.QueryParameters.Set("name", sdns.CacheGroupName) + p, _, err := to.GetCacheGroups(opts) + if err != nil { + return err + } + if len(p.Response) == 0 { + return errors.New("no CacheGroup named " + sdns.CacheGroupName) + } + if p.Response[0].ID == nil { + return errors.New("CacheGroup named " + sdns.CacheGroupName + " has a nil ID") + } + sdns.CacheGroupID = *p.Response[0].ID + } + + if sdns.DeliveryServiceID == 0 && sdns.DeliveryService != "" { + opts := NewRequestOptions() + opts.QueryParameters.Set("xmlId", sdns.DeliveryService) + dses, _, err := to.GetDeliveryServices(opts) + if err != nil { + return err + } + if len(dses.Response) == 0 { + return errors.New("no deliveryservice with name " + sdns.DeliveryService) + } + if dses.Response[0].ID == nil { + return errors.New("Deliveryservice with name " + sdns.DeliveryService + " has a nil ID") + } + sdns.DeliveryServiceID = *dses.Response[0].ID + } + + if sdns.TypeID == 0 && sdns.Type != "" { + opts := NewRequestOptions() + opts.QueryParameters.Set("name", sdns.Type) + types, _, err := to.GetTypes(opts) + if err != nil { + return err + } + if len(types.Response) == 0 { + return errors.New("no type with name " + sdns.Type) + } + sdns.TypeID = types.Response[0].ID + } + + return nil +} + +// CreateStaticDNSEntry creates the given Static DNS Entry. +func (to *Session) CreateStaticDNSEntry(sdns tc.StaticDNSEntry, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + // fill in missing IDs from names + var alerts tc.Alerts + err := staticDNSEntryIDs(to, &sdns) + if err != nil { + return alerts, toclientlib.ReqInf{CacheHitStatus: toclientlib.CacheHitStatusMiss}, err + } + reqInf, err := to.post(apiStaticDNSEntries, opts, sdns, &alerts) + return alerts, reqInf, err +} + +// UpdateStaticDNSEntry replaces the Static DNS Entry identified by 'id' with +// the one provided. +func (to *Session) UpdateStaticDNSEntry(id int, sdns tc.StaticDNSEntry, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + // fill in missing IDs from names + var alerts tc.Alerts + err := staticDNSEntryIDs(to, &sdns) + if err != nil { + return alerts, toclientlib.ReqInf{CacheHitStatus: toclientlib.CacheHitStatusMiss}, err + } + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("id", strconv.Itoa(id)) + reqInf, err := to.put(apiStaticDNSEntries, opts, sdns, &alerts) + return alerts, reqInf, err +} + +// GetStaticDNSEntries retrieves all Static DNS Entries stored in Traffic Ops. +func (to *Session) GetStaticDNSEntries(opts RequestOptions) (tc.StaticDNSEntriesResponse, toclientlib.ReqInf, error) { + var data tc.StaticDNSEntriesResponse + reqInf, err := to.get(apiStaticDNSEntries, opts, &data) + return data, reqInf, err +} + +// DeleteStaticDNSEntry deletes the Static DNS Entry with the given ID. +func (to *Session) DeleteStaticDNSEntry(id int, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("id", strconv.Itoa(id)) + var alerts tc.Alerts + reqInf, err := to.del(apiStaticDNSEntries, opts, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/stats_summary.go b/traffic_ops/v5-client/stats_summary.go new file mode 100644 index 0000000000..9b8e9a683d --- /dev/null +++ b/traffic_ops/v5-client/stats_summary.go @@ -0,0 +1,53 @@ +package client + +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/url" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiStatsSummary is the full path to the /stats_summary API endpoint. +const apiStatsSummary = "/stats_summary" + +// GetSummaryStats gets a list of Summary Stats with the ability to filter on +// CDN, Delivery Service, and/or stat name. +func (to *Session) GetSummaryStats(opts RequestOptions) (tc.StatsSummaryResponse, toclientlib.ReqInf, error) { + var resp tc.StatsSummaryResponse + reqInf, err := to.get(apiStatsSummary, opts, &resp) + return resp, reqInf, err +} + +// GetSummaryStatsLastUpdated gets the time at which Stat Summaries were last +// updated. +// If 'statName' isn't nil, the response will be limited to the stat thereby +// named. +func (to *Session) GetSummaryStatsLastUpdated(opts RequestOptions) (tc.StatsSummaryLastUpdatedAPIResponse, toclientlib.ReqInf, error) { + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("lastSummaryDate", "true") + + var resp tc.StatsSummaryLastUpdatedAPIResponse + reqInf, err := to.get(apiStatsSummary, opts, &resp) + return resp, reqInf, err +} + +// CreateSummaryStats creates the given Stats Summary. +func (to *Session) CreateSummaryStats(statsSummary tc.StatsSummary, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(apiStatsSummary, opts, statsSummary, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/status.go b/traffic_ops/v5-client/status.go new file mode 100644 index 0000000000..4da8b778f5 --- /dev/null +++ b/traffic_ops/v5-client/status.go @@ -0,0 +1,56 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "fmt" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiStatuses is the API version-relative path to the /statuses API endpoint. +const apiStatuses = "/statuses" + +// CreateStatus creates the given Status. +func (to *Session) CreateStatus(status tc.StatusNullable, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(apiStatuses, opts, status, &alerts) + return alerts, reqInf, err +} + +// UpdateStatus replaces the Status identified by 'id' with the one provided. +func (to *Session) UpdateStatus(id int, status tc.Status, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + route := fmt.Sprintf("%s/%d", apiStatuses, id) + var alerts tc.Alerts + reqInf, err := to.put(route, opts, status, &alerts) + return alerts, reqInf, err +} + +// GetStatuses retrieves all Statuses stored in Traffic Ops. +func (to *Session) GetStatuses(opts RequestOptions) (tc.StatusesResponse, toclientlib.ReqInf, error) { + var data tc.StatusesResponse + reqInf, err := to.get(apiStatuses, opts, &data) + return data, reqInf, err +} + +// DeleteStatus deletes the Status with the given ID. +func (to *Session) DeleteStatus(id int, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + route := fmt.Sprintf("%s/%d", apiStatuses, id) + var alerts tc.Alerts + reqInf, err := to.del(route, opts, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/steering.go b/traffic_ops/v5-client/steering.go new file mode 100644 index 0000000000..d7c846d58c --- /dev/null +++ b/traffic_ops/v5-client/steering.go @@ -0,0 +1,29 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// Steering retrieves information about all (Tenant-accessible) Steering +// Delivery Services stored in Traffic Ops assigned to the requesting user. +func (to *Session) Steering(opts RequestOptions) (tc.SteeringResponse, toclientlib.ReqInf, error) { + var data tc.SteeringResponse + reqInf, err := to.get(`/steering`, opts, &data) + return data, reqInf, err +} diff --git a/traffic_ops/v5-client/steeringtarget.go b/traffic_ops/v5-client/steeringtarget.go new file mode 100644 index 0000000000..3fbe31736c --- /dev/null +++ b/traffic_ops/v5-client/steeringtarget.go @@ -0,0 +1,71 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "errors" + "fmt" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// CreateSteeringTarget adds the given Steering Target to a Steering Delivery +// Service. +func (to *Session) CreateSteeringTarget(st tc.SteeringTargetNullable, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + if st.DeliveryServiceID == nil { + return tc.Alerts{}, toclientlib.ReqInf{CacheHitStatus: toclientlib.CacheHitStatusMiss}, errors.New("missing delivery service id") + } + alerts := tc.Alerts{} + route := fmt.Sprintf("/steering/%d/targets", *st.DeliveryServiceID) + reqInf, err := to.post(route, opts, st, &alerts) + return alerts, reqInf, err +} + +// UpdateSteeringTarget replaces an existing Steering Target association with +// the newly provided configuration. 'st' must have both a Delivery Service ID +// and a Target ID. +func (to *Session) UpdateSteeringTarget(st tc.SteeringTargetNullable, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + reqInf := toclientlib.ReqInf{CacheHitStatus: toclientlib.CacheHitStatusMiss} + if st.DeliveryServiceID == nil { + return tc.Alerts{}, reqInf, errors.New("missing delivery service id") + } + if st.TargetID == nil { + return tc.Alerts{}, reqInf, errors.New("missing target id") + } + route := fmt.Sprintf("/steering/%d/targets/%d", *st.DeliveryServiceID, *st.TargetID) + alerts := tc.Alerts{} + reqInf, err := to.put(route, opts, st, &alerts) + return alerts, reqInf, err +} + +// GetSteeringTargets retrieves all Targets for the Steering Delivery Service +// with the given ID. +func (to *Session) GetSteeringTargets(dsID int, opts RequestOptions) (tc.SteeringTargetsResponse, toclientlib.ReqInf, error) { + route := fmt.Sprintf("steering/%d/targets", dsID) + var data tc.SteeringTargetsResponse + reqInf, err := to.get(route, opts, &data) + return data, reqInf, err +} + +// DeleteSteeringTarget removes the Target identified by 'targetID' from the +// Delivery Service identified by 'dsID'. +func (to *Session) DeleteSteeringTarget(dsID int, targetID int, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + route := fmt.Sprintf("steering/%d/targets/%d", dsID, targetID) + alerts := tc.Alerts{} + reqInf, err := to.del(route, opts, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/tenant.go b/traffic_ops/v5-client/tenant.go new file mode 100644 index 0000000000..a7fddab8d8 --- /dev/null +++ b/traffic_ops/v5-client/tenant.go @@ -0,0 +1,70 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "fmt" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiTenants is the API version-relative path to the /tenants API endpoint. +const apiTenants = "/tenants" + +// apiTenantID is the API version-relative path to the /tenants/{{ID}} API endpoint. +const apiTenantID = apiTenants + "/%d" + +// GetTenants retrieves all Tenants stored in Traffic Ops. +func (to *Session) GetTenants(opts RequestOptions) (tc.GetTenantsResponse, toclientlib.ReqInf, error) { + var data tc.GetTenantsResponse + reqInf, err := to.get(apiTenants, opts, &data) + return data, reqInf, err +} + +// CreateTenant creates the Tenant it's passed. +func (to *Session) CreateTenant(t tc.Tenant, opts RequestOptions) (tc.TenantResponse, toclientlib.ReqInf, error) { + if t.ParentID == 0 && t.ParentName != "" { + parentOpts := NewRequestOptions() + parentOpts.QueryParameters.Set("name", t.ParentName) + tenant, reqInf, err := to.GetTenants(parentOpts) + if err != nil { + return tc.TenantResponse{Alerts: tenant.Alerts}, reqInf, err + } + if len(tenant.Response) < 1 { + return tc.TenantResponse{Alerts: tenant.Alerts}, reqInf, fmt.Errorf("no Tenant could be found for Parent Tenant '%s'", t.ParentName) + } + t.ParentID = tenant.Response[0].ID + } + + var data tc.TenantResponse + reqInf, err := to.post(apiTenants, opts, t, &data) + return data, reqInf, err +} + +// UpdateTenant replaces the Tenant identified by 'id' with the one provided. +func (to *Session) UpdateTenant(id int, t tc.Tenant, opts RequestOptions) (tc.TenantResponse, toclientlib.ReqInf, error) { + var data tc.TenantResponse + reqInf, err := to.put(fmt.Sprintf(apiTenantID, id), opts, t, &data) + return data, reqInf, err +} + +// DeleteTenant deletes the Tenant matching the ID it's passed. +func (to *Session) DeleteTenant(id int, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var data tc.Alerts + reqInf, err := to.del(fmt.Sprintf(apiTenantID, id), opts, &data) + return data, reqInf, err +} diff --git a/traffic_ops/v5-client/topology.go b/traffic_ops/v5-client/topology.go new file mode 100644 index 0000000000..c543cce3f9 --- /dev/null +++ b/traffic_ops/v5-client/topology.go @@ -0,0 +1,62 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "net/url" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiTopologies is the API version-relative path to the /topologies API endpoint. +const apiTopologies = "/topologies" + +// CreateTopology creates the passed Topology. +func (to *Session) CreateTopology(top tc.Topology, opts RequestOptions) (tc.TopologyResponse, toclientlib.ReqInf, error) { + var resp tc.TopologyResponse + reqInf, err := to.post(apiTopologies, opts, top, &resp) + return resp, reqInf, err +} + +// GetTopologies returns all Topologies stored in Traffic Ops. +func (to *Session) GetTopologies(opts RequestOptions) (tc.TopologiesResponse, toclientlib.ReqInf, error) { + var data tc.TopologiesResponse + reqInf, err := to.get(apiTopologies, opts, &data) + return data, reqInf, err +} + +// UpdateTopology updates a Topology by name. +func (to *Session) UpdateTopology(name string, t tc.Topology, opts RequestOptions) (tc.TopologyResponse, toclientlib.ReqInf, error) { + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("name", name) + var response tc.TopologyResponse + reqInf, err := to.put(apiTopologies, opts, t, &response) + return response, reqInf, err +} + +// DeleteTopology deletes the Topology with the given name. +func (to *Session) DeleteTopology(name string, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + opts.QueryParameters.Set("name", name) + var alerts tc.Alerts + reqInf, err := to.del(apiTopologies, opts, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/topology_queue_updates.go b/traffic_ops/v5-client/topology_queue_updates.go new file mode 100644 index 0000000000..e2235a5a9a --- /dev/null +++ b/traffic_ops/v5-client/topology_queue_updates.go @@ -0,0 +1,36 @@ +package client + +/* + * 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 ( + "fmt" + "net/url" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// TopologiesQueueUpdate queues updates for the Topology with the given Name. +func (to *Session) TopologiesQueueUpdate(topologyName string, req tc.TopologiesQueueUpdateRequest, opts RequestOptions) (tc.TopologiesQueueUpdateResponse, toclientlib.ReqInf, error) { + path := fmt.Sprintf(apiTopologies+"/%s/queue_update", url.PathEscape(topologyName)) + var resp tc.TopologiesQueueUpdateResponse + reqInf, err := to.post(path, opts, req, &resp) + return resp, reqInf, err +} diff --git a/traffic_ops/v5-client/traffic_monitor.go b/traffic_ops/v5-client/traffic_monitor.go new file mode 100644 index 0000000000..b6acc2e152 --- /dev/null +++ b/traffic_ops/v5-client/traffic_monitor.go @@ -0,0 +1,42 @@ +package client + +/* + * 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 ( + "fmt" + "net/url" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiCDNMonitoringConfig is the API path on which Traffic Ops serves the CDN monitoring +// configuration information. It is meant to be used with fmt.Sprintf to insert the necessary +// path parameters (namely the Name of the CDN of interest). +// See Also: https://traffic-control-cdn.readthedocs.io/en/latest/api/v3/cdns_name_configs_monitoring.html +const apiCDNMonitoringConfig = "/cdns/%s/configs/monitoring" + +// GetTrafficMonitorConfig returns the monitoring configuration for the CDN named by 'cdn'. +func (to *Session) GetTrafficMonitorConfig(cdn string, opts RequestOptions) (tc.TMConfigResponse, toclientlib.ReqInf, error) { + route := fmt.Sprintf(apiCDNMonitoringConfig, url.PathEscape(cdn)) + var data tc.TMConfigResponse + reqInf, err := to.get(route, opts, &data) + return data, reqInf, err +} diff --git a/traffic_ops/v5-client/traffic_stats.go b/traffic_ops/v5-client/traffic_stats.go new file mode 100644 index 0000000000..68bf2f7194 --- /dev/null +++ b/traffic_ops/v5-client/traffic_stats.go @@ -0,0 +1,27 @@ +package client + +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// GetCurrentStats gets current stats for each CDNs and a total across them. +func (to *Session) GetCurrentStats(opts RequestOptions) (tc.CurrentStatsResponse, toclientlib.ReqInf, error) { + var resp tc.CurrentStatsResponse + reqInf, err := to.get("/current_stats", opts, &resp) + return resp, reqInf, err +} diff --git a/traffic_ops/v5-client/type.go b/traffic_ops/v5-client/type.go new file mode 100644 index 0000000000..521f1d8a94 --- /dev/null +++ b/traffic_ops/v5-client/type.go @@ -0,0 +1,59 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "fmt" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// apiTypes is the API version-relative path to the /types API endpoint. +const apiTypes = "/types" + +// CreateType creates the given Type. There should be a very good reason for doing this. +func (to *Session) CreateType(typ tc.Type, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.post(apiTypes, opts, typ, &alerts) + return alerts, reqInf, err +} + +// UpdateType replaces the Type identified by 'id' with the one provided. +func (to *Session) UpdateType(id int, typ tc.Type, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + route := fmt.Sprintf("%s/%d", apiTypes, id) + var alerts tc.Alerts + reqInf, err := to.put(route, opts, typ, &alerts) + return alerts, reqInf, err +} + +// GetTypes returns a list of Types, with an http header and 'useInTable' parameters. +// If a 'useInTable' parameter is passed, the returned Types are restricted to those with +// that exact 'useInTable' property. Only exactly 1 or exactly 0 'useInTable' parameters may +// be passed; passing more will result in an error being returned. +func (to *Session) GetTypes(opts RequestOptions) (tc.TypesResponse, toclientlib.ReqInf, error) { + var data tc.TypesResponse + reqInf, err := to.get(apiTypes, opts, &data) + return data, reqInf, err +} + +// DeleteType deletes the Type with the given ID. +func (to *Session) DeleteType(id int, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + route := fmt.Sprintf("%s/%d", apiTypes, id) + var alerts tc.Alerts + reqInf, err := to.del(route, opts, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/user.go b/traffic_ops/v5-client/user.go new file mode 100644 index 0000000000..38b46be17c --- /dev/null +++ b/traffic_ops/v5-client/user.go @@ -0,0 +1,114 @@ +package client + +/* + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "fmt" + "strconv" + + "github.com/apache/trafficcontrol/lib/go-rfc" + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +// UserCurrentResponseV4 is an alias to avoid client breaking changes. In-case of a minor or major version change, we replace the below alias with a new structure. +type UserCurrentResponseV4 = tc.UserCurrentResponseV4 + +// GetUsers retrieves all (Tenant-accessible) Users stored in Traffic Ops. +func (to *Session) GetUsers(opts RequestOptions) (tc.UsersResponseV4, toclientlib.ReqInf, error) { + data := tc.UsersResponseV4{} + route := "/users" + inf, err := to.get(route, opts, &data) + return data, inf, err +} + +// GetUserCurrent retrieves the currently authenticated User. +func (to *Session) GetUserCurrent(opts RequestOptions) (UserCurrentResponseV4, toclientlib.ReqInf, error) { + route := `/user/current` + resp := UserCurrentResponseV4{} + reqInf, err := to.get(route, opts, &resp) + return resp, reqInf, err +} + +// UpdateCurrentUser replaces the current user data with the provided tc.UserV4 structure. +func (to *Session) UpdateCurrentUser(u tc.UserV4, opts RequestOptions) (tc.UpdateUserResponseV4, toclientlib.ReqInf, error) { + var clientResp tc.UpdateUserResponseV4 + reqInf, err := to.put("/user/current", opts, u, &clientResp) + return clientResp, reqInf, err +} + +// CreateUser creates the given user. +func (to *Session) CreateUser(user tc.UserV4, opts RequestOptions) (tc.CreateUserResponseV4, toclientlib.ReqInf, error) { + if user.Tenant != nil { + innerOpts := NewRequestOptions() + innerOpts.QueryParameters.Set("name", *user.Tenant) + tenant, _, err := to.GetTenants(innerOpts) + if err != nil { + return tc.CreateUserResponseV4{Alerts: tenant.Alerts}, toclientlib.ReqInf{}, fmt.Errorf("resolving Tenant name '%s' to an ID: %w", *user.Tenant, err) + } + if len(tenant.Response) < 1 { + return tc.CreateUserResponseV4{Alerts: tenant.Alerts}, toclientlib.ReqInf{}, fmt.Errorf("no such Tenant: '%s'", *user.Tenant) + } + user.TenantID = tenant.Response[0].ID + } + + if user.Role != "" { + innerOpts := NewRequestOptions() + innerOpts.QueryParameters.Set("name", user.Role) + roles, _, err := to.GetRoles(innerOpts) + if err != nil { + return tc.CreateUserResponseV4{Alerts: roles.Alerts}, toclientlib.ReqInf{}, fmt.Errorf("resolving Role name '%s' to an ID: %w", user.Role, err) + } + if len(roles.Response) == 0 { + return tc.CreateUserResponseV4{Alerts: roles.Alerts}, toclientlib.ReqInf{}, fmt.Errorf("no such Role: '%s'", user.Role) + } + user.Role = roles.Response[0].Name + } + + route := "/users" + var clientResp tc.CreateUserResponseV4 + reqInf, err := to.post(route, opts, user, &clientResp) + return clientResp, reqInf, err +} + +// UpdateUser replaces the User identified by 'id' with the one provided. +func (to *Session) UpdateUser(id int, u tc.UserV4, opts RequestOptions) (tc.UpdateUserResponseV4, toclientlib.ReqInf, error) { + route := "/users/" + strconv.Itoa(id) + var clientResp tc.UpdateUserResponseV4 + reqInf, err := to.put(route, opts, u, &clientResp) + return clientResp, reqInf, err +} + +// DeleteUser deletes the User with the given ID. +func (to *Session) DeleteUser(id int, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + route := "/users/" + strconv.Itoa(id) + var alerts tc.Alerts + reqInf, err := to.del(route, opts, &alerts) + return alerts, reqInf, err +} + +// RegisterNewUser requests the registration of a new user with the given tenant ID and role ID, +// through their email. +func (to *Session) RegisterNewUser(tenantID uint, role string, email rfc.EmailAddress, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqBody := tc.UserRegistrationRequestV40{ + Email: email, + TenantID: tenantID, + Role: role, + } + reqInf, err := to.post("/users/register", opts, reqBody, &alerts) + return alerts, reqInf, err +} diff --git a/traffic_ops/v5-client/vault.go b/traffic_ops/v5-client/vault.go new file mode 100644 index 0000000000..26d85bbe3b --- /dev/null +++ b/traffic_ops/v5-client/vault.go @@ -0,0 +1,32 @@ +package client + +/* + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +import ( + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/traffic_ops/toclientlib" +) + +const ( + // apiVaultPing is the partial path (excluding the /api/ prefix) to the /vault/ping API endpoint. + apiVaultPing = "/vault/ping" +) + +// TrafficVaultPing returns a response indicating whether or not Traffic Vault is responsive. +func (to *Session) TrafficVaultPing(opts RequestOptions) (tc.TrafficVaultPingResponse, toclientlib.ReqInf, error) { + var data tc.TrafficVaultPingResponse + reqInf, err := to.get(apiVaultPing, opts, &data) + return data, reqInf, err +}