Compare commits

...

8 Commits

Author SHA1 Message Date
Bel LaPointe
550ce91a7f accept more noauth codes from ntg 2022-01-11 08:35:07 -05:00
Bel LaPointe
9d8f561b54 logs 2022-01-11 08:24:46 -05:00
Bel LaPointe
e12299ac20 add config name for identifying who wants what 2022-01-11 08:16:02 -05:00
Bel LaPointe
29ae26153f add logs 2022-01-11 07:58:08 -05:00
Bel LaPointe
8109bb3fa0 sleep on start before getting email 2022-01-11 07:57:11 -05:00
Bel LaPointe
27605997c1 emailer configurable interval 2022-01-11 07:50:58 -05:00
Bel LaPointe
6c455764e1 durations are now optionally spans for jitter 2022-01-11 07:39:24 -05:00
Bel LaPointe
31a3d4948b separate ok and err intervals for sleep, dont exit on err 2022-01-11 07:39:07 -05:00
8 changed files with 194 additions and 30 deletions

View File

@@ -2,6 +2,7 @@ package broker
import ( import (
"fmt" "fmt"
"local/truckstop/config"
"time" "time"
) )
@@ -37,7 +38,8 @@ func (j JobLocation) String() string {
func (j Job) FormatMultilineText() string { func (j Job) FormatMultilineText() string {
return fmt.Sprintf( return fmt.Sprintf(
"--- %s => %s ---\nPickup: %s\nDropoff: %s\nNotes: %d lbs, %d miles, %s", "--- %s: %s => %s ---\nPickup: %s\nDropoff: %s\nNotes: %d lbs, %d miles, %s",
config.Get().Name,
j.Pickup.State, j.Pickup.State,
j.Dropoff.State, j.Dropoff.State,
j.Pickup.String(), j.Pickup.String(),

View File

@@ -146,7 +146,7 @@ func (ntg NTGVision) _search(states []config.State) (io.ReadCloser, error) {
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
b, _ := ioutil.ReadAll(resp.Body) b, _ := ioutil.ReadAll(resp.Body)
resp.Body.Close() resp.Body.Close()
if resp.StatusCode > 400 && resp.StatusCode < 404 { if resp.StatusCode > 400 && resp.StatusCode < 500 && resp.StatusCode != 404 && resp.StatusCode != 410 {
return nil, ErrNoAuth return nil, ErrNoAuth
} }
return nil, fmt.Errorf("bad status searching ntg: %d: %s", resp.StatusCode, b) return nil, fmt.Errorf("bad status searching ntg: %d: %s", resp.StatusCode, b)

View File

@@ -1,5 +1,10 @@
{ {
"Interval": "6h0m0s", "Name": "pa",
"Interval": {
"OK": "6h0m0s",
"Error": "6h",
"Email": "15m"
},
"States": [ "States": [
"GA" "GA"
], ],

View File

@@ -10,7 +10,12 @@ import (
) )
type Config struct { type Config struct {
Interval Duration Name string
Interval struct {
Email Duration
OK Duration
Error Duration
}
States []State States []State
Storage []string Storage []string
Client string Client string

View File

@@ -2,17 +2,29 @@ package config
import ( import (
"encoding/json" "encoding/json"
"errors"
"math/rand"
"strings"
"time" "time"
) )
type Duration time.Duration type Duration struct {
least time.Duration
most time.Duration
}
func (d Duration) Get() time.Duration { func (d Duration) Get() time.Duration {
return time.Duration(d) jitter := d.most - d.least
if jitter >= time.Second {
jitter = time.Second * time.Duration(rand.Int()%int(jitter.Seconds()))
} else {
jitter = 0
}
return time.Duration(d.least + jitter)
} }
func (d Duration) MarshalJSON() ([]byte, error) { func (d Duration) MarshalJSON() ([]byte, error) {
return json.Marshal(d.Get().String()) return json.Marshal(d.least.String() + ".." + d.most.String())
} }
func (d *Duration) UnmarshalJSON(b []byte) error { func (d *Duration) UnmarshalJSON(b []byte) error {
@@ -20,10 +32,25 @@ func (d *Duration) UnmarshalJSON(b []byte) error {
if err := json.Unmarshal(b, &s); err != nil { if err := json.Unmarshal(b, &s); err != nil {
return err return err
} }
d2, err := time.ParseDuration(s) if !strings.Contains(s, "..") {
s = s + ".." + s
}
splits := strings.Split(s, "..")
if len(splits) != 2 {
return errors.New("unexpected ..")
}
least, err := time.ParseDuration(splits[0])
if err != nil { if err != nil {
return err return err
} }
*d = Duration(d2) most, err := time.ParseDuration(splits[1])
if err != nil {
return err
}
if least > most {
most, least = least, most
}
d.most = most
d.least = least
return nil return nil
} }

105
config/duration_test.go Normal file
View File

@@ -0,0 +1,105 @@
package config
import (
"encoding/json"
"testing"
"time"
)
func TestDurationMarshal(t *testing.T) {
cases := map[string]struct {
in string
want Duration
wantJS string
}{
"empty string": {
wantJS: `"0s..0s"`,
},
"random in": {
in: "abc",
want: Duration{},
wantJS: `"0s..0s"`,
},
"1 dur": {
in: "1m",
want: Duration{
least: time.Minute,
most: time.Minute,
},
wantJS: `"1m0s..1m0s"`,
},
"descending": {
in: "2m..1m",
want: Duration{
least: time.Minute,
most: 2 * time.Minute,
},
wantJS: `"1m0s..2m0s"`,
},
"happy span": {
in: "1m..2m",
want: Duration{
least: time.Minute,
most: 2 * time.Minute,
},
wantJS: `"1m0s..2m0s"`,
},
"multi unit descending": {
in: "1h1m..2m3ms",
want: Duration{
least: 2*time.Minute + 3*time.Millisecond,
most: time.Hour + time.Minute,
},
wantJS: `"2m0.003s..1h1m0s"`,
},
}
for name, d := range cases {
c := d
t.Run(name, func(t *testing.T) {
var got Duration
err := json.Unmarshal([]byte(`"`+c.in+`"`), &got)
if got != c.want {
t.Fatalf("err %v: want %+v, got %+v", err, c.want, got)
}
js, _ := json.Marshal(got)
if string(js) != c.wantJS {
t.Fatalf("want marshalled %s, got %s", c.wantJS, js)
}
var got2 Duration
json.Unmarshal(js, &got2)
if got != got2 {
t.Fatalf("want unmarshal-marshal-unmarshal %+v, got %+v", got, got2)
}
})
}
}
func TestDurationGet(t *testing.T) {
t.Run("same", func(t *testing.T) {
d := Duration{least: time.Second, most: time.Second}
for i := 0; i < 50; i++ {
if got := d.Get(); got != time.Second {
t.Error(got)
}
}
})
t.Run("1ms span", func(t *testing.T) {
d := Duration{least: time.Second, most: time.Second + time.Millisecond}
for i := 0; i < 50; i++ {
if got := d.Get(); got != time.Second {
t.Error(got)
}
}
})
t.Run("5s span", func(t *testing.T) {
d := Duration{least: time.Second, most: 6 * time.Second}
for i := 0; i < 50; i++ {
if got := d.Get(); got > time.Second*6 || got < time.Second {
t.Error(got)
}
}
})
}

35
main.go
View File

@@ -21,17 +21,16 @@ var stateFinder = regexp.MustCompile(`[A-Za-z]+`)
func main() { func main() {
lock := &sync.Mutex{} lock := &sync.Mutex{}
go func() { go func() {
c := time.NewTicker(time.Minute) for {
for range c.C { time.Sleep(config.Get().Interval.Email.Get())
if !config.Get().EmailerEnabled { if config.Get().EmailerEnabled {
continue
}
lock.Lock() lock.Lock()
if err := email(); err != nil { if err := email(); err != nil {
log.Print(err) log.Print(err)
} }
lock.Unlock() lock.Unlock()
} }
}
}() }()
if err := _main(); err != nil { if err := _main(); err != nil {
panic(err) panic(err)
@@ -94,17 +93,32 @@ func parseOutStates(b []byte) []config.State {
func _main() error { func _main() error {
for { for {
err := _mainOne()
if err != nil {
log.Println(err)
}
if config.Get().Once {
return err
}
if err != nil {
time.Sleep(config.Get().Interval.Error.Get())
} else {
time.Sleep(config.Get().Interval.OK.Get())
}
}
return nil
}
func _mainOne() error {
log.Println("config.refreshing...")
if err := config.Refresh(); err != nil { if err := config.Refresh(); err != nil {
return err return err
} }
log.Println("once...")
if err := once(); err != nil { if err := once(); err != nil {
return err return err
} }
if config.Get().Once { log.Println("/_mainOne")
break
}
time.Sleep(config.Get().Interval.Get())
}
return nil return nil
} }
@@ -125,6 +139,7 @@ func once() error {
if err := sendJob(jobs[i]); err != nil { if err := sendJob(jobs[i]); err != nil {
return err return err
} }
log.Println("sent job", jobs[i])
if err := config.Get().DB().Set(jobs[i].ID, []byte(`sent`)); err != nil { if err := config.Get().DB().Set(jobs[i].ID, []byte(`sent`)); err != nil {
return err return err
} }

View File

@@ -3,8 +3,13 @@ todo:
subtasks: subtasks:
- banlist criteria like vendors, brokers, metadata - banlist criteria like vendors, brokers, metadata
- quiet hours - quiet hours
- setup pa on element - setup ma on element
- accept states via element for one system
- set up copy for caleb, broc
done: done:
- setup pa on element
- configurable email interval
- jitter on intervals, including dedicated err span
- email doesnt get all matches - email doesnt get all matches
- send jobs - send jobs
- read jobs - read jobs