Skip to content

Commit 98a46a4

Browse files
committed
switch to plans
1 parent 50145f0 commit 98a46a4

File tree

14 files changed

+334
-97
lines changed

14 files changed

+334
-97
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,5 +25,6 @@ go.work.sum
2525
# popmaint
2626
/log
2727
/state
28+
/plans
2829
*.json
2930

README.md

Lines changed: 0 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -45,24 +45,6 @@ plan1.toml
4545
plan2.toml
4646
```
4747

48-
49-
50-
Storing State
51-
-------------
52-
* State needs to support all actions
53-
* Need to handle read-only databases
54-
* Need to handle databases that come and go
55-
* State struct
56-
* Key(domain, computer, instance, database)
57-
* Value: last DBCC, last dbcc settings, last seen
58-
* Saved in `./state/plan1.state.json`
59-
* Maybe a state log that stores each event. But it will get big. Maybe compress on startup.
60-
* Read in the state and update it as we go. Write with each update.
61-
* DBCCEngine
62-
DBCCState - struct that holds all the DBCC we have done
63-
CheckDB(...)
64-
65-
6648
Finding Work
6749
------------
6850
* Get all the databases
@@ -87,11 +69,6 @@ Fun Stuff
8769
* Write sql.Rows to a log file
8870
* Write the log file and rotate it
8971

90-
Read-Only Databases
91-
-------------------
92-
* Need to store state with the executable
93-
* Write a
94-
9572
Next Test #1
9673
------------
9774
* Monitor for blocking or blocked by and abort (EXECMON)

cmd/checkdb/main.go

Lines changed: 19 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,41 +1,35 @@
11
package main
22

33
import (
4-
"context"
54
"flag"
65
"log"
6+
"os"
77
"popmaint/pkg/app"
8-
"popmaint/pkg/maint"
9-
"popmaint/pkg/state"
108
)
119

1210
func main() {
13-
out := app.OutWriter{}
14-
ctx := context.Background()
15-
do := maint.CheckDBOptions{}
16-
var fqdn string
1711

18-
flag.BoolVar(&do.NoIndex, "noindex", false, "Set the NOINDEX option")
19-
flag.BoolVar(&do.InfoMessage, "messages", false, "Display Info Messages (disable NO_INFOMSGS)")
20-
flag.BoolVar(&do.PhysicalOnly, "physical_only", false, "Set the PHYSICAL_ONLY option")
21-
flag.IntVar(&do.MaxSizeMB, "max_size", 0, "max database size to check (MB)")
22-
flag.StringVar(&fqdn, "fqdn", "", "server to run against")
23-
flag.BoolVar(&do.NoExec, "noexec", false, "do not execute the DBCC")
12+
//ctx := context.Background()
13+
//do := maint.CheckDBOptions{}
14+
var plan string
15+
var noexec bool
16+
// flag.BoolVar(&do.NoIndex, "noindex", false, "Set the NOINDEX option")
17+
// flag.BoolVar(&do.InfoMessage, "messages", false, "Display Info Messages (disable NO_INFOMSGS)")
18+
// flag.BoolVar(&do.PhysicalOnly, "physical_only", false, "Set the PHYSICAL_ONLY option")
19+
// flag.IntVar(&do.MaxSizeMB, "max_size", 0, "max database size to check (MB)")
20+
flag.StringVar(&plan, "plan", "", "plan to run")
21+
flag.BoolVar(&noexec, "noexec", false, "do not execute the DBCC (display only)")
2422
flag.Parse()
25-
if fqdn == "" {
26-
log.Fatal("fatal: --fqdn is required")
23+
if plan == "" {
24+
log.Fatal("fatal: --plan is required")
2725
return
2826
}
29-
// log settings:
30-
out.WriteStringf("host: %s noindex: %t messages: %t physical_only: %t max_size: %d", fqdn, do.NoIndex, do.InfoMessage, do.PhysicalOnly, do.MaxSizeMB)
31-
st, err := state.NewState("Plan1")
32-
if err != nil {
33-
out.WriteError(err)
34-
return
27+
exitCode := app.Run(plan, noexec)
28+
if exitCode > 125 {
29+
exitCode = 125
3530
}
36-
ce := app.NewCheckDBEngine(&out, st, do)
37-
err = ce.CheckDB(ctx, fqdn)
38-
if err != nil {
39-
out.WriteError(err)
31+
if exitCode < 0 {
32+
exitCode = 0
4033
}
34+
os.Exit(exitCode)
4135
}

go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ require (
77
github.com/golang-sql/sqlexp v0.1.0
88
github.com/jmoiron/sqlx v1.4.0
99
github.com/microsoft/go-mssqldb v1.7.2
10+
github.com/pelletier/go-toml/v2 v2.2.2
11+
github.com/pkg/errors v0.9.1
1012
)
1113

1214
require (
1315
github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect
14-
github.com/pkg/errors v0.9.1 // indirect
1516
github.com/spf13/afero v1.9.5 // indirect
16-
github.com/stretchr/testify v1.9.0 // indirect
1717
golang.org/x/crypto v0.18.0 // indirect
1818
golang.org/x/sys v0.16.0 // indirect
1919
golang.org/x/text v0.14.0 // indirect

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -163,6 +163,8 @@ github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o
163163
github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
164164
github.com/microsoft/go-mssqldb v1.7.2 h1:CHkFJiObW7ItKTJfHo1QX7QBBD1iV+mn1eOyRP3b/PA=
165165
github.com/microsoft/go-mssqldb v1.7.2/go.mod h1:kOvZKUdrhhFQmxLZqbwUV0rHkNkZpthMITIb2Ko1IoA=
166+
github.com/pelletier/go-toml/v2 v2.2.2 h1:aYUidT7k73Pcl9nb2gScu7NSrKCSHIDE89b3+6Wq+LM=
167+
github.com/pelletier/go-toml/v2 v2.2.2/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs=
166168
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
167169
github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c/go.mod h1:7rwL4CYBLnjLxUqIJNnCWiEdr3bn6IUYi15bNlnbCCU=
168170
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
@@ -175,9 +177,15 @@ github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
175177
github.com/spf13/afero v1.9.5 h1:stMpOSZFs//0Lv29HduCmli3GUfpFoF3Y1Q/aXj/wVM=
176178
github.com/spf13/afero v1.9.5/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ=
177179
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
180+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
181+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
182+
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
178183
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
179184
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
180185
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
186+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
187+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
188+
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
181189
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
182190
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
183191
github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74=

pkg/app/app.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
package app
2+
3+
import (
4+
"context"
5+
"errors"
6+
"fmt"
7+
"popmaint/pkg/config.go"
8+
"popmaint/pkg/state"
9+
)
10+
11+
var ErrRunError = errors.New("error running plan")
12+
13+
func Run(planName string, noexec bool) int {
14+
ctx := context.Background()
15+
out := OutWriter{}
16+
plan, err := config.ReadPlan(planName)
17+
if err != nil {
18+
out.WriteError(err)
19+
return 1
20+
}
21+
out.WriteStringf("%s: servers: %d noexec: %t", planName, len(plan.Servers), noexec)
22+
st, err := state.NewState(planName)
23+
if err != nil {
24+
out.WriteError(err)
25+
return 1
26+
}
27+
defer func(st *state.State) {
28+
if err := st.Close(); err != nil {
29+
out.WriteError(fmt.Errorf("state.close: %w", err))
30+
}
31+
}(st)
32+
33+
engine := NewEngine(&out, st)
34+
return engine.runPlan(ctx, plan, noexec)
35+
}

pkg/app/checkdb.go

Lines changed: 111 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -2,79 +2,154 @@ package app
22

33
import (
44
"context"
5+
"popmaint/pkg/config.go"
56
"popmaint/pkg/maint"
67
"popmaint/pkg/mssqlz"
78
"popmaint/pkg/state"
89
"sort"
10+
"time"
911

10-
"github.com/billgraziano/mssqlh"
12+
"github.com/pkg/errors"
1113
)
1214

13-
type CheckDBEngine struct {
14-
out mssqlz.Outputer
15-
do maint.CheckDBOptions
16-
st *state.State
15+
type Engine struct {
16+
out mssqlz.Outputer
17+
st *state.State
18+
start time.Time
1719
}
1820

19-
func NewCheckDBEngine(out mssqlz.Outputer, st *state.State, do maint.CheckDBOptions) CheckDBEngine {
20-
return CheckDBEngine{
21-
out: out,
22-
do: do,
23-
st: st,
21+
func NewEngine(out mssqlz.Outputer, st *state.State) Engine {
22+
return Engine{
23+
out: out,
24+
st: st,
25+
start: time.Now(),
2426
}
2527
}
2628

27-
func (ce *CheckDBEngine) CheckDB(ctx context.Context, host string) error {
28-
pool, err := mssqlh.Open(host, "master")
29-
if err != nil {
30-
return err
31-
}
32-
defer pool.Close()
29+
func (engine *Engine) runPlan(ctx context.Context, plan config.Plan, noexec bool) int {
30+
// TODO sort the plan entries and run in order
31+
return engine.runCheckDB(ctx, plan, noexec)
32+
}
3333

34-
databases, err := mssqlz.OnlineDatabases(ctx, host)
35-
if err != nil {
36-
return err
37-
}
34+
func (engine *Engine) runCheckDB(ctx context.Context, plan config.Plan, noexec bool) int {
35+
var err error
36+
exitCode := 0
37+
timeLimit := time.Duration(plan.CheckDB.TimeLimit)
38+
start := time.Now()
39+
engine.out.WriteStringf("%s: checkdb: time_limit: %s noindex: %t physical_only: %t max_size_mb: %d info_messages: %t", plan.Name, timeLimit, plan.CheckDB.NoIndex, plan.CheckDB.PhysicalOnly, plan.CheckDB.MaxSizeMB, plan.CheckDB.InfoMessages)
3840

39-
// Get the last CheckDB date from `state`.
40-
// Secondary replicas always report the value from the primary.
41-
// That means we will use our date.
41+
// loop through all servers and get databases
42+
databases := make([]mssqlz.Database, 0)
43+
for _, fqdn := range plan.Servers {
44+
srv, err := mssqlz.GetServer(ctx, fqdn)
45+
if err != nil {
46+
engine.out.WriteError(errors.Wrap(err, fqdn))
47+
continue
48+
}
49+
dbs, err := mssqlz.OnlineDatabases(ctx, fqdn)
50+
if err != nil {
51+
engine.out.WriteError(errors.Wrap(err, fqdn))
52+
continue
53+
}
54+
databases = append(databases, dbs...)
55+
engine.out.WriteStringf("fqdn: %s server: %s databases: %d", fqdn, srv.ServerName, len(dbs))
56+
}
57+
// sort the databases, filter, and get state
4258
for i, db := range databases {
43-
tm, ok := ce.st.GetCheckDB(db)
59+
tm, ok := engine.st.GetLastCheckDBDate(db)
4460
if ok {
4561
databases[i].LastDBCC = tm
4662
}
4763
}
48-
49-
// sort the oldest first, and then the largest
5064
sort.SliceStable(databases, func(i, j int) bool {
5165
if databases[i].LastDBCC.Before(databases[j].LastDBCC) {
5266
return true
5367
}
5468

5569
return databases[i].DatabaseMB > databases[j].DatabaseMB
5670
})
57-
71+
// if !noexec, run each one
5872
for _, db := range databases {
59-
if ce.do.MaxSizeMB > 0 && db.DatabaseMB > ce.do.MaxSizeMB {
73+
if time.Now().After(start.Add(timeLimit)) {
74+
engine.out.WriteStringf("%s: time_limit (%s) exceeded", plan.Name, timeLimit)
75+
break
76+
}
77+
if plan.CheckDB.MaxSizeMB > 0 && db.DatabaseMB > plan.CheckDB.MaxSizeMB {
6078
continue
6179
}
62-
err = maint.CheckDB(ctx, ce.out, host, db, ce.do)
80+
engine.out.WriteStringf("%s: database: %s size_mb: %d last_dbcc: %s", db.ServerName, db.DatabaseName, db.DatabaseMB, db.LastDBCC.Format("2006-01-02 15:04:05"))
81+
82+
err = maint.CheckDB(ctx, engine.out, db.FQDN, db, plan, noexec)
6383
if err != nil {
64-
// TODO keep going on error?
65-
return err
84+
// Log the error and keep going
85+
exitCode = 1
86+
engine.out.WriteErrorf("%s: %s", db.ServerName, err.Error())
87+
continue // don't set the state
6688
}
67-
if !ce.do.NoExec { // if we really did it, save it
68-
err = ce.st.SaveCheckDB(db)
89+
if !noexec { // if we really did it, save it
90+
err = engine.st.SetLastCheckDBDate(db)
6991
if err != nil {
70-
ce.out.WriteError(err)
71-
return err
92+
engine.out.WriteError(err)
93+
exitCode = 1
7294
}
95+
// TODO Log the run time for this: FQDN, server, database, size, duration (rounded to second)
7396
}
7497
}
75-
return nil
98+
return exitCode
7699
}
77100

101+
// // TODO: CheckDB only checks one mssqlz.Database row and writes the results
102+
// func (ce *Engine) CheckDB(ctx context.Context, host string, plan config.Plan, noexec bool) error {
103+
// pool, err := mssqlh.Open(host, "master")
104+
// if err != nil {
105+
// return err
106+
// }
107+
// defer pool.Close()
108+
109+
// databases, err := mssqlz.OnlineDatabases(ctx, host)
110+
// if err != nil {
111+
// return err
112+
// }
113+
114+
// // Get the last CheckDB date from `state`.
115+
// // Secondary replicas always report the value from the primary.
116+
// // That means we will use our date.
117+
// for i, db := range databases {
118+
// tm, ok := ce.st.GetLastCheckDBDate(db)
119+
// if ok {
120+
// databases[i].LastDBCC = tm
121+
// }
122+
// }
123+
124+
// // sort the oldest first, and then the largest
125+
// sort.SliceStable(databases, func(i, j int) bool {
126+
// if databases[i].LastDBCC.Before(databases[j].LastDBCC) {
127+
// return true
128+
// }
129+
130+
// return databases[i].DatabaseMB > databases[j].DatabaseMB
131+
// })
132+
133+
// for _, db := range databases {
134+
// if plan.CheckDB.MaxSizeMB > 0 && db.DatabaseMB > plan.CheckDB.MaxSizeMB {
135+
// continue
136+
// }
137+
// err = maint.CheckDB(ctx, ce.out, host, db, plan, noexec)
138+
// if err != nil {
139+
// // TODO keep going on error?
140+
// return err
141+
// }
142+
// if !noexec { // if we really did it, save it
143+
// err = ce.st.SetLastCheckDBDate(db)
144+
// if err != nil {
145+
// ce.out.WriteError(err)
146+
// return err
147+
// }
148+
// }
149+
// }
150+
// return nil
151+
// }
152+
78153
// // sort by oldest defrag, then largest size
79154
// func sortDatabases(databases []mssqlz.Database) {
80155
// sort.SliceStable(databases, func(i, j int) bool {

pkg/app/outwriter.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ func (ow *OutWriter) WriteError(err error) error {
5151
return nil
5252
}
5353

54+
func (ow *OutWriter) WriteErrorf(fmt string, args ...any) error {
55+
log.Printf(fmt, args...)
56+
return nil
57+
}
58+
5459
func (ow *OutWriter) WriteRowSet() error {
5560
panic("rowset!")
5661
}

0 commit comments

Comments
 (0)