mirror of
https://github.com/containerd/containerd.git
synced 2026-06-24 08:48:48 +00:00
258 lines
7.4 KiB
Go
258 lines
7.4 KiB
Go
/*
|
|
Copyright The containerd Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package integration
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/containerd/nri/pkg/api"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/vishvananda/netlink"
|
|
"github.com/vishvananda/netns"
|
|
)
|
|
|
|
// Test NRI networking capabilities.
|
|
func TestNriPluginNetworkingSynchronization(t *testing.T) {
|
|
skipNriTestIfNecessary(t)
|
|
|
|
t.Log("Test that NRI plugins get pod networking attributes on synchronization.")
|
|
|
|
var (
|
|
tc = &nriTest{
|
|
t: t,
|
|
}
|
|
podCount = 3
|
|
ctrPerPod = 2
|
|
podPrefix = "pod-net-sync"
|
|
)
|
|
|
|
tc.setup()
|
|
|
|
for i := range podCount {
|
|
podID := tc.runPod(fmt.Sprintf("%s%d", podPrefix, i))
|
|
for j := range ctrPerPod {
|
|
tc.startContainer(podID, fmt.Sprintf("ctr%d", j))
|
|
}
|
|
}
|
|
|
|
numPods := 0
|
|
// override hooks are executed after the sync events, use a channel to synchronize the test
|
|
syncCh := make(chan struct{})
|
|
plugin := &mockPlugin{
|
|
synchronize: func(mp *mockPlugin, pods []*api.PodSandbox, c []*api.Container) ([]*api.ContainerUpdate, error) {
|
|
defer close(syncCh)
|
|
for _, pod := range pods {
|
|
t.Logf("synchronize pod %s/%s: namespace=%s ips=%v", pod.GetNamespace(), pod.GetName(), getNetworkNamespace(pod), pod.GetIps())
|
|
// only process the pods created by this test
|
|
if !strings.HasPrefix(pod.GetName(), podPrefix) {
|
|
t.Logf("DEBUG pod namespace %s name %s tc.namespace %s", pod.GetNamespace(), pod.GetName(), tc.namespace)
|
|
continue
|
|
}
|
|
numPods++
|
|
// get the pod network namespace
|
|
ns := getNetworkNamespace(pod)
|
|
// test only creates pods with network
|
|
require.NotEmpty(t, ns)
|
|
require.ElementsMatch(t, pod.GetIps(), getNetworkNamespaceIPs(ns))
|
|
}
|
|
return nil, nil
|
|
},
|
|
}
|
|
|
|
tc.connectNewPlugin(plugin)
|
|
|
|
err := plugin.Wait(PluginSynchronized, time.After(pluginSyncTimeout))
|
|
require.NoError(t, err, "plugin sync wait")
|
|
|
|
for _, id := range tc.pods {
|
|
for _, plugin := range tc.plugins {
|
|
_, ok := plugin.pods[id]
|
|
require.True(tc.t, ok, "runtime sync of pod "+id)
|
|
}
|
|
}
|
|
|
|
for _, id := range tc.ctrs {
|
|
for _, plugin := range tc.plugins {
|
|
_, ok := plugin.ctrs[id]
|
|
require.True(t, ok, "runtime sync of container "+id)
|
|
}
|
|
}
|
|
|
|
select {
|
|
case <-syncCh:
|
|
case <-time.After(pluginSyncTimeout):
|
|
t.Fatalf("test timed out waiting for the plugin to sync")
|
|
}
|
|
require.Equal(t, numPods, podCount)
|
|
}
|
|
|
|
func TestNriPluginNetworkingLifecycle(t *testing.T) {
|
|
skipNriTestIfNecessary(t)
|
|
|
|
t.Log("Test that NRI plugins get pod networking attributes during the pod lifecycle.")
|
|
|
|
var (
|
|
tc = &nriTest{
|
|
t: t,
|
|
}
|
|
podName = "pod-net-lifecycle"
|
|
)
|
|
|
|
tc.setup()
|
|
|
|
hookExecs := 0
|
|
// override hooks are executed after the sync events, use channels to synchronize the test
|
|
runPodSandboxCh := make(chan struct{})
|
|
stopPodSandboxCh := make(chan struct{})
|
|
removePodSandboxCh := make(chan struct{})
|
|
|
|
plugin := &mockPlugin{
|
|
runPodSandbox: func(mp *mockPlugin, pod *api.PodSandbox) error {
|
|
t.Logf("runPodSandbox pod %s/%s: namespace=%s ips=%v", pod.GetNamespace(), pod.GetName(), getNetworkNamespace(pod), pod.GetIps())
|
|
if pod.Name != podName {
|
|
return nil
|
|
}
|
|
defer close(runPodSandboxCh)
|
|
// get the pod network namespace
|
|
ns := getNetworkNamespace(pod)
|
|
// test only creates pods with network
|
|
require.NotEmpty(t, ns)
|
|
require.ElementsMatch(t, pod.GetIps(), getNetworkNamespaceIPs(ns))
|
|
hookExecs++
|
|
return nil
|
|
},
|
|
stopPodSandbox: func(mp *mockPlugin, pod *api.PodSandbox) error {
|
|
t.Logf("stopPodSandbox pod %s/%s: namespace=%s ips=%v", pod.GetNamespace(), pod.GetName(), getNetworkNamespace(pod), pod.GetIps())
|
|
if pod.Name != podName {
|
|
return nil
|
|
}
|
|
defer close(stopPodSandboxCh)
|
|
// get the pod network namespace
|
|
ns := getNetworkNamespace(pod)
|
|
// test only creates pods with network
|
|
require.NotEmpty(t, ns)
|
|
require.ElementsMatch(t, pod.GetIps(), getNetworkNamespaceIPs(ns))
|
|
hookExecs++
|
|
return nil
|
|
},
|
|
removePodSandbox: func(mp *mockPlugin, pod *api.PodSandbox) error {
|
|
t.Logf("removePodSandbox pod %s/%s: namespace=%s ips=%v", pod.GetNamespace(), pod.GetName(), getNetworkNamespace(pod), pod.GetIps())
|
|
if pod.Name != podName {
|
|
return nil
|
|
}
|
|
defer close(removePodSandboxCh)
|
|
// get the pod network namespace
|
|
ns := getNetworkNamespace(pod)
|
|
// test only creates pods with network but at this point networking namespace is not present
|
|
require.Empty(t, ns)
|
|
// the Pod assigned IPs are still available
|
|
require.NotEmpty(t, pod.GetIps())
|
|
hookExecs++
|
|
return nil
|
|
},
|
|
}
|
|
|
|
tc.connectNewPlugin(plugin)
|
|
|
|
err := plugin.Wait(PluginSynchronized, time.After(pluginSyncTimeout))
|
|
require.NoError(t, err, "plugin sync wait")
|
|
|
|
sbConfig := PodSandboxConfig(podName, tc.namespace)
|
|
podID, err := runtimeService.RunPodSandbox(sbConfig, *runtimeHandler)
|
|
require.NoError(t, err)
|
|
|
|
err = plugin.Wait(&Event{Pod: podID, Type: EventType(RunPodSandbox)}, time.After(pluginSyncTimeout))
|
|
require.NoError(t, err, "plugin sync wait")
|
|
select {
|
|
case <-runPodSandboxCh:
|
|
case <-time.After(pluginSyncTimeout):
|
|
t.Fatalf("test timed out waiting for the RunPodSandbox hook to be executed")
|
|
}
|
|
|
|
assert.NoError(t, runtimeService.StopPodSandbox(podID))
|
|
err = plugin.Wait(&Event{Pod: podID, Type: EventType(StopPodSandbox)}, time.After(pluginSyncTimeout))
|
|
require.NoError(t, err, "plugin sync wait")
|
|
select {
|
|
case <-stopPodSandboxCh:
|
|
case <-time.After(pluginSyncTimeout):
|
|
t.Fatalf("test timed out waiting for the StopPodSandbox hook to be executed")
|
|
}
|
|
|
|
assert.NoError(t, runtimeService.RemovePodSandbox(podID))
|
|
err = plugin.Wait(&Event{Pod: podID, Type: EventType(RemovePodSandbox)}, time.After(pluginSyncTimeout))
|
|
require.NoError(t, err, "plugin sync wait")
|
|
select {
|
|
case <-removePodSandboxCh:
|
|
case <-time.After(pluginSyncTimeout):
|
|
t.Fatalf("test timed out waiting for the RemovePodSandbox hook to be executed")
|
|
}
|
|
|
|
if hookExecs != 3 {
|
|
t.Fatalf("expected 3 hooks executed only got %d", hookExecs)
|
|
}
|
|
|
|
}
|
|
|
|
func getNetworkNamespace(pod *api.PodSandbox) string {
|
|
// get the pod network namespace
|
|
for _, namespace := range pod.Linux.GetNamespaces() {
|
|
if namespace.Type == "network" {
|
|
return namespace.Path
|
|
}
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func getNetworkNamespaceIPs(nsPath string) []string {
|
|
ips := []string{}
|
|
sandboxNs, err := netns.GetFromPath(nsPath)
|
|
if err != nil {
|
|
return ips
|
|
}
|
|
// to avoid golang problem with goroutines we create the socket in the
|
|
// namespace and use it directly
|
|
nhNs, err := netlink.NewHandleAt(sandboxNs)
|
|
if err != nil {
|
|
return ips
|
|
}
|
|
|
|
// there is a convention the interface inside the Pod is always named eth0
|
|
// internal/cri/server/helpers.go: defaultIfName = "eth0"
|
|
nsLink, err := nhNs.LinkByName("eth0")
|
|
if err != nil {
|
|
return ips
|
|
}
|
|
addrs, err := nhNs.AddrList(nsLink, netlink.FAMILY_ALL)
|
|
if err != nil {
|
|
return ips
|
|
}
|
|
for _, addr := range addrs {
|
|
// ignore link local and loopback addresses
|
|
// those are not added by the CNI
|
|
if addr.IP.IsGlobalUnicast() {
|
|
ips = append(ips, addr.IP.String())
|
|
}
|
|
}
|
|
return ips
|
|
}
|