make version checks specific to region (1.4.x) (#14912)
* One-time tokens are not replicated between regions, so we don't want to enforce that the version check across all of serf, just members in the same region. * Scheduler: Disconnected clients handling is specific to a single region, so we don't want to enforce that the version check across all of serf, just members in the same region. * Variables: enforce version check in Apply RPC * Cleans up a bunch of legacy checks. This changeset is specific to 1.4.x and the changes for previous versions of Nomad will be manually backported in a separate PR.
This commit is contained in:
parent
306b4dd38e
commit
3c78980b78
|
@ -0,0 +1,9 @@
|
||||||
|
```release-note:bug
|
||||||
|
variables: Fixed a bug where Nomad version checking was not enforced for writing to variables
|
||||||
|
```
|
||||||
|
```release-note:bug
|
||||||
|
acl: Fixed a bug where Nomad version checking for one-time tokens was enforced across regions
|
||||||
|
```
|
||||||
|
```release-note:bug
|
||||||
|
scheduler: Fixed a bug where version checking for disconnected clients handling was enforced across regions
|
||||||
|
```
|
|
@ -944,8 +944,8 @@ func (a *ACL) UpsertOneTimeToken(args *structs.OneTimeTokenUpsertRequest, reply
|
||||||
defer metrics.MeasureSince(
|
defer metrics.MeasureSince(
|
||||||
[]string{"nomad", "acl", "upsert_one_time_token"}, time.Now())
|
[]string{"nomad", "acl", "upsert_one_time_token"}, time.Now())
|
||||||
|
|
||||||
if !ServersMeetMinimumVersion(a.srv.Members(), minOneTimeAuthenticationTokenVersion, false) {
|
if !ServersMeetMinimumVersion(a.srv.Members(), a.srv.Region(), minOneTimeAuthenticationTokenVersion, false) {
|
||||||
return fmt.Errorf("All servers should be running version %v or later to use one-time authentication tokens", minAutopilotVersion)
|
return fmt.Errorf("All servers should be running version %v or later to use one-time authentication tokens", minOneTimeAuthenticationTokenVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot the state
|
// Snapshot the state
|
||||||
|
@ -996,8 +996,8 @@ func (a *ACL) ExchangeOneTimeToken(args *structs.OneTimeTokenExchangeRequest, re
|
||||||
defer metrics.MeasureSince(
|
defer metrics.MeasureSince(
|
||||||
[]string{"nomad", "acl", "exchange_one_time_token"}, time.Now())
|
[]string{"nomad", "acl", "exchange_one_time_token"}, time.Now())
|
||||||
|
|
||||||
if !ServersMeetMinimumVersion(a.srv.Members(), minOneTimeAuthenticationTokenVersion, false) {
|
if !ServersMeetMinimumVersion(a.srv.Members(), a.srv.Region(), minOneTimeAuthenticationTokenVersion, false) {
|
||||||
return fmt.Errorf("All servers should be running version %v or later to use one-time authentication tokens", minAutopilotVersion)
|
return fmt.Errorf("All servers should be running version %v or later to use one-time authentication tokens", minOneTimeAuthenticationTokenVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Snapshot the state
|
// Snapshot the state
|
||||||
|
@ -1053,8 +1053,8 @@ func (a *ACL) ExpireOneTimeTokens(args *structs.OneTimeTokenExpireRequest, reply
|
||||||
defer metrics.MeasureSince(
|
defer metrics.MeasureSince(
|
||||||
[]string{"nomad", "acl", "expire_one_time_tokens"}, time.Now())
|
[]string{"nomad", "acl", "expire_one_time_tokens"}, time.Now())
|
||||||
|
|
||||||
if !ServersMeetMinimumVersion(a.srv.Members(), minOneTimeAuthenticationTokenVersion, false) {
|
if !ServersMeetMinimumVersion(a.srv.Members(), a.srv.Region(), minOneTimeAuthenticationTokenVersion, false) {
|
||||||
return fmt.Errorf("All servers should be running version %v or later to use one-time authentication tokens", minAutopilotVersion)
|
return fmt.Errorf("All servers should be running version %v or later to use one-time authentication tokens", minOneTimeAuthenticationTokenVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check management level permissions
|
// Check management level permissions
|
||||||
|
|
|
@ -479,7 +479,7 @@ OUTER:
|
||||||
func (c *CoreScheduler) nodeReap(eval *structs.Evaluation, nodeIDs []string) error {
|
func (c *CoreScheduler) nodeReap(eval *structs.Evaluation, nodeIDs []string) error {
|
||||||
// For old clusters, send single deregistration messages COMPAT(0.11)
|
// For old clusters, send single deregistration messages COMPAT(0.11)
|
||||||
minVersionBatchNodeDeregister := version.Must(version.NewVersion("0.9.4"))
|
minVersionBatchNodeDeregister := version.Must(version.NewVersion("0.9.4"))
|
||||||
if !ServersMeetMinimumVersion(c.srv.Members(), minVersionBatchNodeDeregister, true) {
|
if !ServersMeetMinimumVersion(c.srv.Members(), c.srv.Region(), minVersionBatchNodeDeregister, true) {
|
||||||
for _, id := range nodeIDs {
|
for _, id := range nodeIDs {
|
||||||
req := structs.NodeDeregisterRequest{
|
req := structs.NodeDeregisterRequest{
|
||||||
NodeID: id,
|
NodeID: id,
|
||||||
|
|
|
@ -359,7 +359,7 @@ func (j *Job) Register(args *structs.JobRegisterRequest, reply *structs.JobRegis
|
||||||
|
|
||||||
// COMPAT(1.1.0): Remove the ServerMeetMinimumVersion check to always set args.Eval
|
// COMPAT(1.1.0): Remove the ServerMeetMinimumVersion check to always set args.Eval
|
||||||
// 0.12.1 introduced atomic eval job registration
|
// 0.12.1 introduced atomic eval job registration
|
||||||
if eval != nil && ServersMeetMinimumVersion(j.srv.Members(), minJobRegisterAtomicEvalVersion, false) {
|
if eval != nil && ServersMeetMinimumVersion(j.srv.Members(), j.srv.Region(), minJobRegisterAtomicEvalVersion, false) {
|
||||||
args.Eval = eval
|
args.Eval = eval
|
||||||
submittedEval = true
|
submittedEval = true
|
||||||
}
|
}
|
||||||
|
@ -848,7 +848,7 @@ func (j *Job) Deregister(args *structs.JobDeregisterRequest, reply *structs.JobD
|
||||||
}
|
}
|
||||||
|
|
||||||
// COMPAT(1.1.0): remove conditional and always set args.Eval
|
// COMPAT(1.1.0): remove conditional and always set args.Eval
|
||||||
if ServersMeetMinimumVersion(j.srv.Members(), minJobRegisterAtomicEvalVersion, false) {
|
if ServersMeetMinimumVersion(j.srv.Members(), j.srv.Region(), minJobRegisterAtomicEvalVersion, false) {
|
||||||
args.Eval = eval
|
args.Eval = eval
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -814,7 +814,7 @@ func (s *Server) schedulePeriodic(stopCh chan struct{}) {
|
||||||
s.evalBroker.Enqueue(s.coreJobEval(structs.CoreJobCSIVolumeClaimGC, index))
|
s.evalBroker.Enqueue(s.coreJobEval(structs.CoreJobCSIVolumeClaimGC, index))
|
||||||
}
|
}
|
||||||
case <-oneTimeTokenGC.C:
|
case <-oneTimeTokenGC.C:
|
||||||
if !ServersMeetMinimumVersion(s.Members(), minOneTimeAuthenticationTokenVersion, false) {
|
if !ServersMeetMinimumVersion(s.Members(), s.Region(), minOneTimeAuthenticationTokenVersion, false) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1922,7 +1922,7 @@ func (s *Server) getOrCreateAutopilotConfig() *structs.AutopilotConfig {
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
|
|
||||||
if !ServersMeetMinimumVersion(s.Members(), minAutopilotVersion, false) {
|
if !ServersMeetMinimumVersion(s.Members(), AllRegions, minAutopilotVersion, false) {
|
||||||
s.logger.Named("autopilot").Warn("can't initialize until all servers are above minimum version", "min_version", minAutopilotVersion)
|
s.logger.Named("autopilot").Warn("can't initialize until all servers are above minimum version", "min_version", minAutopilotVersion)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1949,7 +1949,7 @@ func (s *Server) getOrCreateSchedulerConfig() *structs.SchedulerConfiguration {
|
||||||
if config != nil {
|
if config != nil {
|
||||||
return config
|
return config
|
||||||
}
|
}
|
||||||
if !ServersMeetMinimumVersion(s.Members(), minSchedulerConfigVersion, false) {
|
if !ServersMeetMinimumVersion(s.Members(), s.Region(), minSchedulerConfigVersion, false) {
|
||||||
s.logger.Named("core").Warn("can't initialize scheduler config until all servers are above minimum version", "min_version", minSchedulerConfigVersion)
|
s.logger.Named("core").Warn("can't initialize scheduler config until all servers are above minimum version", "min_version", minSchedulerConfigVersion)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -1991,14 +1991,7 @@ func (s *Server) initializeKeyring(stopCh <-chan struct{}) {
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
|
|
||||||
members := s.serf.Members()
|
if ServersMeetMinimumVersion(s.serf.Members(), s.Region(), minVersionKeyring, true) {
|
||||||
regionMembers := []serf.Member{}
|
|
||||||
for _, member := range members {
|
|
||||||
if member.Tags["region"] == s.Region() {
|
|
||||||
regionMembers = append(regionMembers, member)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if ServersMeetMinimumVersion(regionMembers, minVersionKeyring, true) {
|
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2034,7 +2027,7 @@ func (s *Server) initializeKeyring(stopCh <-chan struct{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Server) generateClusterID() (string, error) {
|
func (s *Server) generateClusterID() (string, error) {
|
||||||
if !ServersMeetMinimumVersion(s.Members(), minClusterIDVersion, false) {
|
if !ServersMeetMinimumVersion(s.Members(), AllRegions, minClusterIDVersion, false) {
|
||||||
s.logger.Named("core").Warn("cannot initialize cluster ID until all servers are above minimum version", "min_version", minClusterIDVersion)
|
s.logger.Named("core").Warn("cannot initialize cluster ID until all servers are above minimum version", "min_version", minClusterIDVersion)
|
||||||
return "", fmt.Errorf("cluster ID cannot be created until all servers are above minimum version %s", minClusterIDVersion)
|
return "", fmt.Errorf("cluster ID cannot be created until all servers are above minimum version %s", minClusterIDVersion)
|
||||||
}
|
}
|
||||||
|
|
|
@ -247,7 +247,7 @@ func (op *Operator) AutopilotSetConfiguration(args *structs.AutopilotSetConfigRe
|
||||||
}
|
}
|
||||||
|
|
||||||
// All servers should be at or above 0.8.0 to apply this operatation
|
// All servers should be at or above 0.8.0 to apply this operatation
|
||||||
if !ServersMeetMinimumVersion(op.srv.Members(), minAutopilotVersion, false) {
|
if !ServersMeetMinimumVersion(op.srv.Members(), op.srv.Region(), minAutopilotVersion, false) {
|
||||||
return fmt.Errorf("All servers should be running version %v to update autopilot config", minAutopilotVersion)
|
return fmt.Errorf("All servers should be running version %v to update autopilot config", minAutopilotVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -315,7 +315,7 @@ func (op *Operator) SchedulerSetConfiguration(args *structs.SchedulerSetConfigRe
|
||||||
}
|
}
|
||||||
|
|
||||||
// All servers should be at or above 0.9.0 to apply this operation
|
// All servers should be at or above 0.9.0 to apply this operation
|
||||||
if !ServersMeetMinimumVersion(op.srv.Members(), minSchedulerConfigVersion, false) {
|
if !ServersMeetMinimumVersion(op.srv.Members(), op.srv.Region(), minSchedulerConfigVersion, false) {
|
||||||
return fmt.Errorf("All servers should be running version %v to update scheduler config", minSchedulerConfigVersion)
|
return fmt.Errorf("All servers should be running version %v to update scheduler config", minSchedulerConfigVersion)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -80,7 +80,7 @@ func (s *Server) DispatchJob(job *structs.Job) (*structs.Evaluation, error) {
|
||||||
eval.ModifyIndex = index
|
eval.ModifyIndex = index
|
||||||
|
|
||||||
// COMPAT(1.1): Remove in 1.1.0 - 0.12.1 introduced atomic eval job registration
|
// COMPAT(1.1): Remove in 1.1.0 - 0.12.1 introduced atomic eval job registration
|
||||||
if !ServersMeetMinimumVersion(s.Members(), minJobRegisterAtomicEvalVersion, false) {
|
if !ServersMeetMinimumVersion(s.Members(), s.Region(), minJobRegisterAtomicEvalVersion, false) {
|
||||||
// Create a new evaluation
|
// Create a new evaluation
|
||||||
eval.JobModifyIndex = index
|
eval.JobModifyIndex = index
|
||||||
update := &structs.EvalUpdateRequest{
|
update := &structs.EvalUpdateRequest{
|
||||||
|
|
|
@ -254,7 +254,7 @@ func (p *planner) applyPlan(plan *structs.Plan, result *structs.PlanResult, snap
|
||||||
|
|
||||||
preemptedJobIDs := make(map[structs.NamespacedID]struct{})
|
preemptedJobIDs := make(map[structs.NamespacedID]struct{})
|
||||||
|
|
||||||
if ServersMeetMinimumVersion(p.Members(), MinVersionPlanNormalization, true) {
|
if ServersMeetMinimumVersion(p.Members(), p.Region(), MinVersionPlanNormalization, true) {
|
||||||
// Initialize the allocs request using the new optimized log entry format.
|
// Initialize the allocs request using the new optimized log entry format.
|
||||||
// Determine the minimum number of updates, could be more if there
|
// Determine the minimum number of updates, could be more if there
|
||||||
// are multiple updates per node
|
// are multiple updates per node
|
||||||
|
|
|
@ -13,6 +13,7 @@ import (
|
||||||
"github.com/hashicorp/nomad/nomad/state"
|
"github.com/hashicorp/nomad/nomad/state"
|
||||||
"github.com/hashicorp/nomad/nomad/structs"
|
"github.com/hashicorp/nomad/nomad/structs"
|
||||||
"github.com/hashicorp/serf/serf"
|
"github.com/hashicorp/serf/serf"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
@ -143,15 +144,20 @@ func isNomadServer(m serf.Member) (bool, *serverParts) {
|
||||||
return true, parts
|
return true, parts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const AllRegions = ""
|
||||||
|
|
||||||
// ServersMeetMinimumVersion returns whether the Nomad servers are at least on the
|
// ServersMeetMinimumVersion returns whether the Nomad servers are at least on the
|
||||||
// given Nomad version. The checkFailedServers parameter specifies whether version
|
// given Nomad version. The checkFailedServers parameter specifies whether version
|
||||||
// for the failed servers should be verified.
|
// for the failed servers should be verified.
|
||||||
func ServersMeetMinimumVersion(members []serf.Member, minVersion *version.Version, checkFailedServers bool) bool {
|
func ServersMeetMinimumVersion(members []serf.Member, region string, minVersion *version.Version, checkFailedServers bool) bool {
|
||||||
for _, member := range members {
|
for _, member := range members {
|
||||||
if valid, parts := isNomadServer(member); valid && (parts.Status == serf.StatusAlive || (checkFailedServers && parts.Status == serf.StatusFailed)) {
|
valid, parts := isNomadServer(member)
|
||||||
|
if valid &&
|
||||||
|
(parts.Region == region || region == AllRegions) &&
|
||||||
|
(parts.Status == serf.StatusAlive || (checkFailedServers && parts.Status == serf.StatusFailed)) {
|
||||||
// Check if the versions match - version.LessThan will return true for
|
// Check if the versions match - version.LessThan will return true for
|
||||||
// 0.8.0-rc1 < 0.8.0, so we want to ignore the metadata
|
// 0.8.0-rc1 < 0.8.0, so we want to ignore the metadata
|
||||||
versionsMatch := slicesMatch(minVersion.Segments(), parts.Build.Segments())
|
versionsMatch := slices.Equal(minVersion.Segments(), parts.Build.Segments())
|
||||||
if parts.Build.LessThan(minVersion) && !versionsMatch {
|
if parts.Build.LessThan(minVersion) && !versionsMatch {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
@ -161,28 +167,6 @@ func ServersMeetMinimumVersion(members []serf.Member, minVersion *version.Versio
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
func slicesMatch(a, b []int) bool {
|
|
||||||
if a == nil && b == nil {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
if a == nil || b == nil {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(a) != len(b) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := range a {
|
|
||||||
if a[i] != b[i] {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// shuffleStrings randomly shuffles the list of strings
|
// shuffleStrings randomly shuffles the list of strings
|
||||||
func shuffleStrings(list []string) {
|
func shuffleStrings(list []string) {
|
||||||
for i := range list {
|
for i := range list {
|
||||||
|
|
|
@ -148,7 +148,7 @@ func TestServersMeetMinimumVersionExcludingFailed(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
result := ServersMeetMinimumVersion(tc.members, tc.ver, false)
|
result := ServersMeetMinimumVersion(tc.members, AllRegions, tc.ver, false)
|
||||||
if result != tc.expected {
|
if result != tc.expected {
|
||||||
t.Fatalf("bad: %v, %v, %v", result, tc.ver.String(), tc)
|
t.Fatalf("bad: %v, %v, %v", result, tc.ver.String(), tc)
|
||||||
}
|
}
|
||||||
|
@ -186,7 +186,7 @@ func TestServersMeetMinimumVersionIncludingFailed(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
result := ServersMeetMinimumVersion(tc.members, tc.ver, true)
|
result := ServersMeetMinimumVersion(tc.members, AllRegions, tc.ver, true)
|
||||||
if result != tc.expected {
|
if result != tc.expected {
|
||||||
t.Fatalf("bad: %v, %v, %v", result, tc.ver.String(), tc)
|
t.Fatalf("bad: %v, %v, %v", result, tc.ver.String(), tc)
|
||||||
}
|
}
|
||||||
|
@ -224,7 +224,7 @@ func TestServersMeetMinimumVersionSuffix(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
result := ServersMeetMinimumVersion(tc.members, tc.ver, true)
|
result := ServersMeetMinimumVersion(tc.members, AllRegions, tc.ver, true)
|
||||||
if result != tc.expected {
|
if result != tc.expected {
|
||||||
t.Fatalf("bad: %v, %v, %v", result, tc.ver.String(), tc)
|
t.Fatalf("bad: %v, %v, %v", result, tc.ver.String(), tc)
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,6 +46,11 @@ func (sv *Variables) Apply(args *structs.VariablesApplyRequest, reply *structs.V
|
||||||
args.Var.Namespace = targetNS
|
args.Var.Namespace = targetNS
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if !ServersMeetMinimumVersion(
|
||||||
|
sv.srv.serf.Members(), sv.srv.Region(), minVersionKeyring, true) {
|
||||||
|
return fmt.Errorf("all servers must be running version %v or later to apply variables", minVersionKeyring)
|
||||||
|
}
|
||||||
|
|
||||||
canRead, err := svePreApply(sv, args, args.Var)
|
canRead, err := svePreApply(sv, args, args.Var)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -585,7 +585,7 @@ func (w *Worker) invokeScheduler(snap *state.StateSnapshot, eval *structs.Evalua
|
||||||
// other packages to perform server version checks without direct references to
|
// other packages to perform server version checks without direct references to
|
||||||
// the Nomad server.
|
// the Nomad server.
|
||||||
func (w *Worker) ServersMeetMinimumVersion(minVersion *version.Version, checkFailedServers bool) bool {
|
func (w *Worker) ServersMeetMinimumVersion(minVersion *version.Version, checkFailedServers bool) bool {
|
||||||
return ServersMeetMinimumVersion(w.srv.Members(), minVersion, checkFailedServers)
|
return ServersMeetMinimumVersion(w.srv.Members(), w.srv.Region(), minVersion, checkFailedServers)
|
||||||
}
|
}
|
||||||
|
|
||||||
// SubmitPlan is used to submit a plan for consideration. This allows
|
// SubmitPlan is used to submit a plan for consideration. This allows
|
||||||
|
@ -606,7 +606,7 @@ func (w *Worker) SubmitPlan(plan *structs.Plan) (*structs.PlanResult, scheduler.
|
||||||
plan.SnapshotIndex = w.snapshotIndex
|
plan.SnapshotIndex = w.snapshotIndex
|
||||||
|
|
||||||
// Normalize stopped and preempted allocs before RPC
|
// Normalize stopped and preempted allocs before RPC
|
||||||
normalizePlan := ServersMeetMinimumVersion(w.srv.Members(), MinVersionPlanNormalization, true)
|
normalizePlan := ServersMeetMinimumVersion(w.srv.Members(), w.srv.Region(), MinVersionPlanNormalization, true)
|
||||||
if normalizePlan {
|
if normalizePlan {
|
||||||
plan.NormalizeAllocations()
|
plan.NormalizeAllocations()
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,8 +134,9 @@ type Planner interface {
|
||||||
// that on leader changes, the evaluation will be reblocked properly.
|
// that on leader changes, the evaluation will be reblocked properly.
|
||||||
ReblockEval(*structs.Evaluation) error
|
ReblockEval(*structs.Evaluation) error
|
||||||
|
|
||||||
// ServersMeetMinimumVersion returns whether the Nomad servers are at least on the
|
// ServersMeetMinimumVersion returns whether the Nomad servers in the
|
||||||
// given Nomad version. The checkFailedServers parameter specifies whether version
|
// worker's region are at least on the given Nomad version. The
|
||||||
// for the failed servers should be verified.
|
// checkFailedServers parameter specifies whether version for the failed
|
||||||
|
// servers should be verified.
|
||||||
ServersMeetMinimumVersion(minVersion *version.Version, checkFailedServers bool) bool
|
ServersMeetMinimumVersion(minVersion *version.Version, checkFailedServers bool) bool
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue