mitchell852 closed pull request #2081: Go support for /profileparameters
URL: https://github.com/apache/incubator-trafficcontrol/pull/2081
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git a/CHANGELOG.md b/CHANGELOG.md
index a3cc28bbe..54f645012 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -7,33 +7,35 @@ The format is based on [Keep a 
Changelog](http://keepachangelog.com/en/1.0.0/).
 ### Added
 - Per-DeliveryService Routing Names: you can now choose a Delivery Service's 
Routing Name (rather than a hardcoded "tr" or "edge" name). This might require 
a few pre-upgrade steps detailed 
[here](http://traffic-control-cdn.readthedocs.io/en/latest/admin/traffic_ops/migration_from_20_to_22.html#per-deliveryservice-routing-names)
 - [Delivery Service 
Requests](http://traffic-control-cdn.readthedocs.io/en/latest/admin/quick_howto/ds_requests.html#ds-requests):
 When enabled, delivery service requests are created when ALL users attempt to 
create, update or delete a delivery service. This allows users with higher 
level permissions to review delivery service changes for completeness and 
accuracy before deploying the changes.
-- Traffic Ops Golang Proxy Endpoints (R=REST endpoints for GET, POST, PUT, 
DELETE)
-  - /api/1.3/about
-  - /api/1.3/asns (R)
-  - /api/1.3/cdns (R)
-  - /api/1.3/cdns/capacity
-  - /api/1.3/cdns/configs
-  - /api/1.3/cdns/dnsseckeys
-  - /api/1.3/cdns/domain
-  - /api/1.3/cdns/monitoring
-  - /api/1.3/cdns/health
-  - /api/1.3/cdns/routing
-  - /api/1.3/deliveryservice_requests (R)
-  - /api/1.3/divisions (R)
-  - /api/1.3/hwinfos
-  - /api/1.3/parameters (R)
-  - /api/1.3/phys_locations (R)
-  - /api/1.3/ping
-  - /api/1.3/profiles (R)
-  - /api/1.3/regions (R)
-  - /api/1.3/servers (R)
-  - /api/1.3/servers/checks
-  - /api/1.3/servers/details
-  - /api/1.3/servers/status
-  - /api/1.3/servers/totals
-  - /api/1.3/statuses (R)
-  - /api/1.3/system/info
-  - /api/1.3/types (R)
+- Traffic Ops Golang Proxy Endpoints
+  - /api/1.3/about `(GET)`
+  - /api/1.3/asns `(GET,POST,PUT,DELETE)`
+  - /api/1.3/cachegroups `(GET,POST,PUT,DELETE)`
+  - /api/1.3/cdns `(GET,POST,PUT,DELETE)`
+  - /api/1.3/cdns/capacity `(GET)`
+  - /api/1.3/cdns/configs `(GET)`
+  - /api/1.3/cdns/dnsseckeys `(GET)`
+  - /api/1.3/cdns/domain `(GET)`
+  - /api/1.3/cdns/monitoring `(GET)`
+  - /api/1.3/cdns/health `(GET)`
+  - /api/1.3/cdns/routing `(GET)`
+  - /api/1.3/deliveryservice_requests `(GET,POST,PUT,DELETE)`
+  - /api/1.3/divisions `(GET,POST,PUT,DELETE)`
+  - /api/1.3/hwinfos `(GET)`
+  - /api/1.3/parameters `(GET,POST,PUT,DELETE)`
+  - /api/1.3/profileparameters `(GET,POST,PUT,DELETE)`
+  - /api/1.3/phys_locations `(GET,POST,PUT,DELETE)`
+  - /api/1.3/ping `(GET)`
+  - /api/1.3/profiles `(GET,POST,PUT,DELETE)`
+  - /api/1.3/regions `(GET,POST,PUT,DELETE)`
+  - /api/1.3/servers `(GET,POST,PUT,DELETE)`
+  - /api/1.3/servers/checks `(GET)`
+  - /api/1.3/servers/details `(GET)`
+  - /api/1.3/servers/status `(GET)`
+  - /api/1.3/servers/totals `(GET)`
+  - /api/1.3/statuses `(GET,POST,PUT,DELETE)`
+  - /api/1.3/system/info `(GET)`
+  - /api/1.3/types `(GET,POST,PUT,DELETE)`
 - Fair Queuing Pacing: Using the FQ Pacing Rate parameter in Delivery Services 
allows operators to limit the rate of individual sessions to the edge cache. 
This feature requires a Trafficserver RPM containing the fq_pacing experimental 
plugin AND setting 'fq' as the default Linux qdisc in sysctl. 
 
 ### Changed
diff --git a/lib/go-tc/v13/profile_parameters.go 
b/lib/go-tc/v13/profile_parameters.go
new file mode 100644
index 000000000..67afa6ca1
--- /dev/null
+++ b/lib/go-tc/v13/profile_parameters.go
@@ -0,0 +1,53 @@
+package v13
+
+import tc "github.com/apache/incubator-trafficcontrol/lib/go-tc"
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+// ProfileParametersResponse ...
+type ProfileParametersResponse struct {
+       Response []ProfileParameter `json:"response"`
+}
+
+// A Single ProfileParameter Response for Create to depict what changed
+// swagger:response ProfileParameterResponse
+// in: body
+type ProfileParameterResponse struct {
+       // in: body
+       Response ProfileParameter `json:"response"`
+}
+
+// ProfileParameter ...
+type ProfileParameter struct {
+       LastUpdated tc.TimeNoMod `json:"lastUpdated"`
+       Profile     string       `json:"profile"`
+       ProfileID   int          `json:"profileId"`
+       Parameter   string       `json:"parameter"`
+       ParameterID int          `json:"parameterId"`
+}
+
+// ProfileParameterNullable ...
+type ProfileParameterNullable struct {
+       LastUpdated *tc.TimeNoMod `json:"lastUpdated" db:"last_updated"`
+       Profile     *string       `json:"profile" db:"profile"`
+       ProfileID   *int          `json:"profileId" db:"profile_id"`
+       Parameter   *string       `json:"parameter" db:"parameter"`
+       ParameterID *int          `json:"parameterId" db:"parameter_id"`
+}
diff --git a/traffic_ops/client/v13/profile_parameter.go 
b/traffic_ops/client/v13/profile_parameter.go
new file mode 100644
index 000000000..5f9b5589a
--- /dev/null
+++ b/traffic_ops/client/v13/profile_parameter.go
@@ -0,0 +1,97 @@
+/*
+
+   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.
+*/
+
+package v13
+
+import (
+       "encoding/json"
+       "fmt"
+       "net"
+       "net/http"
+
+       "github.com/apache/incubator-trafficcontrol/lib/go-tc"
+       "github.com/apache/incubator-trafficcontrol/lib/go-tc/v13"
+)
+
+const (
+       API_v13_Profile_Parameters = "/api/1.3/profile_parameters"
+       ProfileIdQueryParam        = "profileId"
+       ParameterIdQueryParam      = "parameterId"
+)
+
+// Create a ProfileParameter
+func (to *Session) CreateProfileParameter(pp v13.ProfileParameter) (tc.Alerts, 
ReqInf, error) {
+
+       var remoteAddr net.Addr
+       reqBody, err := json.Marshal(pp)
+       reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: 
remoteAddr}
+       if err != nil {
+               return tc.Alerts{}, reqInf, err
+       }
+       resp, remoteAddr, err := to.request(http.MethodPost, 
API_v13_Profile_Parameters, reqBody)
+       if err != nil {
+               return tc.Alerts{}, reqInf, err
+       }
+       defer resp.Body.Close()
+       var alerts tc.Alerts
+       err = json.NewDecoder(resp.Body).Decode(&alerts)
+       return alerts, reqInf, nil
+}
+
+// Returns a list of Profile Parameters
+func (to *Session) GetProfileParameters() ([]v13.ProfileParameter, ReqInf, 
error) {
+       resp, remoteAddr, err := to.request(http.MethodGet, 
API_v13_Profile_Parameters, nil)
+       reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: 
remoteAddr}
+       if err != nil {
+               return nil, reqInf, err
+       }
+       defer resp.Body.Close()
+
+       var data v13.ProfileParametersResponse
+       err = json.NewDecoder(resp.Body).Decode(&data)
+       return data.Response, reqInf, nil
+}
+
+// GET a Profile Parameter by the Parameter
+func (to *Session) GetProfileParameterByQueryParams(queryParams string) 
([]v13.ProfileParameter, ReqInf, error) {
+       URI := API_v13_Profile_Parameters + queryParams
+       resp, remoteAddr, err := to.request(http.MethodGet, URI, nil)
+       reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: 
remoteAddr}
+       if err != nil {
+               return nil, reqInf, err
+       }
+       defer resp.Body.Close()
+
+       var data v13.ProfileParametersResponse
+       if err := json.NewDecoder(resp.Body).Decode(&data); err != nil {
+               return nil, reqInf, err
+       }
+
+       return data.Response, reqInf, nil
+}
+
+// DELETE a Parameter by Parameter
+func (to *Session) DeleteParameterByProfileParameter(profile int, parameter 
int) (tc.Alerts, ReqInf, error) {
+       URI := fmt.Sprintf("%s/profile/%d/parameter/%d", 
API_v13_Profile_Parameters, profile, parameter)
+       resp, remoteAddr, err := to.request(http.MethodDelete, URI, nil)
+       reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: 
remoteAddr}
+       if err != nil {
+               return tc.Alerts{}, reqInf, err
+       }
+       defer resp.Body.Close()
+       var alerts tc.Alerts
+       err = json.NewDecoder(resp.Body).Decode(&alerts)
+       return alerts, reqInf, nil
+}
diff --git a/traffic_ops/testing/api/v13/profile_parameters_test.go 
b/traffic_ops/testing/api/v13/profile_parameters_test.go
new file mode 100644
index 000000000..00b80351a
--- /dev/null
+++ b/traffic_ops/testing/api/v13/profile_parameters_test.go
@@ -0,0 +1,132 @@
+/*
+
+   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.
+*/
+
+package v13
+
+import (
+       "fmt"
+       "sync"
+       "testing"
+
+       "github.com/apache/incubator-trafficcontrol/lib/go-log"
+       "github.com/apache/incubator-trafficcontrol/lib/go-tc/v13"
+)
+
+const queryParamFormat = "?profileId=%d&parameterId=%d"
+
+func TestProfileParameters(t *testing.T) {
+
+       CreateTestCDNs(t)
+       CreateTestTypes(t)
+       CreateTestParameters(t)
+       CreateTestProfiles(t)
+       CreateTestProfileParameters(t)
+       GetTestProfileParameters(t)
+       DeleteTestProfileParameters(t)
+       DeleteTestParameters(t)
+       DeleteTestProfiles(t)
+       DeleteTestTypes(t)
+       DeleteTestCDNs(t)
+
+}
+
+func CreateTestProfileParameters(t *testing.T) {
+
+       firstProfile := testData.Profiles[0]
+       profileResp, _, err := TOSession.GetProfileByName(firstProfile.Name)
+       if err != nil {
+               t.Errorf("cannot GET Profile by name: %v - %v\n", 
firstProfile.Name, err)
+       }
+
+       firstParameter := testData.Parameters[0]
+       paramResp, _, err := TOSession.GetParameterByName(firstParameter.Name)
+       if err != nil {
+               t.Errorf("cannot GET Parameter by name: %v - %v\n", 
firstParameter.Name, err)
+       }
+
+       profileID := profileResp[0].ID
+       parameterID := paramResp[0].ID
+
+       pp := v13.ProfileParameter{
+               ProfileID:   profileID,
+               ParameterID: parameterID,
+       }
+       resp, _, err := TOSession.CreateProfileParameter(pp)
+       log.Debugln("Response: ", resp)
+       if err != nil {
+               t.Errorf("could not CREATE profile parameters: %v\n", err)
+       }
+
+}
+
+func GetTestProfileParameters(t *testing.T) {
+
+       for _, pp := range testData.ProfileParameters {
+               queryParams := fmt.Sprintf(queryParamFormat, pp.ProfileID, 
pp.ParameterID)
+               resp, _, err := 
TOSession.GetProfileParameterByQueryParams(queryParams)
+               if err != nil {
+                       t.Errorf("cannot GET Parameter by name: %v - %v\n", 
err, resp)
+               }
+       }
+}
+
+func DeleteTestProfileParametersParallel(t *testing.T) {
+
+       var wg sync.WaitGroup
+       for _, pp := range testData.ProfileParameters {
+
+               wg.Add(1)
+               go func() {
+                       defer wg.Done()
+                       DeleteTestProfileParameter(t, pp)
+               }()
+
+       }
+       wg.Wait()
+}
+
+func DeleteTestProfileParameters(t *testing.T) {
+
+       for _, pp := range testData.ProfileParameters {
+               DeleteTestProfileParameter(t, pp)
+       }
+}
+
+func DeleteTestProfileParameter(t *testing.T, pp v13.ProfileParameter) {
+
+       queryParams := fmt.Sprintf(queryParamFormat, pp.ProfileID, 
pp.ParameterID)
+       // Retrieve the PtofileParameter by profile so we can get the id for 
the Update
+       resp, _, err := TOSession.GetProfileParameterByQueryParams(queryParams)
+       if err != nil {
+               t.Errorf("cannot GET Parameter by profile: %v - %v\n", 
pp.Profile, err)
+       }
+       if len(resp) > 0 {
+               respPP := resp[0]
+
+               delResp, _, err := 
TOSession.DeleteParameterByProfileParameter(respPP.ProfileID, 
respPP.ParameterID)
+               if err != nil {
+                       t.Errorf("cannot DELETE Parameter by profile: %v - 
%v\n", err, delResp)
+               }
+
+               // Retrieve the Parameter to see if it got deleted
+               pps, _, err := 
TOSession.GetProfileParameterByQueryParams(queryParams)
+               if err != nil {
+                       t.Errorf("error deleting Parameter name: %s\n", 
err.Error())
+               }
+               if len(pps) > 0 {
+                       t.Errorf("expected Parameter Name: %s and ConfigFile: 
%s to be deleted\n", pp.Profile, pp.Parameter)
+               }
+       }
+}
diff --git a/traffic_ops/testing/api/v13/tc-fixtures.json 
b/traffic_ops/testing/api/v13/tc-fixtures.json
index 27012b91f..9462ee291 100644
--- a/traffic_ops/testing/api/v13/tc-fixtures.json
+++ b/traffic_ops/testing/api/v13/tc-fixtures.json
@@ -595,6 +595,16 @@
             "type": "ATS_PROFILE"
         }
     ],
