-
Notifications
You must be signed in to change notification settings - Fork 38
/
switch.go
151 lines (135 loc) · 6.97 KB
/
switch.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
package context_switch
import (
"context"
"fmt"
"github.com/kurtosis-tech/kurtosis-portal/api/golang/constructors"
"github.com/kurtosis-tech/kurtosis/api/golang/engine/lib/kurtosis_context"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/highlevel/context_id_arg"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/args"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_framework/lowlevel/flags"
"github.com/kurtosis-tech/kurtosis/cli/cli/command_str_consts"
"github.com/kurtosis-tech/kurtosis/cli/cli/defaults"
"github.com/kurtosis-tech/kurtosis/cli/cli/helpers/engine_manager"
"github.com/kurtosis-tech/kurtosis/cli/cli/helpers/portal_manager"
"github.com/kurtosis-tech/kurtosis/contexts-config-store/store"
"github.com/kurtosis-tech/stacktrace"
"github.com/sirupsen/logrus"
)
const (
contextIdentifierArgKey = "context"
contextIdentifierArgIsGreedy = false
noEngineVersion = ""
restartEngineOnSameVersionIfAnyRunning = true
)
var ContextSwitchCmd = &lowlevel.LowlevelKurtosisCommand{
CommandStr: command_str_consts.ContextSwitchCmdStr,
ShortDescription: "Switches to a different Kurtosis context",
LongDescription: fmt.Sprintf("Switches to a different Kurtosis context. The context needs to be added "+
"first using the `%s` command. When switching to a remote context, the connection will be established with "+
"the remote Kurtosis server. Kurtosis Portal needs to be running for this. If the remote server can't be "+
"reached, the context will remain unchanged.", command_str_consts.ContextAddCmdStr),
Flags: []*flags.FlagConfig{},
Args: []*args.ArgConfig{
context_id_arg.NewContextIdentifierArg(store.GetContextsConfigStore(), contextIdentifierArgKey, contextIdentifierArgIsGreedy),
},
PreValidationAndRunFunc: nil,
RunFunc: run,
PostValidationAndRunFunc: nil,
}
func run(ctx context.Context, _ *flags.ParsedFlags, args *args.ParsedArgs) error {
contextIdentifier, err := args.GetNonGreedyArg(contextIdentifierArgKey)
if err != nil {
return stacktrace.Propagate(err, "Expected a value for context identifier arg '%v' but none was found; this is a bug in the Kurtosis CLI!", contextIdentifierArgKey)
}
return SwitchContext(ctx, contextIdentifier)
}
func SwitchContext(
ctx context.Context,
contextIdentifier string,
) error {
isContextSwitchSuccessful := false
logrus.Info("Switching context...")
contextsConfigStore := store.GetContextsConfigStore()
contextPriorToSwitch, err := contextsConfigStore.GetCurrentContext()
if err != nil {
return stacktrace.NewError("An error occurred retrieving current context prior to switching to the new one '%s'", contextIdentifier)
}
contextsMatchingIdentifiers, err := context_id_arg.GetContextUuidForContextIdentifier(contextsConfigStore, []string{contextIdentifier})
if err != nil {
return stacktrace.Propagate(err, "Error searching for context matching context identifier: '%s'", contextIdentifier)
}
contextUuidToSwitchTo, found := contextsMatchingIdentifiers[contextIdentifier]
if !found {
return stacktrace.NewError("No context matching identifier '%s' could be found", contextIdentifier)
}
if contextUuidToSwitchTo.GetValue() == contextPriorToSwitch.GetUuid().GetValue() {
logrus.Infof("Already on context '%s'", contextPriorToSwitch.GetName())
return nil
}
if err = contextsConfigStore.SwitchContext(contextUuidToSwitchTo); err != nil {
return stacktrace.Propagate(err, "An error occurred switching to context '%s' with UUID '%s'", contextIdentifier, contextUuidToSwitchTo.GetValue())
}
defer func() {
if isContextSwitchSuccessful {
return
}
if err = contextsConfigStore.SwitchContext(contextPriorToSwitch.GetUuid()); err != nil {
logrus.Errorf("An unexpected error occurred switching to context '%s' with UUID "+
"'%s'. Kurtosis tried to roll back to previous context '%s' with UUID '%s' but the roll back "+
"failed. It is likely that the current context is still set to '%s' but it is not fully functional. "+
"Try manually switching back to '%s' to get back to a working state. Error was:\n%v",
contextIdentifier, contextUuidToSwitchTo.GetValue(), contextPriorToSwitch.GetName(),
contextPriorToSwitch.GetUuid(), contextIdentifier, contextPriorToSwitch.GetName(), err.Error())
}
}()
currentContext, err := contextsConfigStore.GetCurrentContext()
if err != nil {
return stacktrace.Propagate(err, "Error retrieving context info for context '%s' after switching to it", contextIdentifier)
}
portalManager := portal_manager.NewPortalManager()
if portalManager.IsReachable() {
portalDaemonClient := portalManager.GetClient()
if portalDaemonClient != nil {
switchContextArg := constructors.NewSwitchContextArgs()
if _, err = portalDaemonClient.SwitchContext(ctx, switchContextArg); err != nil {
return stacktrace.Propagate(err, "Error switching Kurtosis portal context")
}
if store.IsRemote(currentContext) {
// Forward the remote engine port to the local machine
portalClient := portalManager.GetClient()
forwardEnginePortArgs := constructors.NewForwardPortArgs(uint32(kurtosis_context.DefaultGrpcEngineServerPortNum), uint32(kurtosis_context.DefaultGrpcEngineServerPortNum), &kurtosis_context.EnginePortTransportProtocol)
if _, err := portalClient.ForwardPort(ctx, forwardEnginePortArgs); err != nil {
return stacktrace.Propagate(err, "Unable to forward the remote engine port to the local machine")
}
}
}
} else {
if store.IsRemote(currentContext) {
return stacktrace.NewError("New context is remote but Kurtosis Portal is not reachable locally. " +
"Make sure Kurtosis Portal is running before switching to a remote context again.")
}
}
logrus.Infof("Context switched to '%s', Kurtosis engine will now be restarted", contextIdentifier)
// Instantiate the engine manager after storing the new context so the manager can read it.
engineManager, err := engine_manager.NewEngineManager(ctx)
if err != nil {
return stacktrace.Propagate(err, "An error occurred creating an engine manager.")
}
_, engineClientCloseFunc, restartEngineErr := engineManager.RestartEngineIdempotently(ctx, logrus.InfoLevel, noEngineVersion, restartEngineOnSameVersionIfAnyRunning, defaults.DefaultEngineEnclavePoolSize)
if restartEngineErr != nil {
return stacktrace.Propagate(restartEngineErr, "Engine could not be restarted after context was switched. The context"+
"will be rolled back, but it is possible the engine will remain stopped. Its status can be retrieved "+
"running 'kurtosis %s %s' and it can potentially be restarted running 'kurtosis %s %s'",
command_str_consts.EngineCmdStr, command_str_consts.EngineStatusCmdStr, command_str_consts.EngineCmdStr,
command_str_consts.EngineStartCmdStr)
}
defer func() {
if err = engineClientCloseFunc(); err != nil {
logrus.Warnf("Error closing the engine client:\n'%v'", err)
}
}()
logrus.Info("Successfully switched context")
isContextSwitchSuccessful = true
return nil
}