Skip to content

Commit

Permalink
Add test to ensure no two tunnels can run simultaneously.
Browse files Browse the repository at this point in the history
Add new error reason to disallow simultaneously running two tunnel processes
When tunnel fails to acquire lock due to contention, exit gracefully
  • Loading branch information
OmSaran committed Feb 13, 2023
1 parent a0c316a commit 40b1bb5
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 3 deletions.
9 changes: 6 additions & 3 deletions cmd/minikube/cmd/tunnel.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ var tunnelCmd = &cobra.Command{
RootCmd.PersistentPreRun(cmd, args)
},
Run: func(cmd *cobra.Command, args []string) {
defer cleanupLock()
manager := tunnel.NewManager()
cname := ClusterFlagValue()
co := mustload.Healthy(cname)
Expand All @@ -78,6 +77,7 @@ var tunnelCmd = &cobra.Command{
}

checkNoOtherTunnelProcess()
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.
Expand Down Expand Up @@ -124,7 +124,10 @@ var tunnelCmd = &cobra.Command{

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

Expand All @@ -145,7 +148,7 @@ func checkNoOtherTunnelProcess() {
lockHandle = fslock.New(tunnelLockPath)
err = lockHandle.TryLock()
if err == fslock.ErrLocked {
exit.Error(reason.SvcTunnelStart, "another tunnel process already running", fmt.Errorf("another tunnel process already running"))
exit.Message(reason.SvcTunnelAlreadyRunning, "Another tunnel process already running, terminate the older to start a new one")
}
}

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
45 changes: 45 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,49 @@ func validateTunnelStart(ctx context.Context, t *testing.T, profile string) {
tunnelSession = *ss
}

// validateNoSecondTunnel ensures more than 1 tunnel can NOT run simultaneously
func validateNoSecondTunnel(ctx context.Context, t *testing.T, profile string) {
checkRoutePassword(t)

exitCodeCh := make(chan int)
sessions := make([]*StartSession, 2)

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

ctx2, cancel := context.WithTimeout(ctx, Seconds(5))
session, err := Start(t, exec.CommandContext(ctx2, Target(), args...))
if err != nil {
t.Errorf("Failed to start tunnel for the first time")
}
sessions[idx] = session
defer cancel()

err = session.cmd.Wait()
if exErr, ok := err.(*exec.ExitError); ok {
exitCodeCh <- exErr.ExitCode()
} else {
exitCodeCh <- -1
t.Errorf("Second tunnel command failed due to a different reason: %s", err)
}
}

// 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)

exitCode := <-exitCodeCh

if exitCode != reason.SvcTunnelAlreadyRunning.ExitCode {
t.Errorf("Failed to due to some other reason: %d", exitCode)
}

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

// 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

0 comments on commit 40b1bb5

Please sign in to comment.