github.com/alibaba/ilogtail/pkg@v0.0.0-20250526110833-c53b480d046c/helper/containercenter/container_center_test.go (about)

     1  // Copyright 2021 iLogtail Authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package containercenter
    16  
    17  import (
    18  	"context"
    19  	"errors"
    20  	"os"
    21  	"sync"
    22  	"testing"
    23  	"time"
    24  
    25  	"github.com/docker/docker/api/types"
    26  	"github.com/docker/docker/api/types/container"
    27  	"github.com/docker/docker/api/types/events"
    28  	"github.com/stretchr/testify/assert"
    29  	"github.com/stretchr/testify/mock"
    30  	"github.com/stretchr/testify/require"
    31  )
    32  
    33  var hostFileContent1 = `
    34  192.168.5.3	8be13ee0dd9e
    35  127.0.0.1  	  localhost
    36  ::1     localhost ip6-localhost ip6-loopback
    37  fe00::0 ip6-localnet
    38  ff00::0 ip6-mcastprefix
    39  ff02::1 ip6-allnodes
    40  ff02::2 ip6-allrouters
    41  `
    42  
    43  var hostFileContent2 = `
    44  # Kubernetes-managed hosts file.
    45  127.0.0.1	localhost
    46  ::1	localhost ip6-localhost ip6-loopback
    47  fe00::0	ip6-localnet
    48  fe00::0	ip6-mcastprefix
    49  fe00::1	ip6-allnodes
    50  fe00::2	ip6-allrouters
    51  172.20.4.5	nginx-5fd7568b67-4sh8c
    52  `
    53  
    54  func resetContainerCenter() {
    55  	containerCenterInstance = nil
    56  	onceDocker = sync.Once{}
    57  
    58  }
    59  
    60  func TestGetIpByHost_1(t *testing.T) {
    61  	hostFileName := "./tmp_TestGetIpByHost.txt"
    62  	os.WriteFile(hostFileName, []byte(hostFileContent1), 0x777)
    63  	ip := getIPByHosts(hostFileName, "8be13ee0dd9e")
    64  	if ip != "192.168.5.3" {
    65  		t.Errorf("GetIpByHosts = %v, want %v", ip, "192.168.5.3")
    66  	}
    67  	os.Remove(hostFileName)
    68  }
    69  
    70  func TestGetIpByHost_2(t *testing.T) {
    71  	hostFileName := "./tmp_TestGetIpByHost.txt"
    72  	os.WriteFile(hostFileName, []byte(hostFileContent2), 0x777)
    73  	ip := getIPByHosts(hostFileName, "nginx-5fd7568b67-4sh8c")
    74  	if ip != "172.20.4.5" {
    75  		t.Errorf("GetIpByHosts = %v, want %v", ip, "172.20.4.5")
    76  	}
    77  	os.Remove(hostFileName)
    78  }
    79  
    80  func TestGetAllAcceptedInfoV2(t *testing.T) {
    81  	resetContainerCenter()
    82  	dc := getContainerCenterInstance()
    83  
    84  	newContainer := func(id string) *DockerInfoDetail {
    85  		return dc.CreateInfoDetail(types.ContainerJSON{
    86  			ContainerJSONBase: &types.ContainerJSONBase{
    87  				ID:    id,
    88  				Name:  id,
    89  				State: &types.ContainerState{},
    90  			},
    91  			Config: &container.Config{
    92  				Env: make([]string, 0),
    93  			},
    94  		}, "", false)
    95  	}
    96  
    97  	fullList := make(map[string]bool)
    98  	matchList := make(map[string]*DockerInfoDetail)
    99  
   100  	// Init.
   101  	{
   102  		dc.updateContainers(map[string]*DockerInfoDetail{
   103  			"c1": newContainer("c1"),
   104  		})
   105  
   106  		newCount, delCount, matchAddedList, matchDeletedList := dc.getAllAcceptedInfoV2(
   107  			fullList,
   108  			matchList,
   109  			nil, nil, nil, nil, nil, nil, nil, nil, nil)
   110  		require.Equal(t, len(fullList), 1)
   111  		require.Equal(t, len(matchList), 1)
   112  		require.True(t, fullList["c1"])
   113  		require.True(t, matchList["c1"] != nil)
   114  		require.Equal(t, newCount, 1)
   115  		require.Equal(t, delCount, 0)
   116  		require.Equal(t, len(matchAddedList), 0)
   117  		require.Equal(t, len(matchDeletedList), 0)
   118  	}
   119  
   120  	// New container.
   121  	{
   122  		dc.updateContainer("c2", newContainer("c2"))
   123  
   124  		newCount, delCount, matchAddedList, matchDeletedList := dc.getAllAcceptedInfoV2(
   125  			fullList,
   126  			matchList,
   127  			nil, nil, nil, nil, nil, nil, nil, nil, nil)
   128  		require.Equal(t, len(fullList), 2)
   129  		require.Equal(t, len(matchList), 2)
   130  		require.True(t, fullList["c1"])
   131  		require.True(t, fullList["c2"])
   132  		require.True(t, matchList["c1"] != nil)
   133  		require.True(t, matchList["c2"] != nil)
   134  		require.Equal(t, newCount, 1)
   135  		require.Equal(t, delCount, 0)
   136  		require.Equal(t, len(matchAddedList), 1)
   137  		require.Equal(t, len(matchDeletedList), 0)
   138  	}
   139  
   140  	// Delete container.
   141  	{
   142  		delete(dc.containerMap, "c1")
   143  
   144  		newCount, delCount, matchAddedList, matchDeletedList := dc.getAllAcceptedInfoV2(
   145  			fullList,
   146  			matchList,
   147  			nil, nil, nil, nil, nil, nil, nil, nil, nil)
   148  		require.Equal(t, len(fullList), 1)
   149  		require.Equal(t, len(matchList), 1)
   150  		require.True(t, fullList["c2"])
   151  		require.True(t, matchList["c2"] != nil)
   152  		require.Equal(t, newCount, 0)
   153  		require.Equal(t, delCount, 1)
   154  		require.Equal(t, len(matchAddedList), 0)
   155  		require.Equal(t, len(matchDeletedList), 1)
   156  	}
   157  
   158  	// New and Delete container.
   159  	{
   160  		dc.updateContainers(map[string]*DockerInfoDetail{
   161  			"c3": newContainer("c3"),
   162  			"c4": newContainer("c4"),
   163  		})
   164  		delete(dc.containerMap, "c2")
   165  
   166  		newCount, delCount, matchAddedList, matchDeletedList := dc.getAllAcceptedInfoV2(
   167  			fullList,
   168  			matchList,
   169  			nil, nil, nil, nil, nil, nil, nil, nil, nil)
   170  		require.Equal(t, len(fullList), 2)
   171  		require.Equal(t, len(matchList), 2)
   172  		require.True(t, fullList["c3"])
   173  		require.True(t, fullList["c4"])
   174  		require.True(t, matchList["c3"] != nil)
   175  		require.True(t, matchList["c4"] != nil)
   176  		require.Equal(t, newCount, 2)
   177  		require.Equal(t, delCount, 1)
   178  		require.Equal(t, len(matchAddedList), 2)
   179  		require.Equal(t, len(matchDeletedList), 1)
   180  	}
   181  
   182  	// With unmatched filter.
   183  	fullList = make(map[string]bool)
   184  	matchList = make(map[string]*DockerInfoDetail)
   185  	{
   186  		newCount, delCount, matchAddedList, matchDeletedList := dc.getAllAcceptedInfoV2(
   187  			fullList,
   188  			matchList,
   189  			map[string]string{
   190  				"label": "label",
   191  			},
   192  			nil, nil, nil, nil, nil, nil, nil, nil)
   193  		require.Equal(t, len(fullList), 2)
   194  		require.Equal(t, len(matchList), 0)
   195  		require.True(t, fullList["c3"])
   196  		require.True(t, fullList["c4"])
   197  		require.Equal(t, newCount, 0)
   198  		require.Equal(t, delCount, 0)
   199  		require.Equal(t, len(matchAddedList), 0)
   200  		require.Equal(t, len(matchDeletedList), 0)
   201  	}
   202  
   203  	// Delete unmatched container.
   204  	{
   205  		delete(dc.containerMap, "c3")
   206  
   207  		newCount, delCount, matchAddedList, matchDeletedList := dc.getAllAcceptedInfoV2(
   208  			fullList,
   209  			matchList,
   210  			map[string]string{
   211  				"label": "label",
   212  			},
   213  			nil, nil, nil, nil, nil, nil, nil, nil)
   214  		require.Equal(t, len(fullList), 1)
   215  		require.Equal(t, len(matchList), 0)
   216  		require.True(t, fullList["c4"])
   217  		require.Equal(t, newCount, 0)
   218  		require.Equal(t, delCount, 0)
   219  		require.Equal(t, len(matchAddedList), 0)
   220  		require.Equal(t, len(matchDeletedList), 0)
   221  	}
   222  }
   223  
   224  func TestK8SInfo_IsMatch(t *testing.T) {
   225  	type fields struct {
   226  		Namespace       string
   227  		Pod             string
   228  		ContainerName   string
   229  		Labels          map[string]string
   230  		PausedContainer bool
   231  	}
   232  	type args struct {
   233  		filter *K8SFilter
   234  	}
   235  	filter := func(ns, pod, container string, includeK8sLabels, excludeK8sLabels map[string]string) *K8SFilter {
   236  		filter, _ := CreateK8SFilter(ns, pod, container, includeK8sLabels, excludeK8sLabels)
   237  		return filter
   238  	}
   239  	tests := []struct {
   240  		name   string
   241  		fields fields
   242  		args   args
   243  		want   bool
   244  	}{
   245  		{
   246  			name: "namespaceMatch",
   247  			fields: fields{
   248  				Namespace:     "ns",
   249  				Pod:           "pod",
   250  				ContainerName: "container",
   251  				Labels: map[string]string{
   252  					"a": "b",
   253  				},
   254  				PausedContainer: false,
   255  			},
   256  			args: args{
   257  				filter: filter("^ns$", "", "", nil, nil),
   258  			},
   259  			want: true,
   260  		},
   261  		{
   262  			name: "podMatch",
   263  			fields: fields{
   264  				Namespace:     "ns",
   265  				Pod:           "pod",
   266  				ContainerName: "container",
   267  				Labels: map[string]string{
   268  					"a": "b",
   269  				},
   270  				PausedContainer: false,
   271  			},
   272  			args: args{
   273  				filter: filter("", "^pod$", "", nil, nil),
   274  			},
   275  			want: true,
   276  		},
   277  		{
   278  			name: "containerMatch",
   279  			fields: fields{
   280  				Namespace:     "ns",
   281  				Pod:           "pod",
   282  				ContainerName: "container",
   283  				Labels: map[string]string{
   284  					"a": "b",
   285  				},
   286  				PausedContainer: false,
   287  			},
   288  			args: args{
   289  				filter: filter("", "", "^container$", nil, nil),
   290  			},
   291  			want: true,
   292  		},
   293  		{
   294  			name: "includeLabelMatch",
   295  			fields: fields{
   296  				Namespace:     "ns",
   297  				Pod:           "pod",
   298  				ContainerName: "container",
   299  				Labels: map[string]string{
   300  					"a": "b",
   301  					"c": "d",
   302  				},
   303  				PausedContainer: false,
   304  			},
   305  			args: args{
   306  				filter: filter("^ns$", "", "", map[string]string{
   307  					"a": "b",
   308  					"e": "f",
   309  				}, nil),
   310  			},
   311  			want: true,
   312  		},
   313  		{
   314  			name: "excludeLabelMatch",
   315  			fields: fields{
   316  				Namespace:     "ns",
   317  				Pod:           "pod",
   318  				ContainerName: "container",
   319  				Labels: map[string]string{
   320  					"a": "b",
   321  					"c": "d",
   322  				},
   323  				PausedContainer: false,
   324  			},
   325  			args: args{
   326  				filter: filter("^ns$", "", "", nil, map[string]string{
   327  					"a": "b",
   328  				}),
   329  			},
   330  			want: false,
   331  		},
   332  	}
   333  	for _, tt := range tests {
   334  		t.Run(tt.name, func(t *testing.T) {
   335  			info := &K8SInfo{
   336  				Namespace:       tt.fields.Namespace,
   337  				Pod:             tt.fields.Pod,
   338  				ContainerName:   tt.fields.ContainerName,
   339  				Labels:          tt.fields.Labels,
   340  				PausedContainer: tt.fields.PausedContainer,
   341  			}
   342  			assert.Equalf(t, tt.want, info.IsMatch(tt.args.filter), "IsMatch(%v)", tt.args.filter)
   343  		})
   344  	}
   345  }
   346  
   347  type DockerClientMock struct {
   348  	mock.Mock
   349  }
   350  
   351  type ContainerHelperMock struct {
   352  	mock.Mock
   353  }
   354  
   355  // Events 实现了 DockerClient 的 Events 方法
   356  func (m *DockerClientMock) Events(ctx context.Context, options types.EventsOptions) (<-chan events.Message, <-chan error) {
   357  	args := m.Called(ctx, options)
   358  	return args.Get(0).(chan events.Message), args.Get(1).(chan error)
   359  }
   360  
   361  func (m *DockerClientMock) ImageInspectWithRaw(ctx context.Context, imageID string) (types.ImageInspect, []byte, error) {
   362  	args := m.Called(ctx, imageID)
   363  	return args.Get(0).(types.ImageInspect), args.Get(1).([]byte), args.Error(2)
   364  }
   365  
   366  func (m *DockerClientMock) ContainerInspect(ctx context.Context, containerID string) (types.ContainerJSON, error) {
   367  	args := m.Called(ctx, containerID)
   368  	return args.Get(0).(types.ContainerJSON), args.Error(1)
   369  }
   370  func (m *DockerClientMock) ContainerList(ctx context.Context, options types.ContainerListOptions) ([]types.Container, error) {
   371  	args := m.Called(ctx, options)
   372  	return args.Get(0).([]types.Container), args.Error(1)
   373  }
   374  
   375  func (m *ContainerHelperMock) ContainerProcessAlive(pid int) bool {
   376  	args := m.Called(pid)
   377  	return args.Get(0).(bool)
   378  }
   379  
   380  func TestContainerCenterEvents(t *testing.T) {
   381  	containerCenterInstance = &ContainerCenter{}
   382  	containerCenterInstance.imageCache = make(map[string]string)
   383  	containerCenterInstance.containerMap = make(map[string]*DockerInfoDetail)
   384  
   385  	mockClient := DockerClientMock{}
   386  	containerCenterInstance.client = &mockClient
   387  
   388  	containerHelper := ContainerHelperMock{}
   389  
   390  	// 创建一个模拟的事件通道
   391  	eventChan := make(chan events.Message, 1)
   392  	errChan := make(chan error, 1)
   393  
   394  	mockClient.On("Events", mock.Anything, mock.Anything).Return(eventChan, errChan)
   395  
   396  	go containerCenterInstance.eventListener()
   397  
   398  	containerHelper.On("ContainerProcessAlive", mock.Anything).Return(false).Once()
   399  	mockClient.On("ContainerInspect", mock.Anything, "event1").Return(types.ContainerJSON{
   400  		ContainerJSONBase: &types.ContainerJSONBase{
   401  			ID:    "event1",
   402  			Name:  "name1",
   403  			State: &types.ContainerState{},
   404  		},
   405  		Config: &container.Config{
   406  			Env: make([]string, 0),
   407  		},
   408  	}, nil).Once()
   409  
   410  	eventChan <- events.Message{ID: "event1", Status: "rename"}
   411  
   412  	time.Sleep(5 * time.Second)
   413  	containerLen := len(containerCenterInstance.containerMap)
   414  	assert.Equal(t, 1, containerLen)
   415  
   416  	containerHelper.On("ContainerProcessAlive", mock.Anything).Return(true).Once()
   417  	mockClient.On("ContainerInspect", mock.Anything, "event1").Return(types.ContainerJSON{
   418  		ContainerJSONBase: &types.ContainerJSONBase{
   419  			ID:    "event1",
   420  			Name:  "start",
   421  			State: &types.ContainerState{},
   422  		},
   423  		Config: &container.Config{
   424  			Env: make([]string, 0),
   425  		},
   426  	}, nil).Once()
   427  	eventChan <- events.Message{ID: "event1", Status: "start"}
   428  
   429  	time.Sleep(5 * time.Second)
   430  	// 设置期望
   431  	close(eventChan)
   432  
   433  	containerLen = len(containerCenterInstance.containerMap)
   434  	assert.Equal(t, 1, containerLen)
   435  }
   436  
   437  func TestContainerCenterFetchAll(t *testing.T) {
   438  	containerCenterInstance = &ContainerCenter{}
   439  	containerCenterInstance.imageCache = make(map[string]string)
   440  	containerCenterInstance.containerMap = make(map[string]*DockerInfoDetail)
   441  
   442  	mockClient := DockerClientMock{}
   443  	containerCenterInstance.client = &mockClient
   444  
   445  	containerHelper := ContainerHelperMock{}
   446  
   447  	mockContainerListResult := []types.Container{
   448  		{ID: "id1"},
   449  		{ID: "id2"},
   450  		{ID: "id3"},
   451  	}
   452  
   453  	containerHelper.On("ContainerProcessAlive", mock.Anything).Return(true)
   454  
   455  	mockClient.On("ContainerList", mock.Anything, mock.Anything).Return(mockContainerListResult, nil).Once()
   456  
   457  	mockClient.On("ContainerInspect", mock.Anything, "id1").Return(types.ContainerJSON{
   458  		ContainerJSONBase: &types.ContainerJSONBase{
   459  			ID:    "id1",
   460  			Name:  "event_name1",
   461  			State: &types.ContainerState{},
   462  		},
   463  		Config: &container.Config{
   464  			Env: make([]string, 0),
   465  		},
   466  	}, nil).Once()
   467  	mockClient.On("ContainerInspect", mock.Anything, "id2").Return(types.ContainerJSON{
   468  		ContainerJSONBase: &types.ContainerJSONBase{
   469  			ID:    "id2",
   470  			Name:  "event_name2",
   471  			State: &types.ContainerState{},
   472  		},
   473  		Config: &container.Config{
   474  			Env: make([]string, 0),
   475  		},
   476  	}, nil).Once()
   477  	// one failed inspect
   478  	mockClient.On("ContainerInspect", mock.Anything, "id3").Return(types.ContainerJSON{}, errors.New("id3 not exist")).Times(3)
   479  
   480  	err := containerCenterInstance.fetchAll()
   481  	assert.Nil(t, err)
   482  
   483  	containerLen := len(containerCenterInstance.containerMap)
   484  	assert.Equal(t, 2, containerLen)
   485  
   486  	mockContainerListResult2 := []types.Container{
   487  		{ID: "id4"},
   488  		{ID: "id5"},
   489  	}
   490  
   491  	mockClient.On("ContainerList", mock.Anything, mock.Anything).Return(mockContainerListResult2, nil).Once()
   492  
   493  	mockClient.On("ContainerInspect", mock.Anything, "id4").Return(types.ContainerJSON{
   494  		ContainerJSONBase: &types.ContainerJSONBase{
   495  			ID:    "id4",
   496  			Name:  "event_name4",
   497  			State: &types.ContainerState{},
   498  		},
   499  		Config: &container.Config{
   500  			Env: make([]string, 0),
   501  		},
   502  	}, nil).Once()
   503  
   504  	mockClient.On("ContainerInspect", mock.Anything, "id5").Return(types.ContainerJSON{
   505  		ContainerJSONBase: &types.ContainerJSONBase{
   506  			ID:    "id5",
   507  			Name:  "event_name5",
   508  			State: &types.ContainerState{},
   509  		},
   510  		Config: &container.Config{
   511  			Env: make([]string, 0),
   512  		},
   513  	}, nil).Once()
   514  
   515  	err = containerCenterInstance.fetchAll()
   516  	assert.Nil(t, err)
   517  
   518  	containerLen = len(containerCenterInstance.containerMap)
   519  	assert.Equal(t, 4, containerLen)
   520  }
   521  
   522  func TestContainerCenterFetchAllAndOne(t *testing.T) {
   523  	containerCenterInstance = &ContainerCenter{}
   524  	containerCenterInstance.imageCache = make(map[string]string)
   525  	containerCenterInstance.containerMap = make(map[string]*DockerInfoDetail)
   526  
   527  	mockClient := DockerClientMock{}
   528  	containerCenterInstance.client = &mockClient
   529  
   530  	containerHelper := ContainerHelperMock{}
   531  
   532  	mockContainerListResult := []types.Container{
   533  		{ID: "id1"},
   534  		{ID: "id2"},
   535  	}
   536  
   537  	mockClient.On("ContainerList", mock.Anything, mock.Anything).Return(mockContainerListResult, nil)
   538  
   539  	mockClient.On("ContainerInspect", mock.Anything, "id1").Return(types.ContainerJSON{
   540  		ContainerJSONBase: &types.ContainerJSONBase{
   541  			ID:    "id1",
   542  			Name:  "event_name1",
   543  			State: &types.ContainerState{},
   544  		},
   545  		Config: &container.Config{
   546  			Env: make([]string, 0),
   547  		},
   548  	}, nil)
   549  	mockClient.On("ContainerInspect", mock.Anything, "id2").Return(types.ContainerJSON{
   550  		ContainerJSONBase: &types.ContainerJSONBase{
   551  			ID:    "id2",
   552  			Name:  "event_name2",
   553  			State: &types.ContainerState{},
   554  		},
   555  		Config: &container.Config{
   556  			Env: make([]string, 0),
   557  		},
   558  	}, nil)
   559  
   560  	containerHelper.On("ContainerProcessAlive", mock.Anything).Return(true).Times(2)
   561  
   562  	err := containerCenterInstance.fetchAll()
   563  	assert.Nil(t, err)
   564  
   565  	containerCenterInstance.markRemove("id1")
   566  	containerCenterInstance.markRemove("id2")
   567  
   568  	containerHelper.On("ContainerProcessAlive", mock.Anything).Return(false).Times(2)
   569  	err = containerCenterInstance.fetchAll()
   570  	assert.Nil(t, err)
   571  
   572  	containerLen := len(containerCenterInstance.containerMap)
   573  	assert.Equal(t, 2, containerLen)
   574  
   575  	for _, container := range containerCenterInstance.containerMap {
   576  		assert.Equal(t, true, container.deleteFlag)
   577  	}
   578  }