| // Package towrap wraps two versions of Traffic Ops clients to give up-to-date |
| // information, possibly using legacy API versions. |
| package towrap |
| |
| /* |
| * 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 ( |
| "crypto/tls" |
| "errors" |
| "fmt" |
| "io/ioutil" |
| "net/http" |
| "net/http/cookiejar" |
| "net/url" |
| "os" |
| "strconv" |
| "sync" |
| "time" |
| |
| "github.com/apache/trafficcontrol/lib/go-log" |
| "github.com/apache/trafficcontrol/lib/go-tc" |
| "github.com/apache/trafficcontrol/traffic_monitor/config" |
| legacyClient "github.com/apache/trafficcontrol/traffic_ops/v3-client" |
| client "github.com/apache/trafficcontrol/traffic_ops/v4-client" |
| |
| jsoniter "github.com/json-iterator/go" |
| "golang.org/x/net/publicsuffix" |
| ) |
| |
| const localHostIP = "127.0.0.1" |
| |
| // ErrNilSession is the error returned by operations performed on a nil session. |
| var ErrNilSession = errors.New("nil session") |
| |
| // ByteTime is a structure for associating a set of raw data with some CDN |
| // Snapshot statistics, and a certain time. |
| type ByteTime struct { |
| bytes []byte |
| time time.Time |
| stats *tc.CRConfigStats |
| } |
| |
| // ByteMapCache is a thread-access-safe map of cache server hostnames to |
| // ByteTime structures. |
| type ByteMapCache struct { |
| cache *map[string]ByteTime |
| m *sync.RWMutex |
| } |
| |
| // NewByteMapCache constructs a new, empty ByteMapCache. |
| func NewByteMapCache() ByteMapCache { |
| return ByteMapCache{m: &sync.RWMutex{}, cache: &map[string]ByteTime{}} |
| } |
| |
| // Set sets the entry given by 'key' to a new ByteTime structure with the given |
| // raw data ('newBytes') and the given statistics ('stats') at the current time. |
| func (c ByteMapCache) Set(key string, newBytes []byte, stats *tc.CRConfigStats) { |
| c.m.Lock() |
| defer c.m.Unlock() |
| (*c.cache)[key] = ByteTime{bytes: newBytes, stats: stats, time: time.Now()} |
| } |
| |
| // Get retrieves the raw data, associated time, and statistics of the entry |
| // given by 'key'. |
| func (c ByteMapCache) Get(key string) ([]byte, time.Time, *tc.CRConfigStats) { |
| c.m.RLock() |
| defer c.m.RUnlock() |
| if byteTime, ok := (*c.cache)[key]; !ok { |
| return nil, time.Time{}, nil |
| } else { |
| return byteTime.bytes, byteTime.time, byteTime.stats |
| } |
| } |
| |
| func (s TrafficOpsSessionThreadsafe) BackupFileExists() bool { |
| if _, err := os.Stat(s.CRConfigBackupFile); !os.IsNotExist(err) { |
| if _, err = os.Stat(s.TMConfigBackupFile); !os.IsNotExist(err) { |
| return true |
| } |
| } |
| return false |
| } |
| |
| // CRConfigStat represents a set of statistics from a CDN Snapshot requested at |
| // a particular time. |
| type CRConfigStat struct { |
| // Err contains any error that may have occurred when obtaining the |
| // statistics. |
| Err error `json:"error"` |
| // ReqAddr is the network address from which the statistics were requested. |
| ReqAddr string `json:"request_address"` |
| // ReqTime is the time at which the request for statistics was made. |
| ReqTime time.Time `json:"request_time"` |
| // Stats contains the actual statistics. |
| Stats tc.CRConfigStats `json:"stats"` |
| } |
| |
| // CopyCRConfigStat makes a deep copy of a slice of CRConfigStats. |
| func CopyCRConfigStat(old []CRConfigStat) []CRConfigStat { |
| newStats := make([]CRConfigStat, len(old)) |
| copy(newStats, old) |
| return newStats |
| } |
| |
| // CRConfigHistoryThreadsafe stores history in a circular buffer. |
| type CRConfigHistoryThreadsafe struct { |
| hist *[]CRConfigStat |
| m *sync.RWMutex |
| limit *uint64 |
| length *uint64 |
| pos *uint64 |
| } |
| |
| // NewCRConfigHistoryThreadsafe constructs a new, empty |
| // CRConfigHistoryThreadsafe - this is the ONLY way to safely create a |
| // CRConfigHistoryThreadsafe, using the zero value of the structure will cause |
| // all operations to encounter segmentation faults, and there is no way to |
| // preempt this. |
| // |
| // 'limit' indicates the size of the circular buffer - effectively the number of |
| // entries it will be capable of storing. |
| func NewCRConfigHistoryThreadsafe(limit uint64) CRConfigHistoryThreadsafe { |
| hist := make([]CRConfigStat, limit, limit) |
| length := uint64(0) |
| pos := uint64(0) |
| return CRConfigHistoryThreadsafe{hist: &hist, m: &sync.RWMutex{}, limit: &limit, length: &length, pos: &pos} |
| } |
| |
| // Add adds the given stat to the history. Does not add new additions with the |
| // same remote address and CRConfig Date as the previous. |
| func (h CRConfigHistoryThreadsafe) Add(i *CRConfigStat) { |
| h.m.Lock() |
| defer h.m.Unlock() |
| |
| if *h.length != 0 { |
| last := (*h.hist)[(*h.pos-1)%*h.limit] |
| datesEqual := (i.Stats.DateUnixSeconds == nil && last.Stats.DateUnixSeconds == nil) || (i.Stats.DateUnixSeconds != nil && last.Stats.DateUnixSeconds != nil && *i.Stats.DateUnixSeconds == *last.Stats.DateUnixSeconds) |
| cdnsEqual := (i.Stats.CDNName == nil && last.Stats.CDNName == nil) || (i.Stats.CDNName != nil && last.Stats.CDNName != nil && *i.Stats.CDNName == *last.Stats.CDNName) |
| reqAddrsEqual := i.ReqAddr == last.ReqAddr |
| if reqAddrsEqual && datesEqual && cdnsEqual { |
| return |
| } |
| } |
| |
| (*h.hist)[*h.pos] = *i |
| *h.pos = (*h.pos + 1) % *h.limit |
| if *h.length < *h.limit { |
| *h.length++ |
| } |
| } |
| |
| // Get retrieves the stored history of CRConfigStat entries. |
| func (h CRConfigHistoryThreadsafe) Get() []CRConfigStat { |
| h.m.RLock() |
| defer h.m.RUnlock() |
| if *h.length < *h.limit { |
| return CopyCRConfigStat((*h.hist)[:*h.length]) |
| } |
| newStats := make([]CRConfigStat, *h.limit) |
| copy(newStats, (*h.hist)[*h.pos:]) |
| copy(newStats[*h.length-*h.pos:], (*h.hist)[:*h.pos]) |
| return newStats |
| } |
| |
| // Len gives the number of currently stored items in the buffer. |
| // |
| // An uninitialized buffer has zero length. |
| func (h CRConfigHistoryThreadsafe) Len() uint64 { |
| if h.length == nil { |
| return 0 |
| } |
| return *h.length |
| } |
| |
| // TrafficOpsSessionThreadsafe provides access to the Traffic Ops client safe |
| // for multiple goroutines. This fulfills the ITrafficOpsSession interface. |
| type TrafficOpsSessionThreadsafe struct { |
| session **client.Session // pointer-to-pointer, because we're given a pointer from the Traffic Ops package, and we don't want to copy it. |
| legacySession **legacyClient.Session |
| m *sync.Mutex |
| lastCRConfig ByteMapCache |
| crConfigHist CRConfigHistoryThreadsafe |
| CRConfigBackupFile string |
| TMConfigBackupFile string |
| } |
| |
| // NewTrafficOpsSessionThreadsafe returns a new threadsafe |
| // TrafficOpsSessionThreadsafe wrapping the given `Session`. |
| func NewTrafficOpsSessionThreadsafe(s *client.Session, ls *legacyClient.Session, histLimit uint64, cfg config.Config) TrafficOpsSessionThreadsafe { |
| return TrafficOpsSessionThreadsafe{ |
| CRConfigBackupFile: cfg.CRConfigBackupFile, |
| crConfigHist: NewCRConfigHistoryThreadsafe(histLimit), |
| lastCRConfig: NewByteMapCache(), |
| m: &sync.Mutex{}, |
| session: &s, |
| legacySession: &ls, |
| TMConfigBackupFile: cfg.TMConfigBackupFile, |
| } |
| } |
| |
| // Initialized tells whether or not the TrafficOpsSessionThreadsafe has been |
| // properly initialized with non-nil sessions. |
| func (s TrafficOpsSessionThreadsafe) Initialized() bool { |
| return s.session != nil && *s.session != nil && s.legacySession != nil && *s.legacySession != nil |
| } |
| |
| // Update updates the TrafficOpsSessionThreadsafe's connection information with |
| // the provided information. It's safe for calling by multiple goroutines, being |
| // aware that they will race. |
| func (s *TrafficOpsSessionThreadsafe) Update( |
| url string, |
| username string, |
| password string, |
| insecure bool, |
| userAgent string, |
| useCache bool, |
| timeout time.Duration, |
| ) error { |
| if s == nil { |
| return errors.New("cannot update nil session") |
| } |
| s.m.Lock() |
| defer s.m.Unlock() |
| |
| // always set unauthenticated sessions first which can eventually authenticate themselves when attempting requests |
| if err := s.setSession(url, username, password, insecure, userAgent, useCache, timeout); err != nil { |
| return err |
| } |
| if err := s.setLegacySession(url, username, password, insecure, userAgent, useCache, timeout); err != nil { |
| return err |
| } |
| |
| session, _, err := client.LoginWithAgent(url, username, password, insecure, userAgent, useCache, timeout) |
| if err != nil { |
| log.Errorf("logging in using up-to-date client: %v", err) |
| legacySession, _, err := legacyClient.LoginWithAgent(url, username, password, insecure, userAgent, useCache, timeout) |
| if err != nil || legacySession == nil { |
| err = fmt.Errorf("logging in using legacy client: %v", err) |
| return err |
| } |
| *s.legacySession = legacySession |
| } else { |
| *s.session = session |
| } |
| |
| return nil |
| } |
| |
| // setSession sets the session for the up-to-date client without logging in. |
| func (s *TrafficOpsSessionThreadsafe) setSession(url, username, password string, insecure bool, userAgent string, useCache bool, timeout time.Duration) error { |
| options := cookiejar.Options{ |
| PublicSuffixList: publicsuffix.List, |
| } |
| jar, err := cookiejar.New(&options) |
| if err != nil { |
| return err |
| } |
| to := client.NewSession(username, password, url, userAgent, &http.Client{ |
| Timeout: timeout, |
| Transport: &http.Transport{ |
| TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure}, |
| }, |
| Jar: jar, |
| }, useCache) |
| *s.session = to |
| return nil |
| } |
| |
| // setSession sets the session for the legacy client without logging in. |
| func (s *TrafficOpsSessionThreadsafe) setLegacySession(url, username, password string, insecure bool, userAgent string, useCache bool, timeout time.Duration) error { |
| options := cookiejar.Options{ |
| PublicSuffixList: publicsuffix.List, |
| } |
| jar, err := cookiejar.New(&options) |
| if err != nil { |
| return err |
| } |
| to := legacyClient.NewSession(username, password, url, userAgent, &http.Client{ |
| Timeout: timeout, |
| Transport: &http.Transport{ |
| TLSClientConfig: &tls.Config{InsecureSkipVerify: insecure}, |
| }, |
| Jar: jar, |
| }, useCache) |
| *s.legacySession = to |
| return nil |
| } |
| |
| // getThreadsafeSession is used internally to get a copy of the session pointer, |
| // or nil if it doesn't exist. This should not be used outside |
| // TrafficOpsSessionThreadsafe, and never stored, because part of the purpose of |
| // rafficOpsSessionThreadsafe is to store a pointer to the Session pointer, so |
| // it can be updated by one goroutine and immediately used by another. This |
| // should only be called immediately before using the session, since someone |
| // else may update it concurrently. |
| func (s TrafficOpsSessionThreadsafe) get() *client.Session { |
| s.m.Lock() |
| defer s.m.Unlock() |
| if s.session == nil || *s.session == nil { |
| return nil |
| } |
| return *s.session |
| } |
| |
| func (s TrafficOpsSessionThreadsafe) getLegacy() *legacyClient.Session { |
| s.m.Lock() |
| defer s.m.Unlock() |
| if s.legacySession == nil || *s.legacySession == nil { |
| return nil |
| } |
| return *s.legacySession |
| } |
| |
| // CRConfigHistory gets all of the stored, historical data about CRConfig |
| // Snapshots' Stats sections. |
| func (s TrafficOpsSessionThreadsafe) CRConfigHistory() []CRConfigStat { |
| return s.crConfigHist.Get() |
| } |
| |
| // CRConfigValid checks if the passed tc.CRConfig structure is valid, and |
| // ensures that it is from the same CDN as the last CRConfig Snapshot, as well |
| // as that it is newer than the last CRConfig Snapshot. |
| func (s *TrafficOpsSessionThreadsafe) CRConfigValid(crc *tc.CRConfig, cdn string) error { |
| if crc == nil { |
| return errors.New("CRConfig is nil") |
| } |
| if crc.Stats.CDNName == nil { |
| return errors.New("CRConfig.Stats.CDN missing") |
| } |
| if crc.Stats.DateUnixSeconds == nil { |
| return errors.New("CRConfig.Stats.Date missing") |
| } |
| |
| // Note this intentionally takes intended CDN, rather than trusting |
| // crc.Stats |
| lastCrc, lastCrcTime, lastCrcStats := s.lastCRConfig.Get(cdn) |
| |
| if lastCrc == nil { |
| return nil |
| } |
| if lastCrcStats.DateUnixSeconds == nil { |
| log.Warnln("TrafficOpsSessionThreadsafe.CRConfigValid returning no error, but last CRConfig Date was missing!") |
| return nil |
| } |
| if lastCrcStats.CDNName == nil { |
| log.Warnln("TrafficOpsSessionThreadsafe.CRConfigValid returning no error, but last CRConfig CDN was missing!") |
| return nil |
| } |
| if *lastCrcStats.CDNName != *crc.Stats.CDNName { |
| return errors.New("CRConfig.Stats.CDN " + *crc.Stats.CDNName + " different than last received CRConfig.Stats.CDNName " + *lastCrcStats.CDNName + " received at " + lastCrcTime.Format(time.RFC3339Nano)) |
| } |
| if *lastCrcStats.DateUnixSeconds > *crc.Stats.DateUnixSeconds { |
| return errors.New("CRConfig.Stats.Date " + strconv.FormatInt(*crc.Stats.DateUnixSeconds, 10) + " older than last received CRConfig.Stats.Date " + strconv.FormatInt(*lastCrcStats.DateUnixSeconds, 10) + " received at " + lastCrcTime.Format(time.RFC3339Nano)) |
| } |
| return nil |
| } |
| |
| // CRConfigRaw returns the CRConfig from the Traffic Ops. This is safe for |
| // multiple goroutines. |
| func (s TrafficOpsSessionThreadsafe) CRConfigRaw(cdn string) ([]byte, error) { |
| |
| var remoteAddr string |
| var err error |
| var crConfig *tc.CRConfig |
| var configBytes []byte |
| json := jsoniter.ConfigFastest |
| |
| ss := s.get() |
| if ss == nil { |
| return nil, ErrNilSession |
| } |
| response, reqInf, err := ss.GetCRConfig(cdn, client.RequestOptions{}) |
| if reqInf.RemoteAddr != nil { |
| remoteAddr = reqInf.RemoteAddr.String() |
| } |
| if err != nil { |
| log.Warnln("getting CRConfig from Traffic Ops using up-to-date client: " + err.Error() + ". Retrying with legacy client") |
| ls := s.getLegacy() |
| if ls == nil { |
| return nil, ErrNilSession |
| } |
| configBytes, reqInf, err = ls.GetCRConfig(cdn) |
| if reqInf.RemoteAddr != nil { |
| remoteAddr = reqInf.RemoteAddr.String() |
| } |
| if err != nil { |
| log.Errorln("getting CRConfig from Traffic Ops using legacy client: " + err.Error() + ". Checking for backup") |
| } |
| } else { |
| crConfig = &response.Response |
| configBytes, err = json.Marshal(crConfig) |
| if err != nil { |
| crConfig = nil |
| log.Warnln("failed to marshal CRConfig using up-to-date client: " + err.Error()) |
| } |
| } |
| |
| if err == nil { |
| log.Infoln("successfully got CRConfig from Traffic Ops. Writing to backup file") |
| if wErr := ioutil.WriteFile(s.CRConfigBackupFile, configBytes, 0644); wErr != nil { |
| log.Errorf("failed to write CRConfig backup file: %v", wErr) |
| } |
| } else { |
| if s.BackupFileExists() { |
| log.Errorln("using backup file for CRConfig snapshot due to error fetching CRConfig snapshot from Traffic Ops: " + err.Error()) |
| configBytes, err = ioutil.ReadFile(s.CRConfigBackupFile) |
| if err != nil { |
| return nil, fmt.Errorf("reading CRConfig backup file: %v", err) |
| } |
| remoteAddr = localHostIP |
| err = nil |
| } else { |
| return nil, fmt.Errorf("failed to get CRConfig from Traffic Ops (%v), and there is no backup file", err) |
| } |
| } |
| |
| hist := &CRConfigStat{ |
| Err: err, |
| ReqAddr: remoteAddr, |
| ReqTime: time.Now(), |
| Stats: tc.CRConfigStats{}, |
| } |
| defer s.crConfigHist.Add(hist) |
| |
| if crConfig == nil { |
| if err = json.Unmarshal(configBytes, crConfig); err != nil { |
| err = errors.New("invalid JSON: " + err.Error()) |
| hist.Err = err |
| return configBytes, err |
| } |
| } |
| hist.Stats = crConfig.Stats |
| |
| if err = s.CRConfigValid(crConfig, cdn); err != nil { |
| err = errors.New("invalid CRConfig: " + err.Error()) |
| hist.Err = err |
| return configBytes, err |
| } |
| |
| s.lastCRConfig.Set(cdn, configBytes, &crConfig.Stats) |
| return configBytes, nil |
| } |
| |
| // LastCRConfig returns the last CRConfig requested from CRConfigRaw, and the |
| // time it was returned. This is designed to be used in conjunction with a |
| // poller which regularly calls CRConfigRaw. If no last CRConfig exists, because |
| // CRConfigRaw has never been called successfully, this calls CRConfigRaw once |
| // to try to get the CRConfig from Traffic Ops. |
| func (s TrafficOpsSessionThreadsafe) LastCRConfig(cdn string) ([]byte, time.Time, error) { |
| crConfig, crConfigTime, _ := s.lastCRConfig.Get(cdn) |
| if len(crConfig) == 0 { |
| b, err := s.CRConfigRaw(cdn) |
| return b, time.Now(), err |
| } |
| return crConfig, crConfigTime, nil |
| } |
| |
| func (s TrafficOpsSessionThreadsafe) fetchTMConfig(cdn string) (*tc.TrafficMonitorConfig, error) { |
| ss := s.get() |
| if ss == nil { |
| return nil, ErrNilSession |
| } |
| |
| m, _, e := ss.GetTrafficMonitorConfig(cdn, client.NewRequestOptions()) |
| return &m.Response, e |
| } |
| |
| func (s TrafficOpsSessionThreadsafe) fetchLegacyTMConfig(cdn string) (*tc.TrafficMonitorConfig, error) { |
| ss := s.getLegacy() |
| if ss == nil { |
| return nil, ErrNilSession |
| } |
| |
| m, _, e := ss.GetTrafficMonitorConfig(cdn) |
| if m == nil { |
| return nil, e |
| } |
| return m, e |
| } |
| |
| // trafficMonitorConfigMapRaw returns the Traffic Monitor config map from the |
| // Traffic Ops, directly from the monitoring endpoint. This is not usually |
| // what is needed, rather monitoring needs the snapshotted CRConfig data, which |
| // is filled in by `LegacyTrafficMonitorConfigMap`. This is safe for multiple |
| // goroutines. |
| func (s TrafficOpsSessionThreadsafe) trafficMonitorConfigMapRaw(cdn string) (*tc.TrafficMonitorConfigMap, error) { |
| var config *tc.TrafficMonitorConfig |
| var configMap *tc.TrafficMonitorConfigMap |
| var err error |
| |
| config, err = s.fetchTMConfig(cdn) |
| if err != nil { |
| log.Warnln("getting Traffic Monitor config from Traffic Ops using up-to-date client: " + err.Error() + ". Retrying with legacy client") |
| config, err = s.fetchLegacyTMConfig(cdn) |
| if err != nil { |
| log.Errorln("getting Traffic Monitor config from Traffic Ops using legacy client: " + err.Error()) |
| } |
| } |
| |
| if err == nil { |
| log.Infoln("successfully got Traffic Monitor config from Traffic Ops") |
| if config == nil { |
| return nil, fmt.Errorf("nil Traffic Monitor config after successful fetch") |
| } |
| configMap, err = tc.TrafficMonitorTransformToMap(config) |
| } |
| |
| if err != nil { |
| // Default error case, no backup file exists |
| if !s.BackupFileExists() { |
| return nil, err |
| } |
| log.Errorln("using backup file for monitoring config snapshot due to invalid monitoring config snapshot from Traffic Ops: " + err.Error()) |
| |
| b, err := ioutil.ReadFile(s.TMConfigBackupFile) |
| if err != nil { |
| return nil, errors.New("reading TMConfigBackupFile: " + err.Error()) |
| } |
| |
| json := jsoniter.ConfigFastest |
| var tmConfig tc.TrafficMonitorConfig |
| if err := json.Unmarshal(b, &tmConfig); err != nil { |
| return nil, errors.New("unmarshalling backup file monitoring.json: " + err.Error()) |
| } |
| return tc.TrafficMonitorTransformToMap(&tmConfig) |
| } |
| |
| json := jsoniter.ConfigFastest |
| data, err := json.Marshal(*config) |
| if err == nil { |
| if wErr := ioutil.WriteFile(s.TMConfigBackupFile, data, 0644); wErr != nil { |
| log.Errorf("failed to write TM config backup file: %v", wErr) |
| } |
| } |
| |
| return configMap, err |
| } |
| |
| // TrafficMonitorConfigMap returns the Traffic Monitor config map from the |
| // Traffic Ops. This is safe for multiple goroutines. |
| func (s TrafficOpsSessionThreadsafe) TrafficMonitorConfigMap(cdn string) (*tc.TrafficMonitorConfigMap, error) { |
| mc, err := s.trafficMonitorConfigMapRaw(cdn) |
| if err != nil { |
| return nil, fmt.Errorf("getting monitor config map: %v", err) |
| } |
| return mc, nil |
| } |
| |
| func (s TrafficOpsSessionThreadsafe) fetchServerByHostname(hostName string) (tc.ServerV40, error) { |
| ss := s.get() |
| if ss == nil { |
| return tc.ServerV40{}, ErrNilSession |
| } |
| |
| params := url.Values{} |
| params.Set("hostName", hostName) |
| resp, _, err := ss.GetServers(client.RequestOptions{QueryParameters: params}) |
| if err != nil { |
| return tc.ServerV40{}, fmt.Errorf("fetching server by hostname '%s': %v", hostName, err) |
| } |
| |
| respLen := len(resp.Response) |
| if respLen < 1 { |
| return tc.ServerV40{}, fmt.Errorf("no server '%s' found in Traffic Ops", hostName) |
| } |
| |
| var server tc.ServerV40 |
| var num int |
| found := false |
| for i, srv := range resp.Response { |
| num = i |
| if srv.CDNName != nil && srv.HostName != nil && *srv.HostName == hostName { |
| server = srv |
| found = true |
| break |
| } |
| } |
| if !found { |
| return tc.ServerV40{}, fmt.Errorf("either no server '%s' found in Traffic Ops, or none by that hostName had non-nil CDN", hostName) |
| } |
| |
| if respLen > 1 { |
| log.Warnf("Getting monitor server by hostname '%s' returned %d servers - selecting #%d", hostName, respLen, num) |
| } |
| |
| return server, nil |
| } |
| |
| func (s TrafficOpsSessionThreadsafe) fetchLegacyServerByHostname(hostName string) (tc.ServerV40, error) { |
| ss := s.getLegacy() |
| if ss == nil { |
| return tc.ServerV40{}, ErrNilSession |
| } |
| |
| params := url.Values{} |
| params.Set("hostName", hostName) |
| resp, _, err := ss.GetServersWithHdr(¶ms, nil) |
| if err != nil { |
| return tc.ServerV40{}, fmt.Errorf("fetching server by hostname '%s': %v", hostName, err) |
| } |
| |
| respLen := len(resp.Response) |
| if respLen < 1 { |
| return tc.ServerV40{}, fmt.Errorf("no server '%s' found in Traffic Ops", hostName) |
| } |
| |
| var server tc.ServerV30 |
| var num int |
| found := false |
| for i, srv := range resp.Response { |
| num = i |
| if srv.CDNName != nil && srv.HostName != nil && *srv.HostName == hostName { |
| server = srv |
| found = true |
| break |
| } |
| } |
| if !found { |
| return tc.ServerV40{}, fmt.Errorf("either no server '%s' found in Traffic Ops, or none by that hostName had non-nil CDN", hostName) |
| } |
| |
| if respLen > 1 { |
| log.Warnf("Getting monitor server by hostname '%s' returned %d servers - selecting #%d", hostName, respLen, num) |
| } |
| |
| if server.Profile == nil { |
| return tc.ServerV40{}, fmt.Errorf("server with hostname '%s' has no profile", hostName) |
| } |
| newServer, err := server.UpgradeToV40([]string{*server.Profile}) |
| if err != nil { |
| return newServer, fmt.Errorf("coercing legacy server to new format: %v", err) |
| } |
| return newServer, nil |
| } |
| |
| // MonitorCDN returns the name of the CDN of a Traffic Monitor with the given |
| // hostName. |
| func (s TrafficOpsSessionThreadsafe) MonitorCDN(hostName string) (string, error) { |
| var server tc.ServerV40 |
| var err error |
| |
| server, err = s.fetchServerByHostname(hostName) |
| if err != nil { |
| log.Warnln("getting server by hostname '" + hostName + "' using up-to-date client: " + err.Error() + ". Retrying with legacy client") |
| server, err = s.fetchLegacyServerByHostname(hostName) |
| } |
| |
| if err != nil { |
| return "", fmt.Errorf("getting monitor CDN: %v", err) |
| } |
| |
| // nil-dereference checks done already in each 'fetch' method; they'll just |
| // return an error in that case |
| return *server.CDNName, nil |
| } |