Skip to content

Commit

Permalink
F #553: Scale service roles at cardinality changes
Browse files Browse the repository at this point in the history
The role scale feature is currently supported in OpenNebula >= 6.8.0,
please refer to OpenNebula/one#5591.
  • Loading branch information
sk4zuzu committed Jun 3, 2024
1 parent 2c33564 commit dbbaa99
Show file tree
Hide file tree
Showing 3 changed files with 231 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ FEATURES:
* resources/opennebula_virtual_network: allow to modify the user owning the resource (#529)
* resources/opennebula_virtual_machine: add nil checks before type casting (#530)
* resources/opennebula_virtual_router_nic: add floating_only nic argument (#547)
* 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"
}
}
`

0 comments on commit dbbaa99

Please sign in to comment.