You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by sh...@apache.org on 2022/05/17 21:25:22 UTC

[trafficcontrol] branch master updated: Remove `/servers/details` from APIv4 (#6819)

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

shamrick pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git


The following commit(s) were added to refs/heads/master by this push:
     new b9cdfd5b9d Remove `/servers/details` from APIv4 (#6819)
b9cdfd5b9d is described below

commit b9cdfd5b9d150a17437c4c22d6cbdcd0a26066cb
Author: ocket8888 <oc...@apache.org>
AuthorDate: Tue May 17 15:25:17 2022 -0600

    Remove `/servers/details` from APIv4 (#6819)
    
    * Add deprecation notices, remove v4 handling, fix #6800
    
    * Update documentation - add deprecation notices to v3, remove from v4
    
    * Update clients - remove from v4, add deprecation notices to v3
    
    * Update CHANGELOG
    
    * fix unable to get server details by hostname if physloc not also provided
    
    * Fix embedded 'response' object
---
 CHANGELOG.md                                       |   4 +-
 docs/source/api/v3/servers_details.rst             |   3 +
 docs/source/api/v4/servers_details.rst             | 192 ---------------------
 .../clients/python/trafficops/tosession.py         |   7 +-
 traffic_ops/testing/api/v4/servers_test.go         |  33 ----
 traffic_ops/traffic_ops_golang/routing/routes.go   |   3 -
 traffic_ops/traffic_ops_golang/server/detail.go    | 175 +++++++++++--------
 traffic_ops/v3-client/server.go                    |   4 +
 traffic_ops/v4-client/server.go                    |  11 --
 9 files changed, 114 insertions(+), 318 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 768c04969c..e8e85c9a58 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -17,11 +17,10 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 - Traffic Monitor: Add support for `access.log` to TM.
 - Added functionality for login to provide a Bearer token and for that token to be later used for authorization.
 - [Traffic Ops] Added support for backend configurations so that Traffic Ops can act as a reverse proxy for these services [#6754](https://github.com/apache/trafficcontrol/pull/6754).
-- Added functionality for CDN locks, so that they can be shared amongst a list of specified usernames. 
+- Added functionality for CDN locks, so that they can be shared amongst a list of specified usernames.
 - [Traffic Ops | Traffic Go Clients | T3C] Add additional timestamp fields to server for queuing and dequeueing config and revalidate updates.
 - Added layered profile feature to 4.0 for `GET` /servers/, `POST` /servers/, `PUT` /servers/{id} and `DELETE` /servers/{id}.
 - Added a Traffic Ops endpoint and Traffic Portal page to view all CDNi configuration update requests and approve or deny.
-- Added layered profile feature to 4.0 for `GET` /servers/details.
 - Added layered profile feature to 4.0 for `GET` /deliveryservices/{id}/servers/ and /deliveryservices/{id}/servers/eligible.
 
 ### Fixed
@@ -57,6 +56,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 - Replace `forever` with `pm2` for process management of the traffic portal node server to remediate security issues.
 - Removed the Traffic Monitor `peer_polling_protocol` option. Traffic Monitor now just uses hostnames to request peer states, which can be handled via IPv4 or IPv6 depending on the underlying IP version in use.
 - Dropped CentOS 8 support
+- The `/servers/details` endpoint of the Traffic Ops API has been dropped in version 4.0, and marked deprecated in earlier versions.
 
 ### Changed
 - [#6654](https://github.com/apache/trafficcontrol/issues/6654) Traffic Monitor now uses the TO API 4.0 by default and falls back to 3.1
diff --git a/docs/source/api/v3/servers_details.rst b/docs/source/api/v3/servers_details.rst
index 4dfe2e96ec..f1e746aa45 100644
--- a/docs/source/api/v3/servers_details.rst
+++ b/docs/source/api/v3/servers_details.rst
@@ -20,6 +20,9 @@
 *******************
 Retrieves details of :ref:`tp-configure-servers`.
 
+.. deprecated:: 3.1
+	This endpoint has been removed from the latest version of the API, and clients are advised to use :ref:`to-api-v3-servers` instead.
+
 
 ``GET``
 =======
diff --git a/docs/source/api/v4/servers_details.rst b/docs/source/api/v4/servers_details.rst
deleted file mode 100644
index dc0a917b50..0000000000
--- a/docs/source/api/v4/servers_details.rst
+++ /dev/null
@@ -1,192 +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-servers-details:
-
-*******************
-``servers/details``
-*******************
-Retrieves details of :ref:`tp-configure-servers`.
-
-
-``GET``
-=======
-: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
-
-.. note:: On top of the response including the response key that is of type array it will also include the keys ``limit``, ``orderby``, and ``size``.
-
-Request Structure
------------------
-.. table:: Request Query Parameters
-
-	+----------------+----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
-	| Name           | Required                               | Description                                                                                                                                                    |
-	+================+========================================+================================================================================================================================================================+
-	| hostName       | Required if no physLocationID provided | Return only the servers with this (short) hostname. Capitalization of "hostName" is important.                                                                 |
-	+----------------+----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
-	| physLocationID | Required if no hostName provided       | Return only servers with this integral, unique identifier for the physical location where the server resides. Capitalization of "physLocationID" is important. |
-	+----------------+----------------------------------------+----------------------------------------------------------------------------------------------------------------------------------------------------------------+
-
-.. code-block:: http
-	:caption: Request Example
-
-	GET /api/4.0/servers/details?hostName=edge HTTP/1.1
-	User-Agent: python-requests/2.22.0
-	Accept-Encoding: gzip, deflate
-	Accept: */*
-	Connection: keep-alive
-	Cookie: mojolicious=...
-
-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 the details of a given :ref:`Server <tp-configure-servers>`.
-
-	:cachegroup:            A string that is the :ref:`name of the Cache Group <cache-group-name>` to which the server belongs
-	:cdnName:               Name of the CDN to which the server belongs
-	:deliveryservices:      An array of integral, unique identifiers for :term:`Delivery Services` to which this 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\ [1]_
-	:iloIpGateway:          The IPv4 gateway address of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [1]_
-	:iloIpNetmask:          The IPv4 subnet mask of the server's :abbr:`ILO (Integrated Lights-Out)` service\ [1]_
-	:iloPassword:           The password of the of the server's :abbr:`ILO (Integrated Lights-Out)` service user\ [1]_ - displays as simply ``******`` if the currently logged-in user does not have the 'admin' or 'operations' :term:`Role(s) <Role>`
-	:iloUsername:           The user name for the server's :abbr:`ILO (Integrated Lights-Out)` service\ [1]_
-	: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 `0` 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 <https://en.wikipedia.org/wiki/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
-
-		:routerHostName:       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 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
-	:physLocation:          The name of the physical location where the server resides
-	:profileNames:          List of :ref:`profile-name` of the :term:`Profiles` used by this server
-	:rack:  A string indicating "server rack" location
-	:status:                The status of the 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
-	: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
-
-: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-Encoding: gzip
-	Content-Type: application/json
-	Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 24 Feb 2020 01:27:36 GMT; Max-Age=3600; HttpOnly
-	Whole-Content-Sha512: HW2F3CEpohNAvNlEDhUfXmtwpEka4dwUWFVUSSjW98aXiv10vI6ysRIcC2P9huabCz5fdHqY3tp0LR4ekwEHqw==
-	X-Server-Name: traffic_ops_golang/
-	Date: Mon, 24 Feb 2020 00:27:36 GMT
-	Content-Length: 493
-
-	{
-		"limit": 1000,
-		"orderby": "hostName",
-		"response": [
-			{
-				"cachegroup": "CDN_in_a_Box_Edge",
-				"cdnName": "CDN-in-a-Box",
-				"deliveryservices": [
-					1
-				],
-				"domainName": "infra.ciab.test",
-				"guid": null,
-				"hardwareInfo": null,
-				"hostName": "edge",
-				"httpsPort": 443,
-				"id": 5,
-				"iloIpAddress": "",
-				"iloIpGateway": "",
-				"iloIpNetmask": "",
-				"iloPassword": "",
-				"iloUsername": "",
-				"mgmtIpAddress": "",
-				"mgmtIpGateway": "",
-				"mgmtIpNetmask": "",
-				"offlineReason": "",
-				"physLocation": "Apachecon North America 2018",
-				"profileNames": ["ATS_EDGE_TIER_CACHE"],
-				"rack": "",
-				"status": "REPORTED",
-				"tcpPort": 80,
-				"type": "EDGE",
-				"xmppId": "edge",
-				"xmppPasswd": "",
-				"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",
-						"routerHostName": "",
-						"routerPortName": ""
-					}
-				]
-			}
-		],
-		"size": 1
-	}
-
-.. [1] For more information see the `Wikipedia page on Lights-Out management <https://en.wikipedia.org/wiki/Out-of-band_management>`_\ .
diff --git a/traffic_control/clients/python/trafficops/tosession.py b/traffic_control/clients/python/trafficops/tosession.py
index f907911f04..150b51311d 100644
--- a/traffic_control/clients/python/trafficops/tosession.py
+++ b/traffic_control/clients/python/trafficops/tosession.py
@@ -1648,13 +1648,16 @@ class TOSession(RestApiSession):
 		:raises: Union[LoginError, OperationError]
 		"""
 
-	@api_request('get', 'servers/details?hostName={name}', ('3.0','4.0',))
+	@api_request('get', 'servers/details?hostName={name}', ('3.0',))
 	def get_server_details(self, name=None):
 		"""
 		Get servers/details
-		:ref:`to-api-servers-details`
+		:ref:`to-api-v3-servers-details`
 		:rtype: Tuple[Union[Dict[str, Any], List[Dict[str, Any]]], requests.Response]
 		:raises: Union[LoginError, OperationError]
+
+		.. deprecated:: 3.0
+			The endpoint this represents has been removed from APIv4 and clients should use get_servers instead.
 		"""
 
 	@api_request('post', 'servercheck', ('3.0',))
diff --git a/traffic_ops/testing/api/v4/servers_test.go b/traffic_ops/testing/api/v4/servers_test.go
index 4adf0d8345..a6f1680a4d 100644
--- a/traffic_ops/testing/api/v4/servers_test.go
+++ b/traffic_ops/testing/api/v4/servers_test.go
@@ -42,7 +42,6 @@ func TestServers(t *testing.T) {
 		header.Set(rfc.IfUnmodifiedSince, time)
 		UpdateTestServers(t)
 		UpdateTestServersWithHeaders(t, header)
-		GetTestServersDetails(t)
 		GetTestServers(t)
 		GetTestServersIMSAfterChange(t, header)
 		GetTestServersQueryParameters(t)
@@ -657,38 +656,6 @@ func GetTestServers(t *testing.T) {
 	}
 }
 
-func GetTestServersDetails(t *testing.T) {
-	opts := client.NewRequestOptions()
-	for _, server := range testData.Servers {
-		if server.HostName == nil {
-			t.Errorf("found server with nil hostname: %+v", server)
-			continue
-		}
-		opts.QueryParameters.Set("hostName", *server.HostName)
-		resp, _, err := TOSession.GetServersDetails(opts)
-		if err != nil {
-			t.Errorf("cannot get Server Details: %v - alerts: %+v", err, resp.Alerts)
-		}
-		if len(resp.Response) == 0 {
-			t.Fatal("no servers in response, quitting")
-		}
-		if len(resp.Response[0].ServerInterfaces) == 0 {
-			t.Fatalf("no interfaces to check, quitting")
-		}
-		if len(server.Interfaces) == 0 {
-			t.Fatalf("no interfaces to check, quitting")
-		}
-
-		// just check the first interface for noe
-		if resp.Response[0].ServerInterfaces[0].RouterHostName != server.Interfaces[0].RouterHostName {
-			t.Errorf("expected router host name to be %s, but got %s", server.Interfaces[0].RouterHostName, resp.Response[0].ServerInterfaces[0].RouterHostName)
-		}
-		if resp.Response[0].ServerInterfaces[0].RouterPortName != server.Interfaces[0].RouterPortName {
-			t.Errorf("expected router port to be %s, but got %s", server.Interfaces[0].RouterPortName, resp.Response[0].ServerInterfaces[0].RouterPortName)
-		}
-	}
-}
-
 func GetTestServersQueryParameters(t *testing.T) {
 	dses, _, err := TOSession.GetDeliveryServices(client.RequestOptions{QueryParameters: url.Values{"xmlId": []string{"ds1"}}})
 	if err != nil {
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go b/traffic_ops/traffic_ops_golang/routing/routes.go
index cbfa250307..452ab76157 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -311,9 +311,6 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
 		{Version: api.Version{Major: 4, 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: 4834985993},
 		{Version: api.Version{Major: 4, 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: 4804982993},
 
-		//Server Details
-		{Version: api.Version{Major: 4, Minor: 0}, Method: http.MethodGet, Path: `servers/details/?$`, Handler: server.GetDetailParamHandler, 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: 42612647143},
-
 		//Server status
 		{Version: api.Version{Major: 4, 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: 4766638513},
 		{Version: api.Version{Major: 4, 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: 41894713},
diff --git a/traffic_ops/traffic_ops_golang/server/detail.go b/traffic_ops/traffic_ops_golang/server/detail.go
index 287e0e4011..a327a40595 100644
--- a/traffic_ops/traffic_ops_golang/server/detail.go
+++ b/traffic_ops/traffic_ops_golang/server/detail.go
@@ -28,6 +28,7 @@ import (
 
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
 
+	"github.com/apache/trafficcontrol/lib/go-log"
 	"github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/lib/go-util"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
@@ -36,27 +37,32 @@ import (
 	"github.com/lib/pq"
 )
 
+// GetDetailParamHandler handles GET requests to /servers/details (the name
+// includes "Param" for legacy reasons).
+//
+// Deprecated: This endpoint has been removed from APIv4.
 func GetDetailParamHandler(w http.ResponseWriter, r *http.Request) {
+	alt := "/servers"
 	inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
 	if userErr != nil || sysErr != nil {
-		api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+		api.HandleDeprecatedErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr, &alt)
 		return
 	}
 	defer inf.Close()
 
 	hostName := inf.Params["hostName"]
 	physLocationIDStr := inf.Params["physLocationID"]
-	physLocationID := -1
+	var physLocationID int
 	if physLocationIDStr != "" {
-		err := error(nil)
+		var err error
 		physLocationID, err = strconv.Atoi(physLocationIDStr)
 		if err != nil {
-			api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("physLocationID parameter is not an integer"), nil)
+			api.HandleDeprecatedErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("physLocationID parameter is not an integer"), err, &alt)
 			return
 		}
 	}
 	if hostName == "" && physLocationIDStr == "" {
-		api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("missing required fields: 'hostName' or 'physLocationID'"), nil)
+		api.HandleDeprecatedErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("missing required fields: 'hostName' or 'physLocationID'"), nil, &alt)
 		return
 	}
 	orderBy := "hostName"
@@ -65,22 +71,22 @@ func GetDetailParamHandler(w http.ResponseWriter, r *http.Request) {
 	}
 	limit := 1000
 	if limitStr, ok := inf.Params["limit"]; ok {
-		err := error(nil)
+		var err error
 		limit, err = strconv.Atoi(limitStr)
 		if err != nil {
-			api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("limit parameter is not an integer"), nil)
+			api.HandleDeprecatedErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("limit parameter is not an integer"), err, &alt)
 			return
 		}
 	}
 	servers, err := getDetailServers(inf.Tx.Tx, inf.User, hostName, physLocationID, util.CamelToSnakeCase(orderBy), limit, *inf.Version)
-	respVals := map[string]interface{}{
-		"orderby": orderBy,
-		"limit":   limit,
-		"size":    len(servers),
+	if err != nil {
+		api.HandleDeprecatedErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, err, &alt)
 	}
+	var resp interface{}
+	size := len(servers)
 
 	if inf.Version.Major <= 2 {
-		v11ServerList := []tc.ServerDetailV11{}
+		v11ServerList := make([]tc.ServerDetailV11, 0, size)
 		for _, server := range servers {
 			interfaces := server.ServerInterfaces
 			routerHostName := ""
@@ -93,24 +99,23 @@ func GetDetailParamHandler(w http.ResponseWriter, r *http.Request) {
 			v11server := tc.ServerDetailV11{}
 			v11server.ServerDetail, err = dbhelpers.GetServerDetailFromV4(server, inf.Tx.Tx)
 			if err != nil {
-				api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("failed to GetServerDetailFromV4: %w", err))
+				api.HandleDeprecatedErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("failed to GetServerDetailFromV4: %w", err), &alt)
 				return
 			}
 			v11server.RouterHostName = &routerHostName
 			v11server.RouterPortName = &routerPortName
 			legacyInterface, err := tc.V4InterfaceInfoToLegacyInterfaces(interfaces)
 			if err != nil {
-				api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("converting to server detail v11: "+err.Error()))
+				api.HandleDeprecatedErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("converting to server detail v11: %w", err), &alt)
 				return
 			}
 			v11server.LegacyInterfaceDetails = legacyInterface
 
 			v11ServerList = append(v11ServerList, v11server)
 		}
-		api.RespWriterVals(w, r, inf.Tx.Tx, respVals)(v11ServerList, err)
-		return
+		resp = v11ServerList
 	} else if inf.Version.Major <= 3 {
-		v3ServerList := []tc.ServerDetailV30{}
+		v3ServerList := make([]tc.ServerDetailV30, 0, size)
 		for _, server := range servers {
 			v3Server := tc.ServerDetailV30{}
 			interfaces := server.ServerInterfaces
@@ -123,33 +128,49 @@ func GetDetailParamHandler(w http.ResponseWriter, r *http.Request) {
 			}
 			v3Server.ServerDetail, err = dbhelpers.GetServerDetailFromV4(server, inf.Tx.Tx)
 			if err != nil {
-				api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("failed to GetServerDetailFromV4: %w", err))
+				api.HandleDeprecatedErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("failed to GetServerDetailFromV4: %w", err), &alt)
 				return
 			}
 			v3Server.RouterHostName = &routerHostName
 			v3Server.RouterPortName = &routerPortName
 			v3Interfaces, err := tc.V4InterfaceInfoToV3Interfaces(interfaces)
 			if err != nil {
-				api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("converting to server detail v3: "+err.Error()))
+				api.HandleDeprecatedErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("converting to server detail v3: %w", err), &alt)
 				return
 			}
 			v3Server.ServerInterfaces = &v3Interfaces
 			v3ServerList = append(v3ServerList, v3Server)
 		}
-		api.RespWriterVals(w, r, inf.Tx.Tx, respVals)(v3ServerList, err)
+		resp = v3ServerList
+	} else {
+		api.WriteRespAlertNotFound(w, r)
 		return
 	}
-	api.RespWriterVals(w, r, inf.Tx.Tx, respVals)(servers, err)
+
+	api.WriteRespVals(w, r, resp, map[string]interface{}{
+		"alerts":  api.CreateDeprecationAlerts(&alt).Alerts,
+		"limit":   limit,
+		"orderby": orderBy,
+		"size":    size,
+	})
 }
 
+// AddWhereClauseAndQuery adds a WHERE clause to the query given in `q` (does
+// NOT check for existing WHERE clauses or that the end of the string is the
+// proper place to put one!) that limits the query results to those with the
+// given hostname and/or Physical Location ID and, with orderByStr and limitStr
+// appended (in that order), returns the result of querying the given
+// transaction.
+// Use an empty string for the hostname to not filter by hostname, use -1 as
+// physLocationID to not filter by Physical Location.
 func AddWhereClauseAndQuery(tx *sql.Tx, q string, hostName string, physLocationID int, orderByStr string, limitStr string) (*sql.Rows, error) {
-	if hostName != "" && physLocationID != -1 {
+	if hostName != "" && physLocationID != 0 {
 		q += ` WHERE server.host_name = $1::text AND server.phys_location = $2::bigint` + orderByStr + limitStr
 		return tx.Query(q, hostName, physLocationID)
 	} else if hostName != "" {
 		q += ` WHERE server.host_name = $1::text` + orderByStr + limitStr
 		return tx.Query(q, hostName)
-	} else if physLocationID != -1 {
+	} else if physLocationID != 0 {
 		q += ` WHERE server.phys_location = $1::int` + orderByStr + limitStr
 		return tx.Query(q, physLocationID)
 	} else {
@@ -158,6 +179,40 @@ func AddWhereClauseAndQuery(tx *sql.Tx, q string, hostName string, physLocationI
 	}
 }
 
+const dataFetchQuery = `,
+cg.name AS cachegroup,
+cdn.name AS cdn_name,
+ARRAY(select deliveryservice from deliveryservice_server where server = server.id),
+server.domain_name,
+server.guid,
+server.host_name,
+server.https_port,
+server.ilo_ip_address,
+server.ilo_ip_gateway,
+server.ilo_ip_netmask,
+server.ilo_password,
+server.ilo_username,
+(SELECT address FROM ip_address WHERE service_address = true AND family(address) = 4 AND server = server.id) AS service_ip,
+(SELECT address FROM ip_address WHERE service_address = true AND family(address) = 6 AND server = server.id) AS service_ip6,
+(SELECT gateway FROM ip_address WHERE service_address = true AND family(address) = 4 AND server = server.id) AS service_gateway,
+(SELECT gateway FROM ip_address WHERE service_address = true AND family(address) = 6 AND server = server.id) AS service_gateway6,
+(SELECT host(netmask(ip_address.address)) FROM ip_address WHERE service_address = true AND family(address) = 4 AND server = server.id) AS service_netmask,
+(SELECT interface FROM ip_address WHERE service_address = true AND family(address) = 4 AND server = server.id) AS interface_name,
+(SELECT mtu FROM interface WHERE server.id = interface.server AND interface.name = (SELECT interface FROM ip_address WHERE service_address = true AND family(address) = 4 AND server = server.id)) AS interface_mtu,
+server.mgmt_ip_address,
+server.mgmt_ip_gateway,
+server.mgmt_ip_netmask,
+server.offline_reason,
+pl.name as phys_location,
+(SELECT ARRAY_AGG(profile_name) FROM server_profile WHERE server_profile.server=server.id) AS profile_name,
+server.rack,
+st.name as status,
+server.tcp_port,
+t.name as server_type,
+server.xmpp_id,
+server.xmpp_passwd
+`
+
 func getDetailServers(tx *sql.Tx, user *auth.CurrentUser, hostName string, physLocationID int, orderBy string, limit int, reqVersion api.Version) ([]tc.ServerDetailV40, error) {
 	allowedOrderByCols := map[string]string{
 		"":                "",
@@ -192,39 +247,6 @@ func getDetailServers(tx *sql.Tx, user *auth.CurrentUser, hostName string, physL
 		return nil, errors.New("orderBy '" + orderBy + "' not permitted")
 	}
 
-	dataFetchQuery := `,
-cg.name AS cachegroup,
-cdn.name AS cdn_name,
-ARRAY(select deliveryservice from deliveryservice_server where server = server.id),
-server.domain_name,
-server.guid,
-server.host_name,
-server.https_port,
-server.ilo_ip_address,
-server.ilo_ip_gateway,
-server.ilo_ip_netmask,
-server.ilo_password,
-server.ilo_username,
-(SELECT address FROM ip_address WHERE service_address = true AND family(address) = 4 AND server = server.id) AS service_ip,
-(SELECT address FROM ip_address WHERE service_address = true AND family(address) = 6 AND server = server.id) AS service_ip6,
-(SELECT gateway FROM ip_address WHERE service_address = true AND family(address) = 4 AND server = server.id) AS service_gateway,
-(SELECT gateway FROM ip_address WHERE service_address = true AND family(address) = 6 AND server = server.id) AS service_gateway6,
-(SELECT host(netmask(ip_address.address)) FROM ip_address WHERE service_address = true AND family(address) = 4 AND server = server.id) AS service_netmask,
-(SELECT interface FROM ip_address WHERE service_address = true AND family(address) = 4 AND server = server.id) AS interface_name,
-(SELECT mtu FROM interface WHERE server.id = interface.server AND interface.name = (SELECT interface FROM ip_address WHERE service_address = true AND family(address) = 4 AND server = server.id)) AS interface_mtu,
-server.mgmt_ip_address,
-server.mgmt_ip_gateway,
-server.mgmt_ip_netmask,
-server.offline_reason,
-pl.name as phys_location,
-(SELECT ARRAY_AGG(profile_name) FROM server_profile WHERE server_profile.server=server.id) AS profile_name,
-server.rack,
-st.name as status,
-server.tcp_port,
-t.name as server_type,
-server.xmpp_id,
-server.xmpp_passwd
-`
 	queryFormatString := `
 SELECT
 	server.id
@@ -247,42 +269,43 @@ JOIN type t ON server.type = t.id
 	}
 	idRows, err := AddWhereClauseAndQuery(tx, fmt.Sprintf(queryFormatString, ""), hostName, physLocationID, orderByStr, limitStr)
 	if err != nil {
-		return nil, errors.New("querying delivery service eligible servers: " + err.Error())
+		return nil, fmt.Errorf("querying delivery service eligible servers: %w", err)
 	}
-	defer idRows.Close()
+	defer log.Close(idRows, "getting IDs for server details names")
 	var serverIDs []int
 	for idRows.Next() {
 		var serverID *int
 		err := idRows.Scan(&serverID)
 		if err != nil {
-			return nil, errors.New("querying delivery service eligible server ids: " + err.Error())
+			return nil, fmt.Errorf("querying delivery service eligible server ids: %w", err)
 		}
 		serverIDs = append(serverIDs, *serverID)
 	}
 	serversMap, err := dbhelpers.GetServersInterfaces(serverIDs, tx)
 	if err != nil {
-		return nil, errors.New("unable to get server interfaces: " + err.Error())
+		return nil, fmt.Errorf("unable to get server interfaces: %w", err)
 	}
 	rows, err := AddWhereClauseAndQuery(tx, fmt.Sprintf(queryFormatString, dataFetchQuery), hostName, physLocationID, orderByStr, limitStr)
 	if err != nil {
-		return nil, errors.New("Error querying detail servers: " + err.Error())
+		return nil, fmt.Errorf("querying detail servers: %w", err)
 	}
 
-	defer rows.Close()
+	defer log.Close(rows, "getting server details data")
 	sIDs := []int{}
 	servers := []tc.ServerDetailV40{}
 
-	serviceAddress := util.StrPtr("")
-	service6Address := util.StrPtr("")
-	serviceGateway := util.StrPtr("")
-	service6Gateway := util.StrPtr("")
-	serviceNetmask := util.StrPtr("")
-	serviceInterface := util.StrPtr("")
-	serviceMtu := util.StrPtr("")
+	serviceAddress := new(string)
+	service6Address := new(string)
+	serviceGateway := new(string)
+	service6Gateway := new(string)
+	serviceNetmask := new(string)
+	serviceInterface := new(string)
+	serviceMtu := new(string)
 
 	for rows.Next() {
 		s := tc.ServerDetailV40{}
-		if err := rows.Scan(&s.ID,
+		err = rows.Scan(
+			&s.ID,
 			&s.CacheGroup,
 			&s.CDNName,
 			pq.Array(&s.DeliveryServiceIDs),
@@ -313,8 +336,10 @@ JOIN type t ON server.type = t.id
 			&s.TCPPort,
 			&s.Type,
 			&s.XMPPID,
-			&s.XMPPPasswd); err != nil {
-			return nil, errors.New("Error scanning detail server: " + err.Error())
+			&s.XMPPPasswd,
+		)
+		if err != nil {
+			return nil, fmt.Errorf("scanning detail server: %w", err)
 		}
 		s.ServerInterfaces = []tc.ServerInterfaceInfoV40{}
 		if interfacesMap, ok := serversMap[*s.ID]; ok {
@@ -335,16 +360,16 @@ JOIN type t ON server.type = t.id
 
 	rows, err = tx.Query(`SELECT serverid, description, val from hwinfo where serverid = ANY($1);`, pq.Array(sIDs))
 	if err != nil {
-		return nil, errors.New("Error querying detail servers hardware info: " + err.Error())
+		return nil, fmt.Errorf("querying detail servers hardware info: %w", err)
 	}
-	defer rows.Close()
+	defer log.Close(rows, "getting hwinfo data")
 	hwInfos := map[int]map[string]string{}
 	for rows.Next() {
 		serverID := 0
 		desc := ""
 		val := ""
 		if err := rows.Scan(&serverID, &desc, &val); err != nil {
-			return nil, errors.New("Error scanning detail server hardware info: " + err.Error())
+			return nil, fmt.Errorf("scanning detail server hardware info: %w", err)
 		}
 
 		hwInfo, ok := hwInfos[serverID]
diff --git a/traffic_ops/v3-client/server.go b/traffic_ops/v3-client/server.go
index 13b2fad97c..c7d026c3e6 100644
--- a/traffic_ops/v3-client/server.go
+++ b/traffic_ops/v3-client/server.go
@@ -204,6 +204,10 @@ func (to *Session) GetFirstServer(params *url.Values, header http.Header) (tc.Se
 	return firstServer, reqInf, err
 }
 
+// GetServerDetailsByHostNameWithHdr retrieves the "details" of all servers with
+// the given hostname.
+// Deprecated: Server "details" as a concept have been removed from the latest
+// version of the API, and clients should use GetServersWithHdr instead.
 func (to *Session) GetServerDetailsByHostNameWithHdr(hostName string, header http.Header) ([]tc.ServerDetailV30, toclientlib.ReqInf, error) {
 	v := url.Values{}
 	v.Add("hostName", hostName)
diff --git a/traffic_ops/v4-client/server.go b/traffic_ops/v4-client/server.go
index 59d3009488..ab56c42c47 100644
--- a/traffic_ops/v4-client/server.go
+++ b/traffic_ops/v4-client/server.go
@@ -29,9 +29,6 @@ const (
 	// apiServers is the API version-relative path to the /servers API
 	// endpoint.
 	apiServers = "/servers"
-	// apiServersDetails is the API version-relative path to the
-	// /servers/details API endpoint.
-	apiServersDetails = "/servers/details"
 )
 
 func needAndCanFetch(id *int, name *string) bool {
@@ -127,14 +124,6 @@ func (to *Session) GetServers(opts RequestOptions) (tc.ServersV4Response, toclie
 	return data, reqInf, err
 }
 
-// GetServersDetails retrieves the Server Details of the Server with the given
-// (short) Hostname.
-func (to *Session) GetServersDetails(opts RequestOptions) (tc.ServersV4DetailResponse, toclientlib.ReqInf, error) {
-	var data tc.ServersV4DetailResponse
-	reqInf, err := to.get(apiServersDetails, 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)