Skip to content

Commit

Permalink
Merge pull request #131 from citrix/concurrent_save
Browse files Browse the repository at this point in the history
Concurrent save
  • Loading branch information
George Nikolopoulos authored Jan 22, 2021
2 parents f64fac2 + 78107db commit c081b10
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 18 deletions.
104 changes: 98 additions & 6 deletions citrixadc/resource_citrixadc_nsconfig_save.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,12 @@ import (
"github.com/chiradeep/go-nitro/config/ns"

"github.com/chiradeep/go-nitro/netscaler"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/helper/schema"

"log"
"strings"
"time"
)

func resourceCitrixAdcNsconfigSave() *schema.Resource {
Expand All @@ -26,26 +29,115 @@ func resourceCitrixAdcNsconfigSave() *schema.Resource {
Required: true,
ForceNew: true,
},
"concurrent_save_ok": &schema.Schema{
Type: schema.TypeBool,
Optional: true,
Default: true,
ForceNew: true,
},
"concurrent_save_retries": &schema.Schema{
Type: schema.TypeInt,
Optional: true,
Default: 0,
ForceNew: true,
},
"concurrent_save_timeout": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "5m",
ForceNew: true,
},
"concurrent_save_interval": &schema.Schema{
Type: schema.TypeString,
Optional: true,
Default: "10s",
ForceNew: true,
},
},
}
}

func createNsconfigSaveFunc(d *schema.ResourceData, meta interface{}) error {
log.Printf("[DEBUG] citrixadc-provider: In createNsconfigSaveFunc")
client := meta.(*NetScalerNitroClient).client

timestamp := d.Get("timestamp").(string)
log.Printf("[DEBUG] citrixadc-provider: timestamp %s", timestamp)

nsconfig := ns.Nsconfig{
All: d.Get("all").(bool),
}
err := doSaveConfig(d, meta)

err := client.ActOnResource(netscaler.Nsconfig.Type(), &nsconfig, "save")
if err != nil {
return err
if !strings.Contains(err.Error(), "\"errorcode\": 293") {
return err
}
// Fallthrough

// Check concurrent save flag
if !d.Get("concurrent_save_ok").(bool) {
return err
}
// Fallthrough

concurrentSaveRetries := d.Get("concurrent_save_retries").(int)

// Do retries only when it is a non zero value
if concurrentSaveRetries > 0 {

// Do retries
var concurrent_save_interval time.Duration
if concurrent_save_interval, err = time.ParseDuration(d.Get("concurrent_save_interval").(string)); err != nil {
return err
}

var concurrent_save_timeout time.Duration
if concurrent_save_timeout, err = time.ParseDuration(d.Get("concurrent_save_timeout").(string)); err != nil {
return err
}
stateConf := &resource.StateChangeConf{
Pending: []string{"saving"},
Target: []string{"saved"},
Refresh: saveConfigPoll(d, meta),
PollInterval: concurrent_save_interval,
Delay: concurrent_save_interval,
Timeout: concurrent_save_timeout,
NotFoundChecks: concurrentSaveRetries,
}

_, err = stateConf.WaitForState()
if err != nil {
return err
}
}
}

d.SetId(timestamp)

return nil
}

func doSaveConfig(d *schema.ResourceData, meta interface{}) error {
log.Printf("[DEBUG] citrixadc-provider: In doSaveConfig")
client := meta.(*NetScalerNitroClient).client

nsconfig := ns.Nsconfig{
All: d.Get("all").(bool),
}

err := client.ActOnResource(netscaler.Nsconfig.Type(), &nsconfig, "save")
return err
}

func saveConfigPoll(d *schema.ResourceData, meta interface{}) resource.StateRefreshFunc {
return func() (interface{}, string, error) {
log.Printf("[DEBUG] citrixadc-provider: In saveConfigPoll")
err := doSaveConfig(d, meta)
if err != nil {
if strings.Contains(err.Error(), "\"errorcode\": 293") {
return nil, "saving", nil
} else {
return nil, "saving", err
}
} else {
return "saved", "saved", nil
}
}
}
113 changes: 113 additions & 0 deletions citrixadc/resource_citrixadc_nsconfig_save_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,13 @@ package citrixadc

import (
"fmt"
"os"
"testing"
"time"

"github.com/chiradeep/go-nitro/config/lb"
"github.com/chiradeep/go-nitro/config/ns"
"github.com/chiradeep/go-nitro/netscaler"
"github.com/hashicorp/terraform/helper/resource"
"github.com/hashicorp/terraform/terraform"
)
Expand All @@ -45,6 +50,26 @@ func TestAccNsconfigSave_basic(t *testing.T) {
})
}

