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

ocket8888 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 e831c58  Rewrite DELETE federation delivery service to golang (#3967)
e831c58 is described below

commit e831c587b7f1016d49620fbcad03b81e655f258f
Author: Michael Hoppal <[email protected]>
AuthorDate: Fri Oct 11 20:42:33 2019 -0600

    Rewrite DELETE federation delivery service to golang (#3967)
    
    * Rewrite DELETE federation delivery service to golang
    
    * Fix test case
    
    * Dont change path as part of rewrite
    
    * Minor fix
    
    * Update ds.go
---
 .../api/federations_id_deliveryservices_id.rst     |  2 +-
 traffic_ops/client/federation.go                   | 16 ++++
 traffic_ops/testing/api/v14/federations_test.go    | 34 ++++++--
 traffic_ops/traffic_ops_golang/federations/ds.go   | 94 ++++++++++++++++++++-
 .../traffic_ops_golang/federations/ds_test.go      | 98 ++++++++++++++++++++++
 traffic_ops/traffic_ops_golang/routing/routes.go   |  1 +
 6 files changed, 236 insertions(+), 9 deletions(-)

diff --git a/docs/source/api/federations_id_deliveryservices_id.rst 
b/docs/source/api/federations_id_deliveryservices_id.rst
index 58edb2a..a982fbe 100644
--- a/docs/source/api/federations_id_deliveryservices_id.rst
+++ b/docs/source/api/federations_id_deliveryservices_id.rst
@@ -70,6 +70,6 @@ Response Structure
        { "alerts": [
                {
                        "level": "success",
-                       "text": "Removed delivery service [ test ] from 
federation [ foo.bar. ]"
+                       "text": "federation deliveryservice was deleted."
                }
        ]}
diff --git a/traffic_ops/client/federation.go b/traffic_ops/client/federation.go
index 10908fa..64138a9 100644
--- a/traffic_ops/client/federation.go
+++ b/traffic_ops/client/federation.go
@@ -89,3 +89,19 @@ func (to *Session) 
GetFederationDeliveryServices(federationID int) ([]tc.Federat
        inf, err := get(to, fmt.Sprintf("%s/federations/%v/deliveryservices", 
apiBase, federationID), &data)
        return data.Response, inf, err
 }
+
+// DeleteFederationDeliveryService Deletes a given Delivery Service from a 
Federation
+func (to *Session) DeleteFederationDeliveryService(federationID, 
deliveryServiceID int) (tc.Alerts, ReqInf, error) {
+       route := fmt.Sprintf("%s/federations/%v/deliveryservices/%v", apiBase, 
federationID, deliveryServiceID)
+       resp, remoteAddr, err := to.request(http.MethodDelete, route, nil)
+       reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss, RemoteAddr: 
remoteAddr}
+       if err != nil {
+               return tc.Alerts{}, reqInf, err
+       }
+       defer resp.Body.Close()
+       var alerts tc.Alerts
+       if err = json.NewDecoder(resp.Body).Decode(&alerts); err != nil {
+               return tc.Alerts{}, reqInf, err
+       }
+       return alerts, reqInf, nil
+}
diff --git a/traffic_ops/testing/api/v14/federations_test.go 
b/traffic_ops/testing/api/v14/federations_test.go
index a757abb..23fd672 100644
--- a/traffic_ops/testing/api/v14/federations_test.go
+++ b/traffic_ops/testing/api/v14/federations_test.go
@@ -21,7 +21,7 @@ import (
 
 func TestFederations(t *testing.T) {
        WithObjs(t, []TCObj{CDNs, Types, Tenants, Parameters, Profiles, 
Statuses, Divisions, Regions, PhysLocations, CacheGroups, DeliveryServices, 
UsersDeliveryServices, CDNFederations}, func() {
-               PostTestFederationsDeliveryServices(t)
+               PostDeleteTestFederationsDeliveryServices(t)
                GetTestFederations(t)
        })
 }
@@ -76,7 +76,7 @@ func GetTestFederations(t *testing.T) {
        }
 }
 
-func PostTestFederationsDeliveryServices(t *testing.T) {
+func PostDeleteTestFederationsDeliveryServices(t *testing.T) {
        dses, _, err := TOSession.GetDeliveryServices()
        if err != nil {
                t.Fatalf("cannot GET DeliveryServices: %v - %v\n", err, dses)
@@ -85,24 +85,46 @@ func PostTestFederationsDeliveryServices(t *testing.T) {
                t.Fatalf("no delivery services, must have at least 1 ds to test 
federations deliveryservices\n")
        }
        ds := dses[0]
+       ds1 := dses[1]
+
        if len(fedIDs) == 0 {
                t.Fatalf("no federations, must have at least 1 federation to 
test federations deliveryservices\n")
        }
        fedID := fedIDs[0]
 
-       if _, err = TOSession.CreateFederationDeliveryServices(fedID, 
[]int{ds.ID}, true); err != nil {
+       if _, err = TOSession.CreateFederationDeliveryServices(fedID, 
[]int{ds.ID, ds1.ID}, true); err != nil {
                t.Fatalf("creating federations delivery services: %v\n", err)
        }
 
-       // Test get created Federation Delivery Service
+       // Test get created Federation Delivery Services
        fedDSes, _, err := TOSession.GetFederationDeliveryServices(fedID)
        if err != nil {
                t.Fatalf("cannot GET Federation DeliveryServices: %v\n", err)
        }
+       if len(fedDSes) != 2 {
+               t.Fatalf("two Federation DeliveryService exepected for 
Federation %v, %v was returned\n", fedID, len(fedDSes))
+       }
+
+       // Delete one of the Delivery Services from the Federation
+       _, _, err = TOSession.DeleteFederationDeliveryService(fedID, ds.ID)
+       if err != nil {
+               t.Fatalf("cannot Delete Federation %v DeliveryService %v: 
%v\n", fedID, ds.ID, err)
+       }
+
+       // Make sure it is deleted
+
+       // Test get created Federation Delivery Services
+       fedDSes, _, err = TOSession.GetFederationDeliveryServices(fedID)
+       if err != nil {
+               t.Fatalf("cannot GET Federation DeliveryServices: %v\n", err)
+       }
        if len(fedDSes) != 1 {
                t.Fatalf("one Federation DeliveryService exepected for 
Federation %v, %v was returned\n", fedID, len(fedDSes))
        }
-       if *fedDSes[0].ID != ds.ID {
-               t.Errorf("expected DeliveryService %v to be returned for 
Federation %v DeliveryServices, %v was instead returned\n", ds.ID, fedID, 
*fedDSes[0].ID)
+
+       // Attempt to delete the last one which should fail as you cannot 
remove the last
+       _, _, err = TOSession.DeleteFederationDeliveryService(fedID, ds1.ID)
+       if err == nil {
+               t.Fatalf("expected to receive error from attempting to delete 
last Delivery Service from a Federation\n")
        }
 }
diff --git a/traffic_ops/traffic_ops_golang/federations/ds.go 
b/traffic_ops/traffic_ops_golang/federations/ds.go
index d2951e7..1e844c3 100644
--- a/traffic_ops/traffic_ops_golang/federations/ds.go
+++ b/traffic_ops/traffic_ops_golang/federations/ds.go
@@ -27,6 +27,7 @@ import (
        "strconv"
 
        "github.com/apache/trafficcontrol/lib/go-tc"
+
        "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
        
"github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
 
@@ -87,6 +88,12 @@ func deleteDSFeds(tx *sql.Tx, fedID int) error {
        return err
 }
 
+func deleteFedDS(tx *sql.Tx, fedID, dsID int) error {
+       qry := `DELETE FROM federation_deliveryservice WHERE federation = $1 
AND deliveryservice = $2`
+       _, err := tx.Exec(qry, fedID, dsID)
+       return err
+}
+
 func insertDSFeds(tx *sql.Tx, fedID int, dsIDs []int) error {
        qry := `
 INSERT INTO federation_deliveryservice (federation, deliveryservice)
@@ -108,9 +115,10 @@ func getFedNameByID(tx *sql.Tx, id int) (string, bool, 
error) {
        return name, true, nil
 }
 
-// TOFedDSes data structure to use on read of federation deliveryservices
+// TOFedDSes data structure to use on read/delete of federation 
deliveryservices
 type TOFedDSes struct {
        api.APIInfoImpl `json:"-"`
+       fedID           *int
        tc.FederationDeliveryServiceNullable
 }
 
@@ -123,11 +131,93 @@ func (v *TOFedDSes) ParamColumns() 
map[string]dbhelpers.WhereColumnInfo {
        }
 }
 func (v *TOFedDSes) GetType() string {
-       return "federation_deliveryservice"
+       return "federation deliveryservice"
+}
+
+func (v *TOFedDSes) SetKeys(keys map[string]interface{}) {
+       i, _ := keys["id"].(int)
+       v.fedID = &i
+}
+
+func (v *TOFedDSes) GetKeys() (map[string]interface{}, bool) {
+       if v.fedID == nil {
+               return map[string]interface{}{"id": 0}, false
+       }
+       return map[string]interface{}{"id": *v.fedID}, true
+}
+
+func (v *TOFedDSes) GetAuditName() string {
+       if v.XMLID != nil {
+               return *v.XMLID
+       }
+       return strconv.Itoa(*v.ID)
+}
+
+func (v *TOFedDSes) GetKeyFieldsInfo() []api.KeyFieldInfo {
+       return []api.KeyFieldInfo{
+               {"id", api.GetIntKey},
+       }
 }
 
 func (v *TOFedDSes) Read() ([]interface{}, error, error, int) { return 
api.GenericRead(v) }
 
+func (v *TOFedDSes) Delete() (error, error, int) {
+       dsIDStr, ok := v.APIInfo().Params["dsID"]
+       if !ok {
+               return errors.New("dsID must be specified for deletion"), nil, 
http.StatusBadRequest
+       }
+       dsID, err := strconv.Atoi(dsIDStr)
+       if err != nil {
+               return errors.New("dsID must be an integer"), nil, 
http.StatusBadRequest
+       }
+       v.ID = &dsID
+
+       // Check that we can delete it
+       if respCode, usrErr, sysErr := checkFedDSDeletion(v.APIInfo().Tx.Tx, 
*v.fedID, dsID); usrErr != nil || sysErr != nil {
+               if usrErr != nil {
+                       return usrErr, sysErr, respCode
+               }
+               return usrErr, sysErr, respCode
+       }
+
+       // Actually delete the DS from the Federation
+       if err := deleteFedDS(v.APIInfo().Tx.Tx, *v.fedID, dsID); err != nil {
+               return api.ParseDBError(err)
+       }
+
+       return nil, nil, http.StatusOK
+}
+
+func checkFedDSDeletion(tx *sql.Tx, fedID, dsID int) (int, error, error) {
+
+       q := `SELECT ARRAY(SELECT deliveryservice FROM 
federation_deliveryservice WHERE federation=$1)`
+       dsIDs := []int64{} // pq.Array does not support int slice needs to be 
int64
+       err := tx.QueryRow(q, fedID).Scan(pq.Array(&dsIDs))
+       if err != nil {
+               return http.StatusInternalServerError, nil, 
fmt.Errorf("querying federation %v delivery services - %v", fedID, err)
+       }
+
+       if len(dsIDs) == 0 {
+               return http.StatusNotFound, fmt.Errorf("federation %v not 
found", fedID), nil
+       }
+
+       if len(dsIDs) < 2 {
+               return http.StatusBadRequest, fmt.Errorf("a federation must 
have at least one delivery service assigned"), nil
+       }
+       found := false
+       dsID64 := int64(dsID) // need in order to compare
+       for _, id := range dsIDs {
+               if id == dsID64 {
+                       found = true
+                       break
+               }
+       }
+       if !found {
+               return http.StatusBadRequest, fmt.Errorf("delivery service %v 
is not associated with federation %v", dsID, fedID), nil
+       }
+       return http.StatusOK, nil, nil
+}
+
 func selectQuery() string {
 
        query := `SELECT
diff --git a/traffic_ops/traffic_ops_golang/federations/ds_test.go 
b/traffic_ops/traffic_ops_golang/federations/ds_test.go
new file mode 100644
index 0000000..b56b29f
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/federations/ds_test.go
@@ -0,0 +1,98 @@
+package federations
+
+/*
+ * 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"
+
+       "github.com/jmoiron/sqlx"
+       "gopkg.in/DATA-DOG/go-sqlmock.v1"
+)
+
+func TestCheckFedDSDeletion(t *testing.T) {
+       var testCases = []struct {
+               description  string
+               returnArray  string
+               expectUsrErr bool
+               expectUsrStr string
+               inputDSID    int
+       }{
+               {
+                       description:  "Success: Deleted Federation Delivery 
Service",
+                       returnArray:  "{1,2}",
+                       expectUsrErr: false,
+                       expectUsrStr: "",
+                       inputDSID:    1,
+               },
+               {
+                       description:  "Failure: Remove last Federation Delivery 
Service",
+                       returnArray:  "{1}",
+                       expectUsrErr: true,
+                       expectUsrStr: "a federation must have at least one 
delivery service assigned",
+                       inputDSID:    1,
+               },
+               {
+                       description:  "Failure: Federation not found",
+                       returnArray:  "{}",
+                       expectUsrErr: true,
+                       expectUsrStr: "federation 1 not found",
+                       inputDSID:    1,
+               },
+               {
+                       description:  "Failure: Delivery Service not found",
+                       returnArray:  "{3,4}",
+                       expectUsrErr: true,
+                       expectUsrStr: "delivery service 1 is not associated 
with federation 1",
+                       inputDSID:    1,
+               },
+       }
+       for _, tc := range testCases {
+               t.Run(tc.description, func(t *testing.T) {
+                       t.Log("Starting test scenario: ", tc.description)
+                       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()
+                       rows := sqlmock.NewRows([]string{"array"})
+                       rows = rows.AddRow(tc.returnArray)
+                       mock.ExpectBegin()
+                       mock.ExpectQuery("SELECT").WillReturnRows(rows)
+                       mock.ExpectCommit()
+                       _, usrErr, sysErr := 
checkFedDSDeletion(db.MustBegin().Tx, 1, tc.inputDSID)
+                       if tc.expectUsrErr {
+                               if usrErr == nil {
+                                       t.Errorf("User error expected: received 
none")
+                               }
+                               if usrErr.Error() != tc.expectUsrStr {
+                                       t.Errorf("Expected error with text %v: 
received %v", tc.expectUsrStr, usrErr.Error())
+                               }
+                       }
+                       if !tc.expectUsrErr && usrErr != nil {
+                               t.Errorf("User error not expected: received 
error %v", err)
+                       }
+                       if sysErr != nil {
+                               t.Errorf("System error not expected: received 
error %v", err)
+                       }
+               })
+       }
+}
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go 
b/traffic_ops/traffic_ops_golang/routing/routes.go
index 5f90653..28a47ae 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -444,6 +444,7 @@ func Routes(d ServerData) ([]Route, []RawRoute, 
http.Handler, error) {
                {1.1, http.MethodGet, `federations/?(\.json)?$`, 
federations.Get, auth.PrivLevelFederation, Authenticated, nil},
                {1.1, http.MethodPost, 
`federations/{id}/deliveryservices?(\.json)?$`, federations.PostDSes, 
auth.PrivLevelAdmin, Authenticated, nil},
                {1.1, http.MethodGet, 
`federations/{id}/deliveryservices?(\.json)?$`, 
api.ReadHandler(&federations.TOFedDSes{}), auth.PrivLevelReadOnly, 
Authenticated, nil},
+               {1.1, http.MethodDelete, 
`federations/{id}/deliveryservices/{dsID}/?(\.json)?$`, 
api.DeleteHandler(&federations.TOFedDSes{}), auth.PrivLevelAdmin, 
Authenticated, nil},
 
                ////DeliveryServices
                {1.1, http.MethodGet, `deliveryservices/?(\.json)?$`, 
api.ReadHandler(&deliveryservice.TODeliveryService{}), auth.PrivLevelReadOnly, 
Authenticated, nil},

Reply via email to