Skip to content
This repository was archived by the owner on Nov 24, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
83 changes: 83 additions & 0 deletions docs/source/api/v4/multiple_server_capabilities.rst
Original file line number Diff line number Diff line change
@@ -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``
********************************

Comment thread
rimashah25 marked this conversation as resolved.
.. 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"]
}
}
6 changes: 6 additions & 0 deletions lib/go-tc/server_server_capability.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Comment thread
rimashah25 marked this conversation as resolved.
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 {
Expand Down
25 changes: 24 additions & 1 deletion traffic_ops/testing/api/v4/server_server_capabilities_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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": {
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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)
}

Expand All @@ -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 {
Expand Down
3 changes: 3 additions & 0 deletions traffic_ops/traffic_ops_golang/routing/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down
86 changes: 86 additions & 0 deletions traffic_ops/traffic_ops_golang/server/servers_server_capability.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ package server
*/

import (
"encoding/json"
"errors"
"fmt"
"net/http"
Expand Down Expand Up @@ -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
}
1 change: 1 addition & 0 deletions traffic_ops/v4-client/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ package client
// Versions are ordered latest-first.
func apiVersions() []string {
return []string{
"4.1",
Comment thread
rimashah25 marked this conversation as resolved.
"4.0",
}
}
10 changes: 10 additions & 0 deletions traffic_ops/v4-client/server_server_capabilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
}