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 <[email protected]>
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

Reply via email to