You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ro...@apache.org on 2021/03/23 22:08:57 UTC

[trafficcontrol] branch master updated: * Adds the Traffic Ops ORT updater, to_updater (#5614)

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

rob 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 fa014af   * Adds the Traffic Ops ORT updater, to_updater (#5614)
fa014af is described below

commit fa014afccea03196184a13fb2a13144cc3b20a05
Author: John J. Rushford <jr...@apache.org>
AuthorDate: Tue Mar 23 16:08:40 2021 -0600

     * Adds the Traffic Ops ORT updater, to_updater (#5614)
    
    * Adds and ORT integration tests for the to_updater
       and to_requester
     * Updates the pborman/getopt vendored package with
       required changes for the to_updater
     * Updates the traffic_ops_ort RPM spec file to include
       the to_requester and to_updater
---
 go.mod                                             |   5 +-
 go.sum                                             |  12 +-
 traffic_ops/toclientlib/toclientlib.go             |   1 -
 traffic_ops_ort/build/build_rpm.sh                 |   6 +
 traffic_ops_ort/build/traffic_ops_ort.spec         |  21 ++
 traffic_ops_ort/t3clib/getdata.go                  | 243 +++++++++++++++++++++
 traffic_ops_ort/t3clib/utils.go                    |  61 ++++++
 traffic_ops_ort/testing/ort-tests/t3c_mode_test.go |   9 +-
 traffic_ops_ort/testing/ort-tests/tc-fixtures.json |   2 +-
 .../testing/ort-tests/to_requester_test.go         | 145 ++++++++++++
 .../testing/ort-tests/to_updater_test.go           | 130 +++++++++++
 traffic_ops_ort/to_requester/config/config.go      |  49 ++---
 traffic_ops_ort/to_requester/to_requester.go       |  37 +---
 traffic_ops_ort/to_updater/README.md               |  66 ++++++
 .../{to_requester => to_updater}/config/config.go  |  73 ++-----
 traffic_ops_ort/to_updater/to_updater.go           |  71 ++++++
 vendor/github.com/pborman/getopt/v2/getopt.go      |  74 ++++++-
 vendor/github.com/pborman/getopt/v2/option.go      |  48 ++--
 vendor/github.com/pborman/getopt/v2/set.go         |  16 ++
 vendor/modules.txt                                 |   6 +-
 20 files changed, 922 insertions(+), 153 deletions(-)

diff --git a/go.mod b/go.mod
index df926f2..a9905a9 100644
--- a/go.mod
+++ b/go.mod
@@ -22,7 +22,6 @@ go 1.15
 replace (
 	github.com/fsnotify/fsnotify v1.4.9 => github.com/fsnotify/fsnotify v1.3.0
 	github.com/golang/protobuf v1.4.2 => github.com/golang/protobuf v0.0.0-20171021043952-1643683e1b54
-	github.com/pborman/getopt/v2 v2.1.0 => github.com/pborman/getopt/v2 v2.0.0-20200816005738-fd0d075bf4de
 	gopkg.in/yaml.v2 v2.3.0 => gopkg.in/yaml.v2 v2.2.1
 )
 
@@ -63,8 +62,8 @@ require (
 	github.com/stretchr/testify v1.6.1 // indirect
 	go.etcd.io/bbolt v1.3.5
 	golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83
-	golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
-	golang.org/x/sys v0.0.0-20210227040730-b0d1d43c014d
+	golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4
+	golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005
 	golang.org/x/text v0.3.5 // indirect
 	golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
 	gopkg.in/DATA-DOG/go-sqlmock.v1 v1.3.0
diff --git a/go.sum b/go.sum
index 6227238..80eb155 100644
--- a/go.sum
+++ b/go.sum
@@ -73,8 +73,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
 github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
 github.com/onsi/gomega v1.10.3 h1:gph6h/qe9GSUw1NhH1gp+qb+h8rXD8Cy60Z32Qw3ELA=
 github.com/onsi/gomega v1.10.3/go.mod h1:V9xEwhxec5O8UDM77eCW8vLymOMltsqPVYWrpDsH8xc=
-github.com/pborman/getopt/v2 v2.0.0-20200816005738-fd0d075bf4de h1:i3rei6KlOqwCd9rxoYKfXEzf9LRN9dQMarOfAvH5Fos=
-github.com/pborman/getopt/v2 v2.0.0-20200816005738-fd0d075bf4de/go.mod h1:4NtW75ny4eBw9fO1bhtNdYTlZKYX5/tBLtsOpwKIKd0=
+github.com/pborman/getopt/v2 v2.1.0 h1:eNfR+r+dWLdWmV8g5OlpyrTYHkhVNxHBdN2cCrJmOEA=
+github.com/pborman/getopt/v2 v2.1.0/go.mod h1:4NtW75ny4eBw9fO1bhtNdYTlZKYX5/tBLtsOpwKIKd0=
 github.com/pkg/errors v0.8.2-0.20190227000051-27936f6d90f9 h1:PCj9X21C4pet4sEcElTfAi6LSl5ShkjE8doieLc+cbU=
 github.com/pkg/errors v0.8.2-0.20190227000051-27936f6d90f9/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
 github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -92,8 +92,8 @@ golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73r
 golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A=
 golang.org/x/net v0.0.0-20201006153459-a7d1128ccaa0/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110 h1:qWPm9rbaAMKs8Bq/9LRpbMqxWRVUAQwMI9fVrssnTfw=
-golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
+golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4 h1:b0LrWgu8+q7z4J+0Y3Umo5q1dL7NXBkKBWkaVkAq17E=
+golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4/go.mod h1:RBQZq4jEuRlivfhVLdyRGr576XBO4/greRjx4P4O3yc=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f h1:wMNYb4v58l5UBM7MYRLPG6ZhfOqbKu7X5eyFl8ZhKvA=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
@@ -107,8 +107,8 @@ golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20200519105757-fe76b779f299/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
-golang.org/x/sys v0.0.0-20210227040730-b0d1d43c014d h1:9fH9JvLNoSpsDWcXJ4dSE3lZW99Z3OCUZLr07g60U6o=
-golang.org/x/sys v0.0.0-20210227040730-b0d1d43c014d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005 h1:pDMpM2zh2MT0kHy037cKlSby2nEhD50SYqwQk76Nm40=
+golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
diff --git a/traffic_ops/toclientlib/toclientlib.go b/traffic_ops/toclientlib/toclientlib.go
index 83f0820..042be03 100644
--- a/traffic_ops/toclientlib/toclientlib.go
+++ b/traffic_ops/toclientlib/toclientlib.go
@@ -34,7 +34,6 @@ import (
 
 	"github.com/apache/trafficcontrol/lib/go-log"
 	"github.com/apache/trafficcontrol/lib/go-tc"
-
 	"golang.org/x/net/publicsuffix"
 )
 
diff --git a/traffic_ops_ort/build/build_rpm.sh b/traffic_ops_ort/build/build_rpm.sh
index b493d31..9720f21 100755
--- a/traffic_ops_ort/build/build_rpm.sh
+++ b/traffic_ops_ort/build/build_rpm.sh
@@ -67,6 +67,12 @@ initBuildArea() {
 	(cd t3c;
 	go build -v -gcflags "$gcflags" -ldflags "${ldflags} -X main.GitRevision=$(git rev-parse HEAD) -X main.BuildTimestamp=$(date +'%Y-%M-%dT%H:%M:%s') -X main.Version=${TC_VERSION}" -tags "$tags")
 
+	(cd to_requester;
+	go build -v -gcflags "$gcflags" -ldflags "${ldflags} -X main.GitRevision=$(git rev-parse HEAD) -X main.BuildTimestamp=$(date +'%Y-%M-%dT%H:%M:%s') -X main.Version=${TC_VERSION}" -tags "$tags")
+
+	(cd to_updater;
+	go build -v -gcflags "$gcflags" -ldflags "${ldflags} -X main.GitRevision=$(git rev-parse HEAD) -X main.BuildTimestamp=$(date +'%Y-%M-%dT%H:%M:%s') -X main.Version=${TC_VERSION}" -tags "$tags")
+
 	cp -p traffic_ops_ort.pl "$dest";
 	cp -p supermicro_udev_mapper.pl "$dest";
 	mkdir -p "${dest}/build";
diff --git a/traffic_ops_ort/build/traffic_ops_ort.spec b/traffic_ops_ort/build/traffic_ops_ort.spec
index 55202c3..195313a 100644
--- a/traffic_ops_ort/build/traffic_ops_ort.spec
+++ b/traffic_ops_ort/build/traffic_ops_ort.spec
@@ -57,6 +57,19 @@ got3cdir=src/github.com/apache/trafficcontrol/traffic_ops_ort/t3c
 	cp "$TC_DIR"/traffic_ops_ort/t3c/t3c .
 ) || { echo "Could not copy go program at $(pwd): $!"; exit 1; }
 
+# copy to_requester binary
+go_toreq_dir=src/github.com/apache/trafficcontrol/traffic_ops_ort/to_requester
+( mkdir -p "$go_toreq_dir" && \
+	cd "$go_toreq_dir" && \
+	cp "$TC_DIR"/traffic_ops_ort/to_requester/to_requester .
+) || { echo "Could not copy go program at $(pwd): $!"; exit 1; }
+
+# copy to_updater binary
+go_toupd_dir=src/github.com/apache/trafficcontrol/traffic_ops_ort/to_updater
+( mkdir -p "$go_toupd_dir" && \
+	cd "$go_toupd_dir" && \
+	cp "$TC_DIR"/traffic_ops_ort/to_updater/to_updater .
+) || { echo "Could not copy go program at $(pwd): $!"; exit 1; }
 
 %install
 mkdir -p ${RPM_BUILD_ROOT}/opt/ort
@@ -74,6 +87,12 @@ cp -p "$src"/atstccfg/atstccfg ${RPM_BUILD_ROOT}/opt/ort
 t3csrc=src/github.com/apache/trafficcontrol/traffic_ops_ort/t3c
 cp -p "$t3csrc"/t3c ${RPM_BUILD_ROOT}/opt/ort
 
+to_req_src=src/github.com/apache/trafficcontrol/traffic_ops_ort/to_requester
+cp -p "$to_req_src"/to_requester ${RPM_BUILD_ROOT}/opt/ort
+
+to_upd_src=src/github.com/apache/trafficcontrol/traffic_ops_ort/to_updater
+cp -p "$to_upd_src"/to_updater ${RPM_BUILD_ROOT}/opt/ort
+
 %clean
 rm -rf ${RPM_BUILD_ROOT}
 
@@ -86,6 +105,8 @@ rm -rf ${RPM_BUILD_ROOT}
 /opt/ort/supermicro_udev_mapper.pl
 /opt/ort/atstccfg
 /opt/ort/t3c
+/opt/ort/to_requester
+/opt/ort/to_updater
 
 %config(noreplace) /etc/logrotate.d/atstccfg
 %config(noreplace) /var/log/ort/atstccfg.log
diff --git a/traffic_ops_ort/t3clib/getdata.go b/traffic_ops_ort/t3clib/getdata.go
new file mode 100644
index 0000000..e38542d
--- /dev/null
+++ b/traffic_ops_ort/t3clib/getdata.go
@@ -0,0 +1,243 @@
+/*
+ * 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.
+ */
+
+// package getdata gets and posts non-config data from Traffic Ops which is related to config generation and needed by ORT.
+// For example, the --get-data, --set-queue-status, and --set-reval-status arguments.
+package t3clib
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io"
+	"io/ioutil"
+	"net/http"
+	"net/url"
+	"os"
+	"time"
+
+	"github.com/apache/trafficcontrol/lib/go-atscfg"
+	"github.com/apache/trafficcontrol/lib/go-log"
+	"github.com/apache/trafficcontrol/lib/go-tc"
+	"github.com/apache/trafficcontrol/traffic_ops_ort/atstccfg/toreq"
+	"github.com/apache/trafficcontrol/traffic_ops_ort/atstccfg/torequtil"
+)
+
+type TCCfg struct {
+	CacheHostName string
+	GetData       string
+	TOClient      *toreq.TOClient
+	TOInsecure    bool
+	TOTimeoutMS   time.Duration
+	TOPass        string
+	TOUser        string
+	TOURL         *url.URL
+	UserAgent     string
+}
+
+func GetDataFuncs() map[string]func(TCCfg, io.Writer) error {
+	return map[string]func(TCCfg, io.Writer) error{
+		`update-status`: WriteServerUpdateStatus,
+		`packages`:      WritePackages,
+		`chkconfig`:     WriteChkconfig,
+		`system-info`:   WriteSystemInfo,
+		`statuses`:      WriteStatuses,
+	}
+}
+
+func GetServerUpdateStatus(cfg TCCfg) (*tc.ServerUpdateStatus, error) {
+	status, _, err := cfg.TOClient.GetServerUpdateStatus(tc.CacheName(cfg.CacheHostName))
+	if err != nil {
+		return nil, errors.New("getting server update status: " + err.Error())
+	}
+	return &status, nil
+}
+
+func WriteData(cfg TCCfg) error {
+	log.Infoln("Getting data '" + cfg.GetData + "'")
+	dataF, ok := GetDataFuncs()[cfg.GetData]
+	if !ok {
+		return errors.New("unknown data request '" + cfg.GetData + "'")
+	}
+	return dataF(cfg, os.Stdout)
+}
+
+const SystemInfoParamConfigFile = `global`
+
+// WriteSystemInfo writes the "system info" to output.
+//
+// This is the same info at /api/1.x/system/info, which is actually just all Parameters with the config_file 'global'.
+// Note this is different than the more common "global parameters", which usually refers to all Parameters on the Profile named 'GLOBAL'.
+//
+// This is identical to the /api/1.x/system/info endpoint, except it does not include a '{response: {parameters:' wrapper.
+//
+func WriteSystemInfo(cfg TCCfg, output io.Writer) error {
+	paramArr, _, err := cfg.TOClient.GetConfigFileParameters(SystemInfoParamConfigFile)
+	if err != nil {
+		return errors.New("getting system info parameters: " + err.Error())
+	}
+	params := map[string]string{}
+	for _, param := range paramArr {
+		params[param.Name] = param.Value
+	}
+	if err := json.NewEncoder(output).Encode(params); err != nil {
+		return errors.New("encoding system info parameters: " + err.Error())
+	}
+	return nil
+}
+
+// WriteStatuses writes the Traffic Ops statuses to output.
+// Note this is identical to /api/1.x/statuses except it omits the '{response:' wrapper.
+func WriteStatuses(cfg TCCfg, output io.Writer) error {
+	statuses, _, err := cfg.TOClient.GetStatuses()
+	if err != nil {
+		return errors.New("getting statuses: " + err.Error())
+	}
+	if err := json.NewEncoder(output).Encode(statuses); err != nil {
+		return errors.New("encoding statuses: " + err.Error())
+	}
+	return nil
+}
+
+// WriteUpdateStatus writes the Traffic Ops server update status to output.
+// Note this is identical to /api/1.x/servers/name/update_status except it omits the '[]' wrapper.
+func WriteServerUpdateStatus(cfg TCCfg, output io.Writer) error {
+	status, err := GetServerUpdateStatus(cfg)
+	if err != nil {
+		return err
+	}
+	if err := json.NewEncoder(output).Encode(status); err != nil {
+		return errors.New("encoding server update status: " + err.Error())
+	}
+	return nil
+}
+
+// WriteORTServerPackages writes the packages for serverName to output.
+// Note this is identical to /ort/serverName/packages.
+func WritePackages(cfg TCCfg, output io.Writer) error {
+	packages, err := GetPackages(cfg)
+	if err != nil {
+		return errors.New("getting ORT server packages: " + err.Error())
+	}
+	if err := json.NewEncoder(output).Encode(packages); err != nil {
+		return errors.New("writing packages: " + err.Error())
+	}
+	return nil
+}
+
+func GetPackages(cfg TCCfg) ([]Package, error) {
+	server, _, err := cfg.TOClient.GetServerByHostName(string(cfg.CacheHostName))
+	if err != nil {
+		return nil, errors.New("getting server: " + err.Error())
+	} else if server.Profile == nil {
+		return nil, errors.New("getting server: nil profile")
+	}
+	params, _, err := cfg.TOClient.GetServerProfileParameters(*server.Profile)
+	if err != nil {
+		return nil, errors.New("getting server profile '" + *server.Profile + "' parameters: " + err.Error())
+	}
+	packages := []Package{}
+	for _, param := range params {
+		if param.ConfigFile != atscfg.PackagesParamConfigFile {
+			continue
+		}
+		packages = append(packages, Package{Name: param.Name, Version: param.Value})
+	}
+	return packages, nil
+}
+
+type Package struct {
+	Name    string `json:"name"`
+	Version string `json:"version"`
+}
+
+// WriteChkconfig writes the chkconfig for cfg.CacheHostName to output.
+// Note this is identical to /ort/serverName/chkconfig.
+func WriteChkconfig(cfg TCCfg, output io.Writer) error {
+	chkconfig, err := GetChkconfig(cfg)
+	if err != nil {
+		return errors.New("getting chkconfig: " + err.Error())
+	}
+	if err := json.NewEncoder(output).Encode(chkconfig); err != nil {
+		return errors.New("writing chkconfig: " + err.Error())
+	}
+	return nil
+}
+
+func GetChkconfig(cfg TCCfg) ([]ChkConfigEntry, error) {
+	server, _, err := cfg.TOClient.GetServerByHostName(string(cfg.CacheHostName))
+	if err != nil {
+		return nil, errors.New("getting server: " + err.Error())
+	} else if server.Profile == nil {
+		return nil, errors.New("getting server: nil profile")
+	}
+	params, _, err := cfg.TOClient.GetServerProfileParameters(*server.Profile)
+	if err != nil {
+		return nil, errors.New("getting server profile '" + *server.Profile + "' parameters: " + err.Error())
+	}
+	chkconfig := []ChkConfigEntry{}
+	for _, param := range params {
+		if param.ConfigFile != atscfg.ChkconfigParamConfigFile {
+			continue
+		}
+		chkconfig = append(chkconfig, ChkConfigEntry{Name: param.Name, Val: param.Value})
+	}
+	return chkconfig, nil
+}
+
+type ChkConfigEntry struct {
+	Name string `json:"name"`
+	Val  string `json:"value"`
+}
+
+// SetUpdateStatus sets the queue and reval status of serverName in Traffic Ops.
+func SetUpdateStatus(cfg TCCfg, serverName string, queue bool, revalPending bool) error {
+	reqInf, err := cfg.TOClient.C.SetUpdateServerStatuses(string(serverName), &queue, &revalPending)
+	if err != nil {
+		return errors.New("setting update statuses (Traffic Ops '" + torequtil.MaybeIPStr(reqInf.RemoteAddr) + "'): " + err.Error())
+	}
+	return nil
+}
+
+// setUpdateStatusLegacy sets the queue and reval status of serverName in Traffic Ops,
+// using the legacy pre-2.0 /update endpoint.
+func setUpdateStatusLegacy(cfg TCCfg, serverName tc.CacheName, queue bool, revalPending bool) error {
+	path := `/update/` + string(serverName) + `?updated=` + jsonBoolStr(queue) + `&reval_updated=` + jsonBoolStr(revalPending)
+	// C and RawRequest should generally never be used, but the alternatve here is to manually get the cookie and do an http.Get. We need to hit a non-API endpoint, no API endpoint exists for what we need.
+	resp, _, err := cfg.TOClient.C.RawRequest(http.MethodPost, path, nil)
+	if err != nil {
+		return errors.New("setting update statuses: " + err.Error())
+	}
+	defer resp.Body.Close()
+	if resp.StatusCode != 200 {
+		bodyBts, err := ioutil.ReadAll(resp.Body)
+		if err == nil {
+			return fmt.Errorf("Traffic Ops returned %v %v", resp.StatusCode, string(bodyBts))
+		}
+		return fmt.Errorf("Traffic Ops returned %v (error reading body)", resp.StatusCode)
+	}
+	return nil
+}
+
+func jsonBoolStr(b bool) string {
+	if b {
+		return `true`
+	}
+	return `false`
+}
diff --git a/traffic_ops_ort/t3clib/utils.go b/traffic_ops_ort/t3clib/utils.go
new file mode 100644
index 0000000..33210c2
--- /dev/null
+++ b/traffic_ops_ort/t3clib/utils.go
@@ -0,0 +1,61 @@
+package t3clib
+
+/*
+ * 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.
+ */
+
+// Utility functions.
+
+import (
+	"errors"
+	"net/url"
+	"strings"
+
+	"github.com/apache/trafficcontrol/traffic_ops_ort/atstccfg/toreq"
+)
+
+func TOConnect(tccfg *TCCfg) (*TCCfg, error) {
+	_toClient, err := toreq.New(
+		tccfg.TOURL,
+		tccfg.TOUser,
+		tccfg.TOPass,
+		tccfg.TOInsecure,
+		tccfg.TOTimeoutMS,
+		tccfg.UserAgent)
+
+	if err != nil {
+		return nil, errors.New("failed to connect to traffic ops: " + err.Error())
+	}
+
+	tccfg.TOClient = _toClient
+
+	return tccfg, nil
+}
+
+func ValidateURL(u *url.URL) error {
+	if u == nil {
+		return errors.New("nil url")
+	}
+	if u.Scheme != "http" && u.Scheme != "https" {
+		return errors.New("scheme expected 'http' or 'https', actual '" + u.Scheme + "'")
+	}
+	if strings.TrimSpace(u.Host) == "" {
+		return errors.New("no host")
+	}
+	return nil
+}
diff --git a/traffic_ops_ort/testing/ort-tests/t3c_mode_test.go b/traffic_ops_ort/testing/ort-tests/t3c_mode_test.go
index 3e2050f..f20f9fe 100644
--- a/traffic_ops_ort/testing/ort-tests/t3c_mode_test.go
+++ b/traffic_ops_ort/testing/ort-tests/t3c_mode_test.go
@@ -17,6 +17,7 @@ package orttest
 import (
 	"bytes"
 	"errors"
+	"fmt"
 	"github.com/apache/trafficcontrol/traffic_ops_ort/testing/ort-tests/tcdata"
 	"github.com/apache/trafficcontrol/traffic_ops_ort/testing/ort-tests/util"
 	"os"
@@ -42,7 +43,7 @@ var (
 )
 
 func TestT3cBadassAndSyncDs(t *testing.T) {
-	t.Logf("------------- Starting TestT3cBadassAndSyncDs ---------------")
+	fmt.Println("------------- Starting TestT3cBadassAndSyncDs ---------------")
 	tcd.WithObjs(t, []tcdata.TCObj{
 		tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters,
 		tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses,
@@ -77,7 +78,7 @@ func TestT3cBadassAndSyncDs(t *testing.T) {
 
 		time.Sleep(time.Second * 5)
 
-		t.Logf("------------------------ running SYNCDS Test ------------------")
+		fmt.Println("------------------------ running SYNCDS Test ------------------")
 		// remove the remap.config in preparation for running syncds
 		remap := test_config_dir + "/remap.config"
 		err = os.Remove(remap)
@@ -100,10 +101,10 @@ func TestT3cBadassAndSyncDs(t *testing.T) {
 		if !util.FileExists(remap) {
 			t.Fatalf("ERROR: syncds failed to pull down %s\n", remap)
 		}
-		t.Logf("------------------------ end SYNCDS Test ------------------")
+		fmt.Println("------------------------ end SYNCDS Test ------------------")
 
 	})
-	t.Logf("------------- End of TestT3cBadassAndSyncDs ---------------")
+	fmt.Println("------------- End of TestT3cBadassAndSyncDs ---------------")
 }
 
 func setQueueUpdateStatus(host_name string, update string) error {
diff --git a/traffic_ops_ort/testing/ort-tests/tc-fixtures.json b/traffic_ops_ort/testing/ort-tests/tc-fixtures.json
index b9bc068..2b13899 100644
--- a/traffic_ops_ort/testing/ort-tests/tc-fixtures.json
+++ b/traffic_ops_ort/testing/ort-tests/tc-fixtures.json
@@ -1337,7 +1337,7 @@
             "lastUpdated": "2020-04-21T05:19:43.853831+00:00",
             "name": "tm.instance_name",
             "secure": false,
-            "value": "Traffic Ops API Tests"
+            "value": "Traffic Ops ORT Tests"
         }
     ],
     "physLocations": [
diff --git a/traffic_ops_ort/testing/ort-tests/to_requester_test.go b/traffic_ops_ort/testing/ort-tests/to_requester_test.go
new file mode 100644
index 0000000..0b3e8cf
--- /dev/null
+++ b/traffic_ops_ort/testing/ort-tests/to_requester_test.go
@@ -0,0 +1,145 @@
+package orttest
+
+/*
+   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 (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"github.com/apache/trafficcontrol/lib/go-tc"
+	"github.com/apache/trafficcontrol/traffic_ops_ort/testing/ort-tests/tcdata"
+	"os/exec"
+	"strings"
+	"testing"
+)
+
+type Package struct {
+	Name    *string `json:"name"`
+	Version *string `json:"version"`
+}
+
+func TestTORequester(t *testing.T) {
+	fmt.Println("------------- Starting TestTORequester tests ---------------")
+	tcd.WithObjs(t, []tcdata.TCObj{
+		tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters,
+		tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses,
+		tcdata.Divisions, tcdata.Regions, tcdata.PhysLocations,
+		tcdata.CacheGroups, tcdata.Servers, tcdata.Topologies,
+		tcdata.DeliveryServices}, func() {
+
+		// chkconfig test
+		output, err := ExecTORequester("atlanta-edge-03", "chkconfig")
+		if err != nil {
+			t.Fatalf("ERROR: to_requester exec failed: %v\n", err)
+		}
+		var chkConfig []map[string]interface{}
+		err = json.Unmarshal([]byte(output), &chkConfig)
+		if err != nil {
+			t.Fatalf("ERROR unmarshalling json output: " + err.Error())
+		}
+		if chkConfig[0]["name"] != "trafficserver" {
+			t.Fatal("ERROR unexpected result, expected 'trafficserver'")
+		}
+
+		// get system-info test
+		output, err = ExecTORequester("atlanta-edge-03", "system-info")
+		if err != nil {
+			t.Fatalf("ERROR: to_requester exec failed: %v\n", err)
+		}
+		var sysInfo map[string]interface{}
+		err = json.Unmarshal([]byte(output), &sysInfo)
+		if err != nil {
+			t.Fatalf("ERROR unmarshalling json output: " + err.Error())
+		}
+		if sysInfo["tm.instance_name"] != "Traffic Ops ORT Tests" {
+			t.Fatalf("ERROR: unexpected 'tm.instance_name'")
+		}
+
+		// statuses test
+		output, err = ExecTORequester("atlanta-edge-03", "statuses")
+		if err != nil {
+			t.Fatalf("ERROR: to_requester exec failed: %v\n", err)
+		}
+		// should parse json to an array of 'tc.Status'
+		var statuses []tc.Status
+		err = json.Unmarshal([]byte(output), &statuses)
+		if err != nil {
+			t.Fatalf("ERROR unmarshalling json output: " + err.Error())
+		}
+
+		// packages test
+		output, err = ExecTORequester("atlanta-edge-03", "packages")
+		if err != nil {
+			t.Fatalf("ERROR: to_requester exec failed: %v\n", err)
+		}
+		// should parse to an array of 'Package'
+		var packages []Package
+		err = json.Unmarshal([]byte(output), &packages)
+		if err != nil {
+			t.Fatalf("ERROR unmarshalling json output: " + err.Error())
+		}
+		if *packages[0].Name != "trafficserver" {
+			t.Fatal("ERROR unexpected result, expected 'trafficserver'")
+		}
+
+		// update-status test
+		output, err = ExecTORequester("atlanta-edge-03", "update-status")
+		if err != nil {
+			t.Fatalf("ERROR: to_requester exec failed: %v\n", err)
+		}
+		var serverStatus tc.ServerUpdateStatus
+		err = json.Unmarshal([]byte(output), &serverStatus)
+		if err != nil {
+			t.Fatalf("ERROR unmarshalling json output: " + err.Error())
+		}
+		if serverStatus.HostName != "atlanta-edge-03" {
+			t.Fatal("ERROR unexpected result, expected 'atlanta-edge-03'")
+		}
+
+	})
+	fmt.Println("------------- End of TestTORequester tests ---------------")
+}
+
+func ExecTORequester(host string, data_req string) (string, error) {
+	args := []string{
+		"--traffic-ops-insecure=true",
+		"--login-dispersion=0",
+		"--traffic-ops-timeout-milliseconds=3000",
+		"--traffic-ops-user=" + tcd.Config.TrafficOps.Users.Admin,
+		"--traffic-ops-password=" + tcd.Config.TrafficOps.UserPassword,
+		"--traffic-ops-url=" + tcd.Config.TrafficOps.URL,
+		"--cache-host-name=" + host,
+		"--log-location-error=test.log",
+		"--log-location-info=test.log",
+		"--log-location-debug=test.log",
+		"--get-data=" + data_req,
+	}
+	cmd := exec.Command("/opt/ort/to_requester", args...)
+	var out bytes.Buffer
+	var errOut bytes.Buffer
+	cmd.Stdout = &out
+	cmd.Stderr = &errOut
+	err := cmd.Run()
+	if err != nil {
+		return "", errors.New(err.Error() + ": " + "stdout: " + out.String() + " stderr: " + errOut.String())
+	}
+
+	// capture the last line of JSON in the 'Stdout' buffer 'out'
+	output := strings.Split(strings.TrimSpace(strings.Replace(out.String(), "\r\n", "\n", -1)), "\n")
+	lastLine := output[len(output)-1]
+
+	return lastLine, nil
+}
diff --git a/traffic_ops_ort/testing/ort-tests/to_updater_test.go b/traffic_ops_ort/testing/ort-tests/to_updater_test.go
new file mode 100644
index 0000000..b288fe3
--- /dev/null
+++ b/traffic_ops_ort/testing/ort-tests/to_updater_test.go
@@ -0,0 +1,130 @@
+package orttest
+
+/*
+   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 (
+	"bytes"
+	"encoding/json"
+	"errors"
+	"fmt"
+	"github.com/apache/trafficcontrol/lib/go-tc"
+	"github.com/apache/trafficcontrol/traffic_ops_ort/testing/ort-tests/tcdata"
+	"os/exec"
+	"strconv"
+	"testing"
+)
+
+func TestTOUpdater(t *testing.T) {
+	fmt.Println("------------- Starting TestTOUpdater tests ---------------")
+	tcd.WithObjs(t, []tcdata.TCObj{
+		tcdata.CDNs, tcdata.Types, tcdata.Tenants, tcdata.Parameters,
+		tcdata.Profiles, tcdata.ProfileParameters, tcdata.Statuses,
+		tcdata.Divisions, tcdata.Regions, tcdata.PhysLocations,
+		tcdata.CacheGroups, tcdata.Servers, tcdata.Topologies,
+		tcdata.DeliveryServices}, func() {
+
+		// retrieve the current server status
+		output, err := ExecTORequester("atlanta-edge-03", "update-status")
+		if err != nil {
+			t.Fatalf("ERROR: to_requester Exec failed: %v\n", err)
+		}
+		var serverStatus tc.ServerUpdateStatus
+		err = json.Unmarshal([]byte(output), &serverStatus)
+		if err != nil {
+			t.Fatalf("ERROR unmarshalling json output: " + err.Error())
+		}
+		if serverStatus.HostName != "atlanta-edge-03" {
+			t.Fatal("ERROR unexpected result, expected 'atlanta-edge-03'")
+		}
+		if serverStatus.RevalPending != false {
+			t.Fatal("ERROR unexpected result, expected RevalPending is 'false'")
+		}
+		if serverStatus.UpdatePending != false {
+			t.Fatal("ERROR unexpected result, expected UpdatePending is 'false'")
+		}
+
+		// change the server update status
+		err = ExecTOUpdater("atlanta-edge-03", false, true)
+		if err != nil {
+			t.Fatalf("ERROR: to_updater Exec failed: %v\n", err)
+		}
+		// verify the update status is now 'true'
+		output, err = ExecTORequester("atlanta-edge-03", "update-status")
+		if err != nil {
+			t.Fatalf("ERROR: to_requester Exec failed: %v\n", err)
+		}
+		err = json.Unmarshal([]byte(output), &serverStatus)
+		if err != nil {
+			t.Fatalf("ERROR unmarshalling json output: " + err.Error())
+		}
+		if serverStatus.RevalPending != false {
+			t.Fatal("ERROR unexpected result, expected RevalPending is 'false'")
+		}
+		if serverStatus.UpdatePending != true {
+			t.Fatal("ERROR unexpected result, expected UpdatePending is 'true'")
+		}
+
+		// now change the reval stat and put server update status back
+		err = ExecTOUpdater("atlanta-edge-03", true, false)
+		if err != nil {
+			t.Fatalf("ERROR: to_updater Exec failed: %v\n", err)
+		}
+		// verify the change
+		output, err = ExecTORequester("atlanta-edge-03", "update-status")
+		if err != nil {
+			t.Fatalf("ERROR: to_requester Exec failed: %v\n", err)
+		}
+		err = json.Unmarshal([]byte(output), &serverStatus)
+		if err != nil {
+			t.Fatalf("ERROR unmarshalling json output: " + err.Error())
+		}
+		if serverStatus.RevalPending != true {
+			t.Fatal("ERROR unexpected result, expected RevalPending is 'false'")
+		}
+		if serverStatus.UpdatePending != false {
+			t.Fatal("ERROR unexpected result, expected UpdatePending is 'true'")
+		}
+
+	})
+	fmt.Println("------------- End of TestTOUpdater tests ---------------")
+}
+
+func ExecTOUpdater(host string, reval_status bool, update_status bool) error {
+	args := []string{
+		"--traffic-ops-insecure=true",
+		"--login-dispersion=0",
+		"--traffic-ops-timeout-milliseconds=3000",
+		"--traffic-ops-user=" + tcd.Config.TrafficOps.Users.Admin,
+		"--traffic-ops-password=" + tcd.Config.TrafficOps.UserPassword,
+		"--traffic-ops-url=" + tcd.Config.TrafficOps.URL,
+		"--cache-host-name=" + host,
+		"--log-location-error=test.log",
+		"--log-location-info=test.log",
+		"--log-location-debug=test.log",
+		"--set-reval-status=" + strconv.FormatBool(reval_status),
+		"--set-update-status=" + strconv.FormatBool(update_status),
+	}
+	cmd := exec.Command("/opt/ort/to_updater", args...)
+	var out bytes.Buffer
+	var errOut bytes.Buffer
+	cmd.Stdout = &out
+	cmd.Stderr = &errOut
+	err := cmd.Run()
+	if err != nil {
+		return errors.New(err.Error() + ": " + "stdout: " + out.String() + " stderr: " + errOut.String())
+	}
+
+	return nil
+}
diff --git a/traffic_ops_ort/to_requester/config/config.go b/traffic_ops_ort/to_requester/config/config.go
index 72a4cee..ac22ee9 100644
--- a/traffic_ops_ort/to_requester/config/config.go
+++ b/traffic_ops_ort/to_requester/config/config.go
@@ -24,11 +24,10 @@ import (
 	"fmt"
 	"net/url"
 	"os"
-	"strings"
 	"time"
 
 	"github.com/apache/trafficcontrol/lib/go-log"
-	"github.com/apache/trafficcontrol/traffic_ops_ort/atstccfg/toreq"
+	"github.com/apache/trafficcontrol/traffic_ops_ort/t3clib"
 	"github.com/pborman/getopt/v2"
 )
 
@@ -42,18 +41,7 @@ type Cfg struct {
 	LogLocationError string
 	LogLocationInfo  string
 	LoginDispersion  time.Duration
-	CacheHostName    string
-	GetData          string
-	TOInsecure       bool
-	TOTimeoutMS      time.Duration
-	TOUser           string
-	TOPass           string
-	TOURL            *url.URL
-}
-
-type TCCfg struct {
-	Cfg
-	TOClient *toreq.TOClient
+	t3clib.TCCfg
 }
 
 func (cfg Cfg) DebugLog() log.LogLocation   { return log.LogLocation(cfg.LogLocationDebug) }
@@ -76,7 +64,7 @@ func InitConfig() (Cfg, error) {
 	logLocationInfoPtr := getopt.StringLong("log-location-info", 'i', "stderr", "Where to log infos. May be a file path, stdout, stderr")
 	dispersionPtr := getopt.IntLong("login-dispersion", 'l', 0, "[seconds] wait a random number of seconds between 0 and [seconds] before login to traffic ops, default 0")
 	cacheHostNamePtr := getopt.StringLong("cache-host-name", 'H', "", "Host name of the cache to generate config for. Must be the server host name in Traffic Ops, not a URL, and not the FQDN")
-	getDataPtr := getopt.StringLong("get-data", 'D', "system-info", "non-config-file Traffic Ops Data to get. Valid values are all, update-status, packages, chkconfig, system-info, and statuses")
+	getDataPtr := getopt.StringLong("get-data", 'D', "system-info", "non-config-file Traffic Ops Data to get. Valid values are update-status, packages, chkconfig, system-info, and statuses")
 	toInsecurePtr := getopt.BoolLong("traffic-ops-insecure", 'I', "[true | false] ignore certificate errors from Traffic Ops")
 	toTimeoutMSPtr := getopt.IntLong("traffic-ops-timeout-milliseconds", 't', 30000, "Timeout in milli-seconds for Traffic Ops requests, default is 30000")
 	toURLPtr := getopt.StringLong("traffic-ops-url", 'u', "", "Traffic Ops URL. Must be the full URL, including the scheme. Required. May also be set with     the environment variable TO_URL")
@@ -111,7 +99,7 @@ func InitConfig() (Cfg, error) {
 	toURLParsed, err := url.Parse(toURL)
 	if err != nil {
 		return Cfg{}, errors.New("parsing Traffic Ops URL from " + urlSourceStr + " '" + toURL + "': " + err.Error())
-	} else if err := ValidateURL(toURLParsed); err != nil {
+	} else if err := t3clib.ValidateURL(toURLParsed); err != nil {
 		return Cfg{}, errors.New("invalid Traffic Ops URL from " + urlSourceStr + " '" + toURL + "': " + err.Error())
 	}
 
@@ -131,13 +119,15 @@ func InitConfig() (Cfg, error) {
 		LogLocationError: *logLocationErrorPtr,
 		LogLocationInfo:  *logLocationInfoPtr,
 		LoginDispersion:  dispersion,
-		CacheHostName:    cacheHostName,
-		GetData:          *getDataPtr,
-		TOInsecure:       *toInsecurePtr,
-		TOTimeoutMS:      toTimeoutMS,
-		TOUser:           toUser,
-		TOPass:           toPass,
-		TOURL:            toURLParsed,
+		TCCfg: t3clib.TCCfg{
+			CacheHostName: cacheHostName,
+			GetData:       *getDataPtr,
+			TOInsecure:    *toInsecurePtr,
+			TOTimeoutMS:   toTimeoutMS,
+			TOUser:        toUser,
+			TOPass:        toPass,
+			TOURL:         toURLParsed,
+		},
 	}
 
 	if err := log.InitCfg(cfg); err != nil {
@@ -147,19 +137,6 @@ func InitConfig() (Cfg, error) {
 	return cfg, nil
 }
 
-func ValidateURL(u *url.URL) error {
-	if u == nil {
-		return errors.New("nil url")
-	}
-	if u.Scheme != "http" && u.Scheme != "https" {
-		return errors.New("scheme expected 'http' or 'https', actual '" + u.Scheme + "'")
-	}
-	if strings.TrimSpace(u.Host) == "" {
-		return errors.New("no host")
-	}
-	return nil
-}
-
 func (cfg Cfg) PrintConfig() {
 	fmt.Printf("CommandArgs: %s\n", cfg.CommandArgs)
 	fmt.Printf("LogLocationDebug: %s\n", cfg.LogLocationDebug)
diff --git a/traffic_ops_ort/to_requester/to_requester.go b/traffic_ops_ort/to_requester/to_requester.go
index 7be297b..f4a76f7 100644
--- a/traffic_ops_ort/to_requester/to_requester.go
+++ b/traffic_ops_ort/to_requester/to_requester.go
@@ -15,8 +15,9 @@ Description
 Options
 	-D, --get-data=value
       	non-config-file Traffic Ops Data to get. Valid values are
-        all, update-status, packages, chkconfig, system-info, and
-        statuses [all]
+        update-status, packages, chkconfig, system-info, and
+        statuses
+				Default is system-info
 	-d, --log-location-debug=value
         Where to log debugs. May be a file path, stdout or stderr.
         Default is no debug logging.
@@ -73,14 +74,12 @@ package main
  */
 
 import (
-	"errors"
 	"fmt"
 	"os"
 
 	"github.com/apache/trafficcontrol/lib/go-log"
-	"github.com/apache/trafficcontrol/traffic_ops_ort/atstccfg/toreq"
+	"github.com/apache/trafficcontrol/traffic_ops_ort/t3clib"
 	"github.com/apache/trafficcontrol/traffic_ops_ort/to_requester/config"
-	"github.com/apache/trafficcontrol/traffic_ops_ort/to_requester/getdata"
 )
 
 var (
@@ -100,36 +99,16 @@ func main() {
 	}
 
 	// login to traffic ops.
-	tccfg, err := toConnect()
+	tccfg, err := t3clib.TOConnect(&cfg.TCCfg)
 	if err != nil {
 		log.Errorf("%s\n", err)
 		os.Exit(2)
 	}
-	if tccfg.Cfg.GetData != "" {
-		if err := getdata.WriteData(*tccfg); err != nil {
+
+	if cfg.GetData != "" {
+		if err := t3clib.WriteData(*tccfg); err != nil {
 			log.Errorf("writing data: %s\n", err.Error())
 			os.Exit(3)
 		}
 	}
 }
-
-/*
- * connect and login to traffic ops
- */
-func toConnect() (*config.TCCfg, error) {
-	toClient, err := toreq.New(cfg.TOURL, cfg.TOUser, cfg.TOPass, cfg.TOInsecure, cfg.TOTimeoutMS, config.UserAgent)
-	if err != nil {
-		return nil, errors.New("failed to connect to traffic ops: " + err.Error())
-	}
-
-	if toClient.FellBack() {
-		log.Warnln("Traffic Ops does not support the latest version supported by this app! Falling back to previous major Traffic Ops API version!")
-	}
-
-	tccfg := config.TCCfg{
-		Cfg:      cfg,
-		TOClient: toClient,
-	}
-
-	return &tccfg, nil
-}
diff --git a/traffic_ops_ort/to_updater/README.md b/traffic_ops_ort/to_updater/README.md
new file mode 100644
index 0000000..9c7361e
--- /dev/null
+++ b/traffic_ops_ort/to_updater/README.md
@@ -0,0 +1,66 @@
+<!--
+    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.
+-->
+
+# Traffic Ops Updater
+
+## Synopsis
+	to_updater [-h] [-a value] [-d value] [-e value] [-H value] [-i value] \
+		[-l value] [-P value] [-q value] [-t value] [-u value] [-U value]
+
+## Description
+  The to_requester is used to set the update and reval status, 
+  on Traffic Ops.
+
+## Options
+  -a  --set-reval-status [true | false] set the servers revalidate status (required)
+  -q  --set-update-status [true | false] set the servers update queue status (required)
+
+	-d, --log-location-debug=value
+        Where to log debugs. May be a file path, stdout or stderr.
+        Default is no debug logging.
+	-e, --log-location-error=value
+        Where to log errors. May be a file path, stdout, or stderr.
+        Default is stderr.
+	-i, --log-location-info=value
+        Where to log infos. May be a file path, stdout or stderr.
+        Default is stderr.
+	-H, --cache-host-name=value
+     		Host name of the cache to update the statuses of on TrafficOps.
+        Server host name in Traffic Ops, not a URL, and not the FQDN.
+        Defaults to the OS configured hostname.
+	-h, --help  Print usage information and exit
+ 	-I, --traffic-ops-insecure
+				[true | false] ignore certificate errors from Traffic Ops
+	-l, --login-dispersion=value
+        [seconds] wait a random number of seconds between 0 and
+        [seconds] before login to traffic ops, default 0
+	-P, --traffic-ops-password=value
+        Traffic Ops password. Required. May also be set with the
+        environment variable TO_PASS
+	-t, --traffic-ops-timeout-milliseconds=value
+        Timeout in milli-seconds for Traffic Ops requests, default
+        is 30000 [30000]
+	-u, --traffic-ops-url=value
+        Traffic Ops URL. Must be the full URL, including the scheme.
+        Required. May also be set with     the environment variable
+        TO_URL
+	-U, --traffic-ops-user=value
+        Traffic Ops username. Required. May also be set with the
+        environment variable TO_USER
+
diff --git a/traffic_ops_ort/to_requester/config/config.go b/traffic_ops_ort/to_updater/config/config.go
similarity index 74%
copy from traffic_ops_ort/to_requester/config/config.go
copy to traffic_ops_ort/to_updater/config/config.go
index 72a4cee..152cf97 100644
--- a/traffic_ops_ort/to_requester/config/config.go
+++ b/traffic_ops_ort/to_updater/config/config.go
@@ -21,22 +21,21 @@ package config
 
 import (
 	"errors"
-	"fmt"
 	"net/url"
 	"os"
-	"strings"
 	"time"
 
 	"github.com/apache/trafficcontrol/lib/go-log"
-	"github.com/apache/trafficcontrol/traffic_ops_ort/atstccfg/toreq"
+	"github.com/apache/trafficcontrol/traffic_ops_ort/t3clib"
 	"github.com/pborman/getopt/v2"
 )
 
-const AppName = "to_requester"
+const AppName = "to_updater"
 const Version = "0.1"
 const UserAgent = AppName + "/" + Version
 
 type Cfg struct {
+	Debug            bool
 	CommandArgs      []string
 	LogLocationDebug string
 	LogLocationError string
@@ -44,16 +43,9 @@ type Cfg struct {
 	LoginDispersion  time.Duration
 	CacheHostName    string
 	GetData          string
-	TOInsecure       bool
-	TOTimeoutMS      time.Duration
-	TOUser           string
-	TOPass           string
-	TOURL            *url.URL
-}
-
-type TCCfg struct {
-	Cfg
-	TOClient *toreq.TOClient
+	UpdatePending    bool
+	RevalPending     bool
+	t3clib.TCCfg
 }
 
 func (cfg Cfg) DebugLog() log.LogLocation   { return log.LogLocation(cfg.LogLocationDebug) }
@@ -70,13 +62,15 @@ func Usage() {
 
 // InitConfig() intializes the configuration variables and loggers.
 func InitConfig() (Cfg, error) {
-
 	logLocationDebugPtr := getopt.StringLong("log-location-debug", 'd', "", "Where to log debugs. May be a file path, stdout, stderr")
 	logLocationErrorPtr := getopt.StringLong("log-location-error", 'e', "stderr", "Where to log errors. May be a file path, stdout, stderr")
 	logLocationInfoPtr := getopt.StringLong("log-location-info", 'i', "stderr", "Where to log infos. May be a file path, stdout, stderr")
 	dispersionPtr := getopt.IntLong("login-dispersion", 'l', 0, "[seconds] wait a random number of seconds between 0 and [seconds] before login to traffic ops, default 0")
 	cacheHostNamePtr := getopt.StringLong("cache-host-name", 'H', "", "Host name of the cache to generate config for. Must be the server host name in Traffic Ops, not a URL, and not the FQDN")
-	getDataPtr := getopt.StringLong("get-data", 'D', "system-info", "non-config-file Traffic Ops Data to get. Valid values are all, update-status, packages, chkconfig, system-info, and statuses")
+	var updatePendingPtr bool
+	var revalPendingPtr bool
+	getopt.FlagLong(&updatePendingPtr, "set-update-status", 'q', "[true | false] sets the servers update status").Mandatory()
+	getopt.FlagLong(&revalPendingPtr, "set-reval-status", 'a', "[true | false] sets the servers revalidate status").Mandatory()
 	toInsecurePtr := getopt.BoolLong("traffic-ops-insecure", 'I', "[true | false] ignore certificate errors from Traffic Ops")
 	toTimeoutMSPtr := getopt.IntLong("traffic-ops-timeout-milliseconds", 't', 30000, "Timeout in milli-seconds for Traffic Ops requests, default is 30000")
 	toURLPtr := getopt.StringLong("traffic-ops-url", 'u', "", "Traffic Ops URL. Must be the full URL, including the scheme. Required. May also be set with     the environment variable TO_URL")
@@ -111,7 +105,7 @@ func InitConfig() (Cfg, error) {
 	toURLParsed, err := url.Parse(toURL)
 	if err != nil {
 		return Cfg{}, errors.New("parsing Traffic Ops URL from " + urlSourceStr + " '" + toURL + "': " + err.Error())
-	} else if err := ValidateURL(toURLParsed); err != nil {
+	} else if err := t3clib.ValidateURL(toURLParsed); err != nil {
 		return Cfg{}, errors.New("invalid Traffic Ops URL from " + urlSourceStr + " '" + toURL + "': " + err.Error())
 	}
 
@@ -131,13 +125,17 @@ func InitConfig() (Cfg, error) {
 		LogLocationError: *logLocationErrorPtr,
 		LogLocationInfo:  *logLocationInfoPtr,
 		LoginDispersion:  dispersion,
-		CacheHostName:    cacheHostName,
-		GetData:          *getDataPtr,
-		TOInsecure:       *toInsecurePtr,
-		TOTimeoutMS:      toTimeoutMS,
-		TOUser:           toUser,
-		TOPass:           toPass,
-		TOURL:            toURLParsed,
+		UpdatePending:    updatePendingPtr,
+		RevalPending:     revalPendingPtr,
+		TCCfg: t3clib.TCCfg{
+			CacheHostName: cacheHostName,
+			GetData:       "update-status",
+			TOInsecure:    *toInsecurePtr,
+			TOTimeoutMS:   toTimeoutMS,
+			TOUser:        toUser,
+			TOPass:        toPass,
+			TOURL:         toURLParsed,
+		},
 	}
 
 	if err := log.InitCfg(cfg); err != nil {
@@ -146,30 +144,3 @@ func InitConfig() (Cfg, error) {
 
 	return cfg, nil
 }
-
-func ValidateURL(u *url.URL) error {
-	if u == nil {
-		return errors.New("nil url")
-	}
-	if u.Scheme != "http" && u.Scheme != "https" {
-		return errors.New("scheme expected 'http' or 'https', actual '" + u.Scheme + "'")
-	}
-	if strings.TrimSpace(u.Host) == "" {
-		return errors.New("no host")
-	}
-	return nil
-}
-
-func (cfg Cfg) PrintConfig() {
-	fmt.Printf("CommandArgs: %s\n", cfg.CommandArgs)
-	fmt.Printf("LogLocationDebug: %s\n", cfg.LogLocationDebug)
-	fmt.Printf("LogLocationError: %s\n", cfg.LogLocationError)
-	fmt.Printf("LogLocationInfo: %s\n", cfg.LogLocationInfo)
-	fmt.Printf("LoginDispersion : %s\n", cfg.LoginDispersion)
-	fmt.Printf("CacheHostName: %s\n", cfg.CacheHostName)
-	fmt.Printf("TOInsecure: %s\n", cfg.TOInsecure)
-	fmt.Printf("TOTimeoutMS: %s\n", cfg.TOTimeoutMS)
-	fmt.Printf("TOUser: %s\n", cfg.TOUser)
-	fmt.Printf("TOPass: xxxxxx\n")
-	fmt.Printf("TOURL: %s\n", cfg.TOURL)
-}
diff --git a/traffic_ops_ort/to_updater/to_updater.go b/traffic_ops_ort/to_updater/to_updater.go
new file mode 100644
index 0000000..33106e5
--- /dev/null
+++ b/traffic_ops_ort/to_updater/to_updater.go
@@ -0,0 +1,71 @@
+package main
+
+/*
+ * 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 (
+	"fmt"
+	"os"
+
+	"github.com/apache/trafficcontrol/lib/go-log"
+	"github.com/apache/trafficcontrol/traffic_ops_ort/t3clib"
+	"github.com/apache/trafficcontrol/traffic_ops_ort/to_updater/config"
+)
+
+var (
+	cfg config.Cfg
+)
+
+func main() {
+	var err error
+
+	cfg, err = config.InitConfig()
+	if err != nil {
+		fmt.Fprintf(os.Stderr, "ERROR: %s\n", err.Error())
+		os.Exit(1)
+	} else {
+		log.Infoln("configuration initialized")
+	}
+
+	// login to TrafficOps
+	tccfg, err := t3clib.TOConnect(&cfg.TCCfg)
+	if err != nil {
+		log.Errorf("%s\n", err)
+		os.Exit(2)
+	}
+
+	err = t3clib.SetUpdateStatus(*tccfg, cfg.TCCfg.CacheHostName, cfg.UpdatePending, cfg.RevalPending)
+	if err != nil {
+		log.Errorf("%s, %s\n", err, cfg.TCCfg.CacheHostName)
+		os.Exit(3)
+	}
+
+	cur_status, err := t3clib.GetServerUpdateStatus(*tccfg)
+	if err != nil {
+		log.Errorf("%s, %s\n", err, cfg.TCCfg.CacheHostName)
+		os.Exit(4)
+	}
+
+	if cur_status.UpdatePending != cfg.UpdatePending && cfg.RevalPending != cfg.RevalPending {
+		log.Errorf("ERROR: update failed, update status and/or reval status was not set.\n")
+	} else {
+		log.Infoln("Update successfully completed")
+	}
+
+}
diff --git a/vendor/github.com/pborman/getopt/v2/getopt.go b/vendor/github.com/pborman/getopt/v2/getopt.go
index 1ffbfaa..e5c52bf 100644
--- a/vendor/github.com/pborman/getopt/v2/getopt.go
+++ b/vendor/github.com/pborman/getopt/v2/getopt.go
@@ -152,6 +152,34 @@
 // Unless an option type explicitly prohibits it, an option may appear more than
 // once in the arguments.  The last value provided to the option is the value.
 //
+// MANDATORY OPTIONS
+//
+// An option marked as mandatory and not seen when parsing will cause an error
+// to be reported such as: "program: --name is a mandatory option".  An option
+// is marked mandatory by using the Mandatory method:
+//
+//	getopt.FlagLong(&fileName, "path", 0, "the path").Mandatory()
+//
+// Mandatory options have (required) appended to their help message:
+//
+//	--path=value    the path (required)
+//
+// MUTUALLY EXCLUSIVE OPTIONS
+//
+// Options can be marked as part of a mutually exclusive group.  When two or
+// more options in a mutually exclusive group are both seen while parsing then
+// an error such as "program: options -a and -b are mutually exclusive" will be
+// reported.  Mutually exclusive groups are declared using the SetGroup method:
+//
+//	getopt.Flag(&a, 'a', "use method A").SetGroup("method")
+//	getopt.Flag(&a, 'b', "use method B").SetGroup("method")
+//
+// A set can have multiple mutually exclusive groups.  Mutually exclusive groups
+// are identified with their group name in {}'s appeneded to their help message:
+//
+//	 -a    use method A {method}
+//	 -b    use method B {method}
+//
 // BUILTIN TYPES
 //
 // The Flag and FlagLong functions support most standard Go types.  For the
@@ -318,7 +346,7 @@ func (s *Set) PrintOptions(w io.Writer) {
 	for _, opt := range s.options {
 		if opt.uname != "" {
 			opt.help = strings.TrimSpace(opt.help)
-			if len(opt.help) == 0 {
+			if len(opt.help) == 0 && !opt.mandatory && opt.group == "" {
 				fmt.Fprintf(w, " %s\n", opt.uname)
 				continue
 			}
@@ -350,6 +378,12 @@ func (s *Set) PrintOptions(w io.Writer) {
 			if def != "" {
 				helpMsg += " [" + def + "]"
 			}
+			if opt.group != "" {
+				helpMsg += " {" + opt.group + "}"
+			}
+			if opt.mandatory {
+				helpMsg += " (required)"
+			}
 
 			help := strings.Split(helpMsg, "\n")
 			// If they did not put in newlines then we will insert
@@ -444,6 +478,12 @@ func (s *Set) Getopt(args []string, fn func(Option) bool) (err error) {
 			}
 		}
 	}()
+
+	defer func() {
+		if err == nil {
+			err = s.checkOptions()
+		}
+	}()
 	if fn == nil {
 		fn = func(Option) bool { return true }
 	}
@@ -562,3 +602,35 @@ Parsing:
 	s.args = []string{}
 	return nil
 }
+
+func (s *Set) checkOptions() error {
+	groups := map[string]Option{}
+	for _, opt := range s.options {
+		if !opt.Seen() {
+			if opt.mandatory {
+				return fmt.Errorf("option %s is mandatory", opt.Name())
+			}
+			continue
+		}
+		if opt.group == "" {
+			continue
+		}
+		if opt2 := groups[opt.group]; opt2 != nil {
+			return fmt.Errorf("options %s and %s are mutually exclusive", opt2.Name(), opt.Name())
+		}
+		groups[opt.group] = opt
+	}
+	for _, group := range s.requiredGroups {
+		if groups[group] != nil {
+			continue
+		}
+		var flags []string
+		for _, opt := range s.options {
+			if opt.group == group {
+				flags = append(flags, opt.Name())
+			}
+		}
+		return fmt.Errorf("exactly one of the following options must be specified: %s", strings.Join(flags, ", "))
+	}
+	return nil
+}
diff --git a/vendor/github.com/pborman/getopt/v2/option.go b/vendor/github.com/pborman/getopt/v2/option.go
index 67822bc..c97bb29 100644
--- a/vendor/github.com/pborman/getopt/v2/option.go
+++ b/vendor/github.com/pborman/getopt/v2/option.go
@@ -59,21 +59,31 @@ type Option interface {
 	// yet been seen, including resetting the value of the option
 	// to its original default state.
 	Reset()
+
+	// Mandataory sets the mandatory flag of the option.  Parse will
+	// fail if a mandatory option is missing.
+	Mandatory() Option
+
+	// SetGroup sets the option as part of a radio group.  Parse will
+	// fail if two options in the same group are seen.
+	SetGroup(string) Option
 }
 
 type option struct {
-	short    rune   // 0 means no short name
-	long     string // "" means no long name
-	isLong   bool   // True if they used the long name
-	flag     bool   // true if a boolean flag
-	defval   string // default value
-	optional bool   // true if we take an optional value
-	help     string // help message
-	where    string // file where the option was defined
-	value    Value  // current value of option
-	count    int    // number of times we have seen this option
-	name     string // name of the value (for usage)
-	uname    string // name of the option (for usage)
+	short     rune   // 0 means no short name
+	long      string // "" means no long name
+	isLong    bool   // True if they used the long name
+	flag      bool   // true if a boolean flag
+	defval    string // default value
+	optional  bool   // true if we take an optional value
+	help      string // help message
+	where     string // file where the option was defined
+	value     Value  // current value of option
+	count     int    // number of times we have seen this option
+	name      string // name of the value (for usage)
+	uname     string // name of the option (for usage)
+	mandatory bool   // this option must be specified
+	group     string // mutual exclusion group
 }
 
 // usageName returns the name of the option for printing usage lines in one
@@ -121,12 +131,14 @@ func (o *option) sortName() string {
 	return o.long[:1] + o.long
 }
 
-func (o *option) Seen() bool          { return o.count > 0 }
-func (o *option) Count() int          { return o.count }
-func (o *option) IsFlag() bool        { return o.flag }
-func (o *option) String() string      { return o.value.String() }
-func (o *option) SetOptional() Option { o.optional = true; return o }
-func (o *option) SetFlag() Option     { o.flag = true; return o }
+func (o *option) Seen() bool               { return o.count > 0 }
+func (o *option) Count() int               { return o.count }
+func (o *option) IsFlag() bool             { return o.flag }
+func (o *option) String() string           { return o.value.String() }
+func (o *option) SetOptional() Option      { o.optional = true; return o }
+func (o *option) SetFlag() Option          { o.flag = true; return o }
+func (o *option) Mandatory() Option        { o.mandatory = true; return o }
+func (o *option) SetGroup(g string) Option { o.group = g; return o }
 
 func (o *option) Value() Value {
 	if o == nil {
diff --git a/vendor/github.com/pborman/getopt/v2/set.go b/vendor/github.com/pborman/getopt/v2/set.go
index a7895c8..9f73ede 100644
--- a/vendor/github.com/pborman/getopt/v2/set.go
+++ b/vendor/github.com/pborman/getopt/v2/set.go
@@ -46,6 +46,7 @@ type Set struct {
 	shortOptions map[rune]*option
 	longOptions  map[string]*option
 	options      optionList
+	requiredGroups []string
 }
 
 // New returns a newly created option set.
@@ -291,3 +292,18 @@ func (s *Set) Reset() {
 		opt.Reset()
 	}
 }
+
+// RequiredGroup marks the group set with Option.SetGroup as required.  At least
+// one option in the group must be seen by parse.  Calling RequiredGroup with a
+// group name that has no options will cause parsing to always fail.
+func (s *Set) RequiredGroup(group string) {
+	s.requiredGroups = append(s.requiredGroups, group)
+}
+
+// RequiredGroup marks the group set with Option.SetGroup as required on the
+// command line.  At least one option in the group must be seen by parse.
+// Calling RequiredGroup with a group name that has no options will cause
+// parsing to always fail.
+func RequiredGroup(group string) {
+	CommandLine.requiredGroups = append(CommandLine.requiredGroups, group)
+}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 431ea0f..583cecf 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -165,7 +165,7 @@ github.com/onsi/gomega/matchers/support/goraph/edge
 github.com/onsi/gomega/matchers/support/goraph/node
 github.com/onsi/gomega/matchers/support/goraph/util
 github.com/onsi/gomega/types
-# github.com/pborman/getopt/v2 v2.1.0 => github.com/pborman/getopt/v2 v2.0.0-20200816005738-fd0d075bf4de
+# github.com/pborman/getopt/v2 v2.1.0
 ## explicit
 github.com/pborman/getopt/v2
 # github.com/pkg/errors v0.8.2-0.20190227000051-27936f6d90f9
@@ -183,7 +183,7 @@ golang.org/x/crypto/ed25519/internal/edwards25519
 golang.org/x/crypto/ocsp
 golang.org/x/crypto/pbkdf2
 golang.org/x/crypto/scrypt
-# golang.org/x/net v0.0.0-20210226172049-e18ecbb05110
+# golang.org/x/net v0.0.0-20210316092652-d523dce5a7f4
 ## explicit
 golang.org/x/net/bpf
 golang.org/x/net/html
@@ -198,7 +198,7 @@ golang.org/x/net/internal/socket
 golang.org/x/net/ipv4
 golang.org/x/net/ipv6
 golang.org/x/net/publicsuffix
-# golang.org/x/sys v0.0.0-20210227040730-b0d1d43c014d
+# golang.org/x/sys v0.0.0-20210315160823-c6e025ad8005
 ## explicit
 golang.org/x/sys/internal/unsafeheader
 golang.org/x/sys/unix