From 78f7dce7291a1f4d1229c1eb4de7177a687cb30a Mon Sep 17 00:00:00 2001 From: Gunjan Vyas Date: Tue, 13 Jan 2026 17:34:24 +0530 Subject: [PATCH] Add notification feature documentation and unit tests - Document the --notification flag usage in README with examples - Add unit tests for NotificationSender Assisted by: Claude (Anthropic AI) Signed-off-by: Gunjan Vyas --- README.md | 38 +++++++++++++- pkg/notification/sender_test.go | 91 +++++++++++++++++++++++++++++++++ 2 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 pkg/notification/sender_test.go diff --git a/README.md b/README.md index d54168e8..351413df 100644 --- a/README.md +++ b/README.md @@ -210,5 +210,41 @@ This is the same behaviour as [slirp](https://wiki.qemu.org/index.php/Documentat 3. The tap device receives the packets and injects them in the kernel. 4. The http server receives the request and send back the response. -### Development +## Notifications + +`gvproxy` can send notifications over a unix socket about hypervisor +connections, and about network switch connections/disconnections. + +These notifications can be enabled with the `--notification unix://$NOTIF_PATH` argument. +`$NOTIF_PATH` is the path to a listening unix socket. +`gvproxy` will then send json messages on this socket. + +To receive notifications, 2 terminals need to be opened. + +### Terminal 1: + +```bash +$ nc -k -U -l /tmp/notification.sock +``` + +### Terminal 2: + +```bash +$ gvproxy --notification unix:///tmp/notification.sock +``` + +The terminal where `nc` is running will print: +```json +{"notification_type":"ready"} +{"notification_type":"connection_established","mac_address":"5a:94:ef:e4:0c:ee"} +{"notification_type":"connection_closed","mac_address":"5a:94:ef:e4:0c:ee"} +``` + +Notification types: +- `ready` - sent when gvproxy is ready to accept connections +- `connection_established` - sent when a VM connects (includes `mac_address`) +- `connection_closed` - sent when a VM disconnects (includes `mac_address`) +- `hypervisor_error` - sent on hypervisor errors + +## Development Developers who want to work on gvisor-tap-vsock should visit the [Development](./DEVELOPMENT.md) document. diff --git a/pkg/notification/sender_test.go b/pkg/notification/sender_test.go new file mode 100644 index 00000000..7933ce14 --- /dev/null +++ b/pkg/notification/sender_test.go @@ -0,0 +1,91 @@ +package notification + +import ( + "context" + "encoding/json" + "net" + "path/filepath" + "testing" + "time" + + "github.com/containers/gvisor-tap-vsock/pkg/types" + "github.com/stretchr/testify/assert" +) + +func TestNewNotificationSender_EmptySocket(t *testing.T) { + sender := NewNotificationSender("") + assert.Nil(t, sender.notificationCh) + assert.Empty(t, sender.socket) +} + +func TestNewNotificationSender_NonEmptySocket(t *testing.T) { + sender := NewNotificationSender("test.sock") + assert.NotNil(t, sender) + assert.Equal(t, "test.sock", sender.socket) + assert.NotNil(t, sender.notificationCh) +} + +func TestNotificationSender_NilChannel(t *testing.T) { + sender := NewNotificationSender("") + assert.Nil(t, sender.notificationCh) + + // should not panic + sender.Send(types.NotificationMessage{ + NotificationType: types.ConnectionEstablished, + MacAddress: "5a:94:ef:e4:0c:ee", + }) +} + +func TestNotificationSender_Success(t *testing.T) { + tmpDir := t.TempDir() + socketPath := filepath.Join(tmpDir, "test.sock") + listener, err := net.Listen("unix", socketPath) + assert.NoError(t, err) + defer listener.Close() + + expectedNotifications := []types.NotificationMessage{ + { + NotificationType: types.Ready, + }, + { + NotificationType: types.ConnectionEstablished, + MacAddress: "5a:94:ef:e4:0c:ee", + }, + { + NotificationType: types.HypervisorError, + }, + { + NotificationType: types.ConnectionClosed, + MacAddress: "5a:94:ef:e4:0c:ee", + }, + } + + for _, expectedNotification := range expectedNotifications { + t.Run(string(expectedNotification.NotificationType), func(t *testing.T) { + done := make(chan struct{}) + go func() { + defer close(done) + conn, err := listener.Accept() + assert.NoError(t, err) + assert.NotNil(t, conn) + defer conn.Close() + + dec := json.NewDecoder(conn) + var notification types.NotificationMessage + assert.NoError(t, dec.Decode(¬ification)) + assert.Equal(t, expectedNotification.NotificationType, notification.NotificationType) + assert.Equal(t, expectedNotification.MacAddress, notification.MacAddress) + }() + + sender := NewNotificationSender(socketPath) + go sender.Start(context.Background()) + + sender.Send(expectedNotification) + select { + case <-done: + case <-time.After(2 * time.Second): + t.Fatal("timeout waiting for notification") + } + }) + } +}