@@ -20,9 +20,12 @@ import (
20
20
"encoding/json"
21
21
"flag"
22
22
"fmt"
23
+ "math"
23
24
"net"
24
25
"os/exec"
25
26
"regexp"
27
+ "strconv"
28
+ "strings"
26
29
"time"
27
30
28
31
"github.com/docker/machine/libmachine"
@@ -44,6 +47,17 @@ import (
44
47
pkgutil "k8s.io/minikube/pkg/util"
45
48
)
46
49
50
+ // hostRunner is a minimal host.Host based interface for running commands
51
+ type hostRunner interface {
52
+ RunSSHCommand (string ) (string , error )
53
+ }
54
+
55
+ var (
56
+ // The maximum the guest VM clock is allowed to be ahead and behind. This value is intentionally
57
+ // large to allow for inaccurate methodology, but still small enough so that certificates are likely valid.
58
+ maxClockDesyncSeconds = 2.1
59
+ )
60
+
47
61
//This init function is used to set the logtostderr variable to false so that INFO level log info does not clutter the CLI
48
62
//INFO lvl logging is displayed due to the kubernetes api calling flag.Set("logtostderr", "true") in its init()
49
63
//see: https://github.com/kubernetes/kubernetes/blob/master/pkg/kubectl/util/logs/logs.go#L32-L34
@@ -117,16 +131,15 @@ func StartHost(api libmachine.API, config cfg.MachineConfig) (*host.Host, error)
117
131
e := engineOptions (config )
118
132
glog .Infof ("engine options: %+v" , e )
119
133
120
- err = waitForSSHAccess (h , e )
134
+ err = configureHost (h , e )
121
135
if err != nil {
122
136
return nil , err
123
137
}
124
-
125
138
return h , nil
126
139
}
127
140
128
- func waitForSSHAccess ( h * host. Host , e * engine. Options ) error {
129
-
141
+ // configureHost handles any post-powerup configuration required
142
+ func configureHost ( h * host. Host , e * engine. Options ) error {
130
143
// Slightly counter-intuitive, but this is what DetectProvisioner & ConfigureAuth block on.
131
144
console .OutStyle ("waiting" , "Waiting for SSH access ..." )
132
145
@@ -145,11 +158,60 @@ func waitForSSHAccess(h *host.Host, e *engine.Options) error {
145
158
if err := h .ConfigureAuth (); err != nil {
146
159
return & util.RetriableError {Err : errors .Wrap (err , "Error configuring auth on host" )}
147
160
}
161
+ return ensureSyncedGuestClock (h )
148
162
}
149
163
150
164
return nil
151
165
}
152
166
167
+ // ensureGuestClockSync ensures that the guest system clock is relatively in-sync
168
+ func ensureSyncedGuestClock (h hostRunner ) error {
169
+ d , err := guestClockDelta (h , time .Now ())
170
+ if err != nil {
171
+ glog .Warningf ("Unable to measure system clock delta: %v" , err )
172
+ return nil
173
+ }
174
+ if math .Abs (d .Seconds ()) < maxClockDesyncSeconds {
175
+ glog .Infof ("guest clock delta is within tolerance: %s" , d )
176
+ return nil
177
+ }
178
+ if err := adjustGuestClock (h , time .Now ()); err != nil {
179
+ return errors .Wrap (err , "adjusting system clock" )
180
+ }
181
+ return nil
182
+ }
183
+
184
+ // systemClockDelta returns the approximate difference between the host and guest system clock
185
+ // NOTE: This does not currently take into account ssh latency.
186
+ func guestClockDelta (h hostRunner , local time.Time ) (time.Duration , error ) {
187
+ out , err := h .RunSSHCommand ("date +%s.%N" )
188
+ if err != nil {
189
+ return 0 , errors .Wrap (err , "get clock" )
190
+ }
191
+ glog .Infof ("guest clock: %s" , out )
192
+ ns := strings .Split (strings .TrimSpace (out ), "." )
193
+ secs , err := strconv .ParseInt (strings .TrimSpace (ns [0 ]), 10 , 64 )
194
+ if err != nil {
195
+ return 0 , errors .Wrap (err , "atoi" )
196
+ }
197
+ nsecs , err := strconv .ParseInt (strings .TrimSpace (ns [1 ]), 10 , 64 )
198
+ if err != nil {
199
+ return 0 , errors .Wrap (err , "atoi" )
200
+ }
201
+ // NOTE: In a synced state, remote is a few hundred ms ahead of local
202
+ remote := time .Unix (secs , nsecs )
203
+ d := remote .Sub (local )
204
+ glog .Infof ("Guest: %s Remote: %s (delta=%s)" , remote , local , d )
205
+ return d , nil
206
+ }
207
+
208
+ // adjustSystemClock adjusts the guest system clock to be nearer to the host system clock
209
+ func adjustGuestClock (h hostRunner , t time.Time ) error {
210
+ out , err := h .RunSSHCommand (fmt .Sprintf ("sudo date -s @%d" , t .Unix ()))
211
+ glog .Infof ("clock set: %s (err=%v)" , out , err )
212
+ return err
213
+ }
214
+
153
215
// trySSHPowerOff runs the poweroff command on the guest VM to speed up deletion
154
216
func trySSHPowerOff (h * host.Host ) {
155
217
s , err := h .Driver .GetState ()
0 commit comments