Skip to content

Commit 1d37a91

Browse files
author
priyawadhwa
authored
Merge pull request #9689 from priyawadhwa/ss-windows
Implement scheduled stop on windows
2 parents 2179b04 + 4cd7463 commit 1d37a91

File tree

13 files changed

+235
-23
lines changed

13 files changed

+235
-23
lines changed

cmd/minikube/cmd/stop.go

+4-3
Original file line numberDiff line numberDiff line change
@@ -99,12 +99,13 @@ func runStop(cmd *cobra.Command, args []string) {
9999
schedule.KillExisting(profilesToStop)
100100

101101
if scheduledStopDuration != 0 {
102-
if runtime.GOOS == "windows" {
103-
exit.Message(reason.Usage, "the --schedule flag is currently not supported on windows")
104-
}
105102
if err := schedule.Daemonize(profilesToStop, scheduledStopDuration); err != nil {
106103
exit.Message(reason.DaemonizeError, "unable to daemonize: {{.err}}", out.V{"err": err.Error()})
107104
}
105+
// if OS is windows, scheduled stop is now being handled within minikube, so return
106+
if runtime.GOOS == "windows" {
107+
return
108+
}
108109
klog.Infof("sleeping %s before completing stop...", scheduledStopDuration.String())
109110
time.Sleep(scheduledStopDuration)
110111
}

deploy/iso/minikube-iso/package/Config.in

+1
Original file line numberDiff line numberDiff line change
@@ -14,4 +14,5 @@ menu "System tools"
1414
source "$BR2_EXTERNAL_MINIKUBE_PATH/package/vbox-guest/Config.in"
1515
source "$BR2_EXTERNAL_MINIKUBE_PATH/package/containerd-bin/Config.in"
1616
source "$BR2_EXTERNAL_MINIKUBE_PATH/package/falco-module/Config.in"
17+
source "$BR2_EXTERNAL_MINIKUBE_PATH/package/scheduled-stop/Config.in"
1718
endmenu
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
config BR2_PACKAGE_SCHEDULED_STOP
2+
bool "scheduled-stop"
3+
default y
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
3+
set -x
4+
5+
echo "running scheduled stop ...";
6+
7+
echo "sleeping %$SLEEP seconds..."
8+
sleep $SLEEP
9+
10+
echo "running poweroff..."
11+
sudo systemctl poweroff
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[Unit]
2+
Description=minikube scheduled stop
3+
4+
[Install]
5+
WantedBy=multi-user.target
6+
7+
[Service]
8+
Type=simple
9+
User=root
10+
ExecStart=/usr/sbin/minikube-scheduled-stop
11+
EnvironmentFile=/var/lib/minikube/scheduled-stop/environment
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
################################################################################
2+
#
3+
# minikube scheduled-stop
4+
#
5+
################################################################################
6+
7+
define SCHEDULED_STOP_INSTALL_INIT_SYSTEMD
8+
$(INSTALL) -D -m 644 \
9+
$(SCHEDULED_STOP_PKGDIR)/minikube-scheduled-stop.service \
10+
$(TARGET_DIR)/usr/lib/systemd/system/minikube-scheduled-stop.service
11+
12+
mkdir -p $(TARGET_DIR)/etc/systemd/system/multi-user.target.wants
13+
ln -fs /usr/lib/systemd/system/minikube-scheduled-stop.service \
14+
$(TARGET_DIR)/etc/systemd/system/multi-user.target.wants/minikube-scheduled-stop.service
15+
endef
16+
17+
define SCHEDULED_STOP_INSTALL_TARGET_CMDS
18+
$(INSTALL) -Dm755 \
19+
$(SCHEDULED_STOP_PKGDIR)/minikube-scheduled-stop \
20+
$(TARGET_DIR)/usr/sbin/minikube-scheduled-stop
21+
endef
22+
23+
$(eval $(generic-package))

deploy/kicbase/Dockerfile

+7
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,13 @@ COPY automount/minikube-automount.service /usr/lib/systemd/system/minikube-autom
130130
RUN ln -fs /usr/lib/systemd/system/minikube-automount.service \
131131
/etc/systemd/system/multi-user.target.wants/minikube-automount.service
132132

133+
# scheduled stop service
134+
COPY scheduled-stop/minikube-scheduled-stop /var/lib/minikube/scheduled-stop/minikube-scheduled-stop
135+
COPY scheduled-stop/minikube-scheduled-stop.service /usr/lib/systemd/system/minikube-scheduled-stop.service
136+
RUN ln -fs /usr/lib/systemd/system/minikube-scheduled-stop.service \
137+
/etc/systemd/system/multi-user.target.wants/minikube-scheduled-stop.service && \
138+
chmod +x /var/lib/minikube/scheduled-stop/minikube-scheduled-stop
139+
133140
# disable non-docker runtimes by default
134141
RUN systemctl disable containerd && systemctl disable crio && rm /etc/crictl.yaml
135142
# enable docker which is default
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
#!/bin/bash
2+
3+
set -x
4+
5+
echo "running scheduled stop ...";
6+
7+
echo "sleeping %$SLEEP seconds..."
8+
sleep $SLEEP
9+
10+
echo "running poweroff..."
11+
sudo systemctl poweroff
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
[Unit]
2+
Description=minikube scheduled stop
3+
4+
[Install]
5+
WantedBy=multi-user.target
6+
7+
[Service]
8+
Type=simple
9+
User=root
10+
ExecStart=/var/lib/minikube/scheduled-stop/minikube-scheduled-stop
11+
EnvironmentFile=/var/lib/minikube/scheduled-stop/environment

pkg/minikube/constants/constants.go

+4
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,10 @@ const (
8080
// TestDiskUsedEnv is used in integration tests for insufficient storage with 'minikube status'
8181
TestDiskUsedEnv = "MINIKUBE_TEST_STORAGE_CAPACITY"
8282

83+
// scheduled stop constants
84+
ScheduledStopEnvFile = "/var/lib/minikube/scheduled-stop/environment"
85+
ScheduledStopSystemdService = "minikube-scheduled-stop"
86+
8387
// MinikubeExistingPrefix is used to save the original environment when executing docker-env
8488
MinikubeExistingPrefix = "MINIKUBE_EXISTING_"
8589

pkg/minikube/schedule/daemonize_windows.go

+79-2
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,93 @@ package schedule
2020

2121
import (
2222
"fmt"
23+
"os/exec"
2324
"time"
2425

26+
"github.com/pkg/errors"
2527
"k8s.io/klog/v2"
28+
"k8s.io/minikube/pkg/minikube/assets"
29+
"k8s.io/minikube/pkg/minikube/constants"
30+
"k8s.io/minikube/pkg/minikube/machine"
31+
"k8s.io/minikube/pkg/minikube/sysinit"
2632
)
2733

2834
// KillExisting will kill existing scheduled stops
2935
func KillExisting(profiles []string) {
30-
klog.Errorf("not yet implemented for windows")
36+
for _, profile := range profiles {
37+
if err := killExisting(profile); err != nil {
38+
klog.Errorf("error terminating scheduled stop for profile %s: %v", profile, err)
39+
}
40+
}
3141
}
3242

43+
func killExisting(profile string) error {
44+
klog.Infof("trying to kill existing schedule stop for profile %s...", profile)
45+
api, err := machine.NewAPIClient()
46+
if err != nil {
47+
return errors.Wrapf(err, "getting api client for profile %s", profile)
48+
}
49+
h, err := api.Load(profile)
50+
if err != nil {
51+
return errors.Wrap(err, "Error loading existing host. Please try running [minikube delete], then run [minikube start] again.")
52+
}
53+
runner, err := machine.CommandRunner(h)
54+
if err != nil {
55+
return errors.Wrap(err, "getting command runner")
56+
}
57+
// restart scheduled stop service in container
58+
sysManger := sysinit.New(runner)
59+
if err := sysManger.Stop(constants.ScheduledStopSystemdService); err != nil {
60+
return errors.Wrapf(err, "stopping schedule-stop service for profile %s", profile)
61+
}
62+
return nil
63+
}
64+
65+
// to daemonize on windows, we schedule the stop within minikube itself
66+
// starting the minikube-scheduled-stop systemd service kicks off the scheduled stop
3367
func daemonize(profiles []string, duration time.Duration) error {
34-
return fmt.Errorf("not yet implemented for windows")
68+
for _, profile := range profiles {
69+
if err := startSystemdService(profile, duration); err != nil {
70+
return errors.Wrapf(err, "implementing scheduled stop for %s", profile)
71+
}
72+
}
73+
return nil
74+
}
75+
76+
// to start the systemd service, we first have to tell the systemd service how long to sleep for
77+
// before shutting down minikube from within
78+
// we do this by settig the SLEEP environment variable in the environment file to the users
79+
// requested duration
80+
func startSystemdService(profile string, duration time.Duration) error {
81+
// get ssh runner
82+
klog.Infof("starting systemd service for profile %s...", profile)
83+
api, err := machine.NewAPIClient()
84+
if err != nil {
85+
return errors.Wrapf(err, "getting api client for profile %s", profile)
86+
}
87+
h, err := api.Load(profile)
88+
if err != nil {
89+
return errors.Wrap(err, "Error loading existing host. Please try running [minikube delete], then run [minikube start] again.")
90+
}
91+
runner, err := machine.CommandRunner(h)
92+
if err != nil {
93+
return errors.Wrap(err, "getting command runner")
94+
}
95+
if rr, err := runner.RunCmd(exec.Command("sudo", "mkdir", "-p", "/var/lib/minikube/scheduled-stop")); err != nil {
96+
return errors.Wrapf(err, "creating dirs: %v", rr.Output())
97+
}
98+
// update environment file to include duration
99+
if err := runner.Copy(environmentFile(duration)); err != nil {
100+
return errors.Wrap(err, "copying scheduled stop env file")
101+
}
102+
// restart scheduled stop service in container
103+
sysManger := sysinit.New(runner)
104+
return sysManger.Restart(constants.ScheduledStopSystemdService)
105+
}
106+
107+
// return the contents of the environment file for minikube-scheduled-stop systemd service
108+
// should be of the format SLEEP=<scheduled stop requested by user in seconds>
109+
func environmentFile(duration time.Duration) assets.CopyableFile {
110+
contents := []byte(fmt.Sprintf("SLEEP=%v", duration.Seconds()))
111+
return assets.NewMemoryAssetTarget(contents, constants.ScheduledStopEnvFile, "0644")
35112
}

pkg/minikube/schedule/schedule.go

+11-3
Original file line numberDiff line numberDiff line change
@@ -41,11 +41,19 @@ func Daemonize(profiles []string, duration time.Duration) error {
4141
continue
4242
}
4343
daemonizeProfiles = append(daemonizeProfiles, p)
44+
}
45+
46+
if err := daemonize(daemonizeProfiles, duration); err != nil {
47+
return errors.Wrap(err, "daemonizing")
48+
}
49+
50+
// save scheduled stop config if daemonize was successful
51+
for _, d := range daemonizeProfiles {
52+
_, cc := mustload.Partial(d)
4453
cc.ScheduledStop = scheduledStop
45-
if err := config.SaveProfile(p, cc); err != nil {
54+
if err := config.SaveProfile(d, cc); err != nil {
4655
return errors.Wrap(err, "saving profile")
4756
}
4857
}
49-
50-
return daemonize(daemonizeProfiles, duration)
58+
return nil
5159
}

test/integration/scheduled_stop_test.go

+59-15
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,58 @@ import (
2424
"io/ioutil"
2525
"os"
2626
"os/exec"
27+
"runtime"
2728
"strconv"
29+
"strings"
2830
"syscall"
2931
"testing"
3032
"time"
3133

3234
"github.com/docker/machine/libmachine/state"
35+
"k8s.io/minikube/pkg/minikube/constants"
3336
"k8s.io/minikube/pkg/minikube/localpath"
3437
"k8s.io/minikube/pkg/util/retry"
3538
)
3639

37-
func TestScheduledStop(t *testing.T) {
40+
func TestScheduledStopWindows(t *testing.T) {
41+
if runtime.GOOS != "windows" {
42+
t.Skip("test only runs on windows")
43+
}
3844
if NoneDriver() {
39-
t.Skip("--schedule does not apply to none driver ")
45+
t.Skip("--schedule does not work with the none driver")
46+
}
47+
profile := UniqueProfileName("scheduled-stop")
48+
ctx, cancel := context.WithTimeout(context.Background(), Minutes(5))
49+
defer CleanupWithLogs(t, profile, cancel)
50+
startMinikube(ctx, t, profile)
51+
52+
// schedule a stop for 5m from now
53+
scheduledStopMinikube(ctx, t, profile, "5m")
54+
55+
// make sure the systemd service is running
56+
rr, err := Run(t, exec.CommandContext(ctx, Target(), []string{"ssh", "-p", profile, "--", "sudo", "systemctl", "show", constants.ScheduledStopSystemdService, "--no-page"}...))
57+
if err != nil {
58+
t.Fatalf("getting minikube-scheduled-stop status: %v\n%s", err, rr.Output())
59+
}
60+
if !strings.Contains(rr.Output(), "ActiveState=active") {
61+
t.Fatalf("minikube-scheduled-stop is not running: %v", rr.Output())
62+
}
63+
64+
// reschedule stop for 5 seconds from now
65+
scheduledStopMinikube(ctx, t, profile, "5s")
66+
67+
// sleep for 5 seconds
68+
time.Sleep(5 * time.Second)
69+
// make sure minikube status is "Stopped"
70+
ensureMinikubeStatusStopped(ctx, t, profile)
71+
}
72+
73+
func TestScheduledStopUnix(t *testing.T) {
74+
if runtime.GOOS == "windows" {
75+
t.Skip("test only runs on unix")
76+
}
77+
if NoneDriver() {
78+
t.Skip("--schedule does not work with the none driver")
4079
}
4180
profile := UniqueProfileName("scheduled-stop")
4281
ctx, cancel := context.WithTimeout(context.Background(), Minutes(5))
@@ -56,19 +95,8 @@ func TestScheduledStop(t *testing.T) {
5695
t.Fatalf("process %v running but should have been killed on reschedule of stop", pid)
5796
}
5897
checkPID(t, profile)
59-
// wait allotted time to make sure minikube status is "Stopped"
60-
checkStatus := func() error {
61-
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(10*time.Second))
62-
defer cancel()
63-
got := Status(ctx, t, Target(), profile, "Host", profile)
64-
if got != state.Stopped.String() {
65-
return fmt.Errorf("expected post-stop host status to be -%q- but got *%q*", state.Stopped, got)
66-
}
67-
return nil
68-
}
69-
if err := retry.Expo(checkStatus, time.Second, time.Minute); err != nil {
70-
t.Fatalf("error %v", err)
71-
}
98+
// make sure minikube status is "Stopped"
99+
ensureMinikubeStatusStopped(ctx, t, profile)
72100
}
73101

74102
func startMinikube(ctx context.Context, t *testing.T, profile string) {
@@ -116,3 +144,19 @@ func processRunning(t *testing.T, pid string) bool {
116144
t.Log("signal error was: ", err)
117145
return err == nil
118146
}
147+
148+
func ensureMinikubeStatusStopped(ctx context.Context, t *testing.T, profile string) {
149+
// wait allotted time to make sure minikube status is "Stopped"
150+
checkStatus := func() error {
151+
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(10*time.Second))
152+
defer cancel()
153+
got := Status(ctx, t, Target(), profile, "Host", profile)
154+
if got != state.Stopped.String() {
155+
return fmt.Errorf("expected post-stop host status to be -%q- but got *%q*", state.Stopped, got)
156+
}
157+
return nil
158+
}
159+
if err := retry.Expo(checkStatus, time.Second, time.Minute); err != nil {
160+
t.Fatalf("error %v", err)
161+
}
162+
}

0 commit comments

Comments
 (0)