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

Upgrade LavinMQ instances #296

Merged
merged 6 commits into from
Sep 4, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## Unreleased

FEATURES:

* Added support to upgrade LavinMQ instances ([#296](https://github.com/cloudamqp/terraform-provider-cloudamqp/pull/296))

## 1.31.0 (Aug 19, 2024)

FEATURES:
Expand Down
116 changes: 116 additions & 0 deletions api/upgrade_lavinmq.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package api

import (
"fmt"
"log"
"time"
)

// ReadVersions - Read versions LavinMQ can upgrade to
func (api *API) ReadLavinMQVersions(instanceID int) (map[string]any, error) {
var (
data map[string]any
failed map[string]any
path = fmt.Sprintf("api/instances/%d/actions/new-lavinmq-versions", instanceID)
)

log.Printf("[DEBUG] api::upgrade_lavinmq#read_versions path: %s", path)
response, err := api.sling.New().Path(path).Receive(&data, &failed)
if err != nil {
return nil, err
}

switch response.StatusCode {
case 200:
return data, nil
default:
return nil, fmt.Errorf("ReadVersions failed, status: %d, message: %s",
response.StatusCode, failed)
}
}

// UpgradeLavinMQ - Upgrade to latest possible version or a specific available version
func (api *API) UpgradeLavinMQ(instanceID int, new_version string) (string, error) {
log.Printf("[DEBUG] api::upgrade_lavinmq#upgrade_lavinmq instanceID: %d"+
", new_version: %s", instanceID, new_version)

if new_version == "" {
return api.UpgradeToLatestLavinMQVersion(instanceID)
} else {
return api.UpgradeToSpecificLavinMQVersion(instanceID, new_version)
}
}

func (api *API) UpgradeToSpecificLavinMQVersion(instanceID int, version string) (string, error) {
var (
data map[string]any
failed map[string]any
path = fmt.Sprintf("api/instances/%d/actions/upgrade-lavinmq", instanceID)
params = make(map[string]any)
)

params["version"] = version
log.Printf("[DEBUG] api::upgrade_lavinmq#upgrade_to_specific_version path: %s, params: %v",
path, params)
response, err := api.sling.New().Post(path).BodyJSON(params).Receive(&data, &failed)
if err != nil {
return "", err
}

switch response.StatusCode {
case 200:
return api.waitUntilLavinMQUpgraded(instanceID)
default:
return "", fmt.Errorf("upgrade LavinMQ failed, status: %d, message: %s",
response.StatusCode, failed)
}
}

func (api *API) UpgradeToLatestLavinMQVersion(instanceID int) (string, error) {
var (
data map[string]any
failed map[string]any
path = fmt.Sprintf("api/instances/%d/actions/upgrade-lavinmq", instanceID)
)

log.Printf("[DEBUG] api::upgrade_lavinmq#upgrade_to_latest_version path: %s", path)
response, err := api.sling.New().Post(path).Receive(&data, &failed)
if err != nil {
return "", err
}

switch response.StatusCode {
case 200:
return "Already at highest possible version", nil
case 202:
return api.waitUntilLavinMQUpgraded(instanceID)
default:
return "", fmt.Errorf("upgrade LavinMQ failed, status: %d, message: %s",
response.StatusCode, failed)
}
}

func (api *API) waitUntilLavinMQUpgraded(instanceID int) (string, error) {
var (
data []map[string]any
failed map[string]any
path = fmt.Sprintf("api/instances/%d/nodes", instanceID)
)

for {
_, err := api.sling.New().Path(path).Receive(&data, &failed)
if err != nil {
return "", err
}

log.Printf("[DEBUG] api::upgrade_lavinmq#waitUntilUpgraded data: %v", data)
ready := true
for _, node := range data {
ready = ready && node["configured"].(bool)
}
if ready {
return "", nil
}
time.Sleep(10 * time.Second)
}
}
1 change: 1 addition & 0 deletions cloudamqp/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ func Provider(v string, client *http.Client) *schema.Provider {
"cloudamqp_rabbitmq_configuration": resourceRabbitMqConfiguration(),
"cloudamqp_security_firewall": resourceSecurityFirewall(),
"cloudamqp_upgrade_rabbitmq": resourceUpgradeRabbitMQ(),
"cloudamqp_upgrade_lavinmq": resourceUpgradeLavinMQ(),
"cloudamqp_vpc_connect": resourceVpcConnect(),
"cloudamqp_vpc_gcp_peering": resourceVpcGcpPeering(),
"cloudamqp_vpc_peering": resourceVpcPeering(),
Expand Down
65 changes: 65 additions & 0 deletions cloudamqp/resource_cloudamqp_upgrade_lavinmq.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package cloudamqp

import (
"log"
"strconv"

"github.com/cloudamqp/terraform-provider-cloudamqp/api"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

func resourceUpgradeLavinMQ() *schema.Resource {
return &schema.Resource{
Create: resourceUpgradeLavinMQInvoke,
Read: resourceUpgradeLavinMQRead,
Update: resourceUpgradeLavinMQUpdate,
Delete: resourceUpgradeLavinMQRemove,
Schema: map[string]*schema.Schema{
"instance_id": {
Type: schema.TypeInt,
Required: true,
Description: "The CloudAMQP instance identifier",
},
"new_version": {
Type: schema.TypeString,
ForceNew: true,
Optional: true,
Description: "The new version to upgrade to",
},
},
}
}

func resourceUpgradeLavinMQInvoke(d *schema.ResourceData, meta interface{}) error {
var (
api = meta.(*api.API)
instanceID = d.Get("instance_id").(int)
new_version = d.Get("new_version").(string)
)

log.Printf("[DEBUG] - Upgrading LavinMQ instance %d to version %s", instanceID, new_version)
response, err := api.UpgradeLavinMQ(instanceID, new_version)
if err != nil {
return err
}

d.SetId(strconv.Itoa(instanceID))

if len(response) > 0 {
log.Printf("[INFO] - " + response)
}

return nil
}

func resourceUpgradeLavinMQRead(d *schema.ResourceData, meta interface{}) error {
return nil
}

func resourceUpgradeLavinMQUpdate(d *schema.ResourceData, meta interface{}) error {
return nil
}

func resourceUpgradeLavinMQRemove(d *schema.ResourceData, meta interface{}) error {
return nil
}
91 changes: 91 additions & 0 deletions cloudamqp/resource_cloudamqp_upgrade_lavinmq_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
package cloudamqp

import (
"fmt"
"testing"

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

"github.com/cloudamqp/terraform-provider-cloudamqp/cloudamqp/vcr-testing/configuration"
"github.com/cloudamqp/terraform-provider-cloudamqp/cloudamqp/vcr-testing/converter"
)

// TestAccUpgradeLavintMQ: Upgrade LavinMQ to a specific version, from 1.3.1 -> 2.0.0-rc.3
// Extra checks are needed when comparing versions, because next step is executed before backend
// have been updated.
func TestAccUpgradeLavinMQ(t *testing.T) {
var (
fileNames = []string{"instance_with_version", "data_source/nodes"}
instanceResourceName = "cloudamqp_instance.instance"
dataSourceNodesName = "data.cloudamqp_nodes.nodes"
plan = "wolverine-1"

params = map[string]string{
"InstanceName": "TestAccUpgradeLavinMQ",
"InstanceTags": converter.CommaStringArray([]string{"terraform"}),
"InstanceID": fmt.Sprintf("%s.id", instanceResourceName),
"InstanceRmqVersion": "1.3.1",
"InstancePlan": plan,
}

fileNamesUpgrade = []string{"instance", "data_source/nodes", "upgrade_lavinmq"}

paramsUpgrade01 = map[string]string{
"InstanceName": "TestAccUpgradeLavinMQ",
"InstanceTags": converter.CommaStringArray([]string{"terraform"}),
"InstanceID": fmt.Sprintf("%s.id", instanceResourceName),
"UpgradeLavinMQNewVersion": "2.0.0-rc.3",
"InstancePlan": plan,
}

fileNamesCheckUpgrade = []string{"instance", "data_source/nodes"}
paramsCheck = map[string]string{
"InstanceName": "TestAccUpgradeLavinMQ",
"InstanceTags": converter.CommaStringArray([]string{"terraform"}),
"InstanceID": fmt.Sprintf("%s.id", instanceResourceName),
"InstancePlan": plan,
}
)

cloudamqpResourceTest(t, resource.TestCase{
PreCheck: func() { testAccPreCheck(t) },
ProviderFactories: testAccProviderFactory,
Steps: []resource.TestStep{
{
Config: configuration.GetTemplatedConfig(t, fileNames, params),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(instanceResourceName, "name", params["InstanceName"]),
resource.TestCheckResourceAttr(instanceResourceName, "plan", "wolverine-1"),
resource.TestCheckResourceAttr(instanceResourceName, "region", "amazon-web-services::us-east-1"),
resource.TestCheckResourceAttr(instanceResourceName, "rmq_version", params["InstanceRmqVersion"]),
resource.TestCheckResourceAttr(instanceResourceName, "tags.#", "1"),
resource.TestCheckResourceAttr(instanceResourceName, "tags.0", "terraform"),
resource.TestCheckResourceAttr(instanceResourceName, "nodes", "1"),
resource.TestCheckResourceAttr(dataSourceNodesName, "nodes.#", "1"),
resource.TestCheckResourceAttr(dataSourceNodesName, "nodes.0.rabbitmq_version", params["InstanceRmqVersion"]),
),
},
{
Config: configuration.GetTemplatedConfig(t, fileNamesUpgrade, paramsUpgrade01),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(dataSourceNodesName, "nodes.#", "1"),
resource.TestCheckResourceAttr(dataSourceNodesName, "nodes.0.rabbitmq_version", "1.3.1"),
),
},
{
Config: configuration.GetTemplatedConfig(t, fileNamesCheckUpgrade, paramsCheck),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(dataSourceNodesName, "nodes.#", "1"),
resource.TestCheckResourceAttr(dataSourceNodesName, "nodes.0.rabbitmq_version", "2.0.0-rc.3"),
),
},
{
Config: configuration.GetTemplatedConfig(t, fileNamesCheckUpgrade, paramsCheck),
Check: resource.ComposeAggregateTestCheckFunc(
resource.TestCheckResourceAttr(dataSourceNodesName, "nodes.#", "1"),
resource.TestCheckResourceAttr(dataSourceNodesName, "nodes.0.rabbitmq_version", "2.0.0-rc.3"),
),
},
},
})
}
92 changes: 92 additions & 0 deletions docs/resources/upgrade_lavinmq.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
layout: "cloudamqp"
page_title: "CloudAMQP: cloudamqp_upgrade_lavinmq"
description: |-
Invoke upgrade to latest possible upgradable versions for LavinMQ.
---

# cloudamqp_upgrade_lavinmq

This resource allows you to upgrade LavinMQ version.

There is two different ways to trigger the version upgrade

> - Specify LavinMQ version to upgrade to
> - Upgrade to latest LavinMQ version
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@viktorerlingsson Do we want to support upgrade to latest for LavinMQ? Been testing around a bit and will require some more changes to support that. If not we can remove some part of the documentation describing this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, you're right. I removed all text about upgrading to latest and re-phrased some stuff


See, below example usage for the difference.

Only available for dedicated subscription plans running ***LavinMQ***.

## Example Usage

<details>
<summary>
<b>
<i>Specify version upgrade, from v1.31.0</i>
</b>
</summary>

Specify the version to upgrade to. List available upgradable versions, use [CloudAMQP API](https://docs.cloudamqp.com/cloudamqp_api.html#get-available-versions).
After the upgrade finished, there can still be newer versions available.

```hcl
resource "cloudamqp_instance" "instance" {
name = "lavinmq-version-upgrade-test"
plan = "lynx-1"
region = "amazon-web-services::us-west-1"
}

resource "cloudamqp_upgrade_lavinmq" "upgrade" {
instance_id = cloudamqp_instance.instance.id
new_version = "1.3.1"
}
```

</details>

<details>
<summary>
<b>
<i>Upgrade to latest possible version, from v1.31.0</i>
</b>
</summary>

This will upgrade LavinMQ to the latest possible version detected by the data source `cloudamqp_upgradable_versions`.

```hcl
resource "cloudamqp_instance" "instance" {
name = "lavinmq-version-upgrade-test"
plan = "lynx-1"
region = "amazon-web-services::us-west-1"
}

data "cloudamqp_upgradable_versions" "upgradable_versions" {
instance_id = cloudamqp_instance.instance.id
}

resource "cloudamqp_upgrade_lavinmq" "upgrade" {
instance_id = cloudamqp_instance.instance.id
current_version = cloudamqp_instance.instance.rmq_version
new_version = data.cloudamqp_upgradable_versions.upgradable_versions.new_lavinmq_version
}
```

</details>


## Argument Reference

The following arguments are supported:

* `instance_id` - (Required) The CloudAMQP instance identifier
* `new_version` - (Optional/ForceNew) The new version to upgrade to

## Import

Not possible to import this resource.

## Important Upgrade Information

> - All single node upgrades will require some downtime since LavinMQ needs a restart.
> - Auto delete queues (queues that are marked AD) will be deleted during the update.
4 changes: 4 additions & 0 deletions test/configurations/upgrade_lavinmq.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
resource "cloudamqp_upgrade_lavinmq" "upgrade" {
instance_id = {{.InstanceID}}
new_version = "{{.UpgradeLavinMQNewVersion}}"
}
Loading
Loading