You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by oc...@apache.org on 2020/04/09 16:40:13 UTC
[trafficcontrol] branch master updated: Add TO /server/update
(#4599)
This is an automated email from the ASF dual-hosted git repository.
ocket8888 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/trafficcontrol.git
The following commit(s) were added to refs/heads/master by this push:
new 7108d05 Add TO /server/update (#4599)
7108d05 is described below
commit 7108d055721286070f798a75a34dfb1672150dce
Author: Robert O Butts <ro...@users.noreply.github.com>
AuthorDate: Thu Apr 9 10:40:06 2020 -0600
Add TO /server/update (#4599)
Needed by ORT. The only existing endpoint do set reval without
overwriting existing values and creating race conditions is /update
outside the API.
---
CHANGELOG.md | 1 +
docs/source/api/v2/servers_hostname_update.rst | 87 ++++++++++++
traffic_ops/client/server_update_status.go | 30 ++++
.../atstccfg/update-to-client/update-to-client.go | 1 -
.../testing/api/v2/serverupdatestatus_test.go | 60 ++++++++
traffic_ops/traffic_ops_golang/routing/routes.go | 1 +
traffic_ops/traffic_ops_golang/server/update.go | 157 +++++++++++++++++++++
7 files changed, 336 insertions(+), 1 deletion(-)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index deb9525..5330ac5 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -33,6 +33,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/).
- /api/2.0/servercheck `GET`
- /api/2.0/servercheck/extensions/:id `(DELETE)`
- /api/2.0/servercheck/extensions `(GET, POST)`
+ - /api/2.0/servers/:name-or-id/update `POST`
- /api/2.0/plugins `(GET)`
- /api/2.0/snapshot `PUT`
diff --git a/docs/source/api/v2/servers_hostname_update.rst b/docs/source/api/v2/servers_hostname_update.rst
new file mode 100644
index 0000000..7494b75
--- /dev/null
+++ b/docs/source/api/v2/servers_hostname_update.rst
@@ -0,0 +1,87 @@
+..
+..
+.. 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-servers-hostname-update:
+
+*************************************
+``servers/{{HostName-Or-ID}}/update``
+*************************************
+
+``POST``
+========
+:term:`Queue` or dequeue updates and revalidation updates for a specific server.
+
+:Auth. Required: Yes
+:Roles Required: "admin" or "operations"
+:Response Type: undefined
+
+Request Structure
+-----------------
+.. table:: Request Path Parameters
+
+ +------------------+---------------------------------------------------------------------------------------------------------+
+ | Name | Description |
+ +==================+=========================================================================================================+
+ | HostName-OR-ID | The hostName or integral, unique identifier of the server on which updates are being queued or dequeued |
+ +------------------+---------------------------------------------------------------------------------------------------------+
+
+.. table:: Request Query Parameters
+
+ +---------------+----------+--------------------------------------------------------------------------------------+
+ | Name | Required | Description |
+ +===============+==========+======================================================================================+
+ | updated | no | The value to set for the queue update flag on this server. May be 'true' or 'false'. |
+ +---------------+----------+--------------------------------------------------------------------------------------+
+ | reval_updated | no | The value to set for the queue update flag on this server. May be 'true' or 'false'. |
+ +---------------+----------+--------------------------------------------------------------------------------------+
+
+.. code-block:: http
+ :caption: Request Example
+
+ GET /api/2.0/servers/my-edge/update?updated=true&reval_updated=false HTTP/1.1
+ Host: trafficops.infra.ciab.test
+ User-Agent: curl/7.47.0
+ Accept: */*
+ Cookie: mojolicious=...
+
+Response Structure
+------------------
+
+.. code-block:: http
+ :caption: Response Example
+
+ HTTP/1.1 200 OK
+ Access-Control-Allow-Credentials: true
+ Access-Control-Allow-Headers: Origin, X-Requested-With, Content-Type, Accept
+ Access-Control-Allow-Methods: POST,GET,OPTIONS,PUT,DELETE
+ Access-Control-Allow-Origin: *
+ Cache-Control: no-cache, no-store, max-age=0, must-revalidate
+ Content-Type: application/json
+ Date: Mon, 10 Dec 2018 18:20:04 GMT
+ X-Server-Name: traffic_ops_golang/
+ Set-Cookie: mojolicious=...; Path=/; Expires=Mon, 18 Nov 2019 17:40:54 GMT; Max-Age=3600; HttpOnly
+ Vary: Accept-Encoding
+ Whole-Content-Sha512: 9Mmo9hIFZyF5gAvfdJD//VH9eNgiHVLinXt88H0GlJSHhwND8gMxaFyC+f9XZfiNAoGd1MKi1934ZJGmaIR6qQ==
+ Content-Length: 49
+
+ {
+ "alerts" :
+ [
+ {
+ "text" : "successfully set server 'my-edge' updated=true reval_updated=false",
+ "level" : "success"
+ }
+ ]
+ }
diff --git a/traffic_ops/client/server_update_status.go b/traffic_ops/client/server_update_status.go
index 3ed1046..717f2d5 100644
--- a/traffic_ops/client/server_update_status.go
+++ b/traffic_ops/client/server_update_status.go
@@ -17,8 +17,11 @@ package client
import (
"encoding/json"
+ "errors"
"fmt"
"net/http"
+ "strconv"
+ "strings"
"github.com/apache/trafficcontrol/lib/go-tc"
)
@@ -78,3 +81,30 @@ func (to *Session) SetServerQueueUpdate(serverID int, queueUpdate bool) (tc.Serv
return resp, reqInf, err
}
+
+// UpdateServerStatus 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).
+func (to *Session) SetUpdateServerStatuses(serverName string, updateStatus *bool, revalStatus *bool) (ReqInf, error) {
+ reqInf := ReqInf{CacheHitStatus: CacheHitStatusMiss}
+ if updateStatus == nil && revalStatus == nil {
+ return reqInf, errors.New("either updateStatus or revalStatus must be non-nil; nothing to do")
+ }
+
+ path := apiBase + `/servers/` + serverName + `/update?`
+ queryParams := []string{}
+ if updateStatus != nil {
+ queryParams = append(queryParams, `updated=`+strconv.FormatBool(*updateStatus))
+ }
+ if revalStatus != nil {
+ queryParams = append(queryParams, `reval_updated=`+strconv.FormatBool(*revalStatus))
+ }
+ path += strings.Join(queryParams, `&`)
+
+ resp, remoteAddr, err := to.request(http.MethodPost, path, nil)
+ reqInf.RemoteAddr = remoteAddr
+ if err != nil {
+ return reqInf, err
+ }
+ resp.Body.Close()
+ return reqInf, nil
+}
diff --git a/traffic_ops/ort/atstccfg/update-to-client/update-to-client.go b/traffic_ops/ort/atstccfg/update-to-client/update-to-client.go
index 99855df..5f6144e 100644
--- a/traffic_ops/ort/atstccfg/update-to-client/update-to-client.go
+++ b/traffic_ops/ort/atstccfg/update-to-client/update-to-client.go
@@ -182,7 +182,6 @@ func updateNewClientUsage(appDir string) error {
}
if line == spacePrefix+`if `+errVar+` == nil && `+unsupportedVar+` {` ||
line == spacePrefix+`if `+unsupportedVar+` && `+errVar+` == nil `+` {` ||
- line == spacePrefix+`if `+unsupportedVar+` {` ||
line == spacePrefix+`if `+unsupportedVar+` {` {
state = stateInTOClientNewUnsupportedBlock
continue // continue without adding this line - we want to remove the check-and-fallback
diff --git a/traffic_ops/testing/api/v2/serverupdatestatus_test.go b/traffic_ops/testing/api/v2/serverupdatestatus_test.go
index 961fd99..0191cd5 100644
--- a/traffic_ops/testing/api/v2/serverupdatestatus_test.go
+++ b/traffic_ops/testing/api/v2/serverupdatestatus_test.go
@@ -235,3 +235,63 @@ func TestServerQueueUpdate(t *testing.T) {
})
})
}
+
+func TestSetServerUpdateStatuses(t *testing.T) {
+ WithObjs(t, []TCObj{CDNs, Types, Parameters, Profiles, Statuses, Divisions, Regions, PhysLocations, CacheGroups, Servers}, func() {
+ if len(testData.Servers) < 1 {
+ t.Fatal("cannot GET Server: no test data")
+ }
+ testServer := testData.Servers[0]
+
+ testVals := func(queue *bool, reval *bool) {
+ existingServer, _, err := TOSession.GetServerByHostName(testServer.HostName)
+ if err != nil {
+ t.Errorf("cannot GET Server by name: %v - %v", err, existingServer)
+ } else if len(existingServer) != 1 {
+ t.Fatalf("GET Server expected 1, actual %v", len(existingServer))
+ }
+
+ if _, err := TOSession.SetUpdateServerStatuses(testServer.HostName, queue, reval); err != nil {
+ t.Fatalf("UpdateServerStatuses error expected: nil, actual: %v", err)
+ }
+
+ newServer, _, err := TOSession.GetServerByHostName(testServer.HostName)
+ if err != nil {
+ t.Errorf("cannot GET Server by name: %v - %v", err, existingServer)
+ } else if len(newServer) != 1 {
+ t.Fatalf("GET Server expected 1, actual %v", len(newServer))
+ }
+
+ if queue != nil {
+ if newServer[0].UpdPending != *queue {
+ t.Errorf("set queue update pending to %v, but then got server %v", *queue, newServer[0].UpdPending)
+ }
+ } else {
+ if newServer[0].UpdPending != existingServer[0].UpdPending {
+ t.Errorf("set queue update pending with nil (don't update), but then got server %v which didn't match pre-update value %v", newServer[0].UpdPending, existingServer[0].UpdPending)
+ }
+ }
+ if reval != nil {
+ if newServer[0].RevalPending != *reval {
+ t.Errorf("set reval update pending to %v, but then got server %v", *reval, newServer[0].RevalPending)
+ }
+ } else {
+ if newServer[0].RevalPending != existingServer[0].RevalPending {
+ t.Errorf("set reval update pending with nil (don't update), but then got server %v which didn't match pre-update value %v", newServer[0].RevalPending, existingServer[0].RevalPending)
+ }
+ }
+ }
+
+ testVals(util.BoolPtr(true), util.BoolPtr(true))
+ testVals(util.BoolPtr(true), util.BoolPtr(false))
+ testVals(util.BoolPtr(false), util.BoolPtr(false))
+ testVals(nil, util.BoolPtr(true))
+ testVals(nil, util.BoolPtr(false))
+ testVals(util.BoolPtr(true), nil)
+ testVals(util.BoolPtr(false), nil)
+
+ if _, err := TOSession.SetUpdateServerStatuses(testServer.HostName, nil, nil); err == nil {
+ t.Errorf("UpdateServerStatuses with (nil,nil) expected error, actual nil")
+ }
+ })
+}
diff --git a/traffic_ops/traffic_ops_golang/routing/routes.go b/traffic_ops/traffic_ops_golang/routing/routes.go
index 8b326a5..a1d70ab 100644
--- a/traffic_ops/traffic_ops_golang/routing/routes.go
+++ b/traffic_ops/traffic_ops_golang/routing/routes.go
@@ -409,6 +409,7 @@ func Routes(d ServerData) ([]Route, []RawRoute, http.Handler, error) {
//Servers
{api.Version{2, 0}, http.MethodPost, `servers/{id}/deliveryservices$`, server.AssignDeliveryServicesToServerHandler, auth.PrivLevelOperations, Authenticated, nil, 280128253, noPerlBypass},
{api.Version{2, 0}, http.MethodGet, `servers/{host_name}/update_status$`, server.GetServerUpdateStatusHandler, auth.PrivLevelReadOnly, Authenticated, nil, 238451599, noPerlBypass},
+ {api.Version{2, 0}, http.MethodPost, `servers/{id-or-name}/update$`, server.UpdateHandler, auth.PrivLevelOperations, Authenticated, nil, 14381323, noPerlBypass},
//StaticDNSEntries
{api.Version{2, 0}, http.MethodGet, `staticdnsentries/?$`, api.ReadHandler(&staticdnsentry.TOStaticDNSEntry{}), auth.PrivLevelReadOnly, Authenticated, nil, 228939477, noPerlBypass},
diff --git a/traffic_ops/traffic_ops_golang/server/update.go b/traffic_ops/traffic_ops_golang/server/update.go
new file mode 100644
index 0000000..21fcd63
--- /dev/null
+++ b/traffic_ops/traffic_ops_golang/server/update.go
@@ -0,0 +1,157 @@
+package server
+
+/*
+ * 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"
+ "net/http"
+ "strconv"
+ "strings"
+
+ "github.com/apache/trafficcontrol/lib/go-tc"
+ "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/api"
+ "github.com/apache/trafficcontrol/traffic_ops/traffic_ops_golang/dbhelpers"
+)
+
+// UpdateHandler implements an http handler that updates a server's upd_pending and reval_pending values.
+func UpdateHandler(w http.ResponseWriter, r *http.Request) {
+ inf, userErr, sysErr, errCode := api.NewInfo(r, []string{"id-or-name"}, nil)
+ if userErr != nil || sysErr != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, errCode, userErr, sysErr)
+ return
+ }
+ defer inf.Close()
+
+ idOrName := inf.Params["id-or-name"]
+ id, err := strconv.Atoi(idOrName)
+ hostName := ""
+ if err == nil {
+ name, ok, err := dbhelpers.GetServerNameFromID(inf.Tx.Tx, id)
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting server name from id '"+idOrName+"': "+err.Error()))
+ return
+ } else if !ok {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, errors.New("server ID '"+idOrName+"' not found"), nil)
+ return
+ }
+ hostName = name
+ } else {
+ hostName = idOrName
+ if _, ok, err := dbhelpers.GetServerIDFromName(hostName, inf.Tx.Tx); err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("getting server id from name '"+idOrName+"': "+err.Error()))
+ return
+ } else if !ok {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusNotFound, errors.New("server name '"+idOrName+"' not found"), nil)
+ return
+ }
+ }
+
+ updated, hasUpdated := inf.Params["updated"]
+ revalUpdated, hasRevalUpdated := inf.Params["reval_updated"]
+ if !hasUpdated && !hasRevalUpdated {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("Must pass at least one query paramter of 'updated' or 'reval_updated'"), nil)
+ return
+ }
+ updated = strings.ToLower(updated)
+ revalUpdated = strings.ToLower(revalUpdated)
+
+ if hasUpdated && updated != `t` && updated != `true` && updated != `f` && updated != `false` {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("query parameter 'updated' must be 'true' or 'false'"), nil)
+ return
+ }
+ if hasRevalUpdated && revalUpdated != `t` && revalUpdated != `true` && revalUpdated != `f` && revalUpdated != `false` {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusBadRequest, errors.New("query parameter 'reval_updated' must be 'true' or 'false'"), nil)
+ return
+ }
+
+ strToBool := func(s string) bool {
+ return !strings.HasPrefix(strings.ToLower(s), "f")
+ }
+
+ updatedPtr := (*bool)(nil)
+ if hasUpdated {
+ updatedBool := strToBool(updated)
+ updatedPtr = &updatedBool
+ }
+ revalUpdatedPtr := (*bool)(nil)
+ if hasRevalUpdated {
+ revalUpdatedBool := strToBool(revalUpdated)
+ revalUpdatedPtr = &revalUpdatedBool
+ }
+
+ if err := setUpdateStatuses(inf.Tx.Tx, hostName, updatedPtr, revalUpdatedPtr); err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("setting updated statuses: "+err.Error()))
+ return
+ }
+
+ err = api.CreateChangeLogBuildMsg(
+ api.ApiChange,
+ api.Updated,
+ inf.User,
+ inf.Tx.Tx,
+ "server-update-status",
+ hostName,
+ map[string]interface{}{"host_name": hostName},
+ )
+ if err != nil {
+ api.HandleErr(w, r, inf.Tx.Tx, http.StatusInternalServerError, nil, errors.New("writing changelog: "+err.Error()))
+ return
+ }
+
+ respMsg := "successfully set server '" + hostName + "'"
+ if hasUpdated {
+ respMsg += " updated=" + strconv.FormatBool(strToBool(updated))
+ }
+ if hasRevalUpdated {
+ respMsg += " reval_updated=" + strconv.FormatBool(strToBool(revalUpdated))
+ }
+
+ api.WriteAlerts(w, r, http.StatusOK, tc.CreateAlerts(tc.SuccessLevel, respMsg))
+}
+
+// setUpdateStatuses sets the upd_pending and reval_pending columns of a server.
+// If updatePending or revalPending is nil, that value is not changed.
+func setUpdateStatuses(tx *sql.Tx, hostName string, updatePending *bool, revalPending *bool) error {
+ if updatePending == nil && revalPending == nil {
+ return errors.New("either updatePending or revalPending must not be nil")
+ }
+ qry := `UPDATE server SET `
+ updateStrs := []string{}
+ nextI := 1
+ qryVals := []interface{}{}
+ if updatePending != nil {
+ updateStrs = append(updateStrs, `upd_pending = $`+strconv.Itoa(nextI))
+ nextI++
+ qryVals = append(qryVals, *updatePending)
+ }
+ if revalPending != nil {
+ updateStrs = append(updateStrs, `reval_pending = $`+strconv.Itoa(nextI))
+ nextI++
+ qryVals = append(qryVals, *revalPending)
+ }
+ qry += strings.Join(updateStrs, ", ") + ` WHERE host_name = $` + strconv.Itoa(nextI)
+ qryVals = append(qryVals, hostName)
+
+ if _, err := tx.Exec(qry, qryVals...); err != nil {
+ return errors.New("executing: " + err.Error())
+ }
+ return nil
+}