github.com/kaisenlinux/docker.io@v0.0.0-20230510090727-ea55db55fac7/swarmkit/agent/worker_test.go (about)

     1  package agent
     2  
     3  import (
     4  	"context"
     5  	"testing"
     6  
     7  	"github.com/docker/swarmkit/agent/exec"
     8  	"github.com/docker/swarmkit/api"
     9  	"github.com/docker/swarmkit/log"
    10  	"github.com/sirupsen/logrus"
    11  	"github.com/stretchr/testify/assert"
    12  	bolt "go.etcd.io/bbolt"
    13  )
    14  
    15  type testPublisherProvider struct {
    16  }
    17  
    18  func (tpp *testPublisherProvider) Publisher(ctx context.Context, subscriptionID string) (exec.LogPublisher, func(), error) {
    19  	return exec.LogPublisherFunc(func(ctx context.Context, message api.LogMessage) error {
    20  			log.G(ctx).WithFields(logrus.Fields{
    21  				"subscription": subscriptionID,
    22  				"task.id":      message.Context.TaskID,
    23  				"node.id":      message.Context.NodeID,
    24  				"service.id":   message.Context.ServiceID,
    25  			}).Info(message.Data)
    26  			return nil
    27  		}), func() {
    28  		}, nil
    29  }
    30  
    31  func TestWorkerAssign(t *testing.T) {
    32  	db, cleanup := storageTestEnv(t)
    33  	defer cleanup()
    34  
    35  	ctx := context.Background()
    36  	executor := &mockExecutor{dependencies: NewDependencyManager()}
    37  	worker := newWorker(db, executor, &testPublisherProvider{})
    38  	reporter := statusReporterFunc(func(ctx context.Context, taskID string, status *api.TaskStatus) error {
    39  		log.G(ctx).WithFields(logrus.Fields{"task.id": taskID, "status": status}).Info("status update received")
    40  		return nil
    41  	})
    42  
    43  	worker.Listen(ctx, reporter)
    44  
    45  	for _, testcase := range []struct {
    46  		changeSet        []*api.AssignmentChange
    47  		expectedTasks    []*api.Task
    48  		expectedSecrets  []*api.Secret
    49  		expectedConfigs  []*api.Config
    50  		expectedAssigned []*api.Task
    51  	}{
    52  		{}, // handle nil case.
    53  		{
    54  			changeSet: []*api.AssignmentChange{
    55  				{
    56  					Assignment: &api.Assignment{
    57  						Item: &api.Assignment_Task{
    58  							Task: &api.Task{ID: "task-1"},
    59  						},
    60  					},
    61  					Action: api.AssignmentChange_AssignmentActionUpdate,
    62  				},
    63  				{
    64  					Assignment: &api.Assignment{
    65  						Item: &api.Assignment_Secret{
    66  							Secret: &api.Secret{ID: "secret-1"},
    67  						},
    68  					},
    69  					Action: api.AssignmentChange_AssignmentActionUpdate,
    70  				},
    71  				{
    72  					Assignment: &api.Assignment{
    73  						Item: &api.Assignment_Config{
    74  							Config: &api.Config{ID: "config-1"},
    75  						},
    76  					},
    77  					Action: api.AssignmentChange_AssignmentActionUpdate,
    78  				},
    79  				// these should be ignored
    80  				{
    81  					Assignment: &api.Assignment{
    82  						Item: &api.Assignment_Secret{
    83  							Secret: &api.Secret{ID: "secret-2"},
    84  						},
    85  					},
    86  					Action: api.AssignmentChange_AssignmentActionRemove,
    87  				},
    88  				{
    89  					Assignment: &api.Assignment{
    90  						Item: &api.Assignment_Task{
    91  							Task: &api.Task{ID: "task-2"},
    92  						},
    93  					},
    94  					Action: api.AssignmentChange_AssignmentActionRemove,
    95  				},
    96  				{
    97  					Assignment: &api.Assignment{
    98  						Item: &api.Assignment_Config{
    99  							Config: &api.Config{ID: "config-2"},
   100  						},
   101  					},
   102  					Action: api.AssignmentChange_AssignmentActionRemove,
   103  				},
   104  			},
   105  			expectedTasks: []*api.Task{
   106  				{ID: "task-1"},
   107  			},
   108  			expectedSecrets: []*api.Secret{
   109  				{ID: "secret-1"},
   110  			},
   111  			expectedConfigs: []*api.Config{
   112  				{ID: "config-1"},
   113  			},
   114  			expectedAssigned: []*api.Task{
   115  				{ID: "task-1"},
   116  			},
   117  		},
   118  		{ // completely replaces the existing tasks and secrets
   119  			changeSet: []*api.AssignmentChange{
   120  				{
   121  					Assignment: &api.Assignment{
   122  						Item: &api.Assignment_Task{
   123  							Task: &api.Task{ID: "task-2"},
   124  						},
   125  					},
   126  					Action: api.AssignmentChange_AssignmentActionUpdate,
   127  				},
   128  				{
   129  					Assignment: &api.Assignment{
   130  						Item: &api.Assignment_Secret{
   131  							Secret: &api.Secret{ID: "secret-2"},
   132  						},
   133  					},
   134  					Action: api.AssignmentChange_AssignmentActionUpdate,
   135  				},
   136  				{
   137  					Assignment: &api.Assignment{
   138  						Item: &api.Assignment_Config{
   139  							Config: &api.Config{ID: "config-2"},
   140  						},
   141  					},
   142  					Action: api.AssignmentChange_AssignmentActionUpdate,
   143  				},
   144  			},
   145  			expectedTasks: []*api.Task{
   146  				{ID: "task-2"},
   147  			},
   148  			expectedSecrets: []*api.Secret{
   149  				{ID: "secret-2"},
   150  			},
   151  			expectedConfigs: []*api.Config{
   152  				{ID: "config-2"},
   153  			},
   154  			expectedAssigned: []*api.Task{
   155  				// task-1 should be cleaned up and deleted.
   156  				{ID: "task-2"},
   157  			},
   158  		},
   159  		{
   160  			// remove assigned tasks, secret and config no longer present
   161  			// there should be no tasks in the tasks db after this.
   162  			expectedTasks: nil,
   163  		},
   164  
   165  		// TODO(stevvooe): There are a few more states here we need to get
   166  		// covered to ensure correct during code changes.
   167  	} {
   168  		assert.NoError(t, worker.Assign(ctx, testcase.changeSet))
   169  
   170  		var (
   171  			tasks    []*api.Task
   172  			assigned []*api.Task
   173  		)
   174  
   175  		assert.NoError(t, worker.db.View(func(tx *bolt.Tx) error {
   176  			return WalkTasks(tx, func(task *api.Task) error {
   177  				tasks = append(tasks, task)
   178  				if TaskAssigned(tx, task.ID) {
   179  					assigned = append(assigned, task)
   180  				}
   181  				return nil
   182  			})
   183  		}))
   184  
   185  		assert.Equal(t, testcase.expectedTasks, tasks)
   186  		assert.Equal(t, testcase.expectedAssigned, assigned)
   187  		for _, secret := range testcase.expectedSecrets {
   188  			secret, err := executor.Secrets().Get(secret.ID)
   189  			assert.NoError(t, err)
   190  			assert.NotNil(t, secret)
   191  		}
   192  		for _, config := range testcase.expectedConfigs {
   193  			config, err := executor.Configs().Get(config.ID)
   194  			assert.NoError(t, err)
   195  			assert.NotNil(t, config)
   196  		}
   197  	}
   198  }
   199  
   200  func TestWorkerWait(t *testing.T) {
   201  	db, cleanup := storageTestEnv(t)
   202  	defer cleanup()
   203  
   204  	ctx := context.Background()
   205  	executor := &mockExecutor{dependencies: NewDependencyManager()}
   206  	worker := newWorker(db, executor, &testPublisherProvider{})
   207  	reporter := statusReporterFunc(func(ctx context.Context, taskID string, status *api.TaskStatus) error {
   208  		log.G(ctx).WithFields(logrus.Fields{"task.id": taskID, "status": status}).Info("status update received")
   209  		return nil
   210  	})
   211  
   212  	worker.Listen(ctx, reporter)
   213  
   214  	changeSet := []*api.AssignmentChange{
   215  		{
   216  			Assignment: &api.Assignment{
   217  				Item: &api.Assignment_Task{
   218  					Task: &api.Task{ID: "task-1"},
   219  				},
   220  			},
   221  			Action: api.AssignmentChange_AssignmentActionUpdate,
   222  		},
   223  		{
   224  			Assignment: &api.Assignment{
   225  				Item: &api.Assignment_Task{
   226  					Task: &api.Task{ID: "task-2"},
   227  				},
   228  			},
   229  			Action: api.AssignmentChange_AssignmentActionUpdate,
   230  		},
   231  		{
   232  			Assignment: &api.Assignment{
   233  				Item: &api.Assignment_Secret{
   234  					Secret: &api.Secret{ID: "secret-1"},
   235  				},
   236  			},
   237  			Action: api.AssignmentChange_AssignmentActionUpdate,
   238  		},
   239  		{
   240  			Assignment: &api.Assignment{
   241  				Item: &api.Assignment_Config{
   242  					Config: &api.Config{ID: "config-1"},
   243  				},
   244  			},
   245  			Action: api.AssignmentChange_AssignmentActionUpdate,
   246  		},
   247  	}
   248  
   249  	expectedTasks := []*api.Task{
   250  		{ID: "task-1"},
   251  		{ID: "task-2"},
   252  	}
   253  
   254  	expectedSecrets := []*api.Secret{
   255  		{ID: "secret-1"},
   256  	}
   257  
   258  	expectedConfigs := []*api.Config{
   259  		{ID: "config-1"},
   260  	}
   261  
   262  	expectedAssigned := []*api.Task{
   263  		{ID: "task-1"},
   264  		{ID: "task-2"},
   265  	}
   266  
   267  	var (
   268  		tasks    []*api.Task
   269  		assigned []*api.Task
   270  	)
   271  	assert.NoError(t, worker.Assign(ctx, changeSet))
   272  
   273  	assert.NoError(t, worker.db.View(func(tx *bolt.Tx) error {
   274  		return WalkTasks(tx, func(task *api.Task) error {
   275  			tasks = append(tasks, task)
   276  			if TaskAssigned(tx, task.ID) {
   277  				assigned = append(assigned, task)
   278  			}
   279  			return nil
   280  		})
   281  	}))
   282  
   283  	assert.Equal(t, expectedTasks, tasks)
   284  	assert.Equal(t, expectedAssigned, assigned)
   285  	for _, secret := range expectedSecrets {
   286  		secret, err := executor.Secrets().Get(secret.ID)
   287  		assert.NoError(t, err)
   288  		assert.NotNil(t, secret)
   289  	}
   290  	for _, config := range expectedConfigs {
   291  		config, err := executor.Configs().Get(config.ID)
   292  		assert.NoError(t, err)
   293  		assert.NotNil(t, config)
   294  	}
   295  
   296  	err := worker.Assign(ctx, nil)
   297  	assert.Nil(t, err)
   298  
   299  	err = worker.Wait(ctx)
   300  	assert.Nil(t, err)
   301  
   302  	assigned = assigned[:0]
   303  
   304  	assert.NoError(t, worker.db.View(func(tx *bolt.Tx) error {
   305  		return WalkTasks(tx, func(task *api.Task) error {
   306  			if TaskAssigned(tx, task.ID) {
   307  				assigned = append(assigned, task)
   308  			}
   309  			return nil
   310  		})
   311  	}))
   312  	assert.Equal(t, len(assigned), 0)
   313  }
   314  
   315  func TestWorkerUpdate(t *testing.T) {
   316  	db, cleanup := storageTestEnv(t)
   317  	defer cleanup()
   318  
   319  	ctx := context.Background()
   320  	executor := &mockExecutor{dependencies: NewDependencyManager()}
   321  	worker := newWorker(db, executor, &testPublisherProvider{})
   322  	reporter := statusReporterFunc(func(ctx context.Context, taskID string, status *api.TaskStatus) error {
   323  		log.G(ctx).WithFields(logrus.Fields{"task.id": taskID, "status": status}).Info("status update received")
   324  		return nil
   325  	})
   326  
   327  	worker.Listen(ctx, reporter)
   328  
   329  	// create existing task/secret/config
   330  	assert.NoError(t, worker.Assign(ctx, []*api.AssignmentChange{
   331  		{
   332  			Assignment: &api.Assignment{
   333  				Item: &api.Assignment_Task{
   334  					Task: &api.Task{ID: "task-1"},
   335  				},
   336  			},
   337  			Action: api.AssignmentChange_AssignmentActionUpdate,
   338  		},
   339  		{
   340  			Assignment: &api.Assignment{
   341  				Item: &api.Assignment_Secret{
   342  					Secret: &api.Secret{ID: "secret-1"},
   343  				},
   344  			},
   345  			Action: api.AssignmentChange_AssignmentActionUpdate,
   346  		},
   347  		{
   348  			Assignment: &api.Assignment{
   349  				Item: &api.Assignment_Config{
   350  					Config: &api.Config{ID: "config-1"},
   351  				},
   352  			},
   353  			Action: api.AssignmentChange_AssignmentActionUpdate,
   354  		},
   355  	}))
   356  
   357  	for _, testcase := range []struct {
   358  		changeSet        []*api.AssignmentChange
   359  		expectedTasks    []*api.Task
   360  		expectedSecrets  []*api.Secret
   361  		expectedConfigs  []*api.Config
   362  		expectedAssigned []*api.Task
   363  	}{
   364  		{ // handle nil changeSet case.
   365  			expectedTasks: []*api.Task{
   366  				{ID: "task-1"},
   367  			},
   368  			expectedSecrets: []*api.Secret{
   369  				{ID: "secret-1"},
   370  			},
   371  			expectedConfigs: []*api.Config{
   372  				{ID: "config-1"},
   373  			},
   374  			expectedAssigned: []*api.Task{
   375  				{ID: "task-1"},
   376  			},
   377  		},
   378  		{
   379  			// no changes
   380  			changeSet: []*api.AssignmentChange{
   381  				{
   382  					Assignment: &api.Assignment{
   383  						Item: &api.Assignment_Task{
   384  							Task: &api.Task{ID: "task-1"},
   385  						},
   386  					},
   387  					Action: api.AssignmentChange_AssignmentActionUpdate,
   388  				},
   389  			},
   390  			expectedTasks: []*api.Task{
   391  				{ID: "task-1"},
   392  			},
   393  			expectedSecrets: []*api.Secret{
   394  				{ID: "secret-1"},
   395  			},
   396  			expectedConfigs: []*api.Config{
   397  				{ID: "config-1"},
   398  			},
   399  			expectedAssigned: []*api.Task{
   400  				{ID: "task-1"},
   401  			},
   402  		},
   403  		{
   404  			// adding a secret and task
   405  			changeSet: []*api.AssignmentChange{
   406  				{
   407  					Assignment: &api.Assignment{
   408  						Item: &api.Assignment_Task{
   409  							Task: &api.Task{ID: "task-2"},
   410  						},
   411  					},
   412  					Action: api.AssignmentChange_AssignmentActionUpdate,
   413  				},
   414  				{
   415  					Assignment: &api.Assignment{
   416  						Item: &api.Assignment_Secret{
   417  							Secret: &api.Secret{ID: "secret-2"},
   418  						},
   419  					},
   420  					Action: api.AssignmentChange_AssignmentActionUpdate,
   421  				},
   422  				{
   423  					Assignment: &api.Assignment{
   424  						Item: &api.Assignment_Config{
   425  							Config: &api.Config{ID: "config-2"},
   426  						},
   427  					},
   428  					Action: api.AssignmentChange_AssignmentActionUpdate,
   429  				},
   430  			},
   431  			expectedTasks: []*api.Task{
   432  				{ID: "task-1"},
   433  				{ID: "task-2"},
   434  			},
   435  			expectedSecrets: []*api.Secret{
   436  				{ID: "secret-1"},
   437  				{ID: "secret-2"},
   438  			},
   439  			expectedConfigs: []*api.Config{
   440  				{ID: "config-1"},
   441  				{ID: "config-2"},
   442  			},
   443  			expectedAssigned: []*api.Task{
   444  				{ID: "task-1"},
   445  				{ID: "task-2"},
   446  			},
   447  		},
   448  		{
   449  			// remove assigned task and secret, updating existing secret
   450  			changeSet: []*api.AssignmentChange{
   451  				{
   452  					Assignment: &api.Assignment{
   453  						Item: &api.Assignment_Task{
   454  							Task: &api.Task{ID: "task-1"},
   455  						},
   456  					},
   457  					Action: api.AssignmentChange_AssignmentActionRemove,
   458  				},
   459  				{
   460  					Assignment: &api.Assignment{
   461  						Item: &api.Assignment_Secret{
   462  							Secret: &api.Secret{ID: "secret-1"},
   463  						},
   464  					},
   465  					Action: api.AssignmentChange_AssignmentActionRemove,
   466  				},
   467  				{
   468  					Assignment: &api.Assignment{
   469  						Item: &api.Assignment_Secret{
   470  							Secret: &api.Secret{ID: "secret-2"},
   471  						},
   472  					},
   473  					Action: api.AssignmentChange_AssignmentActionUpdate,
   474  				},
   475  				{
   476  					Assignment: &api.Assignment{
   477  						Item: &api.Assignment_Config{
   478  							Config: &api.Config{ID: "config-1"},
   479  						},
   480  					},
   481  					Action: api.AssignmentChange_AssignmentActionRemove,
   482  				},
   483  				{
   484  					Assignment: &api.Assignment{
   485  						Item: &api.Assignment_Config{
   486  							Config: &api.Config{ID: "config-2"},
   487  						},
   488  					},
   489  					Action: api.AssignmentChange_AssignmentActionUpdate,
   490  				},
   491  			},
   492  			expectedTasks: []*api.Task{
   493  				{ID: "task-2"},
   494  			},
   495  			expectedSecrets: []*api.Secret{
   496  				{ID: "secret-2"},
   497  			},
   498  			expectedConfigs: []*api.Config{
   499  				{ID: "config-2"},
   500  			},
   501  			expectedAssigned: []*api.Task{
   502  				{ID: "task-2"},
   503  			},
   504  		},
   505  		{
   506  			// removing nonexistent items doesn't fail
   507  			changeSet: []*api.AssignmentChange{
   508  				{
   509  					Assignment: &api.Assignment{
   510  						Item: &api.Assignment_Task{
   511  							Task: &api.Task{ID: "task-1"},
   512  						},
   513  					},
   514  					Action: api.AssignmentChange_AssignmentActionRemove,
   515  				},
   516  				{
   517  					Assignment: &api.Assignment{
   518  						Item: &api.Assignment_Secret{
   519  							Secret: &api.Secret{ID: "secret-1"},
   520  						},
   521  					},
   522  					Action: api.AssignmentChange_AssignmentActionRemove,
   523  				},
   524  				{
   525  					Assignment: &api.Assignment{
   526  						Item: &api.Assignment_Task{
   527  							Task: &api.Task{ID: "task-2"},
   528  						},
   529  					},
   530  					Action: api.AssignmentChange_AssignmentActionRemove,
   531  				},
   532  				{
   533  					Assignment: &api.Assignment{
   534  						Item: &api.Assignment_Secret{
   535  							Secret: &api.Secret{ID: "secret-2"},
   536  						},
   537  					},
   538  					Action: api.AssignmentChange_AssignmentActionRemove,
   539  				},
   540  				{
   541  					Assignment: &api.Assignment{
   542  						Item: &api.Assignment_Config{
   543  							Config: &api.Config{ID: "config-1"},
   544  						},
   545  					},
   546  					Action: api.AssignmentChange_AssignmentActionRemove,
   547  				},
   548  				{
   549  					Assignment: &api.Assignment{
   550  						Item: &api.Assignment_Config{
   551  							Config: &api.Config{ID: "config-2"},
   552  						},
   553  					},
   554  					Action: api.AssignmentChange_AssignmentActionRemove,
   555  				},
   556  			},
   557  		},
   558  	} {
   559  		assert.NoError(t, worker.Update(ctx, testcase.changeSet))
   560  
   561  		var (
   562  			tasks    []*api.Task
   563  			assigned []*api.Task
   564  		)
   565  		assert.NoError(t, worker.db.View(func(tx *bolt.Tx) error {
   566  			return WalkTasks(tx, func(task *api.Task) error {
   567  				tasks = append(tasks, task)
   568  				if TaskAssigned(tx, task.ID) {
   569  					assigned = append(assigned, task)
   570  				}
   571  				return nil
   572  			})
   573  		}))
   574  
   575  		assert.Equal(t, testcase.expectedTasks, tasks)
   576  		assert.Equal(t, testcase.expectedAssigned, assigned)
   577  		for _, secret := range testcase.expectedSecrets {
   578  			secret, err := executor.Secrets().Get(secret.ID)
   579  			assert.NoError(t, err)
   580  			assert.NotNil(t, secret)
   581  		}
   582  		for _, config := range testcase.expectedConfigs {
   583  			config, err := executor.Configs().Get(config.ID)
   584  			assert.NoError(t, err)
   585  			assert.NotNil(t, config)
   586  		}
   587  	}
   588  }
   589  
   590  type mockTaskController struct {
   591  	exec.Controller
   592  	task         *api.Task
   593  	dependencies exec.DependencyGetter
   594  }
   595  
   596  func (mtc *mockTaskController) Remove(ctx context.Context) error {
   597  	return nil
   598  }
   599  
   600  func (mtc *mockTaskController) Close() error {
   601  	return nil
   602  }
   603  
   604  type mockExecutor struct {
   605  	exec.Executor
   606  	dependencies exec.DependencyManager
   607  }
   608  
   609  func (m *mockExecutor) Controller(task *api.Task) (exec.Controller, error) {
   610  	return &mockTaskController{task: task, dependencies: Restrict(m.dependencies, task)}, nil
   611  }
   612  
   613  func (m *mockExecutor) Secrets() exec.SecretsManager {
   614  	return m.dependencies.Secrets()
   615  }
   616  
   617  func (m *mockExecutor) Configs() exec.ConfigsManager {
   618  	return m.dependencies.Configs()
   619  }