Skip to content

Commit

Permalink
feat: App stats availability measurement
Browse files Browse the repository at this point in the history
Currently concerned with when stats are seen as unavailable by the
client. Does not include when stats are set to zero by the Cloud
Controller when one or more required metrics are missing.

Co-authored-by: Rebecca Roberts <rebecca.roberts@broadcom.com>
  • Loading branch information
acrmp and rroberts2222 committed Apr 30, 2024
1 parent 463bcd6 commit 2ae8a06
Show file tree
Hide file tree
Showing 11 changed files with 334 additions and 15 deletions.
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ It measures:
by periodically pushing a very simple app.
- app syslog availability,
by periodically checking that app logs
drain to a syslog sink.
drain to a syslog sink,
- app stats availability,
by periodically checking that app stats
are not unavailable.

The CF Release Integration team uses it
to monitor availability during migrations
from `cf-release` to `cf-deployment`,
and during upgrade deployments.
It is often used to monitor availability
during upgrade deployments.

## Installation

Expand Down Expand Up @@ -66,6 +67,7 @@ Here is an example config `json`:
"allowed_failures": {
"app_pushability": 2,
"http_availability": 5,
"app_stats": 2,
"recent_logs": 2,
"streaming_logs": 2,
"app_syslog_availability": 2
Expand Down
9 changes: 9 additions & 0 deletions cfCmdGenerator/cfCmdGenerator.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type CfCmdGenerator interface {
DeleteOrg(org string) cmdStartWaiter.CmdStartWaiter
DeleteQuota(quota string) cmdStartWaiter.CmdStartWaiter
LogOut() cmdStartWaiter.CmdStartWaiter
AppStats(appName string) cmdStartWaiter.CmdStartWaiter
RecentLogs(appName string) cmdStartWaiter.CmdStartWaiter
StreamLogs(ctx context.Context, appName string) cmdStartWaiter.CmdStartWaiter
MapRoute(appName, domain string, port int) cmdStartWaiter.CmdStartWaiter
Expand Down Expand Up @@ -175,6 +176,14 @@ func (c *cfCmdGenerator) LogOut() cmdStartWaiter.CmdStartWaiter {
)
}

func (c *cfCmdGenerator) AppStats(appName string) cmdStartWaiter.CmdStartWaiter {
return c.setCfHome(
exec.Command(
"cf", "app", appName,
),
)
}

func (c *cfCmdGenerator) RecentLogs(appName string) cmdStartWaiter.CmdStartWaiter {
return c.setCfHome(
exec.Command(
Expand Down
8 changes: 8 additions & 0 deletions cfCmdGenerator/cfCmdGenerator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,14 @@ var _ = Describe("CfCmdGenerator", func() {
})
})

Describe("AppStats", func() {
It("Generates the correct command", func() {
expectedCmd := exec.Command("cf", "app", "appName")
cmd := generator.AppStats("appName")
expectCommandToBeEquivalent(cmd, expectedCmd, cfHomeEnvVar)
})
})

Describe("RecentLogs", func() {
It("Generates the correct command", func() {
expectedCmd := exec.Command("cf", "logs", "appName", "--recent")
Expand Down
10 changes: 10 additions & 0 deletions cfWorkflow/cfWorkflow.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ type CfWorkflow interface {
Delete(cfCmdGenerator.CfCmdGenerator) []cmdStartWaiter.CmdStartWaiter
TearDown(cfCmdGenerator.CfCmdGenerator) []cmdStartWaiter.CmdStartWaiter
RecentLogs(cfCmdGenerator.CfCmdGenerator) []cmdStartWaiter.CmdStartWaiter
AppStats(cfCmdGenerator.CfCmdGenerator) []cmdStartWaiter.CmdStartWaiter
StreamLogs(context.Context, cfCmdGenerator.CfCmdGenerator) []cmdStartWaiter.CmdStartWaiter

MapSyslogRoute(cfCmdGenerator.CfCmdGenerator) []cmdStartWaiter.CmdStartWaiter
Expand Down Expand Up @@ -141,6 +142,15 @@ func (c *cfWorkflow) TearDown(ccg cfCmdGenerator.CfCmdGenerator) []cmdStartWaite
return ret
}

func (c *cfWorkflow) AppStats(ccg cfCmdGenerator.CfCmdGenerator) []cmdStartWaiter.CmdStartWaiter {
return []cmdStartWaiter.CmdStartWaiter{
ccg.Api(c.cf.API),
ccg.Auth(c.cf.AdminUser, c.cf.AdminPassword),
ccg.Target(c.org, c.space),
ccg.AppStats(c.appName),
}
}

func (c *cfWorkflow) RecentLogs(ccg cfCmdGenerator.CfCmdGenerator) []cmdStartWaiter.CmdStartWaiter {
return []cmdStartWaiter.CmdStartWaiter{
ccg.Api(c.cf.API),
Expand Down
15 changes: 15 additions & 0 deletions cfWorkflow/cfWorkflow_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,21 @@ var _ = Describe("CfWorkflow", func() {
})
})

Describe("AppStats", func() {
It("returns a set of commands to get stats for an app", func() {
cmds := cw.AppStats(ccg)

Expect(cmds).To(Equal(
[]cmdStartWaiter.CmdStartWaiter{
ccg.Api("jigglypuff.cf-app.com"),
ccg.Auth("pika", "chu"),
ccg.Target("someOrg", "someSpace"),
ccg.AppStats("doraApp"),
},
))
})
})

Describe("RecentLogs", func() {
It("returns a set of commands to get recent logs for an app", func() {
cmds := cw.RecentLogs(ccg)
Expand Down
74 changes: 74 additions & 0 deletions cfWorkflow/cfWorkflowfakes/fake_cf_workflow.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ type AllowedFailures struct {
HttpAvailability int `json:"http_availability"`
RecentLogs int `json:"recent_logs"`
StreamingLogs int `json:"streaming_logs"`
AppStats int `json:"app_stats"`
AppSyslogAvailability int `json:"app_syslog_availability"`
TCPAvailability int `json:"tcp_availability"`
}
Expand Down
44 changes: 34 additions & 10 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ func main() {
}
logger.Println("Finished preparing included syslog sink app")
}
orcTmpDir, recentLogsTmpDir, streamingLogsTmpDir, pushTmpDir, tcpTmpDir, sinkTmpDir, err := createTmpDirs()
orcTmpDir, recentLogsTmpDir, streamingLogsTmpDir, appStatsTmpDir, pushTmpDir, tcpTmpDir, sinkTmpDir, err := createTmpDirs()
if err != nil {
logger.Println("Failed to create temp dirs:", err)
performMeasurements = false
Expand Down Expand Up @@ -179,6 +179,7 @@ func main() {
pushWorkflowGeneratorFunc,
cfCmdGenerator.New(recentLogsTmpDir, *useBuildpackDetection),
cfCmdGenerator.New(streamingLogsTmpDir, *useBuildpackDetection),
cfCmdGenerator.New(appStatsTmpDir, *useBuildpackDetection),
pushCmdGenerator,
cfg.AllowedFailures,
authFailedRetryFunc,
Expand Down Expand Up @@ -250,33 +251,37 @@ func main() {
os.Exit(exitCode)
}

func createTmpDirs() (string, string, string, string, string, string, error) {
func createTmpDirs() (string, string, string, string, string, string, string, error) {
orcTmpDir, err := os.MkdirTemp("", "uptimer")
if err != nil {
return "", "", "", "", "", "", err
return "", "", "", "", "", "", "", err
}
recentLogsTmpDir, err := os.MkdirTemp("", "uptimer")
if err != nil {
return "", "", "", "", "", "", err
return "", "", "", "", "", "", "", err
}
streamingLogsTmpDir, err := os.MkdirTemp("", "uptimer")
if err != nil {
return "", "", "", "", "", "", err
return "", "", "", "", "", "", "", err
}
appsStatsTmpDir, err := os.MkdirTemp("", "uptimer")
if err != nil {
return "", "", "", "", "", "", "", err
}
pushTmpDir, err := os.MkdirTemp("", "uptimer")
if err != nil {
return "", "", "", "", "", "", err
return "", "", "", "", "", "", "", err
}
tcpTmpDir, err := os.MkdirTemp("", "uptimer")
if err != nil {
return "", "", "", "", "", "", err
return "", "", "", "", "", "", "", err
}
sinkTmpDir, err := os.MkdirTemp("", "uptimer")
if err != nil {
return "", "", "", "", "", "", err
return "", "", "", "", "", "", "", err
}

return orcTmpDir, recentLogsTmpDir, streamingLogsTmpDir, pushTmpDir, tcpTmpDir, sinkTmpDir, nil
return orcTmpDir, recentLogsTmpDir, streamingLogsTmpDir, appsStatsTmpDir, pushTmpDir, tcpTmpDir, sinkTmpDir, nil
}

func prepareIncludedApp(name, source string) (string, error) {
Expand Down Expand Up @@ -328,7 +333,7 @@ func createMeasurements(
logger *log.Logger,
orcWorkflow cfWorkflow.CfWorkflow,
pushWorkFlowGeneratorFunc func() cfWorkflow.CfWorkflow,
recentLogsCmdGenerator, streamingLogsCmdGenerator, pushCmdGenerator cfCmdGenerator.CfCmdGenerator,
recentLogsCmdGenerator, streamingLogsCmdGenerator, pushCmdGenerator, appStatsCmdGenerator cfCmdGenerator.CfCmdGenerator,
allowedFailures config.AllowedFailures,
authFailedRetryFunc func(stdOut, stdErr string) bool,
) []measurement.Measurement {
Expand Down Expand Up @@ -380,6 +385,16 @@ func createMeasurements(
},
)

appStatsRunner, appStatsRunnerOutBuf, appStatsRunnerErrBuf := createBufferedRunner()
appStatsMeasurement := measurement.NewStatsAvailability(
func() []cmdStartWaiter.CmdStartWaiter {
return orcWorkflow.AppStats(appStatsCmdGenerator)
},
appStatsRunner,
appStatsRunnerOutBuf,
appStatsRunnerErrBuf,
)

return []measurement.Measurement{
measurement.NewPeriodic(
logger,
Expand Down Expand Up @@ -417,6 +432,15 @@ func createMeasurements(
allowedFailures.StreamingLogs,
authFailedRetryFunc,
),
measurement.NewPeriodic(
logger,
clock,
10*time.Second,
appStatsMeasurement,
measurement.NewResultSet(),
allowedFailures.AppStats,
authFailedRetryFunc,
),
}
}

Expand Down
16 changes: 16 additions & 0 deletions measurement/measurement.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,19 @@ func NewAppPushability(
runnerErrBuf: runnerErrBuf,
}
}

func NewStatsAvailability(
statsAvailabilityCommandGeneratorFunc func() []cmdStartWaiter.CmdStartWaiter,
runner cmdRunner.CmdRunner,
runnerOutBuf *bytes.Buffer,
runnerErrBuf *bytes.Buffer,
) BaseMeasurement {
return &statsAvailability{
name: "Stats availability",
summaryPhrase: "retrieve stats for app",
statsAvailabilityCommandGeneratorFunc: statsAvailabilityCommandGeneratorFunc,
runner: runner,
runnerOutBuf: runnerOutBuf,
runnerErrBuf: runnerErrBuf,
}
}
44 changes: 44 additions & 0 deletions measurement/statsAvailability.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package measurement

import (
"bytes"
"strings"

"github.com/cloudfoundry/uptimer/cmdRunner"
"github.com/cloudfoundry/uptimer/cmdStartWaiter"
)

type statsAvailability struct {
name string
summaryPhrase string
statsAvailabilityCommandGeneratorFunc func() []cmdStartWaiter.CmdStartWaiter
runner cmdRunner.CmdRunner
runnerOutBuf *bytes.Buffer
runnerErrBuf *bytes.Buffer
}

func (s *statsAvailability) Name() string {
return s.name
}

func (s *statsAvailability) SummaryPhrase() string {
return s.summaryPhrase
}

func (s *statsAvailability) PerformMeasurement() (string, string, string, bool) {
defer s.runnerOutBuf.Reset()
defer s.runnerErrBuf.Reset()

if err := s.runner.RunInSequence(s.statsAvailabilityCommandGeneratorFunc()...); err != nil {
return err.Error(), s.runnerOutBuf.String(), s.runnerErrBuf.String(), false
}

if strings.Contains(s.runnerErrBuf.String(), "Stats server temporarily unavailable.") {
return "Stats server was unavailable",
s.runnerOutBuf.String(),
s.runnerErrBuf.String(),
false
}

return "", "", "", true
}
Loading

0 comments on commit 2ae8a06

Please sign in to comment.