Skip to content

Commit

Permalink
F #553: Scale service roles at cardinality changes
Browse files Browse the repository at this point in the history
  • Loading branch information
sk4zuzu committed May 30, 2024
1 parent 099209b commit 6c5ac8e
Show file tree
Hide file tree
Showing 3 changed files with 167 additions and 98 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
72 changes: 69 additions & 3 deletions opennebula/resource_opennebula_service.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package opennebula

import (
"context"
"encoding/json"
"fmt"
"log"
"strconv"
Expand Down Expand Up @@ -31,6 +32,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 +54,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 +589,70 @@ 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
}
}
}

for _, v := range desc {
if v.wantScale {
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 +746,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 +801,4 @@ func waitForServiceState(ctx context.Context, d *schema.ResourceData, meta inter
}

return stateConf.WaitForStateContext(ctx)

}
192 changes: 97 additions & 95 deletions opennebula/resource_opennebula_service_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,31 +9,26 @@ import (
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/resource"
"github.com/hashicorp/terraform-plugin-sdk/v2/terraform"

srv_tmpl "github.com/OpenNebula/one/src/oca/go/src/goca/schemas/service_template"
"github.com/OpenNebula/one/src/oca/go/src/goca/schemas/shared"
"github.com/OpenNebula/one/src/oca/go/src/goca/schemas/vm"
vmkeys "github.com/OpenNebula/one/src/oca/go/src/goca/schemas/vm/keys"
)

func TestAccService(t *testing.T) {
service_template_id, vm_template_id, _ := setUpServiceTests()
service_template := testAccServiceConfigBasic(service_template_id)
service_template_update := testAccServiceConfigUpdate(service_template_id)

resource.Test(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
Providers: testAccProviders,
CheckDestroy: testAccCheckServiceDestroy,
Steps: []resource.TestStep{
{
Config: service_template,
Config: testAccServiceConfigBasic,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("opennebula_service.test", "name", "service-test-tf"),
resource.TestCheckResourceAttr("opennebula_service.test", "permissions", "642"),
resource.TestCheckResourceAttrSet("opennebula_service.test", "uid"),
resource.TestCheckResourceAttrSet("opennebula_service.test", "gid"),
resource.TestCheckResourceAttrSet("opennebula_service.test", "uname"),
resource.TestCheckResourceAttrSet("opennebula_service.test", "gname"),
resource.TestCheckResourceAttr("opennebula_service.test", "uid", "1"),
resource.TestCheckResourceAttr("opennebula_service.test", "gid", "1"),
resource.TestCheckResourceAttr("opennebula_service.test", "uname", "serveradmin"),
resource.TestCheckResourceAttr("opennebula_service.test", "gname", "users"),
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"),
testAccCheckServicePermissions(&shared.Permissions{
Expand All @@ -45,23 +40,23 @@ func TestAccService(t *testing.T) {
),
},
{
Config: service_template_update,
Config: testAccServiceConfigUpdate,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("opennebula_service.test", "name", "service-test-tf-renamed"),
resource.TestCheckResourceAttr("opennebula_service.test", "permissions", "777"),
resource.TestCheckResourceAttr("opennebula_service.test", "uid", "1"),
resource.TestCheckResourceAttr("opennebula_service.test", "gid", "1"),
resource.TestCheckResourceAttr("opennebula_service.test", "uname", "serveradmin"),
resource.TestCheckResourceAttr("opennebula_service.test", "gname", "users"),
resource.TestCheckResourceAttr("opennebula_service.test", "uid", "0"),
resource.TestCheckResourceAttr("opennebula_service.test", "gid", "0"),
resource.TestCheckResourceAttr("opennebula_service.test", "uname", "oneadmin"),
resource.TestCheckResourceAttr("opennebula_service.test", "gname", "oneadmin"),
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"),
testAccCheckServicePermissions(&shared.Permissions{1, 1, 1, 1, 1, 1, 1, 1, 1}),
),
},
},
})

tearDownServiceTests(service_template_id, vm_template_id)
}

func testAccCheckServiceDestroy(s *terraform.State) error {
Expand Down Expand Up @@ -93,9 +88,12 @@ func testAccCheckServicePermissions(expected *shared.Permissions) resource.TestC
controller := config.Controller

for _, rs := range s.RootModule().Resources {
serviceID, _ := strconv.ParseUint(rs.Primary.ID, 10, 0)
sc := controller.Service(int(serviceID))
// Get Service
if rs.Type != "opennebula_service" {
continue
}
svID, _ := strconv.ParseUint(rs.Primary.ID, 10, 0)
sc := controller.Service(int(svID))
// Get Service Info
service, _ := sc.Info()
if service == nil {
return fmt.Errorf("Expected service %s to exist when checking permissions", rs.Primary.ID)
Expand All @@ -115,84 +113,88 @@ func testAccCheckServicePermissions(expected *shared.Permissions) resource.TestC
}
}

func setUpServiceTests() (int, int, error) {
config := testAccProvider.Meta().(*Configuration)
controller := config.Controller
var testAccServiceVMTemplate = `
templateName := "tf-test-template-service"
resource "opennebula_template" "test" {
name = "service-test-tf"
permissions = "777"
// Create template
tpl := vm.NewTemplate()
tpl.Add(vmkeys.Name, templateName)
tpl.CPU(1).Memory(64)
cpu = 1
vcpu = 1
memory = 64
vmtmpl_id, err := controller.Templates().Create(tpl.String())
if err != nil {
return -1, -1, fmt.Errorf("Error creating VM template")
}
graphics {
keymap = "en-us"
listen = "0.0.0.0"
type = "VNC"
}
tmpl := srv_tmpl.ServiceTemplate{
Template: srv_tmpl.Template{
Body: srv_tmpl.Body{
Name: "NewTemplateTest",
Deployment: "straight",
Roles: []srv_tmpl.Role{
{
Name: "master",
Cardinality: 1,
VMTemplate: vmtmpl_id,
MinVMs: 1,
},
},
},
},
}

err = controller.STemplates().Create(&tmpl)
if err != nil {
return -1, -1, fmt.Errorf("Error creating service template")
}

return tmpl.ID, vmtmpl_id, nil
os {
arch = "x86_64"
boot = ""
}
}

func tearDownServiceTests(sv_tmpl, vm_tmpl int) error {
config := testAccProvider.Meta().(*Configuration)
controller := config.Controller

err := controller.Template(vm_tmpl).Delete()
if err != nil {
return fmt.Errorf("Error deleting VM template")
}

err = controller.STemplate(sv_tmpl).Delete()
if err != nil {
return fmt.Errorf("Error deleting service template")
}

return nil
`

var testAccServiceTemplate = `
resource "opennebula_service_template" "test" {
name = "service-test-tf"
permissions = "777"
template = jsonencode({
TEMPLATE = {
BODY = {
name = "service"
deployment = "straight"
roles = [
{
name = "role0"
cooldown = 5
vm_template = tonumber(opennebula_template.test.id)
},
{
name = "role1"
parents = ["role0"]
cooldown = 5
vm_template = tonumber(opennebula_template.test.id)
},
]
}
}
})
}

func testAccServiceConfigBasic(tmpl_id int) string {
config := "resource \"opennebula_service\" \"test\" {\n" +
" name = \"service-test-tf\"\n" +
" template_id = " + strconv.Itoa(tmpl_id) + "\n" +
" permissions = \"642\"\n" +
" uid = 0\n" +
" gid = 0\n" +
"}\n"

return config
`

var testAccServiceConfigBasic = testAccServiceVMTemplate + testAccServiceTemplate + `
resource "opennebula_service" "test" {
name = "service-test-tf"
template_id = opennebula_service_template.test.id
permissions = "642"
uid = 1
gid = 1
extra_template = jsonencode({
roles = [
{ cardinality = 0 },
{ cardinality = 0 },
]
})
}

func testAccServiceConfigUpdate(tmpl_id int) string {
config := "resource \"opennebula_service\" \"test\" {\n" +
" name = \"service-test-tf-renamed\"\n" +
" template_id = " + strconv.Itoa(tmpl_id) + "\n" +
" permissions = \"777\"\n" +
" uid = 1\n" +
" gid = 1\n" +
"}\n"

return config
`

var testAccServiceConfigUpdate = testAccServiceVMTemplate + testAccServiceTemplate + `
resource "opennebula_service" "test" {
name = "service-test-tf-renamed"
template_id = opennebula_service_template.test.id
permissions = "777"
uid = 0
gid = 0
extra_template = jsonencode({
roles = [
{ cardinality = 1 },
{ cardinality = 1 },
]
})
}
`

0 comments on commit 6c5ac8e

Please sign in to comment.