const testAccNsconfigSave_save_race_no_retry = `
resource "citrixadc_nsconfig_save" "foo" {
# all = true # "all" attribute not present in 12.0
timestamp = "2020-03-24T12:37:06Z"
concurrent_save_ok = true
concurrent_save_retries = 0
}
`

const testAccNsconfigSave_save_race_retry = `
resource "citrixadc_nsconfig_save" "foo" {
# all = true # "all" attribute not present in 12.0
timestamp = "2020-03-24T12:37:06Z"
concurrent_save_ok = true
concurrent_save_retries = 1
}
`

func testAccCheckNsconfigSaveExist(n string, id *string) resource.TestCheckFunc {
return func(s *terraform.State) error {
rs, ok := s.RootModule().Resources[n]
Expand All @@ -66,3 +91,91 @@ func testAccCheckNsconfigSaveExist(n string, id *string) resource.TestCheckFunc
return nil
}
}

func TestAccNsconfigSave_save_race_no_retry(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccSaveRaceSetup(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccNsconfigSave_save_race_no_retry,
Check: resource.ComposeTestCheckFunc(
testAccCheckNsconfigSaveExist("citrixadc_nsconfig_save.foo", nil),
),
},
},
})
}

func TestAccNsconfigSave_save_race_retry(t *testing.T) {
resource.Test(t, resource.TestCase{
PreCheck: func() { testAccSaveRaceSetup(t) },
Providers: testAccProviders,
Steps: []resource.TestStep{
resource.TestStep{
Config: testAccNsconfigSave_save_race_retry,
Check: resource.ComposeTestCheckFunc(
testAccCheckNsconfigSaveExist("citrixadc_nsconfig_save.foo", nil),
),
},
},
})
}

func testAccSaveRaceSetup(t *testing.T) {
// Run Save config concurrently
url := os.Getenv("NS_URL")
password := os.Getenv("NS_PASSWORD")
params := netscaler.NitroParams{
Url: url,
Username: "nsroot",
Password: password,
SslVerify: false,
}
client, err := netscaler.NewNitroClientFromParams(params)
if err != nil {
t.Fatalf("extra client instantiation error: %s", err.Error())
}

lbvserverName := "tf_acc_saveconfig_race_lb"
create_lb_vserver := func() {
lbvserver := lb.Lbvserver{
Name: lbvserverName,
Ipv46: "10.3.4.25",
Port: 80,
Servicetype: "HTTP",
}
_, err := client.AddResource("lbvserver", lbvserverName, &lbvserver)
if err != nil {
t.Fatalf("Error creating race lb vserver: %s", err.Error())
}
}

remove_lb_vserver := func() {
err := client.DeleteResource("lbvserver", lbvserverName)
if err != nil {
t.Fatalf("Error deletin race lb vserver: %s", err.Error())
}
}

do_save := func() {
t.Log("start of save")
nsconfig := ns.Nsconfig{}
err := client.ActOnResource("nsconfig", &nsconfig, "save")
if err != nil {
t.Fatalf("do_save error: %s", err.Error())
}
t.Log("end of save")
}
// Do some changes to ensure save config is not a NOOP
create_lb_vserver()
remove_lb_vserver()

// Concurrently run the first save config to ensure
// save config from the configuration will raise the NITRO errorcode 293
go do_save()

// Give it 500ms to head start operation
time.Sleep(500 * time.Millisecond)
t.Log("end of setup")
}
3 changes: 3 additions & 0 deletions examples/saveconfig/provider.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
provider "citrixadc" {
endpoint = "http://localhost:8080"
}
16 changes: 16 additions & 0 deletions examples/saveconfig/resources.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
resource "citrixadc_nsconfig_save" "tf_save" {
timestamp = "2020-03-24T12:37:06Z"

# Will not error when save is already in progress
concurrent_save_ok = true

# Set to non zero value to retry the save config operation
# Will throw error if limit is surpassed
concurrent_save_retries = 1

# Time interval between save retries
concurrent_save_interval = "10s"

# Total timeout for all retries
concurrent_save_timeout = "5m"
}
5 changes: 0 additions & 5 deletions tests/provider.tf

This file was deleted.

7 changes: 0 additions & 7 deletions tests/resources.tf

This file was deleted.

0 comments on commit c081b10

Please sign in to comment.