Skip to content

Commit 56ec0ae

Browse files
authored
Merge pull request #4283 from tstromberg/timesunk
Sync guest system clock if desynchronized from host
2 parents db37c03 + 0afdce2 commit 56ec0ae

File tree

2 files changed

+85
-4
lines changed

2 files changed

+85
-4
lines changed

pkg/minikube/cluster/cluster.go

+66-4
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,12 @@ import (
2020
"encoding/json"
2121
"flag"
2222
"fmt"
23+
"math"
2324
"net"
2425
"os/exec"
2526
"regexp"
27+
"strconv"
28+
"strings"
2629
"time"
2730

2831
"github.com/docker/machine/libmachine"
@@ -44,6 +47,17 @@ import (
4447
pkgutil "k8s.io/minikube/pkg/util"
4548
)
4649

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+
4761
//This init function is used to set the logtostderr variable to false so that INFO level log info does not clutter the CLI
4862
//INFO lvl logging is displayed due to the kubernetes api calling flag.Set("logtostderr", "true") in its init()
4963
//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)
117131
e := engineOptions(config)
118132
glog.Infof("engine options: %+v", e)
119133

120-
err = waitForSSHAccess(h, e)
134+
err = configureHost(h, e)
121135
if err != nil {
122136
return nil, err
123137
}
124-
125138
return h, nil
126139
}
127140

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 {
130143
// Slightly counter-intuitive, but this is what DetectProvisioner & ConfigureAuth block on.
131144
console.OutStyle("waiting", "Waiting for SSH access ...")
132145

@@ -145,11 +158,60 @@ func waitForSSHAccess(h *host.Host, e *engine.Options) error {
145158
if err := h.ConfigureAuth(); err != nil {
146159
return &util.RetriableError{Err: errors.Wrap(err, "Error configuring auth on host")}
147160
}
161+
return ensureSyncedGuestClock(h)
148162
}
149163

150164
return nil
151165
}
152166

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+
153215
// trySSHPowerOff runs the poweroff command on the guest VM to speed up deletion
154216
func trySSHPowerOff(h *host.Host) {
155217
s, err := h.Driver.GetState()

pkg/minikube/cluster/cluster_test.go

+19
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,10 @@ limitations under the License.
1717
package cluster
1818

1919
import (
20+
"fmt"
2021
"os"
2122
"testing"
23+
"time"
2224

2325
"github.com/docker/machine/libmachine/drivers"
2426
"github.com/docker/machine/libmachine/host"
@@ -382,3 +384,20 @@ func TestCreateSSHShell(t *testing.T) {
382384
t.Fatalf("Expected ssh session to be run")
383385
}
384386
}
387+
388+
func TestGuestClockDelta(t *testing.T) {
389+
local := time.Now()
390+
h := tests.NewMockHost()
391+
// Truncate remote clock so that it is between 0 and 1 second behind
392+
h.CommandOutput["date +%s.%N"] = fmt.Sprintf("%d.0000", local.Unix())
393+
got, err := guestClockDelta(h, local)
394+
if err != nil {
395+
t.Fatalf("guestClock: %v", err)
396+
}
397+
if got > (0 * time.Second) {
398+
t.Errorf("unexpected positive delta (remote should be behind): %s", got)
399+
}
400+
if got < (-1 * time.Second) {
401+
t.Errorf("unexpectedly negative delta (remote too far behind): %s", got)
402+
}
403+
}

0 commit comments

Comments
 (0)