Repository: armadaproject/armada
Pull request #3585 (2024-05-14 07:08:32)

Move scheduler configuration into the scheduler package
Now that the api no longer needs to instantiate a scheduler, we can move the scheduling configuration from the api to the shceduler package


Patch:
diff --git a/cmd/simulator/cmd/root.go b/cmd/simulator/cmd/root.go
index ae355aa7..fca5efcc 100644
--- a/cmd/simulator/cmd/root.go
+++ b/cmd/simulator/cmd/root.go
@@ -7,9 +7,9 @@ import (
 	"github.com/spf13/cobra"
 	"golang.org/x/exp/maps"
 
-	"github.com/armadaproject/armada/internal/armada/configuration"
 	"github.com/armadaproject/armada/internal/common/armadacontext"
 	"github.com/armadaproject/armada/internal/common/slices"
+	"github.com/armadaproject/armada/internal/scheduler/configuration"
 	"github.com/armadaproject/armada/internal/scheduler/simulator"
 	"github.com/armadaproject/armada/internal/scheduler/testfixtures"
 )
diff --git a/internal/armada/configuration/types.go b/internal/armada/configuration/types.go
index 14b7076d..b8c91825 100644
--- a/internal/armada/configuration/types.go
+++ b/internal/armada/configuration/types.go
@@ -1,19 +1,15 @@
 package configuration
 
 import (
-	"fmt"
 	"time"
 
 	"github.com/apache/pulsar-client-go/pulsar"
-	"github.com/go-playground/validator/v10"
 	"github.com/redis/go-redis/v9"
 	v1 "k8s.io/api/core/v1"
-	"k8s.io/apimachinery/pkg/api/resource"
 
 	authconfig "github.com/armadaproject/armada/internal/common/auth/configuration"
 	grpcconfig "github.com/armadaproject/armada/internal/common/grpc/configuration"
 	armadaresource "github.com/armadaproject/armada/internal/common/resource"
-	"github.com/armadaproject/armada/internal/common/types"
 	"github.com/armadaproject/armada/pkg/client"
 )
 
@@ -133,214 +129,6 @@ type SubmissionConfig struct {
 	DefaultActiveDeadlineByResourceRequest map[string]time.Duration
 }
 
