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 }