diff --git a/nomad/provider.go b/nomad/provider.go index 3966efec..619c448d 100644 --- a/nomad/provider.go +++ b/nomad/provider.go @@ -90,6 +90,7 @@ func Provider() terraform.ResourceProvider { "nomad_quota_specification": resourceQuotaSpecification(), "nomad_sentinel_policy": resourceSentinelPolicy(), "nomad_volume": resourceVolume(), + "nomad_scheduler_config": resourceSchedulerConfig(), }, } } diff --git a/nomad/resource_scheduler_config.go b/nomad/resource_scheduler_config.go new file mode 100644 index 00000000..c539e868 --- /dev/null +++ b/nomad/resource_scheduler_config.go @@ -0,0 +1,135 @@ +package nomad + +import ( + "errors" + "fmt" + "log" + + "github.com/hashicorp/nomad/api" + "github.com/hashicorp/terraform-plugin-sdk/helper/schema" + "github.com/hashicorp/terraform-plugin-sdk/helper/validation" +) + +func resourceSchedulerConfig() *schema.Resource { + return &schema.Resource{ + Create: resourceSchedulerConfigurationCreate, + Update: resourceSchedulerConfigurationCreate, + Delete: resourceSchedulerConfigurationDelete, + Read: resourceSchedulerConfigurationRead, + + Schema: map[string]*schema.Schema{ + "scheduler_algorithm": { + Description: "Specifies whether scheduler binpacks or spreads allocations on available nodes.", + Type: schema.TypeString, + Default: "binpack", + Optional: true, + Elem: &schema.Schema{ + Type: schema.TypeString, + ValidateFunc: validation.StringInSlice([]string{ + "binpack", + "spread", + }, false), + }, + }, + // TODO(jrasell) once the Terraform SDK has been updated within + // this provider, we should add validation.MapKeyMatch to this + // schema entry using a regex such as: + // "("^[service,system,batch]_scheduler_enabled")". + "preemption_config": { + Description: "Options to enable preemption for various schedulers.", + Optional: true, + Type: schema.TypeMap, + Elem: &schema.Schema{ + Type: schema.TypeBool, + }, + }, + }, + } +} + +func resourceSchedulerConfigurationCreate(d *schema.ResourceData, meta interface{}) error { + client := meta.(ProviderConfig).client + operator := client.Operator() + + config := api.SchedulerConfiguration{ + SchedulerAlgorithm: api.SchedulerAlgorithm(d.Get("scheduler_algorithm").(string)), + PreemptionConfig: api.PreemptionConfig{}, + } + + // Unpack the preemption block. + preempt, ok := d.GetOk("preemption_config") + if ok { + preemptMap, ok := preempt.(map[string]interface{}) + if !ok { + return errors.New("failed to unpack preemption configuration block") + } + + if val, ok := preemptMap["batch_scheduler_enabled"].(bool); ok { + config.PreemptionConfig.BatchSchedulerEnabled = val + } + if val, ok := preemptMap["service_scheduler_enabled"].(bool); ok { + config.PreemptionConfig.ServiceSchedulerEnabled = val + } + if val, ok := preemptMap["system_scheduler_enabled"].(bool); ok { + config.PreemptionConfig.SystemSchedulerEnabled = val + } + } + + // Perform the config write. + log.Printf("[DEBUG] Upserting Scheduler configuration") + if _, _, err := operator.SchedulerSetConfiguration(&config, nil); err != nil { + return fmt.Errorf("error upserting scheduler configuration: %s", err.Error()) + } + log.Printf("[DEBUG] Upserted scheduler configuration") + + return resourceSchedulerConfigurationRead(d, meta) +} + +// resourceSchedulerConfigurationDelete does not do anything: +// +// There is not a correct way to destroy this "resource" nor check it was +// destroyed. +// +// Consider the following: +// 1. an operator manually updates the Nomad scheduler config, or doesn't +// 2. they decide to move management over to Terraform +// 3. they get rid of Terraform management +// +// If the destroy reverts to the Nomad default configuration, we are over +// writing changes based on assumption. We cannot go back in time to the +// initial version as we do not have this information available, so we just +// have to leave it be. +func resourceSchedulerConfigurationDelete(_ *schema.ResourceData, _ interface{}) error { return nil } + +func resourceSchedulerConfigurationRead(d *schema.ResourceData, meta interface{}) error { + client := meta.(ProviderConfig).client + operator := client.Operator() + + // The scheduler config doesn't have a UUID, so the resource uses the agent + // region. Grab this so we can set it later. + reg, err := client.Agent().Region() + if err != nil { + return fmt.Errorf("error getting region: %s", err.Error()) + } + // retrieve the config + log.Printf("[DEBUG] Reading scheduler configuration") + config, _, err := operator.SchedulerGetConfiguration(nil) + if err != nil { + return fmt.Errorf("error reading scheduler configuration: %s", err.Error()) + } + log.Printf("[DEBUG] Read scheduler configuration") + + d.SetId(fmt.Sprintf("nomad-scheduler-configuration-%s", reg)) + + // Set the algorithm, handling any error when performing the set. + if err := d.Set("scheduler_algorithm", config.SchedulerConfig.SchedulerAlgorithm); err != nil { + return err + } + + premptMap := map[string]bool{ + "batch_scheduler_enabled": config.SchedulerConfig.PreemptionConfig.BatchSchedulerEnabled, + "service_scheduler_enabled": config.SchedulerConfig.PreemptionConfig.ServiceSchedulerEnabled, + "system_scheduler_enabled": config.SchedulerConfig.PreemptionConfig.SystemSchedulerEnabled, + } + return d.Set("preemption_config", premptMap) +} diff --git a/nomad/resource_scheduler_config_test.go b/nomad/resource_scheduler_config_test.go new file mode 100644 index 00000000..c57f4cbe --- /dev/null +++ b/nomad/resource_scheduler_config_test.go @@ -0,0 +1,94 @@ +package nomad + +import ( + "testing" + + "github.com/hashicorp/terraform-plugin-sdk/helper/resource" + "github.com/hashicorp/terraform-plugin-sdk/terraform" +) + +func TestSchedulerConfig_basic(t *testing.T) { + resource.Test(t, resource.TestCase{ + PreCheck: func() { testAccPreCheck(t) }, + Providers: testProviders, + CheckDestroy: testFinalConfiguration, + Steps: []resource.TestStep{ + { + Config: testAccNomadSchedulerConfigSpread, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "nomad_scheduler_config.config", + "scheduler_algorithm", + "spread", + ), + resource.TestCheckResourceAttr( + "nomad_scheduler_config.config", + "preemption_config.batch_scheduler_enabled", + "true", + ), + resource.TestCheckResourceAttr( + "nomad_scheduler_config.config", + "preemption_config.service_scheduler_enabled", + "true", + ), + resource.TestCheckResourceAttr( + "nomad_scheduler_config.config", + "preemption_config.system_scheduler_enabled", + "true", + ), + ), + }, + { + Config: testAccNomadSchedulerConfigBinpack, + Check: resource.ComposeTestCheckFunc( + resource.TestCheckResourceAttr( + "nomad_scheduler_config.config", + "scheduler_algorithm", + "binpack", + ), + resource.TestCheckResourceAttr( + "nomad_scheduler_config.config", + "preemption_config.batch_scheduler_enabled", + "false", + ), + resource.TestCheckResourceAttr( + "nomad_scheduler_config.config", + "preemption_config.service_scheduler_enabled", + "true", + ), + resource.TestCheckResourceAttr( + "nomad_scheduler_config.config", + "preemption_config.system_scheduler_enabled", + "false", + ), + ), + }, + }, + }) +} + +const testAccNomadSchedulerConfigSpread = ` +resource "nomad_scheduler_config" "config" { + scheduler_algorithm = "spread" + preemption_config = { + system_scheduler_enabled = true + batch_scheduler_enabled = true + service_scheduler_enabled = true + } +} +` + +const testAccNomadSchedulerConfigBinpack = ` +resource "nomad_scheduler_config" "config" { + scheduler_algorithm = "binpack" + preemption_config = { + system_scheduler_enabled = false + batch_scheduler_enabled = false + service_scheduler_enabled = true + } +} +` + +// for details on why this is the way it is, checkout the comments on +// resourceSchedulerConfigurationDelete. +func testFinalConfiguration(_ *terraform.State) error { return nil } diff --git a/website/docs/r/scheduler_config.html.markdown b/website/docs/r/scheduler_config.html.markdown new file mode 100644 index 00000000..114ff506 --- /dev/null +++ b/website/docs/r/scheduler_config.html.markdown @@ -0,0 +1,41 @@ +--- +layout: "nomad" +page_title: "Nomad: nomad_scheduler_config" +sidebar_current: "docs-nomad-scheduler-config" +description: |- + Manages scheduler configuration on the Nomad server. +--- + +# nomad_scheduler_config + +Manages scheduler configuration of the Nomad cluster. + +~> **Warning:** destroying this resource will not have any effect in the +cluster configuration, since there's no clear definition of what a destroy +action should do. The cluster will be left as-is and only the state reference +will be removed. + +## Example Usage + +Set cluster scheduler configuration: + +```hcl +resource "nomad_scheduler_config" "config" { + scheduler_algorithm = "spread" + preemption_config = { + system_scheduler_enabled = true + batch_scheduler_enabled = true + service_scheduler_enabled = true + } +} +``` + +## Argument Reference + +The following arguments are supported: + +- `algorithm` `(string: "binpack")` - Specifies whether scheduler binpacks or spreads allocations on available nodes. Possible values are `binpack` and `spread`. +- `preemption_config` `(map[string]bool)` - Options to enable preemption for various schedulers. + - `system_scheduler_enabled` `(bool: true)` - Specifies whether preemption for system jobs is enabled. Note that if this is set to true, then system jobs can preempt any other jobs. + - `batch_scheduler_enabled` `(bool: false")` - Specifies whether preemption for batch jobs is enabled. Note that if this is set to true, then batch jobs can preempt any other jobs. + - `service_scheduler_enabled` `(bool: false)` - Specifies whether preemption for service jobs is enabled. Note that if this is set to true, then service jobs can preempt any other jobs. diff --git a/website/nomad.erb b/website/nomad.erb index 227d161e..8bdfa9fa 100644 --- a/website/nomad.erb +++ b/website/nomad.erb @@ -82,6 +82,9 @@ > nomad_sentinel_policy + > + nomad_scheduler_config + > nomad_volume