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