-
Notifications
You must be signed in to change notification settings - Fork 1
/
connectivity.go
173 lines (154 loc) · 4.99 KB
/
connectivity.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/*
Copyright 2013-2014 Canonical Ltd.
This program is free software: you can redistribute it and/or modify it
under the terms of the GNU General Public License version 3, as published
by the Free Software Foundation.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranties of
MERCHANTABILITY, SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR
PURPOSE. See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License along
with this program. If not, see <http://www.gnu.org/licenses/>.
*/
// Package connectivity a single, simple stream of booleans to answer
// the quesiton “are we connected?”.
//
// It can potentially fire two falses in a row, if a disconnected
// state is followed by a dbus watch error. Other than that, it's edge
// triggered.
package connectivity
import (
"errors"
"launchpad.net/ubuntu-push/bus"
"launchpad.net/ubuntu-push/bus/networkmanager"
"launchpad.net/ubuntu-push/config"
"launchpad.net/ubuntu-push/logger"
"launchpad.net/ubuntu-push/util"
"time"
)
// the configuration for ConnectedState, with the idea that you'd populate it
// from a config file.
type ConnectivityConfig struct {
// how long to wait after a state change to make sure it's "stable"
// before acting on it
StabilizingTimeout config.ConfigTimeDuration `json:"stabilizing_timeout"`
// How long to wait between online connectivity checks.
RecheckTimeout config.ConfigTimeDuration `json:"recheck_timeout"`
// The URL against which to do the connectivity check.
ConnectivityCheckURL string `json:"connectivity_check_url"`
// The expected MD5 of the content at the ConnectivityCheckURL
ConnectivityCheckMD5 string `json:"connectivity_check_md5"`
}
type connectedState struct {
networkStateCh <-chan networkmanager.State
config ConnectivityConfig
log logger.Logger
endp bus.Endpoint
connAttempts uint32
webget func(ch chan<- bool)
webgetCh chan bool
currentState networkmanager.State
lastSent bool
timer *time.Timer
}
// start connects to the bus, gets the initial NetworkManager state, and sets
// up the watch.
func (cs *connectedState) start() networkmanager.State {
var initial networkmanager.State
var ch <-chan networkmanager.State
var err error
for {
ar := util.NewAutoRedialer(cs.endp)
cs.connAttempts += ar.Redial()
nm := networkmanager.New(cs.endp, cs.log)
// Get the current state.
initial = nm.GetState()
if initial == networkmanager.Unknown {
cs.log.Debugf("Failed to get state.")
goto Continue
}
// set up the watch
ch, err = nm.WatchState()
if err != nil {
cs.log.Debugf("Failed to set up the watch: %s", err)
goto Continue
}
cs.networkStateCh = ch
return initial
Continue:
cs.endp.Close()
time.Sleep(10 * time.Millisecond) // that should cool things
}
}
// connectedStateStep takes one step forwards in the “am I connected?”
// answering state machine.
func (cs *connectedState) connectedStateStep() (bool, error) {
stabilizingTimeout := cs.config.StabilizingTimeout.Duration
recheckTimeout := cs.config.RecheckTimeout.Duration
log := cs.log
Loop:
for {
select {
case v, ok := <-cs.networkStateCh:
if !ok {
// tear it all down and start over
return false, errors.New("Got not-OK from StateChanged watch")
}
cs.webgetCh = nil
cs.currentState = v
cs.timer.Reset(stabilizingTimeout)
log.Debugf("State changed to %s. Assuming disconnect.", v)
if cs.lastSent == true {
log.Infof("Sending 'disconnected'.")
cs.lastSent = false
break Loop
}
case <-cs.timer.C:
if cs.currentState == networkmanager.ConnectedGlobal {
log.Debugf("May be connected; checking...")
cs.webgetCh = make(chan bool)
go cs.webget(cs.webgetCh)
}
case connected := <-cs.webgetCh:
cs.timer.Reset(recheckTimeout)
log.Debugf("Connection check says: %t", connected)
cs.webgetCh = nil
if connected && cs.lastSent == false {
log.Infof("Sending 'connected'.")
cs.lastSent = true
break Loop
}
}
}
return cs.lastSent, nil
}
// ConnectedState sends the initial NetworkManager state and changes to it
// over the "out" channel. Sends "false" as soon as it detects trouble, "true"
// after checking actual connectivity.
//
// The endpoint need not be dialed; connectivity will Dial() and Close()
// it as it sees fit.
func ConnectedState(endp bus.Endpoint, config ConnectivityConfig, log logger.Logger, out chan<- bool) {
wg := NewWebchecker(config.ConnectivityCheckURL, config.ConnectivityCheckMD5, log)
cs := &connectedState{
config: config,
log: log,
endp: endp,
webget: wg.Webcheck,
}
Start:
log.Infof("Sending initial 'disconnected'.")
out <- false
cs.lastSent = false
cs.currentState = cs.start()
cs.timer = time.NewTimer(cs.config.StabilizingTimeout.Duration)
for {
v, err := cs.connectedStateStep()
if err != nil {
// tear it all down and start over
log.Errorf("%s", err)
goto Start
}
out <- v
}
}