a73x

internal/server/syncsvc/exposures_test.go

Ref:   Size: 3.2 KiB   History

package syncsvc

import (
	"testing"

	"github.com/a73x/eitri/internal/pb"
	"github.com/a73x/eitri/internal/server/registry"
	"github.com/a73x/eitri/internal/server/store"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/require"
)

// exposureVM places one live VM on the fixture's host.
func exposureVM(t *testing.T, f *fixture, id, name string) {
	t.Helper()
	require.NoError(t, f.st.CreateVM(store.VM{ID: id, HostID: f.host.ID, Name: name,
		ImageURL: "u", ImageSHA256: "s", VCPUs: 1, MemMB: 512, DiskGB: 5, PowerState: "running"}))
}

// TestSnapshotCarriesExposures pins that a host's desired state names every
// exposure it should be running, at snapshot level rather than nested in a VM:
// exposures are their own objects converging on their own cadence.
func TestSnapshotCarriesExposures(t *testing.T) {
	f := setup(t)
	exposureVM(t, f, "vm1", "web-1")

	e, err := f.st.CreateExposure("vm1", 8080, 30080, "tcp")
	require.NoError(t, err)

	snap, err := f.svc.buildSnapshot(f.host.ID)
	require.NoError(t, err)
	require.Len(t, snap.GetExposures(), 1)
	got := snap.GetExposures()[0]
	assert.Equal(t, e.ID, got.GetId())
	assert.Equal(t, "vm1", got.GetVmId())
	assert.Equal(t, uint32(8080), got.GetGuestPort())
	assert.Equal(t, uint32(30080), got.GetHostPort())
	assert.Equal(t, "tcp", got.GetProtocol())
}

// TestSnapshotDropsExposuresOfATombstonedVM pins that a listener stops being
// desired the moment its VM is on its way out, rather than when its row is
// finally reaped.
func TestSnapshotDropsExposuresOfATombstonedVM(t *testing.T) {
	f := setup(t)
	exposureVM(t, f, "vm1", "web-1")
	_, err := f.st.CreateExposure("vm1", 8080, 30080, "tcp")
	require.NoError(t, err)
	require.NoError(t, f.st.TombstoneVM("vm1"))

	snap, err := f.svc.buildSnapshot(f.host.ID)
	require.NoError(t, err)
	assert.Empty(t, snap.GetExposures())
}

// TestApplyReportFoldsExposureStateIntoTheRegistry pins the return leg: what a
// host says its listeners are doing is live state, held in memory beside the
// VM rows it reports in the same message.
func TestApplyReportFoldsExposureStateIntoTheRegistry(t *testing.T) {
	f := setup(t)

	f.svc.applyReport(f.host.ID, &pb.Report{
		Exposures: []*pb.ExposureStatus{
			{Id: "e1", State: "active"},
			{Id: "e2", State: "failed", Reason: "listen tcp 0.0.0.0:30080: bind: address already in use"},
		},
	})

	got, ok := f.reg.Get(f.host.ID)
	require.True(t, ok)
	require.Len(t, got.Report.Exposures, 2)
	assert.Equal(t, registry.ExposureStatus{ID: "e1", State: "active"}, got.Report.Exposures[0])
	assert.Equal(t, "failed", got.Report.Exposures[1].State)
	assert.Contains(t, got.Report.Exposures[1].Reason, "address already in use")
}

// TestApplyReportRecordsTheHostUplinkAddress pins that the address an operator
// dials is durable, and that a report which carries none leaves it alone.
func TestApplyReportRecordsTheHostUplinkAddress(t *testing.T) {
	f := setup(t)

	f.svc.applyReport(f.host.ID, &pb.Report{HostUplinkAddr: "192.168.0.190"})
	h, err := f.st.GetHost(f.host.ID)
	require.NoError(t, err)
	assert.Equal(t, "192.168.0.190", h.UplinkAddr)

	f.svc.applyReport(f.host.ID, &pb.Report{})
	h, err = f.st.GetHost(f.host.ID)
	require.NoError(t, err)
	assert.Equal(t, "192.168.0.190", h.UplinkAddr, "silence is not a statement")
}