You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by rs...@apache.org on 2023/08/24 01:59:49 UTC

[trafficcontrol] branch master updated: Fixes ProfileParameters V5 apis to respond with RFC3339 date/time Format (#7738)

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

rshah 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 7ce9214315 Fixes ProfileParameters V5 apis to respond with RFC3339 date/time Format (#7738)
7ce9214315 is described below

commit 7ce921431500b7fd0c567c5d0726c35ca7f78f25
Author: Jagan Parthiban <33...@users.noreply.github.com>
AuthorDate: Thu Aug 24 07:29:42 2023 +0530

    Fixes ProfileParameters V5 apis to respond with RFC3339 date/time Format (#7738)
    
    * RFC3339 Changes for ProfileParameters. Fixes https://github.com/apache/trafficcontrol/issues/7737
    
    * CHANGELOG.md entry added
    
    * Fixed PR review comments
    
    * Fixed PR review comments
    
    * Remove strconv function and use APIInfo
---
 CHANGELOG.md                                       |   1 +
 docs/source/api/v5/profileparameters.rst           |  79 ++++--
 lib/go-tc/parameters.go                            |  33 +++
 lib/go-tc/profile_parameters.go                    |  36 +++
 traffic_ops/testing/api/v5/cdn_locks_test.go       |   2 +-
 .../testing/api/v5/profile_parameters_test.go      |   2 +-
 traffic_ops/testing/api/v5/traffic_control_test.go |   2 +-
 .../traffic_ops_golang/dbhelpers/db_helpers.go     |  15 +
 .../profileparameter/profile_parameters.go         | 302 +++++++++++++++++++++
 traffic_ops/traffic_ops_golang/routing/routes.go   |   8 +-
 traffic_ops/v5-client/profile_parameter.go         |   4 +-
 11 files changed, 458 insertions(+), 26 deletions(-)

diff --git a/CHANGELOG.md b/CHANGELOG.md
index 16eaf2d80f..1a46e6c808 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -77,6 +77,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
 - [#7742](https://github.com/apache/trafficcontrol/pull/7742) *Traffic Ops* Changed api tests to supply the absolute path of certs.
 
 ### Fixed
+- [#7738](https://github.com/apache/trafficcontrol/pull/7738) *Traffic Ops* Fixes ProfileParameters V5 apis to respond with RFC3339 date/time Format
 - [#7730](https://github.com/apache/trafficcontrol/pull/7730) *Traffic Monitor* Fixed the panic seen in TM when `plugin.system_stats.timestamp_ms` appears as float and not string.
 - [#4393](https://github.com/apache/trafficcontrol/issues/4393) *Traffic Ops* Fixed the error code and alert structure when TO is queried for a delivery service with no ssl keys.
 - [#7690](https://github.com/apache/trafficcontrol/pull/7690) *Traffic Ops* Fixes Logs V5 api to respond with RFC3339 timestamps.
diff --git a/docs/source/api/v5/profileparameters.rst b/docs/source/api/v5/profileparameters.rst
index 58e184d3ae..efdd82a354 100644
--- a/docs/source/api/v5/profileparameters.rst
+++ b/docs/source/api/v5/profileparameters.rst
@@ -52,7 +52,7 @@ Request Structure
 
 Response Structure
 ------------------
-:lastUpdated: The date and time at which this :term:`Profile`/:term:`Parameter` association was last modified, in :ref:`non-rfc-datetime`
+:lastUpdated: The date and time at which this :term:`Profile`/:term:`Parameter` association was last modified, in :rfc:`3339`
 :parameter:   The :ref:`parameter-id` of a :term:`Parameter` assigned to ``profile``
 :profile:     The :ref:`profile-name` of the :term:`Profile` to which the :term:`Parameter` identified by ``parameter`` is assigned
 
@@ -73,12 +73,12 @@ Response Structure
 
 	{ "response": [
 		{
-			"lastUpdated": "2018-12-05 17:50:49+00",
+			"lastUpdated": "2018-12-05T21:44:14.410503+05:30",
 			"profile": "GLOBAL",
 			"parameter": 4
 		},
 		{
-			"lastUpdated": "2018-12-05 17:50:49+00",
+			"lastUpdated": "2018-12-05T21:44:14.410503+05:30",
 			"profile": "GLOBAL",
 			"parameter": 5
 		}
@@ -152,7 +152,7 @@ Array Format
 
 Response Structure
 ------------------
-:lastUpdated: The date and time at which the :term:`Profile`/:term:`Parameter` assignment was last modified, in :ref:`non-rfc-datetime`
+:lastUpdated: The date and time at which the :term:`Profile`/:term:`Parameter` assignment was last modified, in :rfc:`3339`
 :parameter:   :ref:`parameter-name` of the :term:`Parameter` which is assigned to ``profile``
 :parameterId: The :ref:`parameter-id` of the assigned :term:`Parameter`
 :profile:     :ref:`profile-name` of the :term:`Profile` to which the :term:`Parameter` is assigned
@@ -173,16 +173,61 @@ Response Structure
 	Date: Mon, 10 Dec 2018 13:50:11 GMT
 	Content-Length: 166
 
-	{ "alerts": [
-		{
-			"text": "profileParameter was created.",
-			"level": "success"
-		}
-	],
-	"response": {
-		"lastUpdated": null,
-		"profile": null,
-		"profileId": 18,
-		"parameter": null,
-		"parameterId": 1
-	}}
+    {
+      "alerts": [
+        {
+          "text": "All Requested ProfileParameters were created.",
+          "level": "success"
+        }
+      ],
+      "response": [
+        {
+          "lastUpdated": "2018-12-05T21:44:14.410503+05:30",
+          "profile": "testProfile1",
+          "profileId": 18,
+          "parameter": "testParam1",
+          "parameterId": 1
+        }
+      ]
+    }
+
+.. code-block:: http
+	:caption: Response Example - Array Format
+
+	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, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly
+	Whole-Content-Sha512: eDmIwlzX44fZdxLRPHMNa8aoGAK5fQv9Y70A2eeQHfEkliU4evwcsQ4WeHcH0l3/wPTGlpyC0gwLo8LQQpUxWQ==
+	X-Server-Name: traffic_ops_golang/
+	Date: Mon, 10 Dec 2018 13:50:11 GMT
+	Content-Length: 166
+
+
+    {
+      "alerts": [
+        {
+          "text": "All Requested Profile_Parameters were created.",
+          "level": "success"
+        }
+      ],
+      "response": [
+        {
+          "lastUpdated": "2018-12-05T21:44:14.410503+05:30",
+          "profile": "test1",
+          "profileId": 17,
+          "parameter": "tm.toolname",
+          "parameterId": 1091
+        },
+        {
+          "lastUpdated": "2018-12-05T21:44:14.410503+05:30",
+          "profile": "test2",
+          "profileId": 18,
+          "parameter": "maxRevalDurationDays",
+          "parameterId": 1091
+        }
+      ]
+    }
\ No newline at end of file
diff --git a/lib/go-tc/parameters.go b/lib/go-tc/parameters.go
index 57cce3206f..8b6a84aa5f 100644
--- a/lib/go-tc/parameters.go
+++ b/lib/go-tc/parameters.go
@@ -27,6 +27,7 @@ import (
 	"fmt"
 	"strconv"
 	"strings"
+	"time"
 
 	"github.com/apache/trafficcontrol/lib/go-util"
 
@@ -295,6 +296,16 @@ type ProfileParametersNullable struct {
 	Parameter   *int       `json:"parameter" db:"parameter_id"`
 }
 
+// ProfileParametersNullableV5 is the latest minor version of the major version 5
+type ProfileParametersNullableV5 ProfileParametersNullableV50
+
+// ProfileParametersNullableV50 is an object of the form returned by the Traffic Ops /profileparameters endpoint.
+type ProfileParametersNullableV50 struct {
+	LastUpdated *time.Time `json:"lastUpdated" db:"last_updated"`
+	Profile     *string    `json:"profile" db:"profile"`
+	Parameter   *int       `json:"parameter" db:"parameter_id"`
+}
+
 // ProfileParametersNullableResponse is the structure of a response from
 // Traffic Ops to GET requests made to its /profileparameters API endpoint.
 //
@@ -315,6 +326,18 @@ type ProfileParam struct {
 	LastUpdated *TimeNoMod `json:"lastUpdated"`
 }
 
+// ProfileParamV5 is the latest minor version of the major version 5
+type ProfileParamV5 ProfileParamV50
+
+// ProfileParamV50 is a relationship between a Profile and some Parameter
+// assigned to it as it appears in the Traffic Ops API's responses to the
+// /profileparameters endpoint.
+type ProfileParamV50 struct {
+	Parameter   int        `json:"parameter"`
+	Profile     string     `json:"profile"`
+	LastUpdated *time.Time `json:"lastUpdated"`
+}
+
 // ProfileParameterCreationRequest is the type of data accepted by Traffic
 // Ops as payloads in POST requests to its /profileparameters endpoint.
 type ProfileParameterCreationRequest struct {
@@ -329,6 +352,16 @@ type ProfileParametersAPIResponse struct {
 	Alerts
 }
 
+// ProfileParametersAPIResponseV5 is the latest minor version of the major version 5
+type ProfileParametersAPIResponseV5 ProfileParametersAPIResponseV50
+
+// ProfileParametersAPIResponseV50 is the type of a response from Traffic Ops to
+// requests made to its /profileparameters endpoint.
+type ProfileParametersAPIResponseV50 struct {
+	Response []ProfileParamV5 `json:"response"`
+	Alerts
+}
+
 // ProfileExportImportParameterNullable is an object of the form used by Traffic Ops
 // to represent parameters for exported and imported profiles.
 type ProfileExportImportParameterNullable struct {
diff --git a/lib/go-tc/profile_parameters.go b/lib/go-tc/profile_parameters.go
index f4ff9d7070..7cbef1ccc8 100644
--- a/lib/go-tc/profile_parameters.go
+++ b/lib/go-tc/profile_parameters.go
@@ -19,6 +19,8 @@ package tc
  * under the License.
  */
 
+import "time"
+
 // ProfileParametersResponse is the type of the response from Traffic Ops to
 // GET requests made to its /profileparameters API endpoint.
 type ProfileParametersResponse struct {
@@ -59,3 +61,37 @@ type ProfileParameterNullable struct {
 	Parameter   *string    `json:"parameter" db:"parameter"`
 	ParameterID *int       `json:"parameterId" db:"parameter_id"`
 }
+
+// ProfileParametersResponseV5 is the type of the response from Traffic Ops to
+// GET requests made to its /profileparameters API endpoint.
+type ProfileParametersResponseV5 struct {
+	Response []ProfileParameterV5 `json:"response"`
+	Alerts
+}
+
+// ProfileParameterResponseV5 is a single ProfileParameter response for Create to
+// depict what changed.
+// swagger:response ProfileParameterResponse
+// in: body
+type ProfileParameterResponseV5 struct {
+	// in: body
+	Response ProfileParameterV5 `json:"response"`
+	Alerts
+}
+
+// ProfileParameterV5 is the latest minor version of the major version 5
+type ProfileParameterV5 ProfileParameterV50
+
+// ProfileParameterV50 is a representation of a relationship between a Parameter
+// and a Profile to which it is assigned.
+//
+// Note that not all unique identifiers for each represented object in this
+// relationship structure are guaranteed to be populated by the Traffic Ops
+// API.
+type ProfileParameterV50 struct {
+	LastUpdated time.Time `json:"lastUpdated"`
+	Profile     string    `json:"profile"`
+	ProfileID   int       `json:"profileId"`
+	Parameter   string    `json:"parameter"`
+	ParameterID int       `json:"parameterId"`
+}
diff --git a/traffic_ops/testing/api/v5/cdn_locks_test.go b/traffic_ops/testing/api/v5/cdn_locks_test.go
index 583d22cb41..04e36a2293 100644
--- a/traffic_ops/testing/api/v5/cdn_locks_test.go
+++ b/traffic_ops/testing/api/v5/cdn_locks_test.go
@@ -326,7 +326,7 @@ func TestCDNLocks(t *testing.T) {
 						"profileId":   GetProfileID(t, "EDGEInCDN2")(),
 						"parameterId": GetParameterID(t, "CONFIG proxy.config.admin.user_id", "records.config", "STRING ats")(),
 					},
-					Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+					Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusCreated)),
 				},
 				"FORBIDDEN when ADMIN USER DOESNT OWN LOCK": {
 					ClientSession: TOSession,
diff --git a/traffic_ops/testing/api/v5/profile_parameters_test.go b/traffic_ops/testing/api/v5/profile_parameters_test.go
index d1bb2576a9..16976a8ef3 100644
--- a/traffic_ops/testing/api/v5/profile_parameters_test.go
+++ b/traffic_ops/testing/api/v5/profile_parameters_test.go
@@ -68,7 +68,7 @@ func TestProfileParameters(t *testing.T) {
 							},
 						},
 					},
-					Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusOK)),
+					Expectations: utils.CkRequest(utils.NoError(), utils.HasStatus(http.StatusCreated)),
 				},
 				"BAD REQUEST when INVALID PROFILEID and PARAMETERID": {
 					ClientSession: TOSession,
diff --git a/traffic_ops/testing/api/v5/traffic_control_test.go b/traffic_ops/testing/api/v5/traffic_control_test.go
index 4d9db708b5..6fa619b368 100644
--- a/traffic_ops/testing/api/v5/traffic_control_test.go
+++ b/traffic_ops/testing/api/v5/traffic_control_test.go
@@ -41,7 +41,7 @@ type TrafficControl struct {
 	Origins                                           []tc.Origin                             `json:"origins"`
 	Profiles                                          []tc.Profile                            `json:"profiles"`
 	Parameters                                        []tc.Parameter                          `json:"parameters"`
-	ProfileParameters                                 []tc.ProfileParameter                   `json:"profileParameters"`
+	ProfileParameters                                 []tc.ProfileParameterV5                 `json:"profileParameters"`
 	PhysLocations                                     []tc.PhysLocationV5                     `json:"physLocations"`
 	Regions                                           []tc.RegionV5                           `json:"regions"`
 	Roles                                             []tc.RoleV4                             `json:"roles"`
diff --git a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
index c70056279b..464502c2d2 100644
--- a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
+++ b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
@@ -2304,3 +2304,18 @@ func DeleteCoordinate(tx *sql.Tx, cacheGroupID int, coordinateID int) error {
 	}
 	return nil
 }
+
+// ProfileParameterExists confirms whether the ProfileParameter exists, and an error (if one occurs).
+func ProfileParameterExists(tx *sql.Tx, profileID string, parameterID string) (bool, error) {
+	var count int
+	if err := tx.QueryRow("SELECT count(*) FROM profile_parameter WHERE profile=$1 and parameter=$2", profileID, parameterID).Scan(&count); err != nil {
+		return false, fmt.Errorf("error getting profile_parameter info: %w", err)
+	}
+	if count == 0 {
+		return false, nil
+	}
+	if count != 1 {
+		return false, fmt.Errorf("getting profile_parameter info - expected row count: 1, actual: %d", count)
+	}
+	return true, nil
+}
diff --git a/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters.go b/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters.go
index 85846682f3..507f806e9a 100644
--- a/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters.go
+++ b/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters.go
@@ -20,16 +20,23 @@ package profileparameter
  */
 
 import (
+	"database/sql"
+	"encoding/json"
 	"errors"
+	"fmt"
+	"io"
 	"net/http"
+	"reflect"
 	"strconv"
 	"time"
 
+	"github.com/apache/trafficcontrol/lib/go-log"
 	"github.com/apache/trafficcontrol/lib/go-tc"
 	"github.com/apache/trafficcontrol/lib/go-tc/tovalidate"
 	"github.com/apache/trafficcontrol/lib/go-util"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
 	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
+	"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/util/ims"
 
 	validation "github.com/go-ozzo/ozzo-validation"
 )
@@ -212,3 +219,298 @@ func deleteQuery() string {
 	WHERE profile=:profile_id and parameter=:parameter_id`
 	return query
 }
+
+func GetProfileParameter(w http.ResponseWriter, r *http.Request) {
+	var runSecond bool
+	var maxTime time.Time
+	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{
+		"profileId":   {Column: "pp.profile"},
+		"parameterId": {Column: "pp.parameter"},
+		"lastUpdated": {Column: "pp.last_updated"},
+	}
+	if _, ok := inf.Params["orderby"]; !ok {
+		inf.Params["orderby"] = "parameter"
+	}
+	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
+	}
+
+	if inf.Config.UseIMS {
+		runSecond, maxTime = ims.TryIfModifiedSinceQuery(tx, r.Header, queryValues, selectMaxLastUpdatedQuery(where))
+		if !runSecond {
+			log.Debugln("IMS HIT")
+			api.AddLastModifiedHdr(w, maxTime)
+			w.WriteHeader(http.StatusNotModified)
+			return
+		}
+		log.Debugln("IMS MISS")
+	} else {
+		log.Debugln("Non IMS request")
+	}
+
+	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("Profile Parameter read: error getting Profile Parameter(s): %w", err))
+		return
+	}
+	defer log.Close(rows, "unable to close DB connection")
+
+	profileParams := tc.ProfileParametersNullableV5{}
+	profileParamsList := []tc.ProfileParametersNullableV5{}
+	for rows.Next() {
+		if err = rows.Scan(&profileParams.LastUpdated, &profileParams.Parameter, &profileParams.Profile); err != nil {
+			api.HandleErr(w, r, tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("error getting profile parameter(s): %w", err))
+			return
+		}
+
+		profileParamsList = append(profileParamsList, profileParams)
+	}
+
+	api.WriteResp(w, r, profileParamsList)
+	return
+}
+
+func CreateProfileParameter(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
+
+	body, err := io.ReadAll(r.Body)
+	if err != nil {
+		api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("error reading request body"), nil)
+		return
+	}
+	defer r.Body.Close()
+
+	// Initial Unmarshal to validate request body
+	var data interface{}
+	err = json.Unmarshal(body, &data)
+	if err != nil {
+		api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("invalid request format"), nil)
+		return
+	}
+
+	// This code block decides if the request body is a slice of parameters or a single object.
+	var profileParams []tc.ProfileParameterCreationRequest
+	switch reflect.TypeOf(data).Kind() {
+	case reflect.Slice:
+		if err := json.Unmarshal(body, &profileParams); err != nil {
+			api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("error unmarshalling slice"), nil)
+			return
+		}
+	case reflect.Map:
+		// If it is a single object it is still converted to a slice for code simplicity.
+		var profileParam tc.ProfileParameterCreationRequest
+		if err := json.Unmarshal(body, &profileParam); err != nil {
+			api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("error unmarshalling single object"), nil)
+			return
+		}
+		profileParams = append(profileParams, profileParam)
+	default:
+		api.HandleErr(w, r, tx, http.StatusBadRequest, errors.New("invalid request format"), nil)
+		return
+	}
+
+	// Validate all objects of the every profile parameter from the request slice
+	for _, profileParameter := range profileParams {
+		readValErr := validateRequestProfileParameter(profileParameter)
+		if readValErr != nil {
+			api.HandleErr(w, r, tx, http.StatusBadRequest, readValErr, nil)
+			return
+		}
+	}
+
+	// Check user Permissions on all Profiles requested
+	for _, profileParameter := range profileParams {
+		cdnName, err := dbhelpers.GetCDNNameFromProfileID(tx, profileParameter.ProfileID)
+		if err != nil {
+			api.HandleErr(w, r, tx, http.StatusInternalServerError, err, nil)
+			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
+		}
+	}
+
+	// Check if any of the profile parameter from the request slice already exists
+	for _, profileParameter := range profileParams {
+		var count int
+		err = tx.QueryRow("SELECT count(*) from profile_parameter where profile = $1 and parameter = $2", profileParameter.ProfileID, profileParameter.ParameterID).Scan(&count)
+		if err != nil {
+			api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("error: %w, when checking if profile parameter with profile_id %d and parameter_id %d exists", err, profileParameter.ProfileID, profileParameter.ParameterID))
+			return
+		}
+		if count == 1 {
+			api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("profile parameter with profile_id %d and parameter_id %d already exists", profileParameter.ProfileID, profileParameter.ParameterID), nil)
+			return
+		}
+	}
+
+	// Create all profile parameters from the request slice
+	var objProfileParams []tc.ProfileParameterV5
+	for _, profileParameter := range profileParams {
+		query := `
+				INSERT INTO profile_parameter (
+				profile, 
+				parameter
+				) VALUES (
+				$1, $2
+				) RETURNING profile, parameter, last_updated
+`
+		var objProfileParam tc.ProfileParameterV5
+		err = tx.QueryRow(
+			query,
+			profileParameter.ProfileID,
+			profileParameter.ParameterID,
+		).Scan(
+			&objProfileParam.ProfileID,
+			&objProfileParam.ParameterID,
+			&objProfileParam.LastUpdated,
+		)
+
+		if err != nil {
+			if errors.Is(err, sql.ErrNoRows) {
+				api.HandleErr(w, r, tx, http.StatusInternalServerError, fmt.Errorf("error: %w in profile_parameter with with profile_id %d and parameter_id %d", err, profileParameter.ProfileID, profileParameter.ParameterID), nil)
+				return
+			}
+			usrErr, sysErr, code := api.ParseDBError(err)
+			api.HandleErr(w, r, tx, code, usrErr, sysErr)
+			return
+		}
+
+		// Fetch the Profile Name from ID to insert in type ProfileParameterV5
+		profileName, ok, err := dbhelpers.GetProfileNameFromID(profileParameter.ProfileID, tx)
+		if err != nil {
+			api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting profile name from id: "+err.Error()))
+			return
+		} else if !ok {
+			api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, errors.New("profile not found"), nil)
+			return
+		}
+
+		// Fetch the Parameter Name from ID to insert in type ProfileParameterV5
+		parameterName, ok, err := dbhelpers.GetParamNameByID(tx, profileParameter.ParameterID)
+		if err != nil {
+			api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting parameter name from id: "+err.Error()))
+			return
+		} else if !ok {
+			api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, errors.New("parameter not found"), nil)
+			return
+		}
+
+		objProfileParam.Profile = profileName
+		objProfileParam.Parameter = parameterName
+		objProfileParams = append(objProfileParams, objProfileParam)
+	}
+	alerts := tc.CreateAlerts(tc.SuccessLevel, "All Requested ProfileParameters were created.")
+	api.WriteAlertsObj(w, r, http.StatusCreated, alerts, objProfileParams)
+	return
+
+}
+
+func DeleteProfileParameter(w http.ResponseWriter, r *http.Request) {
+	inf, userErr, sysErr, errCode := api.NewInfo(r, nil, []string{"profileId"})
+	tx := inf.Tx.Tx
+	if userErr != nil || sysErr != nil {
+		api.HandleErr(w, r, tx, errCode, userErr, sysErr)
+		return
+	}
+	defer inf.Close()
+
+	profileID := inf.Params["profileId"]
+	parameterID := inf.Params["parameterId"]
+	intProfileID := inf.IntParams["profileId"]
+
+	if profileID == "" || parameterID == "" {
+		api.HandleErr(w, r, tx, http.StatusBadRequest, fmt.Errorf("couldn't delete Profile_Parameter. profileID  & parameterID Cannot be empty for Delete Operation"), nil)
+		return
+	}
+
+	cdnName, err := dbhelpers.GetCDNNameFromProfileID(tx, intProfileID)
+	if err != nil {
+		api.HandleErr(w, r, tx, http.StatusInternalServerError, err, nil)
+		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
+	}
+
+	exists, err := dbhelpers.ProfileParameterExists(tx, profileID, parameterID)
+	if err != nil {
+		api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, err)
+		return
+	}
+	if !exists {
+		api.HandleErr(w, r, tx, http.StatusNotFound, fmt.Errorf("no profile_parameter exists by profile_id: %s & parameter_id: %s", profileID, parameterID), nil)
+		return
+	}
+
+	res, err := tx.Exec("DELETE FROM profile_parameter AS pp WHERE pp.profile=$1 and parameter=$2", profileID, parameterID)
+	if err != nil {
+		api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, err)
+		return
+	}
+	rowsAffected, err := res.RowsAffected()
+	if err != nil {
+		api.HandleErr(w, r, tx, http.StatusInternalServerError, nil, fmt.Errorf("error determining rows affected for delete profile_parameter: %w", err))
+		return
+	}
+	if rowsAffected == 0 {
+		api.HandleErr(w, r, tx, http.StatusInternalServerError, fmt.Errorf("no rows deleted for profile_parameter"), nil)
+		return
+	}
+	alerts := tc.CreateAlerts(tc.SuccessLevel, "profile_parameter"+
+		" was deleted.")
+	api.WriteAlerts(w, r, http.StatusOK, alerts)
+	return
+}
+
+func selectMaxLastUpdatedQuery(where string) string {
+	return `
+        SELECT max(t) from (
+            SELECT max(pp.last_updated) as t FROM profile_parameter pp
+            JOIN profile prof ON prof.id = pp.profile
+            JOIN parameter param ON param.id = pp.parameter ` + where +
+		` UNION ALL
+            SELECT max(last_updated) as t FROM last_deleted l WHERE l.table_name = 'profile_parameter'
+        ) as res
+    `
+}
+
+// validateRequestProfileParameter validate the JSON objects
+func validateRequestProfileParameter(profileParameter tc.ProfileParameterCreationRequest) error {
+	errs := make(map[string]error)
+
+	errs[ProfileIDQueryParam] = validation.Validate(profileParameter.ProfileID, validation.Required)
+	errs[ParameterIDQueryParam] = validation.Validate(profileParameter.ParameterID, validation.Required)
+
+	if len(errs) > 0 {
+		var errorSlice []error
+		for _, err := range errs {
+			errorSlice = append(errorSlice, err)
+		}
+		userErr := util.JoinErrs(errorSlice)
+		return userErr
+	}
+	return nil
+}
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go b/traffic_ops/traffic_ops_golang/routing/routes.go
index 0a6285cca1..8412bf74f7 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -438,11 +438,11 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
 		{Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `profiles/name/{name}/parameters/?$`, Handler: profileparameter.GetProfileName, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 426773783231},
 		{Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `profiles/name/{name}/parameters/?$`, Handler: profileparameter.PostProfileParamsByName, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:UPDATE", "PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 435594558231},
 		{Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `profiles/{id}/parameters/?$`, Handler: profileparameter.PostProfileParamsByID, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:UPDATE", "PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 41681870831},
-		{Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `profileparameters/?$`, Handler: api.ReadHandler(&profileparameter.TOProfileParameter{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 45060980531},
-		{Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `profileparameters/?$`, Handler: api.CreateHandler(&profileparameter.TOProfileParameter{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:READ", "PARAMETER:READ", "PROFILE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 42880969331},
-		{Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `profileparameter/?$`, Handler: profileparameter.PostProfileParam, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:READ", "PARAMETER:READ", "PROFILE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 42427531},
 		{Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `parameterprofile/?$`, Handler: profileparameter.PostParamProfile, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:UPDATE", "PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 408061086131},
-		{Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `profileparameters/{profileId}/{parameterId}$`, Handler: api.DeleteHandler(&profileparameter.TOProfileParameter{}), RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:UPDATE", "PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 42483952931},
+		{Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `profileparameter/?$`, Handler: profileparameter.PostProfileParam, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:READ", "PARAMETER:READ", "PROFILE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 42427531},
+		{Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `profileparameters/?$`, Handler: profileparameter.GetProfileParameter, RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 45060980531},
+		{Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodPost, Path: `profileparameters/?$`, Handler: profileparameter.CreateProfileParameter, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:READ", "PARAMETER:READ", "PROFILE:UPDATE"}, Authenticated: Authenticated, Middlewares: nil, ID: 42880969331},
+		{Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodDelete, Path: `profileparameters/{profileId}/{parameterId}$`, Handler: profileparameter.DeleteProfileParameter, RequiredPrivLevel: auth.PrivLevelOperations, RequiredPermissions: []string{"PROFILE:UPDATE", "PROFILE:READ", "PARAMETER:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 42483952931},
 
 		//Tenants
 		{Version: api.Version{Major: 5, Minor: 0}, Method: http.MethodGet, Path: `tenants/?$`, Handler: api.ReadHandler(&apitenant.TOTenant{}), RequiredPrivLevel: auth.PrivLevelReadOnly, RequiredPermissions: []string{"TENANT:READ"}, Authenticated: Authenticated, Middlewares: nil, ID: 467796781431},
diff --git a/traffic_ops/v5-client/profile_parameter.go b/traffic_ops/v5-client/profile_parameter.go
index e0adfcbb6e..d0720520b8 100644
--- a/traffic_ops/v5-client/profile_parameter.go
+++ b/traffic_ops/v5-client/profile_parameter.go
@@ -56,8 +56,8 @@ func (to *Session) CreateProfileWithMultipleParameters(pps tc.PostProfileParam,
 }
 
 // GetProfileParameters retrieves associations between Profiles and Parameters.
-func (to *Session) GetProfileParameters(opts RequestOptions) (tc.ProfileParametersAPIResponse, toclientlib.ReqInf, error) {
-	var data tc.ProfileParametersAPIResponse
+func (to *Session) GetProfileParameters(opts RequestOptions) (tc.ProfileParametersAPIResponseV5, toclientlib.ReqInf, error) {
+	var data tc.ProfileParametersAPIResponseV5
 	reqInf, err := to.get(apiProfileParameters, opts, &data)
 	return data, reqInf, err
 }