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  }