You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by oc...@apache.org on 2022/08/09 16:46:38 UTC

[trafficcontrol] branch master updated: Added a new route and logic for assigning multiple server capabilities to a server (#6996)

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

ocket8888 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 073e431438 Added a new route and logic for assigning multiple server capabilities to a server (#6996)
073e431438 is described below

commit 073e431438716a5b7c4624762abe9d77999dcbde
Author: Rima Shah <22...@users.noreply.github.com>
AuthorDate: Tue Aug 9 10:46:30 2022 -0600

    Added a new route and logic for assigning multiple server capabilities to a server (#6996)
    
    * Added a new route and logic for assigning multiple server capabilities to a server.
    
    * Added tests for assigning multiple server capabilities to a server.
    
    * Updated CHANGELOG.md
    
    * Updated test based on latest table driven model
    
    * Added documentation for new route.
    
    * Fixed spacing.
    
    * Fixed spacing.
    
    * Updated based on review comments.
    
    * Updated based on review comments-1.
    
    * Added both API versions to allow client flexibility.
    
    * Updated order of API versions.
    
    * Removed `id` parameter.
    
    * Updated based on another set of review comments.
    
    * Updated test based on route change
    
    * updated format.
    
    * Revert "updated format."
    
    This reverts commit 8181b518391ea1d31c9e6b2b33c6e48a12c92b0c.
    
    * removed format and updated docs
---
 CHANGELOG.md                                       |  3 +
 .../source/api/v4/multiple_server_capabilities.rst | 83 +++++++++++++++++++++
 lib/go-tc/server_server_capability.go              |  6 ++
 .../api/v4/server_server_capabilities_test.go      | 25 ++++++-
 traffic_ops/traffic_ops_golang/routing/routes.go   |  3 +
 .../server/servers_server_capability.go            | 86 ++++++++++++++++++++++
 traffic_ops/v4-client/endpoints.go                 |  1 +
 .../v4-client/server_server_capabilities.go        | 10 +++
 8 files changed, 216 insertions(+), 1 deletion(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 9a1acf2b8c..78c1b611a7 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -4,6 +4,9 @@ All notable changes to this project will be documented in this file.
 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) Added ability to assign multiple server capabilities to a server.
+
 ### Changed
 - Traffic Portal now obscures sensitive text in Delivery Service "Raw Remap" fields, private SSL keys, "Header Rewrite" rules, and ILO interface passwords by default.
 
diff --git a/docs/source/api/v4/multiple_server_capabilities.rst b/docs/source/api/v4/multiple_server_capabilities.rst
new file mode 100644
index 0000000000..37f56b1549
--- /dev/null
+++ b/docs/source/api/v4/multiple_server_capabilities.rst
@@ -0,0 +1,83 @@
+..
+..
+.. Licensed under the Apache License, Version 2.0 (the "License");
+.. you may not use this file except in compliance with the License.
+.. You may obtain a copy of the License at
+..
+..     http://www.apache.org/licenses/LICENSE-2.0
+..
+.. Unless required by applicable law or agreed to in writing, software
+.. distributed under the License is distributed on an "AS IS" BASIS,
+.. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+.. See the License for the specific language governing permissions and
+.. limitations under the License.
+..
+
+.. _to-api-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/lib/go-tc/server_server_capability.go b/lib/go-tc/server_server_capability.go
index 6c406bfa2f..535a95da7f 100644
--- a/lib/go-tc/server_server_capability.go
+++ b/lib/go-tc/server_server_capability.go
@@ -27,6 +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"`
+	ServerCapabilities []string `json:"serverCapabilities" db:"server_capability"`
+}
+
 // ServerServerCapabilitiesResponse is the type of a response from Traffic
 // Ops to a request made to its /server_server_capabilities.
 type ServerServerCapabilitiesResponse struct {
diff --git a/traffic_ops/testing/api/v4/server_server_capabilities_test.go b/traffic_ops/testing/api/v4/server_server_capabilities_test.go
index 71ca9dc9be..b7519355c4 100644
--- a/traffic_ops/testing/api/v4/server_server_capabilities_test.go
+++ b/traffic_ops/testing/api/v4/server_server_capabilities_test.go
@@ -37,6 +37,7 @@ func TestServerServerCapabilities(t *testing.T) {
 
 		currentTime := time.Now().UTC().Add(-15 * time.Second)
 		tomorrow := currentTime.AddDate(0, 0, 1).Format(time.RFC1123)
+		var multipleSCs []string
 
 		methodTests := utils.V4TestCase{
 			"GET": {
@@ -147,6 +148,16 @@ func TestServerServerCapabilities(t *testing.T) {
 					Expectations: utils.CkRequest(utils.HasError(), utils.HasStatus(http.StatusBadRequest)),
 				},
 			},
+			"PUT": {
+				"OK When Assigned Multiple Server Capabilities": {
+					ClientSession: TOSession,
+					RequestBody: map[string]interface{}{
+						"serverId":           GetServerID(t, "dtrc-mid-04")(),
+						"serverCapabilities": append(multipleSCs, "disk", "blah"),
+					},
+					Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+				},
+			},
 			"DELETE": {
 				"OK when NOT the LAST SERVER of CACHE GROUP of TOPOLOGY DS which has REQUIRED CAPABILITIES": {
 					ClientSession: TOSession,
@@ -180,13 +191,18 @@ func TestServerServerCapabilities(t *testing.T) {
 			t.Run(method, func(t *testing.T) {
 				for name, testCase := range testCases {
 					ssc := tc.ServerServerCapability{}
+					msc := tc.MultipleServerCapabilities{}
 					var serverId int
 					var serverCapability string
 
 					if testCase.RequestBody != nil {
 						dat, err := json.Marshal(testCase.RequestBody)
 						assert.NoError(t, err, "Error occurred when marshalling request body: %v", err)
-						err = json.Unmarshal(dat, &ssc)
+						if method == "PUT" {
+							err = json.Unmarshal(dat, &msc)
+						} else {
+							err = json.Unmarshal(dat, &ssc)
+						}
 						assert.NoError(t, err, "Error occurred when unmarshalling request body: %v", err)
 					}
 
@@ -205,6 +221,13 @@ func TestServerServerCapabilities(t *testing.T) {
 								check(t, reqInf, nil, alerts, err)
 							}
 						})
+					case "PUT":
+						t.Run(name, func(t *testing.T) {
+							alerts, reqInf, err := testCase.ClientSession.AssignMultipleServerCapability(msc, testCase.RequestOpts, serverId)
+							for _, check := range testCase.Expectations {
+								check(t, reqInf, nil, alerts, err)
+							}
+						})
 					case "DELETE":
 						t.Run(name, func(t *testing.T) {
 							if val, ok := testCase.RequestOpts.QueryParameters["serverId"]; ok {
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go b/traffic_ops/traffic_ops_golang/routing/routes.go
index 2c02779b60..7089c120bf 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -131,6 +131,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},
+
 		// 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},
 		{Version: api.Version{Major: 4, Minor: 0}, Method: http.MethodPut, Path: `OC/CI/configuration/?$`, Handler: cdni.PutConfiguration, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"CDNI-CAPACITY:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 541357729078},
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 64291f21cd..21ae31db2a 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 (
+	"encoding/json"
 	"errors"
 	"fmt"
 	"net/http"
@@ -441,3 +442,88 @@ 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) {
+	inf, userErr, sysErr, errCode := api.NewInfo(r, nil, nil)
+	tx := inf.Tx.Tx
+	if userErr != nil || sysErr != nil {
+		api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+		return
+	}
+	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)
+		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)
+	}
+	if !exists {
+		userErr := fmt.Errorf("server %d does not exist", msc.ServerID)
+		api.HandleErr(w, r, tx, http.StatusNotFound, userErr, nil)
+		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))
+		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)
+		return
+	}
+
+	cdnName, err := dbhelpers.GetCDNNameFromServerID(tx, int64(msc.ServerID))
+	if err != nil {
+		api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, err)
+		return
+	}
+
+	userErr, sysErr, errCode = dbhelpers.CheckIfCurrentUserCanModifyCDN(tx, string(cdnName), inf.User.UserName)
+	if userErr != nil || sysErr != nil {
+		api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+		return
+	}
+
+	//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)
+		return
+	}
+
+	multipleServerCapabilities := make([]string, 0, len(msc.ServerCapabilities))
+
+	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)`
+
+	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)
+	return
+}
diff --git a/traffic_ops/v4-client/endpoints.go b/traffic_ops/v4-client/endpoints.go
index fd9478cd1c..470afe26da 100644
--- a/traffic_ops/v4-client/endpoints.go
+++ b/traffic_ops/v4-client/endpoints.go
@@ -22,6 +22,7 @@ package client
 // Versions are ordered latest-first.
 func apiVersions() []string {
 	return []string{
+		"4.1",
 		"4.0",
 	}
 }
diff --git a/traffic_ops/v4-client/server_server_capabilities.go b/traffic_ops/v4-client/server_server_capabilities.go
index 9cd03733a7..dd17238517 100644
--- a/traffic_ops/v4-client/server_server_capabilities.go
+++ b/traffic_ops/v4-client/server_server_capabilities.go
@@ -27,6 +27,9 @@ 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"
+
 // CreateServerServerCapability assigns a Server Capability to a Server.
 func (to *Session) CreateServerServerCapability(ssc tc.ServerServerCapability, opts RequestOptions) (tc.Alerts, toclientlib.ReqInf, error) {
 	var alerts tc.Alerts
@@ -53,3 +56,10 @@ func (to *Session) GetServerServerCapabilities(opts RequestOptions) (tc.ServerSe
 	reqInf, err := to.get(apiServerServerCapabilities, opts, &resp)
 	return resp, reqInf, err
 }
+
+// AssignMultipleServerCapability assigns multiple server capabilities to a server.
+func (to *Session) AssignMultipleServerCapability(msc tc.MultipleServerCapabilities, opts RequestOptions, id int) (tc.Alerts, toclientlib.ReqInf, error) {
+	var alerts tc.Alerts
+	reqInf, err := to.put(apiMultipleServerCapabilities, opts, msc, &alerts)
+	return alerts, reqInf, err
+}