Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement scheduled stop on windows #9689

Merged
merged 17 commits into from
Nov 18, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions cmd/minikube/cmd/stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,12 +99,13 @@ func runStop(cmd *cobra.Command, args []string) {
schedule.KillExisting(profilesToStop)

if scheduledStopDuration != 0 {
if runtime.GOOS == "windows" {
exit.Message(reason.Usage, "the --schedule flag is currently not supported on windows")
}
if err := schedule.Daemonize(profilesToStop, scheduledStopDuration); err != nil {
exit.Message(reason.DaemonizeError, "unable to daemonize: {{.err}}", out.V{"err": err.Error()})
}
// if OS is windows, scheduled stop is now being handled within minikube, so return
if runtime.GOOS == "windows" {
return
}
klog.Infof("sleeping %s before completing stop...", scheduledStopDuration.String())
time.Sleep(scheduledStopDuration)
}
Expand Down
1 change: 1 addition & 0 deletions deploy/iso/minikube-iso/package/Config.in
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,5 @@ menu "System tools"
source "$BR2_EXTERNAL_MINIKUBE_PATH/package/vbox-guest/Config.in"
source "$BR2_EXTERNAL_MINIKUBE_PATH/package/containerd-bin/Config.in"
source "$BR2_EXTERNAL_MINIKUBE_PATH/package/falco-module/Config.in"
source "$BR2_EXTERNAL_MINIKUBE_PATH/package/scheduled-stop/Config.in"
endmenu
3 changes: 3 additions & 0 deletions deploy/iso/minikube-iso/package/scheduled-stop/Config.in
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
config BR2_PACKAGE_SCHEDULED_STOP
bool "scheduled-stop"
default y
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash

set -x

echo "running scheduled stop ...";

echo "sleeping %$SLEEP seconds..."
sleep $SLEEP

echo "running poweroff..."
sudo systemctl poweroff
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[Unit]
Description=minikube scheduled stop

[Install]
WantedBy=multi-user.target

[Service]
Type=simple
User=root
ExecStart=/usr/sbin/minikube-scheduled-stop
EnvironmentFile=/var/lib/minikube/scheduled-stop/environment
23 changes: 23 additions & 0 deletions deploy/iso/minikube-iso/package/scheduled-stop/scheduled-stop.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
################################################################################
#
# minikube scheduled-stop
#
################################################################################

define SCHEDULED_STOP_INSTALL_INIT_SYSTEMD
$(INSTALL) -D -m 644 \
$(SCHEDULED_STOP_PKGDIR)/minikube-scheduled-stop.service \
$(TARGET_DIR)/usr/lib/systemd/system/minikube-scheduled-stop.service

mkdir -p $(TARGET_DIR)/etc/systemd/system/multi-user.target.wants
ln -fs /usr/lib/systemd/system/minikube-scheduled-stop.service \
$(TARGET_DIR)/etc/systemd/system/multi-user.target.wants/minikube-scheduled-stop.service
endef

define SCHEDULED_STOP_INSTALL_TARGET_CMDS
$(INSTALL) -Dm755 \
$(SCHEDULED_STOP_PKGDIR)/minikube-scheduled-stop \
$(TARGET_DIR)/usr/sbin/minikube-scheduled-stop
endef

$(eval $(generic-package))
7 changes: 7 additions & 0 deletions deploy/kicbase/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -130,6 +130,13 @@ COPY automount/minikube-automount.service /usr/lib/systemd/system/minikube-autom
RUN ln -fs /usr/lib/systemd/system/minikube-automount.service \
/etc/systemd/system/multi-user.target.wants/minikube-automount.service

# scheduled stop service
COPY scheduled-stop/minikube-scheduled-stop /var/lib/minikube/scheduled-stop/minikube-scheduled-stop
COPY scheduled-stop/minikube-scheduled-stop.service /usr/lib/systemd/system/minikube-scheduled-stop.service
RUN ln -fs /usr/lib/systemd/system/minikube-scheduled-stop.service \
/etc/systemd/system/multi-user.target.wants/minikube-scheduled-stop.service && \
chmod +x /var/lib/minikube/scheduled-stop/minikube-scheduled-stop

# disable non-docker runtimes by default
RUN systemctl disable containerd && systemctl disable crio && rm /etc/crictl.yaml
# enable docker which is default
Expand Down
11 changes: 11 additions & 0 deletions deploy/kicbase/scheduled-stop/minikube-scheduled-stop
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#!/bin/bash

set -x

echo "running scheduled stop ...";

echo "sleeping %$SLEEP seconds..."
sleep $SLEEP

echo "running poweroff..."
sudo systemctl poweroff
11 changes: 11 additions & 0 deletions deploy/kicbase/scheduled-stop/minikube-scheduled-stop.service
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
[Unit]
Description=minikube scheduled stop

[Install]
WantedBy=multi-user.target

[Service]
Type=simple
User=root
ExecStart=/var/lib/minikube/scheduled-stop/minikube-scheduled-stop
EnvironmentFile=/var/lib/minikube/scheduled-stop/environment
4 changes: 4 additions & 0 deletions pkg/minikube/constants/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,10 @@ const (
// TestDiskUsedEnv is used in integration tests for insufficient storage with 'minikube status'
TestDiskUsedEnv = "MINIKUBE_TEST_STORAGE_CAPACITY"

// scheduled stop constants
ScheduledStopEnvFile = "/var/lib/minikube/scheduled-stop/environment"
ScheduledStopSystemdService = "minikube-scheduled-stop"

// MinikubeExistingPrefix is used to save the original environment when executing docker-env
MinikubeExistingPrefix = "MINIKUBE_EXISTING_"

Expand Down
81 changes: 79 additions & 2 deletions pkg/minikube/schedule/daemonize_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,93 @@ package schedule

import (
"fmt"
"os/exec"
"time"

"github.com/pkg/errors"
"k8s.io/klog/v2"
"k8s.io/minikube/pkg/minikube/assets"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/minikube/machine"
"k8s.io/minikube/pkg/minikube/sysinit"
)

// KillExisting will kill existing scheduled stops
func KillExisting(profiles []string) {
klog.Errorf("not yet implemented for windows")
for _, profile := range profiles {
if err := killExisting(profile); err != nil {
klog.Errorf("error terminating scheduled stop for profile %s: %v", profile, err)
}
}
}

func killExisting(profile string) error {
klog.Infof("trying to kill existing schedule stop for profile %s...", profile)
api, err := machine.NewAPIClient()
if err != nil {
return errors.Wrapf(err, "getting api client for profile %s", profile)
}
h, err := api.Load(profile)
if err != nil {
return errors.Wrap(err, "Error loading existing host. Please try running [minikube delete], then run [minikube start] again.")
}
runner, err := machine.CommandRunner(h)
if err != nil {
return errors.Wrap(err, "getting command runner")
}
// restart scheduled stop service in container
sysManger := sysinit.New(runner)
if err := sysManger.Stop(constants.ScheduledStopSystemdService); err != nil {
priyawadhwa marked this conversation as resolved.
Show resolved Hide resolved
return errors.Wrapf(err, "stopping schedule-stop service for profile %s", profile)
}
return nil
}

// to daemonize on windows, we schedule the stop within minikube itself
// starting the minikube-scheduled-stop systemd service kicks off the scheduled stop
func daemonize(profiles []string, duration time.Duration) error {
return fmt.Errorf("not yet implemented for windows")
for _, profile := range profiles {
if err := startSystemdService(profile, duration); err != nil {
return errors.Wrapf(err, "implementing scheduled stop for %s", profile)
}
}
return nil
}

// to start the systemd service, we first have to tell the systemd service how long to sleep for
// before shutting down minikube from within
// we do this by settig the SLEEP environment variable in the environment file to the users
// requested duration
func startSystemdService(profile string, duration time.Duration) error {
// get ssh runner
klog.Infof("starting systemd service for profile %s...", profile)
api, err := machine.NewAPIClient()
if err != nil {
return errors.Wrapf(err, "getting api client for profile %s", profile)
}
h, err := api.Load(profile)
if err != nil {
return errors.Wrap(err, "Error loading existing host. Please try running [minikube delete], then run [minikube start] again.")
}
runner, err := machine.CommandRunner(h)
if err != nil {
return errors.Wrap(err, "getting command runner")
}
if rr, err := runner.RunCmd(exec.Command("sudo", "mkdir", "-p", "/var/lib/minikube/scheduled-stop")); err != nil {
return errors.Wrapf(err, "creating dirs: %v", rr.Output())
}
// update environment file to include duration
if err := runner.Copy(environmentFile(duration)); err != nil {
return errors.Wrap(err, "copying scheduled stop env file")
}
// restart scheduled stop service in container
sysManger := sysinit.New(runner)
return sysManger.Restart(constants.ScheduledStopSystemdService)
}

// return the contents of the environment file for minikube-scheduled-stop systemd service
// should be of the format SLEEP=<scheduled stop requested by user in seconds>
func environmentFile(duration time.Duration) assets.CopyableFile {
contents := []byte(fmt.Sprintf("SLEEP=%v", duration.Seconds()))
return assets.NewMemoryAssetTarget(contents, constants.ScheduledStopEnvFile, "0644")
}
14 changes: 11 additions & 3 deletions pkg/minikube/schedule/schedule.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,19 @@ func Daemonize(profiles []string, duration time.Duration) error {
continue
}
daemonizeProfiles = append(daemonizeProfiles, p)
}

if err := daemonize(daemonizeProfiles, duration); err != nil {
return errors.Wrap(err, "daemonizing")
}

// save scheduled stop config if daemonize was successful
for _, d := range daemonizeProfiles {
_, cc := mustload.Partial(d)
cc.ScheduledStop = scheduledStop
if err := config.SaveProfile(p, cc); err != nil {
if err := config.SaveProfile(d, cc); err != nil {
return errors.Wrap(err, "saving profile")
}
}

return daemonize(daemonizeProfiles, duration)
return nil
}
74 changes: 59 additions & 15 deletions test/integration/scheduled_stop_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,19 +24,58 @@ import (
"io/ioutil"
"os"
"os/exec"
"runtime"
"strconv"
"strings"
"syscall"
"testing"
"time"

"github.com/docker/machine/libmachine/state"
"k8s.io/minikube/pkg/minikube/constants"
"k8s.io/minikube/pkg/minikube/localpath"
"k8s.io/minikube/pkg/util/retry"
)

func TestScheduledStop(t *testing.T) {
func TestScheduledStopWindows(t *testing.T) {
if runtime.GOOS != "windows" {
t.Skip("test only runs on windows")
}
if NoneDriver() {
t.Skip("--schedule does not apply to none driver ")
t.Skip("--schedule does not work with the none driver")
}
profile := UniqueProfileName("scheduled-stop")
ctx, cancel := context.WithTimeout(context.Background(), Minutes(5))
defer CleanupWithLogs(t, profile, cancel)
startMinikube(ctx, t, profile)

// schedule a stop for 5m from now
scheduledStopMinikube(ctx, t, profile, "5m")

// make sure the systemd service is running
rr, err := Run(t, exec.CommandContext(ctx, Target(), []string{"ssh", "-p", profile, "--", "sudo", "systemctl", "show", constants.ScheduledStopSystemdService, "--no-page"}...))
if err != nil {
t.Fatalf("getting minikube-scheduled-stop status: %v\n%s", err, rr.Output())
}
if !strings.Contains(rr.Output(), "ActiveState=active") {
t.Fatalf("minikube-scheduled-stop is not running: %v", rr.Output())
}

// reschedule stop for 5 seconds from now
scheduledStopMinikube(ctx, t, profile, "5s")

// sleep for 5 seconds
time.Sleep(5 * time.Second)
// make sure minikube status is "Stopped"
ensureMinikubeStatusStopped(ctx, t, profile)
}

func TestScheduledStopUnix(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("test only runs on unix")
}
if NoneDriver() {
t.Skip("--schedule does not work with the none driver")
}
profile := UniqueProfileName("scheduled-stop")
ctx, cancel := context.WithTimeout(context.Background(), Minutes(5))
Expand All @@ -56,19 +95,8 @@ func TestScheduledStop(t *testing.T) {
t.Fatalf("process %v running but should have been killed on reschedule of stop", pid)
}
checkPID(t, profile)
// wait allotted time to make sure minikube status is "Stopped"
checkStatus := func() error {
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(10*time.Second))
defer cancel()
got := Status(ctx, t, Target(), profile, "Host", profile)
if got != state.Stopped.String() {
return fmt.Errorf("expected post-stop host status to be -%q- but got *%q*", state.Stopped, got)
}
return nil
}
if err := retry.Expo(checkStatus, time.Second, time.Minute); err != nil {
t.Fatalf("error %v", err)
}
// make sure minikube status is "Stopped"
ensureMinikubeStatusStopped(ctx, t, profile)
}

func startMinikube(ctx context.Context, t *testing.T, profile string) {
Expand Down Expand Up @@ -116,3 +144,19 @@ func processRunning(t *testing.T, pid string) bool {
t.Log("signal error was: ", err)
return err == nil
}

func ensureMinikubeStatusStopped(ctx context.Context, t *testing.T, profile string) {
// wait allotted time to make sure minikube status is "Stopped"
checkStatus := func() error {
ctx, cancel := context.WithDeadline(ctx, time.Now().Add(10*time.Second))
defer cancel()
got := Status(ctx, t, Target(), profile, "Host", profile)
if got != state.Stopped.String() {
return fmt.Errorf("expected post-stop host status to be -%q- but got *%q*", state.Stopped, got)
}
return nil
}
if err := retry.Expo(checkStatus, time.Second, time.Minute); err != nil {
t.Fatalf("error %v", err)
}
}