github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/manager/dispatcher/assignments.go (about)

     1  package dispatcher
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/docker/swarmkit/api"
     7  	"github.com/docker/swarmkit/api/equality"
     8  	"github.com/docker/swarmkit/api/validation"
     9  	"github.com/docker/swarmkit/identity"
    10  	"github.com/docker/swarmkit/manager/drivers"
    11  	"github.com/docker/swarmkit/manager/state/store"
    12  	"github.com/sirupsen/logrus"
    13  )
    14  
    15  type typeAndID struct {
    16  	id      string
    17  	objType api.ResourceType
    18  }
    19  
    20  type assignmentSet struct {
    21  	dp                   *drivers.DriverProvider
    22  	tasksMap             map[string]*api.Task
    23  	tasksUsingDependency map[typeAndID]map[string]struct{}
    24  	changes              map[typeAndID]*api.AssignmentChange
    25  	log                  *logrus.Entry
    26  }
    27  
    28  func newAssignmentSet(log *logrus.Entry, dp *drivers.DriverProvider) *assignmentSet {
    29  	return &assignmentSet{
    30  		dp:                   dp,
    31  		changes:              make(map[typeAndID]*api.AssignmentChange),
    32  		tasksMap:             make(map[string]*api.Task),
    33  		tasksUsingDependency: make(map[typeAndID]map[string]struct{}),
    34  		log:                  log,
    35  	}
    36  }
    37  
    38  func assignSecret(a *assignmentSet, readTx store.ReadTx, mapKey typeAndID, t *api.Task) {
    39  	if _, exists := a.tasksUsingDependency[mapKey]; !exists {
    40  		a.tasksUsingDependency[mapKey] = make(map[string]struct{})
    41  	}
    42  	secret, doNotReuse, err := a.secret(readTx, t, mapKey.id)
    43  	if err != nil {
    44  		a.log.WithFields(logrus.Fields{
    45  			"resource.type": "secret",
    46  			"secret.id":     mapKey.id,
    47  			"error":         err,
    48  		}).Debug("failed to fetch secret")
    49  		return
    50  	}
    51  	// If the secret should not be reused for other tasks, give it a unique ID for the task to allow different values for different tasks.
    52  	if doNotReuse {
    53  		// Give the secret a new ID and mark it as internal
    54  		originalSecretID := secret.ID
    55  		taskSpecificID := identity.CombineTwoIDs(originalSecretID, t.ID)
    56  		secret.ID = taskSpecificID
    57  		secret.Internal = true
    58  		// Create a new mapKey with the new ID and insert it into the dependencies map for the task.
    59  		// This will make the changes map contain an entry with the new ID rather than the original one.
    60  		mapKey = typeAndID{objType: mapKey.objType, id: secret.ID}
    61  		a.tasksUsingDependency[mapKey] = make(map[string]struct{})
    62  		a.tasksUsingDependency[mapKey][t.ID] = struct{}{}
    63  	}
    64  	a.changes[mapKey] = &api.AssignmentChange{
    65  		Assignment: &api.Assignment{
    66  			Item: &api.Assignment_Secret{
    67  				Secret: secret,
    68  			},
    69  		},
    70  		Action: api.AssignmentChange_AssignmentActionUpdate,
    71  	}
    72  }
    73  
    74  func assignConfig(a *assignmentSet, readTx store.ReadTx, mapKey typeAndID) {
    75  	a.tasksUsingDependency[mapKey] = make(map[string]struct{})
    76  	config := store.GetConfig(readTx, mapKey.id)
    77  	if config == nil {
    78  		a.log.WithFields(logrus.Fields{
    79  			"resource.type": "config",
    80  			"config.id":     mapKey.id,
    81  		}).Debug("config not found")
    82  		return
    83  	}
    84  	a.changes[mapKey] = &api.AssignmentChange{
    85  		Assignment: &api.Assignment{
    86  			Item: &api.Assignment_Config{
    87  				Config: config,
    88  			},
    89  		},
    90  		Action: api.AssignmentChange_AssignmentActionUpdate,
    91  	}
    92  }
    93  
    94  func (a *assignmentSet) addTaskDependencies(readTx store.ReadTx, t *api.Task) {
    95  	for _, resourceRef := range t.Spec.ResourceReferences {
    96  		mapKey := typeAndID{objType: resourceRef.ResourceType, id: resourceRef.ResourceID}
    97  		if len(a.tasksUsingDependency[mapKey]) == 0 {
    98  			switch resourceRef.ResourceType {
    99  			case api.ResourceType_SECRET:
   100  				assignSecret(a, readTx, mapKey, t)
   101  			case api.ResourceType_CONFIG:
   102  				assignConfig(a, readTx, mapKey)
   103  			default:
   104  				a.log.WithField(
   105  					"resource.type", resourceRef.ResourceType,
   106  				).Debug("invalid resource type for a task dependency, skipping")
   107  				continue
   108  			}
   109  		}
   110  		a.tasksUsingDependency[mapKey][t.ID] = struct{}{}
   111  	}
   112  
   113  	var secrets []*api.SecretReference
   114  	container := t.Spec.GetContainer()
   115  	if container != nil {
   116  		secrets = container.Secrets
   117  	}
   118  
   119  	for _, secretRef := range secrets {
   120  		secretID := secretRef.SecretID
   121  		mapKey := typeAndID{objType: api.ResourceType_SECRET, id: secretID}
   122  
   123  		// This checks for the presence of each task in the dependency map for the
   124  		// secret. This is currently only done for secrets since the other types of
   125  		// dependencies do not support driver plugins. Arguably, the same task would
   126  		// not have the same secret as a dependency more than once, but this check
   127  		// makes sure the task only gets the secret assigned once.
   128  		if _, exists := a.tasksUsingDependency[mapKey][t.ID]; !exists {
   129  			assignSecret(a, readTx, mapKey, t)
   130  		}
   131  		a.tasksUsingDependency[mapKey][t.ID] = struct{}{}
   132  	}
   133  
   134  	var configs []*api.ConfigReference
   135  	if container != nil {
   136  		configs = container.Configs
   137  	}
   138  	for _, configRef := range configs {
   139  		configID := configRef.ConfigID
   140  		mapKey := typeAndID{objType: api.ResourceType_CONFIG, id: configID}
   141  
   142  		if len(a.tasksUsingDependency[mapKey]) == 0 {
   143  			assignConfig(a, readTx, mapKey)
   144  		}
   145  		a.tasksUsingDependency[mapKey][t.ID] = struct{}{}
   146  	}
   147  }
   148  
   149  func (a *assignmentSet) releaseDependency(mapKey typeAndID, assignment *api.Assignment, taskID string) bool {
   150  	delete(a.tasksUsingDependency[mapKey], taskID)
   151  	if len(a.tasksUsingDependency[mapKey]) != 0 {
   152  		return false
   153  	}
   154  	// No tasks are using the dependency anymore
   155  	delete(a.tasksUsingDependency, mapKey)
   156  	a.changes[mapKey] = &api.AssignmentChange{
   157  		Assignment: assignment,
   158  		Action:     api.AssignmentChange_AssignmentActionRemove,
   159  	}
   160  	return true
   161  }
   162  
   163  func (a *assignmentSet) releaseTaskDependencies(t *api.Task) bool {
   164  	var modified bool
   165  
   166  	for _, resourceRef := range t.Spec.ResourceReferences {
   167  		var assignment *api.Assignment
   168  		switch resourceRef.ResourceType {
   169  		case api.ResourceType_SECRET:
   170  			assignment = &api.Assignment{
   171  				Item: &api.Assignment_Secret{
   172  					Secret: &api.Secret{ID: resourceRef.ResourceID},
   173  				},
   174  			}
   175  		case api.ResourceType_CONFIG:
   176  			assignment = &api.Assignment{
   177  				Item: &api.Assignment_Config{
   178  					Config: &api.Config{ID: resourceRef.ResourceID},
   179  				},
   180  			}
   181  		default:
   182  			a.log.WithField(
   183  				"resource.type", resourceRef.ResourceType,
   184  			).Debug("invalid resource type for a task dependency, skipping")
   185  			continue
   186  		}
   187  
   188  		mapKey := typeAndID{objType: resourceRef.ResourceType, id: resourceRef.ResourceID}
   189  		if a.releaseDependency(mapKey, assignment, t.ID) {
   190  			modified = true
   191  		}
   192  	}
   193  
   194  	container := t.Spec.GetContainer()
   195  
   196  	var secrets []*api.SecretReference
   197  	if container != nil {
   198  		secrets = container.Secrets
   199  	}
   200  
   201  	for _, secretRef := range secrets {
   202  		secretID := secretRef.SecretID
   203  		mapKey := typeAndID{objType: api.ResourceType_SECRET, id: secretID}
   204  		assignment := &api.Assignment{
   205  			Item: &api.Assignment_Secret{
   206  				Secret: &api.Secret{ID: secretID},
   207  			},
   208  		}
   209  		if a.releaseDependency(mapKey, assignment, t.ID) {
   210  			modified = true
   211  		}
   212  	}
   213  
   214  	var configs []*api.ConfigReference
   215  	if container != nil {
   216  		configs = container.Configs
   217  	}
   218  
   219  	for _, configRef := range configs {
   220  		configID := configRef.ConfigID
   221  		mapKey := typeAndID{objType: api.ResourceType_CONFIG, id: configID}
   222  		assignment := &api.Assignment{
   223  			Item: &api.Assignment_Config{
   224  				Config: &api.Config{ID: configID},
   225  			},
   226  		}
   227  		if a.releaseDependency(mapKey, assignment, t.ID) {
   228  			modified = true
   229  		}
   230  	}
   231  
   232  	return modified
   233  }
   234  
   235  func (a *assignmentSet) addOrUpdateTask(readTx store.ReadTx, t *api.Task) bool {
   236  	// We only care about tasks that are ASSIGNED or higher.
   237  	if t.Status.State < api.TaskStateAssigned {
   238  		return false
   239  	}
   240  
   241  	if oldTask, exists := a.tasksMap[t.ID]; exists {
   242  		// States ASSIGNED and below are set by the orchestrator/scheduler,
   243  		// not the agent, so tasks in these states need to be sent to the
   244  		// agent even if nothing else has changed.
   245  		if equality.TasksEqualStable(oldTask, t) && t.Status.State > api.TaskStateAssigned {
   246  			// this update should not trigger a task change for the agent
   247  			a.tasksMap[t.ID] = t
   248  			// If this task got updated to a final state, let's release
   249  			// the dependencies that are being used by the task
   250  			if t.Status.State > api.TaskStateRunning {
   251  				// If releasing the dependencies caused us to
   252  				// remove something from the assignment set,
   253  				// mark one modification.
   254  				return a.releaseTaskDependencies(t)
   255  			}
   256  			return false
   257  		}
   258  	} else if t.Status.State <= api.TaskStateRunning {
   259  		// If this task wasn't part of the assignment set before, and it's <= RUNNING
   260  		// add the dependencies it references to the assignment.
   261  		// Task states > RUNNING are worker reported only, are never created in
   262  		// a > RUNNING state.
   263  		a.addTaskDependencies(readTx, t)
   264  	}
   265  	a.tasksMap[t.ID] = t
   266  	a.changes[typeAndID{objType: api.ResourceType_TASK, id: t.ID}] = &api.AssignmentChange{
   267  		Assignment: &api.Assignment{
   268  			Item: &api.Assignment_Task{
   269  				Task: t,
   270  			},
   271  		},
   272  		Action: api.AssignmentChange_AssignmentActionUpdate,
   273  	}
   274  	return true
   275  }
   276  
   277  func (a *assignmentSet) removeTask(t *api.Task) bool {
   278  	if _, exists := a.tasksMap[t.ID]; !exists {
   279  		return false
   280  	}
   281  
   282  	a.changes[typeAndID{objType: api.ResourceType_TASK, id: t.ID}] = &api.AssignmentChange{
   283  		Assignment: &api.Assignment{
   284  			Item: &api.Assignment_Task{
   285  				Task: &api.Task{ID: t.ID},
   286  			},
   287  		},
   288  		Action: api.AssignmentChange_AssignmentActionRemove,
   289  	}
   290  
   291  	delete(a.tasksMap, t.ID)
   292  
   293  	// Release the dependencies being used by this task.
   294  	// Ignoring the return here. We will always mark this as a
   295  	// modification, since a task is being removed.
   296  	a.releaseTaskDependencies(t)
   297  	return true
   298  }
   299  
   300  func (a *assignmentSet) message() api.AssignmentsMessage {
   301  	var message api.AssignmentsMessage
   302  	for _, change := range a.changes {
   303  		message.Changes = append(message.Changes, change)
   304  	}
   305  
   306  	// The the set of changes is reinitialized to prepare for formation
   307  	// of the next message.
   308  	a.changes = make(map[typeAndID]*api.AssignmentChange)
   309  
   310  	return message
   311  }
   312  
   313  // secret populates the secret value from raft store. For external secrets, the value is populated
   314  // from the secret driver. The function returns: a secret object; an indication of whether the value
   315  // is to be reused across tasks; and an error if the secret is not found in the store, if the secret
   316  // driver responds with one or if the payload does not pass validation.
   317  func (a *assignmentSet) secret(readTx store.ReadTx, task *api.Task, secretID string) (*api.Secret, bool, error) {
   318  	secret := store.GetSecret(readTx, secretID)
   319  	if secret == nil {
   320  		return nil, false, fmt.Errorf("secret not found")
   321  	}
   322  	if secret.Spec.Driver == nil {
   323  		return secret, false, nil
   324  	}
   325  	d, err := a.dp.NewSecretDriver(secret.Spec.Driver)
   326  	if err != nil {
   327  		return nil, false, err
   328  	}
   329  	value, doNotReuse, err := d.Get(&secret.Spec, task)
   330  	if err != nil {
   331  		return nil, false, err
   332  	}
   333  	if err := validation.ValidateSecretPayload(value); err != nil {
   334  		return nil, false, err
   335  	}
   336  	// Assign the secret
   337  	secret.Spec.Data = value
   338  	return secret, doNotReuse, nil
   339  }