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
+}