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

rob pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/incubator-trafficcontrol.git

commit 394ba97e884517e9621582dcc30201cefd4da3ee
Author: ASchmidt <andrew_schm...@comcast.com>
AuthorDate: Fri May 4 14:53:55 2018 -0600

    added DeliveryServicesService Get API in Go
---
 lib/go-tc/deliveryservice_servers.go               | 193 +++++++++++++
 .../deliveryservice/servers/servers.go             | 300 +++++++++++++++++++++
 .../deliveryservice/servers/servers_test.go        | 111 ++++++++
 3 files changed, 604 insertions(+)

diff --git a/lib/go-tc/deliveryservice_servers.go 
b/lib/go-tc/deliveryservice_servers.go
new file mode 100644
index 0000000..9c7e6ae
--- /dev/null
+++ b/lib/go-tc/deliveryservice_servers.go
@@ -0,0 +1,193 @@
+package tc
+
+/*
+
+   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.
+*/
+
+import (
+       "database/sql/driver"
+       "encoding/json"
+       "errors"
+       "fmt"
+       "strconv"
+       "strings"
+
+       log "github.com/apache/incubator-trafficcontrol/lib/go-log"
+)
+
+// IDNoMod type is used to suppress JSON unmarshalling
+type IDNoMod int
+
+// DeliveryServiceRequest is used as part of the workflow to create,
+// modify, or delete a delivery service.
+type DeliveryServiceRequest struct {
+       AssigneeID      int             `json:"assigneeId,omitempty"`
+       Assignee        string          `json:"assignee,omitempty"`
+       AuthorID        IDNoMod         `json:"authorId"`
+       Author          string          `json:"author"`
+       ChangeType      string          `json:"changeType"`
+       CreatedAt       *TimeNoMod      `json:"createdAt"`
+       ID              int             `json:"id"`
+       LastEditedBy    string          `json:"lastEditedBy,omitempty"`
+       LastEditedByID  IDNoMod         `json:"lastEditedById,omitempty"`
+       LastUpdated     *TimeNoMod      `json:"lastUpdated"`
+       DeliveryService DeliveryService `json:"deliveryService"`
+       Status          RequestStatus   `json:"status"`
+       XMLID           string          `json:"-" db:"xml_id"`
+}
+
+// DeliveryServiceRequestNullable is used as part of the workflow to create,
+// modify, or delete a delivery service.
+type DeliveryServiceRequestNullable struct {
+       AssigneeID      *int                     `json:"assigneeId,omitempty" 
db:"assignee_id"`
+       Assignee        *string                  `json:"assignee,omitempty"`
+       AuthorID        *IDNoMod                 `json:"authorId" 
db:"author_id"`
+       Author          *string                  `json:"author"`
+       ChangeType      *string                  `json:"changeType" 
db:"change_type"`
+       CreatedAt       *TimeNoMod               `json:"createdAt" 
db:"created_at"`
+       ID              *int                     `json:"id" db:"id"`
+       LastEditedBy    *string                  `json:"lastEditedBy"`
+       LastEditedByID  *IDNoMod                 `json:"lastEditedById" 
db:"last_edited_by_id"`
+       LastUpdated     *TimeNoMod               `json:"lastUpdated" 
db:"last_updated"`
+       DeliveryService *DeliveryServiceNullable `json:"deliveryService" 
db:"deliveryservice"`
+       Status          *RequestStatus           `json:"status" db:"status"`
+       XMLID           *string                  `json:"-" db:"xml_id"`
+}
+
+// UnmarshalJSON implements the json.Unmarshaller interface to suppress 
unmarshalling for IDNoMod
+func (a *IDNoMod) UnmarshalJSON([]byte) error {
+       return nil
+}
+
+// RequestStatus captures where in the workflow this request is
+type RequestStatus string
+
+const (
+       // RequestStatusInvalid -- invalid state
+       RequestStatusInvalid = RequestStatus("invalid")
+       // RequestStatusDraft -- newly created; not ready to be reviewed
+       RequestStatusDraft = RequestStatus("draft")
+       // RequestStatusSubmitted -- newly created; ready to be reviewed
+       RequestStatusSubmitted = RequestStatus("submitted")
+       // RequestStatusRejected -- reviewed, but problems found
+       RequestStatusRejected = RequestStatus("rejected")
+       // RequestStatusPending -- reviewed and locked; ready to be implemented
+       RequestStatusPending = RequestStatus("pending")
+       // RequestStatusComplete -- implemented and locked
+       RequestStatusComplete = RequestStatus("complete")
+)
+
+// RequestStatuses -- user-visible string associated with each of the above
+var RequestStatuses = []RequestStatus{
+       // "invalid" -- don't list here..
+       "draft",
+       "submitted",
+       "rejected",
+       "pending",
+       "complete",
+}
+
+// UnmarshalJSON implements json.Unmarshaller
+func (r *RequestStatus) UnmarshalJSON(b []byte) error {
+       u, err := strconv.Unquote(string(b))
+       if err != nil {
+               return err
+       }
+
+       // just check to see if the string represents a valid requeststatus
+       _, err = RequestStatusFromString(u)
+       if err != nil {
+               return err
+       }
+       return json.Unmarshal(b, (*string)(r))
+}
+
+// MarshalJSON implements json.Marshaller
+func (r RequestStatus) MarshalJSON() ([]byte, error) {
+       return json.Marshal(string(r))
+}
+
+// Value implements driver.Valuer
+func (r *RequestStatus) Value() (driver.Value, error) {
+       v, err := json.Marshal(r)
+       log.Debugf("value is %v; err is %v", v, err)
+       v = []byte(strings.Trim(string(v), `"`))
+       return v, err
+}
+
+// Scan implements sql.Scanner
+func (r *RequestStatus) Scan(src interface{}) error {
+       b, ok := src.([]byte)
+       if !ok {
+               return fmt.Errorf("expected requeststatus in byte array form; 
got %T", src)
+       }
+       b = []byte(`"` + string(b) + `"`)
+       return json.Unmarshal(b, r)
+}
+
+// RequestStatusFromString gets the status enumeration from a string
+func RequestStatusFromString(rs string) (RequestStatus, error) {
+       if rs == "" {
+               return RequestStatusDraft, nil
+       }
+       for _, s := range RequestStatuses {
+               if string(s) == rs {
+                       return s, nil
+               }
+       }
+       return RequestStatusInvalid, errors.New(rs + " is not a valid 
RequestStatus name")
+}
+
+// ValidTransition returns nil if the transition is allowed for the workflow, 
an error if not
+func (r RequestStatus) ValidTransition(to RequestStatus) error {
+       if r == RequestStatusRejected || r == RequestStatusComplete {
+               // once rejected or completed,  no changes allowed
+               return errors.New(string(r) + " request cannot be changed")
+       }
+
+       if r == to {
+               // no change -- always allowed
+               return nil
+       }
+
+       // indicate if valid transitioning to this RequestStatus
+       switch to {
+       case RequestStatusDraft:
+               // can go back to draft if submitted or rejected
+               if r == RequestStatusSubmitted {
+                       return nil
+               }
+       case RequestStatusSubmitted:
+               // can go be submitted if draft or rejected
+               if r == RequestStatusDraft {
+                       return nil
+               }
+       case RequestStatusRejected:
+               // only submitted can be rejected
+               if r == RequestStatusSubmitted {
+                       return nil
+               }
+       case RequestStatusPending:
+               // only submitted can move to pending
+               if r == RequestStatusSubmitted {
+                       return nil
+               }
+       case RequestStatusComplete:
+               // only pending can be completed.  Completed can never change.
+               if r == RequestStatusPending {
+                       return nil
+               }
+       }
+       return errors.New("invalid transition from " + string(r) + " to " + 
string(to))
+}
diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.go
new file mode 100644
index 0000000..463ff06
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers.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/deliveryservice/servers/servers_test.go 
b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers_test.go
new file mode 100644
index 0000000..9e7ce20
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/deliveryservice/servers/servers_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")
+       }
+}

-- 
To stop receiving notification emails like this one, please contact
r...@apache.org.

Reply via email to