Rewrite cachegroup parameter delete to Go (#4316)

* Rewrite cachegroup parameter delete to Go

* Changelog

* Update parameters.go
diff --git a/CHANGELOG.md b/CHANGELOG.md
index dc0f92b..f0b6c7d 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,8 @@
 - Added a boolean to delivery service in Traffic Portal and Traffic Ops to enable EDNS0 client subnet at the delivery service level and include it in the cr-config.
 - Updated Traffic Router to read new EDSN0 client subnet field and route accordingly only for enabled delivery services. When enabled and a subnet is present in the request, the subnet appears in the `chi` field and the resolver address is in the `rhi` field.
 - Added an optimistic quorum feature to Traffic Monitor to prevent false negative states from propagating to downstream components in the event of network isolation.
+- Traffic Ops Golang Endpoints
+  - /api/1.1/cachegroupparameters/{{cachegroupID}}/{{parameterID}} `(DELETE)`
 
 ### Changed
 
diff --git a/docs/source/api/cachegroupparameters_id_parameterID.rst b/docs/source/api/cachegroupparameters_id_parameterID.rst
index a6731b2..fc4552e 100644
--- a/docs/source/api/cachegroupparameters_id_parameterID.rst
+++ b/docs/source/api/cachegroupparameters_id_parameterID.rst
@@ -70,6 +70,6 @@
 	{ "alerts": [
 		{
 			"level": "success",
-			"text": "Profile parameter association was deleted."
+			"text": "cachegroup parameter was deleted."
 		}
 	]}
diff --git a/traffic_ops/testing/api/v1/cachegroups_parameters_test.go b/traffic_ops/testing/api/v1/cachegroups_parameters_test.go
index 60ca2fd..ef90b12 100644
--- a/traffic_ops/testing/api/v1/cachegroups_parameters_test.go
+++ b/traffic_ops/testing/api/v1/cachegroups_parameters_test.go
@@ -104,7 +104,7 @@
 
 	delResp, _, err := TOSession.DeleteCacheGroupParameter(cgp.CacheGroupID, cgp.ParameterID)
 	if err != nil {
-		t.Errorf("cannot DELETE Parameter by cache group: %v - %v", err, delResp)
+		t.Fatalf("cannot DELETE Parameter by cache group: %v - %v", err, delResp)
 	}
 
 	// Retrieve the Cache Group Parameter to see if it got deleted
@@ -138,4 +138,22 @@
 	if !found {
 		t.Fatalf("parameter %v removed from cache group %v was not found in unassigned parameters response", cgp.ParameterID, cgp.CacheGroupID)
 	}
+
+	// Attempt to delete it again and it should return an error now
+	_, _, err = TOSession.DeleteCacheGroupParameter(cgp.CacheGroupID, cgp.ParameterID)
+	if err == nil {
+		t.Error("expected error when deleting unassociated cache group parameter")
+	}
+
+	// Attempt to delete using a non existing cache group
+	_, _, err = TOSession.DeleteCacheGroupParameter(-1, cgp.ParameterID)
+	if err == nil {
+		t.Error("expected error when deleting cache group parameter with non existing cache group")
+	}
+
+	// Attempt to delete using a non existing parameter
+	_, _, err = TOSession.DeleteCacheGroupParameter(cgp.CacheGroupID, -1)
+	if err == nil {
+		t.Error("expected error when deleting cache group parameter with non existing parameter")
+	}
 }
diff --git a/traffic_ops/traffic_ops_golang/cachegroupparameter/parameters.go b/traffic_ops/traffic_ops_golang/cachegroupparameter/parameters.go
index b0a106f..5f547ff 100644
--- a/traffic_ops/traffic_ops_golang/cachegroupparameter/parameters.go
+++ b/traffic_ops/traffic_ops_golang/cachegroupparameter/parameters.go
@@ -21,6 +21,7 @@
 
 import (
 	"errors"
+	"fmt"
 	"net/http"
 	"strconv"
 
@@ -33,14 +34,16 @@
 )
 
 const (
-	CacheGroupIDQueryParam = "id"
-	ParameterIDQueryParam  = "parameterId"
+	CacheGroupIDQueryParam      = "id"
+	CacheGroupIDNamedQueryParam = "cachegroupID"
+	ParameterIDQueryParam       = "parameterId"
 )
 
