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 <[email protected]>
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
+}

Reply via email to