Skip to content
This repository was archived by the owner on Nov 24, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions cache-config/t3c-apply/torequest/torequest.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ type RestartData struct {
TrafficServerRestart bool // a trafficserver restart is required
RemapConfigReload bool // remap.config should be reloaded
HitchReload bool // hitch should be reloaded
VarnishReload bool // varnish should be reloaded
}

type ConfigFile struct {
Expand Down Expand Up @@ -522,6 +523,7 @@ func (r *TrafficOpsReq) replaceCfgFile(cfg *ConfigFile) (*FileRestartData, error
ntpdRestart := cfg.Name == "ntpd.conf"
sysCtlReload := cfg.Name == "sysctl.conf"
hitchReload := cfg.Name == "hitch.conf"
varnishReload := cfg.Name == "default.vcl"

log.Debugf("Reload state after %s: remap.config: %t reload: %t restart: %t ntpd: %t sysctl: %t", cfg.Name, remapConfigReload, trafficCtlReload, trafficServerRestart, ntpdRestart, sysCtlReload)

Expand All @@ -535,6 +537,7 @@ func (r *TrafficOpsReq) replaceCfgFile(cfg *ConfigFile) (*FileRestartData, error
TrafficServerRestart: trafficServerRestart,
RemapConfigReload: remapConfigReload,
HitchReload: hitchReload,
VarnishReload: varnishReload,
},
}, nil
}
Expand Down Expand Up @@ -814,6 +817,7 @@ func (r *TrafficOpsReq) CheckReloadRestart(data []FileRestartData) RestartData {
rd.TrafficServerRestart = rd.TrafficServerRestart || changedFile.TrafficServerRestart
rd.RemapConfigReload = rd.RemapConfigReload || changedFile.RemapConfigReload
rd.HitchReload = rd.HitchReload || changedFile.HitchReload
rd.VarnishReload = rd.VarnishReload || changedFile.VarnishReload
}
return rd
}
Expand Down Expand Up @@ -1153,7 +1157,7 @@ func (r *TrafficOpsReq) StartServices(syncdsUpdate *UpdateStatus, metaData *t3cu
// If check-reload does not know about these and we do, then we should initiate
// a reload as well
if serviceNeeds != t3cutil.ServiceNeedsRestart && serviceNeeds != t3cutil.ServiceNeedsReload {
if r.TrafficCtlReload || r.RemapConfigReload {
if r.TrafficCtlReload || r.RemapConfigReload || r.VarnishReload {
log.Infof("ATS config files unchanged, we updated files via t3c-apply, ATS needs reload")
serviceNeeds = t3cutil.ServiceNeedsReload
}
Expand Down Expand Up @@ -1215,7 +1219,7 @@ func (r *TrafficOpsReq) StartServices(syncdsUpdate *UpdateStatus, metaData *t3cu
reloadCommand := config.TSHome + config.TrafficCtl
reloadArgs := []string{"config", "reload"}
if cfg.CacheType == "varnish" {
reloadCommand = "varnishreload"
reloadCommand = "/usr/sbin/varnishreload"
reloadArgs = []string{}
}
if _, _, err := util.ExecCommand(reloadCommand, reloadArgs...); err != nil {
Expand Down
35 changes: 35 additions & 0 deletions infrastructure/cdn-in-a-box/docker-compose.varnish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# 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.
#
# This compose file will run cache servers as Varnish instead of ATS.
#
# docker-compose -f docker-compose.yml -f docker-compose.varnish.yml up
#

---
version: '3.8'

services:
edge:
build:
dockerfile: infrastructure/cdn-in-a-box/varnish/Dockerfile
mid-01:
build:
dockerfile: infrastructure/cdn-in-a-box/varnish/Dockerfile
mid-02:
build:
dockerfile: infrastructure/cdn-in-a-box/varnish/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,17 @@
"secure": false,
"value": "1000"
},
{
"configFile": "rascal.properties",
"name": "health.polling.format",
"secure": false,
"value": "vstats"
},
{
"configFile": "rascal.properties",
"name": "health.polling.url",
"secure": false,
"value": "http://${hostname}/_astats?application=&inf.name=${interface_name}"
"value": "http://${hostname}:2000/?inf.name=${interface_name}"
},
{
"configFile": "storage.config",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -155,11 +155,17 @@
"secure": false,
"value": "1000"
},
{
"configFile": "rascal.properties",
"name": "health.polling.format",
"secure": false,
"value": "vstats"
},
{
"configFile": "rascal.properties",
"name": "health.polling.url",
"secure": false,
"value": "http://${hostname}/_astats?application=&inf.name=${interface_name}"
"value": "http://${hostname}:2000/?inf.name=${interface_name}"
},
{
"configFile": "storage.config",
Expand Down
9 changes: 9 additions & 0 deletions infrastructure/cdn-in-a-box/varnish/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@

ARG BASE_IMAGE=rockylinux \
RHEL_VERSION=8

FROM golang:1.20-alpine AS vstats-builder

COPY infrastructure/cdn-in-a-box/varnish/vstats.go /

RUN go build -o /vstats /vstats.go

FROM ${BASE_IMAGE}:${RHEL_VERSION} AS common-varnish-cache-config-layers
ARG RHEL_VERSION=8
# Makes RHEL_VERSION available at runtime
Expand Down Expand Up @@ -51,6 +58,8 @@ RUN rpm -Uvh /$(basename $ORT_RPM) &&\

COPY infrastructure/cdn-in-a-box/varnish/traffic_ops_ort.crontab /etc/cron.d/traffic_ops_ort-cron-template

COPY --from=vstats-builder /vstats /usr/local/bin

ENV CACHE_SERVER_TYPE=VARNISH
CMD /run.sh

Expand Down
1 change: 1 addition & 0 deletions infrastructure/cdn-in-a-box/varnish/run.sh
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ debug_binary="${!debug_variable_name}"
if ! type -p "$debug_binary"; then
t3c apply --cache=varnish --trafficserver-home=/opt/cache --run-mode=badass --traffic-ops-url="$TO_URL" --traffic-ops-user="$TO_USER" --traffic-ops-password="$TO_PASSWORD" --git=yes -vv || { echo "Failed"; }
fi
vstats -port 2000 >> /var/log/vstats.log 2>&1 &

envsubst < "/etc/cron.d/traffic_ops_ort-cron-template" > "/etc/cron.d/traffic_ops_ort-cron" && rm -f "/etc/cron.d/traffic_ops_ort-cron-template"
chmod "0644" "/etc/cron.d/traffic_ops_ort-cron" && crontab "/etc/cron.d/traffic_ops_ort-cron"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
# specific language governing permissions and limitations
# under the License.

*/1 * * * * root t3c apply --cache=varnish --run-mode=syncds --traffic-ops-url=$TO_URL --traffic-ops-user=$TO_USER --traffic-ops-password=$TO_PASSWORD --git=yes -vv --cache-host-name=$(hostname -s) >> /var/log/ort.log 2>&1
*/1 * * * * root t3c apply --cache=varnish --run-mode=syncds --traffic-ops-url=$TO_URL --trafficserver-home=/opt/cache --traffic-ops-user=$TO_USER --traffic-ops-password=$TO_PASSWORD --git=yes -vv --cache-host-name=$(hostname -s) >> /var/log/ort.log 2>&1
112 changes: 112 additions & 0 deletions infrastructure/cdn-in-a-box/varnish/vstats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
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 (
"encoding/json"
"flag"
"fmt"
"log"
"net/http"
"os"
"os/exec"
"strconv"
"strings"
)

type vstats struct {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can this struct be shared with anything that Traffic Monitor already deserializes?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It definitely should be shared however it's implemented as just "main" package separated from TC so it's not a package that TM can call. However, I think it should be a package under TC and be built with pkg considering statistics will be added too. Should that be in this PR or done separately?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If its ok with you, let's keep it separate, since this one is pretty well baked already

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, will open an issue for it to be done later.

ProcLoadavg string `json:"proc.loadavg"`
ProcNetDev string `json:"proc.net.dev"`
InfSpeed int64 `json:"inf_speed"`
NotAvailable bool `json:"not_available"`
// TODO: stats
}

func getSystemData(inf string) vstats {
var vstats vstats
loadavg, err := os.ReadFile("/proc/loadavg")
if err != nil {
log.Printf("failed to read /proc/loadavg: %s\n", err)
}
vstats.ProcLoadavg = strings.TrimSpace(string(loadavg))

procNetDev, err := os.ReadFile("/proc/net/dev")
if err != nil {
log.Printf("failed to read /proc/net/dev: %s\n", err)
}

parts := strings.Split(string(procNetDev), "\n")
for _, line := range parts {
if strings.HasPrefix(strings.TrimSpace(line), inf) {
vstats.ProcNetDev = strings.TrimSpace(line)
break
}
}

infSpeedFile := fmt.Sprintf("/sys/class/net/%s/speed", inf)
speedStr, err := os.ReadFile(infSpeedFile)

Check failure

Code scanning / CodeQL

Uncontrolled data used in path expression

This path depends on a [user-provided value](1).
if err != nil {
log.Printf("failed to read %s: %s\n", infSpeedFile, err)
}
speed, err := strconv.ParseInt(strings.TrimSpace(string(speedStr)), 10, 64)
if err != nil {
log.Printf("failed to convert speed '%s' to int: %s\n", speedStr, err)
}
vstats.InfSpeed = speed

cmd := exec.Command("systemctl", "status", "varnish.service")
err = cmd.Run()
if err != nil {
log.Printf("failed to run systemctl: %s\n", err)
}
if cmd.ProcessState.ExitCode() != 0 {
vstats.NotAvailable = true
}
return vstats
}

func getStats(w http.ResponseWriter, r *http.Request) {
inf := r.URL.Query().Get("inf.name")
if inf == "" {
// assume default eth0?
inf = "eth0"
}
inf = strings.ReplaceAll(inf, ".", "")
inf = strings.ReplaceAll(inf, "/", "")
vstats := getSystemData(inf)
encoder := json.NewEncoder(w)
err := encoder.Encode(vstats)
if err != nil {
log.Printf("failed to write Varnish stats: %s", err)
}
}

func main() {
var port int
flag.IntVar(&port, "port", 2000, "port to run vstats on")

flag.Parse()
http.HandleFunc("/", getStats)

listenAddress := fmt.Sprintf(":%d", port)
if err := http.ListenAndServe(listenAddress, nil); err != nil {
log.Printf("server stopped %s", err)
}
}
79 changes: 79 additions & 0 deletions traffic_monitor/cache/vstats.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
package cache

/*
* 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 (
"errors"
"fmt"
"io"

"github.com/apache/trafficcontrol/lib/go-log"
"github.com/apache/trafficcontrol/traffic_monitor/todata"
jsoniter "github.com/json-iterator/go"
)

func init() {
registerDecoder("vstats", vstatsParse, vstatsPrecompute)
}

// Vstats holds Varnish cache statistics
type Vstats struct {
ProcNetDev string `json:"proc.net.dev"`
ProcLoadAvg string `json:"proc.loadavg"`
NotAvailable bool `json:"not_available"`
InfSpeed int64 `json:"inf_speed"`
Stats map[string]interface{} `json:"stats"`
}

func vstatsParse(cacheName string, r io.Reader, _ interface{}) (Statistics, map[string]interface{}, error) {
var stats Statistics

if r == nil {
log.Warnf("%s handler got nil reader", cacheName)
return stats, nil, errors.New("handler got nil reader")
}

var vstats Vstats
json := jsoniter.ConfigFastest

if err := json.NewDecoder(r).Decode(&vstats); err != nil {
return stats, nil, fmt.Errorf("failed to decode reader data: %w", err)
}
if err := stats.AddInterfaceFromRawLine(vstats.ProcNetDev); err != nil {
return stats, nil, fmt.Errorf("failed to add interface data %s: %w", vstats.ProcNetDev, err)
}

if loadAvg, err := LoadavgFromRawLine(vstats.ProcLoadAvg); err != nil {
return stats, nil, fmt.Errorf("failed to read average load data %s: %w", vstats.ProcLoadAvg, err)
} else {
stats.Loadavg = loadAvg
}

stats.NotAvailable = vstats.NotAvailable
inf := stats.Interfaces["eth0"]
inf.Speed = vstats.InfSpeed
stats.Interfaces["eth0"] = inf

return stats, vstats.Stats, nil
}

func vstatsPrecompute(cacheName string, data todata.TOData, stats Statistics, miscStats map[string]interface{}) PrecomputedData {
return PrecomputedData{DeliveryServiceStats: map[string]*DSStat{}}
}
Loading