-// SchedulingConfig contains config controlling the Armada scheduler.
-//
-// The Armada scheduler is in charge of assigning pods to cluster and nodes.
-// The Armada scheduler is part of the Armada control plane.
-//
-// Features:
-// 1. Queuing and fairly dividing resources between users.
-// 2. Fair preemption, including between jobs of equal priority to balance resource allocation.
-// 3. Gang scheduling, optional across clusters, and with lower and upper bounds on the number of jobs scheduled.
-//
-// Note that Armada still relies on kube-scheduler for binding of pods to nodes.
-// This is achieved by adding to each pod created by Armada a node selector that matches only the intended node.
-type SchedulingConfig struct {
-	// Set to true to disable scheduling
-	DisableScheduling bool
-	// Set to true to enable scheduler assertions. This results in some performance loss.
-	EnableAssertions bool
-	// If using PreemptToFairShare,
-	// the probability of evicting jobs on a node to balance resource usage.
-	// TODO(albin): Remove.
-	NodeEvictionProbability float64
-	// If using PreemptToFairShare,
-	// the probability of evicting jobs on oversubscribed nodes, i.e.,
-	// nodes on which the total resource requests are greater than the available resources.
-	// TODO(albin): Remove.
-	NodeOversubscriptionEvictionProbability float64
-	// Only queues allocated more than this fraction of their fair share are considered for preemption.
-	ProtectedFractionOfFairShare float64 `validate:"gte=0"`
-	// Armada adds a node selector term to every scheduled pod using this label with the node name as value.
-	// This to force kube-scheduler to schedule pods on the node chosen by Armada.
-	// For example, if NodeIdLabel is "kubernetes.io/hostname" and armada schedules a pod on node "myNode",
-	// then Armada adds "kubernetes.io/hostname": "myNode" to the pod node selector before sending it to the executor.
-	NodeIdLabel string `validate:"required"`
-	// Map from priority class names to priority classes.
-	// Must be consistent with Kubernetes priority classes.
-	// I.e., priority classes defined here must be defined in all executor clusters and should map to the same priority.
-	PriorityClasses map[string]types.PriorityClass `validate:"dive"`
-	// Jobs with no priority class are assigned this priority class when ingested by the scheduler.
-	// Must be a key in the PriorityClasses map above.
-	DefaultPriorityClassName string
-	// If set, override the priority class name of pods with this value when sending to an executor.
-	PriorityClassNameOverride *string
-	// Number of jobs to load from the database at a time.
-	MaxQueueLookback uint
-	// In each invocation of the scheduler, no more jobs are scheduled once this limit has been exceeded.
-	// Note that the total scheduled resources may be greater than this limit.
-	MaximumResourceFractionToSchedule map[string]float64
-	// Overrides MaximalClusterFractionToSchedule if set for the current pool.
-	MaximumResourceFractionToScheduleByPool map[string]map[string]float64
-	// The rate at which Armada schedules jobs is rate-limited using a token bucket approach.
-	// Specifically, there is a token bucket that persists between scheduling rounds.
-	// The bucket fills up at a rate of MaximumSchedulingRate tokens per second and has capacity MaximumSchedulingBurst.
-	// A token is removed from the bucket when a scheduling a job and scheduling stops while the bucket is empty.
-	//
-	// Hence, MaximumSchedulingRate controls the maximum number of jobs scheduled per second in steady-state,
-	// i.e., once the burst capacity has been exhausted.
-	//
-	// Rate-limiting is based on the number of tokens available at the start of each scheduling round,
-	// i.e., tokens accumulated while scheduling become available at the start of the next scheduling round.
-	//
-	// For more information about the rate-limiter, see:
-	// https://pkg.go.dev/golang.org/x/time/rate#Limiter
-	MaximumSchedulingRate float64 `validate:"gt=0"`
-	// MaximumSchedulingBurst controls the burst capacity of the rate-limiter.
-	//
-	// There are two important implications:
-	// - Armada will never schedule more than MaximumSchedulingBurst jobs per scheduling round.
-	// - Gang jobs with cardinality greater than MaximumSchedulingBurst can never be scheduled.
-	MaximumSchedulingBurst int `validate:"gt=0"`
-	// In addition to the global rate-limiter, there is a separate rate-limiter for each queue.
-	// These work the same as the global rate-limiter, except they apply only to jobs scheduled from a specific queue.
-	//
-	// Per-queue version of MaximumSchedulingRate.
-	MaximumPerQueueSchedulingRate float64 `validate:"gt=0"`
-	// Per-queue version of MaximumSchedulingBurst.
-	MaximumPerQueueSchedulingBurst int `validate:"gt=0"`
-	// Maximum number of times a job is retried before considered failed.
-	MaxRetries uint
-	// List of resource names, e.g., []string{"cpu", "memory"}, to consider when computing DominantResourceFairness.
-	DominantResourceFairnessResourcesToConsider []string
-	// Once a node has been found on which a pod can be scheduled,
-	// the scheduler will consider up to the next maxExtraNodesToConsider nodes.
-	// The scheduler selects the node with the best score out of the considered nodes.
-	// In particular, the score expresses whether preemption is necessary to schedule a pod.
-	// Hence, a larger MaxExtraNodesToConsider would reduce the expected number of preemptions.
-	// TODO(albin): Remove. It's unused.
-	MaxExtraNodesToConsider uint
-	// Resource types (e.g. memory or nvidia.com/gpu) that the scheduler keeps track of.
-	// Resource types not on this list will be ignored if seen on a node, and any jobs requesting them will fail.
-	SupportedResourceTypes []ResourceType
-	// Resources, e.g., "cpu", "memory", and "nvidia.com/gpu", for which the scheduler creates indexes for efficient lookup.
-	// This list must contain at least one resource. Adding more than one resource is not required, but may speed up scheduling.
-	// Ideally, this list contains all resources that frequently constrain which nodes a job can be scheduled onto.
-	//
-	// In particular, the allocatable resources on each node are rounded to a multiple of the resolution.
-	// Lower resolution speeds up scheduling by improving node lookup speed but may prevent scheduling jobs,
-	// since the allocatable resources may be rounded down to be a multiple of the resolution.
-	//
-	// See NodeDb docs for more details.
-	IndexedResources []ResourceType
-	// Node labels that the scheduler creates indexes for efficient lookup of.
-	// Should include node labels frequently used by node selectors on submitted jobs.
-	//
-	// If not set, no labels are indexed.
-	IndexedNodeLabels []string
-	// Taint keys that the scheduler creates indexes for efficient lookup of.
-	// Should include keys of taints frequently used in tolerations on submitted jobs.
-	//
-	// If not set, all taints are indexed.
-	IndexedTaints []string
-	// WellKnownNodeTypes defines a set of well-known node types used to define "home" and "away" nodes for a given priority class.
-	WellKnownNodeTypes []WellKnownNodeType `validate:"dive"`
-	// Executor that haven't heartbeated in this time period are considered stale.
-	// No new jobs are scheduled onto stale executors.
-	ExecutorTimeout time.Duration
-	// Maximum number of jobs that can be assigned to a executor but not yet acknowledged, before
-	// the scheduler is excluded from consideration by the scheduler.
-	MaxUnacknowledgedJobsPerExecutor uint
-	// If true, do not during scheduling skip jobs with requirements known to be impossible to meet.
-	AlwaysAttemptScheduling bool
-	// The frequency at which the scheduler updates the cluster state.
-	ExecutorUpdateFrequency time.Duration
-	// Controls node and queue success probability estimation.
-	FailureProbabilityEstimation FailureEstimatorConfig
-	// Controls node quarantining, i.e., removing from consideration for scheduling misbehaving nodes.
-	NodeQuarantining NodeQuarantinerConfig
-	// Controls queue quarantining, i.e., rate-limiting scheduling from misbehaving queues.
-	QueueQuarantining QueueQuarantinerConfig
-	// Defines the order in which pools will be scheduled. Higher priority pools will be scheduled first
-	PoolSchedulePriority map[string]int
-	// Default priority for pools that are not in the above list
-	DefaultPoolSchedulePriority int
-}
-
-const (
-	DuplicateWellKnownNodeTypeErrorMessage     = "duplicate well-known node type name"
-	AwayNodeTypesWithoutPreemptionErrorMessage = "priority class has away node types but is not preemptible"
-	UnknownWellKnownNodeTypeErrorMessage       = "priority class refers to unknown well-known node type"
-)
-
-func SchedulingConfigValidation(sl validator.StructLevel) {
-	c := sl.Current().Interface().(SchedulingConfig)
-
-	wellKnownNodeTypes := make(map[string]bool)
-	for i, wellKnownNodeType := range c.WellKnownNodeTypes {
-		if wellKnownNodeTypes[wellKnownNodeType.Name] {
-			fieldName := fmt.Sprintf("WellKnownNodeTypes[%d].Name", i)
-			sl.ReportError(wellKnownNodeType.Name, fieldName, "", DuplicateWellKnownNodeTypeErrorMessage, "")
-		}
-		wellKnownNodeTypes[wellKnownNodeType.Name] = true
-	}
-
-	for priorityClassName, priorityClass := range c.PriorityClasses {
-		if len(priorityClass.AwayNodeTypes) > 0 && !priorityClass.Preemptible {
-			fieldName := fmt.Sprintf("Preemption.PriorityClasses[%s].Preemptible", priorityClassName)
-			sl.ReportError(priorityClass.Preemptible, fieldName, "", AwayNodeTypesWithoutPreemptionErrorMessage, "")
-		}
-
-		for i, awayNodeType := range priorityClass.AwayNodeTypes {
-			if !wellKnownNodeTypes[awayNodeType.WellKnownNodeTypeName] {
-				fieldName := fmt.Sprintf("Preemption.PriorityClasses[%s].AwayNodeTypes[%d].WellKnownNodeTypeName", priorityClassName, i)
-				sl.ReportError(awayNodeType.WellKnownNodeTypeName, fieldName, "", UnknownWellKnownNodeTypeErrorMessage, "")
-			}
-		}
-	}
-}
-
-// ResourceType represents a resource the scheduler indexes for efficient lookup.
-type ResourceType struct {
-	// Resource name, e.g., "cpu", "memory", or "nvidia.com/gpu".
-	Name string
-	// Resolution with which Armada tracks this resource; larger values indicate lower resolution.
-	Resolution resource.Quantity
-}
-
-// A WellKnownNodeType defines a set of nodes; see AwayNodeType.
-type WellKnownNodeType struct {
-	// Name is the unique identifier for this node type.
-	Name string `validate:"required"`
-	// Taints is the set of taints that characterizes this node type; a node is
-	// part of this node type if and only if it has all of these taints.
-	Taints []v1.Taint
-}
-
-// FailureEstimatorConfig controls node and queue success probability estimation.
-// See internal/scheduler/failureestimator.go for details.
-type FailureEstimatorConfig struct {
-	Disabled                           bool
-	NumInnerIterations                 int     `validate:"gt=0"`
-	InnerOptimiserStepSize             float64 `validate:"gt=0"`
-	OuterOptimiserStepSize             float64 `validate:"gt=0"`
-	OuterOptimiserNesterovAcceleration float64 `validate:"gte=0"`
-}
-
-// NodeQuarantinerConfig controls how nodes are quarantined, i.e., removed from consideration when scheduling new jobs.
-// See internal/scheduler/quarantine/node_quarantiner.go for details.
-type NodeQuarantinerConfig struct {
-	FailureProbabilityQuarantineThreshold float64       `validate:"gte=0,lte=1"`
-	FailureProbabilityEstimateTimeout     time.Duration `validate:"gte=0"`
-}
-
-// QueueQuarantinerConfig controls how scheduling from misbehaving queues is rate-limited.
-// See internal/scheduler/quarantine/queue_quarantiner.go for details.
-type QueueQuarantinerConfig struct {
-	QuarantineFactorMultiplier        float64       `validate:"gte=0,lte=1"`
-	FailureProbabilityEstimateTimeout time.Duration `validate:"gte=0"`
-}
-
 // TODO: we can probably just typedef this to map[string]string
 type PostgresConfig struct {
 	Connection map[string]string
diff --git a/internal/scheduler/configuration/configuration.go b/internal/scheduler/configuration/configuration.go
index 264c766f..41b03f59 100644
--- a/internal/scheduler/configuration/configuration.go
+++ b/internal/scheduler/configuration/configuration.go
@@ -1,15 +1,18 @@
 package configuration
 
 import (
+	"fmt"
 	"time"
 
 	"github.com/go-playground/validator/v10"
 	v1 "k8s.io/api/core/v1"
+	"k8s.io/apimachinery/pkg/api/resource"
 
 	"github.com/armadaproject/armada/internal/armada/configuration"
 	authconfig "github.com/armadaproject/armada/internal/common/auth/configuration"
 	"github.com/armadaproject/armada/internal/common/config"
 	grpcconfig "github.com/armadaproject/armada/internal/common/grpc/configuration"
+	"github.com/armadaproject/armada/internal/common/types"
 	"github.com/armadaproject/armada/pkg/client"
 )
 
@@ -36,7 +39,7 @@ type Configuration struct {
 	// Due to replace metrics configured via the above entry.
 	SchedulerMetrics MetricsConfig
 	// Scheduler configuration (this is shared with the old scheduler)
-	Scheduling configuration.SchedulingConfig
+	Scheduling SchedulingConfig
 	Auth       authconfig.AuthConfig
 	Grpc       grpcconfig.GrpcConfig
 	Http       HttpConfig
@@ -66,7 +69,7 @@ type Configuration struct {
 
 func (c Configuration) Validate() error {
 	validate := validator.New()
-	validate.RegisterStructValidation(configuration.SchedulingConfigValidation, configuration.SchedulingConfig{})
+	validate.RegisterStructValidation(SchedulingConfigValidation, SchedulingConfig{})
 	return validate.Struct(c)
 }
 
@@ -152,3 +155,211 @@ type HistogramConfig struct {
 	Factor float64
 	Count  int
 }
+
+// SchedulingConfig contains config controlling the Armada scheduler.
+//
+// The Armada scheduler is in charge of assigning pods to cluster and nodes.
+// The Armada scheduler is part of the Armada control plane.
+//
+// Features:
+// 1. Queuing and fairly dividing resources between users.
+// 2. Fair preemption, including between jobs of equal priority to balance resource allocation.
+// 3. Gang scheduling, optional across clusters, and with lower and upper bounds on the number of jobs scheduled.
+//
+// Note that Armada still relies on kube-scheduler for binding of pods to nodes.
+// This is achieved by adding to each pod created by Armada a node selector that matches only the intended node.
+type SchedulingConfig struct {
+	// Set to true to disable scheduling
+	DisableScheduling bool
+	// Set to true to enable scheduler assertions. This results in some performance loss.
+	EnableAssertions bool
+	// If using PreemptToFairShare,
+	// the probability of evicting jobs on a node to balance resource usage.
+	// TODO(albin): Remove.
+	NodeEvictionProbability float64
+	// If using PreemptToFairShare,
+	// the probability of evicting jobs on oversubscribed nodes, i.e.,
+	// nodes on which the total resource requests are greater than the available resources.
+	// TODO(albin): Remove.
+	NodeOversubscriptionEvictionProbability float64
+	// Only queues allocated more than this fraction of their fair share are considered for preemption.
+	ProtectedFractionOfFairShare float64 `validate:"gte=0"`
+	// Armada adds a node selector term to every scheduled pod using this label with the node name as value.
+	// This to force kube-scheduler to schedule pods on the node chosen by Armada.
+	// For example, if NodeIdLabel is "kubernetes.io/hostname" and armada schedules a pod on node "myNode",
+	// then Armada adds "kubernetes.io/hostname": "myNode" to the pod node selector before sending it to the executor.
+	NodeIdLabel string `validate:"required"`
+	// Map from priority class names to priority classes.
+	// Must be consistent with Kubernetes priority classes.
+	// I.e., priority classes defined here must be defined in all executor clusters and should map to the same priority.
+	PriorityClasses map[string]types.PriorityClass `validate:"dive"`
+	// Jobs with no priority class are assigned this priority class when ingested by the scheduler.
+	// Must be a key in the PriorityClasses map above.
+	DefaultPriorityClassName string
+	// If set, override the priority class name of pods with this value when sending to an executor.
+	PriorityClassNameOverride *string
+	// Number of jobs to load from the database at a time.
+	MaxQueueLookback uint
+	// In each invocation of the scheduler, no more jobs are scheduled once this limit has been exceeded.
+	// Note that the total scheduled resources may be greater than this limit.
+	MaximumResourceFractionToSchedule map[string]float64
+	// Overrides MaximalClusterFractionToSchedule if set for the current pool.
+	MaximumResourceFractionToScheduleByPool map[string]map[string]float64
+	// The rate at which Armada schedules jobs is rate-limited using a token bucket approach.
+	// Specifically, there is a token bucket that persists between scheduling rounds.
+	// The bucket fills up at a rate of MaximumSchedulingRate tokens per second and has capacity MaximumSchedulingBurst.
+	// A token is removed from the bucket when a scheduling a job and scheduling stops while the bucket is empty.
+	//
+	// Hence, MaximumSchedulingRate controls the maximum number of jobs scheduled per second in steady-state,
+	// i.e., once the burst capacity has been exhausted.
+	//
+	// Rate-limiting is based on the number of tokens available at the start of each scheduling round,
+	// i.e., tokens accumulated while scheduling become available at the start of the next scheduling round.
+	//
+	// For more information about the rate-limiter, see:
+	// https://pkg.go.dev/golang.org/x/time/rate#Limiter
+	MaximumSchedulingRate float64 `validate:"gt=0"`
+	// MaximumSchedulingBurst controls the burst capacity of the rate-limiter.
+	//
+	// There are two important implications:
+	// - Armada will never schedule more than MaximumSchedulingBurst jobs per scheduling round.
+	// - Gang jobs with cardinality greater than MaximumSchedulingBurst can never be scheduled.
+	MaximumSchedulingBurst int `validate:"gt=0"`
+	// In addition to the global rate-limiter, there is a separate rate-limiter for each queue.
+	// These work the same as the global rate-limiter, except they apply only to jobs scheduled from a specific queue.
+	//
+	// Per-queue version of MaximumSchedulingRate.
+	MaximumPerQueueSchedulingRate float64 `validate:"gt=0"`
+	// Per-queue version of MaximumSchedulingBurst.
+	MaximumPerQueueSchedulingBurst int `validate:"gt=0"`
+	// Maximum number of times a job is retried before considered failed.
+	MaxRetries uint
+	// List of resource names, e.g., []string{"cpu", "memory"}, to consider when computing DominantResourceFairness.
+	DominantResourceFairnessResourcesToConsider []string
+	// Once a node has been found on which a pod can be scheduled,
+	// the scheduler will consider up to the next maxExtraNodesToConsider nodes.
+	// The scheduler selects the node with the best score out of the considered nodes.
+	// In particular, the score expresses whether preemption is necessary to schedule a pod.
+	// Hence, a larger MaxExtraNodesToConsider would reduce the expected number of preemptions.
+	// TODO(albin): Remove. It's unused.
+	MaxExtraNodesToConsider uint
+	// Resource types (e.g. memory or nvidia.com/gpu) that the scheduler keeps track of.
+	// Resource types not on this list will be ignored if seen on a node, and any jobs requesting them will fail.
+	SupportedResourceTypes []ResourceType
+	// Resources, e.g., "cpu", "memory", and "nvidia.com/gpu", for which the scheduler creates indexes for efficient lookup.
+	// This list must contain at least one resource. Adding more than one resource is not required, but may speed up scheduling.
+	// Ideally, this list contains all resources that frequently constrain which nodes a job can be scheduled onto.
+	//
+	// In particular, the allocatable resources on each node are rounded to a multiple of the resolution.
+	// Lower resolution speeds up scheduling by improving node lookup speed but may prevent scheduling jobs,
+	// since the allocatable resources may be rounded down to be a multiple of the resolution.
+	//
+	// See NodeDb docs for more details.
+	IndexedResources []ResourceType
+	// Node labels that the scheduler creates indexes for efficient lookup of.
+	// Should include node labels frequently used by node selectors on submitted jobs.
+	//
+	// If not set, no labels are indexed.
+	IndexedNodeLabels []string
+	// Taint keys that the scheduler creates indexes for efficient lookup of.
+	// Should include keys of taints frequently used in tolerations on submitted jobs.
+	//
+	// If not set, all taints are indexed.
+	IndexedTaints []string
+	// WellKnownNodeTypes defines a set of well-known node types used to define "home" and "away" nodes for a given priority class.
+	WellKnownNodeTypes []WellKnownNodeType `validate:"dive"`
+	// Executor that haven't heartbeated in this time period are considered stale.
+	// No new jobs are scheduled onto stale executors.
+	ExecutorTimeout time.Duration
+	// Maximum number of jobs that can be assigned to a executor but not yet acknowledged, before
+	// the scheduler is excluded from consideration by the scheduler.
+	MaxUnacknowledgedJobsPerExecutor uint
+	// If true, do not during scheduling skip jobs with requirements known to be impossible to meet.
+	AlwaysAttemptScheduling bool
+	// The frequency at which the scheduler updates the cluster state.
+	ExecutorUpdateFrequency time.Duration
+	// Controls node and queue success probability estimation.
+	FailureProbabilityEstimation FailureEstimatorConfig
+	// Controls node quarantining, i.e., removing from consideration for scheduling misbehaving nodes.
+	NodeQuarantining NodeQuarantinerConfig
+	// Controls queue quarantining, i.e., rate-limiting scheduling from misbehaving queues.
+	QueueQuarantining QueueQuarantinerConfig
+	// Defines the order in which pools will be scheduled. Higher priority pools will be scheduled first
+	PoolSchedulePriority map[string]int
+	// Default priority for pools that are not in the above list
+	DefaultPoolSchedulePriority int
+}
+
+const (
+	DuplicateWellKnownNodeTypeErrorMessage     = "duplicate well-known node type name"
+	AwayNodeTypesWithoutPreemptionErrorMessage = "priority class has away node types but is not preemptible"
+	UnknownWellKnownNodeTypeErrorMessage       = "priority class refers to unknown well-known node type"
+)
+
+func SchedulingConfigValidation(sl validator.StructLevel) {
+	c := sl.Current().Interface().(SchedulingConfig)
+
+	wellKnownNodeTypes := make(map[string]bool)
+	for i, wellKnownNodeType := range c.WellKnownNodeTypes {
+		if wellKnownNodeTypes[wellKnownNodeType.Name] {
+			fieldName := fmt.Sprintf("WellKnownNodeTypes[%d].Name", i)
+			sl.ReportError(wellKnownNodeType.Name, fieldName, "", DuplicateWellKnownNodeTypeErrorMessage, "")
+		}
+		wellKnownNodeTypes[wellKnownNodeType.Name] = true
+	}
+
+	for priorityClassName, priorityClass := range c.PriorityClasses {
+		if len(priorityClass.AwayNodeTypes) > 0 && !priorityClass.Preemptible {
+			fieldName := fmt.Sprintf("Preemption.PriorityClasses[%s].Preemptible", priorityClassName)
+			sl.ReportError(priorityClass.Preemptible, fieldName, "", AwayNodeTypesWithoutPreemptionErrorMessage, "")
+		}
+
+		for i, awayNodeType := range priorityClass.AwayNodeTypes {
+			if !wellKnownNodeTypes[awayNodeType.WellKnownNodeTypeName] {
+				fieldName := fmt.Sprintf("Preemption.PriorityClasses[%s].AwayNodeTypes[%d].WellKnownNodeTypeName", priorityClassName, i)
+				sl.ReportError(awayNodeType.WellKnownNodeTypeName, fieldName, "", UnknownWellKnownNodeTypeErrorMessage, "")
+			}
+		}
+	}
+}
+
+// ResourceType represents a resource the scheduler indexes for efficient lookup.
+type ResourceType struct {
+	// Resource name, e.g., "cpu", "memory", or "nvidia.com/gpu".
+	Name string
+	// Resolution with which Armada tracks this resource; larger values indicate lower resolution.
+	Resolution resource.Quantity
+}
+
+// A WellKnownNodeType defines a set of nodes; see AwayNodeType.
+type WellKnownNodeType struct {
+	// Name is the unique identifier for this node type.
+	Name string `validate:"required"`
+	// Taints is the set of taints that characterizes this node type; a node is
+	// part of this node type if and only if it has all of these taints.
+	Taints []v1.Taint
+}
+
+// FailureEstimatorConfig controls node and queue success probability estimation.
+// See internal/scheduler/failureestimator.go for details.
+type FailureEstimatorConfig struct {
+	Disabled                           bool
+	NumInnerIterations                 int     `validate:"gt=0"`
+	InnerOptimiserStepSize             float64 `validate:"gt=0"`
+	OuterOptimiserStepSize             float64 `validate:"gt=0"`
+	OuterOptimiserNesterovAcceleration float64 `validate:"gte=0"`
+}
+
+// NodeQuarantinerConfig controls how nodes are quarantined, i.e., removed from consideration when scheduling new jobs.
+// See internal/scheduler/quarantine/node_quarantiner.go for details.
+type NodeQuarantinerConfig struct {
+	FailureProbabilityQuarantineThreshold float64       `validate:"gte=0,lte=1"`
+	FailureProbabilityEstimateTimeout     time.Duration `validate:"gte=0"`
+}
+
+// QueueQuarantinerConfig controls how scheduling from misbehaving queues is rate-limited.
+// See internal/scheduler/quarantine/queue_quarantiner.go for details.
+type QueueQuarantinerConfig struct {
+	QuarantineFactorMultiplier        float64       `validate:"gte=0,lte=1"`
+	FailureProbabilityEstimateTimeout time.Duration `validate:"gte=0"`
+}
diff --git a/internal/scheduler/constraints/constraints.go b/internal/scheduler/constraints/constraints.go
index c42da18f..95acfb17 100644
--- a/internal/scheduler/constraints/constraints.go
+++ b/internal/scheduler/constraints/constraints.go
@@ -7,7 +7,7 @@ import (
 	"github.com/pkg/errors"
 	"k8s.io/apimachinery/pkg/api/resource"
 
-	"github.com/armadaproject/armada/internal/armada/configuration"
+	"github.com/armadaproject/armada/internal/scheduler/configuration"
 	schedulercontext "github.com/armadaproject/armada/internal/scheduler/context"
 	"github.com/armadaproject/armada/internal/scheduler/schedulerobjects"
 	"github.com/armadaproject/armada/pkg/api"
diff --git a/internal/scheduler/internaltypes/resource_list_factory.go b/internal/scheduler/internaltypes/resource_list_factory.go
index de20e3bc..8ad39e96 100644
--- a/internal/scheduler/internaltypes/resource_list_factory.go
+++ b/internal/scheduler/internaltypes/resource_list_factory.go
@@ -8,7 +8,7 @@ import (
 
 	k8sResource "k8s.io/apimachinery/pkg/api/resource"
 
-	"github.com/armadaproject/armada/internal/armada/configuration"
+	"github.com/armadaproject/armada/internal/scheduler/configuration"
 	"github.com/armadaproject/armada/internal/scheduler/schedulerobjects"
 )
 
diff --git a/internal/scheduler/nodedb/nodedb.go b/internal/scheduler/nodedb/nodedb.go
index 7d4bfc76..0b2ddaa2 100644
--- a/internal/scheduler/nodedb/nodedb.go
+++ b/internal/scheduler/nodedb/nodedb.go
@@ -14,12 +14,11 @@ import (
 	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
 
-	"github.com/armadaproject/armada/internal/armada/configuration"
 	"github.com/armadaproject/armada/internal/common/armadaerrors"
 	"github.com/armadaproject/armada/internal/common/slices"
 	"github.com/armadaproject/armada/internal/common/stringinterner"
 	"github.com/armadaproject/armada/internal/common/types"
-	schedulerconfig "github.com/armadaproject/armada/internal/scheduler/configuration"
+	"github.com/armadaproject/armada/internal/scheduler/configuration"
 	schedulercontext "github.com/armadaproject/armada/internal/scheduler/context"
 	"github.com/armadaproject/armada/internal/scheduler/internaltypes"
 	"github.com/armadaproject/armada/internal/scheduler/jobdb"
@@ -44,7 +43,7 @@ func (nodeDb *NodeDb) create(node *schedulerobjects.Node) (*internaltypes.Node,
 	}
 
 	labels := nodeDb.copyMapWithIntern(node.GetLabels())
-	labels[schedulerconfig.NodeIdLabel] = node.Id
+	labels[configuration.NodeIdLabel] = node.Id
 
 	totalResources := node.TotalResources
 
@@ -532,7 +531,7 @@ func (nodeDb *NodeDb) SelectNodeForJobWithTxn(txn *memdb.Txn, jctx *schedulercon
 	}()
 
 	// If the nodeIdLabel selector is set, consider only that node.
-	if nodeId, ok := jctx.GetNodeSelector(schedulerconfig.NodeIdLabel); ok {
+	if nodeId, ok := jctx.GetNodeSelector(configuration.NodeIdLabel); ok {
 		if it, err := txn.Get("nodes", "id", nodeId); err != nil {
 			return nil, errors.WithStack(err)
 		} else {
@@ -826,7 +825,7 @@ func (nodeDb *NodeDb) selectNodeForJobWithFairPreemption(txn *memdb.Txn, jctx *s
 	for obj := it.Next(); obj != nil && selectedNode == nil; obj = it.Next() {
 		evictedJobSchedulingContext := obj.(*EvictedJobSchedulingContext)
 		evictedJctx := evictedJobSchedulingContext.JobSchedulingContext
-		nodeId, ok := evictedJctx.GetNodeSelector(schedulerconfig.NodeIdLabel)
+		nodeId, ok := evictedJctx.GetNodeSelector(configuration.NodeIdLabel)
 		if !ok {
 			return nil, errors.Errorf("evicted job %s does not have a nodeIdLabel", evictedJctx.JobId)
 		}
diff --git a/internal/scheduler/pool_assigner.go b/internal/scheduler/pool_assigner.go
index a3b2d09f..88f19b0d 100644
--- a/internal/scheduler/pool_assigner.go
+++ b/internal/scheduler/pool_assigner.go
@@ -8,10 +8,10 @@ import (
 	"github.com/pkg/errors"
 	"k8s.io/apimachinery/pkg/util/clock"
 
-	"github.com/armadaproject/armada/internal/armada/configuration"
 	"github.com/armadaproject/armada/internal/common/armadacontext"
 	"github.com/armadaproject/armada/internal/common/stringinterner"
 	"github.com/armadaproject/armada/internal/common/types"
+	"github.com/armadaproject/armada/internal/scheduler/configuration"
 	"github.com/armadaproject/armada/internal/scheduler/constraints"
 	schedulercontext "github.com/armadaproject/armada/internal/scheduler/context"
 	"github.com/armadaproject/armada/internal/scheduler/database"
diff --git a/internal/scheduler/scheduling_algo.go b/internal/scheduler/scheduling_algo.go
index 7ea297f3..b4f22778 100644
--- a/internal/scheduler/scheduling_algo.go
+++ b/internal/scheduler/scheduling_algo.go
@@ -15,12 +15,12 @@ import (
 	"golang.org/x/time/rate"
 	"k8s.io/apimachinery/pkg/util/clock"
 
-	"github.com/armadaproject/armada/internal/armada/configuration"
 	"github.com/armadaproject/armada/internal/common/armadacontext"
 	"github.com/armadaproject/armada/internal/common/logging"
 	armadaslices "github.com/armadaproject/armada/internal/common/slices"
 	"github.com/armadaproject/armada/internal/common/stringinterner"
 	"github.com/armadaproject/armada/internal/common/util"
+	"github.com/armadaproject/armada/internal/scheduler/configuration"
 	schedulerconstraints "github.com/armadaproject/armada/internal/scheduler/constraints"
 	schedulercontext "github.com/armadaproject/armada/internal/scheduler/context"
 	"github.com/armadaproject/armada/internal/scheduler/database"
diff --git a/internal/scheduler/simulator/runner.go b/internal/scheduler/simulator/runner.go
index d6988489..eb668d1f 100644
--- a/internal/scheduler/simulator/runner.go
+++ b/internal/scheduler/simulator/runner.go
@@ -9,8 +9,8 @@ import (
 	"github.com/pkg/errors"
 	"github.com/spf13/viper"
 
-	"github.com/armadaproject/armada/internal/armada/configuration"
 	commonconfig "github.com/armadaproject/armada/internal/common/config"
+	"github.com/armadaproject/armada/internal/scheduler/configuration"
 )
 
 func SchedulingConfigsByFilePathFromPattern(pattern string) (map[string]configuration.SchedulingConfig, error) {
diff --git a/internal/scheduler/simulator/simulator.go b/internal/scheduler/simulator/simulator.go
index 385ddde9..247707c5 100644
--- a/internal/scheduler/simulator/simulator.go
+++ b/internal/scheduler/simulator/simulator.go
@@ -15,7 +15,6 @@ import (
 	"golang.org/x/time/rate"
 	v1 "k8s.io/api/core/v1"
 
-	"github.com/armadaproject/armada/internal/armada/configuration"
 	"github.com/armadaproject/armada/internal/common/armadacontext"
 	"github.com/armadaproject/armada/internal/common/logging"
 	armadaslices "github.com/armadaproject/armada/internal/common/slices"
@@ -23,6 +22,7 @@ import (
 	"github.com/armadaproject/armada/internal/common/types"
 	"github.com/armadaproject/armada/internal/common/util"
 	"github.com/armadaproject/armada/internal/scheduler"
+	"github.com/armadaproject/armada/internal/scheduler/configuration"
 	schedulerconstraints "github.com/armadaproject/armada/internal/scheduler/constraints"
 	schedulercontext "github.com/armadaproject/armada/internal/scheduler/context"
 	"github.com/armadaproject/armada/internal/scheduler/fairness"
diff --git a/internal/scheduler/submitcheck.go b/internal/scheduler/submitcheck.go
index fb0ab804..c7028aeb 100644
--- a/internal/scheduler/submitcheck.go
+++ b/internal/scheduler/submitcheck.go
@@ -9,11 +9,11 @@ import (
 	lru "github.com/hashicorp/golang-lru"
 	"k8s.io/apimachinery/pkg/util/clock"
 
-	"github.com/armadaproject/armada/internal/armada/configuration"
 	"github.com/armadaproject/armada/internal/common/armadacontext"
 	"github.com/armadaproject/armada/internal/common/logging"
 	armadaslices "github.com/armadaproject/armada/internal/common/slices"
 	"github.com/armadaproject/armada/internal/common/stringinterner"
+	"github.com/armadaproject/armada/internal/scheduler/configuration"
 	schedulercontext "github.com/armadaproject/armada/internal/scheduler/context"
 	"github.com/armadaproject/armada/internal/scheduler/database"
 	"github.com/armadaproject/armada/internal/scheduler/internaltypes"


Test patch:
diff --git a/internal/scheduler/configuration/configuration_test.go b/internal/scheduler/configuration/configuration_test.go
index 00b17af4..149a2b54 100644
--- a/internal/scheduler/configuration/configuration_test.go
+++ b/internal/scheduler/configuration/configuration_test.go
@@ -6,14 +6,13 @@ import (
 	"github.com/stretchr/testify/assert"
 	v1 "k8s.io/api/core/v1"
 
-	"github.com/armadaproject/armada/internal/armada/configuration"
 	"github.com/armadaproject/armada/internal/common/types"
 )
 
 func TestSchedulingConfigValidate(t *testing.T) {
 	c := Configuration{
-		Scheduling: configuration.SchedulingConfig{
-			WellKnownNodeTypes: []configuration.WellKnownNodeType{
+		Scheduling: SchedulingConfig{
+			WellKnownNodeTypes: []WellKnownNodeType{
 				{
 					Name:   "gpu",
 					Taints: []v1.Taint{{Key: "gpu", Value: "true", Effect: v1.TaintEffectNoSchedule}},
@@ -38,9 +37,9 @@ func TestSchedulingConfigValidate(t *testing.T) {
 		},
 	}
 	expected := []string{
-		configuration.DuplicateWellKnownNodeTypeErrorMessage,
-		configuration.AwayNodeTypesWithoutPreemptionErrorMessage,
-		configuration.UnknownWellKnownNodeTypeErrorMessage,
+		DuplicateWellKnownNodeTypeErrorMessage,
+		AwayNodeTypesWithoutPreemptionErrorMessage,
+		UnknownWellKnownNodeTypeErrorMessage,
 	}
 
 	err := c.Validate()
diff --git a/internal/scheduler/constraints/constraints_test.go b/internal/scheduler/constraints/constraints_test.go
index db2f47cd..f10f66f7 100644
--- a/internal/scheduler/constraints/constraints_test.go
+++ b/internal/scheduler/constraints/constraints_test.go
@@ -9,8 +9,8 @@ import (
 	"golang.org/x/time/rate"
 	"k8s.io/apimachinery/pkg/api/resource"
 
-	"github.com/armadaproject/armada/internal/armada/configuration"
 	"github.com/armadaproject/armada/internal/common/types"
+	"github.com/armadaproject/armada/internal/scheduler/configuration"
 	schedulercontext "github.com/armadaproject/armada/internal/scheduler/context"
 	"github.com/armadaproject/armada/internal/scheduler/schedulerobjects"
 	"github.com/armadaproject/armada/pkg/api"
diff --git a/internal/scheduler/gang_scheduler_test.go b/internal/scheduler/gang_scheduler_test.go
index a59a5428..689be00b 100644
--- a/internal/scheduler/gang_scheduler_test.go
+++ b/internal/scheduler/gang_scheduler_test.go
@@ -11,12 +11,12 @@ import (
 	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
 
-	"github.com/armadaproject/armada/internal/armada/configuration"
 	"github.com/armadaproject/armada/internal/common/armadacontext"
 	armadaslices "github.com/armadaproject/armada/internal/common/slices"
 	"github.com/armadaproject/armada/internal/common/stringinterner"
 	"github.com/armadaproject/armada/internal/common/types"
 	"github.com/armadaproject/armada/internal/common/util"
+	"github.com/armadaproject/armada/internal/scheduler/configuration"
 	schedulerconstraints "github.com/armadaproject/armada/internal/scheduler/constraints"
 	schedulercontext "github.com/armadaproject/armada/internal/scheduler/context"
 	"github.com/armadaproject/armada/internal/scheduler/fairness"
diff --git a/internal/scheduler/internaltypes/resource_list_factory_test.go b/internal/scheduler/internaltypes/resource_list_factory_test.go
index c0f087f3..0e863f00 100644
--- a/internal/scheduler/internaltypes/resource_list_factory_test.go
+++ b/internal/scheduler/internaltypes/resource_list_factory_test.go
@@ -7,7 +7,7 @@ import (
 	"github.com/stretchr/testify/assert"
 	k8sResource "k8s.io/apimachinery/pkg/api/resource"
 
-	"github.com/armadaproject/armada/internal/armada/configuration"
+	"github.com/armadaproject/armada/internal/scheduler/configuration"
 	"github.com/armadaproject/armada/internal/scheduler/schedulerobjects"
 )
 
diff --git a/internal/scheduler/pool_assigner_test.go b/internal/scheduler/pool_assigner_test.go
index f6bdb67e..31299781 100644
--- a/internal/scheduler/pool_assigner_test.go
+++ b/internal/scheduler/pool_assigner_test.go
@@ -9,9 +9,9 @@ import (
 	"github.com/stretchr/testify/require"
 	"k8s.io/apimachinery/pkg/util/clock"
 
-	"github.com/armadaproject/armada/internal/armada/configuration"
 	"github.com/armadaproject/armada/internal/common/armadacontext"
 	"github.com/armadaproject/armada/internal/common/util"
+	"github.com/armadaproject/armada/internal/scheduler/configuration"
 	"github.com/armadaproject/armada/internal/scheduler/jobdb"
 	schedulermocks "github.com/armadaproject/armada/internal/scheduler/mocks"
 	"github.com/armadaproject/armada/internal/scheduler/schedulerobjects"
diff --git a/internal/scheduler/preempting_queue_scheduler_test.go b/internal/scheduler/preempting_queue_scheduler_test.go
index 5f32db8b..cdeaaea0 100644
--- a/internal/scheduler/preempting_queue_scheduler_test.go
+++ b/internal/scheduler/preempting_queue_scheduler_test.go
@@ -14,7 +14,6 @@ import (
 	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
 
-	"github.com/armadaproject/armada/internal/armada/configuration"
 	"github.com/armadaproject/armada/internal/common/armadacontext"
 	"github.com/armadaproject/armada/internal/common/logging"
 	armadamaps "github.com/armadaproject/armada/internal/common/maps"
@@ -22,6 +21,7 @@ import (
 	"github.com/armadaproject/armada/internal/common/stringinterner"
 	"github.com/armadaproject/armada/internal/common/types"
 	"github.com/armadaproject/armada/internal/common/util"
+	"github.com/armadaproject/armada/internal/scheduler/configuration"
 	schedulerconstraints "github.com/armadaproject/armada/internal/scheduler/constraints"
 	schedulercontext "github.com/armadaproject/armada/internal/scheduler/context"
 	"github.com/armadaproject/armada/internal/scheduler/fairness"
diff --git a/internal/scheduler/queue_scheduler_test.go b/internal/scheduler/queue_scheduler_test.go
index 91ba0406..87fa2cba 100644
--- a/internal/scheduler/queue_scheduler_test.go
+++ b/internal/scheduler/queue_scheduler_test.go
@@ -12,10 +12,11 @@ import (
 	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
 
-	"github.com/armadaproject/armada/internal/armada/configuration"
+	armadaconfiguration "github.com/armadaproject/armada/internal/armada/configuration"
 	"github.com/armadaproject/armada/internal/common/armadacontext"
 	armadaslices "github.com/armadaproject/armada/internal/common/slices"
 	"github.com/armadaproject/armada/internal/common/stringinterner"
+	"github.com/armadaproject/armada/internal/scheduler/configuration"
 	schedulerconstraints "github.com/armadaproject/armada/internal/scheduler/constraints"
 	schedulercontext "github.com/armadaproject/armada/internal/scheduler/context"
 	"github.com/armadaproject/armada/internal/scheduler/fairness"
@@ -419,16 +420,16 @@ func TestQueueScheduler(t *testing.T) {
 			Nodes:            testfixtures.N32CpuNodes(3, testfixtures.TestPriorities),
 			Jobs: armadaslices.Concatenate(
 				testfixtures.WithAnnotationsJobs(map[string]string{
-					configuration.GangIdAnnotation:                 "my-gang",
-					configuration.GangCardinalityAnnotation:        "2",
-					configuration.GangMinimumCardinalityAnnotation: "1",
+					armadaconfiguration.GangIdAnnotation:                 "my-gang",
+					armadaconfiguration.GangCardinalityAnnotation:        "2",
+					armadaconfiguration.GangMinimumCardinalityAnnotation: "1",
 				},
 					testfixtures.N32Cpu256GiJobs("A", testfixtures.PriorityClass0, 1)),
 				testfixtures.N1Cpu4GiJobs("A", testfixtures.PriorityClass0, 1),
 				testfixtures.WithAnnotationsJobs(map[string]string{
-					configuration.GangIdAnnotation:                 "my-gang",
-					configuration.GangCardinalityAnnotation:        "2",
-					configuration.GangMinimumCardinalityAnnotation: "1",
+					armadaconfiguration.GangIdAnnotation:                 "my-gang",
+					armadaconfiguration.GangCardinalityAnnotation:        "2",
+					armadaconfiguration.GangMinimumCardinalityAnnotation: "1",
 				},
 					testfixtures.N32Cpu256GiJobs("A", testfixtures.PriorityClass0, 1)),
 			),
@@ -447,16 +448,16 @@ func TestQueueScheduler(t *testing.T) {
 			Nodes:            testfixtures.N32CpuNodes(2, testfixtures.TestPriorities),
 			Jobs: armadaslices.Concatenate(
 				testfixtures.WithAnnotationsJobs(map[string]string{
-					configuration.GangIdAnnotation:                 "my-gang",
-					configuration.GangCardinalityAnnotation:        "2",
-					configuration.GangMinimumCardinalityAnnotation: "2",
+					armadaconfiguration.GangIdAnnotation:                 "my-gang",
+					armadaconfiguration.GangCardinalityAnnotation:        "2",
+					armadaconfiguration.GangMinimumCardinalityAnnotation: "2",
 				},
 					testfixtures.N32Cpu256GiJobs("A", testfixtures.PriorityClass0, 1)),
 				testfixtures.N1Cpu4GiJobs("A", testfixtures.PriorityClass0, 1),
 				testfixtures.WithAnnotationsJobs(map[string]string{
-					configuration.GangIdAnnotation:                 "my-gang",
-					configuration.GangCardinalityAnnotation:        "2",
-					configuration.GangMinimumCardinalityAnnotation: "2",
+					armadaconfiguration.GangIdAnnotation:                 "my-gang",
+					armadaconfiguration.GangCardinalityAnnotation:        "2",
+					armadaconfiguration.GangMinimumCardinalityAnnotation: "2",
 				},
 					testfixtures.N32Cpu256GiJobs("A", testfixtures.PriorityClass0, 1)),
 			),
diff --git a/internal/scheduler/scheduling_algo_test.go b/internal/scheduler/scheduling_algo_test.go
index d664aa22..ecbaab4a 100644
--- a/internal/scheduler/scheduling_algo_test.go
+++ b/internal/scheduler/scheduling_algo_test.go
@@ -12,10 +12,11 @@ import (
 	"golang.org/x/exp/slices"
 	"k8s.io/apimachinery/pkg/util/clock"
 
-	"github.com/armadaproject/armada/internal/armada/configuration"
+	armadaconfiguration "github.com/armadaproject/armada/internal/armada/configuration"
 	"github.com/armadaproject/armada/internal/common/armadacontext"
 	armadaslices "github.com/armadaproject/armada/internal/common/slices"
 	"github.com/armadaproject/armada/internal/common/stringinterner"
+	"github.com/armadaproject/armada/internal/scheduler/configuration"
 	"github.com/armadaproject/armada/internal/scheduler/jobdb"
 	schedulermocks "github.com/armadaproject/armada/internal/scheduler/mocks"
 	"github.com/armadaproject/armada/internal/scheduler/nodedb"
@@ -480,7 +481,7 @@ func TestSchedule(t *testing.T) {
 			}
 			// Sanity check: we've set `GangNumJobsScheduledAnnotation` for all scheduled jobs.
 			for _, job := range scheduledJobs {
-				assert.Contains(t, schedulerResult.AdditionalAnnotationsByJobId[job.Id()], configuration.GangNumJobsScheduledAnnotation)
+				assert.Contains(t, schedulerResult.AdditionalAnnotationsByJobId[job.Id()], armadaconfiguration.GangNumJobsScheduledAnnotation)
 			}
 
 			// Check that we failed the correct number of excess jobs when a gang schedules >= minimum cardinality
diff --git a/internal/scheduler/simulator/simulator_test.go b/internal/scheduler/simulator/simulator_test.go
index 7878fa8c..f43ae8a1 100644
--- a/internal/scheduler/simulator/simulator_test.go
+++ b/internal/scheduler/simulator/simulator_test.go
@@ -10,10 +10,10 @@ import (
 	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
 
-	"github.com/armadaproject/armada/internal/armada/configuration"
 	"github.com/armadaproject/armada/internal/common/armadacontext"
 	armadaslices "github.com/armadaproject/armada/internal/common/slices"
 	"github.com/armadaproject/armada/internal/common/types"
+	"github.com/armadaproject/armada/internal/scheduler/configuration"
 	"github.com/armadaproject/armada/internal/scheduler/schedulerobjects"
 	"github.com/armadaproject/armada/internal/scheduler/testfixtures"
 	"github.com/armadaproject/armada/pkg/armadaevents"
diff --git a/internal/scheduler/simulator/test_utils.go b/internal/scheduler/simulator/test_utils.go
index bc2c300f..e238f563 100644
--- a/internal/scheduler/simulator/test_utils.go
+++ b/internal/scheduler/simulator/test_utils.go
@@ -9,9 +9,9 @@ import (
 	v1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/resource"
 
-	"github.com/armadaproject/armada/internal/armada/configuration"
 	armadaslices "github.com/armadaproject/armada/internal/common/slices"
 	"github.com/armadaproject/armada/internal/common/types"
+	"github.com/armadaproject/armada/internal/scheduler/configuration"
 	"github.com/armadaproject/armada/internal/scheduler/constraints"
 	"github.com/armadaproject/armada/internal/scheduler/schedulerobjects"
 	"github.com/armadaproject/armada/pkg/armadaevents"
diff --git a/internal/scheduler/submitcheck_test.go b/internal/scheduler/submitcheck_test.go
index 7e7d9a32..aa4c51a2 100644
--- a/internal/scheduler/submitcheck_test.go
+++ b/internal/scheduler/submitcheck_test.go
@@ -9,8 +9,8 @@ import (
 	"github.com/stretchr/testify/require"
 	"k8s.io/apimachinery/pkg/util/clock"
 
-	"github.com/armadaproject/armada/internal/armada/configuration"
 	"github.com/armadaproject/armada/internal/common/armadacontext"
+	"github.com/armadaproject/armada/internal/scheduler/configuration"
 	"github.com/armadaproject/armada/internal/scheduler/jobdb"
 	schedulermocks "github.com/armadaproject/armada/internal/scheduler/mocks"
 	"github.com/armadaproject/armada/internal/scheduler/schedulerobjects"
diff --git a/internal/scheduler/testfixtures/testfixtures.go b/internal/scheduler/testfixtures/testfixtures.go
index 010a3428..7530cede 100644
--- a/internal/scheduler/testfixtures/testfixtures.go
+++ b/internal/scheduler/testfixtures/testfixtures.go
@@ -16,7 +16,7 @@ import (
 	"k8s.io/apimachinery/pkg/api/resource"
 
 	"github.com/armadaproject/armada/internal/armada/configuration"
-	slices "github.com/armadaproject/armada/internal/common/slices"
+	"github.com/armadaproject/armada/internal/common/slices"
 	"github.com/armadaproject/armada/internal/common/stringinterner"
 	"github.com/armadaproject/armada/internal/common/types"
 	"github.com/armadaproject/armada/internal/common/util"
@@ -54,22 +54,22 @@ var (
 	TestDefaultPriorityClass         = PriorityClass3
 	TestPriorities                   = []int32{0, 1, 2, 3}
 	TestMaxExtraNodesToConsider uint = 1
-	TestResources                    = []configuration.ResourceType{
+	TestResources                    = []schedulerconfiguration.ResourceType{
 		{Name: "cpu", Resolution: resource.MustParse("1")},
 		{Name: "memory", Resolution: resource.MustParse("128Mi")},
 		{Name: "gpu", Resolution: resource.MustParse("1")},
 	}
 	TestResourceNames = slices.Map(
 		TestResources,
-		func(v configuration.ResourceType) string { return v.Name },
+		func(v schedulerconfiguration.ResourceType) string { return v.Name },
 	)
 	TestIndexedResourceResolutionMillis = slices.Map(
 		TestResources,
-		func(v configuration.ResourceType) int64 { return v.Resolution.MilliValue() },
+		func(v schedulerconfiguration.ResourceType) int64 { return v.Resolution.MilliValue() },
 	)
 	TestIndexedTaints      = []string{"largeJobsOnly", "gpu"}
 	TestIndexedNodeLabels  = []string{"largeJobsOnly", "gpu"}
-	TestWellKnownNodeTypes = []configuration.WellKnownNodeType{
+	TestWellKnownNodeTypes = []schedulerconfiguration.WellKnownNodeType{
 		{
 			Name:   "gpu",
 			Taints: []v1.Taint{{Key: "gpu", Value: "true", Effect: v1.TaintEffectNoSchedule}},
@@ -127,8 +127,8 @@ func Repeat[T any](v T, n int) []T {
 	return rv
 }
 
-func TestSchedulingConfig() configuration.SchedulingConfig {
-	return configuration.SchedulingConfig{
+func TestSchedulingConfig() schedulerconfiguration.SchedulingConfig {
+	return schedulerconfiguration.SchedulingConfig{
 		PriorityClasses:                             maps.Clone(TestPriorityClasses),
 		DefaultPriorityClassName:                    TestDefaultPriorityClass,
 		NodeEvictionProbability:                     1.0,
@@ -149,37 +149,37 @@ func TestSchedulingConfig() configuration.SchedulingConfig {
 	}
 }
 
-func WithMaxUnacknowledgedJobsPerExecutorConfig(v uint, config configuration.SchedulingConfig) configuration.SchedulingConfig {
+func WithMaxUnacknowledgedJobsPerExecutorConfig(v uint, config schedulerconfiguration.SchedulingConfig) schedulerconfiguration.SchedulingConfig {
 	config.MaxUnacknowledgedJobsPerExecutor = v
 	return config
 }
 
-func WithProtectedFractionOfFairShareConfig(v float64, config configuration.SchedulingConfig) configuration.SchedulingConfig {
+func WithProtectedFractionOfFairShareConfig(v float64, config schedulerconfiguration.SchedulingConfig) schedulerconfiguration.SchedulingConfig {
 	config.ProtectedFractionOfFairShare = v
 	return config
 }
 
-func WithNodeEvictionProbabilityConfig(p float64, config configuration.SchedulingConfig) configuration.SchedulingConfig {
+func WithNodeEvictionProbabilityConfig(p float64, config schedulerconfiguration.SchedulingConfig) schedulerconfiguration.SchedulingConfig {
 	config.NodeEvictionProbability = p
 	return config
 }
 
-func WithNodeOversubscriptionEvictionProbabilityConfig(p float64, config configuration.SchedulingConfig) configuration.SchedulingConfig {
+func WithNodeOversubscriptionEvictionProbabilityConfig(p float64, config schedulerconfiguration.SchedulingConfig) schedulerconfiguration.SchedulingConfig {
 	config.NodeOversubscriptionEvictionProbability = p
 	return config
 }
 
-func WithRoundLimitsConfig(limits map[string]float64, config configuration.SchedulingConfig) configuration.SchedulingConfig {
+func WithRoundLimitsConfig(limits map[string]float64, config schedulerconfiguration.SchedulingConfig) schedulerconfiguration.SchedulingConfig {
 	config.MaximumResourceFractionToSchedule = limits
 	return config
 }
 
-func WithRoundLimitsPoolConfig(limits map[string]map[string]float64, config configuration.SchedulingConfig) configuration.SchedulingConfig {
+func WithRoundLimitsPoolConfig(limits map[string]map[string]float64, config schedulerconfiguration.SchedulingConfig) schedulerconfiguration.SchedulingConfig {
 	config.MaximumResourceFractionToScheduleByPool = limits
 	return config
 }
 
-func WithPerPriorityLimitsConfig(limits map[string]map[string]float64, config configuration.SchedulingConfig) configuration.SchedulingConfig {
+func WithPerPriorityLimitsConfig(limits map[string]map[string]float64, config schedulerconfiguration.SchedulingConfig) schedulerconfiguration.SchedulingConfig {
 	for priorityClassName, limit := range limits {
 		priorityClass, ok := config.PriorityClasses[priorityClassName]
 		if !ok {
@@ -196,39 +196,39 @@ func WithPerPriorityLimitsConfig(limits map[string]map[string]float64, config co
 	return config
 }
 
-func WithIndexedResourcesConfig(indexResources []configuration.ResourceType, config configuration.SchedulingConfig) configuration.SchedulingConfig {
+func WithIndexedResourcesConfig(indexResources []schedulerconfiguration.ResourceType, config schedulerconfiguration.SchedulingConfig) schedulerconfiguration.SchedulingConfig {
 	config.IndexedResources = indexResources
 	return config
 }
 
-func WithGlobalSchedulingRateLimiterConfig(maximumSchedulingRate float64, maximumSchedulingBurst int, config configuration.SchedulingConfig) configuration.SchedulingConfig {
+func WithGlobalSchedulingRateLimiterConfig(maximumSchedulingRate float64, maximumSchedulingBurst int, config schedulerconfiguration.SchedulingConfig) schedulerconfiguration.SchedulingConfig {
 	config.MaximumSchedulingRate = maximumSchedulingRate
 	config.MaximumSchedulingBurst = maximumSchedulingBurst
 	return config
 }
 
-func WithPerQueueSchedulingLimiterConfig(maximumPerQueueSchedulingRate float64, maximumPerQueueSchedulingBurst int, config configuration.SchedulingConfig) configuration.SchedulingConfig {
+func WithPerQueueSchedulingLimiterConfig(maximumPerQueueSchedulingRate float64, maximumPerQueueSchedulingBurst int, config schedulerconfiguration.SchedulingConfig) schedulerconfiguration.SchedulingConfig {
 	config.MaximumPerQueueSchedulingRate = maximumPerQueueSchedulingRate
 	config.MaximumPerQueueSchedulingBurst = maximumPerQueueSchedulingBurst
 	return config
 }
 
-func WithMaxLookbackPerQueueConfig(n uint, config configuration.SchedulingConfig) configuration.SchedulingConfig {
+func WithMaxLookbackPerQueueConfig(n uint, config schedulerconfiguration.SchedulingConfig) schedulerconfiguration.SchedulingConfig {
 	config.MaxQueueLookback = n
 	return config
 }
 
-func WithIndexedTaintsConfig(indexedTaints []string, config configuration.SchedulingConfig) configuration.SchedulingConfig {
+func WithIndexedTaintsConfig(indexedTaints []string, config schedulerconfiguration.SchedulingConfig) schedulerconfiguration.SchedulingConfig {
 	config.IndexedTaints = append(config.IndexedTaints, indexedTaints...)
 	return config
 }
 
-func WithIndexedNodeLabelsConfig(indexedNodeLabels []string, config configuration.SchedulingConfig) configuration.SchedulingConfig {
+func WithIndexedNodeLabelsConfig(indexedNodeLabels []string, config schedulerconfiguration.SchedulingConfig) schedulerconfiguration.SchedulingConfig {
 	config.IndexedNodeLabels = append(config.IndexedNodeLabels, indexedNodeLabels...)
 	return config
 }
 
-func WithMaxQueueLookbackConfig(maxQueueLookback uint, config configuration.SchedulingConfig) configuration.SchedulingConfig {
+func WithMaxQueueLookbackConfig(maxQueueLookback uint, config schedulerconfiguration.SchedulingConfig) schedulerconfiguration.SchedulingConfig {
 	config.MaxQueueLookback = maxQueueLookback
 	return config
 }
@@ -976,8 +976,8 @@ func MakeTestResourceListFactory() *internaltypes.ResourceListFactory {
 	return result
 }
 
-func GetTestSupportedResourceTypes() []configuration.ResourceType {
-	return []configuration.ResourceType{
+func GetTestSupportedResourceTypes() []schedulerconfiguration.ResourceType {
+	return []schedulerconfiguration.ResourceType{
 		{Name: "memory", Resolution: resource.MustParse("1")},
 		{Name: "cpu", Resolution: resource.MustParse("1m")},
 		{Name: "gpu", Resolution: resource.MustParse("1m")},