istio.io/istio@v0.0.0-20240520182934-d79c90f27776/cni/pkg/nodeagent/ztunnelserver_test.go (about)

     1  // Copyright Istio 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 nodeagent
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"net"
    21  	"os"
    22  	"sync/atomic"
    23  	"testing"
    24  	"time"
    25  
    26  	"golang.org/x/sys/unix"
    27  	"google.golang.org/protobuf/proto"
    28  	v1 "k8s.io/api/core/v1"
    29  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    30  	"k8s.io/apimachinery/pkg/types"
    31  
    32  	"istio.io/istio/pkg/monitoring/monitortest"
    33  	"istio.io/istio/pkg/test/util/assert"
    34  	"istio.io/istio/pkg/zdsapi"
    35  )
    36  
    37  var ztunnelTestCounter atomic.Uint32
    38  
    39  func TestZtunnelSendsPodSnapshot(t *testing.T) {
    40  	ztunnelKeepAliveCheckInterval = time.Second / 10
    41  	mt := monitortest.New(t)
    42  	setupLogging()
    43  	ctx, cancel := context.WithCancel(context.Background())
    44  	defer cancel()
    45  
    46  	fixture := connect(ctx)
    47  	ztunClient := fixture.ztunClient
    48  	uid := fixture.uid
    49  
    50  	m, fds := readRequest(t, ztunClient)
    51  	// we got am essage from ztun, so it should have observed us being connected
    52  	mt.Assert(ztunnelConnected.Name(), nil, monitortest.Exactly(1))
    53  
    54  	// we should get the fd to dev null. note that we can't assert the fd number
    55  	// as the kernel may have given us a different number that refers to the same file.
    56  	assert.Equal(t, len(fds), 1)
    57  	// in theory we should close the fd, but it's just a test..
    58  
    59  	assert.Equal(t, m.Payload.(*zdsapi.WorkloadRequest_Add).Add.Uid, uid)
    60  	// send ack so the server doesn't wait for us.
    61  	sendAck(ztunClient)
    62  
    63  	// second message should be the snap sent message
    64  	m, fds = readRequest(t, ztunClient)
    65  	assert.Equal(t, len(fds), 0)
    66  
    67  	sent := m.Payload.(*zdsapi.WorkloadRequest_SnapshotSent).SnapshotSent
    68  	if sent == nil {
    69  		panic("expected snapshot sent")
    70  	}
    71  	sendAck(ztunClient)
    72  	ztunClient.Close()
    73  	// this will retry for a bit, so shouldn't flake
    74  	mt.Assert(ztunnelConnected.Name(), nil, monitortest.Exactly(0))
    75  }
    76  
    77  func TestZtunnelRemovePod(t *testing.T) {
    78  	ztunnelKeepAliveCheckInterval = time.Second / 10
    79  	mt := monitortest.New(t)
    80  	setupLogging()
    81  	ctx, cancel := context.WithCancel(context.Background())
    82  	defer cancel()
    83  
    84  	fixture := connect(ctx)
    85  	ztunClient := fixture.ztunClient
    86  	uid := fixture.uid
    87  	// read initial pod add
    88  	readRequest(t, ztunClient)
    89  	sendAck(ztunClient)
    90  	// read snapshot sent
    91  	m, fds := readRequest(t, ztunClient)
    92  	assert.Equal(t, len(fds), 0)
    93  	sent := m.Payload.(*zdsapi.WorkloadRequest_SnapshotSent).SnapshotSent
    94  	if sent == nil {
    95  		panic("expected snapshot sent")
    96  	}
    97  	sendAck(ztunClient)
    98  
    99  	// now remove the pod
   100  	ztunnelServer := fixture.ztunServer
   101  	errChan := make(chan error)
   102  	go func() {
   103  		errChan <- ztunnelServer.PodDeleted(ctx, uid)
   104  	}()
   105  	// read the msg to delete from ztunnel
   106  	m, fds = readRequest(t, ztunClient)
   107  	assert.Equal(t, len(fds), 0)
   108  	assert.Equal(t, m.Payload.(*zdsapi.WorkloadRequest_Del).Del.Uid, uid)
   109  	sendAck(ztunClient)
   110  
   111  	assert.NoError(t, <-errChan)
   112  
   113  	ztunClient.Close()
   114  	// this will retry for a bit, so shouldn't flake
   115  	mt.Assert(ztunnelConnected.Name(), nil, monitortest.Exactly(0))
   116  }
   117  
   118  func TestZtunnelPodAdded(t *testing.T) {
   119  	ztunnelKeepAliveCheckInterval = time.Second / 10
   120  	mt := monitortest.New(t)
   121  	setupLogging()
   122  	ctx, cancel := context.WithCancel(context.Background())
   123  	defer cancel()
   124  
   125  	fixture := connect(ctx)
   126  	ztunClient := fixture.ztunClient
   127  	// read initial pod add
   128  	readRequest(t, ztunClient)
   129  	sendAck(ztunClient)
   130  	// read snapshot sent
   131  	m, fds := readRequest(t, ztunClient)
   132  	assert.Equal(t, len(fds), 0)
   133  	sent := m.Payload.(*zdsapi.WorkloadRequest_SnapshotSent).SnapshotSent
   134  	if sent == nil {
   135  		panic("expected snapshot sent")
   136  	}
   137  	sendAck(ztunClient)
   138  
   139  	// now remove the pod
   140  	ztunnelServer := fixture.ztunServer
   141  	errChan := make(chan error)
   142  	pod2, ns2 := podAndNetns()
   143  	go func() {
   144  		errChan <- ztunnelServer.PodAdded(ctx, pod2, ns2)
   145  	}()
   146  	// read the msg to delete from ztunnel
   147  	m, fds = readRequest(t, ztunClient)
   148  	assert.Equal(t, len(fds), 1)
   149  	assert.Equal(t, m.Payload.(*zdsapi.WorkloadRequest_Add).Add.Uid, string(pod2.UID))
   150  	sendAck(ztunClient)
   151  
   152  	assert.NoError(t, <-errChan)
   153  
   154  	ztunClient.Close()
   155  	// this will retry for a bit, so shouldn't flake
   156  	mt.Assert(ztunnelConnected.Name(), nil, monitortest.Exactly(0))
   157  }
   158  
   159  func TestZtunnelPodKept(t *testing.T) {
   160  	ztunnelKeepAliveCheckInterval = time.Second / 10
   161  	mt := monitortest.New(t)
   162  	setupLogging()
   163  	ctx, cancel := context.WithCancel(context.Background())
   164  	defer cancel()
   165  
   166  	pods := &fakePodCache{}
   167  
   168  	pod, f := podAndNetns()
   169  	f.Close()
   170  	pods.pods = map[string]WorkloadInfo{
   171  		string(pod.UID): {}, // simulate unknown netns
   172  	}
   173  
   174  	fixture := connectWithPods(ctx, pods)
   175  	ztunClient := fixture.ztunClient
   176  	// read initial pod add
   177  	keep, fds := readRequest(t, ztunClient)
   178  	assert.Equal(t, len(fds), 0)
   179  	kept := keep.Payload.(*zdsapi.WorkloadRequest_Keep).Keep
   180  	if kept.Uid != string(pod.UID) {
   181  		panic("expected keep received")
   182  	}
   183  
   184  	sendAck(ztunClient)
   185  	// read snapshot sent
   186  	m, fds := readRequest(t, ztunClient)
   187  	assert.Equal(t, len(fds), 0)
   188  	sent := m.Payload.(*zdsapi.WorkloadRequest_SnapshotSent).SnapshotSent
   189  	if sent == nil {
   190  		panic("expected snapshot sent")
   191  	}
   192  	sendAck(ztunClient)
   193  
   194  	ztunClient.Close()
   195  	// this will retry for a bit, so shouldn't flake
   196  	mt.Assert(ztunnelConnected.Name(), nil, monitortest.Exactly(0))
   197  }
   198  
   199  func podAndNetns() (*v1.Pod, *fakeNs) {
   200  	devNull, err := os.Open(os.DevNull)
   201  	if err != nil {
   202  		panic(err)
   203  	}
   204  	// we can't close this now, because we need to pass it from the ztunnel server to the client
   205  	// it would leak, but this is a test, so we don't care
   206  	//	defer devNull.Close()
   207  
   208  	id := ztunnelTestCounter.Add(1)
   209  	pod := &v1.Pod{
   210  		ObjectMeta: metav1.ObjectMeta{
   211  			Name: fmt.Sprintf("name-%d", id),
   212  			UID:  types.UID(fmt.Sprintf("uid-%d", id)),
   213  		},
   214  		Spec:   v1.PodSpec{},
   215  		Status: v1.PodStatus{},
   216  	}
   217  	return pod, newFakeNs(devNull.Fd())
   218  }
   219  
   220  func connect(ctx context.Context) struct {
   221  	ztunClient *net.UnixConn
   222  	ztunServer *ztunnelServer
   223  	uid        string
   224  } {
   225  	pods := &fakePodCache{}
   226  
   227  	pod, ns := podAndNetns()
   228  	workload := WorkloadInfo{
   229  		Workload: podToWorkload(pod),
   230  		Netns:    ns,
   231  	}
   232  	pods.pods = map[string]WorkloadInfo{
   233  		string(pod.UID): workload,
   234  	}
   235  	ret := connectWithPods(ctx, pods)
   236  
   237  	return struct {
   238  		ztunClient *net.UnixConn
   239  		ztunServer *ztunnelServer
   240  		uid        string
   241  	}{ztunClient: ret.ztunClient, ztunServer: ret.ztunServer, uid: string(pod.UID)}
   242  }
   243  
   244  func connectWithPods(ctx context.Context, pods PodNetnsCache) struct {
   245  	ztunClient *net.UnixConn
   246  	ztunServer *ztunnelServer
   247  } {
   248  	// go uses @ instead of \0 for abstract unix sockets
   249  	addr := fmt.Sprintf("@testaddr%d", ztunnelTestCounter.Add(1))
   250  	ztun, err := newZtunnelServer(addr, pods)
   251  	if err != nil {
   252  		panic(err)
   253  	}
   254  	go ztun.Run(ctx)
   255  
   256  	// now as a client connect confirm we and get snapshot
   257  	resolvedAddr, err := net.ResolveUnixAddr("unixpacket", addr)
   258  	if err != nil {
   259  		panic(err)
   260  	}
   261  	ztunClient, err := net.DialUnix("unixpacket", nil, resolvedAddr)
   262  	if err != nil {
   263  		panic(err)
   264  	}
   265  
   266  	// send hello
   267  	sendHello(ztunClient)
   268  
   269  	return struct {
   270  		ztunClient *net.UnixConn
   271  		ztunServer *ztunnelServer
   272  	}{ztunClient: ztunClient, ztunServer: ztun}
   273  }
   274  
   275  func readRequest(t *testing.T, c *net.UnixConn) (*zdsapi.WorkloadRequest, []int) {
   276  	var oob [1024]byte
   277  	m, oobn, err := readProto[zdsapi.WorkloadRequest](c, time.Second, oob[:])
   278  	if err != nil {
   279  		panic(err)
   280  	}
   281  
   282  	receivedoob := oob[:oobn]
   283  	msgs, err := unix.ParseSocketControlMessage(receivedoob)
   284  	if err != nil {
   285  		panic(err)
   286  	}
   287  
   288  	// we should get 0 or 1 oob messages
   289  	if len(msgs) != 0 {
   290  		assert.Equal(t, len(msgs), 1)
   291  	}
   292  
   293  	var fdss []int
   294  	for _, msg := range msgs {
   295  		fds, err := unix.ParseUnixRights(&msg)
   296  		if err != nil {
   297  			panic(err)
   298  		}
   299  		fdss = append(fdss, fds...)
   300  	}
   301  	return m, fdss
   302  }
   303  
   304  func sendAck(c *net.UnixConn) {
   305  	ack := &zdsapi.WorkloadResponse{
   306  		Payload: &zdsapi.WorkloadResponse_Ack{
   307  			Ack: &zdsapi.Ack{},
   308  		},
   309  	}
   310  	data, err := proto.Marshal(ack)
   311  	if err != nil {
   312  		panic(err)
   313  	}
   314  	err = c.SetWriteDeadline(time.Now().Add(time.Second))
   315  	if err != nil {
   316  		panic(err)
   317  	}
   318  	c.Write(data)
   319  }
   320  
   321  func sendHello(c *net.UnixConn) {
   322  	ack := &zdsapi.ZdsHello{
   323  		Version: zdsapi.Version_V1,
   324  	}
   325  	data, err := proto.Marshal(ack)
   326  	if err != nil {
   327  		panic(err)
   328  	}
   329  	err = c.SetWriteDeadline(time.Now().Add(time.Second))
   330  	if err != nil {
   331  		panic(err)
   332  	}
   333  	c.Write(data)
   334  }
   335  
   336  type fakePodCache struct {
   337  	pods map[string]WorkloadInfo
   338  }
   339  
   340  func (f fakePodCache) ReadCurrentPodSnapshot() map[string]WorkloadInfo {
   341  	return f.pods
   342  }