Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scale service roles at cardinality changes #554

Merged
merged 1 commit into from
Oct 17, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ FEATURES:
* resources/opennebula_virtual_machine: add nil checks before type casting (#530)
* resources/opennebula_virtual_router_nic: add floating_only nic argument (#547)
* resources/opennebula_virtual_machine: add method, gateway, dns arguments for nics (#548)
* resources/opennebula_service: add service role scaling (#553)

ENHANCEMENTS:

Expand Down
85 changes: 82 additions & 3 deletions opennebula/resource_opennebula_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,15 @@ package opennebula

import (
"context"
"encoding/json"
"fmt"
"log"
"strconv"
"strings"
"time"

ver "github.com/hashicorp/go-version"

"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
Expand All @@ -31,6 +34,7 @@ func resourceOpennebulaService() *schema.Resource {
Timeouts: &schema.ResourceTimeout{
Create: schema.DefaultTimeout(defaultServiceTimeout),
Delete: schema.DefaultTimeout(defaultServiceTimeout),
Update: schema.DefaultTimeout(defaultServiceTimeout),
},
Importer: &schema.ResourceImporter{
StateContext: schema.ImportStatePassthroughContext,
Expand All @@ -52,7 +56,7 @@ func resourceOpennebulaService() *schema.Resource {
"extra_template": {
Type: schema.TypeString,
Optional: true,
ForceNew: true,
ForceNew: false,
Description: "Extra template information in json format to be added to the service template during instantiate.",
},
"permissions": {
Expand Down Expand Up @@ -587,6 +591,81 @@ func resourceOpennebulaServiceUpdate(ctx context.Context, d *schema.ResourceData
log.Printf("[INFO] Successfully updated owner for Service %s\n", service.Name)
}

if d.HasChange("extra_template") {
extra_template := make(map[string]interface{})
if v, ok := d.GetOk("extra_template"); ok {
if err := json.Unmarshal([]byte(v.(string)), &extra_template); err != nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "Failed to parse extra template",
Detail: fmt.Sprintf("service (ID: %s): %s", d.Id(), err),
})
return diags
}
}

type roleDesc struct {
name string
oldCardinality int
newCardinality int
wantScale bool
}

desc := []roleDesc{}

for _, role := range service.Template.Body.Roles {
desc = append(desc, roleDesc{
name: role.Name,
oldCardinality: role.Cardinality,
})
}

if roles, ok := extra_template["roles"]; ok {
for k := 0; k < len(desc) && k < len(roles.([]interface{})); k++ {
if v, ok := roles.([]interface{})[k].(map[string]interface{})["cardinality"]; ok {
desc[k].newCardinality = int(v.(float64))
desc[k].wantScale = desc[k].newCardinality != desc[k].oldCardinality
}
}
}

minVersion, _ := ver.NewVersion("6.8.0")

for _, v := range desc {
if v.wantScale {
if config.OneVersion.LessThan(minVersion) {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "Role scaling is unsupported for this environment",
Detail: fmt.Sprintf("service (ID: %s): %s", d.Id(), err),
})
return diags
}

if err := sc.Scale(v.name, v.newCardinality, false); err != nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "Failed to scale role",
Detail: fmt.Sprintf("service (ID: %s): %s", d.Id(), err),
})
return diags
}

timeout := d.Timeout(schema.TimeoutUpdate)
if _, err := waitForServiceState(ctx, d, meta, "running", timeout); err != nil {
diags = append(diags, diag.Diagnostic{
Severity: diag.Error,
Summary: "Failed to wait service to be in RUNNING state",
Detail: fmt.Sprintf("service (ID: %s): %s", d.Id(), err),
})
return diags
}
}
}

log.Printf("[INFO] Successfully scaled roles of Service %s\n", service.Name)
}

return resourceOpennebulaServiceRead(ctx, d, meta)
}

