You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by zr...@apache.org on 2023/05/18 18:07:41 UTC

[trafficcontrol] branch master updated: Fixes Server Capabilities apis to respond with RFC3339 date/time Format #7408 (#7482)

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

zrhoffman 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 147db8d363 Fixes Server Capabilities apis to respond with RFC3339 date/time Format #7408 (#7482)
147db8d363 is described below

commit 147db8d363859de9595c1cd5105d6f0f16277ff7
Author: Jagan Parthiban <33...@users.noreply.github.com>
AuthorDate: Thu May 18 23:37:34 2023 +0530

    Fixes Server Capabilities apis to respond with RFC3339 date/time Format #7408 (#7482)
    
    * Fixes Server Capabilities V5 Traffic Ops API to use and respond with RFC3339 date/time strings
    
    * Update CHANGELOG.md with issue details
    
    * Update .rst doc files for RF3339 in server capabilities.
    Issue: https://github.com/apache/trafficcontrol/issues/7465
    
    * Updated error message
---
 CHANGELOG.md                                       |   1 +
 docs/source/api/v5/server_capabilities.rst         |  20 +--
 lib/go-tc/server_capabilities.go                   |  30 ++++
 .../testing/api/v5/server_capabilities_test.go     |  54 ++++----
 traffic_ops/testing/api/v5/traffic_control_test.go |   2 +-
 traffic_ops/traffic_ops_golang/routing/routes.go   |   6 +-
 .../servercapability/servercapability.go           | 154 +++++++++++++++++++++
 traffic_ops/v5-client/servercapability.go          |  18 +--
 8 files changed, 235 insertions(+), 50 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 55737cb8fc..1c63c242c7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -56,6 +56,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 ### Fixed
 - [#7511](https://github.com/apache/trafficcontrol/pull/7511) *Traffic Ops* Fixed the changelog registration message to include the username instead of duplicate email entry.
 - [#7505](https://github.com/apache/trafficcontrol/pull/7505) *Traffic Portal* Fix an issue where a Delivery Service with Geo Limit Countries Set was unable to be updated.
+- [#7465](https://github.com/apache/trafficcontrol/issues/7465) *Traffic Ops* Fixes server_capabilities v5 apis to respond with RFC3339 date/time Format
 - [#7441](https://github.com/apache/trafficcontrol/pull/7441) *Traffic Ops* Fixed the invalidation jobs endpoint to respect CDN locks.
 - [#7413](https://github.com/apache/trafficcontrol/issues/7413) *Traffic Ops* Fixes service_category apis to respond with RFC3339 date/time Format
 - [#7414](https://github.com/apache/trafficcontrol/pull/7414) * Traffic Portal* Fixed DSR difference for DS required capability.
diff --git a/docs/source/api/v5/server_capabilities.rst b/docs/source/api/v5/server_capabilities.rst
index 4827ca8ad7..ad68ca501d 100644
--- a/docs/source/api/v5/server_capabilities.rst
+++ b/docs/source/api/v5/server_capabilities.rst
@@ -51,7 +51,7 @@ Response Structure
 ------------------
 :name:        The name of this :term:`Server Capability`
 :description: The description of this :term:`Server Capability`
-:lastUpdated: The date and time at which this :term:`Server Capability` was last updated, in ISO-like format
+:lastUpdated: The date and time at which this :term:`Server Capability` was last updated, in :rfc:`3339` Format
 
 .. code-block:: http
 	:caption: Response Example
@@ -65,7 +65,7 @@ Response Structure
 	Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly
 	Whole-Content-Sha512: EH8jo8OrCu79Tz9xpgT3YRyKJ/p2NcTmbS3huwtqRByHz9H6qZLQjA59RIPaVSq3ZxsU6QhTaox5nBkQ9LPSAA==
 	X-Server-Name: traffic_ops_golang/
-	Date: Mon, 07 Oct 2019 21:36:13 GMT
+	Date: Wed, 03 May 2023 07:03:45 GMT
 	Content-Length: 68
 
 	{
@@ -73,7 +73,7 @@ Response Structure
 			{
 				"name": "RAM",
 				"description": "ram server capability",
-				"lastUpdated": "2019-10-07 20:38:24+00"
+				"lastUpdated": "2023-05-03T12:24:40.409579+05:30"
 			}
 		]
 	}
@@ -112,7 +112,7 @@ Response Structure
 ------------------
 :name:        The name of this :term:`Server Capability`
 :description: The description of this :term:`Server Capability`
-:lastUpdated: The date and time at which this :term:`Server Capability` was last updated, in ISO-like format
+:lastUpdated: The date and time at which this :term:`Server Capability` was last updated, in :rfc:`3339` Format
 
 .. code-block:: http
 	:caption: Response Example
@@ -126,7 +126,7 @@ Response Structure
 	Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly
 	Whole-Content-Sha512: ysdopC//JQI79BRUa61s6M2HzHxYHpo5RdcuauOoqCYxiVOoUhNZfOVydVkv8zDN2qA374XKnym4kWj3VzQIXg==
 	X-Server-Name: traffic_ops_golang/
-	Date: Mon, 07 Oct 2019 22:10:00 GMT
+	Date: Wed, 03 May 2023 07:02:02 GMT
 	Content-Length: 137
 
 	{
@@ -139,7 +139,7 @@ Response Structure
 		"response": {
 			"name": "RAM",
 			"description": "ram server capability",
-			"lastUpdated": "2019-10-07 22:10:00+00"
+			"lastUpdated": "2023-05-03T12:24:40.409579+05:30"
 		}
 	}
 
@@ -177,7 +177,7 @@ Response Structure
 ------------------
 :name:        The name of this :term:`Server Capability`
 :description: The description of this :term:`Server Capability`
-:lastUpdated: The date and time at which this :term:`Server Capability` was last updated, in ISO-like format
+:lastUpdated: The date and time at which this :term:`Server Capability` was last updated, in :rfc:`3339` Format
 
 .. code-block:: http
 	:caption: Response Example
@@ -191,7 +191,7 @@ Response Structure
 	Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly
 	Whole-Content-Sha512: ysdopC//JQI79BRUa61s6M2HzHxYHpo5RdcuauOoqCYxiVOoUhNZfOVydVkv8zDN2qA374XKnym4kWj3VzQIXg==
 	X-Server-Name: traffic_ops_golang/
-	Date: Wed, 03 March 2021 21:22:08 GMT
+	Date: Wed, 03 May 2023 07:02:02 GMT
 	Content-Length: 137
 
 	{
@@ -204,7 +204,7 @@ Response Structure
 		"response": {
 			"name": "HDD",
 			"description": "HDD server capability",
-			"lastUpdated": "2021-03-03 21:22:08+00"
+			"lastUpdated": "2023-05-03T12:24:40.409579+05:30"
 		}
 	}
 
@@ -251,7 +251,7 @@ Response Structure
 	Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly
 	Whole-Content-Sha512: 8zCAATbCzcqiqigGVBy7WF1duDuXu1Wg2DBe9yfqTw/c+yhE2eUk73hFTA/Oqt0kocaN7+1GkbFdPkQPvbnRaA==
 	X-Server-Name: traffic_ops_golang/
-	Date: Mon, 07 Oct 2019 20:44:40 GMT
+	Date: Wed, 03 May 2023 07:02:02 GMT
 	Content-Length: 72
 
 	{
diff --git a/lib/go-tc/server_capabilities.go b/lib/go-tc/server_capabilities.go
index 328cb6933f..943118e2a7 100644
--- a/lib/go-tc/server_capabilities.go
+++ b/lib/go-tc/server_capabilities.go
@@ -19,6 +19,8 @@ package tc
  * under the License.
  */
 
+import "time"
+
 // ServerCapabilitiesResponse contains the result data from a GET /server_capabilities request.
 type ServerCapabilitiesResponse struct {
 	Response []ServerCapability `json:"response"`
@@ -57,3 +59,31 @@ type ServerCapabilityDetailResponseV41 struct {
 	Response ServerCapabilityV41 `json:"response"`
 	Alerts
 }
+
+// ServerCapabilityV5 is an alias for the latest minor version for the major version 5.
+type ServerCapabilityV5 ServerCapabilityV51
+
+// ServerCapabilityV51 contains information about a given serverCapability in Traffic Ops V5.
+type ServerCapabilityV51 struct {
+	Name        string    `json:"name" db:"name"`
+	LastUpdated time.Time `json:"lastUpdated" db:"last_updated"`
+	Description string    `json:"description" db:"description"`
+}
+
+// ServerCapabilitiesResponseV5 is an alias for the latest minor version for the major version 5.
+type ServerCapabilitiesResponseV5 ServerCapabilitiesResponseV51
+
+// ServerCapabilitiesResponseV51 contains the result data from a GET(v5.1 and above) /server_capabilities request.
+type ServerCapabilitiesResponseV51 struct {
+	Response []ServerCapabilityV5 `json:"response"`
+	Alerts
+}
+
+// ServerCapabilityDetailResponseV5 is an alias for the latest minor version for the major version 5.
+type ServerCapabilityDetailResponseV5 ServerCapabilityDetailResponseV51
+
+// ServerCapabilityDetailResponseV51 contains the result data from a POST(v5.1 and above) /server_capabilities request.
+type ServerCapabilityDetailResponseV51 struct {
+	Response ServerCapabilityV5 `json:"response"`
+	Alerts
+}
diff --git a/traffic_ops/testing/api/v5/server_capabilities_test.go b/traffic_ops/testing/api/v5/server_capabilities_test.go
index b5f678356d..36645126b1 100644
--- a/traffic_ops/testing/api/v5/server_capabilities_test.go
+++ b/traffic_ops/testing/api/v5/server_capabilities_test.go
@@ -36,7 +36,7 @@ func TestServerCapabilities(t *testing.T) {
 		currentTime := time.Now().UTC().Add(-15 * time.Second)
 		currentTimeRFC := currentTime.Format(time.RFC1123)
 
-		methodTests := utils.TestCase[client.Session, client.RequestOptions, tc.ServerCapabilityV41]{
+		methodTests := utils.TestCase[client.Session, client.RequestOptions, tc.ServerCapabilityV5]{
 			"GET": {
 				"OK when VALID request": {
 					ClientSession: TOSession,
@@ -57,17 +57,17 @@ func TestServerCapabilities(t *testing.T) {
 			"POST": {
 				"BAD REQUEST when ALREADY EXISTS": {
 					ClientSession: TOSession,
-					RequestBody: tc.ServerCapabilityV41{
-						ServerCapability: tc.ServerCapability{Name: "foo"},
-						Description:      "foo servers",
+					RequestBody: tc.ServerCapabilityV5{
+						Name:        "foo",
+						Description: "foo servers",
 					},
 					Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
 				},
 				"BAD REQUEST when INVALID NAME": {
 					ClientSession: TOSession,
-					RequestBody: tc.ServerCapabilityV41{
-						ServerCapability: tc.ServerCapability{Name: "b@dname"},
-						Description:      "Server Capability",
+					RequestBody: tc.ServerCapabilityV5{
+						Name:        "b@dname",
+						Description: "Server Capability",
 					},
 					Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
 				},
@@ -76,9 +76,9 @@ func TestServerCapabilities(t *testing.T) {
 				"OK when VALID request": {
 					ClientSession: TOSession,
 					RequestOpts:   client.RequestOptions{QueryParameters: url.Values{"name": {"blah"}}},
-					RequestBody: tc.ServerCapabilityV41{
-						ServerCapability: tc.ServerCapability{Name: "newname"},
-						Description:      "Server Capability for new name",
+					RequestBody: tc.ServerCapabilityV5{
+						Name:        "newname",
+						Description: "Server Capability for new name",
 					},
 					Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK),
 						validateServerCapabilitiesUpdateFields(map[string]interface{}{"Name": "newname"}),
@@ -87,9 +87,9 @@ func TestServerCapabilities(t *testing.T) {
 				"BAD REQUEST when NAME DOESNT EXIST": {
 					ClientSession: TOSession,
 					RequestOpts:   client.RequestOptions{QueryParameters: url.Values{"name": {"invalid"}}},
-					RequestBody: tc.ServerCapabilityV41{
-						ServerCapability: tc.ServerCapability{Name: "newname"},
-						Description:      "Server Capability for new name",
+					RequestBody: tc.ServerCapabilityV5{
+						Name:        "newname",
+						Description: "Server Capability for new name",
 					},
 					Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
 				},
@@ -99,9 +99,9 @@ func TestServerCapabilities(t *testing.T) {
 						QueryParameters: url.Values{"name": {"disk"}},
 						Header:          http.Header{rfc.IfUnmodifiedSince: {currentTimeRFC}},
 					},
-					RequestBody: tc.ServerCapabilityV41{
-						ServerCapability: tc.ServerCapability{Name: "newname"},
-						Description:      "Server Capability for new name",
+					RequestBody: tc.ServerCapabilityV5{
+						Name:        "newname",
+						Description: "Server Capability for new name",
 					},
 					Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)),
 				},
@@ -111,9 +111,9 @@ func TestServerCapabilities(t *testing.T) {
 						QueryParameters: url.Values{"name": {"disk"}},
 						Header:          http.Header{rfc.IfMatch: {rfc.ETag(currentTime)}},
 					},
-					RequestBody: tc.ServerCapabilityV41{
-						ServerCapability: tc.ServerCapability{Name: "newname"},
-						Description:      "Server Capability for new name",
+					RequestBody: tc.ServerCapabilityV5{
+						Name:        "newname",
+						Description: "Server Capability for new name",
 					},
 					Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusPreconditionFailed)),
 				},
@@ -138,21 +138,21 @@ func TestServerCapabilities(t *testing.T) {
 					switch method {
 					case "GET":
 						t.Run(name, func(t *testing.T) {
-							resp, reqInf, err := testCase.ClientSession.GetServerCapabilities(testCase.RequestOpts)
+							resp, reqInf, err := testCase.ClientSession.GetServerCapabilitiesV5(testCase.RequestOpts)
 							for _, check := range testCase.Expectations {
 								check(t, reqInf, resp.Response, resp.Alerts, err)
 							}
 						})
 					case "POST":
 						t.Run(name, func(t *testing.T) {
-							resp, reqInf, err := testCase.ClientSession.CreateServerCapability(testCase.RequestBody, testCase.RequestOpts)
+							resp, reqInf, err := testCase.ClientSession.CreateServerCapabilityV5(testCase.RequestBody, testCase.RequestOpts)
 							for _, check := range testCase.Expectations {
 								check(t, reqInf, resp.Response, resp.Alerts, err)
 							}
 						})
 					case "PUT":
 						t.Run(name, func(t *testing.T) {
-							resp, reqInf, err := testCase.ClientSession.UpdateServerCapability(testCase.RequestOpts.QueryParameters["name"][0], testCase.RequestBody, testCase.RequestOpts)
+							resp, reqInf, err := testCase.ClientSession.UpdateServerCapabilityV5(testCase.RequestOpts.QueryParameters["name"][0], testCase.RequestBody, testCase.RequestOpts)
 							for _, check := range testCase.Expectations {
 								check(t, reqInf, resp.Response, resp.Alerts, err)
 							}
@@ -174,7 +174,7 @@ func TestServerCapabilities(t *testing.T) {
 func validateServerCapabilitiesUpdateFields(expectedResp map[string]interface{}) utils.CkReqFunc {
 	return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, _ tc.Alerts, _ error) {
 		assert.RequireNotNil(t, resp, "Expected Server Capabilities response to not be nil.")
-		serverCapabilitiesResp := resp.(tc.ServerCapabilityV41)
+		serverCapabilitiesResp := resp.(tc.ServerCapabilityV5)
 		for field, expected := range expectedResp {
 			switch field {
 			case "Name":
@@ -190,7 +190,7 @@ func validateServerCapabilitiesSort() utils.CkReqFunc {
 	return func(t *testing.T, _ toclientlib.ReqInf, resp interface{}, alerts tc.Alerts, _ error) {
 		assert.RequireNotNil(t, resp, "Expected Server Capabilities response to not be nil.")
 		var serverCapabilityNames []string
-		serverCapabilitiesResp := resp.([]tc.ServerCapabilityV41)
+		serverCapabilitiesResp := resp.([]tc.ServerCapabilityV5)
 		for _, serverCapability := range serverCapabilitiesResp {
 			serverCapabilityNames = append(serverCapabilityNames, serverCapability.Name)
 		}
@@ -200,13 +200,13 @@ func validateServerCapabilitiesSort() utils.CkReqFunc {
 
 func CreateTestServerCapabilities(t *testing.T) {
 	for _, sc := range testData.ServerCapabilities {
-		resp, _, err := TOSession.CreateServerCapability(sc, client.RequestOptions{})
+		resp, _, err := TOSession.CreateServerCapabilityV5(sc, client.RequestOptions{})
 		assert.RequireNoError(t, err, "Unexpected error creating Server Capability '%s': %v - alerts: %+v", sc.Name, err, resp.Alerts)
 	}
 }
 
 func DeleteTestServerCapabilities(t *testing.T) {
-	serverCapabilities, _, err := TOSession.GetServerCapabilities(client.RequestOptions{})
+	serverCapabilities, _, err := TOSession.GetServerCapabilitiesV5(client.RequestOptions{})
 	assert.NoError(t, err, "Cannot get Server Capabilities: %v - alerts: %+v", err, serverCapabilities.Alerts)
 
 	for _, serverCapability := range serverCapabilities.Response {
@@ -215,7 +215,7 @@ func DeleteTestServerCapabilities(t *testing.T) {
 		// Retrieve the Server Capability to see if it got deleted
 		opts := client.NewRequestOptions()
 		opts.QueryParameters.Set("name", serverCapability.Name)
-		getServerCapability, _, err := TOSession.GetServerCapabilities(opts)
+		getServerCapability, _, err := TOSession.GetServerCapabilitiesV5(opts)
 		assert.NoError(t, err, "Error getting Server Capability '%s' after deletion: %v - alerts: %+v", serverCapability.Name, err, getServerCapability.Alerts)
 		assert.Equal(t, 0, len(getServerCapability.Response), "Expected Server Capability '%s' to be deleted, but it was found in Traffic Ops", serverCapability.Name)
 	}
diff --git a/traffic_ops/testing/api/v5/traffic_control_test.go b/traffic_ops/testing/api/v5/traffic_control_test.go
index b6673951c8..bd18b2b446 100644
--- a/traffic_ops/testing/api/v5/traffic_control_test.go
+++ b/traffic_ops/testing/api/v5/traffic_control_test.go
@@ -47,7 +47,7 @@ type TrafficControl struct {
 	Roles                                             []tc.RoleV4                             `json:"roles"`
 	Servers                                           []tc.ServerV4                           `json:"servers"`
 	ServerServerCapabilities                          []tc.ServerServerCapability             `json:"serverServerCapabilities"`
-	ServerCapabilities                                []tc.ServerCapabilityV41                `json:"serverCapabilities"`
+	ServerCapabilities                                []tc.ServerCapabilityV5                 `json:"serverCapabilities"`
 	ServiceCategories                                 []tc.ServiceCategoryV5                  `json:"serviceCategories"`
 	Statuses                                          []tc.StatusNullable                     `json:"statuses"`
 	StaticDNSEntries                                  []tc.StaticDNSEntry                     `json:"staticdnsentries"`
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go b/traffic_ops/traffic_ops_golang/routing/routes.go
index c5fbb8ba47..8a993a4752 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -325,9 +325,9 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
 		{Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `servers/{id}$`, Handler: server.Delete, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"SERVER:DELETE", "SERVER:READ", "DELIVERY-SERVICE:READ", "CDN:READ", "PHYSICAL-LOCATION:READ", "CACHE-GROUP:READ", "TYPE:READ", "PROFILE:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 49232223331},
 
 		//Server Capability
-		{Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `server_capabilities$`, Handler: servercapability.GetServerCapability, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"SERVER-CAPABILITY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 41040739131},
-		{Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `server_capabilities$`, Handler: servercapability.CreateServerCapability, 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: servercapability.UpdateServerCapability, 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.MethodGet, Path: `server_capabilities$`, Handler: servercapability.GetServerCapabilityV5, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"SERVER-CAPABILITY:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 41040739131},
+		{Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `server_capabilities$`, Handler: servercapability.CreateServerCapabilityV5, 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: servercapability.UpdateServerCapabilityV5, 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: servercapability.DeleteServerCapability, 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.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},
diff --git a/traffic_ops/traffic_ops_golang/servercapability/servercapability.go b/traffic_ops/traffic_ops_golang/servercapability/servercapability.go
index 2f92436805..834a11e0e9 100644
--- a/traffic_ops/traffic_ops_golang/servercapability/servercapability.go
+++ b/traffic_ops/traffic_ops_golang/servercapability/servercapability.go
@@ -367,3 +367,157 @@ func (v *TOServerCapability) SelectMaxLastUpdatedQuery(where, orderBy, paginatio
 
 func (v *TOServerCapability) Create() (error, error, int) { return api.GenericCreateNameBasedID(v) }
 func (v *TOServerCapability) Delete() (error, error, int) { return api.GenericDelete(v) }
+
+func GetServerCapabilityV5(w http.ResponseWriter, r *http.Request) {
+	var sc tc.ServerCapabilityV5
+	inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+	tx := inf.Tx
+	if userErr != nil || sysErr != nil {
+		api.HandleErr(w, r, tx.Tx, errCode, userErr, sysErr)
+		return
+	}
+	defer inf.Close()
+
+	// Query Parameters to Database Query column mappings
+	queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+		"name": {Column: "sc.name", Checker: nil},
+	}
+	if _, ok := inf.Params["orderby"]; !ok {
+		inf.Params["orderby"] = "name"
+	}
+	where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, queryParamsToQueryCols)
+	if len(errs) > 0 {
+		api.HandleErr(w, r, tx.Tx, http.StatusBadRequest, util.JoinErrs(errs), nil)
+		return
+	}
+
+	selectQuery := "SELECT name, description, last_updated FROM server_capability sc"
+	query := selectQuery + where + orderBy + pagination
+	rows, err := tx.NamedQuery(query, queryValues)
+	if err != nil {
+		api.HandleErr(w, r, tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("server capability read: error getting server capability(ies): %w", err))
+		return
+	}
+	defer log.Close(rows, "unable to close DB connection")
+
+	scList := []tc.ServerCapabilityV5{}
+	for rows.Next() {
+		if err = rows.Scan(&sc.Name, &sc.Description, &sc.LastUpdated); err != nil {
+			api.HandleErr(w, r, tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("error getting server capability(ies): %w", err))
+			return
+		}
+		scList = append(scList, sc)
+	}
+
+	api.WriteResp(w, r, scList)
+	return
+}
+
+func CreateServerCapabilityV5(w http.ResponseWriter, r *http.Request) {
+	inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+	if userErr != nil || sysErr != nil {
+		api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+		return
+	}
+	defer inf.Close()
+	tx := inf.Tx.Tx
+
+	sc, readValErr := readAndValidateJsonStructV5(r)
+	if readValErr != nil {
+		api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+		return
+	}
+
+	// check if capability already exists
+	var count int
+	err := tx.QueryRow("SELECT count(*) from server_capability where name = $1", sc.Name).Scan(&count)
+	if err != nil {
+		api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("error: %w, when checking if server capability with name %s exists", err, sc.Name))
+		return
+	}
+	if count == 1 {
+		api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("server_capability name '%s' already exists.", sc.Name), nil)
+		return
+	}
+
+	// create server capability
+	query := `INSERT INTO server_capability (name, description) VALUES ($1, $2) RETURNING last_updated`
+	err = tx.QueryRow(query, sc.Name, sc.Description).Scan(&sc.LastUpdated)
+	if err != nil {
+		if errors.Is(err, sql.ErrNoRows) {
+			api.HandleErr(w, r, tx, http.StatusInternalServerError, fmt.Errorf("error: %w in creating server capability with name: %s", err, sc.Name), nil)
+			return
+		}
+		usrErr, sysErr, code := api.ParseDBError(err)
+		api.HandleErr(w, r, tx, code, usrErr, sysErr)
+		return
+	}
+	alerts := tc.CreateAlerts(tc.SuccessLevel, "server capability was created.")
+	w.Header().Set("Location", fmt.Sprintf("/api/%d.%d/server_capabilities?name=%s", inf.Version.Major, inf.Version.Minor, sc.Name))
+	api.WriteAlertsObj(w, r, http.StatusCreated, alerts, sc)
+	return
+}
+
+func UpdateServerCapabilityV5(w http.ResponseWriter, r *http.Request) {
+	inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+	if userErr != nil || sysErr != nil {
+		api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+		return
+	}
+	defer inf.Close()
+
+	tx := inf.Tx.Tx
+	sc, readValErr := readAndValidateJsonStructV5(r)
+	if readValErr != nil {
+		api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+		return
+	}
+
+	requestedName := inf.Params["name"]
+	// check if the entity was already updated
+	userErr, sysErr, errCode = api.CheckIfUnModifiedByName(r.Header, inf.Tx, requestedName, "server_capability")
+	if userErr != nil || sysErr != nil {
+		api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+		return
+	}
+
+	//update name and description of a capability
+	query := `UPDATE server_capability sc SET
+		name = $1,
+		description = $2
+	WHERE sc.name = $3
+	RETURNING sc.name, sc.description, sc.last_updated`
+
+	err := tx.QueryRow(query, sc.Name, sc.Description, requestedName).Scan(&sc.Name, &sc.Description, &sc.LastUpdated)
+	if err != nil {
+		if errors.Is(err, sql.ErrNoRows) {
+			api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("server capability with name: %s not found", sc.Name), nil)
+			return
+		}
+		usrErr, sysErr, code := api.ParseDBError(err)
+		api.HandleErr(w, r, tx, code, usrErr, sysErr)
+		return
+	}
+	alerts := tc.CreateAlerts(tc.SuccessLevel, "server capability was updated")
+	api.WriteAlertsObj(w, r, http.StatusOK, alerts, sc)
+	return
+}
+
+func readAndValidateJsonStructV5(r *http.Request) (tc.ServerCapabilityV5, error) {
+	var sc tc.ServerCapabilityV5
+	if err := json.NewDecoder(r.Body).Decode(&sc); err != nil {
+		userErr := fmt.Errorf("error decoding POST request body into ServerCapabilityV5 struct %w", err)
+		return sc, userErr
+	}
+
+	// validate JSON body
+	rule := validation.NewStringRule(tovalidate.IsAlphanumericUnderscoreDash, "must consist of only alphanumeric, dash, or underscore characters")
+	errs := tovalidate.ToErrors(validation.Errors{
+		"name": validation.Validate(sc.Name, validation.Required, rule),
+	})
+	if len(errs) > 0 {
+		userErr := util.JoinErrs(errs)
+		return sc, userErr
+	}
+	return sc, nil
+}
diff --git a/traffic_ops/v5-client/servercapability.go b/traffic_ops/v5-client/servercapability.go
index 9e9d8567c3..766dec468e 100644
--- a/traffic_ops/v5-client/servercapability.go
+++ b/traffic_ops/v5-client/servercapability.go
@@ -26,27 +26,27 @@ import (
 // endpoint.
 const apiServerCapabilities = "/server_capabilities"
 
-// CreateServerCapability creates the given Server Capability.
-func (to *Session) CreateServerCapability(sc tc.ServerCapabilityV41, opts RequestOptions) (tc.ServerCapabilityDetailResponseV41, toclientlib.ReqInf, error) {
-	var scResp tc.ServerCapabilityDetailResponseV41
+// CreateServerCapabilityV5 creates the given Server Capability.
+func (to *Session) CreateServerCapabilityV5(sc tc.ServerCapabilityV5, opts RequestOptions) (tc.ServerCapabilityDetailResponseV5, toclientlib.ReqInf, error) {
+	var scResp tc.ServerCapabilityDetailResponseV5
 	reqInf, err := to.post(apiServerCapabilities, opts, sc, &scResp)
 	return scResp, reqInf, err
 }
 
-// GetServerCapabilities returns all the Server Capabilities in Traffic Ops.
-func (to *Session) GetServerCapabilities(opts RequestOptions) (tc.ServerCapabilitiesResponseV41, toclientlib.ReqInf, error) {
-	var data tc.ServerCapabilitiesResponseV41
+// GetServerCapabilitiesV5 returns all the Server Capabilities in Traffic Ops.
+func (to *Session) GetServerCapabilitiesV5(opts RequestOptions) (tc.ServerCapabilitiesResponseV5, toclientlib.ReqInf, error) {
+	var data tc.ServerCapabilitiesResponseV5
 	reqInf, err := to.get(apiServerCapabilities, opts, &data)
 	return data, reqInf, err
 }
 
-// UpdateServerCapability updates a Server Capability by name.
-func (to *Session) UpdateServerCapability(name string, sc tc.ServerCapabilityV41, opts RequestOptions) (tc.ServerCapabilityDetailResponseV41, toclientlib.ReqInf, error) {
+// UpdateServerCapabilityV5 updates a Server Capability by name.
+func (to *Session) UpdateServerCapabilityV5(name string, sc tc.ServerCapabilityV5, opts RequestOptions) (tc.ServerCapabilityDetailResponseV5, toclientlib.ReqInf, error) {
 	if opts.QueryParameters == nil {
 		opts.QueryParameters = url.Values{}
 	}
 	opts.QueryParameters.Set("name", name)
-	var data tc.ServerCapabilityDetailResponseV41
+	var data tc.ServerCapabilityDetailResponseV5
 	reqInf, err := to.put(apiServerCapabilities, opts, sc, &data)
 	return data, reqInf, err
 }