+    "profileParameters": [
+        {
+            "profileId": 100,
+            "parameterId": 100
+        },
+        {
+            "profileId": 200,
+            "parameterId": 200
+        }
+    ],
     "regions": [
         {
             "divisionName": "division1",
diff --git a/traffic_ops/testing/api/v13/traffic_control.go 
b/traffic_ops/testing/api/v13/traffic_control.go
index b804ca0a1..cba0b4832 100644
--- a/traffic_ops/testing/api/v13/traffic_control.go
+++ b/traffic_ops/testing/api/v13/traffic_control.go
@@ -16,25 +16,26 @@
 package v13
 
 import (
-       tcapi "github.com/apache/incubator-trafficcontrol/lib/go-tc"
+       v12 "github.com/apache/incubator-trafficcontrol/lib/go-tc"
        "github.com/apache/incubator-trafficcontrol/lib/go-tc/v13"
 )
 
 // TrafficControl - maps to the tc-fixtures.json file
 type TrafficControl struct {
-       ASNs                           []tcapi.ASN                           
`json:"asns"`
-       CDNs                           []v13.CDN                             
`json:"cdns"`
-       CacheGroups                    []tcapi.CacheGroup                    
`json:"cachegroups"`
-       DeliveryServiceRequests        []tcapi.DeliveryServiceRequest        
`json:"deliveryServiceRequests"`
-       DeliveryServiceRequestComments []tcapi.DeliveryServiceRequestComment 
`json:"deliveryServiceRequestComments"`
-       DeliveryServices               []tcapi.DeliveryService               
`json:"deliveryservices"`
-       Divisions                      []tcapi.Division                      
`json:"divisions"`
-       Profiles                       []tcapi.Profile                       
`json:"profiles"`
-       Parameters                     []tcapi.Parameter                     
`json:"parameters"`
-       PhysLocations                  []tcapi.PhysLocation                  
`json:"physLocations"`
-       Regions                        []tcapi.Region                        
`json:"regions"`
-       Servers                        []v13.Server                          
`json:"servers"`
-       Statuses                       []tcapi.Status                        
`json:"statuses"`
-       Tenants                        []tcapi.Tenant                        
`json:"tenants"`
-       Types                          []tcapi.Type                          
`json:"types"`
+       ASNs                           []v12.ASN                           
`json:"asns"`
+       CDNs                           []v13.CDN                           
`json:"cdns"`
+       CacheGroups                    []v12.CacheGroup                    
`json:"cachegroups"`
+       DeliveryServiceRequests        []v12.DeliveryServiceRequest        
`json:"deliveryServiceRequests"`
+       DeliveryServiceRequestComments []v12.DeliveryServiceRequestComment 
`json:"deliveryServiceRequestComments"`
+       DeliveryServices               []v12.DeliveryService               
`json:"deliveryservices"`
+       Divisions                      []v12.Division                      
`json:"divisions"`
+       Profiles                       []v12.Profile                       
`json:"profiles"`
+       Parameters                     []v12.Parameter                     
`json:"parameters"`
+       ProfileParameters              []v13.ProfileParameter              
`json:"profileParameters"`
+       PhysLocations                  []v12.PhysLocation                  
`json:"physLocations"`
+       Regions                        []v12.Region                        
`json:"regions"`
+       Servers                        []v13.Server                        
`json:"servers"`
+       Statuses                       []v12.Status                        
`json:"statuses"`
+       Tenants                        []v12.Tenant                        
`json:"tenants"`
+       Types                          []v12.Type                          
`json:"types"`
 }
diff --git a/traffic_ops/traffic_ops_golang/parameter/parameters_test.go 
b/traffic_ops/traffic_ops_golang/parameter/parameters_test.go
index 4cbfc8973..a16df6e17 100644
--- a/traffic_ops/traffic_ops_golang/parameter/parameters_test.go
+++ b/traffic_ops/traffic_ops_golang/parameter/parameters_test.go
@@ -91,15 +91,15 @@ func TestGetParameters(t *testing.T) {
                )
        }
        mock.ExpectQuery("SELECT").WillReturnRows(rows)
-       v := map[string]string{"dsId": "1"}
+       v := map[string]string{"name": "1"}
 
-       parameters, errs, _ := refType.Read(db, v, auth.CurrentUser{})
+       pps, errs, _ := refType.Read(db, v, auth.CurrentUser{})
        if len(errs) > 0 {
                t.Errorf("parameter.Read expected: no errors, actual: %v", errs)
        }
 
-       if len(parameters) != 2 {
-               t.Errorf("parameter.Read expected: len(parameters) == 2, 
actual: %v", len(parameters))
+       if len(pps) != 2 {
+               t.Errorf("parameter.Read expected: len(pps) == 2, actual: %v", 
len(pps))
        }
 
 }
diff --git 
a/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters.go 
b/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters.go
new file mode 100644
index 000000000..463ff0654
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters.go
@@ -0,0 +1,300 @@
+package profileparameter
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+import (
+       "errors"
+       "fmt"
+       "strconv"
+
+       "github.com/apache/incubator-trafficcontrol/lib/go-log"
+       tc "github.com/apache/incubator-trafficcontrol/lib/go-tc"
+       "github.com/apache/incubator-trafficcontrol/lib/go-tc/v13"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/auth"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/tovalidate"
+       validation "github.com/go-ozzo/ozzo-validation"
+
+       "github.com/jmoiron/sqlx"
+       "github.com/lib/pq"
+)
+
+const (
+       ProfileIDQueryParam   = "profileId"
+       ParameterIDQueryParam = "parameterId"
+)
+
+//we need a type alias to define functions on
+type TOProfileParameter v13.ProfileParameterNullable
+
+//the refType is passed into the handlers where a copy of its type is used to 
decode the json.
+var refType = TOProfileParameter(v13.ProfileParameterNullable{})
+
+func GetRefType() *TOProfileParameter {
+       return &refType
+}
+
+func (pp TOProfileParameter) GetKeyFieldsInfo() []api.KeyFieldInfo {
+       return []api.KeyFieldInfo{{ProfileIDQueryParam, api.GetIntKey}, 
{ParameterIDQueryParam, api.GetIntKey}}
+}
+
+//Implementation of the Identifier, Validator interface functions
+func (pp TOProfileParameter) GetKeys() (map[string]interface{}, bool) {
+       if pp.ProfileID == nil {
+               return map[string]interface{}{ProfileIDQueryParam: 0}, false
+       }
+       if pp.ParameterID == nil {
+               return map[string]interface{}{ParameterIDQueryParam: 0}, false
+       }
+       keys := make(map[string]interface{})
+       profileID := *pp.ProfileID
+       parameterID := *pp.ParameterID
+
+       keys[ProfileIDQueryParam] = profileID
+       keys[ParameterIDQueryParam] = parameterID
+       return keys, true
+}
+
+func (pp *TOProfileParameter) GetAuditName() string {
+       if pp.ProfileID != nil {
+               return strconv.Itoa(*pp.ProfileID) + "-" + 
strconv.Itoa(*pp.ParameterID)
+       }
+       return "unknown"
+}
+
+func (pp *TOProfileParameter) GetType() string {
+       return "profileParameter"
+}
+
+func (pp *TOProfileParameter) SetKeys(keys map[string]interface{}) {
+       profId, _ := keys[ProfileIDQueryParam].(int) //this utilizes the non 
panicking type assertion, if the thrown away ok variable is false i will be the 
zero of the type, 0 here.
+       pp.ProfileID = &profId
+
+       paramId, _ := keys[ParameterIDQueryParam].(int) //this utilizes the non 
panicking type assertion, if the thrown away ok variable is false i will be the 
zero of the type, 0 here.
+       pp.ParameterID = &paramId
+}
+
+// Validate fulfills the api.Validator interface
+func (pp *TOProfileParameter) Validate(db *sqlx.DB) []error {
+
+       errs := validation.Errors{
+               "profile":   validation.Validate(pp.ProfileID, 
validation.Required),
+               "parameter": validation.Validate(pp.ParameterID, 
validation.Required),
+       }
+
+       return tovalidate.ToErrors(errs)
+}
+
+//The TOProfileParameter implementation of the Creator interface
+//all implementations of Creator should use transactions and return the proper 
errorType
+//ParsePQUniqueConstraintError is used to determine if a profileparameter with 
conflicting values exists
+//if so, it will return an errorType of DataConflict and the type should be 
appended to the
+//generic error message returned
+//The insert sql returns the profile and lastUpdated values of the newly 
inserted profileparameter and have
+//to be added to the struct
+func (pp *TOProfileParameter) Create(db *sqlx.DB, user auth.CurrentUser) 
(error, tc.ApiErrorType) {
+       rollbackTransaction := true
+       tx, err := db.Beginx()
+       defer func() {
+               if tx == nil || !rollbackTransaction {
+                       return
+               }
+               err := tx.Rollback()
+               if err != nil {
+                       log.Errorln(errors.New("rolling back transaction: " + 
err.Error()))
+               }
+       }()
+
+       if err != nil {
+               log.Error.Printf("could not begin transaction: %v", err)
+               return tc.DBError, tc.SystemError
+       }
+       resultRows, err := tx.NamedQuery(insertQuery(), pp)
+       if err != nil {
+               if pqErr, ok := err.(*pq.Error); ok {
+                       err, eType := 
dbhelpers.ParsePQUniqueConstraintError(pqErr)
+                       if eType == tc.DataConflictError {
+                               return errors.New("a parameter with " + 
err.Error()), eType
+                       }
+                       return err, eType
+               }
+               log.Errorf("received non pq error: %++v from create execution", 
err)
+               return tc.DBError, tc.SystemError
+       }
+       defer resultRows.Close()
+
+       var profile int
+       var parameter int
+       var lastUpdated tc.TimeNoMod
+       rowsAffected := 0
+       for resultRows.Next() {
+               rowsAffected++
+               if err := resultRows.Scan(&profile, &parameter, &lastUpdated); 
err != nil {
+                       log.Error.Printf("could not scan profile from insert: 
%s\n", err)
+                       return tc.DBError, tc.SystemError
+               }
+       }
+       if rowsAffected == 0 {
+               err = errors.New("no profile_parameter was inserted, no 
profile+parameter was returned")
+               log.Errorln(err)
+               return tc.DBError, tc.SystemError
+       }
+       if rowsAffected > 1 {
+               err = errors.New("too many ids returned from parameter insert")
+               log.Errorln(err)
+               return tc.DBError, tc.SystemError
+       }
+
+       pp.SetKeys(map[string]interface{}{ProfileIDQueryParam: profile, 
ParameterIDQueryParam: parameter})
+       pp.LastUpdated = &lastUpdated
+       err = tx.Commit()
+       if err != nil {
+               log.Errorln("Could not commit transaction: ", err)
+               return tc.DBError, tc.SystemError
+       }
+       rollbackTransaction = false
+       return nil, tc.NoError
+}
+
+func insertQuery() string {
+       query := `INSERT INTO profile_parameter (
+profile,
+parameter) VALUES (
+:profile_id,
+:parameter_id) RETURNING profile, parameter, last_updated`
+       return query
+}
+
+func (pp *TOProfileParameter) Read(db *sqlx.DB, parameters map[string]string, 
user auth.CurrentUser) ([]interface{}, []error, tc.ApiErrorType) {
+       var rows *sqlx.Rows
+
+       // Query Parameters to Database Query column mappings
+       // see the fields mapped in the SQL query
+       queryParamsToQueryCols := map[string]dbhelpers.WhereColumnInfo{
+               "profileId":   dbhelpers.WhereColumnInfo{"pp.profile", nil},
+               "parameterId": dbhelpers.WhereColumnInfo{"pp.parameter", nil},
+               "lastUpdated": dbhelpers.WhereColumnInfo{"pp.last_updated", 
nil},
+       }
+
+       where, orderBy, queryValues, errs := 
dbhelpers.BuildWhereAndOrderBy(parameters, queryParamsToQueryCols)
+       if len(errs) > 0 {
+               return nil, errs, tc.DataConflictError
+       }
+
+       query := selectQuery() + where + orderBy
+       log.Debugln("Query is ", query)
+
+       rows, err := db.NamedQuery(query, queryValues)
+       if err != nil {
+               log.Errorf("Error querying Parameters: %v", err)
+               return nil, []error{tc.DBError}, tc.SystemError
+       }
+       defer rows.Close()
+
+       params := []interface{}{}
+       for rows.Next() {
+               var p v13.ProfileParameterNullable
+               if err = rows.StructScan(&p); err != nil {
+                       log.Errorf("error parsing pp rows: %v", err)
+                       return nil, []error{tc.DBError}, tc.SystemError
+               }
+               params = append(params, p)
+       }
+
+       return params, []error{}, tc.NoError
+
+}
+
+//The Parameter implementation of the Deleter interface
+//all implementations of Deleter should use transactions and return the proper 
errorType
+func (pp *TOProfileParameter) Delete(db *sqlx.DB, user auth.CurrentUser) 
(error, tc.ApiErrorType) {
+       rollbackTransaction := true
+       tx, err := db.Beginx()
+       defer func() {
+               if tx == nil || !rollbackTransaction {
+                       return
+               }
+               err := tx.Rollback()
+               if err != nil {
+                       log.Errorln(errors.New("rolling back transaction: " + 
err.Error()))
+               }
+       }()
+
+       if err != nil {
+               log.Error.Printf("could not begin transaction: %v", err)
+               return tc.DBError, tc.SystemError
+       }
+       log.Debugf("about to run exec query: %s with parameter: %++v", 
deleteQuery(), pp)
+       result, err := tx.NamedExec(deleteQuery(), pp)
+       if err != nil {
+               log.Errorf("received error: %++v from delete execution", err)
+               return tc.DBError, tc.SystemError
+       }
+       rowsAffected, err := result.RowsAffected()
+       if err != nil {
+               return tc.DBError, tc.SystemError
+       }
+       if rowsAffected < 1 {
+               return errors.New("no parameter with that id found"), 
tc.DataMissingError
+       }
+       if rowsAffected > 1 {
+               return fmt.Errorf("this create affected too many rows: %d", 
rowsAffected), tc.SystemError
+       }
+
+       err = tx.Commit()
+       if err != nil {
+               log.Errorln("Could not commit transaction: ", err)
+               return tc.DBError, tc.SystemError
+       }
+       rollbackTransaction = false
+       return nil, tc.NoError
+}
+
+func selectQuery() string {
+
+       query := `SELECT
+pp.last_updated,
+pp.profile profile_id,
+pp.parameter parameter_id,
+prof.name profile,
+param.name parameter
+FROM profile_parameter pp
+JOIN profile prof ON prof.id = pp.profile
+JOIN parameter param ON param.id = pp.parameter`
+       return query
+}
+
+func updateQuery() string {
+       query := `UPDATE
+profile_parameter SET
+profile=:profile_id,
+parameter=:parameter_id
+WHERE profile=:profile_id AND 
+      parameter = :parameter_id 
+      RETURNING last_updated`
+       return query
+}
+
+func deleteQuery() string {
+       query := `DELETE FROM profile_parameter
+       WHERE profile=:profile_id and parameter=:parameter_id`
+       return query
+}
diff --git 
a/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters_test.go 
b/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters_test.go
new file mode 100644
index 000000000..9e7ce2095
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/profileparameter/profile_parameters_test.go
@@ -0,0 +1,111 @@
+package profileparameter
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+import (
+       "testing"
+       "time"
+
+       "github.com/apache/incubator-trafficcontrol/lib/go-tc"
+       "github.com/apache/incubator-trafficcontrol/lib/go-tc/v13"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/api"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/auth"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/test"
+       "github.com/jmoiron/sqlx"
+
+       sqlmock "gopkg.in/DATA-DOG/go-sqlmock.v1"
+)
+
+func getTestProfileParameters() []v13.ProfileParameterNullable {
+       pps := []v13.ProfileParameterNullable{}
+       lastUpdated := tc.TimeNoMod{}
+       lastUpdated.Scan(time.Now())
+       profileID := 1
+       parameterID := 1
+
+       pp := v13.ProfileParameterNullable{
+               LastUpdated: &lastUpdated,
+               ProfileID:   &profileID,
+               ParameterID: &parameterID,
+       }
+       pps = append(pps, pp)
+
+       pp2 := pp
+       pp2.ProfileID = &profileID
+       pp2.ParameterID = &parameterID
+       pps = append(pps, pp2)
+
+       return pps
+}
+
+func TestGetProfileParameters(t *testing.T) {
+       mockDB, mock, err := sqlmock.New()
+       if err != nil {
+               t.Fatalf("an error '%s' was not expected when opening a stub 
database connection", err)
+       }
+       defer mockDB.Close()
+
+       db := sqlx.NewDb(mockDB, "sqlmock")
+       defer db.Close()
+
+       testPPs := getTestProfileParameters()
+       cols := test.ColsFromStructByTag("db", v13.ProfileParameterNullable{})
+       rows := sqlmock.NewRows(cols)
+
+       for _, ts := range testPPs {
+               rows = rows.AddRow(
+                       ts.LastUpdated,
+                       ts.Profile,
+                       ts.ProfileID,
+                       ts.Parameter,
+                       ts.ParameterID,
+               )
+       }
+       mock.ExpectQuery("SELECT").WillReturnRows(rows)
+       v := map[string]string{"profile": "1"}
+
+       pps, errs, _ := refType.Read(db, v, auth.CurrentUser{})
+       if len(errs) > 0 {
+               t.Errorf("profileparameter.Read expected: no errors, actual: 
%v", errs)
+       }
+
+       if len(pps) != 2 {
+               t.Errorf("profileparameter.Read expected: len(pps) == 2, 
actual: %v", len(pps))
+       }
+
+}
+
+func TestInterfaces(t *testing.T) {
+       var i interface{}
+       i = &TOProfileParameter{}
+
+       if _, ok := i.(api.Creator); !ok {
+               t.Errorf("ProfileParameter must be Creator")
+       }
+       if _, ok := i.(api.Reader); !ok {
+               t.Errorf("ProfileParameter must be Reader")
+       }
+       if _, ok := i.(api.Deleter); !ok {
+               t.Errorf("ProfileParameter must be Deleter")
+       }
+       if _, ok := i.(api.Identifier); !ok {
+               t.Errorf("ProfileParameter must be Identifier")
+       }
+}
diff --git a/traffic_ops/traffic_ops_golang/region/regions_test.go 
b/traffic_ops/traffic_ops_golang/region/regions_test.go
index 3f08cc1f7..0720840df 100644
--- a/traffic_ops/traffic_ops_golang/region/regions_test.go
+++ b/traffic_ops/traffic_ops_golang/region/regions_test.go
@@ -74,7 +74,7 @@ func TestReadRegions(t *testing.T) {
                )
        }
        mock.ExpectQuery("SELECT").WillReturnRows(rows)
-       v := map[string]string{"dsId": "1"}
+       v := map[string]string{"id": "1"}
 
        regions, errs, _ := refType.Read(db, v, auth.CurrentUser{})
        if len(errs) > 0 {
diff --git a/traffic_ops/traffic_ops_golang/routes.go 
b/traffic_ops/traffic_ops_golang/routes.go
index 4d9619b25..1551d2c6c 100644
--- a/traffic_ops/traffic_ops_golang/routes.go
+++ b/traffic_ops/traffic_ops_golang/routes.go
@@ -42,6 +42,7 @@ import (
        
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/physlocation"
        
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/ping"
        
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/profile"
+       
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/profileparameter"
        
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/region"
        
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/server"
        
"github.com/apache/incubator-trafficcontrol/traffic_ops/traffic_ops_golang/status"
@@ -191,6 +192,12 @@ func Routes(d ServerData) ([]Route, http.Handler, error) {
                {1.3, http.MethodPost, `profiles-wip/?$`, 
api.CreateHandler(profile.GetRefType(), d.DB), auth.PrivLevelOperations, 
Authenticated, nil},
                {1.3, http.MethodDelete, `profiles-wip/{id}$`, 
api.DeleteHandler(profile.GetRefType(), d.DB), auth.PrivLevelOperations, 
Authenticated, nil},
 
+               //ProfileParameters
+               {1.3, http.MethodGet, `profile_parameters/?(\.json)?$`, 
api.ReadHandler(profileparameter.GetRefType(), d.DB), auth.PrivLevelReadOnly, 
Authenticated, nil},
+               {1.3, http.MethodGet, `profile_parameters/{id}$`, 
api.ReadHandler(profileparameter.GetRefType(), d.DB), auth.PrivLevelReadOnly, 
Authenticated, nil},
+               {1.3, http.MethodPost, `profile_parameters/?$`, 
api.CreateHandler(profileparameter.GetRefType(), d.DB), 
auth.PrivLevelOperations, Authenticated, nil},
+               {1.3, http.MethodDelete, `profile_parameters/{id}$`, 
api.DeleteHandler(profileparameter.GetRefType(), d.DB), 
auth.PrivLevelOperations, Authenticated, nil},
+
                //SSLKeys deliveryservice endpoints here that are marked  
marked as '-wip' need to have tenancy checks added
                {1.2, http.MethodGet, 
`deliveryservices-wip/xmlId/{xmlID}/sslkeys$`, 
getDeliveryServiceSSLKeysByXMLIDHandler(d.DB, d.Config), auth.PrivLevelAdmin, 
Authenticated, nil},
                {1.2, http.MethodGet, 
`deliveryservices-wip/hostname/{hostName}/sslkeys$`, 
getDeliveryServiceSSLKeysByHostNameHandler(d.DB, d.Config), 
auth.PrivLevelAdmin, Authenticated, nil},
diff --git a/traffic_ops/traffic_ops_golang/swaggerdocs/v13/docs.go 
b/traffic_ops/traffic_ops_golang/swaggerdocs/v13/docs.go
index ee55e9cd7..730a8c752 100644
--- a/traffic_ops/traffic_ops_golang/swaggerdocs/v13/docs.go
+++ b/traffic_ops/traffic_ops_golang/swaggerdocs/v13/docs.go
@@ -1,5 +1,5 @@
 /*
- Package docs Traffic Ops API v1.3
+ Package docs Traffic Ops API
 
  The following REST APIs are used by the Traffic Portal UI along with several 
Traffic Control backend component and services.
 
diff --git 
a/traffic_ops/traffic_ops_golang/swaggerdocs/v13/profileparameters.go 
b/traffic_ops/traffic_ops_golang/swaggerdocs/v13/profileparameters.go
new file mode 100644
index 000000000..04a540826
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/swaggerdocs/v13/profileparameters.go
@@ -0,0 +1,119 @@
+package v13
+
+import v13 "github.com/apache/incubator-trafficcontrol/lib/go-tc/v13"
+
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+// ProfileParameters -  ProfileParametersResponse to get the "response" top 
level key
+// swagger:response ProfileParameters
+// in: body
+type ProfileParameters struct {
+       // ProfileParameter Response Body
+       // in: body
+       ProfileParametersResponse v13.ProfileParametersResponse 
`json:"response"`
+}
+
+// ProfileParameter -  ProfileParameterResponse to get the "response" top 
level key
+// swagger:response ProfileParameter
+// in: body
+type ProfileParameter struct {
+       // ProfileParameter Response Body
+       // in: body
+       ProfileParameterResponse v13.ProfileParameterResponse
+}
+
+// ProfileParameterQueryParams
+//
+// swagger:parameters GetProfileParameters
+type ProfileParameterQueryParams struct {
+
+       // ProfileParametersQueryParams
+
+       // Unique identifier for the ProfileParameter
+       //
+       ProfileID string `json:"profileId"`
+
+       // Unique identifier for the ProfileParameter
+       //
+       ParameterID string `json:"parameterId"`
+
+       // The field in the response to sort the response by
+       //
+       Orderby string `json:"orderby"`
+}
+
+// swagger:parameters PostProfileParameter
+type ProfileParameterPostParam struct {
+       // ProfileParameter Request Body
+       //
+       // in: body
+       // required: true
+       ProfileParameter v13.ProfileParameter
+}
+
+// PostProfileParameter swagger:route POST /profileparameters ProfileParameter 
PostProfileParameter
+//
+// Create a ProfileParameter
+//
+// A ProfileParameter is a join of the Profile and Parameters
+//
+// Responses:
+//          200: Alerts
+func PostProfileParameter(entity ProfileParameterPostParam) (ProfileParameter, 
Alerts) {
+       return ProfileParameter{}, Alerts{}
+}
+
+// GetProfileParameters swagger:route GET /profileparameters ProfileParameter 
GetProfileParameters
+//
+// Retrieve a list of ProfileParameters by narrowing down with query parameters
+//
+// List of ProfileParameters
+//
+// Responses:
+//          200: ProfileParameters
+//          400: Alerts
+func GetProfileParameters() (ProfileParameters, Alerts) {
+       return ProfileParameters{}, Alerts{}
+}
+
+// GetProfileParameterById swagger:route GET /profileparameters?id={id} 
ProfileParameter GetProfileParameterById
+//
+// Retrieve a specific ProfileParameter by Id
+//
+// Retrieve a single division
+//
+// Responses:
+//          200: ProfileParameters
+//          400: Alerts
+func GetProfileParameterById() (ProfileParameters, Alerts) {
+       return ProfileParameters{}, Alerts{}
+}
+
+// DeleteProfileParameter swagger:route DELETE /profileparameters/{id} 
ProfileParameter DeleteProfileParameter
+//
+// Delete a ProfileParameter by Id
+//
+// Delete a single ProfileParameter
+//
+// Responses:
+//          200: Alerts
+func DeleteProfileParameter(entityId int) Alerts {
+       return Alerts{}
+}


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[email protected]


With regards,
Apache Git Services

Reply via email to