Expand Down Expand Up @@ -680,7 +759,8 @@ func waitForServiceState(ctx context.Context, d *schema.ResourceData, meta inter
log.Printf("Waiting for Service (%s) to be in state %s", d.Id(), state)

stateConf := &resource.StateChangeConf{
Pending: []string{"anythingelse"}, Target: []string{state},
Pending: []string{"anythingelse", "cooldown"},
Target: []string{state},
Refresh: func() (interface{}, string, error) {
log.Println("Refreshing Service state...")
if d.Id() != "" {
Expand Down Expand Up @@ -734,5 +814,4 @@ func waitForServiceState(ctx context.Context, d *schema.ResourceData, meta inter
}

return stateConf.WaitForStateContext(ctx)

}
148 changes: 148 additions & 0 deletions opennebula/resource_opennebula_service_scale_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
package opennebula

import (
"context"
"testing"

ver "github.com/hashicorp/go-version"

"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"
)

// NOTE: OneFlow role template merging is unavailable in OpenNebula releases prior to 6.8.0.
func preCheck(t *testing.T) {
testAccPreCheck(t)

if err := testAccProvider.Configure(context.Background(), terraform.NewResourceConfigRaw(nil)); err != nil {
t.Fatal(err)
}

config := testAccProvider.Meta().(*Configuration)

minVersion, _ := ver.NewVersion("6.8.0")

if config.OneVersion.LessThan(minVersion) {
t.Skip()
}
}

func TestAccServiceScale(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { preCheck(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
{
Config: testAccServiceScaleConfigBasic,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("opennebula_service.test", "name", "service-scale-test-tf"),
resource.TestCheckResourceAttr("opennebula_service.test", "roles.0.cardinality", "0"),
resource.TestCheckResourceAttr("opennebula_service.test", "roles.1.cardinality", "0"),
resource.TestCheckResourceAttrSet("opennebula_service.test", "state"),
resource.TestCheckResourceAttrSet("opennebula_service.test", "template_id"),
),
},
{
Config: testAccServiceScaleConfigUpdate,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("opennebula_service.test", "name", "service-scale-test-tf"),
resource.TestCheckResourceAttr("opennebula_service.test", "roles.0.cardinality", "1"),
resource.TestCheckResourceAttr("opennebula_service.test", "roles.1.cardinality", "1"),
resource.TestCheckResourceAttrSet("opennebula_service.test", "state"),
resource.TestCheckResourceAttrSet("opennebula_service.test", "template_id"),
),
},
},
})
}

var testAccServiceScaleVMTemplate = `

resource "opennebula_template" "test" {
name = "service-scale-test-tf"

cpu = 1
vcpu = 1
memory = 64

graphics {
keymap = "en-us"
listen = "0.0.0.0"
type = "VNC"
}

os {
arch = "x86_64"
boot = ""
}
}
`

var testAccServiceScaleTemplate = `

resource "opennebula_service_template" "test" {
name = "service-scale-test-tf"
template = jsonencode({
TEMPLATE = {
BODY = {
name = "service"
deployment = "straight"
roles = [
{
name = "role0"
cooldown = 5 # seconds
vm_template = tonumber(opennebula_template.test.id)
},
{
name = "role1"
parents = ["role0"]
cooldown = 5 # seconds
vm_template = tonumber(opennebula_template.test.id)
},
]
}
}
})
lifecycle {
ignore_changes = all
}
}
`

var testAccServiceScaleConfigBasic = testAccServiceScaleVMTemplate + testAccServiceScaleTemplate + `

resource "opennebula_service" "test" {
name = "service-scale-test-tf"
template_id = opennebula_service_template.test.id
extra_template = jsonencode({
roles = [
{ cardinality = 0 },
{ cardinality = 0 },
]
})
timeouts {
create = "2m"
delete = "2m"
update = "2m"
}
}
`

var testAccServiceScaleConfigUpdate = testAccServiceScaleVMTemplate + testAccServiceScaleTemplate + `

resource "opennebula_service" "test" {
name = "service-scale-test-tf"
template_id = opennebula_service_template.test.id
extra_template = jsonencode({
roles = [
{ cardinality = 1 },
{ cardinality = 1 },
]
})
timeouts {
create = "2m"
delete = "2m"
update = "2m"
}
}
`
Loading