diff --git a/scheduler/feasible.go b/scheduler/feasible.go index 0d720b3af..ce952663d 100644 --- a/scheduler/feasible.go +++ b/scheduler/feasible.go @@ -28,6 +28,21 @@ const ( FilterConstraintDevices = "missing devices" ) +var ( + // predatesBridgeFingerprint returns true if the constraint matches a version + // of nomad that predates the addition of the bridge network finger-printer, + // which was added in Nomad v0.12 + predatesBridgeFingerprint = mustBridgeConstraint() +) + +func mustBridgeConstraint() version.Constraints { + versionC, err := version.NewConstraint("< 0.12") + if err != nil { + panic(err) + } + return versionC +} + // FeasibleIterator is used to iteratively yield nodes that // match feasibility constraints. The iterators may manage // some state for performance optimizations. @@ -343,6 +358,18 @@ func (c *NetworkChecker) SetNetwork(network *structs.NetworkResource) { func (c *NetworkChecker) Feasible(option *structs.Node) bool { if !c.hasNetwork(option) { + + // special case - if the client is running a version older than 0.12 but + // the server is 0.12 or newer, we need to maintain an upgrade path for + // jobs looking for a bridge network that will not have been fingerprinted + // on the client (which was added in 0.12) + if c.networkMode == "bridge" { + sv, err := version.NewSemver(option.Attributes["nomad.version"]) + if err == nil && predatesBridgeFingerprint.Check(sv) { + return true + } + } + c.ctx.Metrics().FilterNode(option, "missing network") return false } diff --git a/scheduler/feasible_test.go b/scheduler/feasible_test.go index 2aef98b50..9caaa72cf 100644 --- a/scheduler/feasible_test.go +++ b/scheduler/feasible_test.go @@ -399,14 +399,19 @@ func TestCSIVolumeChecker(t *testing.T) { func TestNetworkChecker(t *testing.T) { _, ctx := testContext(t) - nodes := []*structs.Node{ - mock.Node(), - mock.Node(), - mock.Node(), + + node := func(mode string) *structs.Node { + n := mock.Node() + n.NodeResources.Networks = append(n.NodeResources.Networks, &structs.NetworkResource{Mode: mode}) + n.Attributes["nomad.version"] = "0.12.0" // mock version is 0.5.0 + return n + } + + nodes := []*structs.Node{ + node("bridge"), + node("bridge"), + node("cni/mynet"), } - nodes[0].NodeResources.Networks = append(nodes[0].NodeResources.Networks, &structs.NetworkResource{Mode: "bridge"}) - nodes[1].NodeResources.Networks = append(nodes[1].NodeResources.Networks, &structs.NetworkResource{Mode: "bridge"}) - nodes[2].NodeResources.Networks = append(nodes[2].NodeResources.Networks, &structs.NetworkResource{Mode: "cni/mynet"}) checker := NewNetworkChecker(ctx) cases := []struct { @@ -439,6 +444,36 @@ func TestNetworkChecker(t *testing.T) { } } +func TestNetworkChecker_bridge_upgrade_path(t *testing.T) { + _, ctx := testContext(t) + + t.Run("older client", func(t *testing.T) { + // Create a client that is still on v0.11, which does not have the bridge + // network finger-printer (and thus no bridge network resource) + oldClient := mock.Node() + oldClient.Attributes["nomad.version"] = "0.11.0" + + checker := NewNetworkChecker(ctx) + checker.SetNetwork(&structs.NetworkResource{Mode: "bridge"}) + + ok := checker.Feasible(oldClient) + require.True(t, ok) + }) + + t.Run("updated client", func(t *testing.T) { + // Create a client that is updated to 0.12, but did not detect a bridge + // network resource. + oldClient := mock.Node() + oldClient.Attributes["nomad.version"] = "0.12.0" + + checker := NewNetworkChecker(ctx) + checker.SetNetwork(&structs.NetworkResource{Mode: "bridge"}) + + ok := checker.Feasible(oldClient) + require.False(t, ok) + }) +} + func TestDriverChecker_DriverInfo(t *testing.T) { _, ctx := testContext(t) nodes := []*structs.Node{