a73x

internal/agent/reconcile/minversion_test.go

Ref:   Size: 3.0 KiB   History

package reconcile

import (
	"context"
	"testing"

	"github.com/stretchr/testify/require"
)

// An agent below the snapshot's floor must not materialise anything from it:
// every live VM reports failed with a reason naming both versions, nothing is
// dispatched, and the epoch is still accepted so the report is not fenced.
func TestStepRefusesSnapshotAboveAgentVersion(t *testing.T) {
	f := setup(t)
	f.eng.AgentVersion = "v0.0.6"
	s := snap(1, vm("vm1"))
	s.MinAgentVersion = "v0.0.7"

	rep := f.eng.Step(context.Background(), s)

	// Settle any worker the guard failed to prevent, so the assertion below
	// reads a finished tick rather than racing one.
	f.eng.manager().waitIdle()

	require.False(t, rep.FenceViolation)
	require.EqualValues(t, 1, rep.LastSeenEpoch)
	require.Len(t, rep.Vms, 1)
	require.Equal(t, "failed", rep.Vms[0].Phase)
	require.Contains(t, rep.Vms[0].LastError, "v0.0.7")
	require.Contains(t, rep.Vms[0].LastError, "v0.0.6")
	require.Empty(t, f.prov.prepared, "nothing is materialised from a snapshot the agent cannot read")
}

// The refusal asserts nothing it did not look at. It runs before any record is
// read, so it cannot know whether a guest is running — and the control plane
// PERSISTS what a report claims. An empty power state is the wire's "no
// observation"; "stopped" would be a fleet-wide lie about power, showing an
// operator healthy VMs as dark because their host is one release behind.
func TestRefusedSnapshotClaimsNoPowerState(t *testing.T) {
	f := setup(t)
	f.eng.AgentVersion = "v0.0.6"
	s := snap(1, vm("vm1"))
	s.MinAgentVersion = "v0.0.7"

	rep := f.eng.Step(context.Background(), s)
	f.eng.manager().waitIdle()

	require.Len(t, rep.Vms, 1)
	require.Empty(t, rep.Vms[0].PowerState, "a refusal that inspected nothing must claim nothing about power")
}

// A tombstoned VM is already deleted. Hanging an upgrade-the-agent error on it
// would leave a spurious failure on a VM on its way out, which no operator can
// clear — so the refusal passes over it entirely.
func TestRefusedSnapshotSkipsTombstonedVMs(t *testing.T) {
	f := setup(t)
	f.eng.AgentVersion = "v0.0.6"
	s := snap(1, vm("vm1"), tombstoned(vm("vm2")))
	s.MinAgentVersion = "v0.0.7"

	rep := f.eng.Step(context.Background(), s)
	f.eng.manager().waitIdle()

	require.Len(t, rep.Vms, 1, "the tombstoned VM earns no row")
	require.Equal(t, "vm1", rep.Vms[0].VmId)
}

// An unstamped agent ("dev") is never floored: it is the build every developer
// runs, and version.Less leaves unparsable versions unordered. The VM must be
// materialised exactly as it would be with no floor at all — asserting on the
// work done, not merely on an empty report, which an unreached snapshot would
// also produce.
func TestStepDoesNotFloorUnstampedAgent(t *testing.T) {
	f := setup(t)
	f.eng.AgentVersion = "dev"
	s := snap(1, vm("vm1"))
	s.MinAgentVersion = "v0.0.7"

	rep := f.eng.Step(context.Background(), s)
	f.eng.manager().waitIdle()

	require.False(t, rep.FenceViolation)
	require.Equal(t, []string{"vm1"}, f.prov.prepared, "an unstamped agent converges the snapshot as if it named no floor")
}