You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@trafficcontrol.apache.org by ne...@apache.org on 2017/03/02 04:09:36 UTC
[2/4] incubator-trafficcontrol git commit: Add TM2 offline validator
utility pkg and service
Add TM2 offline validator utility pkg and service
Project: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/commit/7a329504
Tree: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/tree/7a329504
Diff: http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/diff/7a329504
Branch: refs/heads/master
Commit: 7a32950441b0fa05c26d9fc8617b7b7ebe2a7c29
Parents: d5692bb
Author: Robert Butts <ro...@gmail.com>
Authored: Wed Mar 1 11:22:07 2017 -0700
Committer: David Neuman <da...@gmail.com>
Committed: Wed Mar 1 21:09:12 2017 -0700
----------------------------------------------------------------------
.../traffic_monitor/tmcheck/tmcheck.go | 172 +++++++++++++++++++
.../traffic_monitor/tools/validate-offline.go | 116 +++++++++++++
2 files changed, 288 insertions(+)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/7a329504/traffic_monitor_golang/traffic_monitor/tmcheck/tmcheck.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/tmcheck/tmcheck.go b/traffic_monitor_golang/traffic_monitor/tmcheck/tmcheck.go
new file mode 100644
index 0000000..d6a964e
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/tmcheck/tmcheck.go
@@ -0,0 +1,172 @@
+/*
+ * 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 tmcheck contains utility functions for validating a Traffic Monitor is acting correctly.
+package tmcheck
+
+import (
+ "crypto/tls"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net/http"
+ "time"
+
+ "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/crconfig"
+ "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/enum"
+ "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/peer"
+ to "github.com/apache/incubator-trafficcontrol/traffic_ops/client"
+)
+
+const RequestTimeout = time.Second * time.Duration(30)
+
+const TrafficMonitorCRStatesPath = "/publish/CrStates"
+const TrafficMonitorConfigDocPath = "/publish/ConfigDoc"
+
+func getClient() *http.Client {
+ return &http.Client{
+ Transport: &http.Transport{TLSClientConfig: &tls.Config{InsecureSkipVerify: true}},
+ Timeout: RequestTimeout,
+ }
+}
+
+// TrafficMonitorConfigDoc represents the JSON returned by Traffic Monitor's ConfigDoc endpoint. This currently only contains the CDN member, as needed by this library.
+type TrafficMonitorConfigDoc struct {
+ CDN string `json:"cdnName"`
+}
+
+// GetCDN gets the CDN of the given Traffic Monitor.
+func GetCDN(uri string) (string, error) {
+ resp, err := getClient().Get(uri + TrafficMonitorConfigDocPath)
+ if err != nil {
+ return "", fmt.Errorf("reading reply from %v: %v\n", uri, err)
+ }
+ respBytes, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return "", fmt.Errorf("reading reply from %v: %v\n", uri, err)
+ }
+
+ configDoc := TrafficMonitorConfigDoc{}
+ if err := json.Unmarshal(respBytes, &configDoc); err != nil {
+ return "", fmt.Errorf("unmarshalling: %v", err)
+ }
+ return configDoc.CDN, nil
+}
+
+// GetCRStates gets the CRStates from the given Traffic Monitor.
+func GetCRStates(uri string) (*peer.Crstates, error) {
+ resp, err := getClient().Get(uri)
+ if err != nil {
+ return nil, fmt.Errorf("reading reply from %v: %v\n", uri, err)
+ }
+ respBytes, err := ioutil.ReadAll(resp.Body)
+ if err != nil {
+ return nil, fmt.Errorf("reading reply from %v: %v\n", uri, err)
+ }
+
+ states := peer.Crstates{}
+ if err := json.Unmarshal(respBytes, &states); err != nil {
+ return nil, fmt.Errorf("unmarshalling: %v", err)
+ }
+ return &states, nil
+}
+
+// ValidateOfflineStates validates that no OFFLINE or ADMIN_DOWN caches in the given Traffic Ops' CRConfig are marked Available in the given Traffic Monitor's CRStates.
+func ValidateOfflineStates(tmURI string, toClient *to.Session) error {
+ cdn, err := GetCDN(tmURI)
+ if err != nil {
+ return fmt.Errorf("getting CDN from Traffic Monitor: %v", err)
+ }
+
+ crConfigBytes, err := toClient.CRConfigRaw(cdn)
+ if err != nil {
+ return fmt.Errorf("getting CRConfig: %v", err)
+ }
+
+ crConfig := crconfig.CRConfig{}
+ if err := json.Unmarshal(crConfigBytes, &crConfig); err != nil {
+ return fmt.Errorf("unmarshalling CRConfig JSON: %v", err)
+ }
+
+ crStates, err := GetCRStates(tmURI + TrafficMonitorCRStatesPath)
+ if err != nil {
+ return fmt.Errorf("getting CRStates: %v", err)
+ }
+
+ return ValidateCRStates(crStates, &crConfig)
+}
+
+// ValidateCRStates validates that no OFFLINE or ADMIN_DOWN caches in the given CRConfig are marked Available in the given CRStates.
+func ValidateCRStates(crstates *peer.Crstates, crconfig *crconfig.CRConfig) error {
+ for cacheName, cacheInfo := range crconfig.ContentServers {
+ status := enum.CacheStatusFromString(string(*cacheInfo.Status))
+ if status != enum.CacheStatusOffline || status != enum.CacheStatusOffline {
+ continue
+ }
+
+ available, ok := crstates.Caches[enum.CacheName(cacheName)]
+ if !ok {
+ return fmt.Errorf("Cache %v in CRConfig but not CRStates", cacheName)
+ }
+
+ if available.IsAvailable {
+ return fmt.Errorf("Cache %v is %v in CRConfig, but available in CRStates", cacheName, status)
+ }
+
+ }
+ return nil
+}
+
+// Validator is designed to be run as a goroutine, and does not return. It continously validates every `interval`, and calls `onErr` on failure, `onResumeSuccess` when a failure ceases, and `onCheck` on every poll.
+func Validator(
+ tmURI string,
+ toClient *to.Session,
+ interval time.Duration,
+ grace time.Duration,
+ onErr func(error),
+ onResumeSuccess func(),
+ onCheck func(error),
+) {
+ invalid := false
+ invalidStart := time.Time{}
+ for {
+ err := ValidateOfflineStates(tmURI, toClient)
+
+ if err != nil && !invalid {
+ invalid = true
+ invalidStart = time.Now()
+ }
+
+ if err != nil {
+ invalidSpan := time.Now().Sub(invalidStart)
+ if invalidSpan > grace {
+ onErr(fmt.Errorf("invalid state for %v: %v\n", invalidSpan, err))
+ }
+ }
+
+ if err == nil && invalid {
+ onResumeSuccess()
+ invalid = false
+ }
+
+ onCheck(err)
+
+ time.Sleep(interval)
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-trafficcontrol/blob/7a329504/traffic_monitor_golang/traffic_monitor/tools/validate-offline.go
----------------------------------------------------------------------
diff --git a/traffic_monitor_golang/traffic_monitor/tools/validate-offline.go b/traffic_monitor_golang/traffic_monitor/tools/validate-offline.go
new file mode 100644
index 0000000..30574a0
--- /dev/null
+++ b/traffic_monitor_golang/traffic_monitor/tools/validate-offline.go
@@ -0,0 +1,116 @@
+/*
+ * 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.
+ */
+
+// validate-offline is a utility HTTP service which polls the given Traffic Monitor and validates that no OFFLINE or ADMIN_DOWN caches in the Traffic Ops CRConfig are marked Available in Traffic Monitor's CRstates endpoint.
+
+package main
+
+import (
+ "flag"
+ "fmt"
+ "github.com/apache/incubator-trafficcontrol/traffic_monitor_golang/traffic_monitor/tmcheck"
+ to "github.com/apache/incubator-trafficcontrol/traffic_ops/client"
+ "net/http"
+ "sync"
+ "time"
+)
+
+const UserAgent = "tm-offline-validator/0.1"
+
+type Log struct {
+ l *[]string
+ m *sync.RWMutex
+}
+
+func (l *Log) Add(msg string) {
+ l.m.Lock()
+ defer l.m.Unlock()
+ *l.l = append(*l.l, msg)
+}
+
+func (l *Log) Get() []string {
+ l.m.RLock()
+ defer l.m.RUnlock()
+ return *l.l
+}
+
+func NewLog() Log {
+ s := make([]string, 0)
+ return Log{l: &s, m: &sync.RWMutex{}}
+}
+
+func main() {
+ toURI := flag.String("to", "", "The Traffic Ops URI, whose CRConfig to validate")
+ toUser := flag.String("touser", "", "The Traffic Ops user")
+ toPass := flag.String("topass", "", "The Traffic Ops password")
+ tmURI := flag.String("tm", "", "The Traffic Monitor URI whose CRStates to validate")
+ interval := flag.Duration("interval", time.Second*time.Duration(5), "The interval to validate")
+ grace := flag.Duration("grace", time.Second*time.Duration(30), "The grace period before invalid states are reported")
+ help := flag.Bool("help", false, "Usage info")
+ helpBrief := flag.Bool("h", false, "Usage info")
+ flag.Parse()
+ if *help || *helpBrief {
+ fmt.Printf("Usage: go run validate-offline -to https://traffic-ops.example.net -touser bill -topass thelizard -tm http://traffic-monitor.example.net -interval 5s -grace 30s\n")
+ return
+ }
+
+ toClient, err := to.LoginWithAgent(*toURI, *toUser, *toPass, true, UserAgent, false, tmcheck.RequestTimeout)
+ if err != nil {
+ fmt.Printf("Error logging in to Traffic Ops: %v\n", err)
+ return
+ }
+
+ log := NewLog()
+
+ onErr := func(err error) {
+ log.Add(fmt.Sprintf("%v ERROR %v\n", time.Now(), err))
+ }
+
+ onResumeSuccess := func() {
+ log.Add(fmt.Sprintf("%v INFO State Valid\n", time.Now()))
+ }
+
+ onCheck := func(err error) {
+ if err != nil {
+ log.Add(fmt.Sprintf("%v DEBUG invalid: %v\n", time.Now(), err))
+ } else {
+ log.Add(fmt.Sprintf("%v DEBUG valid\n", time.Now()))
+ }
+ }
+
+ go tmcheck.Validator(*tmURI, toClient, *interval, *grace, onErr, onResumeSuccess, onCheck)
+
+ if err := serve(log); err != nil {
+ fmt.Printf("Serve error: %v\n", err)
+ }
+}
+
+func serve(log Log) error {
+ http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
+ w.Header().Set("Access-Control-Allow-Origin", "*")
+ w.Header().Set("Content-Type", "text/html")
+ fmt.Fprintf(w, `<html><head><meta http-equiv="refresh" content="5"></head><body><pre>`)
+ logCopy := log.Get()
+ for i := len(logCopy) - 1; i >= 0; i-- {
+ fmt.Fprintf(w, "%s\n", logCopy[i])
+ }
+ fmt.Fprintf(w, `</pre></body></html>`)
+ })
+ return http.ListenAndServe(":80", nil)
+}