From e52b6bcffc4a071e422c79c72ff798fb25174b9b Mon Sep 17 00:00:00 2001 From: Robert Butts Date: Fri, 22 Jun 2018 13:03:33 -0600 Subject: [PATCH] Add TO Go put deliveryservices/id/safe --- lib/go-tc/deliveryservices.go | 30 ++++ traffic_ops/client/deliveryservice.go | 20 +++ .../api/v14/deliveryservicesafe_test.go | 75 ++++++++++ .../deliveryservice/safe.go | 128 ++++++++++++++++++ .../traffic_ops_golang/routing/routes.go | 4 + .../traffic_ops_golang/tenant/tenancy.go | 2 +- 6 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 traffic_ops/testing/api/v14/deliveryservicesafe_test.go create mode 100644 traffic_ops/traffic_ops_golang/deliveryservice/safe.go diff --git a/lib/go-tc/deliveryservices.go b/lib/go-tc/deliveryservices.go index 2042ae87ff..24f480fb9b 100644 --- a/lib/go-tc/deliveryservices.go +++ b/lib/go-tc/deliveryservices.go @@ -599,3 +599,33 @@ type AssignedDsResponse struct { DSIds []int `json:"dsIds"` Replace bool `json:"replace"` } + +// DeliveryServiceSafeUpdate is the type deserialized from a PUT deliveryservices/{{ID}}/safe request. +type DeliveryServiceSafeUpdate struct { + DisplayName *string `json:"displayName"` + InfoURL *string `json:"infoUrl"` + LongDesc *string `json:"longDesc"` + LongDesc1 *string `json:"longDesc1"` +} + +// Sanitize sets any nil member pointer to a default-initialized value. +func (ds *DeliveryServiceSafeUpdate) Sanitize() { + if ds.DisplayName == nil { + ds.DisplayName = util.StrPtr("") + } + if ds.InfoURL == nil { + ds.InfoURL = util.StrPtr("") + } + if ds.LongDesc == nil { + ds.LongDesc = util.StrPtr("") + } + if ds.LongDesc1 == nil { + ds.LongDesc1 = util.StrPtr("") + } +} + +// Validate calls Sanitize and returns nil. There is nothing to validate for this object. This fulfills the traffic_ops_golang.api.ParseValidator interface. +func (ds *DeliveryServiceSafeUpdate) Validate(tx *sql.Tx) error { + ds.Sanitize() + return nil +} diff --git a/traffic_ops/client/deliveryservice.go b/traffic_ops/client/deliveryservice.go index 76e2aea520..1ab987395b 100644 --- a/traffic_ops/client/deliveryservice.go +++ b/traffic_ops/client/deliveryservice.go @@ -454,3 +454,23 @@ func (to *Session) GetDeliveryServiceURISigningKeys(dsName string) ([]byte, ReqI } return []byte(data), reqInf, nil } + +// UpdateDeliveryServiceSafe updates the given Delivery Service ID with the given safe fields. +func (to *Session) UpdateDeliveryServiceSafe(id int, ds *tc.DeliveryServiceSafeUpdate) (*tc.DeliveryServiceNullable, error) { + path := apiBase + `/deliveryservices/` + strconv.Itoa(id) + `/safe` + resp := struct { + Response []tc.DeliveryServiceNullable + }{} + + req, err := json.Marshal(ds) + if err != nil { + return nil, err + } + if _, err = put(to, path, req, &resp); err != nil { + return nil, err + } + if len(resp.Response) < 1 { + return nil, errors.New("Traffic Ops returned success, but response was missing the delivery service") + } + return &resp.Response[0], nil +} diff --git a/traffic_ops/testing/api/v14/deliveryservicesafe_test.go b/traffic_ops/testing/api/v14/deliveryservicesafe_test.go new file mode 100644 index 0000000000..d9561ed9ab --- /dev/null +++ b/traffic_ops/testing/api/v14/deliveryservicesafe_test.go @@ -0,0 +1,75 @@ +package v14 + +/* + + 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 ( + "strconv" + "testing" + + "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/lib/go-util" +) + +func TestPutDeliveryServiceSafe(t *testing.T) { + WithObjs(t, []TCObj{CDNs, Types, Tenants, Users, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers, DeliveryServices}, func() { + UpdateTestDeliveryServiceSafe(t) + }) +} + +func UpdateTestDeliveryServiceSafe(t *testing.T) { + firstDS := testData.DeliveryServices[0] + + dses, _, err := TOSession.GetDeliveryServices() + if err != nil { + t.Fatalf("cannot GET Delivery Services: %v\n", err) + } + + remoteDS := tc.DeliveryService{} + found := false + for _, ds := range dses { + if ds.XMLID == firstDS.XMLID { + found = true + remoteDS = ds + break + } + } + if !found { + t.Fatalf("GET Delivery Services missing: %v\n", firstDS.XMLID) + } + + req := tc.DeliveryServiceSafeUpdate{ + DisplayName: util.StrPtr("safe update display name"), + InfoURL: util.StrPtr("safe update info URL"), + LongDesc: util.StrPtr("safe update long desc"), + LongDesc1: util.StrPtr("safe update long desc one"), + } + + if _, err := TOSession.UpdateDeliveryServiceSafe(remoteDS.ID, &req); err != nil { + t.Fatalf("cannot UPDATE DeliveryService safe: %v\n", err) + } + + updatedDS, _, err := TOSession.GetDeliveryService(strconv.Itoa(remoteDS.ID)) + if err != nil { + t.Fatalf("cannot GET Delivery Service by ID: id %v xmlid '%v' - %v\n", remoteDS.ID, remoteDS.XMLID, err) + } + if updatedDS == nil { + t.Fatalf("GET Delivery Service by ID returned nil err, but nil DS: %v - nil\n", remoteDS.XMLID) + } + + if *req.DisplayName != updatedDS.DisplayName || *req.InfoURL != updatedDS.InfoURL || *req.LongDesc != updatedDS.LongDesc || *req.LongDesc1 != updatedDS.LongDesc1 { + t.Fatalf("ds safe update succeeded, but get delivery service didn't match. expected: {'%v' '%v' '%v' '%v'} actual: {'%v' '%v' '%v' '%v'}\n", *req.DisplayName, *req.InfoURL, *req.LongDesc, *req.LongDesc1, updatedDS.DisplayName, updatedDS.InfoURL, updatedDS.LongDesc, updatedDS.LongDesc1) + } +} diff --git a/traffic_ops/traffic_ops_golang/deliveryservice/safe.go b/traffic_ops/traffic_ops_golang/deliveryservice/safe.go new file mode 100644 index 0000000000..72b1a1e687 --- /dev/null +++ b/traffic_ops/traffic_ops_golang/deliveryservice/safe.go @@ -0,0 +1,128 @@ +package deliveryservice + +/* + * 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 ( + "database/sql" + "errors" + "fmt" + "net/http" + + "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/tenant" +) + +func UpdateSafeV14(w http.ResponseWriter, r *http.Request) { + ds, ok := UpdateSafe(w, r) + if !ok { + return + } + api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Deliveryservice safe update was successful.", []tc.DeliveryServiceNullableV14{tc.DeliveryServiceNullableV14(ds)}) +} + +func UpdateSafeV13(w http.ResponseWriter, r *http.Request) { + ds, ok := UpdateSafe(w, r) + if !ok { + return + } + api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Deliveryservice safe update was successful.", []tc.DeliveryServiceNullableV13{ds.DeliveryServiceNullableV13}) +} + +func UpdateSafeV12(w http.ResponseWriter, r *http.Request) { + ds, ok := UpdateSafe(w, r) + if !ok { + return + } + api.WriteRespAlertObj(w, r, tc.SuccessLevel, "Deliveryservice safe update was successful.", []tc.DeliveryServiceNullableV12{ds.DeliveryServiceNullableV12}) +} + +// UpdateSafe updates the delivery service, writing any errors. Returns true on success, or false on error. If an error occured, it will be written to the client and logged appropriately, and the caller shouldn't write anything further. On success, the caller should write the delivery service response to the client. +func UpdateSafe(w http.ResponseWriter, r *http.Request) (tc.DeliveryServiceNullable, bool) { + inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"id"}, []string{"id"}) + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) + return tc.DeliveryServiceNullable{}, false + } + defer inf.Close() + + dsID := inf.IntParams["id"] + + userErr, sysErr, errCode = tenant.CheckID(inf.Tx.Tx, inf.User, dsID) + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) + return tc.DeliveryServiceNullable{}, false + } + + ds := tc.DeliveryServiceSafeUpdate{} + if err := api.Parse(r.Body, inf.Tx.Tx, &ds); err != nil { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("decoding: "+err.Error()), nil) + return tc.DeliveryServiceNullable{}, false + } + + ok, err := updateDSSafe(inf.Tx.Tx, dsID, ds) + if err != nil { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("updating delivery service safe: "+err.Error())) + return tc.DeliveryServiceNullable{}, false + } + if !ok { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, nil, nil) + return tc.DeliveryServiceNullable{}, false + } + + dses, userErr, sysErr, errCode := readGetDeliveryServices(inf.Params, inf.Tx, inf.User) + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) + return tc.DeliveryServiceNullable{}, false + } + if len(dses) != 1 { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("delivery service safe update, read expected 1 delivery service, got %v", len(dses))) + return tc.DeliveryServiceNullable{}, false + } + return dses[0], true +} + +// updateDSSafe updates the given delivery service in the database. Returns whether the DS existed, and any error. +func updateDSSafe(tx *sql.Tx, dsID int, ds tc.DeliveryServiceSafeUpdate) (bool, error) { + q := ` +UPDATE deliveryservice SET +display_name=$1, +info_url=$2, +long_desc=$3, +long_desc_1=$4 +WHERE id = $5 +RETURNING id +` + res, err := tx.Exec(q, ds.DisplayName, ds.InfoURL, ds.LongDesc, ds.LongDesc1, dsID) + if err != nil { + return false, errors.New("updating delivery service safe: " + err.Error()) + } + rowsAffected, err := res.RowsAffected() + if err != nil { + return false, errors.New("updating delivery service safe, checking rows affected: " + err.Error()) + } + if rowsAffected < 1 { + return false, nil + } + if rowsAffected > 1 { + return false, fmt.Errorf("updating delivery service safe, too many rows affected: %v", rowsAffected) + } + return true, nil +} diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go b/traffic_ops/traffic_ops_golang/routing/routes.go index c5669d234a..514bb78639 100644 --- a/traffic_ops/traffic_ops_golang/routing/routes.go +++ b/traffic_ops/traffic_ops_golang/routing/routes.go @@ -544,6 +544,10 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) { {1.3, http.MethodPost, `deliveryservices/?(\.json)?$`, deliveryservice.CreateV13, auth.PrivLevelOperations, Authenticated, nil, 1705681904, noPerlBypass}, {1.1, http.MethodPost, `deliveryservices/?(\.json)?$`, deliveryservice.CreateV12, auth.PrivLevelOperations, Authenticated, nil, 652813412, noPerlBypass}, + {1.4, http.MethodPut, `deliveryservices/{id}/safe/?(\.json)?$`, deliveryservice.UpdateSafeV14, auth.PrivLevelOperations, Authenticated, nil, 547210934, noPerlBypass}, + {1.3, http.MethodPut, `deliveryservices/{id}/safe/?(\.json)?$`, deliveryservice.UpdateSafeV13, auth.PrivLevelOperations, Authenticated, nil, 547210933, noPerlBypass}, + {1.1, http.MethodPut, `deliveryservices/{id}/safe/?(\.json)?$`, deliveryservice.UpdateSafeV12, auth.PrivLevelOperations, Authenticated, nil, 547210931, noPerlBypass}, + {1.4, http.MethodPut, `deliveryservices/{id}/?(\.json)?$`, deliveryservice.UpdateV14, auth.PrivLevelOperations, Authenticated, nil, 1766567526, noPerlBypass}, {1.3, http.MethodPut, `deliveryservices/{id}/?(\.json)?$`, deliveryservice.UpdateV13, auth.PrivLevelOperations, Authenticated, nil, 1559124565, noPerlBypass}, {1.1, http.MethodPut, `deliveryservices/{id}/?(\.json)?$`, deliveryservice.UpdateV12, auth.PrivLevelOperations, Authenticated, nil, 597160536, noPerlBypass}, diff --git a/traffic_ops/traffic_ops_golang/tenant/tenancy.go b/traffic_ops/traffic_ops_golang/tenant/tenancy.go index 15c6843580..03c9e17365 100644 --- a/traffic_ops/traffic_ops_golang/tenant/tenancy.go +++ b/traffic_ops/traffic_ops_golang/tenant/tenancy.go @@ -63,7 +63,7 @@ func GetDeliveryServiceTenantInfoID(tx *sql.Tx, dsID int) (*DeliveryServiceTenan ds.ID = util.IntPtr(dsID) if err := tx.QueryRow(`SELECT tenant_id FROM deliveryservice where id = $1`, &ds.ID).Scan(&ds.TenantID); err != nil { if err == sql.ErrNoRows { - return &ds, errors.New("a deliveryservice with id '" + strconv.Itoa(dsID) + "' was not found") + return &ds, fmt.Errorf("a deliveryservice with id %v was not found", dsID) } return nil, errors.New("querying tenant id from delivery service: " + err.Error()) }