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

Ensure only one instance of tunnel process runs #15834

Merged
merged 13 commits into from
Mar 24, 2023
Merged
28 changes: 28 additions & 0 deletions cmd/minikube/cmd/tunnel.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,14 @@ package cmd

import (
"context"
"fmt"
"os"
"os/signal"
"path/filepath"
"runtime"
"strconv"

"github.com/juju/fslock"
"github.com/spf13/cobra"

"k8s.io/klog/v2"
Expand All @@ -44,6 +46,7 @@ import (

var cleanup bool
var bindAddress string
var lockHandle *fslock.Lock

// tunnelCmd represents the tunnel command
var tunnelCmd = &cobra.Command{
Expand Down Expand Up @@ -73,6 +76,9 @@ var tunnelCmd = &cobra.Command{
}
}

mustLockOrExit()
defer cleanupLock()

// Tunnel uses the k8s clientset to query the API server for services in the LoadBalancerEmulator.
// We define the tunnel and minikube error free if the API server responds within a second.
// This also contributes to better UX, the tunnel status check can happen every second and
Expand Down Expand Up @@ -116,6 +122,28 @@ var tunnelCmd = &cobra.Command{
},
}

func cleanupLock() {
if lockHandle != nil {
err := lockHandle.Unlock()
if err != nil {
out.Styled(style.Warning, fmt.Sprintf("failed to release lock during cleanup: %v", err))
}
}
}

func mustLockOrExit() {
tunnelLockPath := filepath.Join(localpath.MiniPath(), ".tunnel_lock")

lockHandle = fslock.New(tunnelLockPath)
err := lockHandle.TryLock()
if err == fslock.ErrLocked {
exit.Message(reason.SvcTunnelAlreadyRunning, "Another tunnel process is already running, terminate the existing instance to start a new one")
}
if err != nil {
exit.Error(reason.SvcTunnelStart, "failed to acquire lock due to unexpected error", err)
}
}

func outputTunnelStarted() {
out.Styled(style.Success, "Tunnel successfully started")
out.Ln("")
Expand Down
2 changes: 2 additions & 0 deletions pkg/minikube/reason/reason.go
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,8 @@ var (
SvcTunnelStart = Kind{ID: "SVC_TUNNEL_START", ExitCode: ExSvcError}
// minikube could not stop an active tunnel
SvcTunnelStop = Kind{ID: "SVC_TUNNEL_STOP", ExitCode: ExSvcError}
// another instance of tunnel already running
SvcTunnelAlreadyRunning = Kind{ID: "TUNNEL_ALREADY_RUNNING", ExitCode: ExSvcConflict, Style: style.Usage}
// minikube was unable to access the service url
SvcURLTimeout = Kind{ID: "SVC_URL_TIMEOUT", ExitCode: ExSvcTimeout}
// minikube couldn't find the specified service in the specified namespace
Expand Down
3 changes: 3 additions & 0 deletions site/content/en/docs/contrib/errorcodes.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,9 @@ minikube failed to start a tunnel
"SVC_TUNNEL_STOP" (Exit code ExSvcError)
minikube could not stop an active tunnel

"TUNNEL_ALREADY_RUNNING" (Exit code ExSvcConflict)
another instance of tunnel already running

"SVC_URL_TIMEOUT" (Exit code ExSvcTimeout)
minikube was unable to access the service url

Expand Down
3 changes: 3 additions & 0 deletions site/content/en/docs/contrib/tests.en.md
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,9 @@ makes sure the minikube tunnel command works as expected
#### validateTunnelStart
starts `minikube tunnel`

#### validateNoSecondTunnel
ensures only 1 tunnel can run simultaneously

#### validateServiceStable
starts nginx pod, nginx service and waits nginx having loadbalancer ingress IP

Expand Down
65 changes: 65 additions & 0 deletions test/integration/functional_test_tunnel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import (
"k8s.io/minikube/pkg/kapi"
"k8s.io/minikube/pkg/minikube/config"
"k8s.io/minikube/pkg/minikube/detect"
"k8s.io/minikube/pkg/minikube/reason"
"k8s.io/minikube/pkg/util"
"k8s.io/minikube/pkg/util/retry"
)
Expand All @@ -61,6 +62,7 @@ func validateTunnelCmd(ctx context.Context, t *testing.T, profile string) {
name string
validator validateFunc
}{
{"RunSecondTunnel", validateNoSecondTunnel}, // Ensure no two tunnels run simultaneously
{"StartTunnel", validateTunnelStart}, // Start tunnel
{"WaitService", validateServiceStable}, // Wait for service is stable
{"AccessDirect", validateAccessDirect}, // Access test for loadbalancer IP
Expand Down Expand Up @@ -131,6 +133,69 @@ func validateTunnelStart(ctx context.Context, t *testing.T, profile string) {
tunnelSession = *ss
}

// validateNoSecondTunnel ensures only 1 tunnel can run simultaneously
func validateNoSecondTunnel(ctx context.Context, t *testing.T, profile string) {
spowelljr marked this conversation as resolved.
Show resolved Hide resolved
checkRoutePassword(t)

type SessInfo struct {
Stdout string
Stderr string
ExitCode int
}

sessCh := make(chan SessInfo)
sessions := make([]*StartSession, 2)

var runTunnel = func(idx int) {
args := []string{"-p", profile, "tunnel", "--alsologtostderr"}

ctx2, cancel := context.WithTimeout(ctx, Seconds(15))
defer cancel()
session, err := Start(t, exec.CommandContext(ctx2, Target(), args...))
if err != nil {
t.Errorf("failed to start tunnel: %v", err)
}
sessions[idx] = session

stderr, err := io.ReadAll(session.Stderr)
if err != nil {
t.Logf("Failed to read stderr: %v", err)
}
stdout, err := io.ReadAll(session.Stdout)
if err != nil {
t.Logf("Failed to read stdout: %v", err)
}

exitCode := 0
err = session.cmd.Wait()
if err != nil {
if exErr, ok := err.(*exec.ExitError); !ok {
t.Logf("failed to coerce exit error: %v", err)
exitCode = -1
} else {
exitCode = exErr.ExitCode()
}
}

sessCh <- SessInfo{Stdout: string(stdout), Stderr: string(stderr), ExitCode: exitCode}
spowelljr marked this conversation as resolved.
Show resolved Hide resolved
}

// One of the two processes must fail to acquire lock and die. This should be the first process to die.
go runTunnel(0)
go runTunnel(1)

sessInfo := <-sessCh

if sessInfo.ExitCode != reason.SvcTunnelAlreadyRunning.ExitCode {
t.Errorf("tunnel command failed with unexpected error: exit code %d. stderr: %s\n stdout: %s", sessInfo.ExitCode, sessInfo.Stderr, sessInfo.Stdout)
}

for _, sess := range sessions {
sess.Stop(t)
}
<-sessCh
}

// validateServiceStable starts nginx pod, nginx service and waits nginx having loadbalancer ingress IP
func validateServiceStable(ctx context.Context, t *testing.T, profile string) {
if detect.GithubActionRunner() && runtime.GOOS == "darwin" {
Expand Down
2 changes: 2 additions & 0 deletions translations/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"Another hypervisor, such as VirtualBox, is conflicting with KVM. Please stop the other hypervisor, or use --driver to switch to it.": "Ein anderer Hypervisor (wie z.B. VirtualBox) steht im Konflikt mit KVM. Bitte stoppen Sie den anderen Hypervisor oder verwenden Sie --driver um den Hypervisor zu wechseln.",
"Another minikube instance is downloading dependencies... ": "Eine andere Minikube-Instanz lädt Abhängigkeiten herunter... ",
"Another program is using a file required by minikube. If you are using Hyper-V, try stopping the minikube VM from within the Hyper-V manager": "Ein anderes Programm benutzt eine Datei, die Minikube benötigt. Wenn Sie Hyper-V verwenden, versuchen Sie die minikube VM aus dem Hyper-V Manager heraus zu stoppen",
"Another tunnel process is already running, terminate the existing instance to start a new one": "",
"At least needs control plane nodes to enable addon": "Benötige mindestens Control Plane Nodes um das Addon zu aktivieren",
"Auto-pause is already enabled.": "",
"Automatically selected the {{.driver}} driver": "Treiber {{.driver}} wurde automatisch ausgewählt",
Expand Down Expand Up @@ -957,6 +958,7 @@
"error: --output must be 'text', 'yaml' or 'json'": "Fehler: --output muss entweder 'text', 'yaml' oder 'json' sein",
"error: --output must be 'yaml' or 'json'": "Fehler: --output muss entweder 'yaml' oder 'json' sein",
"experimental": "experimentell",
"failed to acquire lock due to unexpected error": "",
"failed to add node": "Hinzufügen des Nodes fehlgeschlagen",
"failed to open browser: {{.error}}": "Öffnen des Browsers fehlgeschlagen: {{.error}}",
"failed to save config": "Speichern der Konfiguration fehlgeschlagen",
Expand Down
2 changes: 2 additions & 0 deletions translations/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"Another hypervisor, such as VirtualBox, is conflicting with KVM. Please stop the other hypervisor, or use --driver to switch to it.": "Otro hipervisor, por ejemplo VirtualBox, está en conflicto con KVM. Por favor detén el otro hipervisor, o usa --driver para cambiarlo.",
"Another minikube instance is downloading dependencies... ": "Otra instancia de minikube esta descargando dependencias...",
"Another program is using a file required by minikube. If you are using Hyper-V, try stopping the minikube VM from within the Hyper-V manager": "Otro programa está usando un archivo requerido por minikube. Si estas usando Hyper-V, intenta detener la máquina virtual de minikube desde el administrador de Hyper-V",
"Another tunnel process is already running, terminate the existing instance to start a new one": "",
"At least needs control plane nodes to enable addon": "Al menos se necesita un nodo de plano de control para habilitar el addon",
"Auto-pause is already enabled.": "",
"Automatically selected the {{.driver}} driver": "Controlador {{.driver}} seleccionado automáticamente",
Expand Down Expand Up @@ -954,6 +955,7 @@
"error: --output must be 'text', 'yaml' or 'json'": "",
"error: --output must be 'yaml' or 'json'": "",
"experimental": "",
"failed to acquire lock due to unexpected error": "",
"failed to add node": "",
"failed to open browser: {{.error}}": "",
"failed to save config": "",
Expand Down
2 changes: 2 additions & 0 deletions translations/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"Another hypervisor, such as VirtualBox, is conflicting with KVM. Please stop the other hypervisor, or use --driver to switch to it.": "Un autre hyperviseur, tel que VirtualBox, est en conflit avec KVM. Veuillez arrêter l'autre hyperviseur ou utiliser --driver pour y basculer.",
"Another minikube instance is downloading dependencies... ": "Une autre instance minikube télécharge des dépendances",
"Another program is using a file required by minikube. If you are using Hyper-V, try stopping the minikube VM from within the Hyper-V manager": "Un autre programme utilise un fichier requis par minikube. Si vous utilisez Hyper-V, essayez d'arrêter la machine virtuelle minikube à partir du gestionnaire Hyper-V",
"Another tunnel process is already running, terminate the existing instance to start a new one": "",
"At least needs control plane nodes to enable addon": "Nécessite au moins des nœuds de plan de contrôle pour activer le module",
"Auto-pause is already enabled.": "La pause automatique est déjà activée.",
"Automatically selected the {{.driver}} driver": "Choix automatique du pilote {{.driver}}",
Expand Down Expand Up @@ -936,6 +937,7 @@
"error: --output must be 'text', 'yaml' or 'json'": "erreur : --output doit être 'text', 'yaml' ou 'json'",
"error: --output must be 'yaml' or 'json'": "erreur : --output doit être 'yaml' ou 'json'",
"experimental": "expérimental",
"failed to acquire lock due to unexpected error": "",
"failed to add node": "échec de l'ajout du nœud",
"failed to open browser: {{.error}}": "échec de l'ouverture du navigateur : {{.error}}",
"failed to save config": "échec de l'enregistrement de la configuration",
Expand Down
2 changes: 2 additions & 0 deletions translations/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@
"Another hypervisor, such as VirtualBox, is conflicting with KVM. Please stop the other hypervisor, or use --driver to switch to it.": "VirtualBox などの別のハイパーバイザーが、KVM と競合しています。他のハイパーバイザーを停止するか、--driver を使用して切り替えてください。",
"Another minikube instance is downloading dependencies... ": "別の minikube のインスタンスが、依存関係をダウンロードしています... ",
"Another program is using a file required by minikube. If you are using Hyper-V, try stopping the minikube VM from within the Hyper-V manager": "別のプログラムが、minikube に必要なファイルを使用しています。Hyper-V を使用している場合は、Hyper-V マネージャー内から minikube VM を停止してみてください",
"Another tunnel process is already running, terminate the existing instance to start a new one": "",
"At least needs control plane nodes to enable addon": "アドオンを有効にするには、少なくともコントロールプレーンノードが必要です",
"Auto-pause is already enabled.": "自動一時停止は既に有効になっています。",
"Automatically selected the {{.driver}} driver": "{{.driver}} ドライバーが自動的に選択されました",
Expand Down Expand Up @@ -894,6 +895,7 @@
"error: --output must be 'text', 'yaml' or 'json'": "エラー: --output は 'text'、'yaml'、'json' のいずれかでなければなりません",
"error: --output must be 'yaml' or 'json'": "エラー: --output は 'yaml'、'json' のいずれかでなければなりません",
"experimental": "実験的",
"failed to acquire lock due to unexpected error": "",
"failed to add node": "ノード追加に失敗しました",
"failed to open browser: {{.error}}": "ブラウザー起動に失敗しました: {{.error}}",
"failed to save config": "設定保存に失敗しました",
Expand Down
2 changes: 2 additions & 0 deletions translations/ko.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"Another hypervisor, such as VirtualBox, is conflicting with KVM. Please stop the other hypervisor, or use --driver to switch to it.": "VirtualBox 와 같은 또 다른 하이퍼바이저가 KVM 과 충돌이 발생합니다. 다른 하이퍼바이저를 중단하거나 --driver 로 변경하세요",
"Another minikube instance is downloading dependencies... ": "",
"Another program is using a file required by minikube. If you are using Hyper-V, try stopping the minikube VM from within the Hyper-V manager": "",
"Another tunnel process is already running, terminate the existing instance to start a new one": "",
"At least needs control plane nodes to enable addon": "",
"Auto-pause is already enabled.": "자동 일시 정지 설정이 이미 활성화되어있습니다",
"Automatically selected the {{.driver}} driver": "자동적으로 {{.driver}} 드라이버가 선택되었습니다",
Expand Down Expand Up @@ -957,6 +958,7 @@
"error: --output must be 'text', 'yaml' or 'json'": "",
"error: --output must be 'yaml' or 'json'": "",
"experimental": "",
"failed to acquire lock due to unexpected error": "",
"failed to add node": "",
"failed to open browser: {{.error}}": "",
"failed to save config": "",
Expand Down
2 changes: 2 additions & 0 deletions translations/pl.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"Another hypervisor, such as VirtualBox, is conflicting with KVM. Please stop the other hypervisor, or use --driver to switch to it.": "Inny hiperwizor, taki jak Virtualbox, powoduje konflikty z KVM. Zatrzymaj innego hiperwizora lub użyj flagi --driver żeby go zmienić.",
"Another minikube instance is downloading dependencies... ": "Inny program minikube już pobiera zależności...",
"Another program is using a file required by minikube. If you are using Hyper-V, try stopping the minikube VM from within the Hyper-V manager": "Inny program używa pliku wymaganego przez minikube. Jeśli używasz Hyper-V, spróbuj zatrzymać maszynę wirtualną minikube z poziomu managera Hyper-V",
"Another tunnel process is already running, terminate the existing instance to start a new one": "",
"At least needs control plane nodes to enable addon": "Wymaga węzłów z płaszczyzny kontrolnej do włączenia addona",
"Auto-pause is already enabled.": "",
"Automatically selected the {{.driver}} driver": "Automatycznie wybrano sterownik {{.driver}}",
Expand Down Expand Up @@ -966,6 +967,7 @@
"error: --output must be 'text', 'yaml' or 'json'": "",
"error: --output must be 'yaml' or 'json'": "",
"experimental": "",
"failed to acquire lock due to unexpected error": "",
"failed to add node": "",
"failed to open browser: {{.error}}": "Nie udało się otworzyć przeglądarki: {{.error}}",
"failed to save config": "",
Expand Down
2 changes: 2 additions & 0 deletions translations/ru.json
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"Another hypervisor, such as VirtualBox, is conflicting with KVM. Please stop the other hypervisor, or use --driver to switch to it.": "",
"Another minikube instance is downloading dependencies... ": "",
"Another program is using a file required by minikube. If you are using Hyper-V, try stopping the minikube VM from within the Hyper-V manager": "",
"Another tunnel process is already running, terminate the existing instance to start a new one": "",
"At least needs control plane nodes to enable addon": "",
"Auto-pause is already enabled.": "",
"Automatically selected the {{.driver}} driver": "",
Expand Down Expand Up @@ -886,6 +887,7 @@
"error: --output must be 'text', 'yaml' or 'json'": "",
"error: --output must be 'yaml' or 'json'": "",
"experimental": "",
"failed to acquire lock due to unexpected error": "",
"failed to add node": "",
"failed to open browser: {{.error}}": "",
"failed to save config": "",
Expand Down
2 changes: 2 additions & 0 deletions translations/strings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@
"Another hypervisor, such as VirtualBox, is conflicting with KVM. Please stop the other hypervisor, or use --driver to switch to it.": "",
"Another minikube instance is downloading dependencies... ": "",
"Another program is using a file required by minikube. If you are using Hyper-V, try stopping the minikube VM from within the Hyper-V manager": "",
"Another tunnel process is already running, terminate the existing instance to start a new one": "",
"At least needs control plane nodes to enable addon": "",
"Auto-pause is already enabled.": "",
"Automatically selected the {{.driver}} driver": "",
Expand Down Expand Up @@ -886,6 +887,7 @@
"error: --output must be 'text', 'yaml' or 'json'": "",
"error: --output must be 'yaml' or 'json'": "",
"experimental": "",
"failed to acquire lock due to unexpected error": "",
"failed to add node": "",
"failed to open browser: {{.error}}": "",
"failed to save config": "",
Expand Down
2 changes: 2 additions & 0 deletions translations/zh-CN.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@
"Another hypervisor, such as VirtualBox, is conflicting with KVM. Please stop the other hypervisor, or use --vm-driver to switch to it.": "另外一个管理程序与 KVM 产生了冲突,如 VirtualBox。请停止其他的管理程序",
"Another minikube instance is downloading dependencies... ": "",
"Another program is using a file required by minikube. If you are using Hyper-V, try stopping the minikube VM from within the Hyper-V manager": "",
"Another tunnel process is already running, terminate the existing instance to start a new one": "",
"At least needs control plane nodes to enable addon": "",
"Auto-pause is already enabled.": "",
"Automatically selected the '{{.driver}}' driver": "自动选择 '{{.driver}}' 驱动",
Expand Down Expand Up @@ -1074,6 +1075,7 @@
"error: --output must be 'text', 'yaml' or 'json'": "",
"error: --output must be 'yaml' or 'json'": "",
"experimental": "",
"failed to acquire lock due to unexpected error": "",
"failed to add node": "",
"failed to open browser: {{.error}}": "",
"failed to save config": "",
Expand Down