diff --git a/CHANGELOG.md b/CHANGELOG.md index a84a14f39a..2ac2f0d6d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). ## [unreleased] ### Added +- [#6033](https://github.com/apache/trafficcontrol/issues/6033) *Traffic Ops, Traffic Portal* Added ability to assign multiple servers per capability. - [#7081](https://github.com/apache/trafficcontrol/issues/7081) *Traffic Router* Added better log messages for TR connection exceptions. - [#7089](https://github.com/apache/trafficcontrol/issues/7089) *Traffic Router* Added the ability to specify HTTPS certificate attributes. - [#7109](https://github.com/apache/trafficcontrol/pull/7109) *Traffic Router* Removed `dnssec.zone.diffing.enabled` and `dnssec.rrsig.cache.enabled` parameters. @@ -14,7 +15,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). - [#7063](https://github.com/apache/trafficcontrol/pull/7063) *Traffic Ops* Added API version 5.0 (IN DEVELOPMENT) - [#2101](https://github.com/apache/trafficcontrol/issues/2101) *Traffic Portal* Added the ability to tell if a Delivery Service is the target of another steering DS. - [#6021](https://github.com/apache/trafficcontrol/issues/6021) *Traffic Portal* Added the ability to view a change logs message in it's entirety by clicking on it. -- [#6033](https://github.com/apache/trafficcontrol/issues/6033) *Traffic Ops, Traffic Portal* Added ability to assign multiple server capabilities to a server. +- [#7078](https://github.com/apache/trafficcontrol/issues/7078) *Traffic Ops, Traffic Portal* Added ability to assign multiple server capabilities to a server. - [#7096](https://github.com/apache/trafficcontrol/issues/7096) [Health Client] Added health client parent health - [#7032](https://github.com/apache/trafficcontrol/issues/7032) *Cache Config* 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. - [#7097](https://github.com/apache/trafficcontrol/issues/7097) *Traffic Ops, Traffic Portal, t3c* Added the `regional` field to Delivery Services, which affects whether `maxOriginConnections` should be per Cache Group diff --git a/docs/source/api/v4/multiple_server_capabilities.rst b/docs/source/api/v4/multiple_server_capabilities.rst deleted file mode 100644 index fed438bdac..0000000000 --- a/docs/source/api/v4/multiple_server_capabilities.rst +++ /dev/null @@ -1,83 +0,0 @@ -.. -.. -.. Licensed under the Apache License, Version 2.0 (the "License"); -.. you may not use this file except in compliance with the License. -.. You may obtain a copy of the License at -.. -.. http://www.apache.org/licenses/LICENSE-2.0 -.. -.. Unless required by applicable law or agreed to in writing, software -.. distributed under the License is distributed on an "AS IS" BASIS, -.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -.. See the License for the specific language governing permissions and -.. limitations under the License. -.. - -.. _to-api-v4-multiple_server_capabilities: - -******************************** -``multiple_server_capabilities`` -******************************** - -.. versionadded:: 4.1 - -``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/4.1/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/v4/multiple_servers_capabilities.rst b/docs/source/api/v4/multiple_servers_capabilities.rst new file mode 100644 index 0000000000..c0f2d97d43 --- /dev/null +++ b/docs/source/api/v4/multiple_servers_capabilities.rst @@ -0,0 +1,192 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT 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-v4-multiple_servers_capabilities: + +********************************* +``multiple_servers_capabilities`` +********************************* + +.. versionadded:: 4.1 + +``POST`` +======== +Inserts a list of :term:`Server Capability` names associated to a server and vice versa i.e insert a list of :term:`Server` ids associated to a server capability. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: SERVER:READ, SERVER:CREATE, SERVER-CAPABILITY:READ, SERVER-CAPABILITY:CREATE +:Response Type: Object + +Request Structure +----------------- +:serverIds: List of :term:`Server` ids (integral, unique identifier) associated with a :term:`Server Capability` +:serverCapabilities: List of :term:`Server Capability` names to associate with a :term:`Server` id +:pageType: To determine which configuration (server or server capabilities) is requesting association. Only two values are permitted: `server` or `sc` (short for server capability). If `server` is chosen, it implies that multiple server capabilities are to be associated with a given server id. If `sc` is chosen, it implies that multiple server ids are to be associated with a given server capability. + +.. code-block:: http + :caption: Request Example1 + + POST /api/4.1/multiple_servers_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 + + { + "serverIds": [1], + "serverCapabilities": ["test", "disk"] + "pageType": "server" + } + +.. code-block:: http + :caption: Request Example2 + + POST /api/4.1/multiple_servers_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 + + { + "serverIds": [2, 3] + "serverCapabilities": ["eas"], + "pageType": "sc" + } + +Response Structure +------------------ +:serverId: List of :term:`Server` ids (integral, unique identifier) associated with a server capability. +:serverCapabilities: List of :term:`Server Capability` names to be associated with a :term:`Server` id. + +.. code-block:: http + :caption: Response Example1 + + 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": { + "serverIds": [1], + "serverCapabilities": ["test", "disk"] + } + } + +.. code-block:: http + :caption: Response Example2 + + 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 Servers assigned to a capability", + "level": "success" + }], + "response": { + "serverIds": [2, 3] + "serverCapabilities": ["eas"], + } + } + +``DELETE`` +========== +Deletes a list of :term:`Server Capability` names associated to a server and vice versa i.e. deletes a list of :term:`Server` ids associated to a server capability. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: SERVER:READ, SERVER:DELETE, SERVER-CAPABILITY:READ, SERVER-CAPABILITY:DELETE +:Response Type: Object + +Request Structure +----------------- +:serverIds: List of :term:`Server` ids (integral, unique identifier) associated with a :term:`Server Capability` +:serverCapabilities: List of :term:`Server Capability` names to associate with a :term:`Server` id +:pageType: To determine which configuration (server or server capabilities) is requesting deletion. Only two values are permitted: `server` or `sc` (short for server capability). If `server` is chosen, it implies that multiple server capabilities are to be deleted for a given server id. If `sc` is chosen, it implies that multiple server ids are to be deleted for a given server capability. + +.. code-block:: http + :caption: Request Example + + DELETE /api/4.1/multiple_servers_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 + + { + "serverIds": [2, 3] + "serverCapabilities": ["eas"], + "pageType": "sc" + } + +Response Structure +------------------ +:serverId: List of :term:`Server` ids (integral, unique identifier) associated with a server capability. +:serverCapabilities: List of :term:`Server Capability` names to be associated with a :term:`Server` 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, 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": "Removed multiple servers from capabilities or multiple servers to a capability", + "level": "success" + }], + "response": { + "serverIds": [2, 3] + "serverCapabilities": ["eas"], + } + } + diff --git a/docs/source/api/v5/multiple_server_capabilities.rst b/docs/source/api/v5/multiple_server_capabilities.rst deleted file mode 100644 index b631807553..0000000000 --- a/docs/source/api/v5/multiple_server_capabilities.rst +++ /dev/null @@ -1,81 +0,0 @@ -.. -.. -.. Licensed under the Apache License, Version 2.0 (the "License"); -.. you may not use this file except in compliance with the License. -.. You may obtain a copy of the License at -.. -.. http://www.apache.org/licenses/LICENSE-2.0 -.. -.. Unless required by applicable law or agreed to in writing, software -.. distributed under the License is distributed on an "AS IS" BASIS, -.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -.. See the License for the specific language governing permissions and -.. limitations under the License. -.. - -.. _to-api-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/multiple_servers_capabilities.rst b/docs/source/api/v5/multiple_servers_capabilities.rst new file mode 100644 index 0000000000..347b15ae4b --- /dev/null +++ b/docs/source/api/v5/multiple_servers_capabilities.rst @@ -0,0 +1,190 @@ +.. +.. +.. Licensed under the Apache License, Version 2.0 (the "License"); +.. you may not use this file except in compliance with the License. +.. You may obtain a copy of the License at +.. +.. http://www.apache.org/licenses/LICENSE-2.0 +.. +.. Unless required by applicable law or agreed to in writing, software +.. distributed under the License is distributed on an "AS IS" BASIS, +.. WITHOUT 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_servers_capabilities: + +********************************* +``multiple_servers_capabilities`` +********************************* + +``POST`` +======== +Inserts a list of :term:`Server Capability` names associated to a server and vice versa i.e insert a list of :term:`Server` ids associated to a server capability. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: SERVER:READ, SERVER:CREATE, SERVER-CAPABILITY:READ, SERVER-CAPABILITY:CREATE +:Response Type: Object + +Request Structure +----------------- +:serverIds: List of :term:`Server` ids (integral, unique identifier) associated with a :term:`Server Capability` +:serverCapabilities: List of :term:`Server Capability` names to associate with a :term:`Server` id +:pageType: To determine which configuration (server or server capabilities) is requesting association. Only two values are permitted: `server` or `sc` (short for server capability). If `server` is chosen, it implies that multiple server capabilities are to be associated with a given server id. If `sc` is chosen, it implies that multiple server ids are to be associated with a given server capability. + +.. code-block:: http + :caption: Request Example1 + + POST /api/5.0/multiple_servers_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 + + { + "serverIds": [1], + "serverCapabilities": ["test", "disk"] + "pageType": "server" + } + +.. code-block:: http + :caption: Request Example2 + + POST /api/5.0/multiple_servers_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 + + { + "serverIds": [2, 3] + "serverCapabilities": ["eas"], + "pageType": "sc" + } + +Response Structure +------------------ +:serverId: List of :term:`Server` ids (integral, unique identifier) associated with a server capability. +:serverCapabilities: List of :term:`Server Capability` names to be associated with a :term:`Server` id. + +.. code-block:: http + :caption: Response Example1 + + 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": { + "serverIds": [1], + "serverCapabilities": ["test", "disk"] + } + } + +.. code-block:: http + :caption: Response Example2 + + 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 Servers assigned to a capability", + "level": "success" + }], + "response": { + "serverIds": [2, 3] + "serverCapabilities": ["eas"], + "pageType": "sc" + } + } + +``DELETE`` +========== +Deletes a list of :term:`Server Capability` names associated to a server and vice versa i.e. deletes a list of :term:`Server` ids associated to a server capability. + +:Auth. Required: Yes +:Roles Required: "admin" or "operations" +:Permissions Required: SERVER:READ, SERVER:DELETE, SERVER-CAPABILITY:READ, SERVER-CAPABILITY:DELETE +:Response Type: Object + +Request Structure +----------------- +:serverIds: List of :term:`Server` ids (integral, unique identifier) associated with a :term:`Server Capability` +:serverCapabilities: List of :term:`Server Capability` names to associate with a :term:`Server` id +:pageType: To determine which configuration (server or server capabilities) is requesting deletion. Only two values are permitted: `server` or `sc` (short for server capability). If `server` is chosen, it implies that multiple server capabilities are to be deleted for a given server id. If `sc` is chosen, it implies that multiple server ids are to be deleted for a given server capability. + +.. code-block:: http + :caption: Request Example + + DELETE /api/5.0/multiple_servers_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 + + { + "serverIds": [2, 3] + "serverCapabilities": ["eas"], + } + +Response Structure +------------------ +:serverId: List of :term:`Server` ids (integral, unique identifier) associated with a server capability. +:serverCapabilities: List of :term:`Server Capability` names to be associated with a :term:`Server` 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, 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": "Removed multiple servers from capabilities or multiple servers to a capability", + "level": "success" + }], + "response": { + "serverIds": [2, 3] + "serverCapabilities": ["eas"], + } + } + diff --git a/docs/source/glossary.rst b/docs/source/glossary.rst index 5e5a08380d..5867238f97 100644 --- a/docs/source/glossary.rst +++ b/docs/source/glossary.rst @@ -415,6 +415,10 @@ Glossary .. seealso:: For a more complete description of Roles, see the :ref:`roles` overview section. + Server + Servers + A :dfn:`Server` implies a :term:`cache servers` and/or :term:`origin servers` and/or any different type of servers (e.g: Traffic_Monitor, Traffic_Ops etc) associated with a :term:`Delivery Service`. + Server Capability Server Capabilities A :dfn:`Server Capability` (not to be confused with a "Capability") expresses the capacity of a :term:`cache server` to serve a particular kind of traffic. For example, a :dfn:`Server Capability` could be created named "RAM" to be assigned to :term:`cache servers` that have RAM-disks allocated for content caching. :dfn:`Server Capabilities` can also be required by :term:`Delivery Services`, which will prevent :term:`cache servers` without that :dfn:`Server Capability` from being assigned to them. It also prevents :term:`Mid-tier cache servers` without said :term:`Server Capability` from being selected to serve upstream requests from those :term:`Edge-tier cache servers` assigned to the requiring :term:`Delivery Services`. diff --git a/lib/go-tc/server_server_capability.go b/lib/go-tc/server_server_capability.go index 535a95da7f..88bb755b89 100644 --- a/lib/go-tc/server_server_capability.go +++ b/lib/go-tc/server_server_capability.go @@ -27,10 +27,12 @@ type ServerServerCapability struct { ServerCapability *string `json:"serverCapability" db:"server_capability"` } -// MultipleServerCapabilities represents an association between a server and list of server capabilities. -type MultipleServerCapabilities struct { - ServerID int `json:"serverId" db:"server"` +// MultipleServersCapabilities represents an association between a server capability and list of servers +// and an association between a server and list of server capabilities. +type MultipleServersCapabilities struct { ServerCapabilities []string `json:"serverCapabilities" db:"server_capability"` + ServerIDs []int64 `json:"serverIds" db:"server"` + PageType string `json:"pageType"` } // ServerServerCapabilitiesResponse is the type of a response from Traffic diff --git a/traffic_ops/testing/api/v4/multiple_server_capabilities_test.go b/traffic_ops/testing/api/v4/multiple_server_capabilities_test.go index f4e6326bf9..c3ab3e91c2 100644 --- a/traffic_ops/testing/api/v4/multiple_server_capabilities_test.go +++ b/traffic_ops/testing/api/v4/multiple_server_capabilities_test.go @@ -16,24 +16,77 @@ package v4 */ import ( + "encoding/json" "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/v4-client" ) func TestMultipleServerCapabilities(t *testing.T) { WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, ServerCapabilities, ServerServerCapabilities}, func() { + var multipleSCs []string + var multipleServerIDs []int64 - methodTests := utils.TestCase[client.Session, client.RequestOptions, tc.MultipleServerCapabilities]{ - "PUT": { - "OK when VALID REQUEST": { + methodTests := utils.V4TestCase{ + "POST": { + "OK When Assigned Multiple Server Capabilities": { ClientSession: TOSession, - RequestBody: tc.MultipleServerCapabilities{ - ServerID: GetServerID(t, "dtrc-mid-04")(), - ServerCapabilities: []string{"disk", "blah"}, + RequestBody: map[string]interface{}{ + "serverCapabilities": append(multipleSCs, "disk", "blah"), + "serverIds": append(multipleServerIDs, int64(GetServerID(t, "dtrc-mid-04")())), + "pageType": "server", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateSSC("dtrc-mid-04", "server")), + }, + "OK When Assigned Multiple Servers Per Capability": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "serverCapabilities": append(multipleSCs, "ram"), + "serverIds": append(multipleServerIDs, int64(GetServerID(t, "dtrc-mid-04")()), int64(GetServerID(t, "dtrc-edge-08")())), + "pageType": "sc", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateSSC("ram", "sc")), + }, + "BAD REQUEST When Assigning Many:Many Relation": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "serverCapabilities": append(multipleSCs, "ram", "disk"), + "serverIds": append(multipleServerIDs, int64(GetServerID(t, "dtrc-mid-04")()), int64(GetServerID(t, "dtrc-edge-08")())), + "pageType": "sc", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST When Missing JSON field": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "serverCapabilities": append(multipleSCs, "ram", "disk"), + "serverIds": append(multipleServerIDs, int64(GetServerID(t, "dtrc-mid-04")()), int64(GetServerID(t, "dtrc-edge-08")())), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "DELETE": { + "OK When Delete Multiple Assigned Servers Per Capability": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "serverCapabilities": append(multipleSCs, "asdf"), + "serverIds": append(multipleServerIDs, int64(GetServerID(t, "dtrc-mid-04")()), int64(GetServerID(t, "dtrc-edge-08")())), + "pageType": "sc", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK When Delete Multiple Assigned Server Capabilities": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "serverCapabilities": append(multipleSCs, "disk", "blah"), + "serverIds": append(multipleServerIDs, int64(GetServerID(t, "dtrc-mid-04")())), + "pageType": "server", }, Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), }, @@ -43,17 +96,47 @@ func TestMultipleServerCapabilities(t *testing.T) { for method, testCases := range methodTests { t.Run(method, func(t *testing.T) { for name, testCase := range testCases { + mssc := tc.MultipleServersCapabilities{} + + 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, &mssc) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + switch method { - case "PUT": + case "POST": t.Run(name, func(t *testing.T) { - alerts, reqInf, err := testCase.ClientSession.AssignMultipleServerCapability(testCase.RequestBody, testCase.RequestOpts, testCase.RequestBody.ServerID) + alerts, reqInf, err := testCase.ClientSession.AssignMultipleServersCapabilities(mssc, testCase.RequestOpts) for _, check := range testCase.Expectations { check(t, reqInf, nil, alerts, err) } }) + + case "DELETE": + alerts, reqInf, err := testCase.ClientSession.DeleteMultipleServersCapabilities(mssc, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } } } }) } }) } + +func validateSSC(name, pageType string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + opts := client.NewRequestOptions() + switch pageType { + case "server": + opts.QueryParameters.Set("serverId", strconv.Itoa(GetServerID(t, name)())) + case "sc": + opts.QueryParameters.Set("serverCapability", name) + } + ssc, _, err := TOSession.GetServerServerCapabilities(opts) + assert.RequireGreaterOrEqual(t, len(ssc.Response), 1, "Expected one or more association with:%s, Got:%d", name, len(ssc.Response)) + assert.RequireNoError(t, err, "Cannot get response: %v - alerts: %+v", err, ssc.Alerts) + } +} diff --git a/traffic_ops/testing/api/v5/multiple_server_capabilities_test.go b/traffic_ops/testing/api/v5/multiple_server_capabilities_test.go index cf8e941d64..c6e2f5d1e0 100644 --- a/traffic_ops/testing/api/v5/multiple_server_capabilities_test.go +++ b/traffic_ops/testing/api/v5/multiple_server_capabilities_test.go @@ -16,24 +16,77 @@ package v5 */ import ( + "encoding/json" "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 TestMultipleServerCapabilities(t *testing.T) { WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, ServerCapabilities, ServerServerCapabilities}, func() { + var multipleSCs []string + var multipleServerIDs []int64 - methodTests := utils.TestCase[client.Session, client.RequestOptions, tc.MultipleServerCapabilities]{ - "PUT": { - "OK when VALID REQUEST": { + methodTests := utils.V5TestCase{ + "POST": { + "OK When Assigned Multiple Server Capabilities": { ClientSession: TOSession, - RequestBody: tc.MultipleServerCapabilities{ - ServerID: GetServerID(t, "dtrc-mid-04")(), - ServerCapabilities: []string{"disk", "blah"}, + RequestBody: map[string]interface{}{ + "serverCapabilities": append(multipleSCs, "disk", "blah"), + "serverIds": append(multipleServerIDs, int64(GetServerID(t, "dtrc-mid-04")())), + "pageType": "server", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateSSC("dtrc-mid-04", "server")), + }, + "OK When Assigned Multiple Servers Per Capability": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "serverCapabilities": append(multipleSCs, "ram"), + "serverIds": append(multipleServerIDs, int64(GetServerID(t, "dtrc-mid-04")()), int64(GetServerID(t, "dtrc-edge-08")())), + "pageType": "sc", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK), validateSSC("ram", "sc")), + }, + "BAD REQUEST When Assigning Many:Many Relation": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "serverCapabilities": append(multipleSCs, "ram", "disk"), + "serverIds": append(multipleServerIDs, int64(GetServerID(t, "dtrc-mid-04")()), int64(GetServerID(t, "dtrc-edge-08")())), + "pageType": "sc", + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + "BAD REQUEST When Missing JSON field": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "serverCapabilities": append(multipleSCs, "ram", "disk"), + "serverIds": append(multipleServerIDs, int64(GetServerID(t, "dtrc-mid-04")()), int64(GetServerID(t, "dtrc-edge-08")())), + }, + Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)), + }, + }, + "DELETE": { + "OK When Delete Multiple Assigned Servers Per Capability": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "serverCapabilities": append(multipleSCs, "asdf"), + "serverIds": append(multipleServerIDs, int64(GetServerID(t, "dtrc-mid-04")()), int64(GetServerID(t, "dtrc-edge-08")())), + "pageType": "sc", + }, + Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), + }, + "OK When Delete Multiple Assigned Server Capabilities": { + ClientSession: TOSession, + RequestBody: map[string]interface{}{ + "serverCapabilities": append(multipleSCs, "disk", "blah"), + "serverIds": append(multipleServerIDs, int64(GetServerID(t, "dtrc-mid-04")())), + "pageType": "server", }, Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)), }, @@ -43,17 +96,47 @@ func TestMultipleServerCapabilities(t *testing.T) { for method, testCases := range methodTests { t.Run(method, func(t *testing.T) { for name, testCase := range testCases { + mssc := tc.MultipleServersCapabilities{} + + 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, &mssc) + assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err) + } + switch method { - case "PUT": + case "POST": t.Run(name, func(t *testing.T) { - alerts, reqInf, err := testCase.ClientSession.AssignMultipleServerCapability(testCase.RequestBody, testCase.RequestOpts, testCase.RequestBody.ServerID) + alerts, reqInf, err := testCase.ClientSession.AssignMultipleServersCapabilities(mssc, testCase.RequestOpts) for _, check := range testCase.Expectations { check(t, reqInf, nil, alerts, err) } }) + + case "DELETE": + alerts, reqInf, err := testCase.ClientSession.DeleteMultipleServersCapabilities(mssc, testCase.RequestOpts) + for _, check := range testCase.Expectations { + check(t, reqInf, nil, alerts, err) + } } } }) } }) } + +func validateSSC(name, pageType string) utils.CkReqFunc { + return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) { + opts := client.NewRequestOptions() + switch pageType { + case "server": + opts.QueryParameters.Set("serverId", strconv.Itoa(GetServerID(t, name)())) + case "sc": + opts.QueryParameters.Set("serverCapability", name) + } + ssc, _, err := TOSession.GetServerServerCapabilities(opts) + assert.RequireGreaterOrEqual(t, len(ssc.Response), 1, "Expected one or more association with:%s, Got:%d", name, len(ssc.Response)) + assert.RequireNoError(t, err, "Cannot get response: %v - alerts: %+v", err, ssc.Alerts) + } +} diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go b/traffic_ops/traffic_ops_golang/routing/routes.go index 387e015cd6..0b5796b39c 100644 --- a/traffic_ops/traffic_ops_golang/routing/routes.go +++ b/traffic_ops/traffic_ops_golang/routing/routes.go @@ -328,7 +328,8 @@ func Routes(d ServerData) ([]Route, http.Handler, error) { {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}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `multiple_servers_capabilities/?$`, Handler: server.AssignMultipleServersCapabilities, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER:READ", "SERVER:CREATE", "SERVER-CAPABILITY:READ", "SERVER-CAPABILITY:CREATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 407924192581}, + {Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `multiple_servers_capabilities/?$`, Handler: server.DeleteMultipleServersCapabilities, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER:READ", "SERVER:DELETE", "SERVER-CAPABILITY:READ", "SERVER-CAPABILITY:DELETE"}, Authenticated: Authenticated, Middlewares: nil, ID: 407924192781}, //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}, @@ -530,9 +531,9 @@ func Routes(d ServerData) ([]Route, http.Handler, error) { /** * 4.x API */ - - // Assign Multiple Server Capabilities - {Version: api.Version{Major: 4, Minor: 1}, 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: 40792419258}, + // Assign Multiple Servers to a capability and assign multiple capabilities to a server + {Version: api.Version{Major: 4, Minor: 1}, Method: http.MethodPost, Path: `multiple_servers_capabilities/?$`, Handler: server.AssignMultipleServersCapabilities, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER:READ", "SERVER:CREATE", "SERVER-CAPABILITY:READ", "SERVER-CAPABILITY:CREATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 40792419258}, + {Version: api.Version{Major: 4, Minor: 1}, Method: http.MethodDelete, Path: `multiple_servers_capabilities/?$`, Handler: server.DeleteMultipleServersCapabilities, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER:READ", "SERVER:DELETE", "SERVER-CAPABILITY:READ", "SERVER-CAPABILITY:DELETE"}, Authenticated: Authenticated, Middlewares: nil, ID: 40792419278}, // CDNI integration {Version: api.Version{Major: 4, 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: 541357729077}, diff --git a/traffic_ops/traffic_ops_golang/server/servers_server_capability.go b/traffic_ops/traffic_ops_golang/server/servers_server_capability.go index 21ae31db2a..ed52f21ad9 100644 --- a/traffic_ops/traffic_ops_golang/server/servers_server_capability.go +++ b/traffic_ops/traffic_ops_golang/server/servers_server_capability.go @@ -20,6 +20,7 @@ package server */ import ( + "database/sql" "encoding/json" "errors" "fmt" @@ -297,19 +298,19 @@ func (ssc *TOServerServerCapability) Create() (error, error, int) { } // Ensure type is correct - correctType := true - if err := tx.Tx.QueryRow(scCheckServerTypeQuery(), ssc.ServerID).Scan(&correctType); err != nil { - return nil, fmt.Errorf("checking server type: %v", err), http.StatusInternalServerError - } - if !correctType { - return fmt.Errorf("server %v has an incorrect server type. Server capabilities can only be assigned to EDGE or MID servers", *ssc.ServerID), nil, http.StatusBadRequest + var sidList []int64 + sidList = append(sidList, int64(*ssc.ServerID)) + errCode, userErr, sysErr := checkServerType(tx.Tx, sidList) + if userErr != nil || sysErr != nil { + return userErr, sysErr, errCode + } cdnName, err := dbhelpers.GetCDNNameFromServerID(tx.Tx, int64(*ssc.ServerID)) if err != nil { return nil, err, http.StatusInternalServerError } - userErr, sysErr, errCode := dbhelpers.CheckIfCurrentUserCanModifyCDN(tx.Tx, string(cdnName), ssc.APIInfo().User.UserName) + userErr, sysErr, errCode = dbhelpers.CheckIfCurrentUserCanModifyCDN(tx.Tx, string(cdnName), ssc.APIInfo().User.UserName) if userErr != nil || sysErr != nil { return userErr, sysErr, errCode } @@ -359,17 +360,6 @@ server) VALUES ( :server) RETURNING server, server_capability, last_updated` } -func scCheckServerTypeQuery() string { - return ` -SELECT EXISTS ( - SELECT s.id - FROM server s - JOIN type t ON s.type = t.id - WHERE s.id = $1 - AND t.use_in_table = 'server' - AND (t.name LIKE 'MID%' OR t.name LIKE 'EDGE%'))` -} - func checkDSReqCapQuery() string { return ` SELECT ARRAY( @@ -443,8 +433,8 @@ func getDSTenantIDsByIDs(tx *sqlx.Tx, dsIDs []int64) ([]DSTenant, error) { return dsTenantIDs, nil } -// AssignMultipleServerCapabilities helps assign multiple server capabilities to a given server. -func AssignMultipleServerCapabilities(w http.ResponseWriter, r *http.Request) { +// AssignMultipleServersCapabilities assigns multiple servers to a capability or multiple server capabilities to a server +func AssignMultipleServersCapabilities(w http.ResponseWriter, r *http.Request) { inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil) tx := inf.Tx.Tx if userErr != nil || sysErr != nil { @@ -453,77 +443,204 @@ func AssignMultipleServerCapabilities(w http.ResponseWriter, r *http.Request) { } defer inf.Close() - var msc tc.MultipleServerCapabilities - if err := json.NewDecoder(r.Body).Decode(&msc); err != nil { - api.HandleErr(w, r, tx, http.StatusBadRequest, err, nil) + var mssc tc.MultipleServersCapabilities + if err := json.NewDecoder(r.Body).Decode(&mssc); err != nil { + api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("error decoding POST request body into MultipleServersCapabilities struct %w", err), nil) return } - // Check existence prior to checking type - _, exists, err := dbhelpers.GetServerNameFromID(tx, int64(msc.ServerID)) - if err != nil { - api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, err) + // validate JSON body. + errs := tovalidate.ToErrors(validation.Errors{ + "serverIds": validation.Validate(mssc.ServerIDs, validation.Required), + "serverCapabilities": validation.Validate(mssc.ServerCapabilities, validation.Required), + "pageType": validation.Validate(mssc.PageType, validation.Required), + }) + + if len(errs) > 0 { + api.HandleErr(w, r, tx, http.StatusBadRequest, util.JoinErrs(errs), nil) + return } - if !exists { - userErr := fmt.Errorf("server %d does not exist", msc.ServerID) - api.HandleErr(w, r, tx, http.StatusNotFound, userErr, nil) + + if len(mssc.ServerIDs) > 1 && len(mssc.ServerCapabilities) > 1 { + api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("not allowed to have many:many association between server and server capability. "+ + "Only associations allowed are; 1:1, 1:many or many:1"), nil) return } + if len(mssc.ServerIDs) >= 1 { + errCode, userErr, sysErr = checkExistingServer(tx, mssc.ServerIDs, inf.User.UserName) + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) + return + } + } + // Ensure type is correct - correctType := true - if err := tx.QueryRow(scCheckServerTypeQuery(), msc.ServerID).Scan(&correctType); err != nil { - api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("checking server type: %w", err)) + errCode, userErr, sysErr = checkServerType(tx, mssc.ServerIDs) + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) return } - if !correctType { - userErr := fmt.Errorf("server %d has an incorrect server type. Server capabilities can only be assigned to EDGE or MID servers", msc.ServerID) - api.HandleErr(w, r, tx, http.StatusBadRequest, userErr, nil) + + // Insert rows in DB + sid := make([]int64, len(mssc.ServerCapabilities)) + scs := make([]string, len(mssc.ServerIDs)) + switch mssc.PageType { + case "sc": + for i := range mssc.ServerIDs { + scs[i] = mssc.ServerCapabilities[0] + } + sid = mssc.ServerIDs + case "server": + for i := range mssc.ServerCapabilities { + sid[i] = mssc.ServerIDs[0] + } + scs = mssc.ServerCapabilities + default: + api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("incorrect page type: '%s'. Should be 'sc' or 'server'", mssc.PageType), nil) return } - cdnName, err := dbhelpers.GetCDNNameFromServerID(tx, int64(msc.ServerID)) + msscQuery := `INSERT INTO server_server_capability + select "server_capability", "server" + FROM UNNEST($1::text[], $2::int[]) AS tmp("server_capability", "server")` + _, err := tx.Query(msscQuery, pq.Array(scs), pq.Array(sid)) if err != nil { - api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, err) + useErr, sysErr, statusCode := api.ParseDBError(err) + api.HandleErr(w, r, tx, statusCode, useErr, sysErr) return } - userErr, sysErr, errCode = dbhelpers.CheckIfCurrentUserCanModifyCDN(tx, string(cdnName), inf.User.UserName) + var alerts tc.Alerts + if mssc.PageType == "sc" { + alerts = tc.CreateAlerts(tc.SuccessLevel, "Assign Server(s) to a capability") + } else { + alerts = tc.CreateAlerts(tc.SuccessLevel, "Assign Server Capability(ies) to a server") + } + api.WriteAlertsObj(w, r, http.StatusOK, alerts, mssc) + return +} + +// DeleteMultipleServersCapabilities deletes multiple servers to a capability or multiple server capabilities to a server +func DeleteMultipleServersCapabilities(w http.ResponseWriter, r *http.Request) { + inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil) + tx := inf.Tx.Tx if userErr != nil || sysErr != nil { - api.HandleErr(w, r, tx, errCode, userErr, sysErr) + api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) return } + defer inf.Close() - //Delete existing rows from server_server_capability for a given server - _, err = tx.Exec("DELETE FROM server_server_capability ssc WHERE ssc.server=$1", msc.ServerID) - if err != nil { - useErr, sysErr, statusCode := api.ParseDBError(err) - api.HandleErr(w, r, tx, statusCode, useErr, sysErr) + var mssc tc.MultipleServersCapabilities + if err := json.NewDecoder(r.Body).Decode(&mssc); err != nil { + api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("error decoding DELETE request body into MultipleServersCapabilities struct %w", err), nil) return } - multipleServerCapabilities := make([]string, 0, len(msc.ServerCapabilities)) + if len(mssc.ServerIDs) >= 1 { + errCode, userErr, sysErr = checkExistingServer(tx, mssc.ServerIDs, inf.User.UserName) + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) + return + } + } - mscQuery := `WITH inserted AS ( - INSERT INTO server_server_capability - SELECT "server_capability", $2 - FROM UNNEST($1::text[]) AS tmp("server_capability") - RETURNING server_capability - ) - SELECT ARRAY_AGG(server_capability) - FROM ( - SELECT server_capability - FROM inserted - ) AS returned(server_capability)` + //Delete existing rows from server_server_capability for a given server or for a given capability + const delQuery = `DELETE FROM server_server_capability ssc WHERE ` + var dq string + var alerts tc.Alerts + var result sql.Result + var err error + switch mssc.PageType { + case "sc": + dq = delQuery + `ssc.server_capability=$1` + if len(mssc.ServerIDs) == 1 { + dq = dq + ` AND ssc.server=$2` + result, err = tx.Exec(dq, mssc.ServerCapabilities[0], mssc.ServerIDs[0]) + } else { + result, err = tx.Exec(dq, mssc.ServerCapabilities[0]) + } + case "server": + dq = delQuery + `ssc.server=$1` + if len(mssc.ServerCapabilities) == 1 { + dq = dq + ` AND ssc.server_capability=$2` + result, err = tx.Exec(dq, mssc.ServerIDs[0], mssc.ServerCapabilities[0]) + } else { + result, err = tx.Exec(dq, mssc.ServerIDs[0]) + } + default: + api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("incorrect page type:'%s'. Should be 'sc' or 'server'", mssc.PageType), nil) + return + } - err = tx.QueryRow(mscQuery, pq.Array(msc.ServerCapabilities), msc.ServerID).Scan(pq.Array(&multipleServerCapabilities)) if err != nil { useErr, sysErr, statusCode := api.ParseDBError(err) api.HandleErr(w, r, tx, statusCode, useErr, sysErr) return } - msc.ServerCapabilities = multipleServerCapabilities - alerts := tc.CreateAlerts(tc.SuccessLevel, "Multiple Server Capabilities assigned to a server") - api.WriteAlertsObj(w, r, http.StatusOK, alerts, msc) + rowsAffected, err := result.RowsAffected() + if err != nil { + api.HandleErr(w, r, tx, http.StatusInternalServerError, fmt.Errorf("no rows were deleted from server_server_capability table: %w", err), sysErr) + return + } + if rowsAffected >= 1 { + if mssc.PageType == "sc" { + alerts = tc.CreateAlerts(tc.SuccessLevel, "Removed Server(s) associated with a capability") + } else { + alerts = tc.CreateAlerts(tc.SuccessLevel, "Removed Server Capability(ies) associated with a server") + } + } + + api.WriteAlertsObj(w, r, http.StatusOK, alerts, mssc) return } + +// checkExistingServer checks server existence +func checkExistingServer(tx *sql.Tx, sidList []int64, uName string) (int, error, error) { + for _, sid := range sidList { + _, exists, err := dbhelpers.GetServerNameFromID(tx, sid) + if err != nil { + return http.StatusInternalServerError, nil, err + } + if !exists { + userErr := fmt.Errorf("server %d does not exist", sid) + return http.StatusNotFound, userErr, nil + } + + cdnName, err := dbhelpers.GetCDNNameFromServerID(tx, sid) + if err != nil { + return http.StatusInternalServerError, nil, err + } + + userErr, sysErr, errCode := dbhelpers.CheckIfCurrentUserCanModifyCDN(tx, string(cdnName), uName) + if userErr != nil || sysErr != nil { + return errCode, userErr, sysErr + } + } + return http.StatusOK, nil, nil +} + +// checkServerType checks if the server type is MID and/or EDGE +func checkServerType(tx *sql.Tx, sids []int64) (int, error, error) { + var servArray []int64 + queryType := `SELECT array_agg(s.id) + FROM server s + JOIN type t ON s.type = t.id + WHERE s.id = any ($1) + AND t.use_in_table = 'server' + AND (t.name LIKE 'MID%' OR t.name LIKE 'EDGE%')` + if err := tx.QueryRow(queryType, pq.Array(sids)).Scan(pq.Array(&servArray)); err != nil { + return http.StatusInternalServerError, nil, fmt.Errorf("checking server type: %w", err) + } + cmp := make(map[int64]bool) + for _, item := range servArray { + cmp[item] = true + } + for _, sid := range sids { + if _, ok := cmp[sid]; !ok { + userErr := fmt.Errorf("server id: %d has an incorrect server type. Server capabilities can only be assigned to EDGE or MID servers", sid) + return http.StatusBadRequest, userErr, nil + } + } + return http.StatusOK, nil, nil +} diff --git a/traffic_ops/v4-client/server_server_capabilities.go b/traffic_ops/v4-client/server_server_capabilities.go index dd17238517..15bd1efbc2 100644 --- a/traffic_ops/v4-client/server_server_capabilities.go +++ b/traffic_ops/v4-client/server_server_capabilities.go @@ -16,6 +16,7 @@ package client */ import ( + "net/http" "net/url" "strconv" @@ -27,8 +28,8 @@ import ( // /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" +// apiMultipleServersCapabilities is the API version-relative path to the /multiple_servers_capabilities API endpoint. +const apiMultipleServersCapabilities = "/multiple_servers_capabilities" // CreateServerServerCapability assigns a Server Capability to a Server. func (to *Session) CreateServerServerCapability(ssc tc.ServerServerCapability, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { @@ -57,9 +58,16 @@ func (to *Session) GetServerServerCapabilities(opts RequestOptions) (tc.ServerSe 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) { +// AssignMultipleServersCapabilities assigns multiple server capabilities to a server or multiple servers to a capability. +func (to *Session) AssignMultipleServersCapabilities(mssc tc.MultipleServersCapabilities, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { var alerts tc.Alerts - reqInf, err := to.put(apiMultipleServerCapabilities, opts, msc, &alerts) + reqInf, err := to.post(apiMultipleServersCapabilities, opts, mssc, &alerts) + return alerts, reqInf, err +} + +// DeleteMultipleServersCapabilities deletes multiple assigned server capabilities to a server or multiple servers to a capability. +func (to *Session) DeleteMultipleServersCapabilities(mssc tc.MultipleServersCapabilities, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.req(http.MethodDelete, apiMultipleServersCapabilities, opts, mssc, &alerts) return alerts, reqInf, err } diff --git a/traffic_ops/v5-client/server_server_capabilities.go b/traffic_ops/v5-client/server_server_capabilities.go index dd17238517..15bd1efbc2 100644 --- a/traffic_ops/v5-client/server_server_capabilities.go +++ b/traffic_ops/v5-client/server_server_capabilities.go @@ -16,6 +16,7 @@ package client */ import ( + "net/http" "net/url" "strconv" @@ -27,8 +28,8 @@ import ( // /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" +// apiMultipleServersCapabilities is the API version-relative path to the /multiple_servers_capabilities API endpoint. +const apiMultipleServersCapabilities = "/multiple_servers_capabilities" // CreateServerServerCapability assigns a Server Capability to a Server. func (to *Session) CreateServerServerCapability(ssc tc.ServerServerCapability, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { @@ -57,9 +58,16 @@ func (to *Session) GetServerServerCapabilities(opts RequestOptions) (tc.ServerSe 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) { +// AssignMultipleServersCapabilities assigns multiple server capabilities to a server or multiple servers to a capability. +func (to *Session) AssignMultipleServersCapabilities(mssc tc.MultipleServersCapabilities, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { var alerts tc.Alerts - reqInf, err := to.put(apiMultipleServerCapabilities, opts, msc, &alerts) + reqInf, err := to.post(apiMultipleServersCapabilities, opts, mssc, &alerts) + return alerts, reqInf, err +} + +// DeleteMultipleServersCapabilities deletes multiple assigned server capabilities to a server or multiple servers to a capability. +func (to *Session) DeleteMultipleServersCapabilities(mssc tc.MultipleServersCapabilities, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) { + var alerts tc.Alerts + reqInf, err := to.req(http.MethodDelete, apiMultipleServersCapabilities, opts, mssc, &alerts) return alerts, reqInf, err } diff --git a/traffic_portal/app/src/common/api/ServerCapabilityService.js b/traffic_portal/app/src/common/api/ServerCapabilityService.js index ca8a827f28..5206856ba9 100644 --- a/traffic_portal/app/src/common/api/ServerCapabilityService.js +++ b/traffic_portal/app/src/common/api/ServerCapabilityService.js @@ -68,8 +68,12 @@ var ServerCapabilityService = function($http, ENV, locationUtils, messageModel) ); }; - this.assignSCsServer = function(serverId, serverCapabilities) { - return $http.put(ENV.api.unstable + 'multiple_server_capabilities',{ serverId: serverId, serverCapabilities: serverCapabilities, replace: true } ).then( + this.assignServersCapabilities = function(server, serverCapability, pageType) { + return $http.post(ENV.api.unstable + 'multiple_servers_capabilities',{ + serverIds: server, + serverCapabilities: serverCapability, + pageType: pageType, replace: true + }).then( function(result) { messageModel.setMessages(result.data.alerts, false); return result; @@ -81,6 +85,24 @@ var ServerCapabilityService = function($http, ENV, locationUtils, messageModel) ); }; + this.deleteServersCapabilities = function(server, serverCapability, pageType) { + return $http.delete(ENV.api.unstable + 'multiple_servers_capabilities',{ data: { + serverIds: server, + serverCapabilities: serverCapability, + pageType: pageType + } }).then( + function(result) { + messageModel.setMessages(result.data.alerts, false); + return result; + }, + function(err) { + messageModel.setMessages(err.data.alerts, false); + throw err; + } + ); + }; + + this.updateServerCapability = function(currentName, serverCapability) { return $http.put(ENV.api.unstable + 'server_capabilities', serverCapability, {params: {"name": currentName}}).then( function(result) { diff --git a/traffic_portal/app/src/common/modules/form/serverCapability/form.serverCapability.tpl.html b/traffic_portal/app/src/common/modules/form/serverCapability/form.serverCapability.tpl.html index 3bd82b9e12..f2875fda32 100644 --- a/traffic_portal/app/src/common/modules/form/serverCapability/form.serverCapability.tpl.html +++ b/traffic_portal/app/src/common/modules/form/serverCapability/form.serverCapability.tpl.html @@ -25,7 +25,7 @@