-//we need a type alias to define functions on
+// TOCacheGroupParameter is a type alias that is used to define CRUD functions on.
 type TOCacheGroupParameter struct {
 	api.APIInfoImpl `json:"-"`
 	tc.CacheGroupParameterNullable
+	CacheGroupID int `json:"-" db:"cachegroup_id"`
 }
 
 func (cgparam *TOCacheGroupParameter) ParamColumns() map[string]dbhelpers.WhereColumnInfo {
@@ -51,7 +54,7 @@
 }
 
 func (cgparam *TOCacheGroupParameter) GetType() string {
-	return "cachegroup_params"
+	return "cachegroup parameter"
 }
 
 func (cgparam *TOCacheGroupParameter) Read() ([]interface{}, error, error, int) {
@@ -109,3 +112,71 @@
 LEFT JOIN cachegroup_parameter cgp ON cgp.parameter = p.id`
 	return query
 }
+
+// GetKeyFieldsInfo implements the api.Identifier interface.
+func (cgparam *TOCacheGroupParameter) GetKeyFieldsInfo() []api.KeyFieldInfo {
+	return []api.KeyFieldInfo{
+		{
+			Field: CacheGroupIDNamedQueryParam,
+			Func:  api.GetIntKey,
+		},
+		{
+			Field: ParameterIDQueryParam,
+			Func:  api.GetIntKey,
+		},
+	}
+}
+
+// SetKeys implements the api.Identifier interface and allows the
+// delete handler to assign cachegroup and parameter ids.
+func (cgparam *TOCacheGroupParameter) SetKeys(keys map[string]interface{}) {
+	id, _ := keys[CacheGroupIDNamedQueryParam].(int)
+	cgparam.CacheGroupID = id
+
+	paramID, _ := keys[ParameterIDQueryParam].(int)
+	cgparam.ID = &paramID
+}
+
+// DeleteQuery implements the api.GenericDeleter interface.
+func (cgparam *TOCacheGroupParameter) DeleteQuery() string {
+	return `DELETE FROM cachegroup_parameter
+	WHERE cachegroup = :cachegroup_id AND parameter = :id`
+}
+
+// GetAuditName implements the api.Identifier interface.
+func (cgparam *TOCacheGroupParameter) GetAuditName() string {
+	if cgparam.ID != nil {
+		return strconv.Itoa(cgparam.CacheGroupID) + "-" + strconv.Itoa(*cgparam.ID)
+	}
+	return "unknown"
+}
+
+// GetKeys implements the api.Identifier interface.
+func (cgparam *TOCacheGroupParameter) GetKeys() (map[string]interface{}, bool) {
+	if cgparam.ID == nil {
+		return map[string]interface{}{ParameterIDQueryParam: 0}, false
+	}
+	return map[string]interface{}{
+		CacheGroupIDNamedQueryParam: cgparam.CacheGroupID,
+		ParameterIDQueryParam:       *cgparam.ID,
+	}, true
+}
+
+// Delete implements the api.CRUDer interface.
+func (cgparam *TOCacheGroupParameter) Delete() (error, error, int) {
+	_, ok, err := dbhelpers.GetCacheGroupNameFromID(cgparam.ReqInfo.Tx.Tx, int64(cgparam.CacheGroupID))
+	if err != nil {
+		return nil, err, http.StatusInternalServerError
+	} else if !ok {
+		return fmt.Errorf("cachegroup %v does not exist", cgparam.CacheGroupID), nil, http.StatusNotFound
+	}
+
+	_, ok, err = dbhelpers.GetParamNameByID(cgparam.ReqInfo.Tx.Tx, *cgparam.ID)
+	if err != nil {
+		return nil, err, http.StatusInternalServerError
+	} else if !ok {
+		return fmt.Errorf("parameter %v does not exist", *cgparam.ID), nil, http.StatusNotFound
+	}
+
+	return api.GenericDelete(cgparam)
+}
diff --git a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
index c0b348b..8b5f2de 100644
--- a/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
+++ b/traffic_ops/traffic_ops_golang/dbhelpers/db_helpers.go
@@ -639,6 +639,18 @@
 	return val, true, nil
 }
 
+// GetParamNameByID returns the name of the param, whether it existed, or any error.
+func GetParamNameByID(tx *sql.Tx, id int) (string, bool, error) {
+	name := ""
+	if err := tx.QueryRow(`select name from parameter where id = $1`, id).Scan(&name); err != nil {
+		if err == sql.ErrNoRows {
+			return "", false, nil
+		}
+		return "", false, fmt.Errorf("Error querying global paramter %v: %v", id, err.Error())
+	}
+	return name, true, nil
+}
+
 // GetCacheGroupNameFromID Get Cache Group name from a given ID
 func GetCacheGroupNameFromID(tx *sql.Tx, id int64) (tc.CacheGroupName, bool, error) {
 	name := ""
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go b/traffic_ops/traffic_ops_golang/routing/routes.go
index fbf0869..50f691b 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -166,6 +166,7 @@
 		//CacheGroup Parameters: CRUD
 		{1.1, http.MethodGet, `cachegroups/{id}/parameters/?(\.json)?$`, api.ReadHandler(&cachegroupparameter.TOCacheGroupParameter{}), auth.PrivLevelReadOnly, Authenticated, nil, 912449723, perlBypass},
 		{1.1, http.MethodGet, `cachegroups/{id}/unassigned_parameters/?(\.json)?$`, api.ReadHandler(&cachegroupparameter.TOCacheGroupUnassignedParameter{}), auth.PrivLevelReadOnly, Authenticated, nil, 1457339250, perlBypass},
+		{1.1, http.MethodDelete, `cachegroupparameters/{cachegroupID}/{parameterId}$`, api.DeleteHandler(&cachegroupparameter.TOCacheGroupParameter{}), auth.PrivLevelOperations, Authenticated, nil, 912449733, perlBypass},
 
 		//CDN
 		{1.1, http.MethodGet, `cdns/name/{name}/sslkeys/?(\.json)?$`, cdn.GetSSLKeys, auth.PrivLevelAdmin, Authenticated, nil, 1278581772, noPerlBypass},