From 6599ab7822e41183c892ebee307a313b571811e4 Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Thu, 8 Jul 2021 18:35:26 -0600 Subject: [PATCH 01/10] wip --- lib/go-tc/servers.go | 8 ++ .../traffic_ops_golang/routing/routes.go | 3 + .../traffic_ops_golang/server/update.go | 124 ++++++++++++++++++ .../app/src/common/api/ProfileService.js | 26 ++++ .../app/src/common/api/TypeService.js | 25 ++++ .../form/profile/FormProfileController.js | 8 ++ .../form/profile/form.profile.tpl.html | 3 + .../modules/form/type/FormTypeController.js | 54 +++++++- .../modules/form/type/form.type.tpl.html | 10 ++ 9 files changed, 259 insertions(+), 2 deletions(-) diff --git a/lib/go-tc/servers.go b/lib/go-tc/servers.go index ffa3bc3d84..5e1b19a4a7 100644 --- a/lib/go-tc/servers.go +++ b/lib/go-tc/servers.go @@ -1192,3 +1192,11 @@ type ServerQueueUpdate struct { ServerID util.JSONIntStr `json:"serverId"` Action string `json:"action"` } + +// ServerGenericQueueUpdateResponse encodes the response data for the POST +// queue_updates endpoint. +type ServerGenericQueueUpdateResponse struct { + Action string `json:"action"` + CDNID int `json:"cdnId"` + TypeID int `json:"typeID"` +} diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go b/traffic_ops/traffic_ops_golang/routing/routes.go index 57e52a88b9..d0f6d8017f 100644 --- a/traffic_ops/traffic_ops_golang/routing/routes.go +++ b/traffic_ops/traffic_ops_golang/routing/routes.go @@ -134,6 +134,9 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) { * 4.x API */ + // Queue Updates + {api.Version{Major: 4, Minor: 0}, http.MethodPost, `queue_updates/?$`, server.ProfileAndTypeQueueUpdateHandler, auth.PrivLevelOperations, Authenticated, nil, 4134390571}, + // CDN lock {api.Version{Major: 4, Minor: 0}, http.MethodGet, `cdn_locks/?$`, cdn_lock.Read, auth.PrivLevelReadOnly, Authenticated, nil, 4134390561}, {api.Version{Major: 4, Minor: 0}, http.MethodPost, `cdn_locks/?$`, cdn_lock.Create, auth.PrivLevelOperations, Authenticated, nil, 4134390562}, diff --git a/traffic_ops/traffic_ops_golang/server/update.go b/traffic_ops/traffic_ops_golang/server/update.go index 54ae48d11c..59b837ef96 100644 --- a/traffic_ops/traffic_ops_golang/server/update.go +++ b/traffic_ops/traffic_ops_golang/server/update.go @@ -21,14 +21,19 @@ package server import ( "database/sql" + "encoding/json" "errors" + "fmt" "net/http" "strconv" "strings" "github.com/apache/trafficcontrol/lib/go-tc" + "github.com/apache/trafficcontrol/lib/go-util" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers" + + "github.com/jmoiron/sqlx" ) // UpdateHandler implements an http handler that updates a server's upd_pending and reval_pending values. @@ -162,3 +167,122 @@ func setUpdateStatuses(tx *sql.Tx, hostName string, updatePending *bool, revalPe } return nil } + +func ProfileAndTypeQueueUpdateHandler(w http.ResponseWriter, r *http.Request) { + var cdnID int + var typeID int + var profileID = -1 + var ok bool + var err error + inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"cdn"}, nil) + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) + return + } + defer inf.Close() + + cols := map[string]dbhelpers.WhereColumnInfo{ + "cdnID": {Column: "server.cdn_id", Checker: nil}, + "typeID": {Column: "server.type", Checker: nil}, + "profileID": {Column: "server.profile", Checker: nil}, + } + + cdn := inf.Params["cdn"] + typeName := inf.Params["type"] + profile := inf.Params["profile"] + + var reqObj tc.ServerQueueUpdateRequest + + if err := json.NewDecoder(r.Body).Decode(&reqObj); err != nil { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, fmt.Errorf("malformed JSON: %v", err), nil) + return + } + + if reqObj.Action != "queue" && reqObj.Action != "dequeue" { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("action must be 'queue' or 'dequeue'"), nil) + return + } + queue := reqObj.Action == "queue" + + // get cdn ID + cdnID, ok, err = dbhelpers.GetCDNIDFromName(inf.Tx.Tx, tc.CDNName(cdn)) + if err != nil { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("error getting CDN ID from name: "+err.Error())) + return + } + if !ok { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, errors.New("no CDN ID found with that name"), nil) + return + } + delete(inf.Params, "cdn") + inf.Params["cdnID"] = strconv.Itoa(cdnID) + + // get type ID + if typeName != "" { + typeID, ok, err = dbhelpers.GetTypeIDByName(typeName, inf.Tx.Tx) + if err != nil { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("error getting type ID from name: "+err.Error())) + return + } + if !ok { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, errors.New("no type ID found with that name"), nil) + return + } + inf.Params["typeID"] = strconv.Itoa(typeID) + } + delete(inf.Params, "type") + + // get profile ID + if profile != "" { + profileID, ok, err = dbhelpers.GetProfileIDFromName(profile, inf.Tx.Tx) + if err != nil { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("error getting profile ID from name: "+err.Error())) + return + } + if !ok { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, errors.New("no profile ID found with that name"), nil) + return + } + inf.Params["profileID"] = strconv.Itoa(profileID) + } + delete(inf.Params, "profile") + + where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, cols) + if len(errs) > 0 { + errCode = http.StatusBadRequest + userErr = util.JoinErrs(errs) + api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, nil) + return + } + + userErr, sysErr, statusCode := dbhelpers.CheckIfCurrentUserHasCdnLock(inf.Tx.Tx, string(cdn), inf.User.UserName) + if userErr != nil || sysErr != nil { + api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, sysErr) + return + } + query := `UPDATE server SET upd_pending = :upd_pending` + query = query + where + orderBy + pagination + queryValues["upd_pending"] = queue + ok, err = queueGenericUpdate(inf.Tx, queryValues, query) + if err != nil { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("queueing updates: %v", err)) + return + } + if !ok { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, fmt.Errorf("no server with the given combination found"), nil) + return + } + api.CreateChangeLogRawTx(api.ApiChange, "CDN: "+cdn+", Type: "+typeName+", ACTION: CDN server updates "+reqObj.Action+"d", inf.User, inf.Tx.Tx) + api.WriteResp(w, r, tc.ServerGenericQueueUpdateResponse{Action: reqObj.Action, CDNID: cdnID, TypeID: typeID}) +} + +func queueGenericUpdate(tx *sqlx.Tx, queryValues map[string]interface{}, query string) (bool, error) { + result, err := tx.NamedExec(query, queryValues) + if err != nil { + return false, errors.New("querying generic queue updates: " + err.Error()) + } else if rc, err := result.RowsAffected(); err != nil { + return false, fmt.Errorf("checking rows updated: %v", err) + } else { + return rc > 0, nil + } +} diff --git a/traffic_portal/app/src/common/api/ProfileService.js b/traffic_portal/app/src/common/api/ProfileService.js index 5b3d1283a9..b2b2487313 100644 --- a/traffic_portal/app/src/common/api/ProfileService.js +++ b/traffic_portal/app/src/common/api/ProfileService.js @@ -132,6 +132,32 @@ var ProfileService = function($http, locationUtils, messageModel, ENV) { ); }; + this.queueServerUpdatesByProfile = function(cdnName, profileName) { + return $http.post(ENV.api['root'] + 'queue_updates?cdn=' + cdnName + '&profile=' + profileName, {action: "queue"}).then( + function(result) { + messageModel.setMessages([{level: 'success', text: 'Queued server updates by profile'}], false); + return result; + }, + function(err) { + messageModel.setMessages(err.data.alerts, false); + throw err; + } + ); + }; + + this.clearServerUpdatesByProfile = function(cdnName, profileName) { + return $http.post(ENV.api['root'] + 'queue_updates?cdn=' + cdnName + '&profile=' + profileName, {action: "dequeue"}).then( + function(result) { + messageModel.setMessages([{level: 'success', text: 'Cleared server updates by profile'}], false); + return result; + }, + function(err) { + messageModel.setMessages(err.data.alerts, false); + throw err; + } + ); + }; + }; ProfileService.$inject = ['$http', 'locationUtils', 'messageModel', 'ENV']; diff --git a/traffic_portal/app/src/common/api/TypeService.js b/traffic_portal/app/src/common/api/TypeService.js index 433dfc4af0..0693b66ed1 100644 --- a/traffic_portal/app/src/common/api/TypeService.js +++ b/traffic_portal/app/src/common/api/TypeService.js @@ -83,6 +83,31 @@ var TypeService = function($http, ENV, locationUtils, messageModel) { ); }; + this.queueServerUpdates = function(cdnName, typeName) { + return $http.post(ENV.api['root'] + 'queue_updates?cdn=' + cdnName + '&type=' + typeName, {action: "queue"}).then( + function(result) { + messageModel.setMessages([{level: 'success', text: 'Queued server updates by type'}], false); + return result; + }, + function(err) { + messageModel.setMessages(err.data.alerts, false); + throw err; + } + ); + }; + + this.clearServerUpdates = function(cdnName, typeName) { + return $http.post(ENV.api['root'] + 'queue_updates?cdn=' + cdnName + '&type=' + typeName, {action: "dequeue"}).then( + function(result) { + messageModel.setMessages([{level: 'success', text: 'Cleared server updates by type'}], false); + return result; + }, + function(err) { + messageModel.setMessages(err.data.alerts, false); + throw err; + } + ); + }; }; TypeService.$inject = ['$http', 'ENV', 'locationUtils', 'messageModel']; diff --git a/traffic_portal/app/src/common/modules/form/profile/FormProfileController.js b/traffic_portal/app/src/common/modules/form/profile/FormProfileController.js index bba8ea9d22..acc0b01cf5 100644 --- a/traffic_portal/app/src/common/modules/form/profile/FormProfileController.js +++ b/traffic_portal/app/src/common/modules/form/profile/FormProfileController.js @@ -95,6 +95,14 @@ var FormProfileController = function(profile, $scope, $location, $uibModal, file }; + $scope.queueUpdatesByProfile = function() { + profileService.queueServerUpdatesByProfile($scope.profile.cdnName, $scope.profile.name).then($scope.refresh); + }; + + $scope.clearUpdatesByProfile = function() { + profileService.clearServerUpdatesByProfile($scope.profile.cdnName, $scope.profile.name).then($scope.refresh); + }; + $scope.navigateToPath = locationUtils.navigateToPath; $scope.hasError = formUtils.hasError; diff --git a/traffic_portal/app/src/common/modules/form/profile/form.profile.tpl.html b/traffic_portal/app/src/common/modules/form/profile/form.profile.tpl.html index 8eddf5d922..5538c0a48e 100644 --- a/traffic_portal/app/src/common/modules/form/profile/form.profile.tpl.html +++ b/traffic_portal/app/src/common/modules/form/profile/form.profile.tpl.html @@ -37,6 +37,9 @@
  • Clone Profile
  • Export Profile
  • +
  • +
  • Queue Updates By Profile
  • +
  • Clear Updates By Profile
  • diff --git a/traffic_portal/app/src/common/modules/form/type/FormTypeController.js b/traffic_portal/app/src/common/modules/form/type/FormTypeController.js index 3b245b5320..cf65d7959b 100644 --- a/traffic_portal/app/src/common/modules/form/type/FormTypeController.js +++ b/traffic_portal/app/src/common/modules/form/type/FormTypeController.js @@ -17,7 +17,7 @@ * under the License. */ -var FormTypeController = function(type, $scope, $location, formUtils, stringUtils, locationUtils) { +var FormTypeController = function(type, $scope, $location, formUtils, stringUtils, locationUtils, $uibModal, cdnService, typeService) { $scope.type = type; @@ -32,6 +32,56 @@ var FormTypeController = function(type, $scope, $location, formUtils, stringUtil $location.path($location.path() + '/servers'); }; + $scope.queueUpdatesByType = function() { + const params = { + title: 'Queue Server Updates By Type', + message: "Please select a CDN" + }; + const modalInstance = $uibModal.open({ + templateUrl: 'common/modules/dialog/select/dialog.select.tpl.html', + controller: 'DialogSelectController', + size: 'md', + resolve: { + params: function () { + return params; + }, + collection: function(cdnService) { + return cdnService.getCDNs(); + } + } + }); + modalInstance.result.then(function(cdn) { + typeService.queueServerUpdates(cdn.name, $scope.type.name).then($scope.refresh); + }, function () { + // do nothing + }); + }; + + $scope.clearUpdatesByType = function() { + const params = { + title: 'Clear Server Updates By Type', + message: "Please select a CDN" + }; + const modalInstance = $uibModal.open({ + templateUrl: 'common/modules/dialog/select/dialog.select.tpl.html', + controller: 'DialogSelectController', + size: 'md', + resolve: { + params: function () { + return params; + }, + collection: function(cdnService) { + return cdnService.getCDNs(); + } + } + }); + modalInstance.result.then(function(cdn) { + typeService.clearServerUpdates(cdn.name, $scope.type.name).then($scope.refresh); + }, function () { + // do nothing + }); + }; + $scope.viewDeliveryServices = function() { $location.path($location.path() + '/delivery-services'); }; @@ -52,5 +102,5 @@ var FormTypeController = function(type, $scope, $location, formUtils, stringUtil }; -FormTypeController.$inject = ['type', '$scope', '$location', 'formUtils', 'stringUtils', 'locationUtils']; +FormTypeController.$inject = ['type', '$scope', '$location', 'formUtils', 'stringUtils', 'locationUtils', '$uibModal', 'cdnService', 'typeService']; module.exports = FormTypeController; diff --git a/traffic_portal/app/src/common/modules/form/type/form.type.tpl.html b/traffic_portal/app/src/common/modules/form/type/form.type.tpl.html index 4d92680834..d2d93d20ec 100644 --- a/traffic_portal/app/src/common/modules/form/type/form.type.tpl.html +++ b/traffic_portal/app/src/common/modules/form/type/form.type.tpl.html @@ -29,6 +29,16 @@ +
    + + +
    From 8ed303c6ce51d4c4c34046e28db6f266923c7b3d Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Sun, 11 Jul 2021 18:35:05 -0600 Subject: [PATCH 02/10] Adding tests --- CHANGELOG.md | 1 + .../v4/queue_updates_by_profile_or_type.rst | 108 +++++++++++++++ lib/go-tc/servers.go | 8 +- ...rver_queue_updates_by_type_profile_test.go | 131 ++++++++++++++++++ .../traffic_ops_golang/routing/routes.go | 2 +- .../traffic_ops_golang/server/update.go | 6 +- traffic_ops/v4-client/server_update_status.go | 29 +++- .../app/src/common/api/ProfileService.js | 4 +- .../app/src/common/api/TypeService.js | 4 +- 9 files changed, 280 insertions(+), 13 deletions(-) create mode 100644 docs/source/api/v4/queue_updates_by_profile_or_type.rst create mode 100644 traffic_ops/testing/api/v4/server_queue_updates_by_type_profile_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index e520f9cf49..ce3a2ecd04 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/). ## [unreleased] ### Added +- [#4982](https://github.com/apache/trafficcontrol/issues/4982) Added the ability to support queueing updates by server type and profile - [#5451](https://github.com/apache/trafficcontrol/issues/5451) Added change log count to user API's response payload and query param (username) to logs API - Added support for CDN locks - Added support for PostgreSQL as a Traffic Vault backend diff --git a/docs/source/api/v4/queue_updates_by_profile_or_type.rst b/docs/source/api/v4/queue_updates_by_profile_or_type.rst new file mode 100644 index 0000000000..7966b1a3be --- /dev/null +++ b/docs/source/api/v4/queue_updates_by_profile_or_type.rst @@ -0,0 +1,108 @@ +.. +.. +.. 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. +.. + +.. _to-api-cdn-locks: + +********************* +``cdn_locks/{{cdn}}`` +********************* + +.. versionadded:: 4.0 + +``PUT`` +======= +Allows user to acquire a lock on a CDN. +:term:`Queue` or dequeue updates for a list of servers. + +:Auth. Required: Yes +:Roles Required: None +:Response Type: Object + +Request Structure +----------------- +.. table:: Request Path Parameters + + +-------+---------------------------------------------------------------------------------------------+ + | Name | Description | + +=======+=============================================================================================+ + | cdn | The name of the cdn, for which the servers are being queued or dequeued | + +-------+---------------------------------------------------------------------------------------------+ + +.. table:: Request Query Parameters + + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +===========+==========+===============================================================================================================+ + | type | no | The name of the ``type`` of servers, for which the updates need to be queued or dequeued. | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | profile | no | The name of the ``profile`` of servers, for which the updates need to be queued or dequeued. | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + +:action: A string describing what action to take regarding server updates; one of: + + queue + :term:`Queue Updates` for the server, propagating configuration changes to the actual server + dequeue + Cancels any pending updates on the server + +.. code-block:: http + :caption: Request Example + + PUT /api/4.0/queue_updates/cdn1?type=EDGE HTTP/2 + Host: localhost:8443 + User-Agent: curl/7.64.2 + Accept: */* + Cookie: mojolicious=... + Content-Type: application/json + Content-Length: 21 + + { + "action": "queue" + } + +Response Structure +------------------ +:action: The action processed, one of: + + queue + :term:`Queue Updates` was performed on the list of servers, propagating configuration changes to the actual servers + dequeue + Canceled any pending updates on the list of servers + +:cdnID: The integral, unique identifier of the cdn on which ``action`` was taken +:typeID: The integral, unique identifier of the type on which ``action`` was taken + +.. code-block:: http + :caption: Response Example + + HTTP/2 200 + Access-Control-Allow-Credentials: true + Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie + Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE + Access-Control-Allow-Origin: * + Content-Type: application/json + Set-Cookie: mojolicious=eyJhdXRoX2Rhd...; Path=/; Expires=Fri, 09 Jul 2021 20:20:24 GMT; Max-Age=3600; HttpOnly + Whole-Content-Sha512: 5ifVe8ihG3kwrRSfu14N7IF7ldLgCtGDoNS/aQEHag3lVAce6vLLALrD4YdiDl7NLwOzifq1MC7SY8YcyHEipQ== + X-Server-Name: traffic_ops_golang/ + Date: Fri, 09 Jul 2021 19:20:25 GMT + Content-Length: 54 + + { + "response": { + "action": "queue", + "cdnID": 5, + "typeID": 11 + } + } diff --git a/lib/go-tc/servers.go b/lib/go-tc/servers.go index 5e1b19a4a7..70797ac32d 100644 --- a/lib/go-tc/servers.go +++ b/lib/go-tc/servers.go @@ -1196,7 +1196,9 @@ type ServerQueueUpdate struct { // ServerGenericQueueUpdateResponse encodes the response data for the POST // queue_updates endpoint. type ServerGenericQueueUpdateResponse struct { - Action string `json:"action"` - CDNID int `json:"cdnId"` - TypeID int `json:"typeID"` + Action string `json:"action"` + CDNID int `json:"cdnID"` + TypeID int `json:"typeID,omitempty"` + ProfileID int `json:"profileID,omitempty"` + Alerts } diff --git a/traffic_ops/testing/api/v4/server_queue_updates_by_type_profile_test.go b/traffic_ops/testing/api/v4/server_queue_updates_by_type_profile_test.go new file mode 100644 index 0000000000..d7c2acd97d --- /dev/null +++ b/traffic_ops/testing/api/v4/server_queue_updates_by_type_profile_test.go @@ -0,0 +1,131 @@ +package v4 + +/* + + 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" + "strings" + "testing" + + client "github.com/apache/trafficcontrol/traffic_ops/v4-client" +) + +func TestServerQueueUpdateByProfileAndType(t *testing.T) { + WithObjs(t, []TCObj{Types, CDNs, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers}, func() { + QueueUpdatesByType(t) + QueueUpdatesByProfile(t) + }) +} + +func QueueUpdatesByType(t *testing.T) { + if len(testData.Servers) < 1 { + t.Fatalf("no servers to run the tests on...quitting.") + } + server := testData.Servers[0] + opts := client.NewRequestOptions() + if server.CDNName == nil { + t.Fatalf("server doesn't have a CDN name...quitting") + } + opts.QueryParameters.Set("name", *server.CDNName) + + // Get the first server's CDN ID + cdns, _, err := TOSession.GetCDNs(opts) + if err != nil { + t.Fatalf("error while getting CDNs: %v", err) + } + if len(cdns.Response) < 1 { + t.Fatalf("expected 1 CDN in response, got %d", len(cdns.Response)) + } + opts.QueryParameters.Del("name") + + // Queue updates by type (and CDN) + _, _, err = TOSession.SetServerQueueUpdatesByType(server.Type, *server.CDNName, true, client.NewRequestOptions()) + if err != nil { + t.Errorf("couldn't queue updates by type (and CDN): %v", err) + } + + // Get all the servers for the same CDN and type as that of the first server + opts.QueryParameters.Set("cdn", strconv.Itoa(cdns.Response[0].ID)) + opts.QueryParameters.Set("type", server.Type) + resp, _, err := TOSession.GetServers(opts) + if err != nil { + t.Fatalf("couldn't get servers by cdn and type: %v", err) + } + if len(resp.Response) < 1 { + t.Fatalf("expected atleast one server in response, got %d", len(resp.Response)) + } + for _, s := range resp.Response { + if s.UpdPending == nil || !*s.UpdPending { + t.Errorf("expected updates to be queued on all the servers filtered by type and CDN, but %s didn't queue updates", *s.HostName) + } + } +} + +func QueueUpdatesByProfile(t *testing.T) { + if len(testData.Servers) < 1 { + t.Fatalf("no servers to run the tests on...quitting.") + } + server := testData.Servers[0] + opts := client.NewRequestOptions() + if server.CDNName == nil || server.Profile == nil { + t.Fatalf("server doesn't have a CDN name or a profile name...quitting") + } + + //Get the first server's CDN ID + opts.QueryParameters.Set("name", strings.TrimSpace(*server.CDNName)) + + cdns, _, err := TOSession.GetCDNs(opts) + if err != nil { + t.Fatalf("error while getting CDNs: %v", err) + } + if len(cdns.Response) < 1 { + t.Fatalf("expected 1 CDN in response, got %d", len(cdns.Response)) + } + opts.QueryParameters.Del("name") + + // Get the first server's Profile ID + opts.QueryParameters.Set("name", *server.Profile) + profiles, _, err := TOSession.GetProfiles(opts) + if err != nil { + t.Fatalf("error while getting profiles: %v", err) + } + if len(profiles.Response) < 1 { + t.Fatalf("expected 1 profile in response, got %d", len(profiles.Response)) + } + opts.QueryParameters.Del("name") + + // Queue updates by profile (and CDN) + _, _, err = TOSession.SetServerQueueUpdatesByProfile(profiles.Response[0].Name, *server.CDNName, true, client.NewRequestOptions()) + if err != nil { + t.Errorf("couldn't queue updates by profile (and CDN): %v", err) + } + + // Get all the servers for the same CDN and profile as that of the first server + opts.QueryParameters.Set("cdn", strconv.Itoa(cdns.Response[0].ID)) + opts.QueryParameters.Set("profileId", strconv.Itoa(profiles.Response[0].ID)) + resp, _, err := TOSession.GetServers(opts) + if err != nil { + t.Fatalf("couldn't get servers by cdn and profile: %v", err) + } + if len(resp.Response) < 1 { + t.Fatalf("expected atleast one server in response, got %d", len(resp.Response)) + } + for _, s := range resp.Response { + if s.UpdPending == nil || !*s.UpdPending { + t.Errorf("expected updates to be queued on all the servers filtered by profile and CDN, but %s didn't queue updates", *s.HostName) + } + } +} diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go b/traffic_ops/traffic_ops_golang/routing/routes.go index d0f6d8017f..730bc5a66b 100644 --- a/traffic_ops/traffic_ops_golang/routing/routes.go +++ b/traffic_ops/traffic_ops_golang/routing/routes.go @@ -135,7 +135,7 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) { */ // Queue Updates - {api.Version{Major: 4, Minor: 0}, http.MethodPost, `queue_updates/?$`, server.ProfileAndTypeQueueUpdateHandler, auth.PrivLevelOperations, Authenticated, nil, 4134390571}, + {api.Version{Major: 4, Minor: 0}, http.MethodPut, `queue_updates/{cdn}?$`, server.ProfileAndTypeQueueUpdateHandler, auth.PrivLevelOperations, Authenticated, nil, 4134390571}, // CDN lock {api.Version{Major: 4, Minor: 0}, http.MethodGet, `cdn_locks/?$`, cdn_lock.Read, auth.PrivLevelReadOnly, Authenticated, nil, 4134390561}, diff --git a/traffic_ops/traffic_ops_golang/server/update.go b/traffic_ops/traffic_ops_golang/server/update.go index 59b837ef96..aa98d2f6a6 100644 --- a/traffic_ops/traffic_ops_golang/server/update.go +++ b/traffic_ops/traffic_ops_golang/server/update.go @@ -263,7 +263,7 @@ func ProfileAndTypeQueueUpdateHandler(w http.ResponseWriter, r *http.Request) { query := `UPDATE server SET upd_pending = :upd_pending` query = query + where + orderBy + pagination queryValues["upd_pending"] = queue - ok, err = queueGenericUpdate(inf.Tx, queryValues, query) + ok, err = queueUpdatesByTypeOrProfile(inf.Tx, queryValues, query) if err != nil { api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("queueing updates: %v", err)) return @@ -273,10 +273,10 @@ func ProfileAndTypeQueueUpdateHandler(w http.ResponseWriter, r *http.Request) { return } api.CreateChangeLogRawTx(api.ApiChange, "CDN: "+cdn+", Type: "+typeName+", ACTION: CDN server updates "+reqObj.Action+"d", inf.User, inf.Tx.Tx) - api.WriteResp(w, r, tc.ServerGenericQueueUpdateResponse{Action: reqObj.Action, CDNID: cdnID, TypeID: typeID}) + api.WriteResp(w, r, tc.ServerGenericQueueUpdateResponse{Action: reqObj.Action, CDNID: cdnID, TypeID: typeID, ProfileID: profileID}) } -func queueGenericUpdate(tx *sqlx.Tx, queryValues map[string]interface{}, query string) (bool, error) { +func queueUpdatesByTypeOrProfile(tx *sqlx.Tx, queryValues map[string]interface{}, query string) (bool, error) { result, err := tx.NamedExec(query, queryValues) if err != nil { return false, errors.New("querying generic queue updates: " + err.Error()) diff --git a/traffic_ops/v4-client/server_update_status.go b/traffic_ops/v4-client/server_update_status.go index e1b6165598..8087cc1448 100644 --- a/traffic_ops/v4-client/server_update_status.go +++ b/traffic_ops/v4-client/server_update_status.go @@ -18,10 +18,9 @@ package client import ( "errors" "fmt" - "net/url" - "github.com/apache/trafficcontrol/lib/go-tc" "github.com/apache/trafficcontrol/traffic_ops/toclientlib" + "net/url" ) // UpdateServerStatus updates the Status of the server identified by @@ -49,6 +48,32 @@ func (to *Session) SetServerQueueUpdate(serverID int, queueUpdate bool, opts Req return resp, reqInf, err } +// SetServerQueueUpdatesByType set the "updPending" field of a list of servers identified by +// 'cdnName' and 'typeName' to the value of 'queueUpdate' +func (to *Session) SetServerQueueUpdatesByType(typeName string, cdnName string, queueUpdate bool, opts RequestOptions) (tc.ServerGenericQueueUpdateResponse, toclientlib.ReqInf, error) { + req := tc.ServerQueueUpdateRequest{Action: queueUpdateActions[queueUpdate]} + var resp tc.ServerGenericQueueUpdateResponse + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + path := fmt.Sprintf("/queue_updates/%s?type=%s", url.PathEscape(cdnName), typeName) + reqInf, err := to.put(path, opts, req, &resp) + return resp, reqInf, err +} + +// SetServerQueueUpdatesByProfile set the "updPending" field of a list of servers identified by +// 'cdnName' and 'profileName' to the value of 'queueUpdate' +func (to *Session) SetServerQueueUpdatesByProfile(profileName string, cdnName string, queueUpdate bool, opts RequestOptions) (tc.ServerGenericQueueUpdateResponse, toclientlib.ReqInf, error) { + req := tc.ServerQueueUpdateRequest{Action: queueUpdateActions[queueUpdate]} + var resp tc.ServerGenericQueueUpdateResponse + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + path := fmt.Sprintf("/queue_updates/%s?profile=%s", url.PathEscape(cdnName), profileName) + reqInf, err := to.put(path, opts, req, &resp) + return resp, reqInf, err +} + // SetUpdateServerStatuses updates a server's queue status and/or reval status. // Either updateStatus or revalStatus may be nil, in which case that status // isn't updated (but not both, because that wouldn't do anything). diff --git a/traffic_portal/app/src/common/api/ProfileService.js b/traffic_portal/app/src/common/api/ProfileService.js index b2b2487313..0f2289d8f8 100644 --- a/traffic_portal/app/src/common/api/ProfileService.js +++ b/traffic_portal/app/src/common/api/ProfileService.js @@ -133,7 +133,7 @@ var ProfileService = function($http, locationUtils, messageModel, ENV) { }; this.queueServerUpdatesByProfile = function(cdnName, profileName) { - return $http.post(ENV.api['root'] + 'queue_updates?cdn=' + cdnName + '&profile=' + profileName, {action: "queue"}).then( + return $http.put(ENV.api['root'] + 'queue_updates/' + cdnName + '?profile=' + profileName, {action: "queue"}).then( function(result) { messageModel.setMessages([{level: 'success', text: 'Queued server updates by profile'}], false); return result; @@ -146,7 +146,7 @@ var ProfileService = function($http, locationUtils, messageModel, ENV) { }; this.clearServerUpdatesByProfile = function(cdnName, profileName) { - return $http.post(ENV.api['root'] + 'queue_updates?cdn=' + cdnName + '&profile=' + profileName, {action: "dequeue"}).then( + return $http.put(ENV.api['root'] + 'queue_updates/' + cdnName + '?profile=' + profileName, {action: "dequeue"}).then( function(result) { messageModel.setMessages([{level: 'success', text: 'Cleared server updates by profile'}], false); return result; diff --git a/traffic_portal/app/src/common/api/TypeService.js b/traffic_portal/app/src/common/api/TypeService.js index 0693b66ed1..b246a37f9d 100644 --- a/traffic_portal/app/src/common/api/TypeService.js +++ b/traffic_portal/app/src/common/api/TypeService.js @@ -84,7 +84,7 @@ var TypeService = function($http, ENV, locationUtils, messageModel) { }; this.queueServerUpdates = function(cdnName, typeName) { - return $http.post(ENV.api['root'] + 'queue_updates?cdn=' + cdnName + '&type=' + typeName, {action: "queue"}).then( + return $http.put(ENV.api['root'] + 'queue_updates/' + cdnName + '?type=' + typeName, {action: "queue"}).then( function(result) { messageModel.setMessages([{level: 'success', text: 'Queued server updates by type'}], false); return result; @@ -97,7 +97,7 @@ var TypeService = function($http, ENV, locationUtils, messageModel) { }; this.clearServerUpdates = function(cdnName, typeName) { - return $http.post(ENV.api['root'] + 'queue_updates?cdn=' + cdnName + '&type=' + typeName, {action: "dequeue"}).then( + return $http.put(ENV.api['root'] + 'queue_updates/' + cdnName + '?type=' + typeName, {action: "dequeue"}).then( function(result) { messageModel.setMessages([{level: 'success', text: 'Cleared server updates by type'}], false); return result; From 2eacda7ed8020b7fdb333b1d762e3bd4b2f5dccb Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Mon, 12 Jul 2021 14:17:52 -0600 Subject: [PATCH 03/10] fix documentation --- docs/source/api/v4/queue_updates_by_profile_or_type.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/source/api/v4/queue_updates_by_profile_or_type.rst b/docs/source/api/v4/queue_updates_by_profile_or_type.rst index 7966b1a3be..00d148dec0 100644 --- a/docs/source/api/v4/queue_updates_by_profile_or_type.rst +++ b/docs/source/api/v4/queue_updates_by_profile_or_type.rst @@ -15,15 +15,15 @@ .. _to-api-cdn-locks: -********************* -``cdn_locks/{{cdn}}`` -********************* +************************* +``queue_updates/{{cdn}}`` +************************* .. versionadded:: 4.0 ``PUT`` ======= -Allows user to acquire a lock on a CDN. +Allows a user to queue updates on the servers of a CDN filtered by ``type`` and/or ``profile``. :term:`Queue` or dequeue updates for a list of servers. :Auth. Required: Yes From 7572fc2353c694c5fca153dc28daf6eeeec29dea Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Mon, 12 Jul 2021 14:18:42 -0600 Subject: [PATCH 04/10] fix documentation --- docs/source/api/v4/queue_updates_by_profile_or_type.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/source/api/v4/queue_updates_by_profile_or_type.rst b/docs/source/api/v4/queue_updates_by_profile_or_type.rst index 00d148dec0..b3f2707360 100644 --- a/docs/source/api/v4/queue_updates_by_profile_or_type.rst +++ b/docs/source/api/v4/queue_updates_by_profile_or_type.rst @@ -13,7 +13,7 @@ .. limitations under the License. .. -.. _to-api-cdn-locks: +.. _to-api-queue-updates-by-type-or-profile: ************************* ``queue_updates/{{cdn}}`` From ba102ea73db79e30ed9c842dc5ec4aac0da393e1 Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Mon, 12 Jul 2021 14:22:14 -0600 Subject: [PATCH 05/10] fix imports, add go docs --- traffic_ops/traffic_ops_golang/server/update.go | 2 ++ traffic_ops/v4-client/server_update_status.go | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/traffic_ops/traffic_ops_golang/server/update.go b/traffic_ops/traffic_ops_golang/server/update.go index aa98d2f6a6..33c20dbcd9 100644 --- a/traffic_ops/traffic_ops_golang/server/update.go +++ b/traffic_ops/traffic_ops_golang/server/update.go @@ -168,6 +168,7 @@ func setUpdateStatuses(tx *sql.Tx, hostName string, updatePending *bool, revalPe return nil } +// ProfileAndTypeQueueUpdateHandler queues/ dequeues updates on servers for a particular CDN, filtered by type and/ or profile. func ProfileAndTypeQueueUpdateHandler(w http.ResponseWriter, r *http.Request) { var cdnID int var typeID int @@ -276,6 +277,7 @@ func ProfileAndTypeQueueUpdateHandler(w http.ResponseWriter, r *http.Request) { api.WriteResp(w, r, tc.ServerGenericQueueUpdateResponse{Action: reqObj.Action, CDNID: cdnID, TypeID: typeID, ProfileID: profileID}) } +// queueUpdatesByTypeOrProfile is the helper function to queue/ dequeue updates on servers for a CDN, filtered by type and/ or profile func queueUpdatesByTypeOrProfile(tx *sqlx.Tx, queryValues map[string]interface{}, query string) (bool, error) { result, err := tx.NamedExec(query, queryValues) if err != nil { diff --git a/traffic_ops/v4-client/server_update_status.go b/traffic_ops/v4-client/server_update_status.go index 8087cc1448..c09b5b47d2 100644 --- a/traffic_ops/v4-client/server_update_status.go +++ b/traffic_ops/v4-client/server_update_status.go @@ -18,9 +18,10 @@ package client import ( "errors" "fmt" + "net/url" + "github.com/apache/trafficcontrol/lib/go-tc" "github.com/apache/trafficcontrol/traffic_ops/toclientlib" - "net/url" ) // UpdateServerStatus updates the Status of the server identified by From 4758bb2bc15133b16f9e3aa4d0d14d6079ef9ad4 Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Tue, 13 Jul 2021 15:49:18 -0600 Subject: [PATCH 06/10] use pre existing route, instead of creating a new one --- docs/source/api/v4/cdns_id_queue_update.rst | 17 ++- .../v4/queue_updates_by_profile_or_type.rst | 108 --------------- ...rver_queue_updates_by_type_profile_test.go | 4 +- traffic_ops/traffic_ops_golang/cdn/queue.go | 89 +++++++++++-- .../traffic_ops_golang/routing/routes.go | 3 - .../traffic_ops_golang/server/update.go | 126 ------------------ traffic_ops/v4-client/server_update_status.go | 12 +- .../app/src/common/api/ProfileService.js | 8 +- .../app/src/common/api/TypeService.js | 8 +- .../form/profile/FormProfileController.js | 4 +- .../modules/form/type/FormTypeController.js | 4 +- 11 files changed, 114 insertions(+), 269 deletions(-) delete mode 100644 docs/source/api/v4/queue_updates_by_profile_or_type.rst diff --git a/docs/source/api/v4/cdns_id_queue_update.rst b/docs/source/api/v4/cdns_id_queue_update.rst index a783741220..14f734468d 100644 --- a/docs/source/api/v4/cdns_id_queue_update.rst +++ b/docs/source/api/v4/cdns_id_queue_update.rst @@ -37,12 +37,22 @@ Request Structure | ID | The integral, unique identifier for the CDN on which to (de)queue updates | +------+---------------------------------------------------------------------------+ +.. table:: Request Query Parameters + + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | Name | Required | Description | + +===========+==========+===============================================================================================================+ + | type | no | The name of the ``type`` of servers, for which the updates need to be queued or dequeued. | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + | profile | no | The name of the ``profile`` of servers, for which the updates need to be queued or dequeued. | + +-----------+----------+---------------------------------------------------------------------------------------------------------------+ + :action: One of "queue" or "dequeue" as appropriate .. code-block:: http :caption: Request Example - POST /api/4.0/cdns/2/queue_update HTTP/1.1 + POST /api/4.0/cdns/2/queue_update?type=EDGE HTTP/1.1 Host: trafficops.infra.ciab.test User-Agent: curl/7.47.0 Accept: */* @@ -70,9 +80,10 @@ Response Structure Whole-Content-Sha512: rBpFfrrP+9IFkwsRloEM+v+I8MuBZDXqFu+WUTGtRGypnAn2gHooPoNQRyVvJGjyIQrLXLvqjEtve+lH2Tj4uw== X-Server-Name: traffic_ops_golang/ Date: Wed, 14 Nov 2018 21:02:07 GMT - Content-Length: 41 + Content-Length: 54 { "response": { "action": "queue", - "cdnId": 2 + "cdnId": 2, + "typeID": 11 }} diff --git a/docs/source/api/v4/queue_updates_by_profile_or_type.rst b/docs/source/api/v4/queue_updates_by_profile_or_type.rst deleted file mode 100644 index b3f2707360..0000000000 --- a/docs/source/api/v4/queue_updates_by_profile_or_type.rst +++ /dev/null @@ -1,108 +0,0 @@ -.. -.. -.. 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. -.. - -.. _to-api-queue-updates-by-type-or-profile: - -************************* -``queue_updates/{{cdn}}`` -************************* - -.. versionadded:: 4.0 - -``PUT`` -======= -Allows a user to queue updates on the servers of a CDN filtered by ``type`` and/or ``profile``. -:term:`Queue` or dequeue updates for a list of servers. - -:Auth. Required: Yes -:Roles Required: None -:Response Type: Object - -Request Structure ------------------ -.. table:: Request Path Parameters - - +-------+---------------------------------------------------------------------------------------------+ - | Name | Description | - +=======+=============================================================================================+ - | cdn | The name of the cdn, for which the servers are being queued or dequeued | - +-------+---------------------------------------------------------------------------------------------+ - -.. table:: Request Query Parameters - - +-----------+----------+---------------------------------------------------------------------------------------------------------------+ - | Name | Required | Description | - +===========+==========+===============================================================================================================+ - | type | no | The name of the ``type`` of servers, for which the updates need to be queued or dequeued. | - +-----------+----------+---------------------------------------------------------------------------------------------------------------+ - | profile | no | The name of the ``profile`` of servers, for which the updates need to be queued or dequeued. | - +-----------+----------+---------------------------------------------------------------------------------------------------------------+ - -:action: A string describing what action to take regarding server updates; one of: - - queue - :term:`Queue Updates` for the server, propagating configuration changes to the actual server - dequeue - Cancels any pending updates on the server - -.. code-block:: http - :caption: Request Example - - PUT /api/4.0/queue_updates/cdn1?type=EDGE HTTP/2 - Host: localhost:8443 - User-Agent: curl/7.64.2 - Accept: */* - Cookie: mojolicious=... - Content-Type: application/json - Content-Length: 21 - - { - "action": "queue" - } - -Response Structure ------------------- -:action: The action processed, one of: - - queue - :term:`Queue Updates` was performed on the list of servers, propagating configuration changes to the actual servers - dequeue - Canceled any pending updates on the list of servers - -:cdnID: The integral, unique identifier of the cdn on which ``action`` was taken -:typeID: The integral, unique identifier of the type on which ``action`` was taken - -.. code-block:: http - :caption: Response Example - - HTTP/2 200 - Access-Control-Allow-Credentials: true - Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept, Set-Cookie, Cookie - Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE - Access-Control-Allow-Origin: * - Content-Type: application/json - Set-Cookie: mojolicious=eyJhdXRoX2Rhd...; Path=/; Expires=Fri, 09 Jul 2021 20:20:24 GMT; Max-Age=3600; HttpOnly - Whole-Content-Sha512: 5ifVe8ihG3kwrRSfu14N7IF7ldLgCtGDoNS/aQEHag3lVAce6vLLALrD4YdiDl7NLwOzifq1MC7SY8YcyHEipQ== - X-Server-Name: traffic_ops_golang/ - Date: Fri, 09 Jul 2021 19:20:25 GMT - Content-Length: 54 - - { - "response": { - "action": "queue", - "cdnID": 5, - "typeID": 11 - } - } diff --git a/traffic_ops/testing/api/v4/server_queue_updates_by_type_profile_test.go b/traffic_ops/testing/api/v4/server_queue_updates_by_type_profile_test.go index d7c2acd97d..ef737f2657 100644 --- a/traffic_ops/testing/api/v4/server_queue_updates_by_type_profile_test.go +++ b/traffic_ops/testing/api/v4/server_queue_updates_by_type_profile_test.go @@ -52,7 +52,7 @@ func QueueUpdatesByType(t *testing.T) { opts.QueryParameters.Del("name") // Queue updates by type (and CDN) - _, _, err = TOSession.SetServerQueueUpdatesByType(server.Type, *server.CDNName, true, client.NewRequestOptions()) + _, _, err = TOSession.SetServerQueueUpdatesByType(server.Type, cdns.Response[0].ID, true, client.NewRequestOptions()) if err != nil { t.Errorf("couldn't queue updates by type (and CDN): %v", err) } @@ -108,7 +108,7 @@ func QueueUpdatesByProfile(t *testing.T) { opts.QueryParameters.Del("name") // Queue updates by profile (and CDN) - _, _, err = TOSession.SetServerQueueUpdatesByProfile(profiles.Response[0].Name, *server.CDNName, true, client.NewRequestOptions()) + _, _, err = TOSession.SetServerQueueUpdatesByProfile(profiles.Response[0].Name, cdns.Response[0].ID, true, client.NewRequestOptions()) if err != nil { t.Errorf("couldn't queue updates by profile (and CDN): %v", err) } diff --git a/traffic_ops/traffic_ops_golang/cdn/queue.go b/traffic_ops/traffic_ops_golang/cdn/queue.go index 05e175dd0b..7be17ac1ad 100644 --- a/traffic_ops/traffic_ops_golang/cdn/queue.go +++ b/traffic_ops/traffic_ops_golang/cdn/queue.go @@ -20,25 +20,42 @@ package cdn */ import ( - "database/sql" "encoding/json" "errors" + "fmt" "net/http" "strconv" "github.com/apache/trafficcontrol/lib/go-tc" - + "github.com/apache/trafficcontrol/lib/go-util" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers" + + "github.com/jmoiron/sqlx" ) func Queue(w http.ResponseWriter, r *http.Request) { + var typeID int + var profileID int + var ok bool + var err error + 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 } defer inf.Close() + + cols := map[string]dbhelpers.WhereColumnInfo{ + "cdnID": {Column: "server.cdn_id", Checker: nil}, + "typeID": {Column: "server.type", Checker: nil}, + "profileID": {Column: "server.profile", Checker: nil}, + } + + typeName := inf.Params["type"] + profile := inf.Params["profile"] + reqObj := tc.CDNQueueUpdateRequest{} if err := json.NewDecoder(r.Body).Decode(&reqObj); err != nil { api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("malformed JSON: "+err.Error()), nil) @@ -56,22 +73,76 @@ func Queue(w http.ResponseWriter, r *http.Request) { api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, nil, nil) return } + + // get type ID + if typeName != "" { + typeID, ok, err = dbhelpers.GetTypeIDByName(typeName, inf.Tx.Tx) + if err != nil { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("error getting type ID from name: "+err.Error())) + return + } + if !ok { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, errors.New("no type ID found with that name"), nil) + return + } + inf.Params["typeID"] = strconv.Itoa(typeID) + } + delete(inf.Params, "type") + + // get profile ID + if profile != "" { + profileID, ok, err = dbhelpers.GetProfileIDFromName(profile, inf.Tx.Tx) + if err != nil { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("error getting profile ID from name: "+err.Error())) + return + } + if !ok { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, errors.New("no profile ID found with that name"), nil) + return + } + inf.Params["profileID"] = strconv.Itoa(profileID) + } + delete(inf.Params, "profile") + userErr, sysErr, statusCode := dbhelpers.CheckIfCurrentUserHasCdnLock(inf.Tx.Tx, string(cdnName), inf.User.UserName) if userErr != nil || sysErr != nil { api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, sysErr) return } - if err := queueUpdates(inf.Tx.Tx, int64(inf.IntParams["id"]), reqObj.Action == "queue"); err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("CDN queueing updates: "+err.Error())) + + where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, cols) + if len(errs) > 0 { + errCode = http.StatusBadRequest + userErr = util.JoinErrs(errs) + api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, nil) + return + } + + query := `UPDATE server SET upd_pending = :upd_pending` + query = query + where + orderBy + pagination + queryValues["upd_pending"] = reqObj.Action == "queue" + ok, err = queueUpdates(inf.Tx, queryValues, query) + if err != nil { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("queueing updates: %v", err)) + return + } + if !ok { + api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, fmt.Errorf("no server with the given combination found"), nil) return } + api.CreateChangeLogRawTx(api.ApiChange, "CDN: "+string(cdnName)+", ID: "+strconv.Itoa(inf.IntParams["id"])+", ACTION: CDN server updates "+reqObj.Action+"d", inf.User, inf.Tx.Tx) - api.WriteResp(w, r, tc.CDNQueueUpdateResponse{Action: reqObj.Action, CDNID: int64(inf.IntParams["id"])}) + api.WriteResp(w, r, tc.ServerGenericQueueUpdateResponse{Action: reqObj.Action, CDNID: inf.IntParams["id"], TypeID: typeID, ProfileID: profileID}) } -func queueUpdates(tx *sql.Tx, cdnID int64, queue bool) error { - if _, err := tx.Exec(`UPDATE server SET upd_pending = $1 WHERE server.cdn_id = $2`, queue, cdnID); err != nil { - return errors.New("querying queue updates: " + err.Error()) +// queueUpdates is the helper function to queue/ dequeue updates on servers for a CDN, optionally filtered by type and/ or profile +func queueUpdates(tx *sqlx.Tx, queryValues map[string]interface{}, query string) (bool, error) { + result, err := tx.NamedExec(query, queryValues) + if err != nil { + return false, errors.New("querying queue updates: " + err.Error()) + } else if rc, err := result.RowsAffected(); err != nil { + return false, fmt.Errorf("checking rows updated: %v", err) + } else { + return rc > 0, nil } - return nil } diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go b/traffic_ops/traffic_ops_golang/routing/routes.go index 730bc5a66b..57e52a88b9 100644 --- a/traffic_ops/traffic_ops_golang/routing/routes.go +++ b/traffic_ops/traffic_ops_golang/routing/routes.go @@ -134,9 +134,6 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) { * 4.x API */ - // Queue Updates - {api.Version{Major: 4, Minor: 0}, http.MethodPut, `queue_updates/{cdn}?$`, server.ProfileAndTypeQueueUpdateHandler, auth.PrivLevelOperations, Authenticated, nil, 4134390571}, - // CDN lock {api.Version{Major: 4, Minor: 0}, http.MethodGet, `cdn_locks/?$`, cdn_lock.Read, auth.PrivLevelReadOnly, Authenticated, nil, 4134390561}, {api.Version{Major: 4, Minor: 0}, http.MethodPost, `cdn_locks/?$`, cdn_lock.Create, auth.PrivLevelOperations, Authenticated, nil, 4134390562}, diff --git a/traffic_ops/traffic_ops_golang/server/update.go b/traffic_ops/traffic_ops_golang/server/update.go index 33c20dbcd9..54ae48d11c 100644 --- a/traffic_ops/traffic_ops_golang/server/update.go +++ b/traffic_ops/traffic_ops_golang/server/update.go @@ -21,19 +21,14 @@ package server import ( "database/sql" - "encoding/json" "errors" - "fmt" "net/http" "strconv" "strings" "github.com/apache/trafficcontrol/lib/go-tc" - "github.com/apache/trafficcontrol/lib/go-util" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api" "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers" - - "github.com/jmoiron/sqlx" ) // UpdateHandler implements an http handler that updates a server's upd_pending and reval_pending values. @@ -167,124 +162,3 @@ func setUpdateStatuses(tx *sql.Tx, hostName string, updatePending *bool, revalPe } return nil } - -// ProfileAndTypeQueueUpdateHandler queues/ dequeues updates on servers for a particular CDN, filtered by type and/ or profile. -func ProfileAndTypeQueueUpdateHandler(w http.ResponseWriter, r *http.Request) { - var cdnID int - var typeID int - var profileID = -1 - var ok bool - var err error - inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"cdn"}, nil) - if userErr != nil || sysErr != nil { - api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr) - return - } - defer inf.Close() - - cols := map[string]dbhelpers.WhereColumnInfo{ - "cdnID": {Column: "server.cdn_id", Checker: nil}, - "typeID": {Column: "server.type", Checker: nil}, - "profileID": {Column: "server.profile", Checker: nil}, - } - - cdn := inf.Params["cdn"] - typeName := inf.Params["type"] - profile := inf.Params["profile"] - - var reqObj tc.ServerQueueUpdateRequest - - if err := json.NewDecoder(r.Body).Decode(&reqObj); err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, fmt.Errorf("malformed JSON: %v", err), nil) - return - } - - if reqObj.Action != "queue" && reqObj.Action != "dequeue" { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("action must be 'queue' or 'dequeue'"), nil) - return - } - queue := reqObj.Action == "queue" - - // get cdn ID - cdnID, ok, err = dbhelpers.GetCDNIDFromName(inf.Tx.Tx, tc.CDNName(cdn)) - if err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("error getting CDN ID from name: "+err.Error())) - return - } - if !ok { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, errors.New("no CDN ID found with that name"), nil) - return - } - delete(inf.Params, "cdn") - inf.Params["cdnID"] = strconv.Itoa(cdnID) - - // get type ID - if typeName != "" { - typeID, ok, err = dbhelpers.GetTypeIDByName(typeName, inf.Tx.Tx) - if err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("error getting type ID from name: "+err.Error())) - return - } - if !ok { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, errors.New("no type ID found with that name"), nil) - return - } - inf.Params["typeID"] = strconv.Itoa(typeID) - } - delete(inf.Params, "type") - - // get profile ID - if profile != "" { - profileID, ok, err = dbhelpers.GetProfileIDFromName(profile, inf.Tx.Tx) - if err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("error getting profile ID from name: "+err.Error())) - return - } - if !ok { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, errors.New("no profile ID found with that name"), nil) - return - } - inf.Params["profileID"] = strconv.Itoa(profileID) - } - delete(inf.Params, "profile") - - where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, cols) - if len(errs) > 0 { - errCode = http.StatusBadRequest - userErr = util.JoinErrs(errs) - api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, nil) - return - } - - userErr, sysErr, statusCode := dbhelpers.CheckIfCurrentUserHasCdnLock(inf.Tx.Tx, string(cdn), inf.User.UserName) - if userErr != nil || sysErr != nil { - api.HandleErr(w, r, inf.Tx.Tx, statusCode, userErr, sysErr) - return - } - query := `UPDATE server SET upd_pending = :upd_pending` - query = query + where + orderBy + pagination - queryValues["upd_pending"] = queue - ok, err = queueUpdatesByTypeOrProfile(inf.Tx, queryValues, query) - if err != nil { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("queueing updates: %v", err)) - return - } - if !ok { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, fmt.Errorf("no server with the given combination found"), nil) - return - } - api.CreateChangeLogRawTx(api.ApiChange, "CDN: "+cdn+", Type: "+typeName+", ACTION: CDN server updates "+reqObj.Action+"d", inf.User, inf.Tx.Tx) - api.WriteResp(w, r, tc.ServerGenericQueueUpdateResponse{Action: reqObj.Action, CDNID: cdnID, TypeID: typeID, ProfileID: profileID}) -} - -// queueUpdatesByTypeOrProfile is the helper function to queue/ dequeue updates on servers for a CDN, filtered by type and/ or profile -func queueUpdatesByTypeOrProfile(tx *sqlx.Tx, queryValues map[string]interface{}, query string) (bool, error) { - result, err := tx.NamedExec(query, queryValues) - if err != nil { - return false, errors.New("querying generic queue updates: " + err.Error()) - } else if rc, err := result.RowsAffected(); err != nil { - return false, fmt.Errorf("checking rows updated: %v", err) - } else { - return rc > 0, nil - } -} diff --git a/traffic_ops/v4-client/server_update_status.go b/traffic_ops/v4-client/server_update_status.go index c09b5b47d2..5fd0723c78 100644 --- a/traffic_ops/v4-client/server_update_status.go +++ b/traffic_ops/v4-client/server_update_status.go @@ -51,27 +51,27 @@ func (to *Session) SetServerQueueUpdate(serverID int, queueUpdate bool, opts Req // SetServerQueueUpdatesByType set the "updPending" field of a list of servers identified by // 'cdnName' and 'typeName' to the value of 'queueUpdate' -func (to *Session) SetServerQueueUpdatesByType(typeName string, cdnName string, queueUpdate bool, opts RequestOptions) (tc.ServerGenericQueueUpdateResponse, toclientlib.ReqInf, error) { +func (to *Session) SetServerQueueUpdatesByType(typeName string, cdnID int, queueUpdate bool, opts RequestOptions) (tc.ServerGenericQueueUpdateResponse, toclientlib.ReqInf, error) { req := tc.ServerQueueUpdateRequest{Action: queueUpdateActions[queueUpdate]} var resp tc.ServerGenericQueueUpdateResponse if opts.QueryParameters == nil { opts.QueryParameters = url.Values{} } - path := fmt.Sprintf("/queue_updates/%s?type=%s", url.PathEscape(cdnName), typeName) - reqInf, err := to.put(path, opts, req, &resp) + path := fmt.Sprintf("/cdns/%d/queue_update?type=%s", cdnID, typeName) + reqInf, err := to.post(path, opts, req, &resp) return resp, reqInf, err } // SetServerQueueUpdatesByProfile set the "updPending" field of a list of servers identified by // 'cdnName' and 'profileName' to the value of 'queueUpdate' -func (to *Session) SetServerQueueUpdatesByProfile(profileName string, cdnName string, queueUpdate bool, opts RequestOptions) (tc.ServerGenericQueueUpdateResponse, toclientlib.ReqInf, error) { +func (to *Session) SetServerQueueUpdatesByProfile(profileName string, cdnID int, queueUpdate bool, opts RequestOptions) (tc.ServerGenericQueueUpdateResponse, toclientlib.ReqInf, error) { req := tc.ServerQueueUpdateRequest{Action: queueUpdateActions[queueUpdate]} var resp tc.ServerGenericQueueUpdateResponse if opts.QueryParameters == nil { opts.QueryParameters = url.Values{} } - path := fmt.Sprintf("/queue_updates/%s?profile=%s", url.PathEscape(cdnName), profileName) - reqInf, err := to.put(path, opts, req, &resp) + path := fmt.Sprintf("/cdns/%d/queue_update?profile=%s", cdnID, profileName) + reqInf, err := to.post(path, opts, req, &resp) return resp, reqInf, err } diff --git a/traffic_portal/app/src/common/api/ProfileService.js b/traffic_portal/app/src/common/api/ProfileService.js index 0f2289d8f8..d8e28f8982 100644 --- a/traffic_portal/app/src/common/api/ProfileService.js +++ b/traffic_portal/app/src/common/api/ProfileService.js @@ -132,8 +132,8 @@ var ProfileService = function($http, locationUtils, messageModel, ENV) { ); }; - this.queueServerUpdatesByProfile = function(cdnName, profileName) { - return $http.put(ENV.api['root'] + 'queue_updates/' + cdnName + '?profile=' + profileName, {action: "queue"}).then( + this.queueServerUpdatesByProfile = function(cdnID, profileName) { + return $http.post(ENV.api['root'] + 'cdns/' + cdnID + '/queue_update?profile=' + profileName, {action: "queue"}).then( function(result) { messageModel.setMessages([{level: 'success', text: 'Queued server updates by profile'}], false); return result; @@ -145,8 +145,8 @@ var ProfileService = function($http, locationUtils, messageModel, ENV) { ); }; - this.clearServerUpdatesByProfile = function(cdnName, profileName) { - return $http.put(ENV.api['root'] + 'queue_updates/' + cdnName + '?profile=' + profileName, {action: "dequeue"}).then( + this.clearServerUpdatesByProfile = function(cdnID, profileName) { + return $http.post(ENV.api['root'] + 'cdns/' + cdnID + '/queue_update?profile=' + profileName, {action: "dequeue"}).then( function(result) { messageModel.setMessages([{level: 'success', text: 'Cleared server updates by profile'}], false); return result; diff --git a/traffic_portal/app/src/common/api/TypeService.js b/traffic_portal/app/src/common/api/TypeService.js index b246a37f9d..c5a3991c07 100644 --- a/traffic_portal/app/src/common/api/TypeService.js +++ b/traffic_portal/app/src/common/api/TypeService.js @@ -83,8 +83,8 @@ var TypeService = function($http, ENV, locationUtils, messageModel) { ); }; - this.queueServerUpdates = function(cdnName, typeName) { - return $http.put(ENV.api['root'] + 'queue_updates/' + cdnName + '?type=' + typeName, {action: "queue"}).then( + this.queueServerUpdates = function(cdnID, typeName) { + return $http.post(ENV.api['root'] + 'cdns/' + cdnID +'/queue_update?type=' + typeName, {action: "queue"}).then( function(result) { messageModel.setMessages([{level: 'success', text: 'Queued server updates by type'}], false); return result; @@ -96,8 +96,8 @@ var TypeService = function($http, ENV, locationUtils, messageModel) { ); }; - this.clearServerUpdates = function(cdnName, typeName) { - return $http.put(ENV.api['root'] + 'queue_updates/' + cdnName + '?type=' + typeName, {action: "dequeue"}).then( + this.clearServerUpdates = function(cdnID, typeName) { + return $http.post(ENV.api['root'] + 'cdns/' + cdnID + '/queue_update?type=' + typeName, {action: "dequeue"}).then( function(result) { messageModel.setMessages([{level: 'success', text: 'Cleared server updates by type'}], false); return result; diff --git a/traffic_portal/app/src/common/modules/form/profile/FormProfileController.js b/traffic_portal/app/src/common/modules/form/profile/FormProfileController.js index acc0b01cf5..50be8ff4f9 100644 --- a/traffic_portal/app/src/common/modules/form/profile/FormProfileController.js +++ b/traffic_portal/app/src/common/modules/form/profile/FormProfileController.js @@ -96,11 +96,11 @@ var FormProfileController = function(profile, $scope, $location, $uibModal, file }; $scope.queueUpdatesByProfile = function() { - profileService.queueServerUpdatesByProfile($scope.profile.cdnName, $scope.profile.name).then($scope.refresh); + profileService.queueServerUpdatesByProfile($scope.profile.cdn, $scope.profile.name).then($scope.refresh); }; $scope.clearUpdatesByProfile = function() { - profileService.clearServerUpdatesByProfile($scope.profile.cdnName, $scope.profile.name).then($scope.refresh); + profileService.clearServerUpdatesByProfile($scope.profile.cdn, $scope.profile.name).then($scope.refresh); }; $scope.navigateToPath = locationUtils.navigateToPath; diff --git a/traffic_portal/app/src/common/modules/form/type/FormTypeController.js b/traffic_portal/app/src/common/modules/form/type/FormTypeController.js index cf65d7959b..b8b514e884 100644 --- a/traffic_portal/app/src/common/modules/form/type/FormTypeController.js +++ b/traffic_portal/app/src/common/modules/form/type/FormTypeController.js @@ -51,7 +51,7 @@ var FormTypeController = function(type, $scope, $location, formUtils, stringUtil } }); modalInstance.result.then(function(cdn) { - typeService.queueServerUpdates(cdn.name, $scope.type.name).then($scope.refresh); + typeService.queueServerUpdates(cdn.id, $scope.type.name).then($scope.refresh); }, function () { // do nothing }); @@ -76,7 +76,7 @@ var FormTypeController = function(type, $scope, $location, formUtils, stringUtil } }); modalInstance.result.then(function(cdn) { - typeService.clearServerUpdates(cdn.name, $scope.type.name).then($scope.refresh); + typeService.clearServerUpdates(cdn.id, $scope.type.name).then($scope.refresh); }, function () { // do nothing }); From 5e01d12ae0599b365cd4752bb05a6b9bd1256b2b Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Thu, 15 Jul 2021 10:39:45 -0600 Subject: [PATCH 07/10] code review fixes --- lib/go-tc/servers.go | 10 ------- ...cdn_queue_updates_by_type_profile_test.go} | 12 +++++---- traffic_ops/traffic_ops_golang/cdn/queue.go | 16 +++++++----- traffic_ops/v4-client/cdn.go | 13 ++++++++++ traffic_ops/v4-client/server_update_status.go | 26 ------------------- .../modules/form/type/form.type.tpl.html | 2 +- 6 files changed, 30 insertions(+), 49 deletions(-) rename traffic_ops/testing/api/v4/{server_queue_updates_by_type_profile_test.go => cdn_queue_updates_by_type_profile_test.go} (90%) diff --git a/lib/go-tc/servers.go b/lib/go-tc/servers.go index 70797ac32d..ffa3bc3d84 100644 --- a/lib/go-tc/servers.go +++ b/lib/go-tc/servers.go @@ -1192,13 +1192,3 @@ type ServerQueueUpdate struct { ServerID util.JSONIntStr `json:"serverId"` Action string `json:"action"` } - -// ServerGenericQueueUpdateResponse encodes the response data for the POST -// queue_updates endpoint. -type ServerGenericQueueUpdateResponse struct { - Action string `json:"action"` - CDNID int `json:"cdnID"` - TypeID int `json:"typeID,omitempty"` - ProfileID int `json:"profileID,omitempty"` - Alerts -} diff --git a/traffic_ops/testing/api/v4/server_queue_updates_by_type_profile_test.go b/traffic_ops/testing/api/v4/cdn_queue_updates_by_type_profile_test.go similarity index 90% rename from traffic_ops/testing/api/v4/server_queue_updates_by_type_profile_test.go rename to traffic_ops/testing/api/v4/cdn_queue_updates_by_type_profile_test.go index ef737f2657..3cafd7508b 100644 --- a/traffic_ops/testing/api/v4/server_queue_updates_by_type_profile_test.go +++ b/traffic_ops/testing/api/v4/cdn_queue_updates_by_type_profile_test.go @@ -23,7 +23,7 @@ import ( client "github.com/apache/trafficcontrol/traffic_ops/v4-client" ) -func TestServerQueueUpdateByProfileAndType(t *testing.T) { +func TestCDNQueueUpdateByProfileAndType(t *testing.T) { WithObjs(t, []TCObj{Types, CDNs, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers}, func() { QueueUpdatesByType(t) QueueUpdatesByProfile(t) @@ -31,6 +31,7 @@ func TestServerQueueUpdateByProfileAndType(t *testing.T) { } func QueueUpdatesByType(t *testing.T) { + queryOpts := client.NewRequestOptions() if len(testData.Servers) < 1 { t.Fatalf("no servers to run the tests on...quitting.") } @@ -50,9 +51,9 @@ func QueueUpdatesByType(t *testing.T) { t.Fatalf("expected 1 CDN in response, got %d", len(cdns.Response)) } opts.QueryParameters.Del("name") - + queryOpts.QueryParameters.Set("type", server.Type) // Queue updates by type (and CDN) - _, _, err = TOSession.SetServerQueueUpdatesByType(server.Type, cdns.Response[0].ID, true, client.NewRequestOptions()) + _, _, err = TOSession.QueueUpdatesForCDN(cdns.Response[0].ID, true, queryOpts) if err != nil { t.Errorf("couldn't queue updates by type (and CDN): %v", err) } @@ -75,6 +76,7 @@ func QueueUpdatesByType(t *testing.T) { } func QueueUpdatesByProfile(t *testing.T) { + queryOpts := client.NewRequestOptions() if len(testData.Servers) < 1 { t.Fatalf("no servers to run the tests on...quitting.") } @@ -106,9 +108,9 @@ func QueueUpdatesByProfile(t *testing.T) { t.Fatalf("expected 1 profile in response, got %d", len(profiles.Response)) } opts.QueryParameters.Del("name") - + queryOpts.QueryParameters.Set("profile", profiles.Response[0].Name) // Queue updates by profile (and CDN) - _, _, err = TOSession.SetServerQueueUpdatesByProfile(profiles.Response[0].Name, cdns.Response[0].ID, true, client.NewRequestOptions()) + _, _, err = TOSession.QueueUpdatesForCDN(cdns.Response[0].ID, true, queryOpts) if err != nil { t.Errorf("couldn't queue updates by profile (and CDN): %v", err) } diff --git a/traffic_ops/traffic_ops_golang/cdn/queue.go b/traffic_ops/traffic_ops_golang/cdn/queue.go index 7be17ac1ad..2c32b07243 100644 --- a/traffic_ops/traffic_ops_golang/cdn/queue.go +++ b/traffic_ops/traffic_ops_golang/cdn/queue.go @@ -39,6 +39,8 @@ func Queue(w http.ResponseWriter, r *http.Request) { var profileID int var ok bool var err error + var str string + queryParams := make(map[string]string, 0) inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"id"}, []string{"id"}) if userErr != nil || sysErr != nil { @@ -85,9 +87,9 @@ func Queue(w http.ResponseWriter, r *http.Request) { api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, errors.New("no type ID found with that name"), nil) return } - inf.Params["typeID"] = strconv.Itoa(typeID) + queryParams["typeID"] = strconv.Itoa(typeID) + str = fmt.Sprintf(" typeID: %d", typeID) } - delete(inf.Params, "type") // get profile ID if profile != "" { @@ -100,9 +102,9 @@ func Queue(w http.ResponseWriter, r *http.Request) { api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, errors.New("no profile ID found with that name"), nil) return } - inf.Params["profileID"] = strconv.Itoa(profileID) + queryParams["profileID"] = strconv.Itoa(profileID) + str = fmt.Sprintf(" profileID: %d", profileID) } - delete(inf.Params, "profile") userErr, sysErr, statusCode := dbhelpers.CheckIfCurrentUserHasCdnLock(inf.Tx.Tx, string(cdnName), inf.User.UserName) if userErr != nil || sysErr != nil { @@ -110,7 +112,7 @@ func Queue(w http.ResponseWriter, r *http.Request) { return } - where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(inf.Params, cols) + where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(queryParams, cols) if len(errs) > 0 { errCode = http.StatusBadRequest userErr = util.JoinErrs(errs) @@ -131,8 +133,8 @@ func Queue(w http.ResponseWriter, r *http.Request) { return } - api.CreateChangeLogRawTx(api.ApiChange, "CDN: "+string(cdnName)+", ID: "+strconv.Itoa(inf.IntParams["id"])+", ACTION: CDN server updates "+reqObj.Action+"d", inf.User, inf.Tx.Tx) - api.WriteResp(w, r, tc.ServerGenericQueueUpdateResponse{Action: reqObj.Action, CDNID: inf.IntParams["id"], TypeID: typeID, ProfileID: profileID}) + api.CreateChangeLogRawTx(api.ApiChange, "CDN: "+string(cdnName)+", ID: "+strconv.Itoa(inf.IntParams["id"])+str+", ACTION: CDN server updates "+reqObj.Action+"d", inf.User, inf.Tx.Tx) + api.WriteResp(w, r, tc.CDNQueueUpdateResponse{Action: reqObj.Action, CDNID: int64(inf.IntParams["id"])}) } // queueUpdates is the helper function to queue/ dequeue updates on servers for a CDN, optionally filtered by type and/ or profile diff --git a/traffic_ops/v4-client/cdn.go b/traffic_ops/v4-client/cdn.go index 4fbb0ee6eb..99979c16fa 100644 --- a/traffic_ops/v4-client/cdn.go +++ b/traffic_ops/v4-client/cdn.go @@ -63,3 +63,16 @@ func (to *Session) GetCDNSSLKeys(name string, opts RequestOptions) (tc.CDNSSLKey reqInf, err := to.get(route, opts, &data) return data, reqInf, err } + +// QueueUpdatesForCDN set the "updPending" field of a list of servers identified by +// 'cdnID' and any other query params (type or profile) to the value of 'queueUpdate' +func (to *Session) QueueUpdatesForCDN(cdnID int, queueUpdate bool, opts RequestOptions) (tc.CDNQueueUpdateResponse, toclientlib.ReqInf, error) { + req := tc.CDNQueueUpdateRequest{Action: queueUpdateActions[queueUpdate]} + var resp tc.CDNQueueUpdateResponse + if opts.QueryParameters == nil { + opts.QueryParameters = url.Values{} + } + path := fmt.Sprintf("/cdns/%d/queue_update", cdnID) + reqInf, err := to.post(path, opts, req, &resp) + return resp, reqInf, err +} diff --git a/traffic_ops/v4-client/server_update_status.go b/traffic_ops/v4-client/server_update_status.go index 5fd0723c78..e1b6165598 100644 --- a/traffic_ops/v4-client/server_update_status.go +++ b/traffic_ops/v4-client/server_update_status.go @@ -49,32 +49,6 @@ func (to *Session) SetServerQueueUpdate(serverID int, queueUpdate bool, opts Req return resp, reqInf, err } -// SetServerQueueUpdatesByType set the "updPending" field of a list of servers identified by -// 'cdnName' and 'typeName' to the value of 'queueUpdate' -func (to *Session) SetServerQueueUpdatesByType(typeName string, cdnID int, queueUpdate bool, opts RequestOptions) (tc.ServerGenericQueueUpdateResponse, toclientlib.ReqInf, error) { - req := tc.ServerQueueUpdateRequest{Action: queueUpdateActions[queueUpdate]} - var resp tc.ServerGenericQueueUpdateResponse - if opts.QueryParameters == nil { - opts.QueryParameters = url.Values{} - } - path := fmt.Sprintf("/cdns/%d/queue_update?type=%s", cdnID, typeName) - reqInf, err := to.post(path, opts, req, &resp) - return resp, reqInf, err -} - -// SetServerQueueUpdatesByProfile set the "updPending" field of a list of servers identified by -// 'cdnName' and 'profileName' to the value of 'queueUpdate' -func (to *Session) SetServerQueueUpdatesByProfile(profileName string, cdnID int, queueUpdate bool, opts RequestOptions) (tc.ServerGenericQueueUpdateResponse, toclientlib.ReqInf, error) { - req := tc.ServerQueueUpdateRequest{Action: queueUpdateActions[queueUpdate]} - var resp tc.ServerGenericQueueUpdateResponse - if opts.QueryParameters == nil { - opts.QueryParameters = url.Values{} - } - path := fmt.Sprintf("/cdns/%d/queue_update?profile=%s", cdnID, profileName) - reqInf, err := to.post(path, opts, req, &resp) - return resp, reqInf, err -} - // SetUpdateServerStatuses updates a server's queue status and/or reval status. // Either updateStatus or revalStatus may be nil, in which case that status // isn't updated (but not both, because that wouldn't do anything). diff --git a/traffic_portal/app/src/common/modules/form/type/form.type.tpl.html b/traffic_portal/app/src/common/modules/form/type/form.type.tpl.html index d2d93d20ec..7afd100dac 100644 --- a/traffic_portal/app/src/common/modules/form/type/form.type.tpl.html +++ b/traffic_portal/app/src/common/modules/form/type/form.type.tpl.html @@ -36,7 +36,7 @@
    From c3102d313337ab6711222c2efd3451cbc20badfd Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Fri, 16 Jul 2021 16:45:20 -0600 Subject: [PATCH 08/10] code review changes --- docs/source/api/v4/cdns_id_queue_update.rst | 5 +- .../cdn_queue_updates_by_type_profile_test.go | 82 +++++++++++++++++++ traffic_ops/traffic_ops_golang/cdn/queue.go | 11 +-- 3 files changed, 90 insertions(+), 8 deletions(-) diff --git a/docs/source/api/v4/cdns_id_queue_update.rst b/docs/source/api/v4/cdns_id_queue_update.rst index 14f734468d..63f0548285 100644 --- a/docs/source/api/v4/cdns_id_queue_update.rst +++ b/docs/source/api/v4/cdns_id_queue_update.rst @@ -80,10 +80,9 @@ Response Structure Whole-Content-Sha512: rBpFfrrP+9IFkwsRloEM+v+I8MuBZDXqFu+WUTGtRGypnAn2gHooPoNQRyVvJGjyIQrLXLvqjEtve+lH2Tj4uw== X-Server-Name: traffic_ops_golang/ Date: Wed, 14 Nov 2018 21:02:07 GMT - Content-Length: 54 + Content-Length: 41 { "response": { "action": "queue", - "cdnId": 2, - "typeID": 11 + "cdnId": 2 }} diff --git a/traffic_ops/testing/api/v4/cdn_queue_updates_by_type_profile_test.go b/traffic_ops/testing/api/v4/cdn_queue_updates_by_type_profile_test.go index 3cafd7508b..4757360597 100644 --- a/traffic_ops/testing/api/v4/cdn_queue_updates_by_type_profile_test.go +++ b/traffic_ops/testing/api/v4/cdn_queue_updates_by_type_profile_test.go @@ -31,6 +31,25 @@ func TestCDNQueueUpdateByProfileAndType(t *testing.T) { } func QueueUpdatesByType(t *testing.T) { + allServersResp, _, err := TOSession.GetServers(client.NewRequestOptions()) + if err != nil { + t.Fatalf("couldn't get all servers: %v", err) + } + + // Clear updates on all servers to begin with + for _, s := range allServersResp.Response { + if s.ID != nil { + _, _, err = TOSession.SetServerQueueUpdate(*s.ID, false, client.NewRequestOptions()) + if err != nil { + t.Errorf("couldn't clear updates on server with ID: %d, err: %v", *s.ID, err.Error()) + } + } + } + + allServersResp, _, err = TOSession.GetServers(client.NewRequestOptions()) + if err != nil { + t.Fatalf("couldn't get all servers: %v", err) + } queryOpts := client.NewRequestOptions() if len(testData.Servers) < 1 { t.Fatalf("no servers to run the tests on...quitting.") @@ -61,6 +80,7 @@ func QueueUpdatesByType(t *testing.T) { // Get all the servers for the same CDN and type as that of the first server opts.QueryParameters.Set("cdn", strconv.Itoa(cdns.Response[0].ID)) opts.QueryParameters.Set("type", server.Type) + serverIDMap := make(map[int]bool, 0) resp, _, err := TOSession.GetServers(opts) if err != nil { t.Fatalf("couldn't get servers by cdn and type: %v", err) @@ -72,10 +92,48 @@ func QueueUpdatesByType(t *testing.T) { if s.UpdPending == nil || !*s.UpdPending { t.Errorf("expected updates to be queued on all the servers filtered by type and CDN, but %s didn't queue updates", *s.HostName) } + if s.ID != nil { + serverIDMap[*s.ID] = true + } + } + + // Make sure that the servers that are not filtered by the above criteria do not have updates queued + allServersResp, _, err = TOSession.GetServers(client.NewRequestOptions()) + if err != nil { + t.Fatalf("couldn't get all servers: %v", err) + } + for _, s := range allServersResp.Response { + if s.ID != nil { + if _, ok := serverIDMap[*s.ID]; !ok { + if s.UpdPending != nil && *s.UpdPending { + t.Errorf("did not expect server with ID: %d to have queued updates", *s.ID) + } + } + + } + } + _, _, err = TOSession.QueueUpdatesForCDN(cdns.Response[0].ID, false, queryOpts) + if err != nil { + t.Errorf("couldn't queue updates by type (and CDN): %v", err) } } func QueueUpdatesByProfile(t *testing.T) { + allServersResp, _, err := TOSession.GetServers(client.NewRequestOptions()) + if err != nil { + t.Fatalf("couldn't get all servers: %v", err) + } + + // Clear updates on all servers to begin with + for _, s := range allServersResp.Response { + if s.ID != nil { + _, _, err = TOSession.SetServerQueueUpdate(*s.ID, false, client.NewRequestOptions()) + if err != nil { + t.Errorf("couldn't clear updates on server with ID: %d, err: %v", *s.ID, err.Error()) + } + } + } + queryOpts := client.NewRequestOptions() if len(testData.Servers) < 1 { t.Fatalf("no servers to run the tests on...quitting.") @@ -118,6 +176,7 @@ func QueueUpdatesByProfile(t *testing.T) { // Get all the servers for the same CDN and profile as that of the first server opts.QueryParameters.Set("cdn", strconv.Itoa(cdns.Response[0].ID)) opts.QueryParameters.Set("profileId", strconv.Itoa(profiles.Response[0].ID)) + serverIDMap := make(map[int]bool, 0) resp, _, err := TOSession.GetServers(opts) if err != nil { t.Fatalf("couldn't get servers by cdn and profile: %v", err) @@ -129,5 +188,28 @@ func QueueUpdatesByProfile(t *testing.T) { if s.UpdPending == nil || !*s.UpdPending { t.Errorf("expected updates to be queued on all the servers filtered by profile and CDN, but %s didn't queue updates", *s.HostName) } + if s.ID != nil { + serverIDMap[*s.ID] = true + } + } + + // Make sure that the servers that are not filtered by the above criteria do not have updates queued + allServersResp, _, err = TOSession.GetServers(client.NewRequestOptions()) + if err != nil { + t.Fatalf("couldn't get all servers: %v", err) + } + for _, s := range allServersResp.Response { + if s.ID != nil { + if _, ok := serverIDMap[*s.ID]; !ok { + if s.UpdPending != nil && *s.UpdPending { + t.Errorf("did not expect server with ID: %d to have queued updates", *s.ID) + } + } + + } + } + _, _, err = TOSession.QueueUpdatesForCDN(cdns.Response[0].ID, false, queryOpts) + if err != nil { + t.Errorf("couldn't queue updates by type (and CDN): %v", err) } } diff --git a/traffic_ops/traffic_ops_golang/cdn/queue.go b/traffic_ops/traffic_ops_golang/cdn/queue.go index 2c32b07243..e2cbf7838a 100644 --- a/traffic_ops/traffic_ops_golang/cdn/queue.go +++ b/traffic_ops/traffic_ops_golang/cdn/queue.go @@ -40,7 +40,7 @@ func Queue(w http.ResponseWriter, r *http.Request) { var ok bool var err error var str string - queryParams := make(map[string]string, 0) + params := make(map[string]string, 0) inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"id"}, []string{"id"}) if userErr != nil || sysErr != nil { @@ -50,7 +50,7 @@ func Queue(w http.ResponseWriter, r *http.Request) { defer inf.Close() cols := map[string]dbhelpers.WhereColumnInfo{ - "cdnID": {Column: "server.cdn_id", Checker: nil}, + "cdnID": {Column: "server.cdn_id", Checker: api.IsInt}, "typeID": {Column: "server.type", Checker: nil}, "profileID": {Column: "server.profile", Checker: nil}, } @@ -67,6 +67,7 @@ func Queue(w http.ResponseWriter, r *http.Request) { api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("action must be 'queue' or 'dequeue'"), nil) return } + params["cdnID"] = strconv.Itoa(inf.IntParams["id"]) cdnName, ok, err := dbhelpers.GetCDNNameFromID(inf.Tx.Tx, int64(inf.IntParams["id"])) if err != nil { api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting cdn name from ID '"+inf.Params["id"]+"': "+err.Error())) @@ -87,7 +88,7 @@ func Queue(w http.ResponseWriter, r *http.Request) { api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, errors.New("no type ID found with that name"), nil) return } - queryParams["typeID"] = strconv.Itoa(typeID) + params["typeID"] = strconv.Itoa(typeID) str = fmt.Sprintf(" typeID: %d", typeID) } @@ -102,7 +103,7 @@ func Queue(w http.ResponseWriter, r *http.Request) { api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, errors.New("no profile ID found with that name"), nil) return } - queryParams["profileID"] = strconv.Itoa(profileID) + params["profileID"] = strconv.Itoa(profileID) str = fmt.Sprintf(" profileID: %d", profileID) } @@ -112,7 +113,7 @@ func Queue(w http.ResponseWriter, r *http.Request) { return } - where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(queryParams, cols) + where, orderBy, pagination, queryValues, errs := dbhelpers.BuildWhereAndOrderByAndPagination(params, cols) if len(errs) > 0 { errCode = http.StatusBadRequest userErr = util.JoinErrs(errs) From 8a45539527a931bd847ffee3799981610632faac Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Mon, 19 Jul 2021 09:45:32 -0600 Subject: [PATCH 09/10] move UI button to right --- .../modules/form/type/form.type.tpl.html | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/traffic_portal/app/src/common/modules/form/type/form.type.tpl.html b/traffic_portal/app/src/common/modules/form/type/form.type.tpl.html index 7afd100dac..58ef037876 100644 --- a/traffic_portal/app/src/common/modules/form/type/form.type.tpl.html +++ b/traffic_portal/app/src/common/modules/form/type/form.type.tpl.html @@ -28,16 +28,16 @@ - -
    - - +
    + + +
    From 7a276b65079e10b5eab542e382f221713a97d0cb Mon Sep 17 00:00:00 2001 From: Srijeet Chatterjee Date: Mon, 19 Jul 2021 11:31:14 -0600 Subject: [PATCH 10/10] do not throw an error if no servers matched the criteria --- traffic_ops/traffic_ops_golang/cdn/queue.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/traffic_ops/traffic_ops_golang/cdn/queue.go b/traffic_ops/traffic_ops_golang/cdn/queue.go index e2cbf7838a..5299d2bad8 100644 --- a/traffic_ops/traffic_ops_golang/cdn/queue.go +++ b/traffic_ops/traffic_ops_golang/cdn/queue.go @@ -124,28 +124,24 @@ func Queue(w http.ResponseWriter, r *http.Request) { query := `UPDATE server SET upd_pending = :upd_pending` query = query + where + orderBy + pagination queryValues["upd_pending"] = reqObj.Action == "queue" - ok, err = queueUpdates(inf.Tx, queryValues, query) + rowsAffected, err := queueUpdates(inf.Tx, queryValues, query) if err != nil { api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, fmt.Errorf("queueing updates: %v", err)) return } - if !ok { - api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, fmt.Errorf("no server with the given combination found"), nil) - return - } - api.CreateChangeLogRawTx(api.ApiChange, "CDN: "+string(cdnName)+", ID: "+strconv.Itoa(inf.IntParams["id"])+str+", ACTION: CDN server updates "+reqObj.Action+"d", inf.User, inf.Tx.Tx) + api.CreateChangeLogRawTx(api.ApiChange, "CDN: "+string(cdnName)+", ID: "+strconv.Itoa(inf.IntParams["id"])+str+", ACTION: server updates "+reqObj.Action+"d on "+strconv.Itoa(int(rowsAffected))+" servers", inf.User, inf.Tx.Tx) api.WriteResp(w, r, tc.CDNQueueUpdateResponse{Action: reqObj.Action, CDNID: int64(inf.IntParams["id"])}) } // queueUpdates is the helper function to queue/ dequeue updates on servers for a CDN, optionally filtered by type and/ or profile -func queueUpdates(tx *sqlx.Tx, queryValues map[string]interface{}, query string) (bool, error) { +func queueUpdates(tx *sqlx.Tx, queryValues map[string]interface{}, query string) (int64, error) { result, err := tx.NamedExec(query, queryValues) if err != nil { - return false, errors.New("querying queue updates: " + err.Error()) + return 0, errors.New("querying queue updates: " + err.Error()) } else if rc, err := result.RowsAffected(); err != nil { - return false, fmt.Errorf("checking rows updated: %v", err) + return rc, fmt.Errorf("checking rows updated: %v", err) } else { - return rc > 0, nil + return rc, nil } }