Skip to content

Commit

Permalink
Finish adding High/Low Orchestator
Browse files Browse the repository at this point in the history
  • Loading branch information
fdevans committed Oct 23, 2023
1 parent 699e93a commit 4d311dc
Show file tree
Hide file tree
Showing 5 changed files with 273 additions and 21 deletions.
6 changes: 4 additions & 2 deletions rundeck/job.go
Original file line number Diff line number Diff line change
Expand Up @@ -320,8 +320,10 @@ type JobNodeFilter struct {

// JobOrchestratorConfig Contains the options for the Job Orchestrators
type JobOrchestratorConfig struct {
Count int `xml:"count,omitempty"`
Percent int `xml:"percent,omitempty"`
Count int `xml:"count,omitempty"`
Percent int `xml:"percent,omitempty"`
Attribute string `xml:"attribute,omitempty"`
Sort string `xml:"sort,omitempty"`
}

// JobOrchestrator describes how to schedule the jobs, in what order, and on how many nodes
Expand Down
60 changes: 48 additions & 12 deletions rundeck/resource_job.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,10 @@ func resourceRundeckJob() *schema.Resource {
Type: schema.TypeBool,
Optional: true,
},

"timeout": {
Type: schema.TypeString,
Optional: true,
},
"orchestrator": {
Type: schema.TypeList,
Optional: true,
Expand All @@ -134,7 +137,7 @@ func resourceRundeckJob() *schema.Resource {
"type": {
Type: schema.TypeString,
Required: true,
Description: "Option of `subset`, `tiered`, `percentage`",
Description: "Option of `subset`, `rankTiered`, `maxPercentage`, `orchestrator-highest-lowest-attribute`",
},
"count": {
Type: schema.TypeInt,
Expand All @@ -146,6 +149,16 @@ func resourceRundeckJob() *schema.Resource {
Optional: true,
Description: "Value for the maxPercentage orchestrator",
},
"attribute": {
Type: schema.TypeString,
Optional: true,
Description: "The Node Attribute that shoud be used to rank nodes in High/Low Orchestrator.",
},
"sort": {
Type: schema.TypeString,
Optional: true,
Description: "Option of `highest` or `lowest` for High/Low Orchestrator",
},
},
},
},
Expand Down Expand Up @@ -656,13 +669,36 @@ func jobFromResourceData(d *schema.ResourceData) (*JobDetail, error) {
Type: orchMap["type"].(string),
Config: JobOrchestratorConfig{},
}
orchCount := orchMap["count"]
if orchCount != nil {
job.Orchestrator.Config.Count = orchCount.(int)
orchType := orchMap["type"].(string)
if orchType == "orchestrator-highest-lowest-attribute" {
orchAttr := orchMap["attribute"]
if orchAttr != nil {
job.Orchestrator.Config.Attribute = orchAttr.(string)
} else {
return nil, fmt.Errorf("high Low Orchestrator must include an attribute to sort against")
}
orchSort := orchMap["sort"]
if orchSort != nil {
job.Orchestrator.Config.Sort = orchSort.(string)
} else {
return nil, fmt.Errorf("high low orchestrator must include sort direction of `high` or `low`")
}
}
orchPct := orchMap["percent"]
if orchPct != nil {
job.Orchestrator.Config.Percent = orchPct.(int)
if orchType == "subset" {
orchCount := orchMap["count"]
if orchCount != nil {
job.Orchestrator.Config.Count = orchCount.(int)
} else {
return nil, fmt.Errorf("subset Orchestrator requires count setting")
}
}
if orchType == "maxPercentage" {
orchPct := orchMap["percent"]
if orchPct != nil {
job.Orchestrator.Config.Percent = orchPct.(int)
} else {
return nil, fmt.Errorf("max Percentage Orchestrator requires a percent integer configuration")
}
}
}

Expand Down Expand Up @@ -726,11 +762,11 @@ func jobFromResourceData(d *schema.ResourceData) (*JobDetail, error) {
ValueIsExposedToScripts: optionMap["exposed_to_scripts"].(bool),
StoragePath: optionMap["storage_path"].(string),
}
if option.StoragePath != "" && option.ObscureInput == false {
return nil, fmt.Errorf("Argument \"obscure_input\" must be set to `true` when \"storage_path\" is not empty.")
if option.StoragePath != "" && !option.ObscureInput {
return nil, fmt.Errorf("argument \"obscure_input\" must be set to `true` when \"storage_path\" is not empty")
}
if option.ValueIsExposedToScripts && option.ObscureInput == false {
return nil, fmt.Errorf("Argument \"obscure_input\" must be set to `true` when \"exposed_to_scripts\" is set to true.")
if option.ValueIsExposedToScripts && !option.ObscureInput {
return nil, fmt.Errorf("argument \"obscure_input\" must be set to `true` when \"exposed_to_scripts\" is set to true")
}

for _, iv := range optionMap["value_choices"].([]interface{}) {
Expand Down
221 changes: 215 additions & 6 deletions rundeck/resource_job_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ func TestAccJob_cmd_nodefilter(t *testing.T) {
},
})
}
func TestAccJob_cmd_orchestrator(t *testing.T) {

func TestOchestrator_high_low(t *testing.T) {
var job JobDetail

resource.Test(t, resource.TestCase{
Expand All @@ -78,18 +79,70 @@ func TestAccJob_cmd_orchestrator(t *testing.T) {
CheckDestroy: testAccJobCheckDestroy(&job),
Steps: []resource.TestStep{
{
Config: testAccJobConfig_cmd_nodefilter,
Config: testOchestration_high_low,
Check: resource.ComposeTestCheckFunc(
testAccJobCheckExists("rundeck_job.test", &job),
func(s *terraform.State) error {
if expected := "subset"; job.Orchestrator.Type != expected {
return fmt.Errorf("wrong subset; expected %v, got %v", expected, job.Orchestrator.Type)
if expected := "basic-job-with-node-filter"; job.Name != expected {
return fmt.Errorf("wrong name; expected %v, got %v", expected, job.Name)
}
if expected := "name: tacobell"; job.CommandSequence.Commands[0].Job.NodeFilter.Query != expected {
return fmt.Errorf("failed to set job node filter; expected %v, got %v", expected, job.CommandSequence.Commands[0].Job.NodeFilter.Query)
}
return nil
},
),
},
},
})
}

func TestOchestrator_max_percent(t *testing.T) {
var job JobDetail

if expected := 1; job.Orchestrator.Config.Count != expected {
return fmt.Errorf("wrong subset count; expected %v, got %v", expected, job.Orchestrator.Config.Count)
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccJobCheckDestroy(&job),
Steps: []resource.TestStep{
{
Config: testOchestration_maxperecent,
Check: resource.ComposeTestCheckFunc(
testAccJobCheckExists("rundeck_job.test", &job),
func(s *terraform.State) error {
if expected := "basic-job-with-node-filter"; job.Name != expected {
return fmt.Errorf("wrong name; expected %v, got %v", expected, job.Name)
}
if expected := "name: tacobell"; job.CommandSequence.Commands[0].Job.NodeFilter.Query != expected {
return fmt.Errorf("failed to set job node filter; expected %v, got %v", expected, job.CommandSequence.Commands[0].Job.NodeFilter.Query)
}
return nil
},
),
},
},
})
}

func TestOchestrator_rankTiered(t *testing.T) {
var job JobDetail

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccJobCheckDestroy(&job),
Steps: []resource.TestStep{
{
Config: testOchestration_rank_tiered,
Check: resource.ComposeTestCheckFunc(
testAccJobCheckExists("rundeck_job.test", &job),
func(s *terraform.State) error {
if expected := "basic-job-with-node-filter"; job.Name != expected {
return fmt.Errorf("wrong name; expected %v, got %v", expected, job.Name)
}
if expected := "name: tacobell"; job.CommandSequence.Commands[0].Job.NodeFilter.Query != expected {
return fmt.Errorf("failed to set job node filter; expected %v, got %v", expected, job.CommandSequence.Commands[0].Job.NodeFilter.Query)
}
return nil
},
),
Expand Down Expand Up @@ -499,3 +552,159 @@ resource "rundeck_job" "test" {
}
}
`

const testOchestration_maxperecent = `
resource "rundeck_project" "test" {
name = "terraform-acc-test-job"
description = "parent project for job acceptance tests"
resource_model_source {
type = "file"
config = {
format = "resourcexml"
file = "/tmp/terraform-acc-tests.xml"
}
}
}
resource "rundeck_job" "test" {
project_name = "${rundeck_project.test.name}"
name = "orchestrator-MaxPercent"
description = "A basic job"
execution_enabled = true
node_filter_query = "example"
allow_concurrent_executions = true
nodes_selected_by_default = false
max_thread_count = 1
rank_order = "ascending"
schedule = "0 0 12 * * * *"
schedule_enabled = true
option {
name = "foo"
default_value = "bar"
}
orchestrator {
type = "maxPercentage"
percent = 10
}
command {
job {
name = "Other Job Name"
run_for_each_node = true
node_filters {
filter = "name: tacobell"
}
}
description = "Prints Hello World"
}
notification {
type = "on_success"
email {
recipients = ["[email protected]"]
}
}
}
`

const testOchestration_high_low = `
resource "rundeck_project" "test" {
name = "terraform-acc-test-job"
description = "parent project for job acceptance tests"
resource_model_source {
type = "file"
config = {
format = "resourcexml"
file = "/tmp/terraform-acc-tests.xml"
}
}
}
resource "rundeck_job" "test" {
project_name = "${rundeck_project.test.name}"
name = "orchestrator-High-Low"
description = "A basic job"
execution_enabled = true
node_filter_query = "example"
allow_concurrent_executions = true
nodes_selected_by_default = false
max_thread_count = 1
rank_order = "ascending"
schedule = "0 0 12 * * * *"
schedule_enabled = true
option {
name = "foo"
default_value = "bar"
}
orchestrator {
type = "orchestrator-highest-lowest-attribute"
sort = "highest"
attribute = "my-attribute"
}
command {
job {
name = "Other Job Name"
run_for_each_node = true
node_filters {
filter = "name: tacobell"
}
}
description = "Prints Hello World"
}
notification {
type = "on_success"
email {
recipients = ["[email protected]"]
}
}
}
`

const testOchestration_rank_tiered = `
resource "rundeck_project" "test" {
name = "terraform-acc-test-job"
description = "parent project for job acceptance tests"
resource_model_source {
type = "file"
config = {
format = "resourcexml"
file = "/tmp/terraform-acc-tests.xml"
}
}
}
resource "rundeck_job" "test" {
project_name = "${rundeck_project.test.name}"
name = "basic-job-with-node-filter"
description = "A basic job"
execution_enabled = true
node_filter_query = "example"
allow_concurrent_executions = true
nodes_selected_by_default = false
max_thread_count = 1
rank_order = "ascending"
schedule = "0 0 12 * * * *"
schedule_enabled = true
option {
name = "foo"
default_value = "bar"
}
orchestrator {
type = "rankTiered"
}
command {
job {
name = "Other Job Name"
run_for_each_node = true
node_filters {
filter = "name: tacobell"
}
}
description = "Prints Hello World"
}
notification {
type = "on_success"
email {
recipients = ["[email protected]"]
}
}
}
`
Binary file added terraform-provider-rundeck
Binary file not shown.
7 changes: 6 additions & 1 deletion website/docs/r/job.html.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,12 @@ The following arguments are supported:

* `schedule` - (Optional) The job's schedule in Quartz schedule cron format. Similar to unix crontab, but with seven fields instead of five: Second Minute Hour Day-of-Month Month Day-of-Week Year

* `orchestrator` - (Optional) The orchestrator for the job, described below.
* `orchestrator` - (Optional) The orchestrator for the job, described below and [here](https://docs.rundeck.com/docs/manual/orchestrator-plugins/bundled.html)
- `type`: Must be one of `subset`, `rankTiered`, `maxPercentage`, `orchestrator-highest-lowest-attribute`
- `count`: Required when types is `subset`. Selects that max number of the target nodes at random to process.
- `percent`: Required when type is `maxPercentage`. Used to determine max percentage of the nodes to process at once.
- `attribute`: Required when type is `orchestrator-highest-lowest-attribute`. The node attribute to use for sorting.
- `sort`:Required when type is `orchestrator-highest-lowest-attribute`. Values accepted are `highest` or `lowest`.

* `schedule_enabled` - (Optional) Sets the job schedule to be enabled or disabled. Defaults to `true`.

Expand Down

0 comments on commit 4d311dc

Please sign in to comment.