github.com/mutagen-io/mutagen@v0.18.0-rc1/pkg/forwarding/manager.go (about) 1 package forwarding 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "sort" 8 9 "github.com/mutagen-io/mutagen/pkg/filesystem" 10 "github.com/mutagen-io/mutagen/pkg/identifier" 11 "github.com/mutagen-io/mutagen/pkg/logging" 12 "github.com/mutagen-io/mutagen/pkg/selection" 13 "github.com/mutagen-io/mutagen/pkg/state" 14 "github.com/mutagen-io/mutagen/pkg/url" 15 ) 16 17 // Manager provides forwarding session management facilities. Its methods are 18 // safe for concurrent usage, so it can be easily exported via an RPC interface. 19 type Manager struct { 20 // logger is the underlying logger. 21 logger *logging.Logger 22 // tracker tracks changes to session states. 23 tracker *state.Tracker 24 // sessionLock locks the sessions registry. 25 sessionsLock *state.TrackingLock 26 // sessions maps sessions to their respective controllers. 27 sessions map[string]*controller 28 } 29 30 // NewManager creates a new Manager instance. 31 func NewManager(logger *logging.Logger) (*Manager, error) { 32 // Create a tracker and corresponding lock to watch for state changes. 33 tracker := state.NewTracker() 34 sessionsLock := state.NewTrackingLock(tracker) 35 36 // Create the session registry. 37 sessions := make(map[string]*controller) 38 39 // Load existing sessions. 40 logger.Info("Looking for existing sessions") 41 sessionsDirectory, err := pathForSession("") 42 if err != nil { 43 return nil, fmt.Errorf("unable to compute sessions directory: %w", err) 44 } 45 sessionsDirectoryContents, err := filesystem.DirectoryContentsByPath(sessionsDirectory) 46 if err != nil { 47 return nil, fmt.Errorf("unable to read contents of sessions directory: %w", err) 48 } 49 for _, c := range sessionsDirectoryContents { 50 id := c.Name() 51 if !identifier.IsValid(id) { 52 logger.Warn("Ignoring invalid session identifier:", id) 53 continue 54 } 55 logger.Info("Loading session", id) 56 if controller, err := loadSession(logger.Sublogger(identifier.Truncated(id)), tracker, id); err != nil { 57 logger.Warnf("Failed to load session %s: %v", id, err) 58 continue 59 } else { 60 sessions[id] = controller 61 } 62 } 63 64 // Success. 65 logger.Info("Session manager initialized") 66 return &Manager{ 67 logger: logger, 68 tracker: tracker, 69 sessionsLock: sessionsLock, 70 sessions: sessions, 71 }, nil 72 } 73 74 // allControllers creates a list of all controllers managed by the manager. 75 func (m *Manager) allControllers() []*controller { 76 // Grab the registry lock and defer its release. 77 m.sessionsLock.Lock() 78 defer m.sessionsLock.UnlockWithoutNotify() 79 80 // Generate a list of all controllers. 81 controllers := make([]*controller, 0, len(m.sessions)) 82 for _, controller := range m.sessions { 83 controllers = append(controllers, controller) 84 } 85 86 // Done. 87 return controllers 88 } 89 90 // findControllersBySpecification generates a list of controllers matching the 91 // given specifications. 92 func (m *Manager) findControllersBySpecification(specifications []string) ([]*controller, error) { 93 // Grab the registry lock and defer its release. 94 m.sessionsLock.Lock() 95 defer m.sessionsLock.UnlockWithoutNotify() 96 97 // Generate a list of controllers matching the specifications. We allow each 98 // specification to match multiple controllers, so we store matches in a set 99 // before converting them to a list. We do require that each specification 100 // match at least one controller. 101 controllerSet := make(map[*controller]bool) 102 for _, specification := range specifications { 103 var matched bool 104 for _, controller := range m.sessions { 105 if controller.session.Identifier == specification || controller.session.Name == specification { 106 controllerSet[controller] = true 107 matched = true 108 } 109 } 110 if !matched { 111 return nil, fmt.Errorf("specification \"%s\" did not match any sessions", specification) 112 } 113 } 114 115 // Convert the set to a list. 116 controllers := make([]*controller, 0, len(controllerSet)) 117 for c := range controllerSet { 118 controllers = append(controllers, c) 119 } 120 121 // Done. 122 return controllers, nil 123 } 124 125 // findControllersByLabelSelector generates a list of controllers using the 126 // specified label selector. 127 func (m *Manager) findControllersByLabelSelector(labelSelector string) ([]*controller, error) { 128 // Parse the label selector. 129 selector, err := selection.ParseLabelSelector(labelSelector) 130 if err != nil { 131 return nil, fmt.Errorf("unable to parse label selector: %w", err) 132 } 133 134 // Grab the registry lock and defer its release. 135 m.sessionsLock.Lock() 136 defer m.sessionsLock.UnlockWithoutNotify() 137 138 // Loop over controllers and look for matches. 139 var controllers []*controller 140 for _, controller := range m.sessions { 141 if selector.Matches(controller.session.Labels) { 142 controllers = append(controllers, controller) 143 } 144 } 145 146 // Done. 147 return controllers, nil 148 } 149 150 // selectControllers generates a list of controllers using the mechanism 151 // specified by the provided selection. 152 func (m *Manager) selectControllers(selection *selection.Selection) ([]*controller, error) { 153 // Dispatch selection based on the requested mechanism. 154 if selection.All { 155 return m.allControllers(), nil 156 } else if len(selection.Specifications) > 0 { 157 return m.findControllersBySpecification(selection.Specifications) 158 } else if selection.LabelSelector != "" { 159 return m.findControllersByLabelSelector(selection.LabelSelector) 160 } else { 161 // TODO: Should we panic here instead? 162 return nil, errors.New("invalid session selection") 163 } 164 } 165 166 // Shutdown tells the manager to gracefully halt sessions. 167 func (m *Manager) Shutdown() { 168 // Log the shutdown. 169 m.logger.Info("Shutting down") 170 171 // Terminate state tracking to terminate monitoring. 172 m.tracker.Terminate() 173 174 // Grab the registry lock and defer its release. 175 m.sessionsLock.Lock() 176 defer m.sessionsLock.UnlockWithoutNotify() 177 178 // Attempt to halt each session so that it can shutdown cleanly. Ignore but 179 // log any that fail to halt. 180 for _, controller := range m.sessions { 181 m.logger.Info("Halting session", controller.session.Identifier) 182 if err := controller.halt(context.Background(), controllerHaltModeShutdown, ""); err != nil { 183 m.logger.Warnf("Failed to halt session %s: %v", controller.session.Identifier, err) 184 } 185 } 186 } 187 188 // Create tells the manager to create a new session. 189 func (m *Manager) Create( 190 ctx context.Context, 191 source, destination *url.URL, 192 configuration, configurationSource, configurationDestination *Configuration, 193 name string, 194 labels map[string]string, 195 paused bool, 196 prompter string, 197 ) (string, error) { 198 // Create a unique session identifier. 199 id, err := identifier.New(identifier.PrefixForwarding) 200 if err != nil { 201 return "", fmt.Errorf("unable to generate identifier for session: %w", err) 202 } 203 204 // Attempt to create a session. 205 controller, err := newSession( 206 ctx, 207 m.logger.Sublogger(identifier.Truncated(id)), 208 m.tracker, 209 id, 210 source, destination, 211 configuration, configurationSource, configurationDestination, 212 name, 213 labels, 214 paused, 215 prompter, 216 ) 217 if err != nil { 218 return "", err 219 } 220 221 // Register the controller. 222 m.sessionsLock.Lock() 223 m.sessions[controller.session.Identifier] = controller 224 m.sessionsLock.Unlock() 225 226 // Done. 227 return controller.session.Identifier, nil 228 } 229 230 // List requests a state snapshot for the specified sessions. 231 func (m *Manager) List(ctx context.Context, selection *selection.Selection, previousStateIndex uint64) (uint64, []*State, error) { 232 // Wait for a state change from the previous index. 233 stateIndex, err := m.tracker.WaitForChange(ctx, previousStateIndex) 234 if err != nil { 235 return 0, nil, fmt.Errorf("unable to track state changes: %w", err) 236 } 237 238 // Extract the controllers for the sessions of interest. 239 controllers, err := m.selectControllers(selection) 240 if err != nil { 241 return 0, nil, fmt.Errorf("unable to locate requested sessions: %w", err) 242 } 243 244 // Create a static snapshot of the state from each controller. 245 states := make([]*State, len(controllers)) 246 for i, controller := range controllers { 247 states[i] = controller.currentState() 248 } 249 250 // Sort session states by session creation time. 251 sort.Slice(states, func(i, j int) bool { 252 iTime := states[i].Session.CreationTime 253 jTime := states[j].Session.CreationTime 254 return iTime.Seconds < jTime.Seconds || 255 (iTime.Seconds == jTime.Seconds && iTime.Nanos < jTime.Nanos) 256 }) 257 258 // Success. 259 return stateIndex, states, nil 260 } 261 262 // Pause tells the manager to pause sessions matching the given specifications. 263 func (m *Manager) Pause(ctx context.Context, selection *selection.Selection, prompter string) error { 264 // Extract the controllers for the sessions of interest. 265 controllers, err := m.selectControllers(selection) 266 if err != nil { 267 return fmt.Errorf("unable to locate requested sessions: %w", err) 268 } 269 270 // Attempt to pause the sessions. 271 for _, controller := range controllers { 272 if err := controller.halt(ctx, controllerHaltModePause, prompter); err != nil { 273 return fmt.Errorf("unable to pause session: %w", err) 274 } 275 } 276 277 // Success. 278 return nil 279 } 280 281 // Resume tells the manager to resume sessions matching the given 282 // specifications. 283 func (m *Manager) Resume(ctx context.Context, selection *selection.Selection, prompter string) error { 284 // Extract the controllers for the sessions of interest. 285 controllers, err := m.selectControllers(selection) 286 if err != nil { 287 return fmt.Errorf("unable to locate requested sessions: %w", err) 288 } 289 290 // Attempt to resume. 291 for _, controller := range controllers { 292 if err := controller.resume(ctx, prompter); err != nil { 293 return fmt.Errorf("unable to resume session: %w", err) 294 } 295 } 296 297 // Success. 298 return nil 299 } 300 301 // Terminate tells the manager to terminate sessions matching the given 302 // specifications. 303 func (m *Manager) Terminate(ctx context.Context, selection *selection.Selection, prompter string) error { 304 // Extract the controllers for the sessions of interest. 305 controllers, err := m.selectControllers(selection) 306 if err != nil { 307 return fmt.Errorf("unable to locate requested sessions: %w", err) 308 } 309 310 // Attempt to terminate the sessions. Since we're terminating them, we're 311 // responsible for removing them from the session map. 312 for _, controller := range controllers { 313 if err := controller.halt(ctx, controllerHaltModeTerminate, prompter); err != nil { 314 return fmt.Errorf("unable to terminate session: %w", err) 315 } 316 m.sessionsLock.Lock() 317 delete(m.sessions, controller.session.Identifier) 318 m.sessionsLock.Unlock() 319 } 320 321 // Success. 322 return nil 323 }